unifi-protect: use pipes, safe kill ffmpeg interocm

This commit is contained in:
Koushik Dutta
2022-03-26 20:57:20 -07:00
parent 025b538689
commit 4d7a3c2dfc
4 changed files with 84 additions and 42 deletions

View File

@@ -1,15 +1,16 @@
{
"name": "@scrypted/unifi-protect",
"version": "0.0.106",
"version": "0.0.107",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/unifi-protect",
"version": "0.0.106",
"version": "0.0.107",
"license": "Apache",
"dependencies": {
"@koush/unifi-protect": "file:../../external/unifi-protect",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"node-fetch": "^3.2.1",
"ws": "^8.5.0"
@@ -20,13 +21,29 @@
"@types/ws": "^7.4.7"
}
},
"../../common": {
"version": "1.0.1",
"license": "ISC",
"dependencies": {
"@koush/werift": "file:../external/werift/packages/webrtc",
"@koush/wrtc": "^0.5.3",
"@scrypted/sdk": "file:../sdk",
"http-auth-utils": "^3.0.2",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
}
},
"../../external/unifi-protect": {
"version": "3.0.2",
"name": "@koush/unifi-protect",
"version": "3.0.5",
"license": "ISC",
"dependencies": {
"abort-controller": "^3.0.0",
"domexception": "^4.0.0",
"node-fetch-cjs": "=3.2.0",
"node-fetch": "^3.2.1",
"util": "^0.12.4",
"ws": "^8.5.0"
},
@@ -45,7 +62,7 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.0.174",
"version": "0.0.177",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.16.7",
@@ -85,6 +102,10 @@
"resolved": "../../external/unifi-protect",
"link": true
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
},
"node_modules/@scrypted/sdk": {
"resolved": "../../sdk",
"link": true
@@ -226,13 +247,25 @@
"abort-controller": "^3.0.0",
"domexception": "^4.0.0",
"eslint": "^8.9.0",
"node-fetch-cjs": "=3.2.0",
"node-fetch": "^3.2.1",
"rimraf": "^3.0.2",
"typescript": "^4.5.5",
"util": "^0.12.4",
"ws": "^8.5.0"
}
},
"@scrypted/common": {
"version": "file:../../common",
"requires": {
"@koush/werift": "file:../external/werift/packages/webrtc",
"@koush/wrtc": "^0.5.3",
"@scrypted/sdk": "file:../sdk",
"@types/node": "^16.9.0",
"http-auth-utils": "^3.0.2",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/unifi-protect",
"version": "0.0.106",
"version": "0.0.107",
"description": "Unifi Protect Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
@@ -37,6 +37,7 @@
},
"dependencies": {
"@koush/unifi-protect": "file:../../external/unifi-protect",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"node-fetch": "^3.2.1",
"ws": "^8.5.0"

View File

@@ -1,14 +1,15 @@
import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, VideoCamera, MediaObject, MotionSensor, ScryptedInterface, Camera, MediaStreamOptions, Intercom, ScryptedMimeTypes, FFMpegInput, ObjectDetector, PictureOptions, ObjectDetectionTypes, ObjectsDetected, Notifier, VideoCameraConfiguration, OnOff, MediaStreamUrl } from "@scrypted/sdk";
import { ProtectCameraChannelConfig, ProtectCameraConfigInterface, ProtectCameraLcdMessagePayload } from "./unifi-protect";
import child_process, { ChildProcess } from 'child_process';
import { ffmpegLogInitialOutput } from '../../../common/src/media-helpers';
import { fitHeightToWidth } from "../../../common/src/resolution-utils";
import { listenZero } from "../../../common/src/listen-cluster";
import { ffmpegLogInitialOutput, safeKillFFmpeg } from '@scrypted/common/src/media-helpers';
import { fitHeightToWidth } from "@scrypted/common/src/resolution-utils";
import { listenZero } from "@scrypted/common/src/listen-cluster";
import net from 'net';
import WS from 'ws';
import { once } from "events";
import { FeatureFlagsShim } from "./shim";
import { UnifiProtect } from "./main";
import { Readable } from "stream";
const { log, deviceManager, mediaManager } = sdk;
@@ -90,6 +91,8 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
}
async startIntercom(media: MediaObject) {
this.stopIntercom();
const buffer = await mediaManager.convertMediaObjectToBuffer(media, ScryptedMimeTypes.FFmpegInput);
const ffmpegInput = JSON.parse(buffer.toString()) as FFMpegInput;
@@ -106,32 +109,6 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
const websocket = new WS(talkbackUrl, { rejectUnauthorized: false });
await once(websocket, 'open');
const server = new net.Server(async (socket) => {
server.close();
this.console.log('sending audio data to', talkbackUrl);
try {
while (websocket.readyState === WS.OPEN) {
await once(socket, 'readable');
while (true) {
const data = socket.read();
if (!data)
break;
websocket.send(data, e => {
if (e)
socket.destroy();
});
}
}
}
finally {
this.console.log('talkback ended')
this.intercomProcess.kill();
}
});
const port = await listenZero(server)
const args = [
'-hide_banner',
@@ -152,17 +129,48 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
"-ac", camera.talkbackSettings.channels.toString(),
"-b:a", "16k",
"-f", "adts",
`tcp://127.0.0.1:${port}`,
`pipe:3`,
);
this.console.log('starting 2 way audio', args);
const ffmpeg = await mediaManager.getFFmpegPath();
this.intercomProcess = child_process.spawn(ffmpeg, args);
this.intercomProcess.on('exit', () => {
websocket.close();
this.intercomProcess = undefined;
const cp = this.intercomProcess = child_process.spawn(ffmpeg, args, {
stdio: ['pipe', 'pipe', 'pipe', 'pipe'],
});
this.intercomProcess.on('exit', () => {
this.intercomProcess = undefined;
websocket.close();
});
websocket.on('close', () => safeKillFFmpeg(cp));
(async () => {
const socket = cp.stdio[3] as Readable;
this.console.log('sending audio data to', talkbackUrl);
try {
while (websocket.readyState === WS.OPEN) {
await once(socket, 'readable');
while (true) {
const data = socket.read();
if (!data)
break;
websocket.send(data, e => {
if (e) {
safeKillFFmpeg(cp);
}
});
}
}
}
finally {
this.console.log('talkback ended')
safeKillFFmpeg(cp);
}
})();
ffmpegLogInitialOutput(this.console, this.intercomProcess);
}

View File

@@ -1,6 +1,6 @@
import sdk, { ScryptedDeviceBase, DeviceProvider, Settings, Setting, ScryptedDeviceType, Device, ScryptedInterface, ObjectsDetected, ObjectDetectionResult } from "@scrypted/sdk";
import { ProtectApi, ProtectApiUpdates, ProtectNvrUpdatePayloadCameraUpdate, ProtectNvrUpdatePayloadEventAdd } from "./unifi-protect";
import { createInstanceableProviderPlugin, enableInstanceableProviderMode, isInstanceableProviderModeEnabled } from '../../../common/src/provider-plugin';
import { createInstanceableProviderPlugin, enableInstanceableProviderMode, isInstanceableProviderModeEnabled } from '@scrypted/common/src/provider-plugin';
import fetch, { RequestInfo, RequestInit, Response } from "node-fetch";
import { defaultSensorTimeout, UnifiCamera } from "./camera";
import { FeatureFlagsShim, LastSeenShim } from "./shim";