diff --git a/docs/episode-transition-stray-pause.md b/docs/episode-transition-stray-pause.md new file mode 100644 index 0000000..1918d19 --- /dev/null +++ b/docs/episode-transition-stray-pause.md @@ -0,0 +1,169 @@ +# Stray pause at an episode transition (open investigation) + +> **Status:** Open. Root cause **not** confirmed — blocked on logs from both sides while it +> happens. Everything below marked *verified* was read from the code; everything marked +> *hypothesis* was not observed yet. Do not "fix" this from the hypotheses alone. +> +> Reported: 2026-08-15, against v3.1.0. Affected platform supports episode auto-sync +> (parseable `SxxEyy` titles) — title *parsing* is explicitly **not** the problem here. + +--- + +## Symptom + +Two peers watch a series together. At the end of an episode, one of them moves on to the +next episode while the other stays **paused at the end of the previous one** and never +recovers. Happens intermittently, not on every transition. + +## Reported sequence + +1. Peer A finishes episode 1, the player advances to episode 2. +2. A detects the transition, opens an episode lobby and pauses itself in episode 2, + waiting for the others. +3. Peer B is still a few seconds from the end of episode **1**. +4. B receives a `PAUSE` and applies it — although that pause originates from a peer that + is already on episode 2, so it should have been discarded. +5. B is now paused before its own transition, so its player never auto-advances, so B + never reports ready. A's lobby runs into the 60 s timeout and is cancelled. + From then on A and B are on different episodes and the guard (correctly) blocks + everything, so nothing recovers. + +## What the code does today (verified) + +**The episode guard already exists and already runs before anything is applied.** +[`content.js:1197-1213`](../extension/content.js) is the first thing in the +`SERVER_COMMAND` branch (after the host-control desync check). It compares the sender's +title from the payload against the local title and skips `PLAY`/`PAUSE`/`SEEK`/ +`FORCE_SYNC_*` on mismatch. + +**The title is already shipped with every content-script command.** `sendContentEvent` +attaches `mediaTitle` to play/pause/seek ([`content.js:1450-1469`](../extension/content.js)), +and `routeToContent` forwards the payload to the content script unchanged +([`background.js:2576`](../extension/background.js)). *Adding* the title to these events is +therefore not an available fix — it is already there. + +**The guard is permissive by design.** `isDifferentEpisode` +([`content.js:897-903`](../extension/content.js)) returns `false` — i.e. "allow" — when +either side has no title or no parseable episode ID. This is deliberate: Netflix exposes +no media title at all, JkAnime none, Disney+ only the series name (see +[TESTED_SERVICES.md](TESTED_SERVICES.md)). Making the comparison mandatory would silently +break sync on those platforms. + +**Titles come from `navigator.mediaSession.metadata`** +([`content.js:863`](../extension/content.js)) — a page-level signal that is *not* coupled +to what the `