From a9b028b0bf9060d639194fa2bce49865bdb331a0 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Sat, 29 Aug 2026 20:09:49 +0200 Subject: [PATCH] Export: fix VideoEncoder.configure TypeError on Chrome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- flow-state/src/export/Exporter.js | 17 ++++++++++++----- flow-state/src/main.js | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/flow-state/src/export/Exporter.js b/flow-state/src/export/Exporter.js index fdf739c..ef137cc 100644 --- a/flow-state/src/export/Exporter.js +++ b/flow-state/src/export/Exporter.js @@ -123,18 +123,24 @@ async function pickVideoConfig(width, height, bitrate, fps) { const config = { codec: cand.codec, width, height, bitrate, framerate: fps }; if (cand.avc) config.avc = cand.avc; let supported = false; + let negotiatedConfig = null; let negotiatedHardware = ''; try { const sup = await VideoEncoder.isConfigSupported(config); supported = !!sup.supported; - negotiatedHardware = sup.config ? sup.config.hardwareAcceleration || '' : ''; + negotiatedConfig = sup.config || null; + negotiatedHardware = negotiatedConfig ? negotiatedConfig.hardwareAcceleration || '' : ''; } catch { supported = false; } attempted.push(`${family.muxerCodec}:${cand.codec}=${supported ? 'ok' : 'no'}`); if (!supported) continue; - // Found a supported profile — does it reorder? Baseline AVC never does; - // VP9/AV1 may, so we enforce the same test. - if (await emitsInPresentationOrder(config)) { - return { config, muxerCodec: family.muxerCodec, hardwareAcceleration: negotiatedHardware }; + // Use the browser-negotiated config when available — it is guaranteed + // to satisfy VideoEncoderConfig validation (required fields, avc + // shape, etc). Chrome has started rejecting our minimal config with + // "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}`); } @@ -160,6 +166,7 @@ export async function probeExportCodec(width, height, bitrate, fps = 60) { const picked = await pickVideoConfig(width, height, bitrate, fps); if (!picked) return null; return { + config: picked.config, codec: picked.config.codec, muxerCodec: picked.muxerCodec, hardwareAcceleration: picked.hardwareAcceleration || '', diff --git a/flow-state/src/main.js b/flow-state/src/main.js index 5b2fab9..535808d 100644 --- a/flow-state/src/main.js +++ b/flow-state/src/main.js @@ -658,7 +658,7 @@ async function runExport(segment) { const exporter = new Exporter(state.show); const blob = segment ? 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 }); const suffix = segment ? `-segment-${state.show.timeline.frame}` : '';