/** * The loader is chosen from the backend's `transport` tag, never from the URL. * * TRACES: UR-079 | DR-224 | UT-213 */ import { describe, expect, it } from "vitest"; import { elementSrcFor, videoLoaderFor, type LoaderCapabilities } from "./streamTransport"; import type { StreamSelection, Transport } from "$lib/api/bindings"; const MODERN: LoaderCapabilities = { hlsJsSupported: true, nativeHlsSupported: false }; const SAFARI: LoaderCapabilities = { hlsJsSupported: false, nativeHlsSupported: true }; const NEITHER: LoaderCapabilities = { hlsJsSupported: false, nativeHlsSupported: false }; function selection(transport: Transport, url: string): Pick { return { url, transport }; } describe("videoLoaderFor", () => { it("attaches hls.js when the backend says HLS and hls.js is available", () => { expect(videoLoaderFor(selection({ type: "hls" }, "https://s/master.m3u8"), MODERN)).toBe( "hlsjs", ); }); it("falls back to the element's own HLS loader when hls.js is unavailable", () => { expect(videoLoaderFor(selection({ type: "hls" }, "https://s/master.m3u8"), SAFARI)).toBe( "nativeHls", ); }); it("loads a progressive stream directly", () => { expect( videoLoaderFor( selection({ type: "progressive" }, "https://s/Videos/1/stream?static=true"), MODERN, ), ).toBe("direct"); }); it("loads a local file directly", () => { expect( videoLoaderFor(selection({ type: "localFile" }, "http://127.0.0.1:9/media/x.mkv"), MODERN), ).toBe("direct"); }); // --------------------------------------------------------------------- // The two cases the `.m3u8` substring check gets wrong. These are the // reason the field exists; both fail against a URL-sniffing implementation. // --------------------------------------------------------------------- it("does NOT attach hls.js to a progressive stream whose URL happens to end .m3u8", () => { // A direct play served from a path containing the substring — nothing stops // a server, a proxy, or a local cache from producing this. expect( videoLoaderFor(selection({ type: "progressive" }, "https://s/files/movie.m3u8.mp4"), MODERN), ).toBe("direct"); expect( videoLoaderFor(selection({ type: "progressive" }, "https://s/x?name=master.m3u8"), MODERN), ).toBe("direct"); }); it("DOES attach hls.js to an HLS stream whose URL does not contain .m3u8", () => { // Jellyfin's own transcoding URLs are not required to end in `.m3u8`, and a // DASH or query-routed playlist endpoint never would. expect(videoLoaderFor(selection({ type: "hls" }, "https://s/Videos/1/hls"), MODERN)).toBe( "hlsjs", ); expect( videoLoaderFor(selection({ type: "hls" }, "https://s/stream?format=playlist"), SAFARI), ).toBe("nativeHls"); }); it("falls back to direct when HLS is requested but nothing can play it", () => { expect(videoLoaderFor(selection({ type: "hls" }, "https://s/master.m3u8"), NEITHER)).toBe( "direct", ); }); }); describe("elementSrcFor", () => { it("empties the element's src only when hls.js drives it", () => { expect(elementSrcFor(selection({ type: "hls" }, "https://s/master.m3u8"), MODERN)).toBe(""); expect(elementSrcFor(selection({ type: "hls" }, "https://s/master.m3u8"), SAFARI)).toBe( "https://s/master.m3u8", ); }); it("keeps the src for a progressive stream that looks like a playlist", () => { const s = selection({ type: "progressive" }, "https://s/files/movie.m3u8.mp4"); expect(elementSrcFor(s, MODERN)).toBe("https://s/files/movie.m3u8.mp4"); }); });