Repository navigation
feat(detection): play the stream the page's own video played - #67
Conversation
Of a page's streams, the player played the longest. Now it first asks what the page's <video> that it replaced was playing: its file, when the video plays a detected file, or else the stream whose measured length matches the video's (within 3 s or 0.25%), as an MSE player sets it from the manifest. A page that loads the next episode, or another longer stream, beside the video no longer gets that one played instead. Never over a far longer stream: when a stream five times as long plays (or a live one), the video is an ad, a trailer or a preview as likely as not, and the longest decides, as before. So does a video that tells nothing: no video (the player fills the page), a box with several videos, or a length that matches no stream. - content.js: snapshots the replaced video's currentSrc and duration, and answers GET_PLAYED_VIDEO for the asking player (its iframeMap entry, or the queue before it linked up) with the live values while the video still plays the same thing. - background.mjs: sendSources asks after the lengths are read, then waits for the sources detected meanwhile only. The mpv pick passes the played video from the user's play event. - StreamPick.mjs: the rule, shared by the player and the mpv pick. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unread length Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GLM's review of the pick: a 30-second pre-roll in the page's video, before a 2:10 clip, played the ad. The clip is not five times as long, so the 5x guard kept the ad. Ads, trailers and previews run a few minutes at most: a video under three minutes now chooses only among streams no longer than it (as long, within the match's tolerance, counts: another rendition). At three minutes or more, the 5x guard as before. The e2e film is now the fixture played twice over (320 s), and the MSE clip 4.5 minutes, both over three minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Content scripts share the page's event loop, so a page that keeps its scripts busy answered GET_PLAYED_VIDEO late, and one that never lets go held the player without sources. Before stream-pick, sendSources never waited on the page. Now the longest decides after 1 s, as before the question. GLM 5.3's review of the rebased branch. Checked on a build whose content.js never answers: the source-length spec's players all got their sources; only the two cases that need the answer failed, playing the longest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6d01367 to
ef32b6e
Compare
|
On hold (your call, 2026-09-30). Tonight's batch went in without this PR. It merges on its own once the failure below is explained. What failed. CI run 36639172303 at ef32b6e:
Every later test in the file failed only because the tab was left in the wrong mode. Against it being this PR:
For it being this PR: both failures follow a switch between the in-page player and mpv with a page reload, and this PR makes the in-page player wait longer for its sources: up to 1 s for the page's answer, plus up to 2.5 s for sources found meanwhile. GLM 5.3 flash looked for a mechanism and found none that the code supports; its guess is a race that the longer wait widens. Next:
|
…player of the new page (#79) Ctrl+Shift+U with the in-page player just opened reloads the page (startMpv). A player still starting then could say it loaded (PLAYER_LOADED) after the new page's FRAME_ADDED: its beforeunload, which sends FRAME_REMOVED, is only added once it has loaded, and the dying out-of-process iframe kept running. The background took it for a player of the new page, so frame 0 had a player: onHeadersReceived dropped every stream the page requested as the player's own, and openPlayer refused, until the page navigated again. mpv got nothing on a play, and Ctrl+Shift+F opened nothing. That is main's red CI (run 36662468457, e2e Windows, mpv-shortcut): the takeover test's play reached no mpv, and on the retry the key-switch test's second Ctrl+Shift+F showed no player. The CDN saw no length probe of the reloaded page's streams, which every detection starts while the tab is on: they were never detected. The same two failures held #67's own run. Since #67 the spec failed so in 4 of 5 attempts, and in none of ~40 runs before; locally it never did (the key lands before the player loads, or after it has added its beforeunload). - content.js names its page (DocumentKey; crypto.getRandomValues, as randomUUID needs a secure context), in FRAME_ADDED and as the player URL's opener. - TabHolder.isPlayerOfGoneDocument: the opener is neither the page in the player's own frame (the redirect) nor the one in the frame above (an iframe). A frame whose page never named itself to this background (it restarted since, or the new page's FRAME_ADDED has not come yet) proves nothing, and the player counts. - background.mjs answers such a PLAYER_LOADED with null and forgets its frame. - mpv-shortcut: a page holding a player that names a gone page, then its own video: the toolbar opens a player for it. On main it fails, and the background trace shows the state CI had (the player attached to frame 0 after FRAME_ADDED, the clip dropped by hasPlayer); here it passes, with the whole spec (12/12). The unit tests fail on each of 5 mutations of the check. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Of a page's streams, the player has played the longest since #50. Now it first asks what the page's
<video>that it replaced was playing, and plays that stream. This fixes three kinds of wrong pick:When the page's video could be an ad, a trailer or a preview, the longest still decides, as before. The player may wait a moment longer, for one message round trip to the page (at most 1 s), and only when there is more than one stream.
This changes which stream plays. As you decided tonight, it merges on green CI, and you test the release in your own Firefox later.
Why
You asked for a better way than length to pick the stream you want, and chose the "never worse" version. In any case where the page's video could be an ad, a trailer or a preview, the old rule (the longest) decides.
This builds on #61 and #66 (a piece of a stream still being read no longer plays), both merged. Both change the same two lines in
background.mjs, where the lengths are taken.The rule
Before it picks, the player asks what the page's
<video>that it replaced was playing:The never-worse part. The old rule (the longest) decides instead:
By file:
chrome/player/utils/StreamPick.mjs: the rule. The player and the mpv pick share it.chrome/content.js:currentSrcand length when it replaces it.GET_PLAYED_VIDEOfor the player that asks. It sends the video's current values while the video still plays the same thing, and otherwise the recorded ones.chrome/background/background.mjs:sendSourcesasks the page after the lengths are read, then waits only for sources detected in the meantime. It waits at most 1 s for the answer: content scripts share the page's event loop, and a page that keeps it busy would otherwise hold the player without sources.frameId. Once a player's iframe links up, its data moves fromreplacedPlayerQueuetoiframeMap. The first version only looked in the queue and so never got an answer; the e2e caught this.What stays as before
The old rule decides in these cases, on purpose:
Tests
Unit,
tests/unit/StreamPick.test.mjs: 15 cases. 17 of 17 mutants ofStreamPick.mjsfail them, each removing or shifting one part: the 3-minute and 5x limits, the match tolerance, the live rule, and the others.e2e,
tests/e2e/ext-specs/source-length.e2e.mjs, 3 new cases:MediaSource, as hls.js does: the 4.5-minute stream of that length plays, not the 12-minute one;The existing 5 cases still pass. On a build where the page's answer never arrived, the first two new cases failed: they test the new rule.
Review: GLM 5.3 reviewed the design and the code three times. Its findings reshaped the rule:
Local, on fix(detection): a piece whose length is still being read no longer plays #66: lint, typecheck, 638 unit tests, the build, and the whole source-length spec in Firefox 156: 8 of 8 pass. That run was before the 1 s cap; CI runs the spec with it.
The classic e2e specs (
test:ext, 9 files): in the first run on this branch,mpv-suspendfailed twice with a hand-off of the same page. It passed 2 of 2 on its own, and then the whole set passed 9 of 9 on this branch and on main, run back to back. This PR leaves alone the code that stops a second hand-off of a page, which is what that spec tests, so it was a flaky run.Not in this PR
🤖 Generated with Claude Code