Export: fix VideoEncoder.configure TypeError on Chrome

probeExportCodec returned codec/muxerCodec but not config, so the
pre-probed pick used by the software-encoding warning path passed
undefined to VideoEncoder.configure, which Chrome rejects as "not of
type VideoEncoderConfig". Include config in the probe result and thread
it through exportSegment (test render was still reprobing and ignoring
the pick).

Also prefer the browser-negotiated config from isConfigSupported when
available — it is guaranteed valid for configure (covers new required
fields), whereas our minimal config had started failing validation on
Chrome despite isConfigSupported reporting "supported".

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Dejvino 2026-08-29 20:09:49 +02:00
parent e5484c9c38
commit a9b028b0bf
2 changed files with 13 additions and 6 deletions

View File

@ -123,18 +123,24 @@ async function pickVideoConfig(width, height, bitrate, fps) {
const config = { codec: cand.codec, width, height, bitrate, framerate: fps }; const config = { codec: cand.codec, width, height, bitrate, framerate: fps };
if (cand.avc) config.avc = cand.avc; if (cand.avc) config.avc = cand.avc;
let supported = false; let supported = false;
let negotiatedConfig = null;
let negotiatedHardware = ''; let negotiatedHardware = '';
try { try {
const sup = await VideoEncoder.isConfigSupported(config); const sup = await VideoEncoder.isConfigSupported(config);
supported = !!sup.supported; supported = !!sup.supported;
negotiatedHardware = sup.config ? sup.config.hardwareAcceleration || '' : ''; negotiatedConfig = sup.config || null;
negotiatedHardware = negotiatedConfig ? negotiatedConfig.hardwareAcceleration || '' : '';
} catch { supported = false; } } catch { supported = false; }
attempted.push(`${family.muxerCodec}:${cand.codec}=${supported ? 'ok' : 'no'}`); attempted.push(`${family.muxerCodec}:${cand.codec}=${supported ? 'ok' : 'no'}`);
if (!supported) continue; if (!supported) continue;
// Found a supported profile — does it reorder? Baseline AVC never does; // Use the browser-negotiated config when available — it is guaranteed
// VP9/AV1 may, so we enforce the same test. // to satisfy VideoEncoderConfig validation (required fields, avc
if (await emitsInPresentationOrder(config)) { // shape, etc). Chrome has started rejecting our minimal config with
return { config, muxerCodec: family.muxerCodec, hardwareAcceleration: negotiatedHardware }; // "not of type VideoEncoderConfig" even though isConfigSupported said
// "supported", while the negotiated config it returned configures fine.
const configToTest = negotiatedConfig || config;
if (await emitsInPresentationOrder(configToTest)) {
return { config: configToTest, muxerCodec: family.muxerCodec, hardwareAcceleration: negotiatedHardware };
} }
reorderFailures.push(`${cand.codec}`); reorderFailures.push(`${cand.codec}`);
} }
@ -160,6 +166,7 @@ export async function probeExportCodec(width, height, bitrate, fps = 60) {
const picked = await pickVideoConfig(width, height, bitrate, fps); const picked = await pickVideoConfig(width, height, bitrate, fps);
if (!picked) return null; if (!picked) return null;
return { return {
config: picked.config,
codec: picked.config.codec, codec: picked.config.codec,
muxerCodec: picked.muxerCodec, muxerCodec: picked.muxerCodec,
hardwareAcceleration: picked.hardwareAcceleration || '', hardwareAcceleration: picked.hardwareAcceleration || '',

View File

@ -658,7 +658,7 @@ async function runExport(segment) {
const exporter = new Exporter(state.show); const exporter = new Exporter(state.show);
const blob = segment const blob = segment
? await exportSegment(state.show, state.show.timeline.frame, ? await exportSegment(state.show, state.show.timeline.frame,
{ seconds: 20, preset, onProgress: exportProgress, exporter }) { seconds: 20, preset, onProgress: exportProgress, exporter, videoPick: pick })
: await exporter.export({ preset, onProgress: exportProgress, videoPick: pick }); : await exporter.export({ preset, onProgress: exportProgress, videoPick: pick });
const suffix = segment ? `-segment-${state.show.timeline.frame}` : ''; const suffix = segment ? `-segment-${state.show.timeline.frame}` : '';