jenkins 9204773e7d av-test: publish bounded continuity watch and recent observations
Vendor tested Lesavka observer assets and numeric receipt schema from 6b97ceb1. Verified 65 JS tests, 10 service tests, kustomize render and Kubernetes client dry-run. No media upload, capture activation or changes to the homepage and private OCR.
2026-10-22 21:47:13 -05:00

51 lines
2.9 KiB
JavaScript

// 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&&receivedAt<this.lastObservedAt))this.clockInvalid=true;
if(!this.clockInvalid&&this.lastObservedAt!==null)
this.maxGap=Math.max(this.maxGap??0,receivedAt-this.lastObservedAt);
this.lastObservedAt=receivedAt;
if (this.last) {
if (timeMs <= this.last.timeMs || presentedFrames <= this.last.presentedFrames) this.resets++;
else {
const gap=timeMs-this.last.timeMs;
this.maxGap=Math.max(this.maxGap??0,gap);
if (this.intervals.length < 15000) this.intervals.push(gap);
else this.overflow++;
this.unobserved += Math.max(0,presentedFrames-this.last.presentedFrames-1);
}
}
this.last = {timeMs,presentedFrames}; this.callbacks++;
}
summary() {
// Once truncated, the lifetime percentile is unavailable. Long watches
// must not repeatedly sort an old 15,000-entry history they cannot use.
const sorted = this.overflow ? [] : [...this.intervals].sort((a,b)=>a-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};
}