{"record":{"id":"3f23721538432e82","repo":"chatwoot/chatwoot","slug":"no-opus-frames-found-in-webm-input","errorCode":null,"errorMessage":"No Opus frames found in WebM input","messagePattern":"No Opus frames found in WebM input","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app/javascript/dashboard/components/widgets/WootWriter/utils/webmOpusToOgg.js","lineNumber":386,"sourceCode":" */\nexport async function remuxWebmToOgg(webmBlob) {\n  const buffer = await webmBlob.arrayBuffer();\n  const bytes = new Uint8Array(buffer);\n\n  // Already OGG? Return unchanged.\n  if (\n    bytes.length >= 4 &&\n    bytes[0] === 0x4f &&\n    bytes[1] === 0x67 &&\n    bytes[2] === 0x67 &&\n    bytes[3] === 0x53\n  ) {\n    return webmBlob;\n  }\n\n  const { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);\n  if (frames.length === 0) {\n    throw new Error('No Opus frames found in WebM input');\n  }\n\n  // Extract pre-skip from the WebM CodecPrivate (which IS the OpusHead)\n  let preSkip = 312;\n  if (codecPrivate && codecPrivate.length >= 12) {\n    const magic = new TextDecoder().decode(codecPrivate.slice(0, 8));\n    if (magic === 'OpusHead') {\n      preSkip = new DataView(\n        codecPrivate.buffer,\n        codecPrivate.byteOffset,\n        codecPrivate.length\n      ).getUint16(10, true);\n    }\n  }\n\n  const serial = (Math.random() * 0x100000000) >>> 0;\n  let pageSeq = 0;\n  const pages = [];","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/chatwoot/chatwoot/blob/ed230f9bc068e7a13dd5c0404d5465a204b68d4c/app/javascript/dashboard/components/widgets/WootWriter/utils/webmOpusToOgg.js#L368-L404","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);\nif (frames.length === 0) {\n  throw new Error('No Opus frames found in WebM input');\n}\n\n// after (caller-side guard + clearer message)\nif (webmBlob.size < 1024) {\n  throw new Error('Recording is empty or too short to convert');\n}\nconst { channels, sampleRate, codecPrivate, frames } = parseWebM(buffer);\nif (frames.length === 0) {\n  throw new Error('No Opus frames found — input has no decodable Opus audio track');\n}","handlingStrategy":"validation","validationCode":"const MIN_REMUX_BYTES = 1024; // header-only WebM is smaller than this\nif (webmBlob.size < MIN_REMUX_BYTES) {\n  throw new Error('Recording is empty or too short to convert');\n}\nconst supportsOpus = MediaRecorder.isTypeSupported('audio/webm;codecs=opus');\nif (!supportsOpus) {\n  /* choose a different output format up front instead of remuxing later */\n}","typeGuard":"const isNonEmptyWebm = blob =>\n  blob instanceof Blob && blob.size > 1024 && /webm/i.test(blob.type);","tryCatchPattern":"try {\n  const ogg = await remuxWebmToOgg(webmBlob);\n} catch (error) {\n  if (error.message.includes('No Opus frames')) {\n    // Treat as empty recording: prompt the user instead of surfacing a codec error\n    return showEmptyRecordingNotice();\n  }\n  throw error;\n}","preventionTips":["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"],"tags":["ogg","opus","webm","remux","media-recorder","voice-note"],"backgroundTag":"empty-media-recording","analyzedSha":"ed230f9bc068e7a13dd5c0404d5465a204b68d4c","analyzedAt":"2026-08-21T12:45:25.920Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}