chatwoot/chatwoot · error · Error
No Opus frames found in WebM input
Error message
No Opus frames found in WebM input
What it means
remuxWebmToOgg parses the WebM container (EBML tracks + SimpleBlocks) to extract Opus packets and repack them into an OGG/Opus stream. The guard fires when parsing succeeded structurally but zero Opus frames were collected — the input had no usable audio track payload. It fires only after the OggS magic-bytes short-circuit, so it is specifically about a WebM file whose audio content is absent or unrecognized.
Source
Thrown at app/javascript/dashboard/components/widgets/WootWriter/utils/webmOpusToOgg.js:386
*/
export async function remuxWebmToOgg(webmBlob) {
const buffer = await webmBlob.arrayBuffer();
const bytes = new Uint8Array(buffer);
// Already OGG? Return unchanged.
if (
bytes.length >= 4 &&
bytes[0] === 0x4f &&
bytes[1] === 0x67 &&
bytes[2] === 0x67 &&
bytes[3] === 0x53
) {
return webmBlob;
}
const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);
if (frames.length === 0) {
throw new Error('No Opus frames found in WebM input');
}
// Extract pre-skip from the WebM CodecPrivate (which IS the OpusHead)
let preSkip = 312;
if (codecPrivate && codecPrivate.length >= 12) {
const magic = new TextDecoder().decode(codecPrivate.slice(0, 8));
if (magic === 'OpusHead') {
preSkip = new DataView(
codecPrivate.buffer,
codecPrivate.byteOffset,
codecPrivate.length
).getUint16(10, true);
}
}
const serial = (Math.random() * 0x100000000) >>> 0;
let pageSeq = 0;
const pages = [];View on GitHub (pinned to ed230f9bc0)
Solutions
- Guard before remuxing: skip conversion for blobs under a minimum size (e.g. a few KB) and treat them as empty recordings in the UI
- Ensure the MediaRecorder is created with an explicitly supported Opus mimeType: MediaRecorder.isTypeSupported('audio/webm;codecs=opus')
- Verify the recorder flow collects chunks via recorder.ondataavailable with timeslice or on stop, and that stop() completed before converting
- Log bytes.length and the parsed track list when the error fires to distinguish empty input from codec mismatch
- If input is header-only due to interruption, surface a 'recording too short' message instead of a raw error
Example fix
// before
const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);
if (frames.length === 0) {
throw new Error('No Opus frames found in WebM input');
}
// after (caller-side guard + clearer message)
if (webmBlob.size < 1024) {
throw new Error('Recording is empty or too short to convert');
}
const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);
if (frames.length === 0) {
throw new Error('No Opus frames found — input has no decodable Opus audio track');
} Defensive patterns
Strategy: validation
Validate before calling
const MIN_REMUX_BYTES = 1024; // header-only WebM is smaller than this
if (webmBlob.size < MIN_REMUX_BYTES) {
throw new Error('Recording is empty or too short to convert');
}
const supportsOpus = MediaRecorder.isTypeSupported('audio/webm;codecs=opus');
if (!supportsOpus) {
/* choose a different output format up front instead of remuxing later */
} Type guard
const isNonEmptyWebm = blob => blob instanceof Blob && blob.size > 1024 && /webm/i.test(blob.type);
Try / catch
try {
const ogg = await remuxWebmToOgg(webmBlob);
} catch (error) {
if (error.message.includes('No Opus frames')) {
// Treat as empty recording: prompt the user instead of surfacing a codec error
return showEmptyRecordingNotice();
}
throw error;
} Prevention
- Create the MediaRecorder with an explicitly Opus mimeType verified via isTypeSupported
- Ignore stop events that fire before the first ondataavailable chunk lands
- Enforce a minimum recording duration in the UI before enabling send/convert
When it happens
Trigger: A recording blob with size > 0 but no data blocks: user hit stop before ondataavailable delivered audio; a MediaRecorder created without an Opus audio codec (e.g. video-only webm or a vp8/vp9-only mimeType) so no block maps to the Opus track; a truncated WebM from an interrupted recording where only the header survived; a corrupted cluster segment the parser skips.
Common situations: Instant tap-and-release on the voice-note button in the WootWriter recorder; browser-specific MediaRecorder quirks where mimeType 'audio/webm;codecs=opus' was requested but not honored; memory pressure on mobile causing the recorder to drop all chunks; feeding a pre-existing OGG-less or header-only file into convertAudio's 'audio/ogg' path because its MIME was reported as webm.
Related errors
AI-assisted analysis of chatwoot/chatwoot@ed230f9bc0 (2026-08-21).
Data as JSON: /api/errors/3f23721538432e82.
Report an issue: GitHub.