// Metadata only. No frames or PCM are retained. These are OBSERVER metrics, // never an automatically passing sync/freshness/artifact report. export class VideoMetrics { constructor(now=()=>performance.now()) { this.last = null; this.intervals = []; this.callbacks = 0; this.unobserved = 0; this.resets = 0; this.overflow = 0; this.maxGap = null; this.now=now;this.lastObservedAt=null;this.clockInvalid=false; } observe(timeMs, presentedFrames) { if (!Number.isFinite(timeMs) || !Number.isSafeInteger(presentedFrames) || presentedFrames < 0) return; const receivedAt=this.now(); if(!Number.isFinite(receivedAt)|| (this.lastObservedAt!==null&&receivedAta-b); const now=this.now(),elapsed=this.lastObservedAt===null?null:now-this.lastObservedAt; const age=!this.clockInvalid&&Number.isFinite(now)&&Number.isFinite(elapsed)&&elapsed>=0?elapsed:null; // A stopped callback stream is visible without waiting for its next // frame. This is observer silence, not proof of a transport freeze. const gap=age===null?this.maxGap:Math.max(this.maxGap??0,age); return {callbacks:this.callbacks,unobservedPresentedFrames:this.unobserved,clockOrCounterResets:this.resets, callbackIntervalP95Ms:!this.overflow&&sorted.length ? sorted[Math.ceil(sorted.length*.95)-1] : null, callbackGapMaxMs:gap,observationAgeMs:age,intervalSampleOverflow:this.overflow, note:"Browser presentation callbacks; gaps are not proof of transport loss or corrupted video."}; } } export function receipt(video,audio,settings,elapsedMs,reason) { return {schema:"av-test.observer.v2",kind:"diagnostic-only",reason,elapsedMs, settings,video:video.summary(),audio, syncMeasured:false,freshnessMeasured:false,artifactsMeasured:false, releaseEligible:false,recordedMedia:false}; }