snapshot/homekit: fix stale snapshots

This commit is contained in:
Koushik Dutta
2024-05-07 20:22:45 -07:00
parent 976ed7f1a5
commit 8b84bac2c2
6 changed files with 61 additions and 52 deletions

View File

@@ -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",

View File

@@ -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",

View File

@@ -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');
}

View File

@@ -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",

View File

@@ -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",

View File

@@ -271,10 +271,6 @@ class SnapshotMixin extends SettingsMixinDeviceBase<Camera> implements Camera {
}
async takePictureRaw(options?: RequestPictureOptions): Promise<Buffer> {
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<Camera> 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<typeof rawPicturePromise>;
let rawPicture: Awaited<typeof debounced>;
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<Camera> 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<Camera> implements Camera {
try {
const key = {
type: 'resize',
pictureTime: rawPicture.pictureTime,
reason: options?.reason,
needSoftwareResize: true,
picture: options.picture,
};