// live-view.ts — async generator of JPEG frames.
/**
* Async generator yielding JPEG frames as Uint8Array.
*
* Stops when the signal is aborted. Swallows transient errors (404 "no
* frame yet", network blips) so consumers don't have to handle them.
*/
export async function* pollLiveViewFrames(
baseUrl: string,
cameraId: string,
options: {
intervalMs?: number;
signal?: AbortSignal;
} = {},
): AsyncGenerator<Uint8Array> {
const interval = options.intervalMs ?? 66; // ~15fps
const signal = options.signal;
const url = `${baseUrl}/api/cameras/${cameraId}/live-view/frame`;
while (!signal?.aborted) {
const iterStart = Date.now();
try {
const res = await fetch(url, { signal });
if (res.ok) {
const buf = new Uint8Array(await res.arrayBuffer());
// Basic sanity check: valid JPEG starts with FF D8 FF
if (buf.length >= 3 && buf[0] === 0xff && buf[1] === 0xd8 && buf[2] === 0xff) {
yield buf;
}
}
// 404 = no frame yet (e.g. live view just started); ignore & retry
} catch (err) {
if (signal?.aborted) return;
// Network blip — backoff slightly so we don't hammer the server
await new Promise((r) => setTimeout(r, 500));
continue;
}
// Pace the loop. If the fetch took longer than the interval, skip the sleep.
const elapsed = Date.now() - iterStart;
const sleep = Math.max(0, interval - elapsed);
if (sleep > 0) await new Promise((r) => setTimeout(r, sleep));
}
}