mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
docs(portals): document forced MPV/VLC launch and pending-start contracts
The rules #1792 settled for forced external launches and playback-start bookkeeping lived only in code comments and specs. Record them as contracts in the owning documents: - embedded-inline-playback.md: the shared detail-host rules (one external player per title, unconfirmed teardown cancels, ownership rechecked after every await, owner-scoped pending state). - xtream-portal-compatibility.md: the series launch chain, page-token duplicate guard and queued choice. - vod-multi-source.md: the movie menu launch and reset follow the copy the primary button acts on; pending resets are a list of targets. - stalker-portal.md: the per-series launch queue, the batch-held choice and the movie launch/reset pending start. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
e8b181fcea
commit
b87c88833b
4 files changed
+162
No files matched your search
@@ -468,6 +468,51 @@ position telemetry overwrites this launch marker when available. This keeps the
|
||||
last-watched season and episode correct even when an external player's progress
|
||||
interface is unavailable; exact external timestamps remain best-effort.
|
||||
|
||||
## Forced External Launches From Detail Pages
|
||||
|
||||
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:
|
||||
|
||||
- **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`.
|
||||
- **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.
|
||||
- **Ownership is rechecked after every await.** Stream resolution, the close
|
||||
and the launch IPC can each outlive the page or be superseded by a newer
|
||||
start. A stale step stops without reporting, and a launch that resolves
|
||||
stale closes the session it just opened.
|
||||
- **No second player while a launch settles.** A repeat of the same launch is
|
||||
ignored, or its control stays disabled. Movie pages refuse or disable every
|
||||
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
|
||||
`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 keys and queueing are provider contracts:
|
||||
|
||||
| Host | Contract |
|
||||
| --- | --- |
|
||||
| Xtream series | [Forced external launches from detail pages](./xtream-portal-compatibility.md#forced-external-launches-from-detail-pages) |
|
||||
| Xtream movie | [Menu launch and reset follow the primary button](./vod-multi-source.md#menu-launch-and-reset-follow-the-primary-button) |
|
||||
| Stalker series and movies | [Forced External Launches](./stalker-portal.md#forced-external-launches) |
|
||||
|
||||
## Series Quick Start CTA
|
||||
|
||||
Xtream and Stalker series detail views share the quick-start decision helper in
|
||||
|
||||
@@ -1558,6 +1558,63 @@ Core decision logic and normalization are centralized in:
|
||||
- `libs/portal/stalker/data-access/src/lib/stalker-vod.utils.ts`
|
||||
- `libs/portal/stalker/data-access/src/lib/models/*.ts`
|
||||
|
||||
## Forced External Launches
|
||||
|
||||
"Open in external player" needs a `create_link` round trip before it reaches
|
||||
MPV/VLC. The shared rules are in
|
||||
[Forced External Launches From Detail Pages](./embedded-inline-playback.md#forced-external-launches-from-detail-pages);
|
||||
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.
|
||||
- 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
|
||||
and the menu's external-player and watched rows.
|
||||
- Before launching, an episode of the same series still running externally is
|
||||
closed (`replaceOwnedExternalSession`). The request is rechecked after that
|
||||
close and after the launch IPC; a superseded launch closes the session it
|
||||
opened.
|
||||
- An episode chosen while a forced launch of its series is mid-flight is held
|
||||
(`StalkerSeriesLaunchQueue.hold`); the latest choice per series wins. On
|
||||
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.
|
||||
|
||||
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.
|
||||
- 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.
|
||||
- "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.
|
||||
|
||||
Regression coverage: `stalker-series-launch-queue.spec.ts`,
|
||||
`stalker-series-view.component.spec.ts`,
|
||||
`stalker-series-view.season-watch.spec.ts`,
|
||||
`stalker-vod-detail-actions.spec.ts`,
|
||||
`stalker-vod-playback-controller.spec.ts` and, in
|
||||
`libs/portal/shared/util/src/lib/`, `pending-playback-start.spec.ts` and
|
||||
`replace-owned-external-session.spec.ts`.
|
||||
|
||||
## Favorites and Recently Viewed
|
||||
|
||||
Current implementation is shared via Stalker-specific helpers:
|
||||
|
||||
@@ -757,6 +757,30 @@ lookup comes back empty, because "never watched" is an answer: the button must
|
||||
read Play, not `Resume 42:18` on a stream that starts at zero. A pin on the
|
||||
route's own row changes nothing; the loaded position already IS that copy's.
|
||||
|
||||
## Menu launch and reset follow the primary button
|
||||
|
||||
The "…" menu acts on the copy the primary button acts on. The shared launch
|
||||
rules are in
|
||||
[Forced External Launches From Detail Pages](./embedded-inline-playback.md#forced-external-launches-from-detail-pages).
|
||||
|
||||
- "Open in external player" and "Start over" are host-owned
|
||||
(`VodDetailsMenuBindings.openExternal` / `restart`); the menu service never
|
||||
builds a playback itself. The route forces MPV/VLC for the pinned copy from
|
||||
that copy's own resume point (`playPinnedSource` with `player` and
|
||||
`replacePlaying`), also while that copy already plays: it is relaunched,
|
||||
never swapped for the route copy. Only an `unavailable` pin falls through
|
||||
to the route copy's Resume or Play.
|
||||
- "Reset progress" clears the row of `primaryTarget`: the pinned copy's own
|
||||
row, otherwise the route copy's.
|
||||
- Resets in flight are a list of targets
|
||||
(`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.
|
||||
|
||||
## Provider codec metadata
|
||||
|
||||
`info.video` / `info.audio` come back in two shapes: the declared string array
|
||||
|
||||
@@ -405,3 +405,39 @@ deduplicated list, `hasMoreContent` derives from accumulated length vs
|
||||
and the facade maps page 0 to the skeleton and later pages to the tail
|
||||
spinner. These catalog/search surfaces use incremental loading instead of
|
||||
page buttons.
|
||||
|
||||
## Forced external launches from detail pages
|
||||
|
||||
The "…" menu's MPV/VLC launch follows the shared rules in
|
||||
[Forced External Launches From Detail Pages](./embedded-inline-playback.md#forced-external-launches-from-detail-pages).
|
||||
The movie page's pin and reset rules are in
|
||||
[VOD Multi-Source](./vod-multi-source.md#menu-launch-and-reset-follow-the-primary-button).
|
||||
|
||||
The series page keeps its launch state at module level in
|
||||
`serial-details-external-launch.ts`, so it outlives a recreated page:
|
||||
|
||||
- The owner is `playlist:series` (`launchOwner()`). It changes when the page
|
||||
shows another series and is null once the page is gone.
|
||||
- Forced launches of one owner run on one chain. A later launch waits for the
|
||||
earlier one to settle, closes the owner's running episode session and then
|
||||
launches. Each step rechecks the owner, and a launch that resolves after the
|
||||
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.
|
||||
- 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
|
||||
the page that made it. Once the chain settles, the player the launch opened
|
||||
is closed first while the owner stays pending. The choice is dropped when
|
||||
that page no longer shows the owner or the close was not confirmed.
|
||||
- The pending flag also disables the menu's external-player row and the
|
||||
season and series watched actions, and counts as active playback for "Reset
|
||||
progress".
|
||||
- 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`.
|
||||
Reference in new issue
Block a user