Dont save frames unless debugging

This commit is contained in:
Kalle
2026-09-26 13:21:59 +03:00
parent f0d4b3400a
commit 382e2390e9
9 changed files with 45 additions and 14 deletions

View File

@@ -48,9 +48,10 @@ opens it, for anyone, through the same handoff Inspect uses.
so events from different page loads share one timeline; the ring buffer
stamps footage the same way.
- **Retention** (`store/events.ts`, on a throttled pass at every save):
whole sessions older than 30 days or beyond the newest 20 go; full-res
frames are kept for everyone — a misread is only reportable while the
frame exists — bounded by 72 h and `MAX_FRAMES`, the event staying with
whole sessions older than 30 days or beyond the newest 20 go. Full-res
frames (and the thumbnails made from them) are only captured in debug
mode — the worker skips the PNG encode otherwise (`attachFrames`) — and
are bounded by 72 h and `MAX_FRAMES`, the event staying with
`hasFrame: false`. Clips have their own cap and outlive session deletion.
- **Upload** is on by default when logged in (settings toggle, persisted).
Live: a scoreboard closes its match and sends it, a 15 s tick retries

View File

@@ -28,6 +28,7 @@ import styles from "./LandingView.module.css";
import { getLiveSession, startCapture, useLiveSession } from "./live-session";
import { SettingsPopover } from "./SettingsPopover";
import { SourceSelect } from "./SourceSelect";
import { useDebug } from "./use-debug";
import { startVodScan } from "./vod-scan";
import { useVods } from "./vods-feed";
@@ -55,6 +56,7 @@ export function LandingView() {
const vods = useVods();
const clips = useClips();
const [telemetry] = useSearchParam(scannerSearchParams, "telemetry");
const debug = useDebug();
const [, setParams] = useSearchParamsTyped(scannerSearchParams);
const [over, setOver] = useState(false);
const [playing, setPlaying] = useState<ScannerClip | null>(null);
@@ -68,7 +70,7 @@ export function LandingView() {
setParams({ view: "live" });
return;
}
await startCapture();
await startCapture({ saveFrames: debug });
if (getLiveSession().status === "running") setParams({ view: "live" });
};
@@ -77,7 +79,7 @@ export function LandingView() {
void inspectScreenshot(file);
return;
}
void startVodScan(file, { telemetry });
void startVodScan(file, { telemetry, saveFrames: debug });
setParams({ view: "vod", name: file.name });
};

View File

@@ -89,7 +89,10 @@ function ScanVodView({ name }: { name: string }) {
const file = e.target.files?.[0];
e.target.value = "";
if (file) {
void startVodScan(file, { telemetry: telemetryOn });
void startVodScan(file, {
telemetry: telemetryOn,
saveFrames: debug,
});
}
}}
/>

View File

@@ -179,8 +179,16 @@ function set(patch: Partial<LiveSnapshot>): void {
for (const listener of listeners) listener();
}
/** Opens the source, brings the worker up, then starts sampling; a second call while running is ignored. */
export async function startCapture(): Promise<void> {
/**
* Opens the source, brings the worker up, then starts sampling; a second call
* while running is ignored. `saveFrames` (debug mode) keeps each event's
* analyzed frame and thumbnail for the raw detections.
*/
export async function startCapture({
saveFrames,
}: {
saveFrames: boolean;
}): Promise<void> {
if (snapshot.status === "starting" || snapshot.status === "running") return;
releaseCaptureLock = await acquireCaptureLock();
if (!releaseCaptureLock) {
@@ -232,6 +240,7 @@ export async function startCapture(): Promise<void> {
client = new AnalyzerClient(onResult, onWorkerError, undefined, {
frameQueueLimit: FRAME_QUEUE_LIMIT,
webgpu: settings.webgpu,
attachFrames: saveFrames,
});
try {
await client.whenReady();

View File

@@ -207,10 +207,14 @@ export async function uploadVodScan(
}
}
/** Scans `file` as fast as decoding allows; a finished scan replaces any saved one of the same name. */
/**
* Scans `file` as fast as decoding allows; a finished scan replaces any saved
* one of the same name. `saveFrames` (debug mode) keeps each event's analyzed
* frame and thumbnail for the raw detections.
*/
export async function startVodScan(
file: File,
{ telemetry }: { telemetry: boolean },
{ telemetry, saveFrames }: { telemetry: boolean; saveFrames: boolean },
): Promise<void> {
cancelVodScan();
const abort = { aborted: false };
@@ -296,7 +300,11 @@ export async function startVodScan(
seek.frameDone?.();
seek.frameDone = null;
},
{ collectTelemetry: telemetry, webgpu: readSettings().webgpu },
{
collectTelemetry: telemetry,
webgpu: readSettings().webgpu,
attachFrames: saveFrames,
},
),
);
await Promise.all(clients.map((c) => c.whenReady()));

View File

@@ -21,8 +21,8 @@ import { EVENTS_STORE, FRAMES_STORE, readwrite, tx } from "./db";
const MAX_EVENTS = 10_000;
/**
* Full-res frame PNGs (~1-2MB each) are what makes a misread reportable, so
* they are kept for everyone — bounded by age and count, whichever bites first.
* Full-res frame PNGs (~1-2MB each) are what makes a misread reportable; only
* debug-mode captures save them, bounded by age and count, whichever bites first.
*/
const MAX_FRAMES = 200;
const FRAME_MAX_AGE_MS = 72 * 60 * 60 * 1000;

View File

@@ -81,6 +81,7 @@ let scheduler: DetectorScheduler | null = null;
/** null unless the init message asked for telemetry */
let telemetry: ScanTelemetry | null = null;
let collectTelemetry = false;
let attachFrames = true;
let chunkAborted = false;
/** last per-frame t, to reset telemetry when a new session rewinds the clock */
let lastFrameT = Number.NEGATIVE_INFINITY;
@@ -110,6 +111,7 @@ async function init({
suppressSteadyFrames = true,
collectTelemetry: collect = false,
webgpu = false,
attachFrames: attach = true,
}: InitRequest): Promise<void> {
// VoD scans need the helper workers: start them booting alongside init
if (typeof VideoDecoder !== "undefined") {
@@ -138,6 +140,7 @@ async function init({
matchClosingTypes: SCOREBOARD_EVENT_TYPES,
});
collectTelemetry = collect;
attachFrames = attach;
telemetry = freshTelemetry();
post({ kind: "ready" });
} catch (error) {
@@ -216,7 +219,7 @@ async function analyzePrepared(
if (action === "added" || action === "replaced") listed = true;
}
const blob =
listed && detector.attachFrame !== false
attachFrames && listed && detector.attachFrame !== false
? await frameBlob()
: undefined;
post({

View File

@@ -68,6 +68,8 @@ export class AnalyzerClient {
frameQueueLimit?: number;
/** match templates on WebGPU when the browser has an adapter (settings `webgpu`) */
webgpu?: boolean;
/** ship the analyzed frame's PNG on results (default true) */
attachFrames?: boolean;
} = {},
) {
this.#frameQueueLimit = options.frameQueueLimit ?? 0;
@@ -123,6 +125,7 @@ export class AnalyzerClient {
suppressSteadyFrames: options.suppressSteadyFrames ?? true,
collectTelemetry: options.collectTelemetry ?? false,
webgpu: options.webgpu ?? false,
attachFrames: options.attachFrames ?? true,
});
}

View File

@@ -15,6 +15,8 @@ export interface InitRequest {
collectTelemetry?: boolean;
/** match templates (and upscale sub-1080p frames) on WebGPU when an adapter exists; default false */
webgpu?: boolean;
/** PNG-encode the analyzed frame onto results whose events fired; default true (live and VoD scans only want it in debug mode) */
attachFrames?: boolean;
}
export interface AnalyzeRequest {