fix(player): re-entering a video no longer opens the audio player (DR-100)
Leaving a video and returning to it rendered the movie/episode in AudioPlayer. Closing a webview-rendered video deliberately emits no "stopped" state (that would break the autoplay handoff), and the direct-play path does not stop the backend on unmount, so the Rust controller still reported that item as its loaded media. Re-entering the route therefore took the "already playing, just show the UI" shortcut, which returns before a stream URL is fetched, and the render fell through to the audio surface. Mostly visible on Android, where video direct-plays; Linux transcodes and stops the backend on unmount. Both decisions move into playerSurface.ts as pure functions: shouldReuseActivePlayback excludes video, so video always takes the full load path and gets its stream URL and resume position; resolvePlayerSurface maps video-without-a-stream-URL to "pending" (spinner) rather than falling through to audio.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Pure decisions for the `/player/[id]` route: which player surface to render,
|
||||
* and whether a load can be skipped because the backend is already playing the
|
||||
* requested item.
|
||||
*
|
||||
* Kept free of Svelte so both can be unit-tested without mounting the route.
|
||||
*
|
||||
* TRACES: UR-005 | DR-100 | UT-092, UT-093
|
||||
*/
|
||||
|
||||
/** Which player component the route should render. */
|
||||
export type PlayerSurface = "video" | "audio" | "pending";
|
||||
|
||||
export interface ReuseActivePlaybackInput {
|
||||
/** Item id the route was asked to play. */
|
||||
requestedId: string;
|
||||
/** Id of the media the backend currently reports as loaded, if any. */
|
||||
activeMediaId: string | null | undefined;
|
||||
/** Whether the requested item is video content. */
|
||||
isVideo: boolean;
|
||||
/** Explicit start position, if the caller asked for one. */
|
||||
startPosition?: number;
|
||||
/** Advancing to a next episode always restarts from the beginning. */
|
||||
forceRestart: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the route can show its UI over the backend's existing playback
|
||||
* instead of reloading the item (e.g. expanding the audio mini player).
|
||||
*
|
||||
* Never for video. The shortcut returns before a stream URL is fetched, which
|
||||
* is fine for audio (the backend owns the stream and the UI only mirrors it)
|
||||
* but leaves `<VideoPlayer>` with nothing to render. Leaving a webview-rendered
|
||||
* video does not clear the Rust controller's media — closing the route emits no
|
||||
* `stopped` state by design — so re-entering the same movie/episode hit this
|
||||
* shortcut and rendered the audio player instead.
|
||||
*/
|
||||
export function shouldReuseActivePlayback(input: ReuseActivePlaybackInput): boolean {
|
||||
return (
|
||||
!input.isVideo &&
|
||||
input.activeMediaId === input.requestedId &&
|
||||
!input.startPosition &&
|
||||
!input.forceRestart
|
||||
);
|
||||
}
|
||||
|
||||
export interface PlayerSurfaceInput {
|
||||
isVideo: boolean;
|
||||
streamUrl: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which surface to render for the loaded item.
|
||||
*
|
||||
* Video without a stream URL is `pending`, never `audio` — falling through to
|
||||
* the audio player is how a movie/episode ended up in it.
|
||||
*/
|
||||
export function resolvePlayerSurface(input: PlayerSurfaceInput): PlayerSurface {
|
||||
if (input.isVideo) {
|
||||
return input.streamUrl ? "video" : "pending";
|
||||
}
|
||||
return "audio";
|
||||
}
|
||||
Reference in New Issue
Block a user