7.8 KiB
External Playback Launch Feedback Design
Goal
Make MPV and VLC recovery actions reversible and understandable. A target stays available after an attempt, the diagnostic panel reports launch progress and outcome, and the workspace dock remains the authoritative global view of the external-player session.
Current Problem
WebPlayerViewComponent records MPV or VLC as attempted before emitting the
fallback request. The recommendation policy then removes attempted targets, so
each button disappears even though IPTVnator only knows that the action was
requested. It does not yet know whether the external player started or played
the stream.
The external-player session already reports launching, opened, playing,
error, and closed, but the dock hides error, renders opened and
playing as the same “Opened” status, and exposes no dismiss action for an
external-player error.
Considered Approaches
1. Keep actions and show only a local timed spinner
This is the smallest change, but the UI would guess when launching finished and could disagree with the Electron session. It is rejected because the app already has structured launch state.
2. Use only callbacks on every PlaybackFallbackRequest host
Every M3U, Xtream, Stalker, and shared host could make the launch promise the
only feedback source. This supplies an exact initial session ID, but by itself
still misses later playing and error session updates.
3. Correlate the owned launch result with the shared external session
This is the selected approach. The web-player component keeps source launch
ownership in its existing host output. Its credential-free tracker binds the
host's returned launch promise to the current fieldless intent token, and only
that exact result may supply the initial session ID. Later status changes come
from PORTAL_EXTERNAL_PLAYBACK.activeSession and must match that ID. This
prevents a delayed timed-out attempt from taking over a retry. No URL, headers,
DRM data, credentials, or raw diagnostic payload enters the ownership state.
State Model
Each external target has component-local state:
idle: no launch is in flight; a previous successful/closed attempt may still be represented by an attempt count;launching: the request is being handed off or Electron is spawning the player;started: Electron confirmed the process or existing player accepted the stream;playing: MPV/VLC position polling confirmed playback;error: the launch/session failed.
The state also contains an attempt count and, only after correlation, the exact
external session ID. It never contains the stream URL, headers, credentials,
engine messages, or error details. A fieldless Symbol intent plus the current
content-session generation rejects stale completions and timers.
Changing playbackSessionKey resets both targets. Destroying the component
invalidates the intent and clears its bounded launch timer. A later session
update is accepted only if it belongs to the correlated session ID.
Recommendation Behaviour
External attempts no longer remove MPV or VLC from the ranked action list. Inline engine attempts retain the current family-exclusion rules.
Within an otherwise unchanged policy result:
- an untried external target ranks ahead of an attempted sibling;
- fewer attempts rank ahead of more attempts;
- policy order remains the tie-breaker;
- the first surviving action remains primary and the total remains capped at three.
Labels describe the action, while adjacent status copy describes the outcome:
| State | Action label | Status |
|---|---|---|
idle, never attempted |
Open in MPV/VLC | none |
launching |
Opening MPV/VLC… | Opening player… |
started |
Open MPV/VLC again | Player started |
playing |
Open MPV/VLC again | Playing |
error |
Try MPV/VLC again | External player error |
Buttons remain mounted with stable recommendation keys, preserving layout and
focus. During a launch handshake, recovery actions expose aria-busy and
aria-disabled; activation handlers enforce the same single-flight guard, so
keyboard or programmatic activation cannot duplicate the request. A small
spinner replaces the external-action icon. State changes are announced through
concise aria-live="polite" status text.
Before a recovery action opens another external player, it closes the currently
tracked live external session through PORTAL_EXTERNAL_PLAYBACK. If the
session is still live and cannot be closed, the new attempt fails locally
instead of starting a second process. The coordinator applies the exact
confirmed close synchronously before emitting the replacement, so coalesced
Angular effects cannot leave stale Playing feedback. If diagnostic ownership
changes while closing, the unlaunched intent is cancelled without reporting a
launch error. MPV and VLC are available again after the handshake.
If no matching session arrives within a bounded timeout, the target transitions
to error. The timeout is feedback for a missing handoff, not evidence about
stream playback.
Dock Behaviour
ExternalPlaybackService.visibleSession keeps error sessions visible until
the user dismisses them or another launch begins. The dock maps session states
without inference:
launching→ “Opening player…” with a spinner;opened→ “Player started”;playing→ “Playing”;error→ the existing session error or a localized generic external-player failure;closedremains hidden by the global service.
The dock uses an aria-live="polite" status region and aria-busy while
launching. A live closable session shows “Close player”; an error shows
“Dismiss”. Dismiss only hides the terminal notification and never stores a
retry payload, so headers and credentials are not retained for a dock retry.
Error And Privacy Boundaries
The diagnostic action shows only localized, app-owned launch-state copy. It does not render rejected promise messages or external-player stderr. The dock may continue to show the existing session error supplied by the Electron boundary; it does not add new logging or persistence.
Recovery recommendations remain user-selected. There is no automatic retry, automatic player switch, persistent history, telemetry, or learning.
Testing
Unit and component coverage will prove:
- external attempts stay visible and rerank without weakening inline engine-family exclusion;
- one handshake at a time, duplicate activation rejection, stable DOM keys,
spinner/labels, and
aria-busy/aria-disabledfeedback; - exact session-ID correlation, stale update/timeout rejection, session-key reset, close-before-switch, and unclosable-session failure;
- dock
launching,opened,playing,error, close, and dismiss states; ExternalPlaybackServicekeeps errors visible until dismissal.
Electron E2E will extend the deterministic recovery fixture to assert that the buttons remain after an MPV attempt, VLC is promoted, the lower dock shows launch feedback, and an error remains dismissible. Existing playback fixtures and player stubs must be used; no real playlist, account, or external process is required.
Documentation And Release Note
Update the canonical playback recovery architecture in
docs/architecture/embedded-inline-playback.md, the shared-player summary in
AGENTS.md and CLAUDE.md, and add a user-facing fix(playback) note under
.changes/.
Out Of Scope
- proving playback for sessions that do not expose position polling;
- retry from the dock;
- retaining playback headers or credentials after the original request;
- automatic failover between MPV and VLC;
- redesigning the diagnostic overlay or dock.