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
| M | frontend/ | 28 | ++++++++++++++++++++++++++-- |
| M | frontend/ | 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; }@@ -246,6 +246,8 @@
246246 async StopTV() {247247 try { await call('POST', '/api/stop', { client: load().clientId }); } catch { /* as the desktop app: ignored */ }248248 },249+ // How long a channel change took, for the server's admin page.250+ ReportTune: (m) => call('POST', '/api/metrics', m).then(() => undefined),249251 DVR: () => call('GET', '/api/dvr'),250252 Record: (req) => call('POST', '/api/dvr/record', req),251253 DeleteRule: (id) => call('DELETE', `/api/dvr/rules/${enc(id)}`).then(() => undefined),