mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
fix(playback): clarify external player launch feedback (#1388)
This commit is contained in:
1 parent
8442747c37
commit
d73acd6bfc
101 files changed
+8819
-850
No files matched your search
@@ -824,7 +824,90 @@ app as a real argument, so it is not an option.
|
||||
exception. PWA capability suppresses managed MPV/VLC, and ClearKey/KODIPROP
|
||||
DRM suppresses external targets because its payload is not transferable. Raw
|
||||
engine messages, arbitrary data, and credentials never enter recommendation
|
||||
evidence or ownership state.
|
||||
evidence or ownership state. MPV/VLC actions remain mounted after an attempt
|
||||
and expose credential-free per-target launching/started/playing/error state;
|
||||
only an exact Electron `playing` update is labelled Playing. One handshake is
|
||||
allowed at a time. The renderer claims the credential-free content identity
|
||||
before awaiting Electron, so primary Play is disabled and a launching or
|
||||
closable-error alternative remains owned before the controller commits it.
|
||||
Every route action that can start the same external playback, including
|
||||
Restart and the provider-source shortcut, observes that local pre-IPC guard.
|
||||
The Xtream VOD diagnostic-fallback handler records the same route-scoped
|
||||
destination and pending generation before invoking MPV/VLC, so route reuse
|
||||
cannot orphan that process outside the next route's close-before-play path.
|
||||
Its fieldless intent is bound to the exact session returned
|
||||
by the source owner's launch promise, so a late timed-out attempt cannot take
|
||||
over a retry; later global updates must match that ID. A replacement waits for
|
||||
confirmed teardown of the tracked external process, applies the old exact
|
||||
close before launch, and cancels an unlaunched handoff if diagnostic ownership
|
||||
changes. Process teardown has bounded graceful and forced confirmation
|
||||
windows, and reusable MPV bounds the IPC command that precedes them; if any
|
||||
stage cannot reach a confirmed exit, the exact session stays live and the
|
||||
replacement fails closed instead of overlapping it. A process-wide teardown
|
||||
gate starts before any potentially slow teardown preparation, including VLC
|
||||
position flush and a reused player's protocol quit, and rejects every
|
||||
MPV/VLC spawn until that exact child reports exit. If bounded
|
||||
teardown fails while a fresh launch is still pending, that launch IPC rejects
|
||||
and the exact session remains a closable error instead of hanging forever.
|
||||
If a pre-content reuse failure has no still-live displaced session to restore,
|
||||
the replacement error keeps its attached closer so Stop can retry the orphaned
|
||||
child teardown. A terminal error without a closer is never restorable.
|
||||
A failed close is single-flight only while its promise is pending: Stop can
|
||||
retry the same exact child after a bounded confirmation failure. Reuse maps
|
||||
the child to its current content session, so a stale older closer becomes a
|
||||
no-op instead of terminating a newer `loadfile`/VLC enqueue handoff.
|
||||
A duplicate close for an already closed session returns its terminal snapshot
|
||||
without re-entering the saved closer, and a late process error cannot revive
|
||||
that terminal session. Reused MPV commands are bound to the socket captured
|
||||
for that exact child, so a later process cannot inherit a stale protocol quit.
|
||||
Stop observed before a pending MPV content command or VLC enqueue command
|
||||
prevents that command from dispatching. A source handoff fails closed while
|
||||
a live session has no closer (`canClose: false`); renderer Dismiss is not
|
||||
teardown confirmation. That denied handoff advances neither the multi-source
|
||||
switch token nor the playback generation, so it cannot cancel the sole launch
|
||||
already in flight.
|
||||
VLC rechecks the gate at each concrete spawn after port allocation or reuse
|
||||
work; if a post-start fallback is blocked there, the opened session becomes
|
||||
an error rather than retaining a false started status. A failed RC-port
|
||||
allocation never claims reuse ownership, so the fallback VLC child retains
|
||||
its exact one-shot closer.
|
||||
Reuse failures before a content command restore the globally displaced
|
||||
renderer session, not the reusable process's prior owner, and only while the
|
||||
exact displaced-session ID is still active; after
|
||||
`loadfile`/VLC `clear` is dispatched,
|
||||
the replacement owns the process and remains a closable error instead of
|
||||
restoring stale content metadata. Stop during an in-flight MPV or VLC reuse
|
||||
command, including during failed-command teardown or the subsequent VLC
|
||||
fallback port-allocation wait, settles that exact close without falling
|
||||
through to a fresh spawn;
|
||||
a stopped VLC spawn error that reports only `close` also settles its original
|
||||
launch IPC with the exact closed session;
|
||||
a fresh fallback retires the old child's exit under its prior session so it
|
||||
cannot close the replacement. Source handoffs recheck ownership after launch
|
||||
and accept only `opened`/`playing`; a stale returned session is closed exactly
|
||||
and a Stop-returned `closed` session is never committed. If that exact stale
|
||||
close fails, its credential-free destination owner is retained for the next
|
||||
close attempt. Retained destination ownership is scoped to the initiating
|
||||
playlist/VOD route key, so route reuse cannot expose Stop for the previous
|
||||
movie's external session. Play/Resume capture that route key before awaiting
|
||||
close and cancel if navigation changes it; a late diagnostic fallback closes
|
||||
its exact returned session instead of adopting it on the new route. They
|
||||
supersede an older source resolution before awaiting the shared
|
||||
close-before-replacement path, and accepting a diagnostic fallback retires
|
||||
the same older resolution before opening MPV/VLC. They publish the route-source
|
||||
badge, caption evidence, and position only after start succeeds.
|
||||
Closable errors still participate in every replacement close and keep Stop as
|
||||
the global dock's only teardown affordance; Dismiss is reserved for terminal
|
||||
errors that have no closer. The shared `isLiveExternalPlayerSession` predicate
|
||||
keeps M3U and series ownership while
|
||||
such an error can still be stopped; consumers must not treat every `error`
|
||||
status as terminal.
|
||||
If the local handshake times out after an exact Electron session is known,
|
||||
that ID remains
|
||||
correlated so a later exact update can recover the UI. The global dock mirrors
|
||||
those statuses, keeps closable errors visible until Stop confirms teardown and
|
||||
terminal errors visible until dismissal, and intentionally has no retry because
|
||||
it does not own the original launch headers or credentials.
|
||||
- DASH + ClearKey (M3U module): `.mpd` channels play through a lazily loaded
|
||||
Shaka Player source engine inside the HTML5 and ArtPlayer components (no new
|
||||
player in settings). ClearKey keys come from `#KODIPROP:inputstream.adaptive.*`
|
||||
@@ -1179,7 +1262,7 @@ engine` (restart required) or
|
||||
|
||||
- Portal mode (full vs. simple) follows OBSERVED behavior, never a URL substring. The single predicate is `isFullStalkerPortalPlaylist()` / `isFullStalkerPortalUrl()` in `@iptvnator/shared/interfaces` (`stalker-portal-mode.util.ts`): the persisted `Playlist.isFullStalkerPortal` flag is authoritative and the URL shape is a fallback for legacy rows only. Three diverging copies of this rule used to exist and shipped broken configurations (#850/#686/#755) — never re-implement it. A token-enforcing `portal.php` panel is a full portal; a `server/load.php` endpoint that answers without a token is a simple one.
|
||||
- Import probes candidates in order (a pasted `.php` endpoint first, then `<base>/portal.php` → `<base>/server/load.php` → `<base>/stalker_portal/server/load.php`) and classifies each by behavior — a token-less `itv/get_genres` returning data proves a token-free panel; the plain-text auth failure proves a full portal, confirmed by a real handshake + `get_profile`. `StalkerPortalDiscoveryService` (`libs/portal/stalker/data-access`) persists the proven endpoint and mode.
|
||||
- `executeStalkerRequest()` (`stores/utils/stalker-request.utils.ts`) is the choke point for catalog, content and playback requests: mode routing, the in-session repair override, and retry-once all live there. Four callers are deliberately outside it because they run below or before the thing it routes on — `StalkerAuthApi` (handshake/`get_profile`/`do_auth`, which the full-portal branch is built from; routing them back would recurse), `StalkerPortalDiscoveryService` (probes precede the mode they determine), `StalkerAccountInfoService.fetchViaProfile()`, and `StreamResolverService` for a collection item with no playlist row. They are exempt from the routing, not from the repair it hooks, but only `fetchViaProfile()` wires `StalkerPortalRepairService` itself: discovery is what repair *drives*, the row-less resolver branch has no playlist to repair, and the auth layer needs nothing — a terminal handshake failure propagates out of the full-portal branch into whichever `executeStalkerRequest()` call triggered the authentication, which is why terminal handshake failures are a repair trigger. Anything new that is not auth or discovery belongs on `executeStalkerRequest()`. Existing playlists are repaired LAZILY (`StalkerPortalRepairService`) — only after a request fails with a shape a wrong endpoint/mode produces, at most once per source configuration per playlist per session, persisted through the atomic `PlaylistsService.transformPlaylistMeta`. There is deliberately **no eager one-shot migration**: a portal that works is never re-probed.
|
||||
- `executeStalkerRequest()` (`stores/utils/stalker-request.utils.ts`) is the choke point for catalog, content and playback requests: mode routing, the in-session repair override, and retry-once all live there. Four callers are deliberately outside it because they run below or before the thing it routes on — `StalkerAuthApi` (handshake/`get_profile`/`do_auth`, which the full-portal branch is built from; routing them back would recurse), `StalkerPortalDiscoveryService` (probes precede the mode they determine), `StalkerAccountInfoService.fetchViaProfile()`, and `StreamResolverService` for a collection item with no playlist row. They are exempt from the routing, not from the repair it hooks, but only `fetchViaProfile()` wires `StalkerPortalRepairService` itself: discovery is what repair _drives_, the row-less resolver branch has no playlist to repair, and the auth layer needs nothing — a terminal handshake failure propagates out of the full-portal branch into whichever `executeStalkerRequest()` call triggered the authentication, which is why terminal handshake failures are a repair trigger. Anything new that is not auth or discovery belongs on `executeStalkerRequest()`. Existing playlists are repaired LAZILY (`StalkerPortalRepairService`) — only after a request fails with a shape a wrong endpoint/mode produces, at most once per source configuration per playlist per session, persisted through the atomic `PlaylistsService.transformPlaylistMeta`. There is deliberately **no eager one-shot migration**: a portal that works is never re-probed.
|
||||
- Both transports build the wire format from the same shared builders in `@iptvnator/shared/interfaces` — `buildStalkerRequestUrl()`, `buildStalkerIdentityRequestContext()`, `encodeStalkerCmdValue()` — so the Electron and PWA legs cannot drift. The mock's `/stalker` mirror shares the identity builder only — it dispatches in-process, so there is no portal URL to build and it mirrors the `JsHttpRequest` default by hand. Never fork any of them.
|
||||
- Simple portals skip the auth lifecycle (no handshake, token or watchdog) but their requests are not stripped to a bare cookie: they still carry everything the shared builder derives from a MAC alone (`mac`/`stb_lang`/`timezone` cookie, MAG `User-Agent`/`X-User-Agent`, `Accept` set). They do NOT carry the serial — `dispatchStalkerRequest()`'s direct branch forwards only `url`/`macAddress`/`params`, so no `SN` header and no serial-derived `__cfduid`, whatever the playlist stores. That gate is on API requests only: `buildStalkerExternalPlaybackHeaders()` reads the serial off the playlist row with no mode check, so the same simple-mode playlist does send `SN`/`__cfduid` with a portal-owned stream.
|
||||
- Contract: `docs/architecture/stalker-portal.md` ("Portal Mode and Endpoint Discovery", "Request Transport and `cmd` Encoding").
|
||||
|
||||
Reference in new issue
Block a user