fix(web): detect HDR via video-dynamic-range media query for Firefox (#259)

* fix(web): detect HDR via video-dynamic-range media query for Firefox

Firefox's (dynamic-range: high) query reflects the browser canvas and
reports standard even on HDR-enabled displays, so the player capability
check always sent hdr=false. Firefox 116+ exposes the video plane via
(video-dynamic-range: high); accept either query. Unknown media features
parse as non-matching, so the extra query is safe in other browsers.

Closes #206

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(web): keep Window receiver when passing matchMedia to HDR helper

Invoking a detached matchMedia reference throws (Illegal invocation /
TypeError) in some browsers, crashing capability detection instead of
reporting HDR. Wrap it in an arrow so the Window receiver is preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Quick
2026-07-02 00:13:51 -04:00
committed by GitHub
co-authored by Claude Opus 4.8
parent 57fbc1e0b4
commit 18e9856227
2 changed files with 42 additions and 3 deletions
+22 -1
View File
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import { detectMaxResolutionFromScreen } from "./useCodecDetection";
import { detectHDRFromMatchMedia, detectMaxResolutionFromScreen } from "./useCodecDetection";
describe("detectMaxResolutionFromScreen", () => {
it("treats a 2560x1440 display as above the 720p bucket", () => {
@@ -10,3 +10,24 @@ describe("detectMaxResolutionFromScreen", () => {
expect(detectMaxResolutionFromScreen(1280, 720)).toBe("720p");
});
});
describe("detectHDRFromMatchMedia", () => {
const fakeMatchMedia = (matching: string[]) =>
((query: string) => ({ matches: matching.includes(query) })) as unknown as typeof matchMedia;
it("returns false when matchMedia is unavailable", () => {
expect(detectHDRFromMatchMedia(undefined)).toBe(false);
});
it("returns true when dynamic-range reports high", () => {
expect(detectHDRFromMatchMedia(fakeMatchMedia(["(dynamic-range: high)"]))).toBe(true);
});
it("returns true when only video-dynamic-range reports high (Firefox)", () => {
expect(detectHDRFromMatchMedia(fakeMatchMedia(["(video-dynamic-range: high)"]))).toBe(true);
});
it("returns false when neither query matches", () => {
expect(detectHDRFromMatchMedia(fakeMatchMedia([]))).toBe(false);
});
});
+20 -2
View File
@@ -82,6 +82,21 @@ export function getPlaybackEnvironmentSnapshot(): PlaybackEnvironmentSnapshot |
};
}
/**
* Detects HDR display support (best effort). Firefox's `dynamic-range` query
* reflects the browser canvas and reports `standard` even on HDR displays;
* the video plane is exposed via `video-dynamic-range` (Firefox 116+), so
* accept either. Browsers treat unknown media features as non-matching, so
* querying both is safe everywhere.
*/
export function detectHDRFromMatchMedia(matchMediaFn: typeof matchMedia | undefined): boolean {
if (!matchMediaFn) return false;
return (
matchMediaFn("(dynamic-range: high)").matches ||
matchMediaFn("(video-dynamic-range: high)").matches
);
}
function testCodec(mimeWithCodec: string): boolean {
if (typeof MediaSource === "undefined") return false;
try {
@@ -170,8 +185,11 @@ export function useCodecDetection() {
// Detect max resolution via reported screen dimensions.
const max_resolution = detectMaxResolutionFromScreen(screen.width, screen.height);
// HDR detection (best effort).
const hdr = typeof matchMedia !== "undefined" && matchMedia("(dynamic-range: high)").matches;
// HDR detection (best effort). Wrap matchMedia so it keeps its Window
// receiver — invoking a detached reference throws in some browsers.
const hdr = detectHDRFromMatchMedia(
typeof matchMedia !== "undefined" ? (query) => matchMedia(query) : undefined,
);
return { codecs_video, codecs_audio, containers, max_resolution, hdr };
}, []);