Master allocated DR-224 and UT-211 while this branch was in flight — the third collision on this work. Everything here moves up by one: DR-224..236 become DR-225..237, UT-211..213 become UT-212..214. UR-079, UR-080 and IR-033 were still free and are unchanged. Mechanical, and matched on each row's own text rather than on its number, so a row cannot be shifted twice or the wrong one caught. Master's DR-224 (the background-audio toggle) and UT-211 are untouched.
89 lines
3.4 KiB
TypeScript
89 lines
3.4 KiB
TypeScript
/**
|
|
* Which loader opens a stream in the webview `<video>` element.
|
|
*
|
|
* Extracted from `VideoPlayer.svelte` so the decision can be unit-tested — the
|
|
* same pattern as `episodeStrip.ts` and `TrackList.logic.test.ts`.
|
|
*
|
|
* TRACES: UR-079 | DR-225 | UT-214
|
|
*/
|
|
|
|
import type { StreamSelection, Transport } from "$lib/api/bindings";
|
|
|
|
/** How the element should be fed. */
|
|
export type VideoLoader =
|
|
/** hls.js drives a MediaSource; the element's own `src` stays empty. */
|
|
| "hlsjs"
|
|
/** The element loads the playlist itself (Safari/WebKit native HLS). */
|
|
| "nativeHls"
|
|
/** The element loads the URL directly — a progressive file or a local one. */
|
|
| "direct";
|
|
|
|
/** What the running browser can do, passed in so the decision stays pure. */
|
|
export interface LoaderCapabilities {
|
|
/** `Hls.isSupported()` */
|
|
hlsJsSupported: boolean;
|
|
/** `video.canPlayType("application/vnd.apple.mpegurl")` was non-empty */
|
|
nativeHlsSupported: boolean;
|
|
}
|
|
|
|
/**
|
|
* Pick the loader from the backend's tagged `transport`.
|
|
*
|
|
* This used to read `url.includes(".m3u8")`, in two places in
|
|
* `VideoPlayer.svelte`. Rust *builds* that URL and knows exactly what it is;
|
|
* re-deriving the answer here by substring match is a domain fact reconstructed
|
|
* in the presentation layer — the same error as leaking item-type taxonomy, and
|
|
* one that fails silently in both directions: a progressive file served from a
|
|
* path containing `.m3u8` gets an HLS loader, and a playlist served from a path
|
|
* without it does not.
|
|
*
|
|
* The transport is the *stream's* property; whether a given loader exists is the
|
|
* *browser's*. Only the second is decided here.
|
|
*/
|
|
export function videoLoaderFor(
|
|
selection: Pick<StreamSelection, "url" | "transport">,
|
|
capabilities: LoaderCapabilities,
|
|
): VideoLoader {
|
|
return loaderForTransport(selection.transport.type, capabilities);
|
|
}
|
|
|
|
/**
|
|
* The same decision, taken from the transport *tag* alone.
|
|
*
|
|
* Exists because a Svelte `$effect` that reads the whole selection re-runs
|
|
* whenever the selection **object** is replaced — even with an identical URL and
|
|
* transport — and the HLS effect's teardown/rebuild is not idempotent: it
|
|
* destroys the hls.js instance and reattaches, which leaves the element with no
|
|
* video until something forces another cycle. The pre-DR-225 code read a plain
|
|
* URL *string*, so re-assigning the same value was a no-op and the effect stayed
|
|
* put. Passing primitives restores that.
|
|
*
|
|
* TRACES: UR-079 | DR-225 | UT-214
|
|
*/
|
|
export function loaderForTransport(
|
|
transport: Transport["type"],
|
|
capabilities: LoaderCapabilities,
|
|
): VideoLoader {
|
|
if (transport !== "hls") {
|
|
// Progressive and local files are what the element loads natively. No
|
|
// MediaSource, no playlist parsing.
|
|
return "direct";
|
|
}
|
|
if (capabilities.hlsJsSupported) return "hlsjs";
|
|
if (capabilities.nativeHlsSupported) return "nativeHls";
|
|
// Nothing here can parse a playlist. Handing the URL to the element is very
|
|
// likely to fail, but it is the only remaining move and it surfaces a real
|
|
// media error rather than silently doing nothing.
|
|
return "direct";
|
|
}
|
|
|
|
/** Convenience for the template: does the element's `src` stay empty? */
|
|
export function elementSrcFor(
|
|
selection: Pick<StreamSelection, "url" | "transport">,
|
|
capabilities: LoaderCapabilities,
|
|
): string {
|
|
return videoLoaderFor(selection, capabilities) === "hlsjs" ? "" : selection.url;
|
|
}
|
|
|
|
export type { Transport };
|