Skip to content

Commit cf75dfd

Browse files
fix(studio-server): a video with a codec nobody listed gets a playable preview copy (#7356)
* fix(studio-server): a video with a codec nobody listed gets a playable preview copy * refactor(studio-server): drop a comment the codec names already carry
1 parent 6161ae7 commit cf75dfd

2 files changed

Lines changed: 35 additions & 13 deletions

File tree

‎packages/studio-server/src/helpers/mediaCodecMap.test.ts‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,25 +269,44 @@ describe("shouldPrewarmProxy", () => {
269269
).toBe(false);
270270
});
271271

272-
// `shouldPrewarmProxy` alone cannot catch a missing `Object.hasOwn` guard —
273-
// `.prewarm === true` is already strict against `Object.prototype.constructor`.
274-
// The input that misbehaves goes through `codecFactsFor`, which would report
275-
// an ordinary asset as browser-hostile.
276-
it("does not treat a codec named after an Object.prototype key as hostile", async () => {
272+
it("treats a codec nobody listed as browser-hostile, so its file gets a playable copy", async () => {
277273
const project = tmpProject();
278274
const videoPath = join(project, "clip.mp4");
279275
writeFileSync(videoPath, "fake video bytes");
280276

281-
const facts = await probeAssetCodec(videoPath, makeRunner({ [videoPath]: "constructor" }));
277+
const facts = await probeAssetCodec(videoPath, makeRunner({ [videoPath]: "wmv3" }));
282278

283279
expect(facts).toEqual({
284-
codecName: "constructor",
285-
browserHostile: false,
280+
codecName: "wmv3",
281+
browserHostile: true,
286282
representativeMime: null,
287283
hasAlpha: false,
288284
});
285+
expect(shouldPrewarmProxy(facts!)).toBe(true);
286+
expect(decideMediaProxyEligibility(facts)).toEqual({ eligible: true });
287+
});
288+
289+
it.each(["h264", "vp8", "vp9", "av1"])("does not pre-warm %s", async (codecName) => {
290+
const project = tmpProject();
291+
const videoPath = join(project, "clip.mp4");
292+
writeFileSync(videoPath, "fake video bytes");
293+
294+
const facts = await probeAssetCodec(videoPath, makeRunner({ [videoPath]: codecName }));
295+
289296
expect(shouldPrewarmProxy(facts!)).toBe(false);
290297
});
298+
299+
// A codec named after an Object.prototype key must not resolve against it: it is an unlisted codec like any other.
300+
it("reads a codec named after an Object.prototype key as an unlisted codec", async () => {
301+
const project = tmpProject();
302+
const videoPath = join(project, "clip.mp4");
303+
writeFileSync(videoPath, "fake video bytes");
304+
305+
const facts = await probeAssetCodec(videoPath, makeRunner({ [videoPath]: "constructor" }));
306+
307+
expect(facts?.browserHostile).toBe(true);
308+
expect(facts?.representativeMime).toBeNull();
309+
});
291310
});
292311

293312
describe("decideMediaProxyEligibility", () => {

‎packages/studio-server/src/helpers/mediaCodecMap.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ export interface BrowserHostileCodec {
5555
}
5656

5757
/**
58-
* Browser-hostile codec table v1. One exported constant so extending it is a
59-
* one-line change. `ffprobe` cannot emit exact RFC 6381 codec strings, so
58+
* Browser-hostile codec table v1: the codecs with a known answer; one not here and not in
59+
* `BROWSER_PLAYABLE_CODECS` is hostile too. `ffprobe` cannot emit exact RFC 6381 codec strings, so
6060
* these `representativeMime` values are deliberately coarse (a false
6161
* positive costs one proxy transcode, never correctness; a false negative is
6262
* rescued by the runtime's reactive zero-videoWidth swap).
@@ -78,12 +78,15 @@ export const BROWSER_HOSTILE_CODECS: Record<string, BrowserHostileCodec> = {
7878
vp9: { representativeMime: 'video/webm; codecs="vp09.00.10.08"', prewarm: false },
7979
};
8080

81+
const BROWSER_PLAYABLE_CODECS: ReadonlySet<string> = new Set(["h264", "vp8"]);
82+
83+
const UNLISTED_HOSTILE: BrowserHostileCodec = { representativeMime: null, prewarm: true };
84+
8185
/** `Object.hasOwn` rather than a bare index: a codec named `constructor` or
8286
* `toString` would otherwise resolve against `Object.prototype`. */
8387
function hostileCodecEntry(codecName: string): BrowserHostileCodec | undefined {
84-
return Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName)
85-
? BROWSER_HOSTILE_CODECS[codecName]
86-
: undefined;
88+
if (Object.hasOwn(BROWSER_HOSTILE_CODECS, codecName)) return BROWSER_HOSTILE_CODECS[codecName];
89+
return BROWSER_PLAYABLE_CODECS.has(codecName) ? undefined : UNLISTED_HOSTILE;
8790
}
8891

8992
/** The pre-warm gate: true only for codecs with no cross-platform browser

0 commit comments

Comments
 (0)