avbridge 2.2.1 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. package/CHANGELOG.md +153 -1
  2. package/NOTICE.md +2 -2
  3. package/README.md +2 -3
  4. package/THIRD_PARTY_LICENSES.md +2 -2
  5. package/dist/avi-2JPBSHGA.js +183 -0
  6. package/dist/avi-2JPBSHGA.js.map +1 -0
  7. package/dist/avi-F6WZJK5T.cjs +185 -0
  8. package/dist/avi-F6WZJK5T.cjs.map +1 -0
  9. package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
  10. package/dist/avi-NJXAXUXK.js.map +1 -0
  11. package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
  12. package/dist/avi-W6L3BTWU.cjs.map +1 -0
  13. package/dist/chunk-2IJ66NTD.cjs +212 -0
  14. package/dist/chunk-2IJ66NTD.cjs.map +1 -0
  15. package/dist/{chunk-ILKDNBSE.js → chunk-2XW2O3YI.cjs} +55 -10
  16. package/dist/chunk-2XW2O3YI.cjs.map +1 -0
  17. package/dist/chunk-5KVLE6YI.js +167 -0
  18. package/dist/chunk-5KVLE6YI.js.map +1 -0
  19. package/dist/chunk-5YAWWKA3.js +18 -0
  20. package/dist/chunk-5YAWWKA3.js.map +1 -0
  21. package/dist/chunk-CPJLFFCC.js +189 -0
  22. package/dist/chunk-CPJLFFCC.js.map +1 -0
  23. package/dist/chunk-CPZ7PXAM.cjs +240 -0
  24. package/dist/chunk-CPZ7PXAM.cjs.map +1 -0
  25. package/dist/{chunk-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
  26. package/dist/chunk-DCSOQH2N.js.map +1 -0
  27. package/dist/{chunk-HZLQNKFN.cjs → chunk-E76AMWI4.js} +40 -15
  28. package/dist/chunk-E76AMWI4.js.map +1 -0
  29. package/dist/chunk-F3LQJKXK.cjs +20 -0
  30. package/dist/chunk-F3LQJKXK.cjs.map +1 -0
  31. package/dist/chunk-IAYKFGFG.js +200 -0
  32. package/dist/chunk-IAYKFGFG.js.map +1 -0
  33. package/dist/{chunk-DMWARSEF.js → chunk-KY2GPCT7.js} +788 -697
  34. package/dist/chunk-KY2GPCT7.js.map +1 -0
  35. package/dist/chunk-LUFA47FP.js +19 -0
  36. package/dist/chunk-LUFA47FP.js.map +1 -0
  37. package/dist/chunk-NNVOHKXJ.cjs +204 -0
  38. package/dist/chunk-NNVOHKXJ.cjs.map +1 -0
  39. package/dist/chunk-Q2VUO52Z.cjs +374 -0
  40. package/dist/chunk-Q2VUO52Z.cjs.map +1 -0
  41. package/dist/chunk-QDJLQR53.cjs +22 -0
  42. package/dist/chunk-QDJLQR53.cjs.map +1 -0
  43. package/dist/chunk-S4WAZC2T.cjs +173 -0
  44. package/dist/chunk-S4WAZC2T.cjs.map +1 -0
  45. package/dist/chunk-SMH6IOP2.js +368 -0
  46. package/dist/chunk-SMH6IOP2.js.map +1 -0
  47. package/dist/chunk-SR3MPV4D.js +237 -0
  48. package/dist/chunk-SR3MPV4D.js.map +1 -0
  49. package/dist/{chunk-UF2N5L63.cjs → chunk-TBW26OPP.cjs} +800 -710
  50. package/dist/chunk-TBW26OPP.cjs.map +1 -0
  51. package/dist/chunk-X2K3GIWE.js +235 -0
  52. package/dist/chunk-X2K3GIWE.js.map +1 -0
  53. package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
  54. package/dist/chunk-Z33SBWL5.cjs.map +1 -0
  55. package/dist/chunk-ZCUXHW55.cjs +242 -0
  56. package/dist/chunk-ZCUXHW55.cjs.map +1 -0
  57. package/dist/element-browser.js +1282 -503
  58. package/dist/element-browser.js.map +1 -1
  59. package/dist/element.cjs +59 -5
  60. package/dist/element.cjs.map +1 -1
  61. package/dist/element.d.cts +39 -1
  62. package/dist/element.d.ts +39 -1
  63. package/dist/element.js +58 -4
  64. package/dist/element.js.map +1 -1
  65. package/dist/index.cjs +605 -327
  66. package/dist/index.cjs.map +1 -1
  67. package/dist/index.d.cts +48 -4
  68. package/dist/index.d.ts +48 -4
  69. package/dist/index.js +528 -319
  70. package/dist/index.js.map +1 -1
  71. package/dist/libav-demux-H2GS46GH.cjs +27 -0
  72. package/dist/{libav-http-reader-NQJVY273.js.map → libav-demux-H2GS46GH.cjs.map} +1 -1
  73. package/dist/libav-demux-OWZ4T2YW.js +6 -0
  74. package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-demux-OWZ4T2YW.js.map} +1 -1
  75. package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
  76. package/dist/libav-http-reader-AZLE7YFS.cjs.map +1 -0
  77. package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
  78. package/dist/libav-http-reader-WXG3Z7AI.js.map +1 -0
  79. package/dist/{libav-import-GST2AMPL.cjs → libav-import-2ZVKV2E7.cjs} +2 -2
  80. package/dist/{libav-import-GST2AMPL.cjs.map → libav-import-2ZVKV2E7.cjs.map} +1 -1
  81. package/dist/{libav-import-2JURFHEW.js → libav-import-6MGLCXVQ.js} +2 -2
  82. package/dist/{libav-import-2JURFHEW.js.map → libav-import-6MGLCXVQ.js.map} +1 -1
  83. package/dist/{player-U2NPmFvA.d.cts → player-B6WB74RD.d.cts} +62 -3
  84. package/dist/{player-U2NPmFvA.d.ts → player-B6WB74RD.d.ts} +62 -3
  85. package/dist/player.cjs +5631 -0
  86. package/dist/player.cjs.map +1 -0
  87. package/dist/player.d.cts +699 -0
  88. package/dist/player.d.ts +699 -0
  89. package/dist/player.js +5629 -0
  90. package/dist/player.js.map +1 -0
  91. package/dist/remux-OBSMIENG.cjs +35 -0
  92. package/dist/remux-OBSMIENG.cjs.map +1 -0
  93. package/dist/remux-WBYIZBBX.js +10 -0
  94. package/dist/remux-WBYIZBBX.js.map +1 -0
  95. package/dist/source-4TZ6KMNV.js +4 -0
  96. package/dist/{source-FFZ7TW2B.js.map → source-4TZ6KMNV.js.map} +1 -1
  97. package/dist/source-7YLO6E7X.cjs +29 -0
  98. package/dist/{source-CN43EI7Z.cjs.map → source-7YLO6E7X.cjs.map} +1 -1
  99. package/dist/source-MTX5ELUZ.js +4 -0
  100. package/dist/source-MTX5ELUZ.js.map +1 -0
  101. package/dist/source-VFLXLOCN.cjs +29 -0
  102. package/dist/source-VFLXLOCN.cjs.map +1 -0
  103. package/dist/subtitles-4T74JRGT.js +4 -0
  104. package/dist/subtitles-4T74JRGT.js.map +1 -0
  105. package/dist/subtitles-QUH4LPI4.cjs +29 -0
  106. package/dist/subtitles-QUH4LPI4.cjs.map +1 -0
  107. package/dist/variant-routing-434STYAB.js +3 -0
  108. package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
  109. package/dist/variant-routing-HONNAA6R.cjs +12 -0
  110. package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
  111. package/package.json +9 -1
  112. package/src/classify/rules.ts +27 -5
  113. package/src/convert/remux.ts +9 -35
  114. package/src/convert/transcode-libav.ts +691 -0
  115. package/src/convert/transcode.ts +53 -12
  116. package/src/element/avbridge-player.ts +861 -0
  117. package/src/element/avbridge-video.ts +54 -0
  118. package/src/element/player-icons.ts +25 -0
  119. package/src/element/player-styles.ts +472 -0
  120. package/src/errors.ts +53 -0
  121. package/src/index.ts +23 -0
  122. package/src/player-element.ts +18 -0
  123. package/src/player.ts +118 -27
  124. package/src/plugins/builtin.ts +2 -2
  125. package/src/probe/avi.ts +4 -0
  126. package/src/probe/index.ts +40 -10
  127. package/src/strategies/fallback/audio-output.ts +31 -0
  128. package/src/strategies/fallback/decoder.ts +179 -175
  129. package/src/strategies/fallback/index.ts +48 -6
  130. package/src/strategies/fallback/libav-import.ts +9 -1
  131. package/src/strategies/fallback/variant-routing.ts +7 -13
  132. package/src/strategies/fallback/video-renderer.ts +231 -32
  133. package/src/strategies/hybrid/decoder.ts +219 -200
  134. package/src/strategies/hybrid/index.ts +48 -7
  135. package/src/strategies/native.ts +6 -3
  136. package/src/strategies/remux/index.ts +14 -2
  137. package/src/strategies/remux/mse.ts +12 -2
  138. package/src/strategies/remux/pipeline.ts +72 -12
  139. package/src/subtitles/index.ts +7 -3
  140. package/src/subtitles/render.ts +8 -0
  141. package/src/types.ts +53 -1
  142. package/src/util/libav-demux.ts +405 -0
  143. package/src/util/libav-http-reader.ts +5 -1
  144. package/src/util/source.ts +28 -8
  145. package/src/util/transport.ts +26 -0
  146. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  147. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  148. package/dist/avi-6SJLWIWW.cjs.map +0 -1
  149. package/dist/avi-GCGM7OJI.js.map +0 -1
  150. package/dist/chunk-DMWARSEF.js.map +0 -1
  151. package/dist/chunk-HZLQNKFN.cjs.map +0 -1
  152. package/dist/chunk-ILKDNBSE.js.map +0 -1
  153. package/dist/chunk-J5MCMN3S.js +0 -27
  154. package/dist/chunk-J5MCMN3S.js.map +0 -1
  155. package/dist/chunk-L4NPOJ36.cjs.map +0 -1
  156. package/dist/chunk-NZU7W256.cjs +0 -29
  157. package/dist/chunk-NZU7W256.cjs.map +0 -1
  158. package/dist/chunk-UF2N5L63.cjs.map +0 -1
  159. package/dist/chunk-WD2ZNQA7.js.map +0 -1
  160. package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
  161. package/dist/libav-http-reader-NQJVY273.js +0 -3
  162. package/dist/source-CN43EI7Z.cjs +0 -28
  163. package/dist/source-FFZ7TW2B.js +0 -3
  164. package/dist/variant-routing-GOHB2RZN.cjs +0 -12
  165. package/dist/variant-routing-JOBWXYKD.js +0 -3
package/dist/index.cjs CHANGED
@@ -1,346 +1,523 @@
1
1
  'use strict';
2
2
 
3
- var chunkUF2N5L63_cjs = require('./chunk-UF2N5L63.cjs');
4
- var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
5
- var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
3
+ var chunkQ2VUO52Z_cjs = require('./chunk-Q2VUO52Z.cjs');
4
+ var chunkTBW26OPP_cjs = require('./chunk-TBW26OPP.cjs');
5
+ var chunkS4WAZC2T_cjs = require('./chunk-S4WAZC2T.cjs');
6
+ var chunkZCUXHW55_cjs = require('./chunk-ZCUXHW55.cjs');
7
+ var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
8
+ require('./chunk-QDJLQR53.cjs');
9
+ require('./chunk-CPZ7PXAM.cjs');
10
+ require('./chunk-Z33SBWL5.cjs');
6
11
  require('./chunk-G4APZMCP.cjs');
7
- require('./chunk-NZU7W256.cjs');
12
+ require('./chunk-F3LQJKXK.cjs');
8
13
 
9
- // src/convert/remux.ts
10
- var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
11
- "mp4",
12
- "mov",
13
- "mkv",
14
- "webm",
15
- "ogg",
16
- "wav",
17
- "mp3",
18
- "flac",
19
- "adts"
20
- ]);
21
- async function remux(source, options = {}) {
22
- const outputFormat = options.outputFormat ?? "mp4";
23
- options.signal?.throwIfAborted();
24
- const ctx = await chunkUF2N5L63_cjs.probe(source);
25
- options.signal?.throwIfAborted();
26
- validateRemuxEligibility(ctx, options.strict ?? false);
27
- if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
28
- return remuxViaMediAbunny(ctx, outputFormat, options);
29
- }
30
- return remuxViaLibav(ctx, outputFormat, options);
14
+ // src/convert/transcode-libav.ts
15
+ function isLibavTranscodeContainer(container) {
16
+ return container === "avi" || container === "asf" || container === "flv" || container === "rm";
31
17
  }
32
- function validateRemuxEligibility(ctx, strict) {
33
- const video = ctx.videoTracks[0];
34
- const audio = ctx.audioTracks[0];
35
- if (video) {
36
- const mbCodec = chunkUF2N5L63_cjs.avbridgeVideoToMediabunny(video.codec);
37
- if (!mbCodec) {
38
- throw new Error(
39
- `Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
40
- );
41
- }
42
- }
43
- if (audio) {
44
- const mbCodec = chunkUF2N5L63_cjs.avbridgeAudioToMediabunny(audio.codec);
45
- if (!mbCodec) {
46
- throw new Error(
47
- `Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
48
- );
49
- }
50
- }
51
- if (strict && video?.codec === "h264" && audio?.codec === "mp3") {
52
- throw new Error(
53
- `Cannot remux in strict mode: H.264 + MP3 is a best-effort combination that may produce playback issues in some browsers. Set strict: false to allow, or use transcode() to re-encode audio to AAC.`
18
+ async function transcodeViaLibav(ctx, options) {
19
+ const outputFormat = options.outputFormat ?? "mp4";
20
+ if (outputFormat !== "mp4" && outputFormat !== "webm" && outputFormat !== "mkv") {
21
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
22
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
23
+ `legacy-container transcode supports MP4, WebM, and MKV output (got "${outputFormat}").`,
24
+ `Use outputFormat: "mp4", "webm", or "mkv".`
54
25
  );
55
26
  }
56
- if (!video && !audio) {
57
- throw new Error("Cannot remux: source has no video or audio tracks.");
58
- }
59
- }
60
- async function remuxViaMediAbunny(ctx, outputFormat, options) {
61
- const mb = await import('mediabunny');
62
- const input = new mb.Input({
63
- source: await chunkUF2N5L63_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
64
- formats: mb.ALL_FORMATS
65
- });
66
- const target = new mb.BufferTarget();
67
- const output = new mb.Output({
68
- format: createOutputFormat(mb, outputFormat),
69
- target
70
- });
71
- const conversion = await mb.Conversion.init({
72
- input,
73
- output,
74
- showWarnings: false
27
+ const videoCodec = options.videoCodec ?? (outputFormat === "webm" ? "vp9" : "h264");
28
+ const audioCodec = options.audioCodec ?? (outputFormat === "webm" ? "opus" : "aac");
29
+ const quality = options.quality ?? "medium";
30
+ options.signal?.throwIfAborted();
31
+ const [
32
+ mb,
33
+ { openLibavDemux, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 },
34
+ { normalizeSource },
35
+ { createOutputFormat: createOutputFormat2, mimeForFormat: mimeForFormat2, generateFilename: generateFilename2 }
36
+ ] = await Promise.all([
37
+ import('mediabunny'),
38
+ import('./libav-demux-H2GS46GH.cjs'),
39
+ import('./source-VFLXLOCN.cjs'),
40
+ import('./remux-OBSMIENG.cjs')
41
+ ]);
42
+ const normalized = await normalizeSource(ctx.source);
43
+ const demux = await openLibavDemux({
44
+ source: normalized,
45
+ filename: ctx.name ?? "input.bin",
46
+ context: ctx
47
+ // transport config is not yet threaded through ConvertOptions; add
48
+ // later if URL-source transcode with signed URLs becomes a need.
75
49
  });
76
- if (!conversion.isValid) {
77
- const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
78
- throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);
79
- }
80
- if (options.onProgress) {
81
- const onProgress = options.onProgress;
82
- conversion.onProgress = (p) => {
83
- onProgress({ percent: p * 100, bytesWritten: 0 });
84
- };
85
- }
86
- let abortHandler;
87
- if (options.signal) {
88
- options.signal.throwIfAborted();
89
- abortHandler = () => void conversion.cancel();
90
- options.signal.addEventListener("abort", abortHandler, { once: true });
91
- }
92
50
  try {
93
- await conversion.execute();
94
- } finally {
95
- if (abortHandler && options.signal) {
96
- options.signal.removeEventListener("abort", abortHandler);
51
+ let throwIfAborted2 = function() {
52
+ if (ac?.aborted) {
53
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
54
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_ABORTED,
55
+ "transcode: aborted by caller.",
56
+ void 0
57
+ );
58
+ }
59
+ }, throwIfDrainError2 = function() {
60
+ if (drainError) {
61
+ const msg = drainError.message;
62
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
63
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
64
+ `transcode: video decoder error: ${msg}`,
65
+ "This usually indicates the WebCodecs decoder rejected a malformed packet."
66
+ );
67
+ }
68
+ };
69
+ var throwIfAborted = throwIfAborted2, throwIfDrainError = throwIfDrainError2;
70
+ options.signal?.throwIfAborted();
71
+ if (!demux.videoStream && !demux.audioStream) {
72
+ throw new Error("transcode: source has no decodable tracks");
97
73
  }
98
- }
99
- if (!target.buffer) {
100
- throw new Error("Remux failed: mediabunny produced no output buffer.");
101
- }
102
- const mimeType = mimeForFormat(outputFormat);
103
- const blob = new Blob([target.buffer], { type: mimeType });
104
- const filename = generateFilename(ctx.name, outputFormat);
105
- options.onProgress?.({ percent: 100, bytesWritten: blob.size });
106
- return {
107
- blob,
108
- mimeType,
109
- container: outputFormat,
110
- videoCodec: ctx.videoTracks[0]?.codec,
111
- audioCodec: ctx.audioTracks[0]?.codec,
112
- duration: ctx.duration,
113
- filename
114
- };
115
- }
116
- async function remuxViaLibav(ctx, outputFormat, options) {
117
- let loadLibav;
118
- let pickLibavVariant;
119
- try {
120
- const loader = await import('./libav-loader-IV4AJ2HW.cjs');
121
- const routing = await import('./variant-routing-GOHB2RZN.cjs');
122
- loadLibav = loader.loadLibav;
123
- pickLibavVariant = routing.pickLibavVariant;
124
- } catch {
125
- throw new Error(
126
- `Cannot remux ${ctx.container.toUpperCase()} source: libav.js is not available. Install @libav.js/variant-webcodecs and libavjs-webcodecs-bridge, or build the custom avbridge variant with scripts/build-libav.sh.`
127
- );
128
- }
129
- const variant = pickLibavVariant(ctx);
130
- const libav = await loadLibav(variant);
131
- const normalized = await chunkHZLQNKFN_cjs.normalizeSource(ctx.source);
132
- const filename = ctx.name ?? `remux-input-${Date.now()}`;
133
- const handle = await chunkL4NPOJ36_cjs.prepareLibavInput(libav, filename, normalized);
134
- try {
135
- return await doLibavRemux(libav, filename, ctx, outputFormat, options);
136
- } finally {
137
- await handle.detach().catch(() => {
74
+ if (options.outputStream) {
75
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
76
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
77
+ "outputStream is not yet supported for the libav-backed transcode path.",
78
+ "Remove the outputStream option to receive the transcoded blob in memory. Streaming output for this path is on the roadmap."
79
+ );
80
+ }
81
+ const bufferTarget = new mb.BufferTarget();
82
+ const output = new mb.Output({
83
+ format: createOutputFormat2(mb, outputFormat),
84
+ target: bufferTarget
138
85
  });
139
- }
140
- }
141
- async function doLibavRemux(libav, filename, ctx, outputFormat, options) {
142
- const mb = await import('mediabunny');
143
- const readPkt = await libav.av_packet_alloc();
144
- const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename);
145
- const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
146
- const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
147
- const videoTrackInfo = ctx.videoTracks[0];
148
- const audioTrackInfo = ctx.audioTracks[0];
149
- const mbVideoCodec = videoTrackInfo ? chunkUF2N5L63_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
- const mbAudioCodec = audioTrackInfo ? chunkUF2N5L63_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
151
- const target = new mb.BufferTarget();
152
- const output = new mb.Output({
153
- format: createOutputFormat(mb, outputFormat),
154
- target
155
- });
156
- let videoSource = null;
157
- let audioSource = null;
158
- if (mbVideoCodec && videoStream) {
159
- videoSource = new mb.EncodedVideoPacketSource(mbVideoCodec);
160
- output.addVideoTrack(videoSource);
161
- }
162
- if (mbAudioCodec && audioStream) {
163
- audioSource = new mb.EncodedAudioPacketSource(mbAudioCodec);
164
- output.addAudioTrack(audioSource);
165
- }
166
- await output.start();
167
- const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
168
- const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
169
- let syntheticVideoUs = 0;
170
- let syntheticAudioUs = 0;
171
- const videoTimeBase = videoStream?.time_base_num && videoStream?.time_base_den ? [videoStream.time_base_num, videoStream.time_base_den] : void 0;
172
- const audioTimeBase = audioStream?.time_base_num && audioStream?.time_base_den ? [audioStream.time_base_num, audioStream.time_base_den] : void 0;
173
- let totalPackets = 0;
174
- const durationUs = ctx.duration ? ctx.duration * 1e6 : 0;
175
- let firstVideoMeta = true;
176
- let firstAudioMeta = true;
177
- while (true) {
178
- options.signal?.throwIfAborted();
179
- let readErr;
180
- let packets;
181
- try {
182
- [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
183
- limit: 64 * 1024
184
- });
185
- } catch (err) {
186
- throw new Error(`libav demux failed: ${err.message}`);
86
+ const bridge = await loadBridge();
87
+ let videoDecoder = null;
88
+ let videoSoftDec = null;
89
+ let videoSource = null;
90
+ let videoBsfCtx = null;
91
+ let videoBsfPkt = null;
92
+ let videoWidth = 0;
93
+ let videoHeight = 0;
94
+ let videoTimeBase;
95
+ const frameQueue = [];
96
+ const MAX_QUEUE = 16;
97
+ let draining = false;
98
+ let drainError = null;
99
+ let activeDrain = null;
100
+ const drain = () => {
101
+ if (draining) return activeDrain ?? Promise.resolve();
102
+ draining = true;
103
+ const run = (async () => {
104
+ try {
105
+ while (frameQueue.length > 0 && !drainError) {
106
+ const frame = frameQueue.shift();
107
+ try {
108
+ const sample = new mb.VideoSample(frame, {
109
+ timestamp: (frame.timestamp ?? 0) / 1e6
110
+ // µs s
111
+ });
112
+ await videoSource.add(sample);
113
+ } finally {
114
+ frame.close();
115
+ }
116
+ }
117
+ } catch (err) {
118
+ drainError = err;
119
+ while (frameQueue.length > 0) {
120
+ try {
121
+ frameQueue.shift().close();
122
+ } catch {
123
+ }
124
+ }
125
+ } finally {
126
+ draining = false;
127
+ activeDrain = null;
128
+ }
129
+ })();
130
+ activeDrain = run;
131
+ return run;
132
+ };
133
+ if (demux.videoStream && !options.dropVideo) {
134
+ try {
135
+ const bitDepth = ctx.videoTracks[0]?.bitDepth ?? 8;
136
+ if (bitDepth > 8) {
137
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
138
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
139
+ `transcode: 10-bit video is not supported in this release (source bit depth: ${bitDepth}).`,
140
+ `Phase 1 transcode handles 8-bit video only. 10-bit support is on the roadmap.`
141
+ );
142
+ }
143
+ if (demux.videoStream.time_base_num && demux.videoStream.time_base_den) {
144
+ videoTimeBase = [demux.videoStream.time_base_num, demux.videoStream.time_base_den];
145
+ }
146
+ let config = null;
147
+ try {
148
+ config = await bridge.videoStreamToConfig(demux.libav, demux.videoStream);
149
+ } catch {
150
+ config = null;
151
+ }
152
+ const supported = config ? await VideoDecoder.isConfigSupported(config).catch(() => ({ supported: false })) : { supported: false };
153
+ videoWidth = config?.codedWidth ?? ctx.videoTracks[0]?.width ?? 0;
154
+ videoHeight = config?.codedHeight ?? ctx.videoTracks[0]?.height ?? 0;
155
+ if (config && supported.supported) {
156
+ videoDecoder = new VideoDecoder({
157
+ output: (frame) => {
158
+ if (frameQueue.length >= MAX_QUEUE) {
159
+ frame.close();
160
+ return;
161
+ }
162
+ frameQueue.push(frame);
163
+ void drain();
164
+ },
165
+ error: (err) => {
166
+ drainError = err;
167
+ }
168
+ });
169
+ videoDecoder.configure(config);
170
+ } else {
171
+ const libavSoft = demux.libav;
172
+ const [, c, pkt, frame] = await libavSoft.ff_init_decoder(
173
+ demux.videoStream.codec_id,
174
+ { codecpar: demux.videoStream.codecpar }
175
+ );
176
+ videoSoftDec = { c, pkt, frame };
177
+ }
178
+ videoSource = new mb.VideoSampleSource({
179
+ codec: avbridgeVideoToMediabunny(videoCodec),
180
+ bitrate: qualityToMediabunny(mb, quality, options.videoBitrate),
181
+ ...options.frameRate !== void 0 ? { frameRate: options.frameRate } : {},
182
+ ...options.hardwareAcceleration !== void 0 ? { hardwareAcceleration: options.hardwareAcceleration } : {},
183
+ // Progress reporting: media-time-based via each encoded packet.
184
+ onEncodedPacket: options.onProgress ? (packet) => {
185
+ const t = packet.timestamp;
186
+ if (Number.isFinite(t) && ctx.duration && ctx.duration > 0) {
187
+ const pct = Math.min(100, t / ctx.duration * 100);
188
+ options.onProgress({ percent: pct, bytesWritten: 0 });
189
+ }
190
+ } : void 0
191
+ });
192
+ const videoMeta = {};
193
+ if (options.width !== void 0) videoMeta.width = options.width;
194
+ else if (videoWidth > 0) videoMeta.width = videoWidth;
195
+ if (options.height !== void 0) videoMeta.height = options.height;
196
+ else if (videoHeight > 0) videoMeta.height = videoHeight;
197
+ if (options.frameRate !== void 0) videoMeta.frameRate = options.frameRate;
198
+ output.addVideoTrack(videoSource, videoMeta);
199
+ if (ctx.videoTracks[0]?.codec === "mpeg4") {
200
+ const runtime = demux.libav;
201
+ try {
202
+ videoBsfCtx = await runtime.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
203
+ if (videoBsfCtx != null && videoBsfCtx >= 0) {
204
+ const parIn = await runtime.AVBSFContext_par_in(videoBsfCtx);
205
+ await runtime.avcodec_parameters_copy(parIn, demux.videoStream.codecpar);
206
+ await runtime.av_bsf_init(videoBsfCtx);
207
+ videoBsfPkt = await demux.libav.av_packet_alloc();
208
+ } else {
209
+ videoBsfCtx = null;
210
+ }
211
+ } catch {
212
+ videoBsfCtx = null;
213
+ }
214
+ }
215
+ } catch (err) {
216
+ if (err instanceof chunk2IJ66NTD_cjs.AvbridgeError) throw err;
217
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
218
+ chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED,
219
+ `transcode: video decoder init failed: ${err.message}`,
220
+ `The source's video codec may not be supported by this browser's WebCodecs implementation.`
221
+ );
222
+ }
223
+ }
224
+ let audioDec = null;
225
+ let audioSource = null;
226
+ let audioTimeBase;
227
+ const includeAudio = demux.audioStream && !options.dropAudio;
228
+ if (includeAudio) {
229
+ try {
230
+ const libav = demux.libav;
231
+ const [, c, pkt, frame] = await libav.ff_init_decoder(
232
+ demux.audioStream.codec_id,
233
+ { codecpar: demux.audioStream.codecpar }
234
+ );
235
+ audioDec = { c, pkt, frame };
236
+ if (demux.audioStream.time_base_num && demux.audioStream.time_base_den) {
237
+ audioTimeBase = [
238
+ demux.audioStream.time_base_num,
239
+ demux.audioStream.time_base_den
240
+ ];
241
+ }
242
+ audioSource = new mb.AudioSampleSource({
243
+ codec: avbridgeAudioToMediabunny(audioCodec),
244
+ bitrate: qualityToMediabunny(mb, quality, options.audioBitrate)
245
+ });
246
+ output.addAudioTrack(audioSource);
247
+ } catch (err) {
248
+ const codecName = ctx.audioTracks[0]?.codec ?? "unknown";
249
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
250
+ chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED,
251
+ `transcode: no decoder available for audio codec "${codecName}" in this libav variant (${err.message}).`,
252
+ `The file may still play via createPlayer() (fallback strategy). Pass { dropAudio: true } to transcode video-only.`
253
+ );
254
+ }
255
+ } else if (options.dropAudio) {
256
+ }
257
+ if (!videoSource && !audioSource) {
258
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
259
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
260
+ "transcode: no video or audio track to encode (did you set both dropVideo and dropAudio?).",
261
+ "Remove dropVideo or dropAudio to include at least one track."
262
+ );
187
263
  }
188
- const videoPackets = videoStream ? packets[videoStream.index] ?? [] : [];
189
- const audioPackets = audioStream ? packets[audioStream.index] ?? [] : [];
190
- if (videoSource) {
191
- for (const pkt of videoPackets) {
264
+ await output.start();
265
+ const videoFps = ctx.videoTracks[0]?.fps && ctx.videoTracks[0].fps > 0 ? ctx.videoTracks[0].fps : 30;
266
+ const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
267
+ let syntheticVideoUs = 0;
268
+ let syntheticAudioUs = 0;
269
+ const libavFull = demux.libav;
270
+ async function applyBSF(packets) {
271
+ if (!videoBsfCtx || !videoBsfPkt) return packets;
272
+ const out = [];
273
+ for (const pkt of packets) {
274
+ await libavFull.ff_copyin_packet(videoBsfPkt, pkt);
275
+ const sendErr = await libavFull.av_bsf_send_packet(videoBsfCtx, videoBsfPkt);
276
+ if (sendErr < 0) {
277
+ out.push(pkt);
278
+ continue;
279
+ }
280
+ while (true) {
281
+ const recvErr = await libavFull.av_bsf_receive_packet(videoBsfCtx, videoBsfPkt);
282
+ if (recvErr < 0) break;
283
+ out.push(await libavFull.ff_copyout_packet(videoBsfPkt));
284
+ }
285
+ }
286
+ return out;
287
+ }
288
+ const ac = options.signal;
289
+ const onVideoPacketsWebCodecs = videoDecoder ? async (pkts) => {
290
+ throwIfAborted2();
291
+ throwIfDrainError2();
292
+ while (!ac?.aborted && (videoDecoder.decodeQueueSize > 16 || frameQueue.length >= MAX_QUEUE - 2)) {
293
+ await new Promise((r) => setTimeout(r, 10));
294
+ }
295
+ throwIfAborted2();
296
+ const processed = await applyBSF(pkts);
297
+ const bridgeAny = bridge;
298
+ for (const pkt of processed) {
192
299
  sanitizePacketTimestamp(pkt, () => {
193
300
  const ts = syntheticVideoUs;
194
301
  syntheticVideoUs += videoFrameStepUs;
195
302
  return ts;
196
303
  }, videoTimeBase);
197
- const mbPacket = libavPacketToMediAbunny(mb, pkt);
198
- await videoSource.add(
199
- mbPacket,
200
- firstVideoMeta ? { decoderConfig: buildVideoDecoderConfig(videoTrackInfo) } : void 0
304
+ try {
305
+ const chunk = bridgeAny.packetToEncodedVideoChunk(pkt, demux.videoStream);
306
+ videoDecoder.decode(chunk);
307
+ } catch (err) {
308
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
309
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
310
+ `transcode: packet \u2192 EncodedVideoChunk failed: ${err.message}`,
311
+ void 0
312
+ );
313
+ }
314
+ }
315
+ } : void 0;
316
+ const onVideoPacketsSoftware = videoSoftDec ? async (pkts) => {
317
+ throwIfAborted2();
318
+ throwIfDrainError2();
319
+ while (!ac?.aborted && frameQueue.length >= MAX_QUEUE - 2) {
320
+ await new Promise((r) => setTimeout(r, 10));
321
+ }
322
+ throwIfAborted2();
323
+ const libavSoft = demux.libav;
324
+ let frames;
325
+ try {
326
+ frames = await libavSoft.ff_decode_multi(
327
+ videoSoftDec.c,
328
+ videoSoftDec.pkt,
329
+ videoSoftDec.frame,
330
+ pkts,
331
+ { ignoreErrors: true }
332
+ );
333
+ } catch (err) {
334
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
335
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
336
+ `transcode: software video decode failed: ${err.message}`,
337
+ void 0
201
338
  );
202
- firstVideoMeta = false;
203
339
  }
340
+ for (const f of frames) {
341
+ sanitizeFrameTimestamp(f, () => {
342
+ const ts = syntheticVideoUs;
343
+ syntheticVideoUs += videoFrameStepUs;
344
+ return ts;
345
+ }, videoTimeBase);
346
+ try {
347
+ const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
348
+ if (frameQueue.length >= MAX_QUEUE) {
349
+ vf.close();
350
+ } else {
351
+ frameQueue.push(vf);
352
+ void drain();
353
+ }
354
+ } catch (err) {
355
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
356
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
357
+ `transcode: laFrameToVideoFrame failed: ${err.message}`,
358
+ void 0
359
+ );
360
+ }
361
+ }
362
+ } : void 0;
363
+ await demux.pump({
364
+ signal: ac,
365
+ onVideoPackets: onVideoPacketsWebCodecs ?? onVideoPacketsSoftware,
366
+ onAudioPackets: audioDec ? async (pkts) => {
367
+ throwIfAborted2();
368
+ await decodeAudioBatch(pkts, false);
369
+ } : void 0,
370
+ onEof: async () => {
371
+ if (videoDecoder && videoDecoder.state === "configured") {
372
+ try {
373
+ await videoDecoder.flush();
374
+ } catch {
375
+ }
376
+ }
377
+ if (videoSoftDec) {
378
+ const libavSoft = demux.libav;
379
+ try {
380
+ const tail = await libavSoft.ff_decode_multi(
381
+ videoSoftDec.c,
382
+ videoSoftDec.pkt,
383
+ videoSoftDec.frame,
384
+ [],
385
+ { fin: true, ignoreErrors: true }
386
+ );
387
+ for (const f of tail) {
388
+ sanitizeFrameTimestamp(f, () => {
389
+ const ts = syntheticVideoUs;
390
+ syntheticVideoUs += videoFrameStepUs;
391
+ return ts;
392
+ }, videoTimeBase);
393
+ try {
394
+ const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
395
+ frameQueue.push(vf);
396
+ void drain();
397
+ } catch {
398
+ }
399
+ }
400
+ } catch {
401
+ }
402
+ }
403
+ await drain();
404
+ if (audioDec) {
405
+ await decodeAudioBatch([], true);
406
+ }
407
+ }
408
+ });
409
+ throwIfAborted2();
410
+ throwIfDrainError2();
411
+ videoSource?.close();
412
+ audioSource?.close();
413
+ await output.finalize();
414
+ if (!bufferTarget.buffer) {
415
+ throw new Error("transcode: mediabunny produced no output buffer");
204
416
  }
205
- if (audioSource) {
206
- for (const pkt of audioPackets) {
207
- sanitizePacketTimestamp(pkt, () => {
417
+ const mimeType = mimeForFormat2(outputFormat);
418
+ const blob = new Blob([bufferTarget.buffer], { type: mimeType });
419
+ options.onProgress?.({ percent: 100, bytesWritten: blob.size });
420
+ return {
421
+ blob,
422
+ mimeType,
423
+ container: outputFormat,
424
+ videoCodec: videoSource ? videoCodec : void 0,
425
+ audioCodec: audioSource ? audioCodec : void 0,
426
+ duration: ctx.duration,
427
+ filename: generateFilename2(ctx.name, outputFormat)
428
+ };
429
+ async function decodeAudioBatch(pkts, flush) {
430
+ if (!audioDec || !audioSource) return;
431
+ const libav = demux.libav;
432
+ let frames;
433
+ try {
434
+ frames = await libav.ff_decode_multi(
435
+ audioDec.c,
436
+ audioDec.pkt,
437
+ audioDec.frame,
438
+ pkts,
439
+ flush ? { fin: true, ignoreErrors: true } : { ignoreErrors: true }
440
+ );
441
+ } catch (err) {
442
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
443
+ chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
444
+ `transcode: audio decode failed: ${err.message}`,
445
+ void 0
446
+ );
447
+ }
448
+ for (const f of frames) {
449
+ sanitizeFrameTimestamp(f, () => {
208
450
  const ts = syntheticAudioUs;
209
- const sampleRate = audioTrackInfo?.sampleRate ?? 44100;
210
- syntheticAudioUs += Math.round(1024 * 1e6 / sampleRate);
451
+ const samples = f.nb_samples ?? 1024;
452
+ const sampleRate = f.sample_rate ?? 44100;
453
+ syntheticAudioUs += Math.round(samples * 1e6 / sampleRate);
211
454
  return ts;
212
455
  }, audioTimeBase);
213
- const mbPacket = libavPacketToMediAbunny(mb, pkt);
214
- await audioSource.add(
215
- mbPacket,
216
- firstAudioMeta ? { decoderConfig: buildAudioDecoderConfig(audioTrackInfo) } : void 0
217
- );
218
- firstAudioMeta = false;
456
+ const pcm = libavFrameToInterleavedFloat32(f);
457
+ if (!pcm) continue;
458
+ const sample = new mb.AudioSample({
459
+ data: pcm.data,
460
+ format: "f32",
461
+ numberOfChannels: pcm.channels,
462
+ sampleRate: pcm.sampleRate,
463
+ timestamp: (f.pts ?? 0) / 1e6
464
+ });
465
+ await audioSource.add(sample);
219
466
  }
220
467
  }
221
- totalPackets += videoPackets.length + audioPackets.length;
222
- if (options.onProgress && durationUs > 0) {
223
- const lastVideoTs = videoPackets.length > 0 ? videoPackets[videoPackets.length - 1].pts ?? 0 : 0;
224
- const lastAudioTs = audioPackets.length > 0 ? audioPackets[audioPackets.length - 1].pts ?? 0 : 0;
225
- const currentUs = Math.max(lastVideoTs, lastAudioTs);
226
- const percent = Math.min(99, currentUs / durationUs * 100);
227
- options.onProgress({ percent, bytesWritten: 0 });
228
- }
229
- if (readErr === libav.AVERROR_EOF) break;
230
- if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {
231
- console.warn("[avbridge] remux: ff_read_frame_multi returned", readErr);
232
- break;
468
+ } finally {
469
+ try {
470
+ await demux.destroy();
471
+ } catch {
233
472
  }
234
473
  }
235
- await output.finalize();
236
- try {
237
- await libav.av_packet_free?.(readPkt);
238
- } catch {
239
- }
474
+ }
475
+ async function loadBridge() {
240
476
  try {
241
- await libav.avformat_close_input_js(fmt_ctx);
242
- } catch {
243
- }
244
- if (!target.buffer) {
245
- throw new Error("Remux failed: mediabunny produced no output buffer.");
477
+ const wrapper = await import('./libav-import-2ZVKV2E7.cjs');
478
+ return wrapper.libavBridge;
479
+ } catch (err) {
480
+ throw new Error(`failed to load libavjs-webcodecs-bridge: ${err.message}`);
246
481
  }
247
- const mimeType = mimeForFormat(outputFormat);
248
- const blob = new Blob([target.buffer], { type: mimeType });
249
- const outputFilename = generateFilename(ctx.name, outputFormat);
250
- options.onProgress?.({ percent: 100, bytesWritten: blob.size });
251
- return {
252
- blob,
253
- mimeType,
254
- container: outputFormat,
255
- videoCodec: videoTrackInfo?.codec,
256
- audioCodec: audioTrackInfo?.codec,
257
- duration: ctx.duration,
258
- filename: outputFilename
259
- };
260
482
  }
261
- function sanitizePacketTimestamp(pkt, nextUs, fallbackTimeBase) {
262
- const lo = pkt.pts ?? 0;
263
- const hi = pkt.ptshi ?? 0;
264
- const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
265
- if (isInvalid) {
266
- const us2 = nextUs();
267
- pkt.pts = us2;
268
- pkt.ptshi = 0;
269
- pkt.time_base_num = 1;
270
- pkt.time_base_den = 1e6;
271
- return;
272
- }
273
- const tb = fallbackTimeBase ?? [1, 1e6];
274
- const pts64 = hi * 4294967296 + lo;
275
- const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
276
- if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
277
- pkt.pts = us;
278
- pkt.ptshi = us < 0 ? -1 : 0;
279
- pkt.time_base_num = 1;
280
- pkt.time_base_den = 1e6;
281
- return;
483
+ function avbridgeVideoToMediabunny(c) {
484
+ switch (c) {
485
+ case "h264":
486
+ return "avc";
487
+ case "h265":
488
+ return "hevc";
489
+ case "vp9":
490
+ return "vp9";
491
+ case "av1":
492
+ return "av1";
282
493
  }
283
- const fallback = nextUs();
284
- pkt.pts = fallback;
285
- pkt.ptshi = 0;
286
- pkt.time_base_num = 1;
287
- pkt.time_base_den = 1e6;
288
494
  }
289
- function createOutputFormat(mb, format) {
290
- switch (format) {
291
- case "mp4":
292
- return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
293
- case "webm":
294
- return new mb.WebMOutputFormat();
295
- case "mkv":
296
- return new mb.MkvOutputFormat();
297
- default:
298
- return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
495
+ function avbridgeAudioToMediabunny(c) {
496
+ switch (c) {
497
+ case "aac":
498
+ return "aac";
499
+ case "opus":
500
+ return "opus";
501
+ case "flac":
502
+ return "flac";
299
503
  }
300
504
  }
301
- function mimeForFormat(format) {
302
- switch (format) {
303
- case "mp4":
304
- return "video/mp4";
305
- case "webm":
306
- return "video/webm";
307
- case "mkv":
308
- return "video/x-matroska";
309
- default:
310
- return "application/octet-stream";
505
+ function qualityToMediabunny(mb, quality, override) {
506
+ if (override !== void 0) return override;
507
+ switch (quality) {
508
+ case "low":
509
+ return mb.QUALITY_LOW;
510
+ case "medium":
511
+ return mb.QUALITY_MEDIUM;
512
+ case "high":
513
+ return mb.QUALITY_HIGH;
514
+ case "very-high":
515
+ return mb.QUALITY_VERY_HIGH;
311
516
  }
312
517
  }
313
- function generateFilename(originalName, format) {
314
- const ext = format === "mkv" ? "mkv" : format;
315
- if (!originalName) return `output.${ext}`;
316
- const base = originalName.replace(/\.[^.]+$/, "");
317
- return `${base}.${ext}`;
318
- }
319
- var _seqCounter = 0;
320
- function libavPacketToMediAbunny(mb, pkt) {
321
- const KEY_FRAME_FLAG = 1;
322
- const timestampSec = (pkt.pts ?? 0) / 1e6;
323
- const durationSec = (pkt.duration ?? 0) / 1e6;
324
- const type = pkt.flags & KEY_FRAME_FLAG ? "key" : "delta";
325
- return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);
326
- }
327
- function buildVideoDecoderConfig(track) {
328
- return {
329
- codec: track.codecString ?? track.codec,
330
- codedWidth: track.width,
331
- codedHeight: track.height
332
- };
333
- }
334
- function buildAudioDecoderConfig(track) {
335
- return {
336
- codec: track.codecString ?? track.codec,
337
- numberOfChannels: track.channels,
338
- sampleRate: track.sampleRate
339
- };
340
- }
341
518
 
342
519
  // src/convert/transcode.ts
343
- var MEDIABUNNY_CONTAINERS2 = /* @__PURE__ */ new Set([
520
+ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
344
521
  "mp4",
345
522
  "mov",
346
523
  "mkv",
@@ -358,11 +535,16 @@ async function transcode(source, options = {}) {
358
535
  const quality = options.quality ?? "medium";
359
536
  validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
360
537
  options.signal?.throwIfAborted();
361
- const ctx = await chunkUF2N5L63_cjs.probe(source);
538
+ const ctx = await chunkZCUXHW55_cjs.probe(source);
362
539
  options.signal?.throwIfAborted();
363
- if (!MEDIABUNNY_CONTAINERS2.has(ctx.container)) {
364
- throw new Error(
365
- `Cannot transcode "${ctx.container}" sources in v1. transcode() only supports inputs that mediabunny can read (MP4, MKV, WebM, OGG, MP3, FLAC, WAV, MOV). For AVI/ASF/FLV sources, use the player's playback strategies instead.`
540
+ if (isLibavTranscodeContainer(ctx.container)) {
541
+ return transcodeViaLibav(ctx, options);
542
+ }
543
+ if (!MEDIABUNNY_CONTAINERS.has(ctx.container)) {
544
+ throw new chunk2IJ66NTD_cjs.AvbridgeError(
545
+ chunk2IJ66NTD_cjs.ERR_CONTAINER_NOT_SUPPORTED,
546
+ `Cannot transcode "${ctx.container}" sources. transcode() supports mediabunny-readable containers (MP4, MKV, WebM, OGG, MP3, FLAC, WAV, MOV) and legacy containers via the libav path (AVI, ASF, FLV).`,
547
+ `If this is a legacy container we don't yet support, use createPlayer() to play it. Transcode support for more containers is on the roadmap.`
366
548
  );
367
549
  }
368
550
  return doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
@@ -370,17 +552,26 @@ async function transcode(source, options = {}) {
370
552
  async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
371
553
  const mb = await import('mediabunny');
372
554
  const input = new mb.Input({
373
- source: await chunkUF2N5L63_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
555
+ source: await chunkZCUXHW55_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
374
556
  formats: mb.ALL_FORMATS
375
557
  });
376
- const target = new mb.BufferTarget();
558
+ let bytesWritten = 0;
559
+ const useStream = !!options.outputStream;
560
+ const bufferTarget = useStream ? null : new mb.BufferTarget();
561
+ const streamTarget = useStream ? new mb.StreamTarget(new WritableStream({
562
+ write(chunk) {
563
+ bytesWritten += chunk.data.byteLength;
564
+ const writer = options.outputStream.getWriter();
565
+ return writer.write(chunk.data).then(() => writer.releaseLock());
566
+ }
567
+ })) : null;
377
568
  const output = new mb.Output({
378
- format: createOutputFormat(mb, outputFormat),
379
- target
569
+ format: chunkQ2VUO52Z_cjs.createOutputFormat(mb, outputFormat),
570
+ target: streamTarget ?? bufferTarget
380
571
  });
381
572
  const videoOptions = options.dropVideo ? { discard: true } : {
382
573
  codec: avbridgeVideoToMediabunny2(videoCodec),
383
- bitrate: options.videoBitrate ?? qualityToMediabunny(mb, quality),
574
+ bitrate: options.videoBitrate ?? qualityToMediabunny2(mb, quality),
384
575
  forceTranscode: true,
385
576
  ...options.width !== void 0 ? { width: options.width } : {},
386
577
  ...options.height !== void 0 ? { height: options.height } : {},
@@ -390,7 +581,7 @@ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quali
390
581
  };
391
582
  const audioOptions = options.dropAudio ? { discard: true } : {
392
583
  codec: avbridgeAudioToMediabunny2(audioCodec),
393
- bitrate: options.audioBitrate ?? qualityToMediabunny(mb, quality),
584
+ bitrate: options.audioBitrate ?? qualityToMediabunny2(mb, quality),
394
585
  forceTranscode: true
395
586
  };
396
587
  const conversion = await mb.Conversion.init({
@@ -425,10 +616,13 @@ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quali
425
616
  options.signal.removeEventListener("abort", abortHandler);
426
617
  }
427
618
  }
428
- if (!target.buffer) {
619
+ if (useStream) {
620
+ return null;
621
+ }
622
+ if (!bufferTarget.buffer) {
429
623
  throw new Error("Transcode failed: mediabunny produced no output buffer.");
430
624
  }
431
- return target.buffer;
625
+ return bufferTarget.buffer;
432
626
  }
433
627
  function isLikelyEncoderInitError(err) {
434
628
  if (!err) return false;
@@ -463,12 +657,25 @@ async function doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, o
463
657
  await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));
464
658
  }
465
659
  }
660
+ const mimeType = chunkQ2VUO52Z_cjs.mimeForFormat(outputFormat);
661
+ const filename = chunkQ2VUO52Z_cjs.generateFilename(ctx.name, outputFormat);
662
+ if (options.outputStream) {
663
+ options.onProgress?.({ percent: 100, bytesWritten: 0 });
664
+ return {
665
+ blob: new Blob([], { type: mimeType }),
666
+ mimeType,
667
+ container: outputFormat,
668
+ videoCodec: options.dropVideo ? void 0 : videoCodec,
669
+ audioCodec: options.dropAudio ? void 0 : audioCodec,
670
+ duration: ctx.duration,
671
+ filename,
672
+ ...notes.length > 0 ? { notes } : {}
673
+ };
674
+ }
466
675
  if (!buffer) {
467
676
  throw new Error("Transcode failed: no buffer produced (this should be unreachable).");
468
677
  }
469
- const mimeType = mimeForFormat(outputFormat);
470
678
  const blob = new Blob([buffer], { type: mimeType });
471
- const filename = generateFilename(ctx.name, outputFormat);
472
679
  options.onProgress?.({ percent: 100, bytesWritten: blob.size });
473
680
  return {
474
681
  blob,
@@ -537,7 +744,7 @@ function avbridgeAudioToMediabunny2(c) {
537
744
  return "flac";
538
745
  }
539
746
  }
540
- function qualityToMediabunny(mb, quality) {
747
+ function qualityToMediabunny2(mb, quality) {
541
748
  switch (quality) {
542
749
  case "low":
543
750
  return mb.QUALITY_LOW;
@@ -550,27 +757,98 @@ function qualityToMediabunny(mb, quality) {
550
757
  }
551
758
  }
552
759
 
760
+ Object.defineProperty(exports, "remux", {
761
+ enumerable: true,
762
+ get: function () { return chunkQ2VUO52Z_cjs.remux; }
763
+ });
764
+ Object.defineProperty(exports, "FALLBACK_AUDIO_CODECS", {
765
+ enumerable: true,
766
+ get: function () { return chunkTBW26OPP_cjs.FALLBACK_AUDIO_CODECS; }
767
+ });
768
+ Object.defineProperty(exports, "FALLBACK_VIDEO_CODECS", {
769
+ enumerable: true,
770
+ get: function () { return chunkTBW26OPP_cjs.FALLBACK_VIDEO_CODECS; }
771
+ });
772
+ Object.defineProperty(exports, "NATIVE_AUDIO_CODECS", {
773
+ enumerable: true,
774
+ get: function () { return chunkTBW26OPP_cjs.NATIVE_AUDIO_CODECS; }
775
+ });
776
+ Object.defineProperty(exports, "NATIVE_VIDEO_CODECS", {
777
+ enumerable: true,
778
+ get: function () { return chunkTBW26OPP_cjs.NATIVE_VIDEO_CODECS; }
779
+ });
553
780
  Object.defineProperty(exports, "UnifiedPlayer", {
554
781
  enumerable: true,
555
- get: function () { return chunkUF2N5L63_cjs.UnifiedPlayer; }
782
+ get: function () { return chunkTBW26OPP_cjs.UnifiedPlayer; }
556
783
  });
557
784
  Object.defineProperty(exports, "classify", {
558
785
  enumerable: true,
559
- get: function () { return chunkUF2N5L63_cjs.classifyContext; }
786
+ get: function () { return chunkTBW26OPP_cjs.classifyContext; }
560
787
  });
561
788
  Object.defineProperty(exports, "createPlayer", {
562
789
  enumerable: true,
563
- get: function () { return chunkUF2N5L63_cjs.createPlayer; }
790
+ get: function () { return chunkTBW26OPP_cjs.createPlayer; }
791
+ });
792
+ Object.defineProperty(exports, "srtToVtt", {
793
+ enumerable: true,
794
+ get: function () { return chunkS4WAZC2T_cjs.srtToVtt; }
564
795
  });
565
796
  Object.defineProperty(exports, "probe", {
566
797
  enumerable: true,
567
- get: function () { return chunkUF2N5L63_cjs.probe; }
798
+ get: function () { return chunkZCUXHW55_cjs.probe; }
568
799
  });
569
- Object.defineProperty(exports, "srtToVtt", {
800
+ Object.defineProperty(exports, "AvbridgeError", {
801
+ enumerable: true,
802
+ get: function () { return chunk2IJ66NTD_cjs.AvbridgeError; }
803
+ });
804
+ Object.defineProperty(exports, "ERR_ALL_STRATEGIES_EXHAUSTED", {
805
+ enumerable: true,
806
+ get: function () { return chunk2IJ66NTD_cjs.ERR_ALL_STRATEGIES_EXHAUSTED; }
807
+ });
808
+ Object.defineProperty(exports, "ERR_CODEC_NOT_SUPPORTED", {
809
+ enumerable: true,
810
+ get: function () { return chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED; }
811
+ });
812
+ Object.defineProperty(exports, "ERR_FETCH_FAILED", {
813
+ enumerable: true,
814
+ get: function () { return chunk2IJ66NTD_cjs.ERR_FETCH_FAILED; }
815
+ });
816
+ Object.defineProperty(exports, "ERR_LIBAV_NOT_REACHABLE", {
817
+ enumerable: true,
818
+ get: function () { return chunk2IJ66NTD_cjs.ERR_LIBAV_NOT_REACHABLE; }
819
+ });
820
+ Object.defineProperty(exports, "ERR_MSE_CODEC_NOT_SUPPORTED", {
821
+ enumerable: true,
822
+ get: function () { return chunk2IJ66NTD_cjs.ERR_MSE_CODEC_NOT_SUPPORTED; }
823
+ });
824
+ Object.defineProperty(exports, "ERR_MSE_NOT_SUPPORTED", {
825
+ enumerable: true,
826
+ get: function () { return chunk2IJ66NTD_cjs.ERR_MSE_NOT_SUPPORTED; }
827
+ });
828
+ Object.defineProperty(exports, "ERR_PLAYER_NOT_READY", {
829
+ enumerable: true,
830
+ get: function () { return chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY; }
831
+ });
832
+ Object.defineProperty(exports, "ERR_PROBE_FAILED", {
833
+ enumerable: true,
834
+ get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_FAILED; }
835
+ });
836
+ Object.defineProperty(exports, "ERR_PROBE_FETCH_FAILED", {
837
+ enumerable: true,
838
+ get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_FETCH_FAILED; }
839
+ });
840
+ Object.defineProperty(exports, "ERR_PROBE_UNKNOWN_CONTAINER", {
841
+ enumerable: true,
842
+ get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_UNKNOWN_CONTAINER; }
843
+ });
844
+ Object.defineProperty(exports, "ERR_RANGE_NOT_SUPPORTED", {
845
+ enumerable: true,
846
+ get: function () { return chunk2IJ66NTD_cjs.ERR_RANGE_NOT_SUPPORTED; }
847
+ });
848
+ Object.defineProperty(exports, "ERR_STRATEGY_FAILED", {
570
849
  enumerable: true,
571
- get: function () { return chunkUF2N5L63_cjs.srtToVtt; }
850
+ get: function () { return chunk2IJ66NTD_cjs.ERR_STRATEGY_FAILED; }
572
851
  });
573
- exports.remux = remux;
574
852
  exports.transcode = transcode;
575
853
  //# sourceMappingURL=index.cjs.map
576
854
  //# sourceMappingURL=index.cjs.map