avbridge 1.0.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 (103) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/LICENSE +21 -0
  3. package/README.md +415 -0
  4. package/dist/avi-M5B4SHRM.cjs +164 -0
  5. package/dist/avi-M5B4SHRM.cjs.map +1 -0
  6. package/dist/avi-POCGZ4JX.js +162 -0
  7. package/dist/avi-POCGZ4JX.js.map +1 -0
  8. package/dist/chunk-5ISVAODK.js +80 -0
  9. package/dist/chunk-5ISVAODK.js.map +1 -0
  10. package/dist/chunk-F7YS2XOA.cjs +2966 -0
  11. package/dist/chunk-F7YS2XOA.cjs.map +1 -0
  12. package/dist/chunk-FKM7QBZU.js +2957 -0
  13. package/dist/chunk-FKM7QBZU.js.map +1 -0
  14. package/dist/chunk-J5MCMN3S.js +27 -0
  15. package/dist/chunk-J5MCMN3S.js.map +1 -0
  16. package/dist/chunk-L4NPOJ36.cjs +180 -0
  17. package/dist/chunk-L4NPOJ36.cjs.map +1 -0
  18. package/dist/chunk-NZU7W256.cjs +29 -0
  19. package/dist/chunk-NZU7W256.cjs.map +1 -0
  20. package/dist/chunk-PQTZS7OA.js +147 -0
  21. package/dist/chunk-PQTZS7OA.js.map +1 -0
  22. package/dist/chunk-WD2ZNQA7.js +177 -0
  23. package/dist/chunk-WD2ZNQA7.js.map +1 -0
  24. package/dist/chunk-Y5FYF5KG.cjs +153 -0
  25. package/dist/chunk-Y5FYF5KG.cjs.map +1 -0
  26. package/dist/chunk-Z2FJ5TJC.cjs +82 -0
  27. package/dist/chunk-Z2FJ5TJC.cjs.map +1 -0
  28. package/dist/element.cjs +433 -0
  29. package/dist/element.cjs.map +1 -0
  30. package/dist/element.d.cts +158 -0
  31. package/dist/element.d.ts +158 -0
  32. package/dist/element.js +431 -0
  33. package/dist/element.js.map +1 -0
  34. package/dist/index.cjs +576 -0
  35. package/dist/index.cjs.map +1 -0
  36. package/dist/index.d.cts +80 -0
  37. package/dist/index.d.ts +80 -0
  38. package/dist/index.js +554 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/libav-http-reader-FPYDBMYK.cjs +16 -0
  41. package/dist/libav-http-reader-FPYDBMYK.cjs.map +1 -0
  42. package/dist/libav-http-reader-NQJVY273.js +3 -0
  43. package/dist/libav-http-reader-NQJVY273.js.map +1 -0
  44. package/dist/libav-import-2JURFHEW.js +8 -0
  45. package/dist/libav-import-2JURFHEW.js.map +1 -0
  46. package/dist/libav-import-GST2AMPL.cjs +30 -0
  47. package/dist/libav-import-GST2AMPL.cjs.map +1 -0
  48. package/dist/libav-loader-KA2MAWLM.js +3 -0
  49. package/dist/libav-loader-KA2MAWLM.js.map +1 -0
  50. package/dist/libav-loader-ZHOERPHW.cjs +12 -0
  51. package/dist/libav-loader-ZHOERPHW.cjs.map +1 -0
  52. package/dist/player-BBwbCkdL.d.cts +365 -0
  53. package/dist/player-BBwbCkdL.d.ts +365 -0
  54. package/dist/source-SC6ZEQYR.cjs +28 -0
  55. package/dist/source-SC6ZEQYR.cjs.map +1 -0
  56. package/dist/source-ZFS4H7J3.js +3 -0
  57. package/dist/source-ZFS4H7J3.js.map +1 -0
  58. package/dist/variant-routing-GOHB2RZN.cjs +12 -0
  59. package/dist/variant-routing-GOHB2RZN.cjs.map +1 -0
  60. package/dist/variant-routing-JOBWXYKD.js +3 -0
  61. package/dist/variant-routing-JOBWXYKD.js.map +1 -0
  62. package/package.json +95 -0
  63. package/src/classify/index.ts +1 -0
  64. package/src/classify/rules.ts +214 -0
  65. package/src/convert/index.ts +2 -0
  66. package/src/convert/remux.ts +522 -0
  67. package/src/convert/transcode.ts +329 -0
  68. package/src/diagnostics.ts +99 -0
  69. package/src/element/avbridge-player.ts +576 -0
  70. package/src/element.ts +19 -0
  71. package/src/events.ts +71 -0
  72. package/src/index.ts +42 -0
  73. package/src/libav-stubs.d.ts +24 -0
  74. package/src/player.ts +455 -0
  75. package/src/plugins/builtin.ts +37 -0
  76. package/src/plugins/registry.ts +32 -0
  77. package/src/probe/avi.ts +242 -0
  78. package/src/probe/index.ts +59 -0
  79. package/src/probe/mediabunny.ts +194 -0
  80. package/src/strategies/fallback/audio-output.ts +293 -0
  81. package/src/strategies/fallback/clock.ts +7 -0
  82. package/src/strategies/fallback/decoder.ts +660 -0
  83. package/src/strategies/fallback/index.ts +170 -0
  84. package/src/strategies/fallback/libav-import.ts +27 -0
  85. package/src/strategies/fallback/libav-loader.ts +190 -0
  86. package/src/strategies/fallback/variant-routing.ts +43 -0
  87. package/src/strategies/fallback/video-renderer.ts +216 -0
  88. package/src/strategies/hybrid/decoder.ts +641 -0
  89. package/src/strategies/hybrid/index.ts +139 -0
  90. package/src/strategies/native.ts +107 -0
  91. package/src/strategies/remux/annexb.ts +112 -0
  92. package/src/strategies/remux/index.ts +79 -0
  93. package/src/strategies/remux/mse.ts +234 -0
  94. package/src/strategies/remux/pipeline.ts +254 -0
  95. package/src/subtitles/index.ts +91 -0
  96. package/src/subtitles/render.ts +62 -0
  97. package/src/subtitles/srt.ts +62 -0
  98. package/src/subtitles/vtt.ts +5 -0
  99. package/src/types-shim.d.ts +3 -0
  100. package/src/types.ts +360 -0
  101. package/src/util/codec-strings.ts +86 -0
  102. package/src/util/libav-http-reader.ts +315 -0
  103. package/src/util/source.ts +274 -0
package/dist/index.js ADDED
@@ -0,0 +1,554 @@
1
+ import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-FKM7QBZU.js';
2
+ export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-FKM7QBZU.js';
3
+ import { normalizeSource } from './chunk-PQTZS7OA.js';
4
+ import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
5
+ import './chunk-5ISVAODK.js';
6
+ import './chunk-J5MCMN3S.js';
7
+
8
+ // src/convert/remux.ts
9
+ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
10
+ "mp4",
11
+ "mov",
12
+ "mkv",
13
+ "webm",
14
+ "ogg",
15
+ "wav",
16
+ "mp3",
17
+ "flac",
18
+ "adts"
19
+ ]);
20
+ async function remux(source, options = {}) {
21
+ const outputFormat = options.outputFormat ?? "mp4";
22
+ options.signal?.throwIfAborted();
23
+ const ctx = await probe(source);
24
+ options.signal?.throwIfAborted();
25
+ validateRemuxEligibility(ctx, options.strict ?? false);
26
+ if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
27
+ return remuxViaMediAbunny(ctx, outputFormat, options);
28
+ }
29
+ return remuxViaLibav(ctx, outputFormat, options);
30
+ }
31
+ function validateRemuxEligibility(ctx, strict) {
32
+ const video = ctx.videoTracks[0];
33
+ const audio = ctx.audioTracks[0];
34
+ if (video) {
35
+ const mbCodec = avbridgeVideoToMediabunny(video.codec);
36
+ if (!mbCodec) {
37
+ throw new Error(
38
+ `Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
39
+ );
40
+ }
41
+ }
42
+ if (audio) {
43
+ const mbCodec = avbridgeAudioToMediabunny(audio.codec);
44
+ if (!mbCodec) {
45
+ throw new Error(
46
+ `Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
47
+ );
48
+ }
49
+ }
50
+ if (strict && video?.codec === "h264" && audio?.codec === "mp3") {
51
+ throw new Error(
52
+ `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.`
53
+ );
54
+ }
55
+ if (!video && !audio) {
56
+ throw new Error("Cannot remux: source has no video or audio tracks.");
57
+ }
58
+ }
59
+ async function remuxViaMediAbunny(ctx, outputFormat, options) {
60
+ const mb = await import('mediabunny');
61
+ const input = new mb.Input({
62
+ source: await buildMediabunnySourceFromInput(mb, ctx.source),
63
+ formats: mb.ALL_FORMATS
64
+ });
65
+ const target = new mb.BufferTarget();
66
+ const output = new mb.Output({
67
+ format: createOutputFormat(mb, outputFormat),
68
+ target
69
+ });
70
+ const conversion = await mb.Conversion.init({
71
+ input,
72
+ output,
73
+ showWarnings: false
74
+ });
75
+ if (!conversion.isValid) {
76
+ const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
77
+ throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);
78
+ }
79
+ if (options.onProgress) {
80
+ const onProgress = options.onProgress;
81
+ conversion.onProgress = (p) => {
82
+ onProgress({ percent: p * 100, bytesWritten: 0 });
83
+ };
84
+ }
85
+ let abortHandler;
86
+ if (options.signal) {
87
+ options.signal.throwIfAborted();
88
+ abortHandler = () => void conversion.cancel();
89
+ options.signal.addEventListener("abort", abortHandler, { once: true });
90
+ }
91
+ try {
92
+ await conversion.execute();
93
+ } finally {
94
+ if (abortHandler && options.signal) {
95
+ options.signal.removeEventListener("abort", abortHandler);
96
+ }
97
+ }
98
+ if (!target.buffer) {
99
+ throw new Error("Remux failed: mediabunny produced no output buffer.");
100
+ }
101
+ const mimeType = mimeForFormat(outputFormat);
102
+ const blob = new Blob([target.buffer], { type: mimeType });
103
+ const filename = generateFilename(ctx.name, outputFormat);
104
+ options.onProgress?.({ percent: 100, bytesWritten: blob.size });
105
+ return {
106
+ blob,
107
+ mimeType,
108
+ container: outputFormat,
109
+ videoCodec: ctx.videoTracks[0]?.codec,
110
+ audioCodec: ctx.audioTracks[0]?.codec,
111
+ duration: ctx.duration,
112
+ filename
113
+ };
114
+ }
115
+ async function remuxViaLibav(ctx, outputFormat, options) {
116
+ let loadLibav;
117
+ let pickLibavVariant;
118
+ try {
119
+ const loader = await import('./libav-loader-KA2MAWLM.js');
120
+ const routing = await import('./variant-routing-JOBWXYKD.js');
121
+ loadLibav = loader.loadLibav;
122
+ pickLibavVariant = routing.pickLibavVariant;
123
+ } catch {
124
+ throw new Error(
125
+ `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.`
126
+ );
127
+ }
128
+ const variant = pickLibavVariant(ctx);
129
+ const libav = await loadLibav(variant);
130
+ const normalized = await normalizeSource(ctx.source);
131
+ const filename = ctx.name ?? `remux-input-${Date.now()}`;
132
+ const handle = await prepareLibavInput(libav, filename, normalized);
133
+ try {
134
+ return await doLibavRemux(libav, filename, ctx, outputFormat, options);
135
+ } finally {
136
+ await handle.detach().catch(() => {
137
+ });
138
+ }
139
+ }
140
+ async function doLibavRemux(libav, filename, ctx, outputFormat, options) {
141
+ const mb = await import('mediabunny');
142
+ const readPkt = await libav.av_packet_alloc();
143
+ const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename);
144
+ const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
145
+ const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
146
+ const videoTrackInfo = ctx.videoTracks[0];
147
+ const audioTrackInfo = ctx.audioTracks[0];
148
+ const mbVideoCodec = videoTrackInfo ? avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
149
+ const mbAudioCodec = audioTrackInfo ? avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
150
+ const target = new mb.BufferTarget();
151
+ const output = new mb.Output({
152
+ format: createOutputFormat(mb, outputFormat),
153
+ target
154
+ });
155
+ let videoSource = null;
156
+ let audioSource = null;
157
+ if (mbVideoCodec && videoStream) {
158
+ videoSource = new mb.EncodedVideoPacketSource(mbVideoCodec);
159
+ output.addVideoTrack(videoSource);
160
+ }
161
+ if (mbAudioCodec && audioStream) {
162
+ audioSource = new mb.EncodedAudioPacketSource(mbAudioCodec);
163
+ output.addAudioTrack(audioSource);
164
+ }
165
+ await output.start();
166
+ const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
167
+ const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
168
+ let syntheticVideoUs = 0;
169
+ let syntheticAudioUs = 0;
170
+ const videoTimeBase = videoStream?.time_base_num && videoStream?.time_base_den ? [videoStream.time_base_num, videoStream.time_base_den] : void 0;
171
+ const audioTimeBase = audioStream?.time_base_num && audioStream?.time_base_den ? [audioStream.time_base_num, audioStream.time_base_den] : void 0;
172
+ let totalPackets = 0;
173
+ const durationUs = ctx.duration ? ctx.duration * 1e6 : 0;
174
+ let firstVideoMeta = true;
175
+ let firstAudioMeta = true;
176
+ while (true) {
177
+ options.signal?.throwIfAborted();
178
+ let readErr;
179
+ let packets;
180
+ try {
181
+ [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
182
+ limit: 64 * 1024
183
+ });
184
+ } catch (err) {
185
+ throw new Error(`libav demux failed: ${err.message}`);
186
+ }
187
+ const videoPackets = videoStream ? packets[videoStream.index] ?? [] : [];
188
+ const audioPackets = audioStream ? packets[audioStream.index] ?? [] : [];
189
+ if (videoSource) {
190
+ for (const pkt of videoPackets) {
191
+ sanitizePacketTimestamp(pkt, () => {
192
+ const ts = syntheticVideoUs;
193
+ syntheticVideoUs += videoFrameStepUs;
194
+ return ts;
195
+ }, videoTimeBase);
196
+ const mbPacket = libavPacketToMediAbunny(mb, pkt);
197
+ await videoSource.add(
198
+ mbPacket,
199
+ firstVideoMeta ? { decoderConfig: buildVideoDecoderConfig(videoTrackInfo) } : void 0
200
+ );
201
+ firstVideoMeta = false;
202
+ }
203
+ }
204
+ if (audioSource) {
205
+ for (const pkt of audioPackets) {
206
+ sanitizePacketTimestamp(pkt, () => {
207
+ const ts = syntheticAudioUs;
208
+ const sampleRate = audioTrackInfo?.sampleRate ?? 44100;
209
+ syntheticAudioUs += Math.round(1024 * 1e6 / sampleRate);
210
+ return ts;
211
+ }, audioTimeBase);
212
+ const mbPacket = libavPacketToMediAbunny(mb, pkt);
213
+ await audioSource.add(
214
+ mbPacket,
215
+ firstAudioMeta ? { decoderConfig: buildAudioDecoderConfig(audioTrackInfo) } : void 0
216
+ );
217
+ firstAudioMeta = false;
218
+ }
219
+ }
220
+ totalPackets += videoPackets.length + audioPackets.length;
221
+ if (options.onProgress && durationUs > 0) {
222
+ const lastVideoTs = videoPackets.length > 0 ? videoPackets[videoPackets.length - 1].pts ?? 0 : 0;
223
+ const lastAudioTs = audioPackets.length > 0 ? audioPackets[audioPackets.length - 1].pts ?? 0 : 0;
224
+ const currentUs = Math.max(lastVideoTs, lastAudioTs);
225
+ const percent = Math.min(99, currentUs / durationUs * 100);
226
+ options.onProgress({ percent, bytesWritten: 0 });
227
+ }
228
+ if (readErr === libav.AVERROR_EOF) break;
229
+ if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {
230
+ console.warn("[avbridge] remux: ff_read_frame_multi returned", readErr);
231
+ break;
232
+ }
233
+ }
234
+ await output.finalize();
235
+ try {
236
+ await libav.av_packet_free?.(readPkt);
237
+ } catch {
238
+ }
239
+ try {
240
+ await libav.avformat_close_input_js(fmt_ctx);
241
+ } catch {
242
+ }
243
+ if (!target.buffer) {
244
+ throw new Error("Remux failed: mediabunny produced no output buffer.");
245
+ }
246
+ const mimeType = mimeForFormat(outputFormat);
247
+ const blob = new Blob([target.buffer], { type: mimeType });
248
+ const outputFilename = generateFilename(ctx.name, outputFormat);
249
+ options.onProgress?.({ percent: 100, bytesWritten: blob.size });
250
+ return {
251
+ blob,
252
+ mimeType,
253
+ container: outputFormat,
254
+ videoCodec: videoTrackInfo?.codec,
255
+ audioCodec: audioTrackInfo?.codec,
256
+ duration: ctx.duration,
257
+ filename: outputFilename
258
+ };
259
+ }
260
+ function sanitizePacketTimestamp(pkt, nextUs, fallbackTimeBase) {
261
+ const lo = pkt.pts ?? 0;
262
+ const hi = pkt.ptshi ?? 0;
263
+ const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
264
+ if (isInvalid) {
265
+ const us2 = nextUs();
266
+ pkt.pts = us2;
267
+ pkt.ptshi = 0;
268
+ pkt.time_base_num = 1;
269
+ pkt.time_base_den = 1e6;
270
+ return;
271
+ }
272
+ const tb = fallbackTimeBase ?? [1, 1e6];
273
+ const pts64 = hi * 4294967296 + lo;
274
+ const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
275
+ if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
276
+ pkt.pts = us;
277
+ pkt.ptshi = us < 0 ? -1 : 0;
278
+ pkt.time_base_num = 1;
279
+ pkt.time_base_den = 1e6;
280
+ return;
281
+ }
282
+ const fallback = nextUs();
283
+ pkt.pts = fallback;
284
+ pkt.ptshi = 0;
285
+ pkt.time_base_num = 1;
286
+ pkt.time_base_den = 1e6;
287
+ }
288
+ function createOutputFormat(mb, format) {
289
+ switch (format) {
290
+ case "mp4":
291
+ return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
292
+ case "webm":
293
+ return new mb.WebMOutputFormat();
294
+ case "mkv":
295
+ return new mb.MkvOutputFormat();
296
+ default:
297
+ return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
298
+ }
299
+ }
300
+ function mimeForFormat(format) {
301
+ switch (format) {
302
+ case "mp4":
303
+ return "video/mp4";
304
+ case "webm":
305
+ return "video/webm";
306
+ case "mkv":
307
+ return "video/x-matroska";
308
+ default:
309
+ return "application/octet-stream";
310
+ }
311
+ }
312
+ function generateFilename(originalName, format) {
313
+ const ext = format === "mkv" ? "mkv" : format;
314
+ if (!originalName) return `output.${ext}`;
315
+ const base = originalName.replace(/\.[^.]+$/, "");
316
+ return `${base}.${ext}`;
317
+ }
318
+ var _seqCounter = 0;
319
+ function libavPacketToMediAbunny(mb, pkt) {
320
+ const KEY_FRAME_FLAG = 1;
321
+ const timestampSec = (pkt.pts ?? 0) / 1e6;
322
+ const durationSec = (pkt.duration ?? 0) / 1e6;
323
+ const type = pkt.flags & KEY_FRAME_FLAG ? "key" : "delta";
324
+ return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);
325
+ }
326
+ function buildVideoDecoderConfig(track) {
327
+ return {
328
+ codec: track.codecString ?? track.codec,
329
+ codedWidth: track.width,
330
+ codedHeight: track.height
331
+ };
332
+ }
333
+ function buildAudioDecoderConfig(track) {
334
+ return {
335
+ codec: track.codecString ?? track.codec,
336
+ numberOfChannels: track.channels,
337
+ sampleRate: track.sampleRate
338
+ };
339
+ }
340
+
341
+ // src/convert/transcode.ts
342
+ var MEDIABUNNY_CONTAINERS2 = /* @__PURE__ */ new Set([
343
+ "mp4",
344
+ "mov",
345
+ "mkv",
346
+ "webm",
347
+ "ogg",
348
+ "wav",
349
+ "mp3",
350
+ "flac",
351
+ "adts"
352
+ ]);
353
+ async function transcode(source, options = {}) {
354
+ const outputFormat = options.outputFormat ?? "mp4";
355
+ const videoCodec = options.videoCodec ?? defaultVideoCodec(outputFormat);
356
+ const audioCodec = options.audioCodec ?? defaultAudioCodec(outputFormat);
357
+ const quality = options.quality ?? "medium";
358
+ validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
359
+ options.signal?.throwIfAborted();
360
+ const ctx = await probe(source);
361
+ options.signal?.throwIfAborted();
362
+ if (!MEDIABUNNY_CONTAINERS2.has(ctx.container)) {
363
+ throw new Error(
364
+ `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.`
365
+ );
366
+ }
367
+ return doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
368
+ }
369
+ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
370
+ const mb = await import('mediabunny');
371
+ const input = new mb.Input({
372
+ source: await buildMediabunnySourceFromInput(mb, ctx.source),
373
+ formats: mb.ALL_FORMATS
374
+ });
375
+ const target = new mb.BufferTarget();
376
+ const output = new mb.Output({
377
+ format: createOutputFormat(mb, outputFormat),
378
+ target
379
+ });
380
+ const videoOptions = options.dropVideo ? { discard: true } : {
381
+ codec: avbridgeVideoToMediabunny2(videoCodec),
382
+ bitrate: options.videoBitrate ?? qualityToMediabunny(mb, quality),
383
+ forceTranscode: true,
384
+ ...options.width !== void 0 ? { width: options.width } : {},
385
+ ...options.height !== void 0 ? { height: options.height } : {},
386
+ ...options.width !== void 0 && options.height !== void 0 ? { fit: "contain" } : {},
387
+ ...options.frameRate !== void 0 ? { frameRate: options.frameRate } : {},
388
+ ...options.hardwareAcceleration !== void 0 ? { hardwareAcceleration: options.hardwareAcceleration } : {}
389
+ };
390
+ const audioOptions = options.dropAudio ? { discard: true } : {
391
+ codec: avbridgeAudioToMediabunny2(audioCodec),
392
+ bitrate: options.audioBitrate ?? qualityToMediabunny(mb, quality),
393
+ forceTranscode: true
394
+ };
395
+ const conversion = await mb.Conversion.init({
396
+ input,
397
+ output,
398
+ video: videoOptions,
399
+ audio: audioOptions,
400
+ showWarnings: false
401
+ });
402
+ if (!conversion.isValid) {
403
+ const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
404
+ throw new Error(
405
+ `Cannot transcode: mediabunny rejected the conversion. ${reasons || "(no reason given)"}`
406
+ );
407
+ }
408
+ if (options.onProgress) {
409
+ const onProgress = options.onProgress;
410
+ conversion.onProgress = (p) => {
411
+ onProgress({ percent: p * 100, bytesWritten: 0 });
412
+ };
413
+ }
414
+ let abortHandler;
415
+ if (options.signal) {
416
+ options.signal.throwIfAborted();
417
+ abortHandler = () => void conversion.cancel();
418
+ options.signal.addEventListener("abort", abortHandler, { once: true });
419
+ }
420
+ try {
421
+ await conversion.execute();
422
+ } finally {
423
+ if (abortHandler && options.signal) {
424
+ options.signal.removeEventListener("abort", abortHandler);
425
+ }
426
+ }
427
+ if (!target.buffer) {
428
+ throw new Error("Transcode failed: mediabunny produced no output buffer.");
429
+ }
430
+ return target.buffer;
431
+ }
432
+ function isLikelyEncoderInitError(err) {
433
+ if (!err) return false;
434
+ const msg = err instanceof Error ? err.message : String(err);
435
+ const lower = msg.toLowerCase();
436
+ return lower.includes("encoding error") || lower.includes("encoder") || lower.includes("encode failed");
437
+ }
438
+ function describeError(err) {
439
+ if (!err) return "(unknown)";
440
+ if (err instanceof Error) return err.message;
441
+ return String(err);
442
+ }
443
+ var MAX_ENCODER_RETRIES = 2;
444
+ async function doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
445
+ const notes = [];
446
+ let buffer = null;
447
+ let lastError;
448
+ for (let attempt = 0; attempt <= MAX_ENCODER_RETRIES; attempt++) {
449
+ try {
450
+ buffer = await attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
451
+ if (attempt > 0) {
452
+ notes.push(
453
+ `Encoder failed ${attempt} time${attempt === 1 ? "" : "s"} before succeeding (known headless Chromium WebCodecs encoder init issue): ${describeError(lastError)}`
454
+ );
455
+ }
456
+ break;
457
+ } catch (err) {
458
+ lastError = err;
459
+ if (options.signal?.aborted) throw err;
460
+ if (!isLikelyEncoderInitError(err)) throw err;
461
+ if (attempt === MAX_ENCODER_RETRIES) throw err;
462
+ await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));
463
+ }
464
+ }
465
+ if (!buffer) {
466
+ throw new Error("Transcode failed: no buffer produced (this should be unreachable).");
467
+ }
468
+ const mimeType = mimeForFormat(outputFormat);
469
+ const blob = new Blob([buffer], { type: mimeType });
470
+ const filename = generateFilename(ctx.name, outputFormat);
471
+ options.onProgress?.({ percent: 100, bytesWritten: blob.size });
472
+ return {
473
+ blob,
474
+ mimeType,
475
+ container: outputFormat,
476
+ videoCodec: options.dropVideo ? void 0 : videoCodec,
477
+ audioCodec: options.dropAudio ? void 0 : audioCodec,
478
+ duration: ctx.duration,
479
+ filename,
480
+ ...notes.length > 0 ? { notes } : {}
481
+ };
482
+ }
483
+ function defaultVideoCodec(format) {
484
+ switch (format) {
485
+ case "webm":
486
+ return "vp9";
487
+ case "mp4":
488
+ case "mkv":
489
+ default:
490
+ return "h264";
491
+ }
492
+ }
493
+ function defaultAudioCodec(format) {
494
+ switch (format) {
495
+ case "webm":
496
+ return "opus";
497
+ case "mp4":
498
+ case "mkv":
499
+ default:
500
+ return "aac";
501
+ }
502
+ }
503
+ function validateCodecCompatibility(format, videoCodec, audioCodec) {
504
+ if (format === "webm") {
505
+ if (videoCodec !== "vp9" && videoCodec !== "av1") {
506
+ throw new Error(
507
+ `WebM does not support video codec "${videoCodec}". Use "vp9" or "av1", or change outputFormat to "mp4" or "mkv".`
508
+ );
509
+ }
510
+ if (audioCodec !== "opus") {
511
+ throw new Error(
512
+ `WebM does not support audio codec "${audioCodec}". Use "opus", or change outputFormat to "mp4" or "mkv".`
513
+ );
514
+ }
515
+ }
516
+ }
517
+ function avbridgeVideoToMediabunny2(c) {
518
+ switch (c) {
519
+ case "h264":
520
+ return "avc";
521
+ case "h265":
522
+ return "hevc";
523
+ case "vp9":
524
+ return "vp9";
525
+ case "av1":
526
+ return "av1";
527
+ }
528
+ }
529
+ function avbridgeAudioToMediabunny2(c) {
530
+ switch (c) {
531
+ case "aac":
532
+ return "aac";
533
+ case "opus":
534
+ return "opus";
535
+ case "flac":
536
+ return "flac";
537
+ }
538
+ }
539
+ function qualityToMediabunny(mb, quality) {
540
+ switch (quality) {
541
+ case "low":
542
+ return mb.QUALITY_LOW;
543
+ case "medium":
544
+ return mb.QUALITY_MEDIUM;
545
+ case "high":
546
+ return mb.QUALITY_HIGH;
547
+ case "very-high":
548
+ return mb.QUALITY_VERY_HIGH;
549
+ }
550
+ }
551
+
552
+ export { remux, transcode };
553
+ //# sourceMappingURL=index.js.map
554
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/convert/remux.ts","../src/convert/transcode.ts"],"names":["us","MEDIABUNNY_CONTAINERS","avbridgeVideoToMediabunny","avbridgeAudioToMediabunny"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Standalone remux function: repackage media into a modern container without\n * re-encoding. Input can be any format avbridge can probe; output is a\n * finalized downloadable file (MP4, WebM, or MKV).\n *\n * Two internal paths:\n * - **Path A** (mediabunny-readable containers): wraps mediabunny's Conversion\n * class for MP4/MKV/WebM/OGG/MOV/WAV/MP3/FLAC/ADTS sources.\n * - **Path B** (AVI/ASF/FLV): libav.js demux + mediabunny mux via manual\n * packet pump. Lazy-loads libav.js — zero cost if unused.\n */\n\nimport { probe } from \"../probe/index.js\";\nimport {\n avbridgeVideoToMediabunny,\n avbridgeAudioToMediabunny,\n buildMediabunnySourceFromInput,\n} from \"../probe/mediabunny.js\";\nimport { normalizeSource } from \"../util/source.js\";\nimport { prepareLibavInput, type LibavInputHandle } from \"../util/libav-http-reader.js\";\nimport type {\n MediaInput,\n MediaContext,\n ConvertOptions,\n ConvertResult,\n OutputFormat,\n} from \"../types.js\";\n\n/** Containers mediabunny can read (and therefore use Conversion for). */\nconst MEDIABUNNY_CONTAINERS = new Set([\n \"mp4\", \"mov\", \"mkv\", \"webm\", \"ogg\", \"wav\", \"mp3\", \"flac\", \"adts\",\n]);\n\n/**\n * Remux a media source into a modern container format without re-encoding.\n *\n * @throws When the source codecs cannot be remuxed (e.g. WMV3 — use `transcode()` instead).\n * @throws When an AVI/ASF/FLV source is provided but libav.js is not installed.\n */\nexport async function remux(\n source: MediaInput,\n options: ConvertOptions = {},\n): Promise<ConvertResult> {\n const outputFormat = options.outputFormat ?? \"mp4\";\n options.signal?.throwIfAborted();\n\n // Probe the source\n const ctx = await probe(source);\n options.signal?.throwIfAborted();\n\n // Validate remux eligibility: all codecs must map to mediabunny output codecs\n validateRemuxEligibility(ctx, options.strict ?? false);\n\n // Route to the appropriate path\n if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {\n return remuxViaMediAbunny(ctx, outputFormat, options);\n }\n return remuxViaLibav(ctx, outputFormat, options);\n}\n\n// ── Eligibility validation ──────────────────────────────────────────────────\n\n/** @internal Exported for testing. */\nexport function validateRemuxEligibility(ctx: MediaContext, strict: boolean): void {\n const video = ctx.videoTracks[0];\n const audio = ctx.audioTracks[0];\n\n if (video) {\n const mbCodec = avbridgeVideoToMediabunny(video.codec);\n if (!mbCodec) {\n throw new Error(\n `Cannot remux: video codec \"${video.codec}\" is not supported for remuxing. ` +\n `Use transcode() to re-encode to a modern codec.`,\n );\n }\n }\n\n if (audio) {\n const mbCodec = avbridgeAudioToMediabunny(audio.codec);\n if (!mbCodec) {\n throw new Error(\n `Cannot remux: audio codec \"${audio.codec}\" is not supported for remuxing. ` +\n `Use transcode() to re-encode to a modern codec.`,\n );\n }\n }\n\n if (strict && video?.codec === \"h264\" && audio?.codec === \"mp3\") {\n throw new Error(\n `Cannot remux in strict mode: H.264 + MP3 is a best-effort combination ` +\n `that may produce playback issues in some browsers. ` +\n `Set strict: false to allow, or use transcode() to re-encode audio to AAC.`,\n );\n }\n\n if (!video && !audio) {\n throw new Error(\"Cannot remux: source has no video or audio tracks.\");\n }\n}\n\n// ── Path A: mediabunny Conversion ───────────────────────────────────────────\n\nasync function remuxViaMediAbunny(\n ctx: MediaContext,\n outputFormat: OutputFormat,\n options: ConvertOptions,\n): Promise<ConvertResult> {\n const mb = await import(\"mediabunny\");\n\n const input = new mb.Input({\n source: await buildMediabunnySourceFromInput(mb, ctx.source),\n formats: mb.ALL_FORMATS,\n });\n\n const target = new mb.BufferTarget();\n const output = new mb.Output({\n format: createOutputFormat(mb, outputFormat),\n target,\n });\n\n const conversion = await mb.Conversion.init({\n input,\n output,\n showWarnings: false,\n });\n\n if (!conversion.isValid) {\n const reasons = conversion.discardedTracks\n .map((d) => `${d.track.type} track discarded: ${d.reason}`)\n .join(\"; \");\n throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);\n }\n\n // Wire progress\n if (options.onProgress) {\n const onProgress = options.onProgress;\n conversion.onProgress = (p) => {\n onProgress({ percent: p * 100, bytesWritten: 0 });\n };\n }\n\n // Wire cancellation\n let abortHandler: (() => void) | undefined;\n if (options.signal) {\n options.signal.throwIfAborted();\n abortHandler = () => void conversion.cancel();\n options.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n await conversion.execute();\n } finally {\n if (abortHandler && options.signal) {\n options.signal.removeEventListener(\"abort\", abortHandler);\n }\n }\n\n if (!target.buffer) {\n throw new Error(\"Remux failed: mediabunny produced no output buffer.\");\n }\n\n const mimeType = mimeForFormat(outputFormat);\n const blob = new Blob([target.buffer], { type: mimeType });\n const filename = generateFilename(ctx.name, outputFormat);\n\n options.onProgress?.({ percent: 100, bytesWritten: blob.size });\n\n return {\n blob,\n mimeType,\n container: outputFormat,\n videoCodec: ctx.videoTracks[0]?.codec,\n audioCodec: ctx.audioTracks[0]?.codec,\n duration: ctx.duration,\n filename,\n };\n}\n\n// ── Path B: libav.js demux + mediabunny mux (AVI/ASF/FLV) ──────────────────\n\nasync function remuxViaLibav(\n ctx: MediaContext,\n outputFormat: OutputFormat,\n options: ConvertOptions,\n): Promise<ConvertResult> {\n // Lazy-load libav\n let loadLibav: typeof import(\"../strategies/fallback/libav-loader.js\").loadLibav;\n let pickLibavVariant: typeof import(\"../strategies/fallback/variant-routing.js\").pickLibavVariant;\n try {\n const loader = await import(\"../strategies/fallback/libav-loader.js\");\n const routing = await import(\"../strategies/fallback/variant-routing.js\");\n loadLibav = loader.loadLibav;\n pickLibavVariant = routing.pickLibavVariant;\n } catch {\n throw new Error(\n `Cannot remux ${ctx.container.toUpperCase()} source: libav.js is not available. ` +\n `Install @libav.js/variant-webcodecs and libavjs-webcodecs-bridge, ` +\n `or build the custom avbridge variant with scripts/build-libav.sh.`,\n );\n }\n\n const variant = pickLibavVariant(ctx);\n const libav = await loadLibav(variant) as unknown as LibavRuntime;\n\n // For Blob/File inputs, libav reads from an in-memory readahead file.\n // For URL inputs, libav demuxes via HTTP Range requests through the\n // block reader — no full download.\n const normalized = await normalizeSource(ctx.source);\n const filename = ctx.name ?? `remux-input-${Date.now()}`;\n const handle: LibavInputHandle = await prepareLibavInput(libav as unknown as Parameters<typeof prepareLibavInput>[0], filename, normalized);\n\n try {\n return await doLibavRemux(libav, filename, ctx, outputFormat, options);\n } finally {\n await handle.detach().catch(() => {});\n }\n}\n\nasync function doLibavRemux(\n libav: LibavRuntime,\n filename: string,\n ctx: MediaContext,\n outputFormat: OutputFormat,\n options: ConvertOptions,\n): Promise<ConvertResult> {\n const mb = await import(\"mediabunny\");\n\n const readPkt = await libav.av_packet_alloc();\n const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename);\n const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;\n const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;\n\n // Map codecs to mediabunny output\n const videoTrackInfo = ctx.videoTracks[0];\n const audioTrackInfo = ctx.audioTracks[0];\n const mbVideoCodec = videoTrackInfo ? avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;\n const mbAudioCodec = audioTrackInfo ? avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;\n\n // Set up mediabunny output with BufferTarget\n const target = new mb.BufferTarget();\n const output = new mb.Output({\n format: createOutputFormat(mb, outputFormat),\n target,\n });\n\n let videoSource: InstanceType<typeof mb.EncodedVideoPacketSource> | null = null;\n let audioSource: InstanceType<typeof mb.EncodedAudioPacketSource> | null = null;\n\n if (mbVideoCodec && videoStream) {\n videoSource = new mb.EncodedVideoPacketSource(mbVideoCodec);\n output.addVideoTrack(videoSource);\n }\n if (mbAudioCodec && audioStream) {\n type AudioCodecArg = ConstructorParameters<typeof mb.EncodedAudioPacketSource>[0];\n audioSource = new mb.EncodedAudioPacketSource(mbAudioCodec as AudioCodecArg);\n output.addAudioTrack(audioSource);\n }\n\n await output.start();\n\n // Timestamp tracking for synthetic timestamps\n const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;\n const videoFrameStepUs = Math.max(1, Math.round(1_000_000 / videoFps));\n let syntheticVideoUs = 0;\n let syntheticAudioUs = 0;\n\n const videoTimeBase: [number, number] | undefined =\n videoStream?.time_base_num && videoStream?.time_base_den\n ? [videoStream.time_base_num, videoStream.time_base_den]\n : undefined;\n const audioTimeBase: [number, number] | undefined =\n audioStream?.time_base_num && audioStream?.time_base_den\n ? [audioStream.time_base_num, audioStream.time_base_den]\n : undefined;\n\n let totalPackets = 0;\n const durationUs = ctx.duration ? ctx.duration * 1_000_000 : 0;\n let firstVideoMeta = true;\n let firstAudioMeta = true;\n\n // Pump loop: read packets from libav, feed to mediabunny output\n while (true) {\n options.signal?.throwIfAborted();\n\n let readErr: number;\n let packets: Record<number, LibavPacket[]>;\n try {\n [readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {\n limit: 64 * 1024,\n });\n } catch (err) {\n throw new Error(`libav demux failed: ${(err as Error).message}`);\n }\n\n const videoPackets = videoStream ? packets[videoStream.index] ?? [] : [];\n const audioPackets = audioStream ? packets[audioStream.index] ?? [] : [];\n\n // Feed video packets\n if (videoSource) {\n for (const pkt of videoPackets) {\n sanitizePacketTimestamp(pkt, () => {\n const ts = syntheticVideoUs;\n syntheticVideoUs += videoFrameStepUs;\n return ts;\n }, videoTimeBase);\n\n const mbPacket = libavPacketToMediAbunny(mb, pkt);\n await videoSource.add(\n mbPacket,\n firstVideoMeta ? { decoderConfig: buildVideoDecoderConfig(videoTrackInfo!) } : undefined,\n );\n firstVideoMeta = false;\n }\n }\n\n // Feed audio packets\n if (audioSource) {\n for (const pkt of audioPackets) {\n sanitizePacketTimestamp(pkt, () => {\n const ts = syntheticAudioUs;\n const sampleRate = audioTrackInfo?.sampleRate ?? 44100;\n syntheticAudioUs += Math.round(1024 * 1_000_000 / sampleRate);\n return ts;\n }, audioTimeBase);\n\n const mbPacket = libavPacketToMediAbunny(mb, pkt);\n await audioSource.add(\n mbPacket,\n firstAudioMeta ? { decoderConfig: buildAudioDecoderConfig(audioTrackInfo!) } : undefined,\n );\n firstAudioMeta = false;\n }\n }\n\n totalPackets += videoPackets.length + audioPackets.length;\n\n // Report progress\n if (options.onProgress && durationUs > 0) {\n const lastVideoTs = videoPackets.length > 0 ? videoPackets[videoPackets.length - 1].pts ?? 0 : 0;\n const lastAudioTs = audioPackets.length > 0 ? audioPackets[audioPackets.length - 1].pts ?? 0 : 0;\n const currentUs = Math.max(lastVideoTs, lastAudioTs);\n const percent = Math.min(99, (currentUs / durationUs) * 100);\n options.onProgress({ percent, bytesWritten: 0 });\n }\n\n if (readErr === libav.AVERROR_EOF) break;\n if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {\n console.warn(\"[avbridge] remux: ff_read_frame_multi returned\", readErr);\n break;\n }\n }\n\n await output.finalize();\n\n // Cleanup libav resources\n try { await libav.av_packet_free?.(readPkt); } catch { /* ignore */ }\n try { await libav.avformat_close_input_js(fmt_ctx); } catch { /* ignore */ }\n\n if (!target.buffer) {\n throw new Error(\"Remux failed: mediabunny produced no output buffer.\");\n }\n\n const mimeType = mimeForFormat(outputFormat);\n const blob = new Blob([target.buffer], { type: mimeType });\n const outputFilename = generateFilename(ctx.name, outputFormat);\n\n options.onProgress?.({ percent: 100, bytesWritten: blob.size });\n\n return {\n blob,\n mimeType,\n container: outputFormat,\n videoCodec: videoTrackInfo?.codec,\n audioCodec: audioTrackInfo?.codec,\n duration: ctx.duration,\n filename: outputFilename,\n };\n}\n\n// ── Packet timestamp sanitizer (from hybrid/decoder.ts) ─────────────────────\n\nfunction sanitizePacketTimestamp(\n pkt: LibavPacket,\n nextUs: () => number,\n fallbackTimeBase?: [number, number],\n): void {\n const lo = pkt.pts ?? 0;\n const hi = pkt.ptshi ?? 0;\n const isInvalid = (hi === -2147483648 && lo === 0) || !Number.isFinite(lo);\n if (isInvalid) {\n const us = nextUs();\n pkt.pts = us;\n pkt.ptshi = 0;\n pkt.time_base_num = 1;\n pkt.time_base_den = 1_000_000;\n return;\n }\n const tb = fallbackTimeBase ?? [1, 1_000_000];\n const pts64 = hi * 0x100000000 + lo;\n const us = Math.round((pts64 * 1_000_000 * tb[0]) / tb[1]);\n if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {\n pkt.pts = us;\n pkt.ptshi = us < 0 ? -1 : 0;\n pkt.time_base_num = 1;\n pkt.time_base_den = 1_000_000;\n return;\n }\n const fallback = nextUs();\n pkt.pts = fallback;\n pkt.ptshi = 0;\n pkt.time_base_num = 1;\n pkt.time_base_den = 1_000_000;\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\n/** @internal Exported for use by transcode(). */\nexport function createOutputFormat(\n mb: typeof import(\"mediabunny\"),\n format: OutputFormat,\n) {\n switch (format) {\n case \"mp4\": return new mb.Mp4OutputFormat({ fastStart: \"in-memory\" });\n case \"webm\": return new mb.WebMOutputFormat();\n case \"mkv\": return new mb.MkvOutputFormat();\n default: return new mb.Mp4OutputFormat({ fastStart: \"in-memory\" });\n }\n}\n\n/** @internal Exported for testing. */\nexport function mimeForFormat(format: OutputFormat): string {\n switch (format) {\n case \"mp4\": return \"video/mp4\";\n case \"webm\": return \"video/webm\";\n case \"mkv\": return \"video/x-matroska\";\n default: return \"application/octet-stream\";\n }\n}\n\n/** @internal Exported for testing. */\nexport function generateFilename(originalName: string | undefined, format: OutputFormat): string {\n const ext = format === \"mkv\" ? \"mkv\" : format;\n if (!originalName) return `output.${ext}`;\n const base = originalName.replace(/\\.[^.]+$/, \"\");\n return `${base}.${ext}`;\n}\n\n/** Sequence counter for decode-order numbering in mediabunny packets. */\nlet _seqCounter = 0;\n\n/**\n * Convert a libav packet to a mediabunny EncodedPacket.\n * Timestamps from libav are in microseconds (after sanitization); mediabunny wants seconds.\n */\nfunction libavPacketToMediAbunny(\n mb: typeof import(\"mediabunny\"),\n pkt: LibavPacket,\n): InstanceType<typeof mb.EncodedPacket> {\n const KEY_FRAME_FLAG = 0x0001;\n const timestampSec = (pkt.pts ?? 0) / 1_000_000;\n const durationSec = (pkt.duration ?? 0) / 1_000_000;\n const type = (pkt.flags & KEY_FRAME_FLAG) ? \"key\" as const : \"delta\" as const;\n return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);\n}\n\nfunction buildVideoDecoderConfig(track: { codec: string; width: number; height: number; codecString?: string }) {\n return {\n codec: track.codecString ?? track.codec,\n codedWidth: track.width,\n codedHeight: track.height,\n };\n}\n\nfunction buildAudioDecoderConfig(track: { codec: string; channels: number; sampleRate: number; codecString?: string }) {\n return {\n codec: track.codecString ?? track.codec,\n numberOfChannels: track.channels,\n sampleRate: track.sampleRate,\n };\n}\n\n// ── Structural types ────────────────────────────────────────────────────────\n\ninterface LibavPacket {\n data: Uint8Array;\n pts: number;\n ptshi?: number;\n duration?: number;\n durationhi?: number;\n flags: number;\n stream_index: number;\n time_base_num?: number;\n time_base_den?: number;\n}\n\ninterface LibavStream {\n index: number;\n codec_type: number;\n codec_id: number;\n codecpar: number;\n time_base_num?: number;\n time_base_den?: number;\n}\n\ninterface LibavRuntime {\n AVMEDIA_TYPE_VIDEO: number;\n AVMEDIA_TYPE_AUDIO: number;\n AVERROR_EOF: number;\n EAGAIN: number;\n\n mkreadaheadfile(name: string, blob: Blob): Promise<void>;\n unlinkreadaheadfile(name: string): Promise<void>;\n ff_init_demuxer_file(name: string): Promise<[number, LibavStream[]]>;\n ff_read_frame_multi(\n fmt_ctx: number,\n pkt: number,\n opts?: { limit?: number },\n ): Promise<[number, Record<number, LibavPacket[]>]>;\n av_packet_alloc(): Promise<number>;\n av_packet_free?(pkt: number): Promise<void>;\n avformat_close_input_js(ctx: number): Promise<void>;\n}\n","/**\n * Standalone transcode function: re-encode media into a modern container with\n * modern codecs. Unlike {@link remux}, this is a lossy operation that decodes\n * and re-encodes the streams.\n *\n * Built on top of mediabunny's `Conversion` class which handles the full\n * decode → encode → mux pipeline. WebCodecs encoders are used when available;\n * mediabunny falls back to other paths internally.\n *\n * Limitations in v1:\n * - Input must be in a mediabunny-readable container (MP4, MKV, WebM, OGG, ...).\n * AVI/ASF/FLV sources are not yet supported by transcode (use remux + native\n * playback or wait for v1.1).\n */\n\nimport { probe } from \"../probe/index.js\";\nimport { buildMediabunnySourceFromInput } from \"../probe/mediabunny.js\";\nimport { createOutputFormat, mimeForFormat, generateFilename } from \"./remux.js\";\nimport type {\n MediaInput,\n MediaContext,\n TranscodeOptions,\n ConvertResult,\n OutputFormat,\n OutputVideoCodec,\n OutputAudioCodec,\n TranscodeQuality,\n} from \"../types.js\";\n\n/** Containers mediabunny can demux. AVI/ASF/FLV are not in this set. */\nconst MEDIABUNNY_CONTAINERS = new Set([\n \"mp4\", \"mov\", \"mkv\", \"webm\", \"ogg\", \"wav\", \"mp3\", \"flac\", \"adts\",\n]);\n\n/**\n * Transcode a media source into a modern container with modern codecs.\n *\n * Re-encodes both video and audio. Use {@link remux} instead when the source\n * codecs are already modern and only the container needs changing — it's much\n * faster and lossless.\n */\nexport async function transcode(\n source: MediaInput,\n options: TranscodeOptions = {},\n): Promise<ConvertResult> {\n const outputFormat: OutputFormat = options.outputFormat ?? \"mp4\";\n const videoCodec = options.videoCodec ?? defaultVideoCodec(outputFormat);\n const audioCodec = options.audioCodec ?? defaultAudioCodec(outputFormat);\n const quality = options.quality ?? \"medium\";\n\n validateCodecCompatibility(outputFormat, videoCodec, audioCodec);\n options.signal?.throwIfAborted();\n\n const ctx = await probe(source);\n options.signal?.throwIfAborted();\n\n if (!MEDIABUNNY_CONTAINERS.has(ctx.container)) {\n throw new Error(\n `Cannot transcode \"${ctx.container}\" sources in v1. ` +\n `transcode() only supports inputs that mediabunny can read (MP4, MKV, WebM, OGG, MP3, FLAC, WAV, MOV). ` +\n `For AVI/ASF/FLV sources, use the player's playback strategies instead.`,\n );\n }\n\n return doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);\n}\n\n/**\n * One attempt at the full mediabunny conversion. Each attempt allocates\n * fresh `Input` / `Output` / `Conversion` instances because they are all\n * single-use in mediabunny.\n *\n * Returns the muxed `ArrayBuffer` on success. Throws on failure.\n */\nasync function attemptTranscode(\n ctx: MediaContext,\n outputFormat: OutputFormat,\n videoCodec: OutputVideoCodec,\n audioCodec: OutputAudioCodec,\n quality: TranscodeQuality,\n options: TranscodeOptions,\n): Promise<ArrayBuffer> {\n const mb = await import(\"mediabunny\");\n\n const input = new mb.Input({\n source: await buildMediabunnySourceFromInput(mb, ctx.source),\n formats: mb.ALL_FORMATS,\n });\n\n const target = new mb.BufferTarget();\n const output = new mb.Output({\n format: createOutputFormat(mb, outputFormat),\n target,\n });\n\n // Build mediabunny ConversionVideoOptions\n const videoOptions = options.dropVideo\n ? { discard: true as const }\n : {\n codec: avbridgeVideoToMediabunny(videoCodec),\n bitrate: options.videoBitrate ?? qualityToMediabunny(mb, quality),\n forceTranscode: true,\n ...(options.width !== undefined ? { width: options.width } : {}),\n ...(options.height !== undefined ? { height: options.height } : {}),\n ...(options.width !== undefined && options.height !== undefined\n ? { fit: \"contain\" as const }\n : {}),\n ...(options.frameRate !== undefined ? { frameRate: options.frameRate } : {}),\n ...(options.hardwareAcceleration !== undefined\n ? { hardwareAcceleration: options.hardwareAcceleration }\n : {}),\n };\n\n const audioOptions = options.dropAudio\n ? { discard: true as const }\n : {\n codec: avbridgeAudioToMediabunny(audioCodec),\n bitrate: options.audioBitrate ?? qualityToMediabunny(mb, quality),\n forceTranscode: true,\n };\n\n const conversion = await mb.Conversion.init({\n input,\n output,\n video: videoOptions,\n audio: audioOptions,\n showWarnings: false,\n });\n\n if (!conversion.isValid) {\n const reasons = conversion.discardedTracks\n .map((d) => `${d.track.type} track discarded: ${d.reason}`)\n .join(\"; \");\n throw new Error(\n `Cannot transcode: mediabunny rejected the conversion. ${reasons || \"(no reason given)\"}`,\n );\n }\n\n // Wire progress\n if (options.onProgress) {\n const onProgress = options.onProgress;\n conversion.onProgress = (p) => {\n onProgress({ percent: p * 100, bytesWritten: 0 });\n };\n }\n\n // Wire cancellation\n let abortHandler: (() => void) | undefined;\n if (options.signal) {\n options.signal.throwIfAborted();\n abortHandler = () => void conversion.cancel();\n options.signal.addEventListener(\"abort\", abortHandler, { once: true });\n }\n\n try {\n await conversion.execute();\n } finally {\n if (abortHandler && options.signal) {\n options.signal.removeEventListener(\"abort\", abortHandler);\n }\n }\n\n if (!target.buffer) {\n throw new Error(\"Transcode failed: mediabunny produced no output buffer.\");\n }\n return target.buffer;\n}\n\n/**\n * Detect the \"Encoding error\" failure pattern that headless Chromium's\n * H.264 WebCodecs encoder hits on its first call per page. The encoder\n * is fully usable on the second attempt, so we retry once.\n *\n * See <https://issues.chromium.org/> — this is a known first-call init\n * issue in the OS-backed encoder pipeline (VideoToolbox on macOS).\n */\nfunction isLikelyEncoderInitError(err: unknown): boolean {\n if (!err) return false;\n const msg = err instanceof Error ? err.message : String(err);\n const lower = msg.toLowerCase();\n // The exact strings WebCodecs / mediabunny surface for encoder failures.\n return (\n lower.includes(\"encoding error\") ||\n lower.includes(\"encoder\") ||\n lower.includes(\"encode failed\")\n );\n}\n\nfunction describeError(err: unknown): string {\n if (!err) return \"(unknown)\";\n if (err instanceof Error) return err.message;\n return String(err);\n}\n\n/**\n * Maximum encoder retry attempts. Headless Chromium's H.264 WebCodecs\n * encoder hits a first-call init failure and *usually* recovers on the\n * second attempt — but in rare cases the second attempt also fails. Two\n * extra attempts (3 total) is enough to make the smoke test reliable\n * without masking real bugs.\n */\nconst MAX_ENCODER_RETRIES = 2;\n\nasync function doTranscode(\n ctx: MediaContext,\n outputFormat: OutputFormat,\n videoCodec: OutputVideoCodec,\n audioCodec: OutputAudioCodec,\n quality: TranscodeQuality,\n options: TranscodeOptions,\n): Promise<ConvertResult> {\n const notes: string[] = [];\n let buffer: ArrayBuffer | null = null;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= MAX_ENCODER_RETRIES; attempt++) {\n try {\n buffer = await attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);\n if (attempt > 0) {\n notes.push(\n `Encoder failed ${attempt} time${attempt === 1 ? \"\" : \"s\"} before succeeding ` +\n `(known headless Chromium WebCodecs encoder init issue): ${describeError(lastError)}`,\n );\n }\n break;\n } catch (err) {\n lastError = err;\n // Don't retry on user cancellation or permanent setup errors.\n if (options.signal?.aborted) throw err;\n if (!isLikelyEncoderInitError(err)) throw err;\n if (attempt === MAX_ENCODER_RETRIES) throw err;\n // Small backoff between attempts.\n await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));\n }\n }\n\n if (!buffer) {\n throw new Error(\"Transcode failed: no buffer produced (this should be unreachable).\");\n }\n\n const mimeType = mimeForFormat(outputFormat);\n const blob = new Blob([buffer], { type: mimeType });\n const filename = generateFilename(ctx.name, outputFormat);\n\n options.onProgress?.({ percent: 100, bytesWritten: blob.size });\n\n return {\n blob,\n mimeType,\n container: outputFormat,\n videoCodec: options.dropVideo ? undefined : videoCodec,\n audioCodec: options.dropAudio ? undefined : audioCodec,\n duration: ctx.duration,\n filename,\n ...(notes.length > 0 ? { notes } : {}),\n };\n}\n\n// ── Helpers ─────────────────────────────────────────────────────────────────\n\n/** @internal Exported for testing. */\nexport function defaultVideoCodec(format: OutputFormat): OutputVideoCodec {\n switch (format) {\n case \"webm\": return \"vp9\";\n case \"mp4\":\n case \"mkv\":\n default: return \"h264\";\n }\n}\n\n/** @internal Exported for testing. */\nexport function defaultAudioCodec(format: OutputFormat): OutputAudioCodec {\n switch (format) {\n case \"webm\": return \"opus\";\n case \"mp4\":\n case \"mkv\":\n default: return \"aac\";\n }\n}\n\n/** @internal Exported for testing. */\nexport function validateCodecCompatibility(\n format: OutputFormat,\n videoCodec: OutputVideoCodec,\n audioCodec: OutputAudioCodec,\n): void {\n // WebM only allows VP8/VP9/AV1 video and Opus/Vorbis audio.\n if (format === \"webm\") {\n if (videoCodec !== \"vp9\" && videoCodec !== \"av1\") {\n throw new Error(\n `WebM does not support video codec \"${videoCodec}\". Use \"vp9\" or \"av1\", or change outputFormat to \"mp4\" or \"mkv\".`,\n );\n }\n if (audioCodec !== \"opus\") {\n throw new Error(\n `WebM does not support audio codec \"${audioCodec}\". Use \"opus\", or change outputFormat to \"mp4\" or \"mkv\".`,\n );\n }\n }\n}\n\nfunction avbridgeVideoToMediabunny(c: OutputVideoCodec): \"avc\" | \"hevc\" | \"vp9\" | \"av1\" {\n switch (c) {\n case \"h264\": return \"avc\";\n case \"h265\": return \"hevc\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n }\n}\n\nfunction avbridgeAudioToMediabunny(c: OutputAudioCodec): \"aac\" | \"opus\" | \"flac\" {\n switch (c) {\n case \"aac\": return \"aac\";\n case \"opus\": return \"opus\";\n case \"flac\": return \"flac\";\n }\n}\n\nfunction qualityToMediabunny(\n mb: typeof import(\"mediabunny\"),\n quality: TranscodeQuality,\n): InstanceType<typeof mb.Quality> {\n switch (quality) {\n case \"low\": return mb.QUALITY_LOW;\n case \"medium\": return mb.QUALITY_MEDIUM;\n case \"high\": return mb.QUALITY_HIGH;\n case \"very-high\": return mb.QUALITY_VERY_HIGH;\n }\n}\n"]}