import { describe, expect, it } from "vitest"; import { buildStatRows, createFragMeter, fmtBitrate, fmtBufferSecs, fmtClock, fmtCodec, fmtFrames, fmtLevel, fmtMode, fmtRes, type PlexStatsInput, } from "./plexStats"; const DASH = "—"; describe("plexStats formatters", () => { it("fmtBitrate scales kbps/Mbps and dashes zero/undefined", () => { expect(fmtBitrate(undefined)).toBe(DASH); expect(fmtBitrate(0)).toBe(DASH); expect(fmtBitrate(820_000)).toBe("820 kbps"); expect(fmtBitrate(4_200_000)).toBe("4.2 Mbps"); expect(fmtBitrate(1_000_000)).toBe("1.0 Mbps"); }); it("fmtBufferSecs shows one decimal, dashes negatives/non-finite", () => { expect(fmtBufferSecs(12.34)).toBe("12.3 s"); expect(fmtBufferSecs(0)).toBe("0.0 s"); expect(fmtBufferSecs(-1)).toBe(DASH); expect(fmtBufferSecs(Infinity)).toBe(DASH); }); it("fmtFrames shows ratio + drop %, dashes when nothing decoded", () => { expect(fmtFrames(0, 0)).toBe(DASH); expect(fmtFrames(0, 1000)).toBe("0 / 1000 (0.00%)"); expect(fmtFrames(5, 1000)).toBe("5 / 1000 (0.50%)"); expect(fmtFrames(30, 1000)).toBe("30 / 1000 (3.0%)"); }); it("fmtRes dashes an undecoded 0×0 frame", () => { expect(fmtRes(1920, 1080)).toBe("1920×1080"); expect(fmtRes(0, 0)).toBe(DASH); expect(fmtRes(1920, 0)).toBe(DASH); }); it("fmtClock renders m:ss and dashes bad input", () => { expect(fmtClock(0)).toBe("0:00"); expect(fmtClock(9)).toBe("0:09"); expect(fmtClock(75)).toBe("1:15"); expect(fmtClock(3661)).toBe("61:01"); expect(fmtClock(-1)).toBe(DASH); }); it("fmtLevel is 1-based with resolution, handles auto and empty", () => { expect(fmtLevel(null)).toBe(DASH); expect(fmtLevel({ index: 1, total: 4, width: 1280, height: 720 })).toBe("2 / 4 · 1280×720"); expect(fmtLevel({ index: -1, total: 3 })).toBe("auto / 3"); expect(fmtLevel({ index: 0, total: 0 })).toBe(DASH); }); it("fmtCodec strips profile suffixes and dashes when absent", () => { expect(fmtCodec("avc1.640028", "mp4a.40.2")).toBe("avc1 / mp4a"); expect(fmtCodec("hvc1.1.6.L120", undefined)).toBe("hvc1"); expect(fmtCodec(undefined, undefined)).toBe(DASH); }); }); describe("buildStatRows", () => { const base: PlexStatsInput = { playable: "remux", transport: "hls", videoWidth: 1280, videoHeight: 720, droppedFrames: 2, totalFrames: 500, bufferAheadS: 8.2, hlsLevel: { index: 0, total: 1, width: 1280, height: 720, bitrate: 3_000_000 }, sessionStartS: 65, abs: 130, duration: 5214, }; it("includes the HLS-only rows (codec/bitrate/level) for hls transport", () => { const keys = buildStatRows(base).map((r) => r.key); expect(keys).toEqual([ "mode", "transport", "res", "codec", "bitrate", "level", "audio", "subtitle", "frames", "buffer", "offset", "position", ]); }); it("drops the HLS-only rows for direct play", () => { const keys = buildStatRows({ ...base, transport: "direct", hlsLevel: null }).map((r) => r.key); expect(keys).not.toContain("codec"); expect(keys).not.toContain("bitrate"); expect(keys).not.toContain("level"); expect(keys).toContain("res"); }); it("surfaces the classification as the mode value and formats position", () => { const rows = buildStatRows({ ...base, playable: "transcode" }); expect(rows.find((r) => r.key === "mode")?.value).toBe("transcode"); expect(rows.find((r) => r.key === "offset")?.value).toBe("1:05"); expect(rows.find((r) => r.key === "position")?.value).toBe("2:10 / 86:54"); }); it("appends the transcode encoder to the mode row when known", () => { const gpu = buildStatRows({ ...base, playable: "transcode", hwEncoder: true }); expect(gpu.find((r) => r.key === "mode")?.value).toBe("transcode · GPU"); const cpu = buildStatRows({ ...base, playable: "transcode", hwEncoder: false }); expect(cpu.find((r) => r.key === "mode")?.value).toBe("transcode · CPU"); }); it("tags the mode row with HDR→SDR for a HW tonemap session", () => { const rows = buildStatRows({ ...base, playable: "transcode", hwEncoder: true, tonemap: true }); expect(rows.find((r) => r.key === "mode")?.value).toBe("transcode · GPU · HDR→SDR"); }); it("shows an empty classification honestly as a dash", () => { expect(buildStatRows({ ...base, playable: "" }).find((r) => r.key === "mode")?.value).toBe( DASH, ); }); }); describe("fmtMode", () => { it("appends the encoder only for a transcode with a known encoder", () => { expect(fmtMode("transcode", true)).toBe("transcode · GPU"); expect(fmtMode("transcode", false)).toBe("transcode · CPU"); expect(fmtMode("transcode", null)).toBe("transcode"); // encoder unknown → bare mode expect(fmtMode("transcode")).toBe("transcode"); expect(fmtMode("remux", true)).toBe("remux"); // not a transcode → no encoder suffix expect(fmtMode("")).toBe(DASH); // The HDR→SDR tag rides on the GPU encoder token for a tonemap session. expect(fmtMode("transcode", true, true)).toBe("transcode · GPU · HDR→SDR"); expect(fmtMode("transcode", true, false)).toBe("transcode · GPU"); expect(fmtMode("transcode", null, true)).toBe("transcode"); // no encoder → no tag either }); }); // --- item 12 of the 2026-09 bug hunt ------------------------------------------------------------ // // The panel reported "Bitrate: 211 kbps" on EVERY remux, which the viewer rightly refused to // believe. It was reading the master playlist's BANDWIDTH, and for a remux ffmpeg computes that from // the only stream whose rate it knows — the `-b:a 192k` AAC track — plus 10%: 192000 × 1.1 = 211200, // a constant that has nothing to do with the video. And the panel never said which audio or subtitle // track was playing, though the player has switched both since E5. describe("measured bitrate", () => { it("reports bytes per second of MEDIA, not of wall clock", () => { const m = createFragMeter(); // A 6 s remux segment of 6 MB is 8 Mbps, however fast it happened to download. m.add({ type: "main", bytes: 6_000_000, durationS: 6 }); expect(m.bitrate()).toBe(8_000_000); }); it("adds the audio rendition's rate instead of averaging it away", () => { // Multi-audio sessions ship audio as its own rendition, so both fragment types cover the SAME // media seconds. Pooling bytes over pooled durations would report half the real bitrate. const m = createFragMeter(); m.add({ type: "main", bytes: 6_000_000, durationS: 6 }); m.add({ type: "audio", bytes: 150_000, durationS: 6 }); expect(m.bitrate()).toBe(8_000_000 + 200_000); }); it("has nothing to say before the first fragment, and after a reset", () => { const m = createFragMeter(); expect(m.bitrate()).toBeUndefined(); // an honest dash — never the manifest's stand-in number m.add({ type: "main", bytes: 6_000_000, durationS: 6 }); m.reset(); expect(m.bitrate()).toBeUndefined(); }); it("ignores init segments and anything with no media duration", () => { const m = createFragMeter(); m.add({ type: "main", bytes: 900, durationS: 0 }); m.add({ type: "main", bytes: 0, durationS: 6 }); expect(m.bitrate()).toBeUndefined(); }); it("follows the stream: old fragments fall out of the rolling window", () => { const m = createFragMeter(2); m.add({ type: "main", bytes: 60_000_000, durationS: 6 }); // a dense scene m.add({ type: "main", bytes: 3_000_000, durationS: 6 }); m.add({ type: "main", bytes: 3_000_000, durationS: 6 }); expect(m.bitrate()).toBe(4_000_000); // the 80 Mbps sample is gone, not still weighing it down }); }); describe("buildStatRows — what is actually playing", () => { const hls = (over: Partial = {}): PlexStatsInput => ({ playable: "remux", transport: "hls", videoWidth: 1920, videoHeight: 1080, droppedFrames: 6, totalFrames: 1458, bufferAheadS: 12, hlsLevel: { index: 0, total: 1, bitrate: 211_200 }, sessionStartS: 0, abs: 10, duration: 8580, ...over, }); const value = (rows: { key: string; value: string }[], key: string) => rows.find((r) => r.key === key)?.value; it("shows the MEASURED bitrate and never the manifest's 211 kbps stand-in", () => { const rows = buildStatRows(hls({ measuredBps: 7_900_000 })); expect(value(rows, "bitrate")).toBe("7.9 Mbps"); }); it("dashes the bitrate until something has been measured", () => { // The level still carries its 211200 BANDWIDTH here — using it would be the old bug. expect(value(buildStatRows(hls()), "bitrate")).toBe(DASH); }); it("names the selected audio and subtitle track", () => { const rows = buildStatRows(hls({ audioLabel: "Magyar AC3 5.1", subLabel: "Magyar" })); expect(value(rows, "audio")).toBe("Magyar AC3 5.1"); expect(value(rows, "subtitle")).toBe("Magyar"); }); it("keeps the track rows for direct play too, where there is no HLS level at all", () => { const rows = buildStatRows( hls({ transport: "direct", hlsLevel: null, audioLabel: "English", subLabel: "Off" }), ); expect(rows.some((r) => r.key === "bitrate")).toBe(false); // HLS-only, as before expect(value(rows, "audio")).toBe("English"); expect(value(rows, "subtitle")).toBe("Off"); }); });