mirror of
https://github.com/koush/scrypted.git
synced 2026-05-04 21:30:30 +01:00
mixin improvements, homekit settings
This commit is contained in:
4
plugins/amcrest/package-lock.json
generated
4
plugins/amcrest/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.17",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.17",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@mhoc/axios-digest-auth": "^0.7.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.16",
|
||||
"version": "0.0.17",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card
|
||||
v-for="[key, settingsGroup] of Object.entries(settingsGroups)"
|
||||
v-for="([key, settingsGroup], index) of Object.entries(settingsGroups)"
|
||||
:class="
|
||||
index === Object.entries(settingsGroups).length - 1 ? undefined : 'mb-6'
|
||||
"
|
||||
:key="key"
|
||||
>
|
||||
<v-card-title
|
||||
@@ -9,18 +12,14 @@
|
||||
>{{ key }}</v-card-title
|
||||
>
|
||||
<v-flex xs12>
|
||||
<v-layout>
|
||||
<v-flex xs12>
|
||||
<v-form>
|
||||
<Setting
|
||||
:device="device"
|
||||
v-for="setting in settingsGroup"
|
||||
:key="setting.key"
|
||||
v-model="setting.value"
|
||||
></Setting>
|
||||
</v-form>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
<v-form>
|
||||
<Setting
|
||||
:device="device"
|
||||
v-for="setting in settingsGroup"
|
||||
:key="setting.key"
|
||||
v-model="setting.value"
|
||||
></Setting>
|
||||
</v-form>
|
||||
</v-flex>
|
||||
</v-card>
|
||||
</div>
|
||||
@@ -51,22 +50,22 @@ export default {
|
||||
settingsGroups() {
|
||||
const ret = {};
|
||||
for (const setting of this.settings) {
|
||||
const group = setting.value.group || 'Settings';
|
||||
const group = setting.value.group || "Settings";
|
||||
if (!ret[group]) {
|
||||
ret[group] = [];
|
||||
}
|
||||
ret[group].push(setting);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async refresh() {
|
||||
const blub = this.rpc().getSettings();
|
||||
var settings = await blub;
|
||||
this.settings = this.settings = settings.map(setting => ({
|
||||
this.settings = this.settings = settings.map((setting) => ({
|
||||
key: setting.key,
|
||||
value: setting
|
||||
value: setting,
|
||||
}));
|
||||
},
|
||||
},
|
||||
|
||||
4
plugins/google-device-access/package-lock.json
generated
4
plugins/google-device-access/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/google-device-access",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/google-device-access",
|
||||
"version": "0.0.9",
|
||||
"version": "0.0.10",
|
||||
"dependencies": {
|
||||
"@googleapis/smartdevicemanagement": "^0.2.0",
|
||||
"axios": "^0.21.1",
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"@types/node": "^14.17.11",
|
||||
"@types/url-parse": "^1.4.3"
|
||||
},
|
||||
"version": "0.0.9"
|
||||
"version": "0.0.10"
|
||||
}
|
||||
|
||||
4
plugins/homekit/package-lock.json
generated
4
plugins/homekit/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.15",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/homekit",
|
||||
"version": "0.0.14",
|
||||
"version": "0.0.15",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"hap-nodejs": "file:../HAP-NodeJS",
|
||||
|
||||
@@ -36,5 +36,5 @@
|
||||
"@types/node": "^14.17.9",
|
||||
"@types/url-parse": "^1.4.3"
|
||||
},
|
||||
"version": "0.0.14"
|
||||
"version": "0.0.15"
|
||||
}
|
||||
|
||||
36
plugins/homekit/src/camera-mixin.ts
Normal file
36
plugins/homekit/src/camera-mixin.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { MixinDeviceBase, VideoCamera, Settings, Setting, ScryptedInterface } from "@scrypted/sdk";
|
||||
|
||||
export class CameraMixin extends MixinDeviceBase<VideoCamera & Settings> implements Settings {
|
||||
constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
|
||||
super(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, providerNativeId);
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
const settings = this.mixinDeviceInterfaces.includes(ScryptedInterface.Settings) ?
|
||||
await this.mixinDevice.getSettings() : [];
|
||||
settings.push({
|
||||
group: 'HomeKit Settings',
|
||||
title: 'Transcode Streaming',
|
||||
type: 'boolean',
|
||||
key: 'transcodeStreaming',
|
||||
value: (this.storage.getItem('transcodeStreaming') === 'true').toString(),
|
||||
description: 'Use FFMpeg to transcode streaming to a format supported by HomeKit.',
|
||||
});
|
||||
|
||||
if (this.interfaces.includes(ScryptedInterface.MotionSensor)) {
|
||||
settings.push({
|
||||
group: 'HomeKit Settings',
|
||||
title: 'Transcode Recording',
|
||||
key: 'transcodeRecording',
|
||||
type: 'boolean',
|
||||
value: (this.storage.getItem('transcodeRecording') === 'true').toString(),
|
||||
description: 'Use FFMpeg to transcode recording to a format supported by HomeKit Secure Video.',
|
||||
});
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
async putSetting(key: string, value: string | number | boolean) {
|
||||
this.storage.setItem(key, value.toString());
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import sdk, { Settings, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, Setting } from '@scrypted/sdk';
|
||||
import sdk, { Settings, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, Setting, ScryptedInterface } from '@scrypted/sdk';
|
||||
import { Bridge, Categories, HAPStorage } from './hap';
|
||||
import os from 'os';
|
||||
import { supportedTypes } from './common';
|
||||
import './types'
|
||||
import { CameraMixin } from './camera-mixin';
|
||||
|
||||
const { systemManager, mediaManager } = sdk;
|
||||
|
||||
@@ -70,7 +71,6 @@ class HomeKit extends ScryptedDeviceBase implements MixinProvider, Settings {
|
||||
}
|
||||
|
||||
async start() {
|
||||
|
||||
let defaultIncluded: any;
|
||||
try {
|
||||
defaultIncluded = JSON.parse(this.storage.getItem('defaultIncluded'));
|
||||
@@ -137,10 +137,18 @@ class HomeKit extends ScryptedDeviceBase implements MixinProvider, Settings {
|
||||
})) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === ScryptedDeviceType.Camera) {
|
||||
return [ScryptedInterface.Settings];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
getMixin(device: ScryptedDevice, deviceState: any) {
|
||||
return device;
|
||||
getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }) {
|
||||
if ((mixinDeviceState.type === ScryptedDeviceType.Camera || mixinDeviceState.type === ScryptedDeviceType.Doorbell)
|
||||
&& mixinDeviceInterfaces.includes(ScryptedInterface.Camera)) {
|
||||
return new CameraMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.nativeId);
|
||||
}
|
||||
return mixinDevice;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { CameraRecordingDelegate, CharacteristicEventTypes, CharacteristicValue,
|
||||
import { AudioRecordingCodec, AudioRecordingCodecType, AudioRecordingSamplerate, AudioRecordingSamplerateValues, CameraRecordingConfiguration, CameraRecordingOptions } from '../../HAP-NodeJS/src/lib/camera/RecordingManagement';
|
||||
import { startFFMPegFragmetedMP4Session } from '@scrypted/common/src/ffmpeg-mp4-parser-session';
|
||||
|
||||
const { log, mediaManager } = sdk;
|
||||
const { log, mediaManager, deviceManager } = sdk;
|
||||
|
||||
async function getPort(): Promise<{ socket: dgram.Socket, port: number }> {
|
||||
const socket = dgram.createSocket('udp4');
|
||||
@@ -53,14 +53,26 @@ async function* handleFragmentsRequests(device: ScryptedDevice & VideoCamera & M
|
||||
const level = configuration.videoCodec.level === H264Level.LEVEL4_0 ? '4.0'
|
||||
: configuration.videoCodec.level === H264Level.LEVEL3_2 ? '3.2' : '3.1';
|
||||
|
||||
const videoArgs: string[] = [
|
||||
'-profile:v', profile,
|
||||
'-level:v', level,
|
||||
'-b:v', `${configuration.videoCodec.bitrate}k`,
|
||||
'-force_key_frames', `expr:gte(t,n_forced*${iframeIntervalSeconds})`,
|
||||
'-r', configuration.videoCodec.resolution[2].toString(),
|
||||
'-vf', `scale=w=${configuration.videoCodec.resolution[0]}:h=${configuration.videoCodec.resolution[1]}:force_original_aspect_ratio=1,pad=${configuration.videoCodec.resolution[0]}:${configuration.videoCodec.resolution[1]}:(ow-iw)/2:(oh-ih)/2`,
|
||||
];
|
||||
|
||||
const storage = deviceManager.getMixinStorage(device.id);
|
||||
const transcodeRecording = storage.getItem('transcodeRecording') === 'true';
|
||||
|
||||
let videoArgs: string[];
|
||||
if (transcodeRecording) {
|
||||
videoArgs = [
|
||||
'-profile:v', profile,
|
||||
'-level:v', level,
|
||||
'-b:v', `${configuration.videoCodec.bitrate}k`,
|
||||
'-force_key_frames', `expr:gte(t,n_forced*${iframeIntervalSeconds})`,
|
||||
'-r', configuration.videoCodec.resolution[2].toString(),
|
||||
'-vf', `scale=w=${configuration.videoCodec.resolution[0]}:h=${configuration.videoCodec.resolution[1]}:force_original_aspect_ratio=1,pad=${configuration.videoCodec.resolution[0]}:${configuration.videoCodec.resolution[1]}:(ow-iw)/2:(oh-ih)/2`,
|
||||
];
|
||||
}
|
||||
else {
|
||||
videoArgs = [
|
||||
'-vcodec', 'copy',
|
||||
];
|
||||
}
|
||||
|
||||
log.i(`${device.name} motion recording starting`);
|
||||
const session = await startFFMPegFragmetedMP4Session(ffmpegInput, audioArgs, videoArgs);
|
||||
@@ -230,20 +242,31 @@ addSupportedType({
|
||||
args.push(...ffmpegInput.inputArguments);
|
||||
args.push(
|
||||
"-an", '-sn', '-dn',
|
||||
);
|
||||
|
||||
"-vcodec", "copy",
|
||||
|
||||
// "-vcodec", "libx264",
|
||||
// '-pix_fmt', 'yuvj420p',
|
||||
// "-profile:v", "high",
|
||||
// '-color_range', 'mpeg',
|
||||
// "-bf", "0",
|
||||
// "-b:v", request.video.max_bit_rate.toString() + "k",
|
||||
// "-bufsize", (2 * request.video.max_bit_rate).toString() + "k",
|
||||
// "-maxrate", request.video.max_bit_rate.toString() + "k",
|
||||
// "-filter:v", "fps=fps=" + request.video.fps.toString(),
|
||||
const storage = deviceManager.getMixinStorage(device.id);
|
||||
const transcodeStreaming = storage.getItem('transcodeStreaming') === 'true';
|
||||
|
||||
if (transcodeStreaming) {
|
||||
args.push(
|
||||
"-vcodec", "libx264",
|
||||
'-pix_fmt', 'yuvj420p',
|
||||
"-profile:v", "high",
|
||||
'-color_range', 'mpeg',
|
||||
"-bf", "0",
|
||||
"-b:v", request.video.max_bit_rate.toString() + "k",
|
||||
"-bufsize", (2 * request.video.max_bit_rate).toString() + "k",
|
||||
"-maxrate", request.video.max_bit_rate.toString() + "k",
|
||||
"-filter:v", "fps=fps=" + request.video.fps.toString(),
|
||||
)
|
||||
}
|
||||
else {
|
||||
args.push(
|
||||
"-vcodec", "copy",
|
||||
);
|
||||
}
|
||||
|
||||
args.push(
|
||||
"-payload_type", (request as StartStreamRequest).video.pt.toString(),
|
||||
"-ssrc", session.videossrc.toString(),
|
||||
"-f", "rtp",
|
||||
|
||||
@@ -17,8 +17,8 @@ async function audioFetch(body: string): Promise<string> {
|
||||
var memoizedAudioFetch = memoizeOne(audioFetch);
|
||||
|
||||
class NotifierMixin extends MixinDeviceBase<MediaPlayer> implements Notifier {
|
||||
constructor(mixinDevice: ScryptedDevice & MediaPlayer, deviceState: any) {
|
||||
super(mixinDevice, deviceState);
|
||||
constructor(mixinDevice: MediaPlayer, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
|
||||
super(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, providerNativeId);
|
||||
}
|
||||
|
||||
async sendNotification(title: string, body: string, media: string | MediaObject, mimeType: string): Promise<void> {
|
||||
@@ -53,8 +53,8 @@ class NotifierProvider extends ScryptedDeviceBase implements MixinProvider {
|
||||
return [ScryptedInterface.Notifier];
|
||||
}
|
||||
|
||||
getMixin(device: ScryptedDevice, deviceState: any) {
|
||||
return new NotifierMixin(device as any, deviceState);
|
||||
getMixin(mixinDevice: MediaPlayer, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }) {
|
||||
return new NotifierMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.nativeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ interface Prebuffer {
|
||||
time: number;
|
||||
}
|
||||
|
||||
class PrebufferMixin extends MixinDeviceBase<VideoCamera> implements VideoCamera, Settings {
|
||||
class PrebufferMixin extends MixinDeviceBase<VideoCamera & Settings> implements VideoCamera, Settings {
|
||||
prebufferSession: Promise<FFMpegFragmentedMP4Session>;
|
||||
prebuffer: Prebuffer[] = [];
|
||||
ftyp: MP4Atom;
|
||||
@@ -24,8 +24,8 @@ class PrebufferMixin extends MixinDeviceBase<VideoCamera> implements VideoCamera
|
||||
events = new EventEmitter();
|
||||
released = false;
|
||||
|
||||
constructor(mixinDevice: ScryptedDevice & VideoCamera, deviceState: any, nativeId: string|undefined) {
|
||||
super(mixinDevice, deviceState, nativeId);
|
||||
constructor(mixinDevice: VideoCamera & Settings, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string) {
|
||||
super(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, providerNativeId);
|
||||
|
||||
// to prevent noisy startup/reload/shutdown, delay the prebuffer starting.
|
||||
log.i(`${this.name} prebuffer session starting in 10 seconds`);
|
||||
@@ -33,7 +33,10 @@ class PrebufferMixin extends MixinDeviceBase<VideoCamera> implements VideoCamera
|
||||
}
|
||||
|
||||
async getSettings(): Promise<Setting[]> {
|
||||
return [
|
||||
const settings = this.interfaces.includes(ScryptedInterface.Settings) ?
|
||||
await this.mixinDevice.getSettings() : [];
|
||||
|
||||
settings.push(
|
||||
{
|
||||
title: 'Prebuffer Duration',
|
||||
description: 'Duration of the prebuffer in milliseconds.',
|
||||
@@ -41,7 +44,8 @@ class PrebufferMixin extends MixinDeviceBase<VideoCamera> implements VideoCamera
|
||||
key: PREBUFFER_DURATION_MS,
|
||||
value: this.storage.getItem(PREBUFFER_DURATION_MS) || defaultPrebufferDuration.toString(),
|
||||
}
|
||||
];
|
||||
);
|
||||
return settings;
|
||||
}
|
||||
async putSetting(key: string, value: string | number | boolean): Promise<void> {
|
||||
this.storage.setItem(key, value.toString());
|
||||
@@ -198,8 +202,8 @@ class PrebufferProvider extends ScryptedDeviceBase implements MixinProvider {
|
||||
return [ScryptedInterface.VideoCamera, ScryptedInterface.Settings];
|
||||
}
|
||||
|
||||
getMixin(device: ScryptedDevice, deviceState: any) {
|
||||
return new PrebufferMixin(device as ScryptedDevice & VideoCamera, deviceState, this.nativeId);
|
||||
getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }) {
|
||||
return new PrebufferMixin(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, this.nativeId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
5
sdk/index.d.ts
vendored
5
sdk/index.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
export * from './types'
|
||||
import { ScryptedStatic, ScryptedDeviceType, Logger, ColorRgb, ColorHsv, DeviceState, TemperatureUnit, LockState, ThermostatMode, Position, ScryptedDevice } from './types';
|
||||
import { ScryptedInterface, ScryptedStatic, ScryptedDeviceType, Logger, ColorRgb, ColorHsv, DeviceState, TemperatureUnit, LockState, ThermostatMode, Position, ScryptedDevice } from './types';
|
||||
|
||||
export class ScryptedDeviceBase implements DeviceState {
|
||||
constructor(nativeId?: string);
|
||||
@@ -64,8 +64,9 @@ export class ScryptedDeviceBase implements DeviceState {
|
||||
|
||||
|
||||
export class MixinDeviceBase<T> implements DeviceState {
|
||||
constructor(mixinDevice: T, deviceState: any, providerNativeId: string);
|
||||
constructor(mixinDevice: T, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }, providerNativeId: string);
|
||||
mixinDevice: ScryptedDevice & T;
|
||||
mixinDeviceInterfaces: ScryptedInterface[];
|
||||
console: Console;
|
||||
storage: Storage;
|
||||
id?: string;
|
||||
|
||||
@@ -40,15 +40,17 @@ class ScryptedDeviceBase {
|
||||
}
|
||||
|
||||
class MixinDeviceBase {
|
||||
constructor(mixinDevice, deviceState, providerNativeId) {
|
||||
constructor(mixinDevice, mixinDeviceInterfaces, mixinDeviceState, providerNativeId) {
|
||||
this.mixinDevice = mixinDevice;
|
||||
this._deviceState = deviceState;
|
||||
this.mixinDevice = mixinDevice;
|
||||
this.mixinDeviceInterfaces = mixinDeviceInterfaces;
|
||||
this._deviceState = mixinDeviceState;
|
||||
this.providerNativeId = providerNativeId;
|
||||
}
|
||||
|
||||
get storage() {
|
||||
if (!this._storage) {
|
||||
this._storage = deviceManager.getMixinStorage(this.providerNativeId);
|
||||
this._storage = deviceManager.getMixinStorage(this.id, this.providerNativeId);
|
||||
}
|
||||
return this._storage;
|
||||
}
|
||||
|
||||
2
sdk/types.d.ts
vendored
2
sdk/types.d.ts
vendored
@@ -849,7 +849,7 @@ export interface MixinProvider {
|
||||
/**
|
||||
* Create a mixin that can be applied to the supplied device.
|
||||
*/
|
||||
getMixin(device: ScryptedDevice, deviceState: any): any;
|
||||
getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }): any;
|
||||
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -86,7 +86,7 @@ export class PluginDeviceProxyHandler implements ProxyHandler<any>, ScryptedDevi
|
||||
const host = this.scrypted.getPluginHostForDeviceId(mixinId);
|
||||
const deviceState = await host.remote.createDeviceState(this.id,
|
||||
async (property, value) => this.scrypted.stateManager.setPluginDeviceState(pluginDevice, property, value));
|
||||
const mixinProxy = await mixin.getMixin(wrappedProxy, deviceState);
|
||||
const mixinProxy = await mixin.getMixin(wrappedProxy, allInterfaces, deviceState);
|
||||
if (!mixinProxy)
|
||||
throw new Error(`mixin provider ${mixinId} did not return mixin for ${this.id}`);
|
||||
allInterfaces.push(...interfaces);
|
||||
|
||||
@@ -424,7 +424,7 @@ export function startPluginClusterWorker() {
|
||||
const stderr = new PassThrough();
|
||||
|
||||
stdout.on('data', data => events.emit('stdout', data, nativeId));
|
||||
stderr.on('data', data => this.events.emit('stderr', data, nativeId));
|
||||
stderr.on('data', data => events.emit('stderr', data, nativeId));
|
||||
|
||||
const ret = new Console(stdout, stderr);
|
||||
|
||||
@@ -520,6 +520,5 @@ class LazyRemote implements PluginRemote {
|
||||
|
||||
async getServicePort(name: string): Promise<number> {
|
||||
return (await this.init).getServicePort(name);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user