homekit/unifi: dynamic bitrate support on remote streaming

This commit is contained in:
Koushik Dutta
2022-01-12 22:53:36 -08:00
parent a20dc3ec52
commit b76cb33e6c
6 changed files with 54 additions and 21 deletions

View File

@@ -74,6 +74,7 @@ export class CameraMixin extends SettingsMixinDeviceBase<any> implements Setting
readonly: true,
description: 'Transcoding audio and video for HomeKit is not recommended. Configure your camera using the camera web portal or app to output the correct HomeKit compatible codecs (h264/aac/2000kbps).',
})
settings.push({
title: 'Transcode Streaming',
group: 'HomeKit Transcoding',
@@ -88,7 +89,15 @@ export class CameraMixin extends SettingsMixinDeviceBase<any> implements Setting
type: 'boolean',
key: 'transcodeStreamingHub',
value: (this.storage.getItem('transcodeStreamingHub') === 'true').toString(),
description: 'Use FFMpeg to transcode streaming to a format supported by HomeKit.',
description: 'Remote Viewing through a HomeKit Hub only: Use FFMpeg to transcode streaming to a format supported by HomeKit.',
});
settings.push({
title: 'Dynamic Bitrate (Hub)',
group: 'HomeKit Transcoding',
type: 'boolean',
key: 'dynamicBitrate',
value: (this.storage.getItem('dynamicBitrate') === 'true').toString(),
description: 'Remote Viewing through a HomeKit Hub only: Adjust the bitrate of the native camera stream on demand to accomodate available bandwidth. This setting should be used on secondary streams (sub streams), and not the main stream connected to an NVR, as it will reduce the recording quality.',
});
let showTranscodeArgs = this.storage.getItem('transcodeStreaming') === 'true'
|| this.storage.getItem('transcodeStreamingHub') === 'true';

View File

@@ -186,23 +186,37 @@ addSupportedType({
selectedStream = msos.find(mso => mso.name === streamingChannel);
}
if (request.type === StreamRequestTypes.RECONFIGURE) {
const tryReconfigureBitrate = () => {
if (!isHomeKitHub)
return;
if (!device.interfaces.includes(ScryptedInterface.VideoCameraConfiguration))
return;
const dynamicBitrate = storage.getItem('dynamicBitrate') === 'true';
if (!dynamicBitrate)
return;
const reconfigured = Object.assign({
video: {
},
}, selectedStream || {});
reconfigured.video.bitrate = request.video.max_bit_rate;
const bitrate = request.video.max_bit_rate * 1000;
reconfigured.video.bitrate = bitrate;
reconfigured.video.maxBitrate = bitrate;
device.setVideoStreamOptions(reconfigured);
console.log('reconfigure selected stream', selectedStream);
}
if (request.type === StreamRequestTypes.RECONFIGURE) {
tryReconfigureBitrate();
return;
}
else {
session.startRequest = request as StartStreamRequest;
}
tryReconfigureBitrate();
// watch for data to verify other side is alive.
session.videoReturn.on('data', () => debounce(() => {

View File

@@ -1,15 +1,8 @@
# Unifi Protect Plugin
# Unifi Protect Plugin for Scrypted
## npm commands
* npm run scrypted-webpack
* npm run scrypted-deploy <ipaddress>
* npm run scrypted-debug <ipaddress>
The Unifi Protect Plugin connects your Unifi Cameras to Scrypted. The Protect appliance such as a Cloud Key or Dream Machine is required.
## scrypted distribution via npm
1. Ensure package.json is set up properly for publishing on npm.
2. npm publish
## Unifi Beta 1.21.0-beta.3
## Visual Studio Code configuration
This beta has a bug in it that causes HomeKit Secure Video recordings to fail. Please roll back to a prior or stable release, and flag [this issue](https://community.ui.com/releases/UniFi-Protect-Application-1-21-0-beta-3/32c7bb7a-697d-4841-8b9f-eef49b8682e9#comment/ad1c2710-2451-4612-847f-6413eb8ec0db).
* If using a remote server, edit [.vscode/settings.json](blob/master/.vscode/settings.json) to specify the IP Address of the Scrypted server.
* Launch Scrypted Debugger from the launch menu.

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/unifi-protect",
"version": "0.0.64",
"version": "0.0.66",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/unifi-protect",
"version": "0.0.64",
"version": "0.0.66",
"license": "Apache",
"dependencies": {
"@koush/unifi-protect": "^0.0.6",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/unifi-protect",
"version": "0.0.64",
"version": "0.0.66",
"description": "Unifi Protect Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",

View File

@@ -1,4 +1,4 @@
import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, Device, MotionSensor, ScryptedInterface, Camera, MediaStreamOptions, Intercom, ScryptedMimeTypes, FFMpegInput, ObjectDetector, PictureOptions, ObjectDetectionTypes, ObjectsDetected, ObjectDetectionResult, Notifier, SCRYPTED_MEDIA_SCHEME } from "@scrypted/sdk";
import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, VideoCamera, MediaObject, Device, MotionSensor, ScryptedInterface, Camera, MediaStreamOptions, Intercom, ScryptedMimeTypes, FFMpegInput, ObjectDetector, PictureOptions, ObjectDetectionTypes, ObjectsDetected, ObjectDetectionResult, Notifier, SCRYPTED_MEDIA_SCHEME, VideoCameraConfiguration } from "@scrypted/sdk";
import { ProtectApi, ProtectCameraLcdMessagePayload } from "@koush/unifi-protect";
import { ProtectApiUpdates, ProtectNvrUpdatePayloadCameraUpdate, ProtectNvrUpdatePayloadEventAdd } from "@koush/unifi-protect";
import { ProtectCameraChannelConfig, ProtectCameraConfigInterface } from "@koush/unifi-protect";
@@ -12,7 +12,7 @@ const { log, deviceManager, mediaManager } = sdk;
const defaultSensorTimeout = 30;
class UnifiCamera extends ScryptedDeviceBase implements Camera, VideoCamera, MotionSensor, Settings, ObjectDetector {
class UnifiCamera extends ScryptedDeviceBase implements Camera, VideoCamera, VideoCameraConfiguration, MotionSensor, Settings, ObjectDetector {
protect: UnifiProtect;
motionTimeout: NodeJS.Timeout;
detectionTimeout: NodeJS.Timeout;
@@ -229,6 +229,22 @@ class UnifiCamera extends ScryptedDeviceBase implements Camera, VideoCamera, Mot
return this.getDefaultOrderedVideoStreamOptions(video);
}
async setVideoStreamOptions(options: MediaStreamOptions): Promise<void> {
const bitrate = options?.video?.bitrate;
const maxBitrate = options?.video?.maxBitrate;
if (!bitrate || !maxBitrate)
return;
const camera = this.findCamera();
const channel = camera.channels.find(channel => channel.id === options.id);
channel.bitrate = bitrate;
channel.maxBitrate = maxBitrate;
const cameraResult = await this.protect.api.updateChannels(camera);
if (!cameraResult) {
throw new Error("setVideoStreamOptions failed")
}
}
async getPictureOptions(): Promise<PictureOptions[]> {
return;
}
@@ -245,7 +261,7 @@ class UnifiDoorbell extends UnifiCamera implements Intercom, Notifier {
this.protect.api.updateCamera(this.findCamera(), {
lcdMessage: payload,
})
if (typeof media === 'string' && media.startsWith(SCRYPTED_MEDIA_SCHEME)) {
media = await mediaManager.createMediaObjectFromUrl(media);
}
@@ -294,7 +310,7 @@ class UnifiProtect extends ScryptedDeviceBase implements Settings, DeviceProvide
cameras: Map<string, UnifiCamera> = new Map();
api: ProtectApi;
startup: Promise<void>;
runningEvents = new Map<string, {promise: Promise<unknown>, resolve: (value: unknown) => void}>();
runningEvents = new Map<string, { promise: Promise<unknown>, resolve: (value: unknown) => void }>();
constructor(nativeId?: string, createOnly?: boolean) {
super(nativeId);
@@ -541,6 +557,7 @@ class UnifiProtect extends ScryptedDeviceBase implements Settings, DeviceProvide
ScryptedInterface.Settings,
ScryptedInterface.Camera,
ScryptedInterface.VideoCamera,
ScryptedInterface.VideoCameraConfiguration,
ScryptedInterface.MotionSensor,
],
type: camera.featureFlags.hasChime