diff --git a/plugins/ring/package-lock.json b/plugins/ring/package-lock.json index b49c5477b5..1e3d39af3e 100644 --- a/plugins/ring/package-lock.json +++ b/plugins/ring/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/ring", - "version": "0.0.99", + "version": "0.0.100", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/ring", - "version": "0.0.99", + "version": "0.0.100", "dependencies": { "@koush/ring-client-api": "file:../../external/ring-client-api", "@scrypted/common": "file:../../common", diff --git a/plugins/ring/package.json b/plugins/ring/package.json index 00e1541e7b..eb201c590c 100644 --- a/plugins/ring/package.json +++ b/plugins/ring/package.json @@ -44,5 +44,5 @@ "got": "11.8.6", "socket.io-client": "^2.5.0" }, - "version": "0.0.99" + "version": "0.0.100" } diff --git a/plugins/ring/src/main.ts b/plugins/ring/src/main.ts index 317bc50007..35c8808fd6 100644 --- a/plugins/ring/src/main.ts +++ b/plugins/ring/src/main.ts @@ -3,7 +3,7 @@ import { RefreshPromise } from "@scrypted/common/src/promise-utils"; import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling'; import { RtspServer } from '@scrypted/common/src/rtsp-server'; import { addTrackControls, parseSdp, replacePorts } from '@scrypted/common/src/sdp-utils'; -import sdk, { Battery, BinarySensor, Camera, Device, DeviceProvider, EntrySensor, FFmpegInput, FloodSensor, Lock, LockState, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, SecuritySystem, SecuritySystemMode, Setting, Settings, SettingValue, TamperSensor, VideoCamera } from '@scrypted/sdk'; +import sdk, { Battery, BinarySensor, Camera, Device, DeviceProvider, EntrySensor, FFmpegInput, FloodSensor, Lock, LockState, MediaObject, MediaStreamUrl, MotionSensor, OnOff, PictureOptions, RequestMediaStreamOptions, RequestPictureOptions, ResponseMediaStreamOptions, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, SecuritySystem, SecuritySystemMode, Setting, Settings, SettingValue, TamperSensor, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk'; import { StorageSettings } from '@scrypted/sdk/storage-settings'; import child_process, { ChildProcess } from 'child_process'; import dgram from 'dgram'; @@ -79,7 +79,7 @@ class RingCameraSiren extends ScryptedDeviceBase implements OnOff { } } -class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvider, Camera, MotionSensor, BinarySensor, RTCSignalingChannel { +class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvider, Camera, MotionSensor, BinarySensor, RTCSignalingChannel, VideoClips { buttonTimeout: NodeJS.Timeout; session: SipSession; rtpDescription: RtpDescription; @@ -89,6 +89,7 @@ class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvider, Cam currentMediaMimeType: string; refreshTimeout: NodeJS.Timeout; picturePromise: RefreshPromise; + videoClips = new Map(); constructor(public plugin: RingPlugin, public location: RingLocationDevice, nativeId: string) { super(nativeId); @@ -98,7 +99,6 @@ class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvider, Cam this.batteryLevel = this.findCamera()?.batteryLevel; } - async startIntercom(media: MediaObject): Promise { if (!this.session) throw new Error("not in call"); @@ -659,6 +659,53 @@ class RingCameraDevice extends ScryptedDeviceBase implements DeviceProvider, Cam siren.on = data.siren_status.seconds_remaining > 0 ? true : false; } } + + async getVideoClips(options?: VideoClipOptions): Promise { + this.videoClips = new Map; + const response = await this.findCamera().videoSearch({ + dateFrom: options.startTime, + dateTo: options.endTime, + }); + + return response.video_search.map((result) => { + const videoClip = { + id: result.ding_id, + startTime: result.created_at, + duration: Math.round(result.duration * 1000), + event: result.kind.toString(), + description: result.kind.toString(), + thumbnailId: result.ding_id, + resources: { + thumbnail: { + href: result.thumbnail_url + }, + video: { + href: result.hq_url + } + } + } + this.videoClips.set(result.ding_id, videoClip) + return videoClip; + }); + } + + async getVideoClip(videoId: string): Promise { + if (this.videoClips.has(videoId)) { + return mediaManager.createMediaObjectFromUrl(this.videoClips.get(videoId).resources.video.href); + } + throw new Error('Failed to get video clip.') + } + + async getVideoClipThumbnail(thumbnailId: string): Promise { + if (this.videoClips.has(thumbnailId)) { + return mediaManager.createMediaObjectFromUrl(this.videoClips.get(thumbnailId).resources.thumbnail.href); + } + throw new Error('Failed to get video clip thumbnail.') + } + + async removeVideoClips(...videoClipIds: string[]): Promise { + throw new Error('Removing video clips not supported.'); + } } class RingLock extends ScryptedDeviceBase implements Battery, Lock { @@ -1022,6 +1069,7 @@ class RingPlugin extends ScryptedDeviceBase implements DeviceProvider, Settings interfaces.push( ScryptedInterface.VideoCamera, ScryptedInterface.Intercom, + ScryptedInterface.VideoClips, ); } if (camera.operatingOnBattery)