Add startup, search and play timing marks plus yt-dlp duration logs

This commit is contained in:
Claude
2026-09-30 06:41:50 +00:00
parent 7b818baf8d
commit 7781e0a116
4 changed files with 32 additions and 1 deletions

View File

@@ -23,6 +23,18 @@ const ZERO = window.zero && typeof window.zero.invoke === 'function' ? window.ze
const WEB = !TAURI && !ZERO; const WEB = !TAURI && !ZERO;
const APP_VERSION = '1.0.0'; const APP_VERSION = '1.0.0';
// Timing marks for the speed work (plans/). performance.measure entries are
// visible in DevTools → Performance; __ytpPerf() prints the latest ones.
function perfMark(name) { try { performance.mark(name); } catch { /* old browser */ } }
function perfMeasure(name, start) {
try { performance.measure(name, start); } catch { /* start mark missing */ }
}
window.__ytpPerf = () => {
const out = {};
try { for (const m of performance.getEntriesByType('measure')) if (m.name.startsWith('ytp:')) out[m.name] = Math.round(m.duration); } catch { /* none */ }
return out;
};
// call(zeroName, tauriName, payload) — routes to whichever native shell is present. // call(zeroName, tauriName, payload) — routes to whichever native shell is present.
async function call(zeroName, tauriName, payload = {}) { async function call(zeroName, tauriName, payload = {}) {
if (TAURI) return await TAURI.invoke(tauriName, payload); if (TAURI) return await TAURI.invoke(tauriName, payload);
@@ -1649,6 +1661,7 @@ const Player = {
}, },
async loadVideo(videoObj, { preferStream = false, resume = true, reveal = true, nocache = false } = {}) { async loadVideo(videoObj, { preferStream = false, resume = true, reveal = true, nocache = false } = {}) {
perfMark('ytp:tap');
if (!this._handoff) Transition.cancel(); if (!this._handoff) Transition.cancel();
// When false (auto-advance / prev), skip resuming the saved timestamp and // When false (auto-advance / prev), skip resuming the saved timestamp and
// start from the beginning (or the A marker, if an A-B loop is set). // start from the beginning (or the A marker, if an A-B loop is set).
@@ -2286,6 +2299,10 @@ function wirePlayerEvents() {
}); });
el.addEventListener('playing', () => { el.addEventListener('playing', () => {
if (!masterIs(el)) return; if (!masterIs(el)) return;
if (performance.getEntriesByName('ytp:tap').length) {
perfMeasure('ytp:tap-to-play', 'ytp:tap');
try { performance.clearMarks('ytp:tap'); } catch { /* ignore */ }
}
showSpinner(false); showSpinner(false);
Player.clearBufferGrace(); Player.clearBufferGrace();
if (Player.secondary && !el.paused && Player.secondary.paused) { if (Player.secondary && !el.paused && Player.secondary.paused) {
@@ -8584,6 +8601,7 @@ function wireUI() {
q = (q || '').trim(); q = (q || '').trim();
if (!q) return; if (!q) return;
const mySeq = ++searchSeq; const mySeq = ++searchSeq;
perfMark('ytp:search-start');
RecentSearches.add(q); RecentSearches.add(q);
hideRecentSearches(); hideRecentSearches();
view = { type: 'search' }; view = { type: 'search' };
@@ -8607,6 +8625,7 @@ function wireUI() {
searchAttempted = true; searchAttempted = true;
renderList(); renderList();
RecentSearches.cacheResults(q, results); RecentSearches.cacheResults(q, results);
perfMeasure('ytp:search', 'ytp:search-start');
} catch (err) { } catch (err) {
if (mySeq !== searchSeq) return; if (mySeq !== searchSeq) return;
if (instant) return; // keep showing the cached results; fail silently if (instant) return; // keep showing the cached results; fail silently
@@ -9747,6 +9766,7 @@ function bootSearchFromUrl() {
// Boot // Boot
// ============================================================================ // ============================================================================
async function boot() { async function boot() {
perfMark('ytp:boot-start');
wirePlayerEvents(); wirePlayerEvents();
wireUI(); wireUI();
wireShortcutHelp(); wireShortcutHelp();
@@ -9795,6 +9815,7 @@ async function boot() {
// first, which left the sidebar empty for as long as the network took. // first, which left the sidebar empty for as long as the network took.
renderSmartSidebar(); renderSmartSidebar();
render(); render();
perfMeasure('ytp:boot', 'ytp:boot-start');
// …then reconcile with the server in the background, with the spinner up, // …then reconcile with the server in the background, with the spinner up,
// and re-render only if something actually changed. // and re-render only if something actually changed.

View File

@@ -8,7 +8,7 @@ green, app boots with no JS errors, P2P on by default, offline boot works).
| Seq | ID | Title | Status | Commit | Notes | | Seq | ID | Title | Status | Commit | Notes |
|-----|----|-------|--------|--------|-------| |-----|----|-------|--------|--------|-------|
| 001 | 001-perf-timing-marks-105acc | Add startup, search and play timing marks plus yt-dlp duration logs | in-progress | | baseline numbers for the rest | | 001 | 001-perf-timing-marks-105acc | Add startup, search and play timing marks plus yt-dlp duration logs | done | Add startup, search and play timing marks plus yt-dlp duration logs | baseline numbers for the rest |
| 002 | 002-compress-and-etag-shell-bd459c | Serve the app shell gzip/brotli-compressed with ETags | queued | | app.js 426 KB → 94 KB (br) | | 002 | 002-compress-and-etag-shell-bd459c | Serve the app shell gzip/brotli-compressed with ETags | queued | | app.js 426 KB → 94 KB (br) |
| 003 | 003-self-host-fonts-89466b | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | queued | | needs network once; CSP font-src fix | | 003 | 003-self-host-fonts-89466b | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | queued | | needs network once; CSP font-src fix |
| 004 | 004-coalesce-stream-resolves-a92d40 | Coalesce concurrent resolveStreams calls for the same video | queued | | | | 004 | 004-coalesce-stream-resolves-a92d40 | Coalesce concurrent resolveStreams calls for the same video | queued | | |

View File

@@ -121,3 +121,9 @@ Output ONLY the following, no other prose:
skipped and why. skipped and why.
Do not commit. Do not push. Do not touch files outside the Steps. Do not commit. Do not push. Do not touch files outside the Steps.
## Execution log
- Executor: in-session Agent (haiku), gateway `delegate.sh` not installed. Attempts: 1. Fix rounds: 0.
- Orchestrator re-ran Verification: syntax OK, `SERVER_OK`, `node --test frontend/*.test.js` 52 pass / 0 fail, mark count 8.
- Executor Findings (verbatim): All 6 plan steps applied successfully. Frontend timing marks added for boot (start->first paint), search (start->cached), tap-to-play (tap->playing event). Server logs yt-dlp duration on every invocation. Verification: syntax OK, server builds, unit tests pass (0 failures), 8 perfMark/perfMeasure occurrences (exceeds required 8). No deviations or skips.

View File

@@ -143,6 +143,9 @@ const CHANNEL_LIMIT = 60;
function runYtdlp(args, { signal } = {}) { function runYtdlp(args, { signal } = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const child = spawn(YTDLP, args, { stdio: ['ignore', 'pipe', 'pipe'] }); const child = spawn(YTDLP, args, { stdio: ['ignore', 'pipe', 'pipe'] });
const t0 = Date.now();
const kind = String(args.find((a) => /^ytsearch|^https?:/.test(String(a))) || args[0] || '')
.replace(/^ytsearch\d*:.*/, 'search').replace(/^https?:\/\/[^/]+\/watch.*/, 'video').slice(0, 40);
// Kill the download when the requesting client goes away — otherwise an // Kill the download when the requesting client goes away — otherwise an
// aborted/retried save leaves yt-dlp running to completion (8 copies of // aborted/retried save leaves yt-dlp running to completion (8 copies of
// one video were found pulling in parallel after the client retried). // one video were found pulling in parallel after the client retried).
@@ -158,6 +161,7 @@ function runYtdlp(args, { signal } = {}) {
child.stderr.on('data', (d) => { err += d; }); child.stderr.on('data', (d) => { err += d; });
child.on('error', (e) => reject(new Error('yt-dlp not found: ' + e.message))); child.on('error', (e) => reject(new Error('yt-dlp not found: ' + e.message)));
child.on('close', (code) => { child.on('close', (code) => {
console.log(`[ytdlp] ${kind} ${Date.now() - t0}ms ${code === 0 ? 'ok' : 'fail'}`);
if (code !== 0) reject(new Error(err.trim() || 'yt-dlp exited with code ' + code)); if (code !== 0) reject(new Error(err.trim() || 'yt-dlp exited with code ' + code));
else resolve(out); else resolve(out);
}); });