* Add non-owning conversions via ConversionOptions.ownsOutput
A conversion with ownsOutput: false only adds tracks to the output and
drives their media data; starting, finalizing, and metadata tags remain
the caller's responsibility. This lets multiple conversions and
directly-added user tracks compose on a single Output (see upstream
issue #436).
- ownsOutput: false allows a pre-populated output (state must still be
'pending') and seeds track-capacity accounting from existing tracks
- execute() requires the output to be started and never finalizes it
- cancel() closes only the conversion's own sources, releasing internal
synchronizer waiters, and leaves the output usable
- tags cannot be combined with ownsOutput: false
- isValid requires at least one contributed track instead of the
format's minimum track counts
Prototype for API discussion; default (owning) behavior is unchanged.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AJdfnbY9AFh9i9dKgtrj6E
* Add external-audio example using a non-owning conversion
Demonstrates composing a user-owned audio track (synthesized voiceover
via OfflineAudioContext + AudioBufferSource) onto a picked video with
Conversion.init({ ownsOutput: false }), including progress reporting
and playback/download of the result.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AJdfnbY9AFh9i9dKgtrj6E
* Release synchronizer waiters when canceling during output finalization
A non-owning conversion's cancel() previously no-oped entirely when the
output (owned by someone else) was already finalizing or finalized,
leaving pump loops parked in the track synchronizer and hanging
execute() forever. Now it still marks the conversion canceled and
releases parked waiters in that state, without force-closing sources
(finalization owns flushing them at that point).
Also adds coverage: non-owning onProgress monotonicity, canceling one
of two sibling conversions, capacity seeding across sequential inits,
exact metadata exclusivity, and cancel-before-execute.
Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AJdfnbY9AFh9i9dKgtrj6E
* Document non-owning conversions on the converting-media-files guide page
Adds the doc section requested in #436: what ownsOutput: false does, the
required choreography (add tracks -> output.start() before execute() ->
run the conversion concurrently with your own sources -> finalize), the
cancellation split (conversion.cancel() leaves the output alive; cancel
both for a full abort and tear the output down on error paths), isValid
semantics in this mode, and the tags restriction with the
setMetadataTags() alternative. Also cross-links the fresh-output rule to
the new section. VitePress build passes with dead-link checking on.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
* Clean up conversion logic, add Output.tracks and .hasEnoughTracks(), move new conversion tests around, remove external audio example
* non-owning -> composable, and update docs
* Update
---------
Co-authored-by: Claude <[email protected]>
Co-authored-by: Vanilagy <[email protected]>
* Fix WebVTT-in-MP4 output by starting the aux writer
The ISOBMFF muxer advertises WebVTT as a supported subtitle codec and maps
it to the wvtt sample entry, but IsobmffMuxer.start() never calls
auxWriter.start(). The aux writer builds subtitle sample boxes in memory, so
the first box write for any subtitle track hits its started === false assert
and muxing fails.
Start the aux writer alongside the main writer so WebVTT subtitle tracks can
be written to MP4/MOV.
* Move code around, add simple WebVTT muxing test
---------
Co-authored-by: hikari <[email protected]>
Co-authored-by: Vanilagy <[email protected]>
* fix: ensure at least one track per type is enabled for Apple ecosystem compatibility
* Add ensureOneEnabledTrack() to ISOBMFF muxer
* Add test case
---------
Co-authored-by: Vanilagy <[email protected]>
* Fix CTS=0 in fragmented fMP4 with multiple tracks
When muxing fragmented MP4 with both video and audio tracks,
compositionTimeOffset (CTS) was zero for all video samples, causing
B-frame content to display in decode order instead of presentation
order (visible judder).
Root cause: During finalize(), interleaveSamples(true) triggers
finalizeFragment() via the cross-track keyframe check in
addSampleToTrack. This writes the trun box before processTimestamps
has computed correct decodeTimestamp values, so CTS = PTS - DTS = 0.
Fix: Call processTimestamps() for all tracks at the start of
finalizeFragment(). This is safe because processTimestamps is a no-op
when the timestampProcessingQueue is empty.
Video-only fragmented muxing was unaffected because the single-track
case never triggers the cross-track keyframe check during
interleaveSamples.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
* Fix the problem at the actual root
* Oopsie doopsie
---------
Co-authored-by: Claude Opus 4.6 <[email protected]>
Co-authored-by: Vanilagy <[email protected]>
* ac3/eac3 detection
* fix tests
* review pass + follow codebase comments style
* Add ac3/eac3 to docs
* WIP mpeg-2
* Set registration_descriptor
* add support for ac3 system b in mpeg-ts
* Update docs with ac3 in .ts support
* fix formatting
* Remove default to fix linter
* Add eac3 to mpeg-ts
* fix formatting
* Fix failing test
* Refactor AC3/EAC3 code a little
---------
Co-authored-by: Vanilagy <[email protected]>
- Added Worker- or AudioContext-based fallbacks in case MediaStreamTrackProcessor is not available
- Fixed fMP4-streamed files not playing in Safari
- Added better error handling for MediaStream sources
- Improved the live recording example to be more robust