clone

a desktop tv and program guide for over-the-air broadcasts: what's on air, what your tuner gets, live tv, recordings

Time channel changes in the app, and clear the static on the first frame

FelineStateMachine committed 22cd865
The app tells the server how long each channel change took, from the key
to the picture playing, for the admin page's times, and the tuning
static clears as the first frame shows rather than a fixed moment after
playback starts.
commit
22cd8657fa68c8051917993a3c010e40246b5b8a
parent
c21939bb04a07a3ab5d0f506631959110a465293
author
FelineStateMachine <226808055+FelineStateMachine@users.noreply.github.com>
kind
feat risk score 1.6 of 3, Jev's estimate
2 files changed, +28 -2
Mfrontend/dist/app.js28++++++++++++++++++++++++++--
Mfrontend/dist/web.js2++
M frontend/dist/app.js +26 -2
@@ -568,7 +568,9 @@ async function toggleFullscreen() {
568568 }569569 570570 // ---------- tuning & playback ----------571-async function tune(ch, { quiet = false } = {}) {571+// at is when the viewer asked for the channel (a key press), for timing572+// the change.573+async function tune(ch, { quiet = false, at = performance.now() } = {}) {572574 if (!ch) return;573575 if (state.recording) saveRecordingProgress();574576 if (state.current && state.current.key !== ch.key) state.previous = state.current;
@@ -589,11 +591,14 @@ async function tune(ch, { quiet = false } = {}) {
589591 // The server ends this screen's stream as it starts the next: the590592 // picture plays out what it has, without asking for more of it.591593 if (state.hls) state.hls.stopLoad();594+ let played = false;592595 try {593596 const pb = await api().Tune(ch.number);594597 if (token !== state.tuneToken) return;595598 state.note = pb.note || '';596599 await play(pb, token);600+ played = token === state.tuneToken;601+ if (played) reportTune(ch, performance.now() - at);597602 } catch (e) {598603 if (token !== state.tuneToken) return;599604 log('warn', `tune ${ch.number}: ${e}`);
@@ -601,11 +606,30 @@ async function tune(ch, { quiet = false } = {}) {
601606 // The server read the tuner while it tried.602607 if (!ch.own) loadSignal();603608 } finally {604- if (token === state.tuneToken) setTimeout(() => stage.classList.remove('tuning'), 200);609+ // The static clears as the first frame shows.610+ if (played) onFirstFrame(() => { if (token === state.tuneToken) stage.classList.remove('tuning'); });611+ else if (token === state.tuneToken) setTimeout(() => stage.classList.remove('tuning'), 200);605612 }606613 renderBanner();607614 }608615 616+// onFirstFrame calls fn once the video's first frame is on screen, where617+// the browser can tell (requestVideoFrameCallback), else a moment after618+// playback starts; within a second either way.619+function onFirstFrame(fn) {620+ let done = false;621+ const once = () => { if (!done) { done = true; fn(); } };622+ if (video.requestVideoFrameCallback) video.requestVideoFrameCallback(once);623+ setTimeout(once, video.requestVideoFrameCallback ? 1000 : 200);624+}625+626+// reportTune tells the server how long a channel change took, from the627+// key to the picture playing, for the times on its admin page.628+function reportTune(ch, ms) {629+ if (!api().ReportTune) return;630+ api().ReportTune({ number: ch.number, kind: ch.own ? ch.kind : 'antenna', firstFrameMs: Math.round(ms) }).catch(() => {});631+}632+609633 function resetPlayer() {610634 state.liveBaseline = null;611635 if (state.hls) { state.hls.destroy(); state.hls = null; }
M frontend/dist/web.js +2 -0