Coalesce concurrent resolveStreams calls for the same video

This commit is contained in:
Claude
2026-09-30 06:50:31 +00:00
parent 617df320c7
commit e2a60792b9
3 changed files with 22 additions and 2 deletions

View File

@@ -11,7 +11,7 @@ green, app boots with no JS errors, P2P on by default, offline boot works).
| 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 | done | Serve the app shell gzip/brotli-compressed with ETags | 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 | done | Self-host the three web fonts and drop the render-blocking Google Fonts CSS | needs network once; CSP font-src fix |
| 004 | 004-coalesce-stream-resolves-a92d40 | Coalesce concurrent resolveStreams calls for the same video | in-progress | | |
| 004 | 004-coalesce-stream-resolves-a92d40 | Coalesce concurrent resolveStreams calls for the same video | done | Coalesce concurrent resolveStreams calls for the same video | |
| 005 | 005-warm-streams-on-intent-47b3d3 | Warm the stream cache for likely next plays | queued | | cold play 7.5 s → cached 1.2 s |
| 006 | 006-innertube-search-48066b | Answer searches from YouTube InnerTube directly with yt-dlp fallback | queued | | search 4–5 s → ~0.8 s |
| 007 | 007-ytdlp-worker-045800 | Keep one long-lived yt-dlp worker process instead of spawning per call | queued | | ~1 s per yt-dlp call |

View File

@@ -83,3 +83,9 @@ Output ONLY the following, no other prose:
3. `Findings:` — max 10 lines.
Do not commit. Do not push. Do not touch files outside the Steps.
## Execution log
- Executor: in-session Agent (haiku). Attempts: 1. Fix rounds: 0.
- Orchestrator re-ran Verification: both functions and `inflightStreams` present, `bun build` ok, `bun run test` all 5 server files 0 fail (media-cache 23 pass is the original baseline).
- Executor Findings (verbatim): Plan executed successfully. Both renamed function and wrapper present in correct location. In-flight map correctly coalesces concurrent calls. All 58 server tests pass. Build completes with no errors. No deviations from plan requirements.

View File

@@ -459,7 +459,7 @@ function parseExpiry(url) {
// Resolve (and briefly cache) a video's playable formats via yt-dlp -J. The
// cache spares a fresh ~2-3s yt-dlp run on every Range request the media
// element fires; its TTL is bounded a minute inside the URLs' own expiry.
async function resolveStreams(videoId) {
async function resolveStreamsUncached(videoId) {
const now = Date.now();
const cached = streamCache.get(videoId);
if (cached && now < cached.expiresAt) return cached;
@@ -498,6 +498,20 @@ async function resolveStreams(videoId) {
return entry;
}
// One yt-dlp -J per video at a time: concurrent callers (warm-up + play,
// two devices, the media cache's getInfo) share the in-flight promise.
const inflightStreams = new Map(); // videoId -> Promise<entry>
function resolveStreams(videoId) {
const cached = streamCache.get(videoId);
if (cached && Date.now() < cached.expiresAt) return Promise.resolve(cached);
let p = inflightStreams.get(videoId);
if (!p) {
p = resolveStreamsUncached(videoId).finally(() => inflightStreams.delete(videoId));
inflightStreams.set(videoId, p);
}
return p;
}
const isVideoFmt = (f) => f.vcodec && f.vcodec !== 'none';
const isAudioFmt = (f) => f.acodec && f.acodec !== 'none';