mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-08 17:06:15 -08:00
feat(playback): recommend recovery actions (#1374)
* docs(playback): design recovery recommendations * docs(playback): plan recovery recommendations * refactor(playback): extract diagnostic utilities * feat(playback): define recovery recommendation contracts * feat(playback): rank recovery recommendations * feat(playback): track session recovery attempts * feat(playback): identify content recovery sessions * feat(ui): add ranked playback diagnostic panel * feat(playback): switch temporarily to recommended players * test(playback): cover temporary player recommendation * test(playback): verify recommendation capability guards * docs(playback): document recovery recommendations * fix(playback): keep recovery keys credential-free * fix(playback): remove derived tracking ownership * fix(playback): preserve distinct recovery fallbacks * fix(playback): reset resume for new sources * fix(playback): preserve desktop recovery guidance * docs(playback): clarify recovery policy exceptions * fix(playback): reject stale progress updates * fix(playback): keep protected recovery guidance neutral * test(playback): cover stale progress output * fix(playback): neutralize protected diagnostic copy * fix(playback): harden runtime guidance ownership * fix(playback): stabilize recovery application ownership * fix(ci): classify playback util coverage * fix(e2e): preserve playback fixture bytes
This commit is contained in:
1 parent
5e4f2ca3dd
commit
fd96b85c19
176 files changed
+15228
-1908
No files matched your search
@@ -19,7 +19,8 @@ This document records the current contract for embedded playback in portal detai
|
||||
saved episode playback positions.
|
||||
- Embedded playback UI is always hosted by the current view. `PlayerService`
|
||||
launches MPV/VLC only and does not open an embedded-player dialog.
|
||||
- Browser-player failures are diagnosed client-side and can offer explicit MPV/VLC fallback actions without changing the saved player setting.
|
||||
- Browser-player failures are diagnosed client-side and produce ranked,
|
||||
user-triggered recovery actions without changing the saved player setting.
|
||||
|
||||
## Scope
|
||||
|
||||
@@ -38,6 +39,58 @@ Collection/search VOD surfaces that expose embedded playback must host
|
||||
`PlayerService.openPlayer(...)` or `PlayerService.openResolvedPlayback(...)` to
|
||||
create embedded UI.
|
||||
|
||||
## Logical Playback Identity
|
||||
|
||||
Every inline playback host owns a required, URL-independent
|
||||
`playbackSessionKey`. Live hosts derive it from the playlist/source and the
|
||||
current channel identity; an M3U session uses `Channel.id` rather than a mutable
|
||||
stream or catch-up URL. VOD and series hosts use the route or catalog
|
||||
content identity, with series episode coordinates. Stalker episode identity
|
||||
also includes the explicit series mode, normalized parent, exact season key,
|
||||
season number, and episode number; synthesized episode hashes are not identity.
|
||||
Mapped episodes retain the original provider command or episode ID for playback
|
||||
resolution. Recovery ownership never serializes that value into the session
|
||||
key or retained recovery state; its recovery-ownership use is limited to a
|
||||
short-lived exact pending-request snapshot/guard that locates the selected
|
||||
episode and rejects stale or out-of-order completion. This keeps token refreshes
|
||||
in one logical session while ensuring colliding tracking hashes cannot select a
|
||||
sibling episode.
|
||||
After a Stalker episode mounts, the host retains only a frozen structural
|
||||
identity (source, normalized parent, series mode, exact season key, season and
|
||||
episode coordinates, and the credential-free session key). Each metadata,
|
||||
navigation, or autoplay read resolves those coordinates against the current
|
||||
`mappedSeasons()` value. Same-owner provider or TMDB refreshes therefore update
|
||||
the mounted episode without changing its session key; if the episode disappears,
|
||||
or if multiple episodes claim the same structural coordinates, the mounted
|
||||
commands fail closed. The command/ID-bearing identity never outlives the pending
|
||||
playback request.
|
||||
Shared wrappers (`VodDetailsComponent` and `PortalInlinePlayerComponent`) pass
|
||||
the key unchanged to `WebPlayerViewComponent`.
|
||||
|
||||
Hosts invalidate both committed playback and pending resolution when the
|
||||
canonical owner changes (playlist/source, content, and mode where applicable).
|
||||
Refreshing data for the same canonical owner preserves the mounted player.
|
||||
Collection UIDs remain a separate persistence concern; legacy M3U collection
|
||||
UIDs continue to use stream URLs so saved favorites ordering remains compatible.
|
||||
|
||||
Temporary portal URLs, catch-up URLs, headers, DRM data, and alternative source
|
||||
payloads are transport details and must not change this logical identity. A
|
||||
content or episode change must produce a different key. The serialized key is
|
||||
created with `createPlaybackSessionKey()` from `@iptvnator/playback/util` so
|
||||
delimiter-bearing provider IDs remain unambiguous.
|
||||
|
||||
The key is host-owned and stable only for the logical selection represented by
|
||||
that mounted host. In particular, M3U identity uses the current playlist/source
|
||||
identity and `Channel.id`; it does not claim durability across a refresh that
|
||||
replaces either identity. Recovery state contains this credential-free key,
|
||||
target IDs, generation counters, and a finite VOD resume position. It never
|
||||
uses a playback URL, headers, DRM configuration, or credentials as identity.
|
||||
|
||||
Inline hosts capture this identity before asynchronous playback resolution. A
|
||||
completion may mount only while the same owner is current; stale completions
|
||||
and embedded starts without a complete canonical identity are ignored without
|
||||
replacing an already committed session.
|
||||
|
||||
## Embedded MPV Harness
|
||||
|
||||
The repository now contains a first-pass native embedded MPV harness for Electron:
|
||||
@@ -231,9 +284,9 @@ Embedded playback does not have a fallback dialog path.
|
||||
`PlayerService.openResolvedPlayback(...)` remains the MPV/VLC external launch
|
||||
entry point; for embedded players it returns without creating UI.
|
||||
|
||||
Diagnostics and fallback UI:
|
||||
Diagnostics, recovery policy, and recovery UI:
|
||||
|
||||
- `/Users/4gray/Code/iptvnator/libs/ui/playback/src/lib/playback-diagnostics/playback-diagnostics.util.ts`
|
||||
- `/Users/4gray/Code/iptvnator/libs/playback/util/src/index.ts`
|
||||
- `/Users/4gray/Code/iptvnator/libs/ui/playback/src/lib/web-player-view/web-player-view.component.ts`
|
||||
|
||||
## Playback Decision Rule
|
||||
@@ -309,7 +362,30 @@ Current contract:
|
||||
|
||||
## Codec And Container Diagnostics
|
||||
|
||||
The shared `WebPlayerViewComponent` is the central browser-player viewport for M3U, Xtream, and Stalker inline playback, including live streams opened from favorites and recently viewed collections. Video.js, HTML5, and ArtPlayer report native media errors, HLS.js errors, mpegts.js errors, and HLS manifest codec metadata into the shared diagnostics classifier.
|
||||
The shared `WebPlayerViewComponent` is the central browser-player viewport for
|
||||
M3U, Xtream, and Stalker inline playback, including live streams opened from
|
||||
favorites and recently viewed collections. Video.js, HTML5, and ArtPlayer
|
||||
report native media errors, hls.js errors, Video.js/VHS errors, Shaka errors,
|
||||
mpegts.js errors, and HLS manifest codec metadata into the DOM-free classifiers
|
||||
exported by `@iptvnator/playback/util`.
|
||||
|
||||
The canonical recovery flow is:
|
||||
|
||||
```text
|
||||
engine public error
|
||||
-> sanitized PlaybackDiagnostic (@iptvnator/playback/util)
|
||||
-> recommendPlaybackRecovery(context)
|
||||
-> ranked maximum-three action model
|
||||
-> WebPlayerView session-local user action
|
||||
```
|
||||
|
||||
Engine adapters own public-event collection and sanitation. The playback
|
||||
utility owns diagnostic contracts, evidence classification, source/engine
|
||||
mapping, capability contracts, and the pure recommendation policy.
|
||||
`WebPlayerViewComponent` owns only the current session state and execution of a
|
||||
user-selected action. Diagnostic producers do not decide which player to show,
|
||||
and the recommendation policy does not inspect Angular, the DOM, settings,
|
||||
storage, or Electron globals.
|
||||
|
||||
The diagnostics remain client-only:
|
||||
|
||||
@@ -418,10 +494,14 @@ or media cause, so stage and failure remain unknown.
|
||||
|
||||
A failed public `Player.isBrowserSupported()` preflight is not a Shaka error
|
||||
and therefore retains fully unknown technical evidence instead of being
|
||||
mislabelled as an unsupported container. For clear DASH, the diagnostic still
|
||||
offers configured MPV/VLC actions because the failure is specific to the web
|
||||
engine. KODIPROP DRM sources keep external fallback disabled because external
|
||||
players do not receive their key configuration.
|
||||
mislabelled as an unsupported container. The app adds only the exact,
|
||||
enumerated `PlaybackRuntimeSupport.ShakaBrowserUnsupported` marker to that
|
||||
otherwise unknown diagnostic. This Shaka/DASH runtime-preflight marker is the
|
||||
sole unknown-code exception that can rank configured MPV/VLC actions, and only
|
||||
for clear, externally transferable DASH in a managed-external runtime. Generic
|
||||
unknown diagnostics remain Retry/alternative-source only. PWA capability and
|
||||
KODIPROP DRM suppress the external actions because those players are absent or
|
||||
the launch contract cannot transfer the key configuration.
|
||||
|
||||
Shaka messages, URLs, headers, request/response bodies, credentials,
|
||||
license/key payloads, and arbitrary `error.data` objects are neither retained
|
||||
@@ -443,7 +523,8 @@ status from the top-level `info.code` slot of
|
||||
Exact public pairs classify HTTP/timeout/exception as network failures,
|
||||
`FormatUnsupported` as an unsupported container, `CodecUnsupported` as an
|
||||
unsupported codec, and `FormatError`/`MediaMSEError` as media failures.
|
||||
`UnrecoverableEarlyEof` remains a fallback-actionable `media-decode-error`:
|
||||
`UnrecoverableEarlyEof` remains a `media-decode-error` that may rank a distinct
|
||||
engine or external player:
|
||||
mpegts.js has already exhausted its internal finite-source early-EOF recovery,
|
||||
and another demuxer may tolerate the truncated transport stream. Mismatched or
|
||||
unknown pairs fail closed to `unknown-playback-error`.
|
||||
@@ -454,17 +535,20 @@ prove CORS, mixed content, CSP, or private-network access, so mpegts.js no
|
||||
longer creates `browser-access-error` from message text. HTTP and other network
|
||||
failures do not claim an external decoder will fix the provider response;
|
||||
container, codec, truncated-stream, format, and MediaSource failures retain
|
||||
the existing explicit MPV/VLC fallback behavior.
|
||||
evidence that can rank an explicit MPV/VLC action when the payload and runtime
|
||||
permit it.
|
||||
|
||||
The diagnostic surface covers the inline player viewport when playback fails,
|
||||
with a compact warning badge, a native-player fallback headline, and
|
||||
player-card actions for configured external players. It exposes technical
|
||||
details on demand: diagnostic code, reporting player/source, detected
|
||||
container/MIME, video/audio codecs, native browser error fields, sanitized
|
||||
structured Video.js/VHS, HLS, Shaka, and mpegts.js evidence. HLS
|
||||
manifest codec metadata also drives a concise browser-support hint for codecs
|
||||
that Chromium/Electron commonly cannot decode inline, such as HEVC, AC-3,
|
||||
E-AC-3, DTS, and MPEG-2 video.
|
||||
with a compact warning badge, one primary recommendation, at most two secondary
|
||||
recommendations, and always-available Copy URL and Technical details utilities.
|
||||
An alternative-source recommendation consumes one of those three slots even
|
||||
when its bounded source list renders several rows. Technical details contain
|
||||
the diagnostic code, reporting player/source, detected container/MIME,
|
||||
video/audio codecs, native browser error fields, and sanitized structured
|
||||
Video.js/VHS, HLS, Shaka, and mpegts.js evidence. HLS manifest codec metadata
|
||||
also drives a concise browser-support hint for codecs that Chromium/Electron
|
||||
commonly cannot decode inline, such as HEVC, AC-3, E-AC-3, DTS, and MPEG-2
|
||||
video.
|
||||
|
||||
URL extension metadata is filtered before diagnostics and player selection use it. Web script extensions such as `.php` are not shown as stream containers; explicit media query metadata such as `extension=ts` or `format=m3u8` is preferred when present.
|
||||
|
||||
@@ -472,14 +556,125 @@ MKV sources are attempted through Chromium's native Matroska path. Video.js
|
||||
receives `video/matroska` for `.mkv` URLs and explicit query metadata such as
|
||||
`extension=mkv` or `container=mkv`; ArtPlayer and HTML5 continue to use their
|
||||
native video paths. This is container support rather than a universal codec
|
||||
guarantee: native source or decode failures still produce the existing
|
||||
diagnostic and explicit MPV/VLC fallback.
|
||||
guarantee: native source or decode failures still produce a diagnostic whose
|
||||
ranked actions may include MPV/VLC.
|
||||
|
||||
Portal VOD and episode payloads with `contentInfo` are treated as non-live by the inline players unless `isLive` is explicitly set. If Chromium leaves the underlying MediaSource duration at `Infinity` for a finite TS VOD, the Video.js wrapper normalizes its UI duration from the finite `seekable` or `buffered` range. Embedded MPV uses the same live decision rule and shows an unknown duration placeholder for VOD/episode snapshots until MPV reports a finite duration. This removes the misleading `LIVE` control state without changing stream decoding, diagnostics, or external fallback behavior.
|
||||
|
||||
When a diagnostic is actionable in Electron, the diagnostic surface may offer `Open in MPV`, `Open in VLC`, `Copy URL`, technical details, and `Retry`. Web builds only expose copy/help text and retry. MPV/VLC fallback requests carry the original `ResolvedPortalPlayback` payload so headers, referer, origin, user-agent, content metadata, and resume offset stay intact. Retry clears the current diagnostic and rebuilds the active inline player inputs; it does not change the saved player setting.
|
||||
## Recovery Recommendation Policy
|
||||
|
||||
`PortalPlayer.openExternalPlayback(playback, player)` is the forced external launch API. It sends the playback payload to MPV or VLC regardless of the current saved player setting, so fallback buttons do not mutate preferences.
|
||||
Recommendations change playback paths only when structured evidence supports
|
||||
that conclusion. A different skin over the same engine family is not a distinct
|
||||
recovery target.
|
||||
|
||||
| Source path | Active engine family | Distinct built-in recommendation |
|
||||
| ---------------------------------------- | ------------------------------- | -------------------------------- |
|
||||
| HLS in Video.js | Video.js/VHS | HTML5 through hls.js |
|
||||
| HLS in HTML5 or ArtPlayer | hls.js | Video.js/VHS |
|
||||
| MPEG-TS in any web player | shared mpegts.js | None |
|
||||
| DASH in HTML5 or ArtPlayer | Shaka | None |
|
||||
| DASH in Video.js | unsupported recommendation path | None |
|
||||
| Native media/container in any web player | browser native-media | None |
|
||||
|
||||
HTML5 is the canonical hls.js alternative to Video.js/VHS; ArtPlayer is not a
|
||||
second independent hls.js choice. MPEG-TS, DASH/Shaka, and native media never
|
||||
offer a same-family built-in alternative. Unknown source or engine-family facts
|
||||
also suppress built-in recommendations.
|
||||
|
||||
The pure policy builds the following order, filters the current, unavailable,
|
||||
incompatible, and already attempted targets, and then returns at most three
|
||||
actions. It also projects attempted inline target IDs through the validated
|
||||
canonical source/target capability matrix and filters every engine family that
|
||||
has already been attempted. HTML5 and ArtPlayer therefore cannot be offered as
|
||||
separate hls.js recovery attempts. The first surviving action is primary and
|
||||
later actions are secondary.
|
||||
|
||||
| Sanitized evidence | Candidate order |
|
||||
| ----------------------------------------- | ------------------------------------------------------------ |
|
||||
| HTTP, timeout, or generic network failure | Retry -> Alternative source |
|
||||
| Generic unknown playback error | Retry -> Alternative source |
|
||||
| Exact Shaka browser-unsupported preflight | MPV -> VLC -> Alternative source |
|
||||
| Browser access/CORS/CSP-class failure | MPV -> VLC -> Alternative source |
|
||||
| Unsupported codec or container | MPV -> VLC -> Alternative source |
|
||||
| Media/decode/engine processing failure | Distinct built-in family -> MPV -> VLC -> Alternative source |
|
||||
| DRM/encryption failure | Compatible built-in path -> Alternative source -> MPV -> VLC |
|
||||
|
||||
Network and generic unknown evidence fail closed: they never claim that
|
||||
changing a decoder will repair the provider response. The exact app-owned
|
||||
Shaka browser-unsupported runtime-preflight marker above is the only
|
||||
unknown-code exception. Contradictory or incomplete capability facts fail
|
||||
closed to Retry and an available alternative source.
|
||||
External targets require both managed external-player support and a transferable
|
||||
payload. PWA builds therefore never rank MPV/VLC. Any ClearKey/KODIPROP DRM
|
||||
payload is non-transferable and suppresses both external targets; the policy
|
||||
does not infer transferability from a message or URL. Eligible Electron portal
|
||||
fallback requests keep using the original `ResolvedPortalPlayback`, so the
|
||||
existing host path can forward its required headers and playback metadata.
|
||||
|
||||
## Recovery Session Lifecycle And Privacy
|
||||
|
||||
Each mounted `WebPlayerViewComponent` owns one in-memory recovery session for
|
||||
its required `playbackSessionKey`. Retry and an alternative source for the same
|
||||
logical content keep the key and attempted-target set. A different channel,
|
||||
movie, or exact episode changes the key and synchronously clears the diagnostic,
|
||||
attempts, temporary player override, and handoff position. Destroying the
|
||||
component also ends the session; the same key in a later component is a new
|
||||
session. Applying a different source under the same key, including another
|
||||
catch-up programme, clears only the VOD handoff position; attempts and the
|
||||
temporary player override remain available for the recovery session.
|
||||
|
||||
On a terminal failure, the current binding is accepted only when both its
|
||||
generation and inline target still match. The current target becomes attempted,
|
||||
the sanitized diagnostic is stored, and recommendations are reranked. The
|
||||
`PlaybackBinding` contains exactly `{ generation, target }`. Changes to the
|
||||
playback URL, headers, DRM, live/VOD mode, target, or reload generation are
|
||||
instead correlated with a fieldless opaque `Symbol` application token. Source
|
||||
applications also advance a second fieldless source-revision `Symbol` that
|
||||
resets the VOD handoff position; target-only switches and Retry leave that
|
||||
revision stable. None of these ownership objects embed source material, and
|
||||
recovery ownership state never stores URLs, headers, DRM keys, error payloads,
|
||||
or credentials. Each rendered web or Embedded MPV application captures the
|
||||
nullable binding, both opaque tokens, and its live/VOD flag. A time update can
|
||||
change the resume position only while that exact capture still owns the current
|
||||
application, so a replaced source cannot repopulate cleared handoff state.
|
||||
|
||||
A separate fieldless intent token invalidates on each new source, target, or
|
||||
reload intent. The application effect synchronizes the content session before
|
||||
tracking that intent, so clearing a temporary player override is incorporated
|
||||
into one application instead of scheduling a duplicate Electron header handoff.
|
||||
Each application start clears both the diagnostic owner and backing signal,
|
||||
making the prior diagnostic neither retained nor actionable before asynchronous
|
||||
header setup completes. False or rejected current handoffs leave it clear, and
|
||||
a stale success, false result, or rejection cannot erase a newer owned
|
||||
diagnostic or mutate the newer application state.
|
||||
|
||||
Selecting a built-in recommendation records the target, immediately detaches
|
||||
the diagnostic, and installs a temporary local override ahead of the host
|
||||
override and saved player setting. The new engine receives the latest finite
|
||||
VOD position as a best-effort resume point; live playback starts at the live
|
||||
edge. Retry reloads the active target without clearing attempts. Selecting MPV
|
||||
or VLC records the external target before emitting the existing fallback
|
||||
request. The system does not infer whether the external process ultimately
|
||||
played the stream.
|
||||
|
||||
No recommendation mutates `Settings.player` or another persisted setting.
|
||||
Recovery recommendations never auto-switch a player or source and do not
|
||||
replace the separate source-owner auto-failover feature. Attempts, overrides,
|
||||
resume handoff, and diagnostics are session-local: there is no persistent
|
||||
history, cross-session learning, correlation, or telemetry.
|
||||
|
||||
Only allowlisted public engine evidence crosses the structured sanitizers.
|
||||
Provider/engine messages, request and response objects, arbitrary `error.data`
|
||||
or `info`, bodies, URLs copied from error payloads, headers, DRM material, and
|
||||
credentials do not enter recommendation evidence or recovery ownership state.
|
||||
The active playback URL remains available only through the pre-existing
|
||||
playback metadata needed by Retry, Copy URL, and eligible explicit
|
||||
external-player actions.
|
||||
|
||||
`PortalPlayer.openExternalPlayback(playback, player)` remains the forced
|
||||
external launch API. It sends the resolved playback payload to MPV or VLC
|
||||
regardless of the current saved player setting, so a recommendation never
|
||||
mutates preferences.
|
||||
|
||||
## External Player Arguments
|
||||
|
||||
|
||||
@@ -994,8 +994,9 @@ player in settings.
|
||||
- `ShakaVideoSession` (`libs/ui/playback/src/lib/shaka-engine/`) owns the
|
||||
engine: lazy `import('shaka-player')` on first use (the module is a separate
|
||||
lazy chunk, ~217 KB transfer), `drm.clearKeys` configuration, an operation
|
||||
queue + generation guard against channel-switch races, and a Shaka `5.2.2`
|
||||
public-error boundary. The boundary version-locks its allowlisted
|
||||
queue + generation guard against channel-switch races. The DOM-free Shaka
|
||||
`5.2.2` public-error boundary lives in `libs/playback/util`; it version-locks
|
||||
its allowlisted
|
||||
severity/category/code values, emits only structured sanitized
|
||||
`PlaybackDiagnosticSource.Shaka` evidence, ignores recoverable error events,
|
||||
and treats a rejected load as terminal even if its final retry error retains
|
||||
@@ -1007,9 +1008,11 @@ player in settings.
|
||||
unknown stage/failure. Public DASH text-parser codes likewise retain their
|
||||
exact `TEXT` category/code but keep stage/failure unknown. A failed
|
||||
`Player.isBrowserSupported()` preflight also stays unknown rather than
|
||||
claiming container incompatibility; clear DASH still offers configured
|
||||
external-player actions, while KODIPROP DRM keeps them disabled because
|
||||
those players never receive its keys.
|
||||
claiming container incompatibility. It carries only the enumerated,
|
||||
app-owned `PlaybackRuntimeSupport.ShakaBrowserUnsupported` marker — never a
|
||||
producer recommendation hint or engine payload. Clear DASH still offers
|
||||
configured external-player actions, while KODIPROP DRM keeps them disabled
|
||||
because those players never receive its keys.
|
||||
Channels with `drm.supported === false` emit a `DrmOrEncryption` diagnostic
|
||||
with a fixed safe detail string and without starting an engine.
|
||||
- HTML5 player: `extension === 'mpd'` branch in `playChannel()`. ArtPlayer:
|
||||
|
||||
@@ -73,6 +73,26 @@ in `libs/portal/shared/util`, while reusable collection views stay in
|
||||
`libs/portal/shared/ui`. Existing injectable or stateful services in a `util`
|
||||
path are legacy debt, not precedent for new placement.
|
||||
|
||||
Playback follows the same split: browser and Angular player integration stays
|
||||
in `libs/ui/playback`, while DOM-free diagnostic contracts and classifiers live
|
||||
in `libs/playback/util` and receive browser capability checks as explicit
|
||||
probes.
|
||||
|
||||
`libs/playback/util` is the `playback-util` Nx project and is imported through
|
||||
`@iptvnator/playback/util`. Its exact tags are `scope:shared`,
|
||||
`domain:playback`, and `type:util`. It owns the public playback diagnostic,
|
||||
structured engine-evidence, source/engine-family, target-capability,
|
||||
content-session-key, and recovery-recommendation contracts and pure helpers.
|
||||
Its public API is `libs/playback/util/src/index.ts`.
|
||||
|
||||
`playback-util` has no Angular, DOM, settings, storage, UI, or Electron IPC
|
||||
ownership. Browser/player adapters collect public engine events and supply
|
||||
explicit capability facts; `playback-util` classifies and ranks them without
|
||||
inspecting runtime globals. As a `type:util` project it may depend only on
|
||||
other utility projects, including shared interface contracts, while
|
||||
`ui-playback` and feature hosts may depend on it to render and execute
|
||||
session-local recovery actions.
|
||||
|
||||
## Project Tags
|
||||
|
||||
Every Nx project keeps one tag from each family in `project.json`:
|
||||
|
||||
@@ -7,8 +7,7 @@ Embedded MPV rendering and native-view bounds behavior remain documented in
|
||||
|
||||
## Current status
|
||||
|
||||
The shared-controls foundation from PR #1148 now supports four runtime
|
||||
consumers and includes:
|
||||
The shared-controls foundation supports four runtime consumers and includes:
|
||||
|
||||
- the `PlayerController` contract, default state, and capability presets;
|
||||
- the standalone `app-player-controls` presentation component and its
|
||||
@@ -82,6 +81,25 @@ also includes a recording coordinator that correlates asynchronous snapshots
|
||||
with the active playback/session owner, serializes toggles, and cancels pending
|
||||
ownership when the session, playback, engine, or component changes.
|
||||
|
||||
## Diagnostics And Recovery Ownership Boundary
|
||||
|
||||
`PlayerController` remains a sibling of playback diagnostics and recovery
|
||||
recommendations. It owns engine-neutral playback state, capabilities, and
|
||||
commands for the shared controls; it does not classify errors, call
|
||||
`recommendPlaybackRecovery()`, rank actions, track recovery attempts, choose a
|
||||
temporary player, or own content-session policy.
|
||||
|
||||
Those pure contracts and policies live in `@iptvnator/playback/util` (Nx
|
||||
project `playback-util`). `WebPlayerViewComponent` owns their in-memory,
|
||||
session-local application. No diagnostic or recommendation state or command is
|
||||
added to `PlayerController`.
|
||||
|
||||
The only controls-layer participation is interaction gating. While the sibling
|
||||
diagnostic panel is visible, a web-player host disables shared surface and
|
||||
keyboard ownership and exits only its own DOM fullscreen so the recovery
|
||||
actions remain reachable. Clearing the diagnostic restores those paths; it
|
||||
does not make the controls contract an owner of the recovery lifecycle.
|
||||
|
||||
## Why this exists
|
||||
|
||||
Historically each playback engine owned both media integration and controls UI.
|
||||
@@ -306,7 +324,7 @@ playback diagnostic is visible: `WebPlayerViewComponent` passes
|
||||
components bind that value to `showControls` and
|
||||
`shortcutsEnabled`. If the active player shell owns DOM fullscreen, its host
|
||||
exits fullscreen before hiding the controls so the sibling diagnostic banner
|
||||
and its retry/fallback actions remain visible; fullscreen owned by another
|
||||
and its recovery actions remain visible; fullscreen owned by another
|
||||
element is left untouched. Retrying playback or clearing the diagnostic
|
||||
restores both interaction paths.
|
||||
|
||||
|
||||
@@ -1263,7 +1263,19 @@ Series inline playback behavior is shared across all three modes:
|
||||
blocks the action rather than binding regular, embedded, or lazy VOD content
|
||||
to another mode; an exact canonical episode id still wins.
|
||||
|
||||
- `StalkerSeriesViewComponent` maps every mode into `mappedSeasons()` and derives the currently playing episode from `inlinePlayback.contentInfo.contentXtreamId`.
|
||||
- `StalkerSeriesViewComponent` maps every mode into `mappedSeasons()` and uses
|
||||
two episode identities. A pending playback request keeps an exact,
|
||||
request-local provider command or episode ID so command rotation and hash
|
||||
collisions reject stale completion. Once mounted, the component freezes only
|
||||
the credential-free structural identity and session key (source, normalized
|
||||
parent, mode, season key, season number, and episode number).
|
||||
- The mounted structural identity is re-resolved against the current
|
||||
`mappedSeasons()` for metadata, Previous/Next, and autoplay. Same-owner
|
||||
provider and TMDB refreshes therefore expose current episode objects and
|
||||
commands without remounting the player; if the episode is missing or its
|
||||
structural coordinates are ambiguous, those surfaces and commands fail
|
||||
closed. Provider commands and IDs are never retained in mounted session
|
||||
state.
|
||||
- The inline player header shows the current episode metadata below the title, for example `S01E03 - Episode title`.
|
||||
- Embedded players receive previous/next episode state for the current season only.
|
||||
- Inline series autoplay is enabled by default. On player EOF (`ended`), Stalker starts the next episode only when it already exists in the current season's mapped episode list.
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,490 @@
|
||||
# Playback Recovery Recommendations
|
||||
|
||||
## Context
|
||||
|
||||
Issue #1159 started as a request for more accurate playback errors and useful
|
||||
guidance when a stream fails in one player but may work in another. The browser
|
||||
players now emit structured, fail-closed diagnostics for native media,
|
||||
hls.js, Video.js/VHS, Shaka Player, and mpegts.js. Those boundaries preserve
|
||||
public engine evidence without retaining provider messages, credentials, or
|
||||
arbitrary error payloads.
|
||||
|
||||
The remaining recovery policy is still embedded in the UI. Every
|
||||
`PlaybackDiagnostic` carries an `externalFallbackRecommended` boolean, and
|
||||
`WebPlayerViewComponent` converts that flag into a fixed MPV/VLC section while
|
||||
always showing Retry and every available alternative-source row. This cannot
|
||||
explain when a distinct built-in engine is the better next step, rank recovery
|
||||
actions by evidence, or avoid recommending a target already tried during the
|
||||
same playback session.
|
||||
|
||||
This design introduces one player-neutral recommendation layer. It ranks a
|
||||
small set of explicit user actions from structured diagnostic evidence and
|
||||
runtime capabilities. It does not perform automatic failover.
|
||||
|
||||
## Goals
|
||||
|
||||
- Convert structured playback diagnostics into deterministic, evidence-based
|
||||
recovery recommendations.
|
||||
- Rank one primary recommendation and at most two secondary recommendations.
|
||||
- Recommend a distinct built-in web engine only when it can plausibly change
|
||||
the failing playback path.
|
||||
- Keep MPV/VLC recommendations for source formats and browser restrictions
|
||||
where an external player can receive the required playback data.
|
||||
- Keep network, HTTP, and unknown failures fail-closed: prefer Retry or an
|
||||
alternative source instead of guessing that another decoder will help.
|
||||
- Let a user temporarily try a recommended built-in player for the current
|
||||
content without changing the saved player setting.
|
||||
- Remember attempted targets only for the current in-memory content session so
|
||||
repeated failures produce a more useful next recommendation.
|
||||
- Establish a pure playback-domain boundary that future structured native
|
||||
diagnostics and target capabilities can extend.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Automatically switching players or sources.
|
||||
- Changing the persisted player setting.
|
||||
- Persistent diagnostic history, cross-session learning, telemetry, or
|
||||
correlation.
|
||||
- A retry scheduler, health monitor, or full failover orchestrator.
|
||||
- Moving diagnostics or recommendation policy into `PlayerController`.
|
||||
- Replacing the existing multi-source auto-failover behavior.
|
||||
- Recommending Embedded MPV as an inline target before it emits an equivalent
|
||||
structured diagnostic lifecycle.
|
||||
- Detecting whether an external MPV/VLC process ultimately played the stream.
|
||||
- AirPlay, Cast, Document Picture-in-Picture, or remote-device capabilities.
|
||||
- Redesigning the diagnostic overlay or the player controls.
|
||||
|
||||
## Approaches Considered
|
||||
|
||||
### Pure ranked policy layer (selected)
|
||||
|
||||
Create a pure playback-domain function that receives structured evidence,
|
||||
source context, target capabilities, and session-local attempts, then returns
|
||||
an ordered list of typed recommendations. `WebPlayerViewComponent` remains the
|
||||
owner of UI state and user-triggered switching.
|
||||
|
||||
This keeps classification, policy, and rendering independently testable. It
|
||||
also makes future evidence sources additive without coupling playback engines
|
||||
to Angular or turning the controls contract into an error orchestrator.
|
||||
|
||||
### Extend diagnostic booleans
|
||||
|
||||
Add fields such as `retryRecommended`, `builtInFallbackRecommended`, and
|
||||
`alternativeSourceRecommended` to `PlaybackDiagnostic` and let the component
|
||||
choose the buttons. This looks small but duplicates ranking logic, makes
|
||||
diagnostic producers own runtime/UI policy, and cannot cleanly account for
|
||||
attempted targets. It is rejected.
|
||||
|
||||
### Full failover orchestrator
|
||||
|
||||
Introduce a state machine that launches players, observes outcomes, mutates
|
||||
preferences, and manages retries. This could eventually support automatic
|
||||
recovery, but it is materially larger and would blur the existing ownership of
|
||||
source selection, settings, and engine lifecycle. It is rejected for v1.
|
||||
|
||||
## Architecture And Ownership
|
||||
|
||||
### New playback utility project
|
||||
|
||||
Create `libs/playback/util` as the pure playback-domain boundary:
|
||||
|
||||
- Nx project name: `playback-util`;
|
||||
- path alias: `@iptvnator/playback/util`;
|
||||
- tags: `scope:shared`, `domain:playback`, `type:util`;
|
||||
- public exports through `libs/playback/util/src/index.ts` only.
|
||||
|
||||
The project contains contracts, evidence normalization, diagnostic
|
||||
classification, source/engine-family mapping, target-capability contracts, and
|
||||
the recommendation policy. It has no Angular, DOM, storage, settings-store, or
|
||||
Electron IPC dependency. As a `type:util` project it depends only on other
|
||||
utility projects, including the existing shared interfaces required for
|
||||
external-player names and resolved playback metadata.
|
||||
|
||||
Move the pure diagnostic model and classifiers from
|
||||
`libs/ui/playback/src/lib/playback-diagnostics/` into this project. Move only
|
||||
the pure Shaka evidence/classifier helpers from the Shaka area; the Shaka
|
||||
engine and video session remain in `ui-playback`. Engine components import the
|
||||
new alias instead of reaching back into UI-owned diagnostic paths.
|
||||
|
||||
For one compatibility window, `@iptvnator/ui/playback` re-exports the public
|
||||
diagnostic contracts from `@iptvnator/playback/util`. Existing consumers can
|
||||
therefore migrate without deep imports or a flag day. New code imports the
|
||||
new alias directly, and the compatibility export can be removed in a later
|
||||
cleanup PR after all consumers have migrated.
|
||||
|
||||
### UI owner
|
||||
|
||||
`WebPlayerViewComponent` owns:
|
||||
|
||||
- the current content-session key;
|
||||
- the temporary built-in player override;
|
||||
- the set of targets attempted in the current session;
|
||||
- the most recent VOD position used for a best-effort engine handoff;
|
||||
- the binding generation that rejects stale engine events;
|
||||
- mapping ranked recommendations to translated, accessible UI;
|
||||
- existing external-player and alternative-source outputs.
|
||||
|
||||
UI-specific translation keys, formatting helpers, Material components, styles,
|
||||
and diagnostic action cards remain in `libs/ui/playback`.
|
||||
|
||||
### Controls contract
|
||||
|
||||
`PlayerController` remains the engine-neutral state, command, and capability
|
||||
contract used by shared controls. Diagnostics and recovery policy stay as a
|
||||
sibling layer. No recommendation state or commands are added to
|
||||
`PlayerController`.
|
||||
|
||||
## Recommendation Contracts
|
||||
|
||||
The utility layer exposes a pure
|
||||
`recommendPlaybackRecovery(context)` function and a discriminated
|
||||
recommendation union:
|
||||
|
||||
```ts
|
||||
type PlaybackRecommendation =
|
||||
| {
|
||||
readonly action: 'retry';
|
||||
readonly reason: PlaybackRecommendationReason;
|
||||
readonly priority: 'primary' | 'secondary';
|
||||
}
|
||||
| {
|
||||
readonly action: 'alternative-source';
|
||||
readonly reason: PlaybackRecommendationReason;
|
||||
readonly priority: 'primary' | 'secondary';
|
||||
}
|
||||
| {
|
||||
readonly action: 'player';
|
||||
readonly target: PlaybackRecommendationTarget;
|
||||
readonly reason: PlaybackRecommendationReason;
|
||||
readonly priority: 'primary' | 'secondary';
|
||||
};
|
||||
```
|
||||
|
||||
`PlaybackRecommendationTarget` is the union of the three built-in diagnostic
|
||||
players (`videojs`, `html5`, `artplayer`) and the managed external targets
|
||||
(`mpv`, `vlc`). Embedded MPV is deliberately absent in v1.
|
||||
|
||||
Reasons are stable app-owned values, not user-visible prose:
|
||||
|
||||
- `retry-transient-failure`;
|
||||
- `retry-unknown-failure`;
|
||||
- `alternative-source-available`;
|
||||
- `different-engine-family`;
|
||||
- `external-codec-or-container-support`;
|
||||
- `external-browser-access`;
|
||||
- `compatible-drm-path`.
|
||||
|
||||
Angular maps those values to translations. The model has no numeric confidence
|
||||
score: the evidence matrix and list order are the contract.
|
||||
|
||||
The policy input contains:
|
||||
|
||||
- the sanitized `PlaybackDiagnostic`;
|
||||
- the active target;
|
||||
- the session-local attempted-target set;
|
||||
- available target capabilities and their engine family for this source;
|
||||
- source kind, live/VOD state, and DRM/external-transfer context;
|
||||
- the count of alternative sources.
|
||||
|
||||
The policy output is deterministic, contains at most three entries, and has at
|
||||
most one primary entry. If the list is non-empty, its first entry is primary
|
||||
and every later entry is secondary. Current, unavailable, incompatible, and
|
||||
already attempted targets are excluded before ranking.
|
||||
|
||||
`PlaybackDiagnostic.externalFallbackRecommended` is removed. Diagnostic
|
||||
producers report evidence and classification only; the recommendation policy
|
||||
decides which actions are safe and useful in the current runtime.
|
||||
|
||||
Copy URL and Technical details are utilities rather than recommendations and
|
||||
are therefore not part of this union.
|
||||
|
||||
## Capability And Source Context
|
||||
|
||||
The caller supplies explicit capability facts instead of asking the policy to
|
||||
inspect settings, the DOM, or Electron globals. Each player target records
|
||||
whether it is available for the current runtime/source and its effective
|
||||
engine family.
|
||||
|
||||
The source context records whether the playback payload can be transferred to
|
||||
an external player. ClearKey/KODIPROP playback is always non-transferable in
|
||||
v1: MPV and VLC are excluded because the current external-player request does
|
||||
not carry an equivalent DRM contract. Header-bearing portal playback is
|
||||
transferable only when its existing host-specific external launch path forwards
|
||||
the required resolved playback fields; otherwise the host capability marks it
|
||||
non-transferable. The policy never infers transferability from an error message
|
||||
or URL substring.
|
||||
|
||||
## Engine-Family Matrix
|
||||
|
||||
Recommendations change engines, not merely skins:
|
||||
|
||||
| Source path | Built-in engine families | v1 built-in alternative |
|
||||
| -------------------------------- | ----------------------------------- | ----------------------- |
|
||||
| HLS in Video.js | Video.js/VHS | HTML5 using hls.js |
|
||||
| HLS in HTML5 or ArtPlayer | hls.js | Video.js/VHS |
|
||||
| MPEG-TS in any web player | mpegts.js | None |
|
||||
| DASH in HTML5 or ArtPlayer | Shaka Player | None |
|
||||
| DASH in Video.js | not a supported recommendation path | None |
|
||||
| Native MP4/MKV in any web player | browser media element | None |
|
||||
|
||||
Only one target represents a distinct engine family in the ranked list. For a
|
||||
Video.js HLS failure, HTML5 is the canonical hls.js target; ArtPlayer is not a
|
||||
second independent engine recommendation. If that target is unavailable or
|
||||
already attempted, the policy proceeds to an external target rather than
|
||||
presenting a duplicate engine-family guess.
|
||||
|
||||
MPEG-TS does not recommend another built-in player because all three use the
|
||||
same mpegts.js engine. DASH does not recommend Video.js in v1 because it is not
|
||||
an equivalent supported Shaka path. Native media does not recommend another
|
||||
web player because all three ultimately depend on the same browser decoder.
|
||||
|
||||
## Policy Matrix
|
||||
|
||||
The policy builds the following exact candidate order, then filters unavailable,
|
||||
current, incompatible, and attempted targets and truncates the result to three
|
||||
entries. “Alternative source” is omitted when its count is zero.
|
||||
|
||||
| Evidence | Ordered candidates | Forbidden guess |
|
||||
| ----------------------------------------- | --------------------------------------------------------- | -------------------------------- |
|
||||
| HTTP, timeout, or generic network failure | Retry → Alternative source | Any player change |
|
||||
| Unknown playback error | Retry → Alternative source | Any player change |
|
||||
| Browser access/CORS/CSP-class evidence | MPV → VLC → Alternative source | Another browser player |
|
||||
| Unsupported codec or container | MPV → VLC → Alternative source | Another browser player |
|
||||
| Media/decode/engine processing failure | Distinct built-in family → MPV → VLC → Alternative source | Same engine family |
|
||||
| DRM/encryption failure | Compatible built-in path → Alternative source → MPV → VLC | Non-transferable external target |
|
||||
|
||||
Network and unknown cases never claim that another decoder is likely to fix
|
||||
the failure. For DRM, every candidate still passes explicit target and payload
|
||||
capability checks; ClearKey/KODIPROP therefore never reaches MPV or VLC.
|
||||
|
||||
For external actions, MPV precedes VLC to preserve the existing primary
|
||||
fallback. If MPV is unavailable or attempted, VLC may become primary. When no
|
||||
ranked recommendation survives, the overlay still exposes Copy URL and
|
||||
Technical details instead of fabricating a guess.
|
||||
|
||||
## Playback Session Lifecycle
|
||||
|
||||
### Stable content identity
|
||||
|
||||
Every `WebPlayerViewComponent` host supplies a required
|
||||
`playbackSessionKey`. The key identifies canonical logical content, not the
|
||||
current URL or selected provider copy:
|
||||
|
||||
- an M3U live channel key uses playlist/source identity plus channel identity;
|
||||
- Xtream and Stalker live keys use provider/account plus content identity;
|
||||
- movie keys use the owning route/catalog's original source plus movie
|
||||
identity;
|
||||
- episode keys use the owning series route's original source and series plus
|
||||
season and episode identity.
|
||||
|
||||
An alternative source's `playback.contentInfo` is provider-scoped playback and
|
||||
resume metadata, not recovery-session identity. Source-owning route and series
|
||||
hosts derive the key before passing it through the inline player, so replacing
|
||||
that playback payload cannot replace the recovery session.
|
||||
|
||||
Retry and alternative sources for the same channel, movie, or episode keep the
|
||||
same key. Selecting a different channel, movie, or episode changes it. A key
|
||||
change synchronously clears the attempted-target set, temporary override,
|
||||
handoff position, and visible diagnostic, then advances the binding generation
|
||||
so every callback from the previous content becomes stale.
|
||||
|
||||
The state is component-local, so a same-content source change must retain the
|
||||
`WebPlayerViewComponent` instance and update its inputs. Destroying that
|
||||
component ends the recovery session even if a later instance receives the same
|
||||
key. Host tests enforce retained identity for supported multi-source flows.
|
||||
|
||||
### Failure and reranking
|
||||
|
||||
When the active web engine emits a terminal diagnostic, the component:
|
||||
|
||||
1. verifies that the event belongs to the current binding generation and
|
||||
active target;
|
||||
2. records the current target as attempted;
|
||||
3. stores the diagnostic;
|
||||
4. emits the existing `playbackFailed` output for source-owner behavior;
|
||||
5. runs the pure recommendation policy with current capabilities and attempts.
|
||||
|
||||
An event from a destroyed or replaced engine is ignored and cannot overwrite
|
||||
the new session's UI.
|
||||
|
||||
### Trying a built-in target
|
||||
|
||||
When the user selects a built-in recommendation, the component records that
|
||||
target as attempted, captures the latest VOD position, clears the diagnostic,
|
||||
and applies a local override that outranks the host override and saved setting
|
||||
for this content session. The player host is recreated for the chosen target.
|
||||
No storage or settings-store mutation occurs.
|
||||
|
||||
For VOD and episodes, the new engine receives the latest finite playback
|
||||
position as a best-effort start time. Live playback restarts at the live edge.
|
||||
If the target fails, the new diagnostic is reranked with both attempted
|
||||
targets excluded. If the target proves unavailable before attachment, the
|
||||
component keeps it attempted and immediately reranks without throwing.
|
||||
|
||||
Retry keeps the same content session and attempts, clears the current
|
||||
diagnostic, and reloads the active target. An alternative-source request also
|
||||
keeps the same content session and attempts; the source-owning host changes the
|
||||
resolved playback URL without resetting the recommendation history.
|
||||
|
||||
### External targets
|
||||
|
||||
MPV/VLC actions continue to emit the existing `PlaybackFallbackRequest` with
|
||||
the full `ResolvedPortalPlayback` and diagnostic. The selected external target
|
||||
is recorded as attempted before emission so returning to the overlay can
|
||||
promote the next useful action. v1 does not infer launch or playback success
|
||||
and does not persist the result.
|
||||
|
||||
## User Interface
|
||||
|
||||
Keep the existing diagnostic overlay and its badge, headline, description,
|
||||
HTTP/container/codec metadata, codec hint, and `role="status"`. This feature
|
||||
changes the action hierarchy, not the overall visual language.
|
||||
|
||||
The overlay renders:
|
||||
|
||||
1. one prominent primary recommendation card;
|
||||
2. at most two compact secondary recommendation cards;
|
||||
3. the always-available Copy URL and Technical details utilities.
|
||||
|
||||
An alternative-source recommendation renders the existing
|
||||
`VodSourceRow`-based block and occupies one recommendation slot regardless of
|
||||
the number of visible source rows. Its existing bounded row count and “more
|
||||
sources” affordance remain.
|
||||
|
||||
Retry moves out of the unconditional utility row and appears only when the
|
||||
policy ranks it. Preserve `playback-retry`, `playback-fallback-mpv`, and
|
||||
`playback-fallback-vlc`. Built-in actions use
|
||||
`playback-recommendation-videojs`, `playback-recommendation-html5`, and
|
||||
`playback-recommendation-artplayer`. Their copy explicitly says that the
|
||||
change is temporary and does not alter the saved setting.
|
||||
|
||||
All actions are native buttons with visible keyboard focus. No autofocus or
|
||||
focus trap is added to the status overlay. A pending switch disables repeated
|
||||
activation until the current operation settles. At narrow widths cards stack
|
||||
vertically, content uses `min-width: 0`, and long translated copy wraps without
|
||||
forcing horizontal overflow. Existing light/dark application tokens remain
|
||||
the styling source.
|
||||
|
||||
## Error Handling And Safety
|
||||
|
||||
- The recommendation function is total and does not throw for missing,
|
||||
unknown, or future diagnostic evidence.
|
||||
- Incomplete or contradictory context fails closed to Retry and an available
|
||||
alternative source; it never upgrades uncertainty into a player claim.
|
||||
- Unknown engine families cannot produce built-in recommendations.
|
||||
- Unavailable or non-transferable targets are filtered before rendering.
|
||||
- A binding generation plus exact target identity rejects stale diagnostic,
|
||||
async header, and delayed player events after a switch.
|
||||
- Switching is single-flight from the UI perspective; double clicks cannot
|
||||
mount two targets or corrupt the attempt set.
|
||||
- Session state remains memory-only and contains stable target IDs and a
|
||||
numeric playback position, never raw error payloads or credentials.
|
||||
- Existing structured-evidence redaction guarantees remain unchanged after
|
||||
moving the pure files into `playback-util`.
|
||||
|
||||
## Testing
|
||||
|
||||
Use test-driven development.
|
||||
|
||||
### Pure policy and boundary tests
|
||||
|
||||
- Add exhaustive table-driven tests in `playback-util` for every policy row,
|
||||
source engine family, priority transition, availability combination, attempt
|
||||
exclusion, DRM transfer constraint, and three-result limit.
|
||||
- Prove network and unknown diagnostics never recommend a player.
|
||||
- Prove MPEG-TS, native media, and Shaka/DASH never offer a same-engine browser
|
||||
alternative.
|
||||
- Prove HLS offers only one distinct built-in engine family.
|
||||
- Prove ClearKey/KODIPROP excludes MPV/VLC.
|
||||
- Preserve and migrate all diagnostic/evidence contract tests, including
|
||||
package-version locks and redaction assertions.
|
||||
- Add a module-boundary assertion that the new utility project has no Angular,
|
||||
DOM, Electron, storage, or UI dependency.
|
||||
|
||||
### Component and host tests
|
||||
|
||||
Extend `WebPlayerViewComponent` tests to cover:
|
||||
|
||||
- deterministic primary/secondary rendering and stable test IDs;
|
||||
- a temporary built-in switch without settings mutation;
|
||||
- attempted-target exclusion and reranking after another failure;
|
||||
- Retry and alternative-source preservation of session state;
|
||||
- complete reset on `playbackSessionKey` change;
|
||||
- VOD position handoff and live-edge behavior;
|
||||
- stale generation/target event rejection;
|
||||
- single-flight action handling and unavailable-target reranking;
|
||||
- DRM and runtime-capability filtering;
|
||||
- keyboard focus, disabled state, and narrow-layout structure.
|
||||
|
||||
Update the closest M3U, Xtream, Stalker, unified live, and portal inline-player
|
||||
specs to prove that each host supplies a stable content key and preserves it
|
||||
while switching sources for the same content.
|
||||
|
||||
### E2E coverage
|
||||
|
||||
- Add a deterministic web Playwright case backed by repository-owned media
|
||||
fixtures: trigger an engine-specific HLS failure, assert the distinct
|
||||
built-in recommendation, activate it, verify the new player host mounts, and
|
||||
verify the persisted player setting remains unchanged.
|
||||
- Extend the existing web and Electron DASH/ClearKey E2E cases to prove that
|
||||
non-transferable DRM never exposes MPV/VLC recommendations.
|
||||
- Preserve or add an Electron case where an eligible browser/container failure
|
||||
still exposes the existing managed MPV/VLC actions and emits the expected
|
||||
external fallback request.
|
||||
|
||||
Do not add a production-only diagnostic injection hook for E2E. If a browser
|
||||
cannot deterministically expose the chosen HLS engine event from a bounded
|
||||
fixture, the implementation plan must select another public, deterministic
|
||||
engine event and retain component-level coverage of the exact policy branch.
|
||||
|
||||
### Validation ladder
|
||||
|
||||
Run at minimum:
|
||||
|
||||
- `pnpm nx test playback-util`;
|
||||
- `pnpm nx lint playback-util`;
|
||||
- `pnpm nx test ui-playback`;
|
||||
- `pnpm nx lint ui-playback`;
|
||||
- focused tests for every host whose session-key binding changes;
|
||||
- the targeted web and Electron E2E atomized targets discovered from Nx;
|
||||
- affected application typecheck/build targets;
|
||||
- `pnpm run i18n:validate`;
|
||||
- `pnpm run release:notes:validate`;
|
||||
- Nx module-boundary and project-discovery checks.
|
||||
|
||||
The implementation plan must use the exact target names reported by the fresh
|
||||
workspace rather than inventing commands.
|
||||
|
||||
## Documentation And Release Note
|
||||
|
||||
Update:
|
||||
|
||||
- `docs/architecture/embedded-inline-playback.md` with the canonical
|
||||
diagnostic-to-recommendation flow and engine-family matrix;
|
||||
- `docs/architecture/nx-workspace-boundaries.md` with the new
|
||||
`playback-util` project and alias;
|
||||
- `AGENTS.md` and `CLAUDE.md` with the new shared playback recommendation
|
||||
ownership and session behavior.
|
||||
|
||||
Update `docs/architecture/player-controls-contract.md` with a short boundary
|
||||
clarification that recovery recommendations remain outside the controls
|
||||
contract; the controls API itself does not change.
|
||||
|
||||
Add a `fix(playback)` note under `.changes/` because users gain new recovery
|
||||
actions and temporary built-in player switching. The note describes the user
|
||||
outcome, not the internal policy extraction.
|
||||
|
||||
## Future Extensions
|
||||
|
||||
The boundary is intentionally extensible where additional evidence changes a
|
||||
decision:
|
||||
|
||||
- structured Embedded MPV/native-view diagnostics and capabilities;
|
||||
- explicit external-launch failure results;
|
||||
- richer per-target codec, container, DRM, and header-transfer capabilities;
|
||||
- session-local outcome adaptation when a target starts successfully;
|
||||
- source health facts supplied by an existing source owner.
|
||||
|
||||
Persistent history, telemetry-driven ranking, and automatic mutation of player
|
||||
settings remain low-value or high-risk until a concrete user problem justifies
|
||||
them.
|
||||
Reference in new issue
Block a user