diff --git a/plugins/snapshot/package-lock.json b/plugins/snapshot/package-lock.json index 06a202cd0..bd975f47f 100644 --- a/plugins/snapshot/package-lock.json +++ b/plugins/snapshot/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/snapshot", - "version": "0.0.16", + "version": "0.0.17", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/snapshot", - "version": "0.0.16", + "version": "0.0.17", "dependencies": { "@koush/axios-digest-auth": "^0.8.5", "@types/node": "^16.6.1", diff --git a/plugins/snapshot/package.json b/plugins/snapshot/package.json index 24498b404..a1077fda9 100644 --- a/plugins/snapshot/package.json +++ b/plugins/snapshot/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/snapshot", - "version": "0.0.16", + "version": "0.0.17", "description": "Snapshot Plugin for Scrypted", "scripts": { "prepublishOnly": "NODE_ENV=production scrypted-webpack", diff --git a/plugins/snapshot/src/main.ts b/plugins/snapshot/src/main.ts index b70b68287..86f72758a 100644 --- a/plugins/snapshot/src/main.ts +++ b/plugins/snapshot/src/main.ts @@ -4,13 +4,15 @@ import { StorageSettings } from "@scrypted/common/src/settings" import AxiosDigestAuth from '@koush/axios-digest-auth'; import https from 'https'; import axios, { Axios } from "axios"; -import { TimeoutError, timeoutPromise } from "@scrypted/common/src/promise-utils"; +import { RefreshPromise, singletonPromise, TimeoutError, timeoutPromise } from "@scrypted/common/src/promise-utils"; import { AutoenableMixinProvider } from "@scrypted/common/src/autoenable-mixin-provider"; import jimp from 'jimp'; -const { systemManager } = sdk; +const { mediaManager, systemManager } = sdk; + +// lol +const FOREVER = 24 * 60 * 60 * 1000; -const { mediaManager } = sdk; const httpsAgent = new https.Agent({ rejectUnauthorized: false }); @@ -27,8 +29,11 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { title: 'Default Snapshot Channel', description: 'The default channel to use for snapshots.', defaultValue: 'Camera Default', - hide: true, + hide: !this.mixinDeviceInterfaces.includes(ScryptedInterface.Camera), onGet: async () => { + if (!this.mixinDeviceInterfaces.includes(ScryptedInterface.Camera)) + return {}; + let psos: PictureOptions[]; try { psos = await this.mixinDevice.getPictureOptions(); @@ -92,10 +97,11 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { }); axiosClient: Axios | AxiosDigestAuth; pendingPicture: Promise; - // this will contain the last picture retrieved, - // or an outdated picture blurred with an error overlay. - lastPicture: Buffer; - rawLastPicture: Buffer; + errorPicture: RefreshPromise; + timeoutPicture: RefreshPromise; + progressPicture: RefreshPromise; + currentPicture: Buffer; + lastAvailablePicture: Buffer; constructor(options: SettingsMixinDeviceOptions) { super(options); @@ -190,17 +196,18 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { try { picture = await takePicture(); picture = await this.cropAndScale(picture); - this.rawLastPicture = picture; + this.clearCachedPictures(); + this.currentPicture = picture; + this.lastAvailablePicture = picture; + setTimeout(() => { + if (this.currentPicture === picture) { + this.clearCachedPictures(); + } + }, 30000); } catch (e) { picture = await this.createErrorImage(e); } - this.lastPicture = picture; - setTimeout(() => { - if (this.lastPicture === picture) { - this.lastPicture = undefined; - } - }, 30000); return picture; })(); @@ -222,13 +229,13 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { else { try { if (this.storageSettings.values.snapshotMode === 'Never Wait') { - if (!this.lastPicture) { + if (!this.currentPicture) { // this triggers an event to refresh the web ui. this.pendingPicture.then(() => this.onDeviceEvent(ScryptedInterface.Camera, undefined)); data = await this.createErrorImage(new NeverWaitError()); } else { - data = this.lastPicture; + data = this.currentPicture; } } else { @@ -266,16 +273,37 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { return cropped; } + clearCachedPictures() { + this.currentPicture = undefined; + this.errorPicture = undefined; + this.pendingPicture = undefined; + this.timeoutPicture = undefined; + } + async createErrorImage(e: any) { this.console.log('creating error snapshot', e); - let text: string; - if (e instanceof TimeoutError) - text = 'Snapshot Timed Out'; - else if (e instanceof NeverWaitError) - text = 'Snapshot in Progress'; - else - text = 'Snapshot Failed'; - if (!this.rawLastPicture) { + if (e instanceof TimeoutError) { + this.timeoutPicture = singletonPromise(this.timeoutPicture, + () => this.createTextErrorImage('Snapshot Timed Out'), + FOREVER); + return this.timeoutPicture.promise; + } + else if (e instanceof NeverWaitError) { + this.progressPicture = singletonPromise(this.timeoutPicture, + () => this.createTextErrorImage('Snapshot In Progress'), + FOREVER); + return this.progressPicture.promise; + } + else { + this.errorPicture = singletonPromise(this.timeoutPicture, + () => this.createTextErrorImage('Snapshot Failed'), + FOREVER); + return this.errorPicture.promise; + } + } + + async createTextErrorImage(text: string) { + if (!this.currentPicture) { const img = await jimp.create(1920 / 2, 1080 / 2); const font = await fontPromise; img.print(font, 0, 0, { @@ -286,7 +314,7 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { return img.getBufferAsync('image/jpeg'); } else { - const img = await jimp.read(this.rawLastPicture); + const img = await jimp.read(this.currentPicture); img.resize(1920 / 2, jimp.AUTO); img.blur(15); img.brightness(-.2);