diff --git a/plugins/homekit/package-lock.json b/plugins/homekit/package-lock.json index b1a641a85..50609dacc 100644 --- a/plugins/homekit/package-lock.json +++ b/plugins/homekit/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/homekit", - "version": "1.2.55", + "version": "1.2.56", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/homekit", - "version": "1.2.55", + "version": "1.2.56", "dependencies": { "@koush/werift-src": "file:../../external/werift", "check-disk-space": "^3.4.0", diff --git a/plugins/homekit/package.json b/plugins/homekit/package.json index 645750534..abe062cda 100644 --- a/plugins/homekit/package.json +++ b/plugins/homekit/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/homekit", - "version": "1.2.55", + "version": "1.2.56", "description": "HomeKit Plugin for Scrypted", "scripts": { "scrypted-setup-project": "scrypted-setup-project", diff --git a/plugins/homekit/src/types/camera/camera-snapshot.ts b/plugins/homekit/src/types/camera/camera-snapshot.ts index c0e502fe7..6e1d8ffb5 100644 --- a/plugins/homekit/src/types/camera/camera-snapshot.ts +++ b/plugins/homekit/src/types/camera/camera-snapshot.ts @@ -24,8 +24,6 @@ export function createSnapshotHandler(device: ScryptedDevice & VideoCamera & Cam width: request.width, height: request.height, }, - // wait up to 2 seconds for the snapshot image, fallback to cached image - timeout: 2000, }) return await mediaManager.convertMediaObjectToBuffer(media, 'image/jpeg'); } diff --git a/plugins/snapshot/package-lock.json b/plugins/snapshot/package-lock.json index 2b923666e..435b1dbff 100644 --- a/plugins/snapshot/package-lock.json +++ b/plugins/snapshot/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/snapshot", - "version": "0.2.50", + "version": "0.2.51", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/snapshot", - "version": "0.2.50", + "version": "0.2.51", "dependencies": { "@types/node": "^20.10.6", "sharp": "^0.33.1", diff --git a/plugins/snapshot/package.json b/plugins/snapshot/package.json index 9c1d3c51c..055917d2a 100644 --- a/plugins/snapshot/package.json +++ b/plugins/snapshot/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/snapshot", - "version": "0.2.50", + "version": "0.2.51", "description": "Snapshot Plugin for Scrypted", "scripts": { "scrypted-setup-project": "scrypted-setup-project", diff --git a/plugins/snapshot/src/main.ts b/plugins/snapshot/src/main.ts index 810c8f6ac..12b71a905 100644 --- a/plugins/snapshot/src/main.ts +++ b/plugins/snapshot/src/main.ts @@ -271,10 +271,6 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { } async takePictureRaw(options?: RequestPictureOptions): Promise { - let rawPicturePromise: Promise<{ - picture: Buffer; - pictureTime: number; - }>; const eventSnapshot = options?.reason === 'event'; const periodicSnapshot = options?.reason === 'periodic'; @@ -282,50 +278,61 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { if (this.currentPictureTime < Date.now() - 1 * 60 * 60 * 1000) this.currentPicture = undefined; - const allowedSnapshotStaleness = eventSnapshot ? 0 : periodicSnapshot ? 20000 : 10000; - - let needRefresh = true; - if (this.currentPicture && this.currentPictureTime > Date.now() - allowedSnapshotStaleness) { - this.debugConsole?.log('Using cached snapshot for', options?.reason); - rawPicturePromise = Promise.resolve({ - picture: this.currentPicture, - pictureTime: this.currentPictureTime, - }); - needRefresh = this.currentPictureTime < Date.now() - allowedSnapshotStaleness / 2; - } - - if (needRefresh) { - const debounced = this.snapshotDebouncer({ - id: options?.id, - reason: options?.reason, - }, eventSnapshot ? 0 : 10000, async () => { - const snapshotTimer = Date.now(); - let picture = await this.takePictureInternal(); - picture = await this.cropAndScale(picture); - this.clearCachedPictures(); - const pictureTime = Date.now(); - this.currentPicture = picture; - this.currentPictureTime = pictureTime; - this.lastAvailablePicture = picture; - this.debugConsole?.debug(`Periodic snapshot took ${(this.currentPictureTime - snapshotTimer) / 1000} seconds to retrieve.`) - return { - picture, - pictureTime, - }; - }); - debounced.catch(() => { }); - - rawPicturePromise ||= debounced; - } + // always grab/debounce a snapshot + // event snapshot are special and should immediately expire. + // other snapshots may be debounced for 4s. + const debounced = this.snapshotDebouncer({ + id: options?.id, + type: 'source', + event: options?.reason === 'event', + }, eventSnapshot ? 0 : 4000, async () => { + const snapshotTimer = Date.now(); + let picture = await this.takePictureInternal(); + picture = await this.cropAndScale(picture); + this.clearCachedPictures(); + const pictureTime = Date.now(); + this.currentPicture = picture; + this.currentPictureTime = pictureTime; + this.lastAvailablePicture = picture; + this.debugConsole?.debug(`Periodic snapshot took ${(this.currentPictureTime - snapshotTimer) / 1000} seconds to retrieve.`) + return { + picture, + pictureTime, + }; + }); + debounced.catch(() => { }); // prevent this from expiring let availablePicture = this.currentPicture; let availablePictureTime = this.currentPictureTime; - let rawPicture: Awaited; + let rawPicture: Awaited; try { - const pictureTimeout = options?.timeout || (periodicSnapshot && availablePicture ? 1000 : 10000) || 10000; - rawPicture = await timeoutPromise(pictureTimeout, rawPicturePromise); + let pictureTimeout = options?.timeout; + if (!pictureTimeout) { + // determine a fetch timeout based on the reason and staleness + const allowedSnapshotStaleness = eventSnapshot ? 0 : periodicSnapshot ? 20000 : 10000; + if (!availablePicture) { + // none available so wait a while + pictureTimeout = 10000; + } + else { + if (availablePictureTime > Date.now() - 3000) { + // very recent, don't wait for too long + pictureTimeout = 1000; + } + else if (availablePictureTime > Date.now() - allowedSnapshotStaleness) { + // fairly recent so give it little time to get a fresh one + // idr interval is typically 4000 for reference + pictureTimeout = 3000; + } + else { + // stale so wait a while + pictureTimeout = 10000; + } + } + } + rawPicture = await timeoutPromise(pictureTimeout, debounced); } catch (e) { // a best effort was made to get a recent snapshot from cache or from a camera request, @@ -336,7 +343,11 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { if (eventSnapshot) throw e; - availablePicture = this.currentPicture || availablePicture; + if (this.currentPicture) { + // use the current picture if it is still available as it may be newer. + availablePicture = this.currentPicture; + availablePictureTime = this.currentPictureTime; + } if (!availablePicture) return this.createErrorImage(e); @@ -358,8 +369,8 @@ class SnapshotMixin extends SettingsMixinDeviceBase implements Camera { try { const key = { + type: 'resize', pictureTime: rawPicture.pictureTime, - reason: options?.reason, needSoftwareResize: true, picture: options.picture, };