Record coordinated reception results and prepare scoped web H264 option

This commit is contained in:
2026-10-12 23:57:10 +02:00
parent 5c10d8b9b8
commit a7e44efdf7
15 changed files with 12390 additions and 17 deletions
+28
View File
@@ -0,0 +1,28 @@
// Summarize measured client smoothness separately from upstream/transport checks.
const fs = require('node:fs');
const input = process.argv[2];
if (!input) throw Error('Usage: node analyze-reception-trial.cjs report.json [summary.json]');
const report = JSON.parse(fs.readFileSync(input, 'utf8'));
const round = x => Number(x.toFixed(3));
const viewers = report.viewers.map(viewer => {
const rows = viewer.samples.filter(row => row.video);
if (rows.length < 2) return { viewer: viewer.viewer, status: 'INSUFFICIENT_DATA' };
const first = rows[0], last = rows.at(-1);
const seconds = (Date.parse(last.utc) - Date.parse(first.utc)) / 1000;
const media = last.video.time - first.video.time;
const frames = last.video.frames - first.video.frames, dropped = last.video.dropped - first.video.dropped;
let waiting, waitingSeconds = [], unmatched = 0;
for (const row of viewer.samples) for (const event of row.events || []) {
if (event.at < Date.parse(first.utc) || event.at > Date.parse(last.utc)) continue;
if (event.type === 'waiting' && waiting === undefined) waiting = event.at;
if (event.type === 'playing' && waiting !== undefined) { waitingSeconds.push((event.at - waiting) / 1000); waiting = undefined; }
}
if (waiting !== undefined) unmatched++;
waitingSeconds.sort((a,b) => a-b);
const totalWait = waitingSeconds.reduce((a,b) => a+b,0);
const realtime = media / seconds, dropFraction = frames > 0 ? dropped / frames : null;
return { viewer: viewer.viewer, sampled_seconds: round(seconds), media_progress_seconds: round(media), realtime_ratio: round(realtime), total_frame_delta: frames, dropped_frame_delta: dropped, dropped_fraction: dropFraction === null ? null : round(dropFraction), approximate_presented_fps: round((frames-dropped)/seconds), waiting_events: waitingSeconds.length, waiting_seconds: round(totalWait), waiting_median_seconds: waitingSeconds.length ? round(waitingSeconds[Math.floor(waitingSeconds.length/2)]) : null, waiting_max_seconds: waitingSeconds.length ? round(waitingSeconds.at(-1)) : null, waiting_unmatched: unmatched, minimum_buffer_seconds: round(Math.min(...rows.map(row=>row.video.buffered_ahead))), audio_decoded_byte_delta: first.video.audio_bytes === null || last.video.audio_bytes === null ? null : last.video.audio_bytes-first.video.audio_bytes, status: realtime >= 0.98 && totalWait/seconds <= 0.02 && dropFraction !== null && dropFraction <= 0.05 ? 'SMOOTH_TEST_CLIENT' : 'CLIENT_NOT_SMOOTH', scope: 'Headless test client only. Frame presentation is approximated; audible sound and other devices are not verified.' };
});
const summary = { input, plugin: report.snapshots.find(s=>s.plugin)?.plugin, isolated_bridge_override: report.isolated_bridge_override || false, channel: report.channel, first_video_utc: report.first_video_utc, finished_utc: report.finished_utc, transport_checks: { pass: report.pass, fail: report.fail, unverified: report.unverified || 0 }, viewers };
if (process.argv[3]) fs.writeFileSync(process.argv[3], JSON.stringify(summary,null,2)+'\n');
console.log(JSON.stringify(summary,null,2));