/** * 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 `` 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"; }