diff --git a/docs/architecture/embedded-inline-playback.md b/docs/architecture/embedded-inline-playback.md index 1f65b2b51..781766308 100644 --- a/docs/architecture/embedded-inline-playback.md +++ b/docs/architecture/embedded-inline-playback.md @@ -472,17 +472,19 @@ interface is unavailable; exact external timestamps remain best-effort. The detail "…" menu's "Open in external player" sends the title to MPV/VLC through `PortalPlayer.openExternalPlayback(playback, player)` whatever the -configured player is. The launch IPC cannot be cancelled, and Electron -publishes the session only once it resolves, so every detail host keeps these -rules: +configured player is. The launch IPC cannot be cancelled, and until it +resolves the session is at most `launching`, with no exact closer. Every +detail host therefore keeps these rules: -- **One external player per title.** Before launching, the host closes the - external session it owns for that title. The `owns` predicate names only the - page's own content; any other session is left alone. With instance reuse - off, a second detached player would otherwise start beside the first. - Stalker hosts use `replaceOwnedExternalSession` from - `@iptvnator/portal/shared/util`; the Xtream pages use the equivalent - `closeRunningExternalSession`. +- **One external player per owner.** Before launching, the host closes the + external session the page owns: the session of the same title on the + Stalker pages and the Xtream series page; the session it launched, else the + one matching its movie, on the Xtream movie page. Sessions the page does not + own are left alone. With instance reuse off, a second detached player would + otherwise start beside the first. Stalker hosts use + `replaceOwnedExternalSession` from + `@iptvnator/portal/shared/util`; the Xtream pages use + `closeRunningExternalSession` with the same outcome rules. - **Unconfirmed teardown cancels the launch.** A live session without a closer, or a close that rejects, leaves the running player in place and nothing new launches. @@ -495,15 +497,19 @@ rules: other start of that title until the launch settles. Series pages hold the latest episode choice and, once the launch settled, replace the player it opened, only while that series is still on screen. -- **Pending state is owner-scoped.** A start still resolving, or a progress - reset still writing, holds the actions of its own title only: another title - shown by the reused page is not blocked by it. Movie hosts track starts with +- **Pending starts are owner-scoped.** A start still resolving holds the + actions of its own title only: another title shown by the reused page is + not blocked by it. Movie hosts track starts with `createPendingPlaybackStart` (`@iptvnator/portal/shared/util`): only the latest start may clear the flag, and `isPendingFor(owner)` answers for one - owner. Stalker hosts, whose starts wait on a portal round trip, also - `retire(owner)` when the selection leaves it, so a request that never - settles cannot hold a return to the same title. The one page-wide gate is - the Xtream movie page's external launch that has not settled yet. + owner. The Stalker movie hosts, whose starts wait on a portal round trip, + also `retire(owner)` when the selection leaves it, so a start that never + settles does not keep the flag set on a return to the same title. A movie's + "Reset progress" is scoped the same way. +- **Two gates are page-wide.** The Xtream movie page refuses every start + while an external launch it made has not settled. A series page runs one + watched or reset batch at a time, whichever series is shown; the Stalker + page also holds episode starts until that batch settles. Owner keys and queueing are provider contracts: diff --git a/docs/architecture/stalker-portal.md b/docs/architecture/stalker-portal.md index 3f7da7ed0..f49d0a4a1 100644 --- a/docs/architecture/stalker-portal.md +++ b/docs/architecture/stalker-portal.md @@ -1567,10 +1567,10 @@ the Stalker keys and queues are: Series (`StalkerSeriesViewComponent`, `stalker-series-launch-queue.ts`): -- Pending starts and held choices are keyed by `playlist:series` - (`currentSeriesKey`). The view is reused across series and provider ids - collide across playlists, so one series settling never drops what another - holds. +- Pending starts and the launch queue's held choices are keyed by + `playlist:series` (`currentSeriesKey`). The view is reused across series + and provider ids collide across playlists, so one series settling never + drops what another holds. - A start is pending for its series from the click until it settles. A forced launch stays pending through the close of the previous player, the launch and the release of a held choice. The pending flag disables the hero button @@ -1584,28 +1584,32 @@ Series (`StalkerSeriesViewComponent`, `stalker-series-launch-queue.ts`): release it is dropped when the series is no longer shown. Otherwise `replacePlayer` closes what the launch opened before the choice starts, and an unconfirmed close drops the choice. -- An episode chosen while a watched or reset batch runs is held until the - batch settles, then goes through the usual gates only on the series it was - made for: episode identities overlap across series. +- An episode chosen while a watched or reset batch runs is held in one slot + tagged with its series; the last choice wins. When the batch settles it + goes through the usual gates only if that series is still shown: episode + identities overlap across series. Movies (`createStalkerVodDetailActions`, used by the catalog detail, the collection detail and search): - A repeat for the same `playlist:movie` while its launch is in flight is - ignored. Launches of other movies are not held back. + ignored, also after leaving the movie and returning to it. Launches of + other movies are not held back. - The launch joins the host's starts (`beginPendingStart`): it supersedes an earlier start, is dropped once a later one begins, and keeps Play, Start over, the watched toggle and the menu rows disabled until it settles. - The resolved stream is discarded when the movie is no longer selected or a newer start took over. The selection check includes the content type, because movie and series ids collide. Otherwise the movie's own external - session is replaced, the inline player closes, and the launch is sent. A - launch that resolves after either condition changed closes the session it - opened; one that fails by then is not reported. + session is replaced, the host's `beforeExternalLaunch` hook runs (the + catalog and collection details close their inline player there), and the + launch is sent. A launch that resolves after either condition changed + closes the session it opened; one that fails by then is not reported. - "Reset progress" counts as a pending start of the movie until the write lands, so a start made meanwhile cannot resume from the row being cleared. - The pending start is owner-scoped (`createPendingPlaybackStart`). Each host - retires it when the selection leaves the owner. + retires it when the selection leaves the owner; that clears the pending + flag, not the repeat guard of a launch still in flight. Regression coverage: `stalker-series-launch-queue.spec.ts`, `stalker-series-view.component.spec.ts`, diff --git a/docs/architecture/vod-multi-source.md b/docs/architecture/vod-multi-source.md index 7eb13709a..11c13119b 100644 --- a/docs/architecture/vod-multi-source.md +++ b/docs/architecture/vod-multi-source.md @@ -776,10 +776,12 @@ rules are in (`VodDetailsPlaybackService.pendingResets`, `vod-details-reset-target.ts`), not one flag: the reused page can show another movie and come back, and resets of one copy can overlap, so each reset removes only its own entry. -- A start is refused (`startResolvedPlayback`), and Play, Start over and the - menu launch are disabled (`startBlocked`), while an external launch has not - settled or the list holds the copy the page currently acts on - (`resetTarget`). A reset still writing for another copy does not block it. +- A start is refused (`startResolvedPlayback`) while a launch this page made + has not settled or the list holds the copy the page currently acts on + (`resetTarget`). `startBlocked` disables Play, Start over and the menu + launch on those conditions and while a matched session is still + `launching`; the menu rows are also held while a start is pending. A reset + still writing for another copy does not block it. ## Provider codec metadata diff --git a/docs/architecture/xtream-portal-compatibility.md b/docs/architecture/xtream-portal-compatibility.md index c9eda0965..5056fdb53 100644 --- a/docs/architecture/xtream-portal-compatibility.md +++ b/docs/architecture/xtream-portal-compatibility.md @@ -424,8 +424,8 @@ The series page keeps its launch state at module level in page left the owner closes the session it opened. - The duplicate guard is keyed by page token plus episode. The token (`pageToken()`) is owner, page instance and visit, so a launch left behind - by an earlier visit of the same series does not swallow the reopened page's - click; that click queues on the owner's chain. + by an earlier visit of the same series does not swallow a launch from the + reopened page; that launch queues on the owner's chain. - While a forced launch of the owner is pending (`forcedLaunchPending`), a start that does not force a player is queued instead of started. One choice is kept per owner, the latest wins, and it carries the host and `start` of @@ -438,6 +438,7 @@ The series page keeps its launch state at module level in - The launch-position marker and a launch-failure message apply only while the page token is unchanged. -Regression coverage: `serial-details-external-launch.spec.ts`, -`serial-details-playback.service.spec.ts` and -`serial-details-menu.service.spec.ts`. +Regression coverage: `serial-details-external-launch.spec.ts` (chain, +duplicate guard, queued choice), `serial-details-playback.service.spec.ts` +(page token) and, for the menu rows, +`libs/ui/components/src/lib/detail-ui/series-hero.state.spec.ts`.