From 56c22eea09f1e6be2a0666ee669bb885afdb223d Mon Sep 17 00:00:00 2001 From: Koushik Dutta Date: Wed, 2 Mar 2022 11:51:10 -0800 Subject: [PATCH] rebroadcast: support rtsps in RTSPClient. google-device-access: fixup interface shenanigans between camera types. --- common/src/rtsp-server.ts | 13 ++++++++- plugins/google-device-access/src/main.ts | 27 +++++++++++++------ plugins/prebuffer-mixin/package-lock.json | 4 +-- plugins/prebuffer-mixin/package.json | 2 +- plugins/prebuffer-mixin/src/main.ts | 2 +- sdk/gen/types.input.ts | 2 +- sdk/scrypted_python/scrypted_sdk/types.py | 2 +- sdk/types/index.d.ts | 2 +- sdk/types/index.ts | 2 +- .../scrypted_python/scrypted_sdk/types.py | 2 +- 10 files changed, 40 insertions(+), 18 deletions(-) diff --git a/common/src/rtsp-server.ts b/common/src/rtsp-server.ts index 79722bf49..2d33c77ea 100644 --- a/common/src/rtsp-server.ts +++ b/common/src/rtsp-server.ts @@ -4,6 +4,7 @@ import { randomBytes } from 'crypto'; import { StreamChunk, StreamParser } from './stream-parser'; import dgram from 'dgram'; import net from 'net'; +import tls from 'tls'; export const RTSP_FRAME_MAGIC = 36; @@ -103,7 +104,17 @@ export class RtspClient extends RtspBase { constructor(public url: string) { super(); const u = new URL(url); - this.client = net.connect(parseInt(u.port) || 554, u.hostname); + const port = parseInt(u.port) || 554; + if (url.startsWith('rtsps')) { + this.client = tls.connect({ + rejectUnauthorized: false, + port, + host: u.hostname, + }) + } + else { + this.client = net.connect(port, u.hostname); + } } async request(method: string, headers?: Headers, path?: string, body?: Buffer) { diff --git a/plugins/google-device-access/src/main.ts b/plugins/google-device-access/src/main.ts index 28bfd545b..47b2aef52 100644 --- a/plugins/google-device-access/src/main.ts +++ b/plugins/google-device-access/src/main.ts @@ -18,8 +18,6 @@ const { deviceManager, mediaManager, endpointManager, systemManager } = sdk; const refreshFrequency = 60; -const black = fs.readFileSync('unavailable.jpg'); - const readmeV1 = fs.readFileSync('README-camera-v1.md').toString(); const readmeV2 = fs.readFileSync('README-camera-v2.md').toString(); @@ -35,10 +33,20 @@ function getSdmRtspMediaStreamOptions(): MediaStreamOptions { codec: 'aac', }, source: 'cloud', + tool: 'scrypted', userConfigurable: false, }; } +function deviceHasEventImages(device: any) { + return !!device?.traits?.['sdm.devices.traits.CameraEventImage']; +} + +function deviceIsWebRtc(device: any) { + return device?.traits?.['sdm.devices.traits.CameraLiveStream']?.supportedProtocols?.includes('WEB_RTC'); +} + + const RtcMediaStreamOptionsId = 'webrtc'; function getSdmRtcMediaStreamOptions(): MediaStreamOptions { @@ -198,7 +206,7 @@ class NestCamera extends ScryptedDeviceBase implements Readme, Camera, VideoCame } // try to fetch the latest event image if one is queued - const hasEventImages = !!this.device?.traits?.['sdm.devices.traits.CameraEventImage']; + const hasEventImages = deviceHasEventImages(this.device); if (hasEventImages && this.lastMotionEventId) { const eventId = this.lastMotionEventId; this.lastMotionEventId = undefined; @@ -217,8 +225,7 @@ class NestCamera extends ScryptedDeviceBase implements Readme, Camera, VideoCame return mediaManager.createMediaObject(data, 'image/jpeg'); } - // send "no snapshot available" image - return mediaManager.createMediaObject(black, 'image/jpeg'); + throw new Error('snapshot unavailable'); } async getPictureOptions(): Promise { @@ -248,7 +255,7 @@ class NestCamera extends ScryptedDeviceBase implements Readme, Camera, VideoCame } get isWebRtc() { - return this.device?.traits?.['sdm.devices.traits.CameraLiveStream']?.supportedProtocols?.includes('WEB_RTC'); + return deviceIsWebRtc(this.device); } async getVideoStream(options?: RequestMediaStreamOptions): Promise { @@ -763,14 +770,18 @@ class GoogleSmartDeviceAccess extends ScryptedDeviceBase implements OauthClient, const interfaces = [ ScryptedInterface.BufferConverter, - ScryptedInterface.RTCSignalingChannel, ScryptedInterface.VideoCamera, - ScryptedInterface.Camera, ScryptedInterface.MotionSensor, ScryptedInterface.ObjectDetector, ScryptedInterface.Readme, ]; + if (deviceHasEventImages(device)) + interfaces.push(ScryptedInterface.Camera); + + if (deviceIsWebRtc(device)) + interfaces.push(ScryptedInterface.RTCSignalingChannel); + let type = ScryptedDeviceType.Camera; if (device.type === 'sdm.devices.types.DOORBELL') { interfaces.push(ScryptedInterface.BinarySensor); diff --git a/plugins/prebuffer-mixin/package-lock.json b/plugins/prebuffer-mixin/package-lock.json index 19a799470..e280bf80e 100644 --- a/plugins/prebuffer-mixin/package-lock.json +++ b/plugins/prebuffer-mixin/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/prebuffer-mixin", - "version": "0.1.171", + "version": "0.1.172", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/prebuffer-mixin", - "version": "0.1.171", + "version": "0.1.172", "license": "Apache-2.0", "dependencies": { "@scrypted/common": "file:../../common", diff --git a/plugins/prebuffer-mixin/package.json b/plugins/prebuffer-mixin/package.json index 13c81739c..6e02378f5 100644 --- a/plugins/prebuffer-mixin/package.json +++ b/plugins/prebuffer-mixin/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/prebuffer-mixin", - "version": "0.1.171", + "version": "0.1.172", "description": "Rebroadcast and Prebuffer for VideoCameras.", "author": "Scrypted", "license": "Apache-2.0", diff --git a/plugins/prebuffer-mixin/src/main.ts b/plugins/prebuffer-mixin/src/main.ts index db73c50b0..06bcd2ddf 100644 --- a/plugins/prebuffer-mixin/src/main.ts +++ b/plugins/prebuffer-mixin/src/main.ts @@ -208,7 +208,7 @@ class PrebufferSession { title: 'Detected Keyframe Interval', description: "Configuring your camera to 4 seconds is recommended (IDR aka Frame Interval = FPS * 4 seconds).", readonly: true, - value: ((this.detectedIdrInterval || 0) / 1000).toString() || 'none', + value: (this.detectedIdrInterval || 0) / 1000 || 'unknown', }, ); } diff --git a/sdk/gen/types.input.ts b/sdk/gen/types.input.ts index c64c2b930..da32e5602 100644 --- a/sdk/gen/types.input.ts +++ b/sdk/gen/types.input.ts @@ -1329,7 +1329,7 @@ export interface RTCSignalingClient { * strict requirements and expectations on client setup. */ export interface RTCSignalingChannel { - startRTCSignalingSession(session: RTCSignalingSession, options?: RTCSignalingClientOptions): Promise; + startRTCSignalingSession(session: RTCSignalingSession, options?: RTCSignalingClientOptions): Promise; } export interface RTCAVSignalingSetup { diff --git a/sdk/scrypted_python/scrypted_sdk/types.py b/sdk/scrypted_python/scrypted_sdk/types.py index b3fbb966e..6f2ee4a0b 100644 --- a/sdk/scrypted_python/scrypted_sdk/types.py +++ b/sdk/scrypted_python/scrypted_sdk/types.py @@ -703,7 +703,7 @@ class PushHandler: pass class RTCSignalingChannel: - async def startRTCSignalingSession(self, session: RTCSignalingSession, options: RTCSignalingClientOptions = None) -> None | RTCSessionControl: + async def startRTCSignalingSession(self, session: RTCSignalingSession, options: RTCSignalingClientOptions = None) -> RTCSessionControl: pass pass diff --git a/sdk/types/index.d.ts b/sdk/types/index.d.ts index 03f0a68df..d03bedac2 100644 --- a/sdk/types/index.d.ts +++ b/sdk/types/index.d.ts @@ -1364,7 +1364,7 @@ export interface RTCSignalingClient { * strict requirements and expectations on client setup. */ export interface RTCSignalingChannel { - startRTCSignalingSession(session: RTCSignalingSession, options?: RTCSignalingClientOptions): Promise; + startRTCSignalingSession(session: RTCSignalingSession, options?: RTCSignalingClientOptions): Promise; } export interface RTCAVSignalingSetup { /** diff --git a/sdk/types/index.ts b/sdk/types/index.ts index b6c0c2653..77eed5f7e 100644 --- a/sdk/types/index.ts +++ b/sdk/types/index.ts @@ -1992,7 +1992,7 @@ export interface RTCSignalingClient { * strict requirements and expectations on client setup. */ export interface RTCSignalingChannel { - startRTCSignalingSession(session: RTCSignalingSession, options?: RTCSignalingClientOptions): Promise; + startRTCSignalingSession(session: RTCSignalingSession, options?: RTCSignalingClientOptions): Promise; } export interface RTCAVSignalingSetup { diff --git a/sdk/types/scrypted_python/scrypted_sdk/types.py b/sdk/types/scrypted_python/scrypted_sdk/types.py index b3fbb966e..6f2ee4a0b 100644 --- a/sdk/types/scrypted_python/scrypted_sdk/types.py +++ b/sdk/types/scrypted_python/scrypted_sdk/types.py @@ -703,7 +703,7 @@ class PushHandler: pass class RTCSignalingChannel: - async def startRTCSignalingSession(self, session: RTCSignalingSession, options: RTCSignalingClientOptions = None) -> None | RTCSessionControl: + async def startRTCSignalingSession(self, session: RTCSignalingSession, options: RTCSignalingClientOptions = None) -> RTCSessionControl: pass pass