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.cjs ADDED
@@ -0,0 +1,576 @@
1
+ 'use strict';
2
+
3
+ var chunkF7YS2XOA_cjs = require('./chunk-F7YS2XOA.cjs');
4
+ var chunkY5FYF5KG_cjs = require('./chunk-Y5FYF5KG.cjs');
5
+ var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
6
+ require('./chunk-Z2FJ5TJC.cjs');
7
+ require('./chunk-NZU7W256.cjs');
8
+
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 chunkF7YS2XOA_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);
31
+ }
32
+ function validateRemuxEligibility(ctx, strict) {
33
+ const video = ctx.videoTracks[0];
34
+ const audio = ctx.audioTracks[0];
35
+ if (video) {
36
+ const mbCodec = chunkF7YS2XOA_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 = chunkF7YS2XOA_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.`
54
+ );
55
+ }
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 chunkF7YS2XOA_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
75
+ });
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
+ try {
93
+ await conversion.execute();
94
+ } finally {
95
+ if (abortHandler && options.signal) {
96
+ options.signal.removeEventListener("abort", abortHandler);
97
+ }
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-ZHOERPHW.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 chunkY5FYF5KG_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(() => {
138
+ });
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 ? chunkF7YS2XOA_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
+ const mbAudioCodec = audioTrackInfo ? chunkF7YS2XOA_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}`);
187
+ }
188
+ const videoPackets = videoStream ? packets[videoStream.index] ?? [] : [];
189
+ const audioPackets = audioStream ? packets[audioStream.index] ?? [] : [];
190
+ if (videoSource) {
191
+ for (const pkt of videoPackets) {
192
+ sanitizePacketTimestamp(pkt, () => {
193
+ const ts = syntheticVideoUs;
194
+ syntheticVideoUs += videoFrameStepUs;
195
+ return ts;
196
+ }, videoTimeBase);
197
+ const mbPacket = libavPacketToMediAbunny(mb, pkt);
198
+ await videoSource.add(
199
+ mbPacket,
200
+ firstVideoMeta ? { decoderConfig: buildVideoDecoderConfig(videoTrackInfo) } : void 0
201
+ );
202
+ firstVideoMeta = false;
203
+ }
204
+ }
205
+ if (audioSource) {
206
+ for (const pkt of audioPackets) {
207
+ sanitizePacketTimestamp(pkt, () => {
208
+ const ts = syntheticAudioUs;
209
+ const sampleRate = audioTrackInfo?.sampleRate ?? 44100;
210
+ syntheticAudioUs += Math.round(1024 * 1e6 / sampleRate);
211
+ return ts;
212
+ }, audioTimeBase);
213
+ const mbPacket = libavPacketToMediAbunny(mb, pkt);
214
+ await audioSource.add(
215
+ mbPacket,
216
+ firstAudioMeta ? { decoderConfig: buildAudioDecoderConfig(audioTrackInfo) } : void 0
217
+ );
218
+ firstAudioMeta = false;
219
+ }
220
+ }
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;
233
+ }
234
+ }
235
+ await output.finalize();
236
+ try {
237
+ await libav.av_packet_free?.(readPkt);
238
+ } catch {
239
+ }
240
+ 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.");
246
+ }
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
+ }
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;
282
+ }
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
+ }
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" });
299
+ }
300
+ }
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";
311
+ }
312
+ }
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
+
342
+ // src/convert/transcode.ts
343
+ var MEDIABUNNY_CONTAINERS2 = /* @__PURE__ */ new Set([
344
+ "mp4",
345
+ "mov",
346
+ "mkv",
347
+ "webm",
348
+ "ogg",
349
+ "wav",
350
+ "mp3",
351
+ "flac",
352
+ "adts"
353
+ ]);
354
+ async function transcode(source, options = {}) {
355
+ const outputFormat = options.outputFormat ?? "mp4";
356
+ const videoCodec = options.videoCodec ?? defaultVideoCodec(outputFormat);
357
+ const audioCodec = options.audioCodec ?? defaultAudioCodec(outputFormat);
358
+ const quality = options.quality ?? "medium";
359
+ validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
360
+ options.signal?.throwIfAborted();
361
+ const ctx = await chunkF7YS2XOA_cjs.probe(source);
362
+ 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.`
366
+ );
367
+ }
368
+ return doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
369
+ }
370
+ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
371
+ const mb = await import('mediabunny');
372
+ const input = new mb.Input({
373
+ source: await chunkF7YS2XOA_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
374
+ formats: mb.ALL_FORMATS
375
+ });
376
+ const target = new mb.BufferTarget();
377
+ const output = new mb.Output({
378
+ format: createOutputFormat(mb, outputFormat),
379
+ target
380
+ });
381
+ const videoOptions = options.dropVideo ? { discard: true } : {
382
+ codec: avbridgeVideoToMediabunny2(videoCodec),
383
+ bitrate: options.videoBitrate ?? qualityToMediabunny(mb, quality),
384
+ forceTranscode: true,
385
+ ...options.width !== void 0 ? { width: options.width } : {},
386
+ ...options.height !== void 0 ? { height: options.height } : {},
387
+ ...options.width !== void 0 && options.height !== void 0 ? { fit: "contain" } : {},
388
+ ...options.frameRate !== void 0 ? { frameRate: options.frameRate } : {},
389
+ ...options.hardwareAcceleration !== void 0 ? { hardwareAcceleration: options.hardwareAcceleration } : {}
390
+ };
391
+ const audioOptions = options.dropAudio ? { discard: true } : {
392
+ codec: avbridgeAudioToMediabunny2(audioCodec),
393
+ bitrate: options.audioBitrate ?? qualityToMediabunny(mb, quality),
394
+ forceTranscode: true
395
+ };
396
+ const conversion = await mb.Conversion.init({
397
+ input,
398
+ output,
399
+ video: videoOptions,
400
+ audio: audioOptions,
401
+ showWarnings: false
402
+ });
403
+ if (!conversion.isValid) {
404
+ const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
405
+ throw new Error(
406
+ `Cannot transcode: mediabunny rejected the conversion. ${reasons || "(no reason given)"}`
407
+ );
408
+ }
409
+ if (options.onProgress) {
410
+ const onProgress = options.onProgress;
411
+ conversion.onProgress = (p) => {
412
+ onProgress({ percent: p * 100, bytesWritten: 0 });
413
+ };
414
+ }
415
+ let abortHandler;
416
+ if (options.signal) {
417
+ options.signal.throwIfAborted();
418
+ abortHandler = () => void conversion.cancel();
419
+ options.signal.addEventListener("abort", abortHandler, { once: true });
420
+ }
421
+ try {
422
+ await conversion.execute();
423
+ } finally {
424
+ if (abortHandler && options.signal) {
425
+ options.signal.removeEventListener("abort", abortHandler);
426
+ }
427
+ }
428
+ if (!target.buffer) {
429
+ throw new Error("Transcode failed: mediabunny produced no output buffer.");
430
+ }
431
+ return target.buffer;
432
+ }
433
+ function isLikelyEncoderInitError(err) {
434
+ if (!err) return false;
435
+ const msg = err instanceof Error ? err.message : String(err);
436
+ const lower = msg.toLowerCase();
437
+ return lower.includes("encoding error") || lower.includes("encoder") || lower.includes("encode failed");
438
+ }
439
+ function describeError(err) {
440
+ if (!err) return "(unknown)";
441
+ if (err instanceof Error) return err.message;
442
+ return String(err);
443
+ }
444
+ var MAX_ENCODER_RETRIES = 2;
445
+ async function doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
446
+ const notes = [];
447
+ let buffer = null;
448
+ let lastError;
449
+ for (let attempt = 0; attempt <= MAX_ENCODER_RETRIES; attempt++) {
450
+ try {
451
+ buffer = await attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
452
+ if (attempt > 0) {
453
+ notes.push(
454
+ `Encoder failed ${attempt} time${attempt === 1 ? "" : "s"} before succeeding (known headless Chromium WebCodecs encoder init issue): ${describeError(lastError)}`
455
+ );
456
+ }
457
+ break;
458
+ } catch (err) {
459
+ lastError = err;
460
+ if (options.signal?.aborted) throw err;
461
+ if (!isLikelyEncoderInitError(err)) throw err;
462
+ if (attempt === MAX_ENCODER_RETRIES) throw err;
463
+ await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));
464
+ }
465
+ }
466
+ if (!buffer) {
467
+ throw new Error("Transcode failed: no buffer produced (this should be unreachable).");
468
+ }
469
+ const mimeType = mimeForFormat(outputFormat);
470
+ const blob = new Blob([buffer], { type: mimeType });
471
+ const filename = generateFilename(ctx.name, outputFormat);
472
+ options.onProgress?.({ percent: 100, bytesWritten: blob.size });
473
+ return {
474
+ blob,
475
+ mimeType,
476
+ container: outputFormat,
477
+ videoCodec: options.dropVideo ? void 0 : videoCodec,
478
+ audioCodec: options.dropAudio ? void 0 : audioCodec,
479
+ duration: ctx.duration,
480
+ filename,
481
+ ...notes.length > 0 ? { notes } : {}
482
+ };
483
+ }
484
+ function defaultVideoCodec(format) {
485
+ switch (format) {
486
+ case "webm":
487
+ return "vp9";
488
+ case "mp4":
489
+ case "mkv":
490
+ default:
491
+ return "h264";
492
+ }
493
+ }
494
+ function defaultAudioCodec(format) {
495
+ switch (format) {
496
+ case "webm":
497
+ return "opus";
498
+ case "mp4":
499
+ case "mkv":
500
+ default:
501
+ return "aac";
502
+ }
503
+ }
504
+ function validateCodecCompatibility(format, videoCodec, audioCodec) {
505
+ if (format === "webm") {
506
+ if (videoCodec !== "vp9" && videoCodec !== "av1") {
507
+ throw new Error(
508
+ `WebM does not support video codec "${videoCodec}". Use "vp9" or "av1", or change outputFormat to "mp4" or "mkv".`
509
+ );
510
+ }
511
+ if (audioCodec !== "opus") {
512
+ throw new Error(
513
+ `WebM does not support audio codec "${audioCodec}". Use "opus", or change outputFormat to "mp4" or "mkv".`
514
+ );
515
+ }
516
+ }
517
+ }
518
+ function avbridgeVideoToMediabunny2(c) {
519
+ switch (c) {
520
+ case "h264":
521
+ return "avc";
522
+ case "h265":
523
+ return "hevc";
524
+ case "vp9":
525
+ return "vp9";
526
+ case "av1":
527
+ return "av1";
528
+ }
529
+ }
530
+ function avbridgeAudioToMediabunny2(c) {
531
+ switch (c) {
532
+ case "aac":
533
+ return "aac";
534
+ case "opus":
535
+ return "opus";
536
+ case "flac":
537
+ return "flac";
538
+ }
539
+ }
540
+ function qualityToMediabunny(mb, quality) {
541
+ switch (quality) {
542
+ case "low":
543
+ return mb.QUALITY_LOW;
544
+ case "medium":
545
+ return mb.QUALITY_MEDIUM;
546
+ case "high":
547
+ return mb.QUALITY_HIGH;
548
+ case "very-high":
549
+ return mb.QUALITY_VERY_HIGH;
550
+ }
551
+ }
552
+
553
+ Object.defineProperty(exports, "UnifiedPlayer", {
554
+ enumerable: true,
555
+ get: function () { return chunkF7YS2XOA_cjs.UnifiedPlayer; }
556
+ });
557
+ Object.defineProperty(exports, "classify", {
558
+ enumerable: true,
559
+ get: function () { return chunkF7YS2XOA_cjs.classifyContext; }
560
+ });
561
+ Object.defineProperty(exports, "createPlayer", {
562
+ enumerable: true,
563
+ get: function () { return chunkF7YS2XOA_cjs.createPlayer; }
564
+ });
565
+ Object.defineProperty(exports, "probe", {
566
+ enumerable: true,
567
+ get: function () { return chunkF7YS2XOA_cjs.probe; }
568
+ });
569
+ Object.defineProperty(exports, "srtToVtt", {
570
+ enumerable: true,
571
+ get: function () { return chunkF7YS2XOA_cjs.srtToVtt; }
572
+ });
573
+ exports.remux = remux;
574
+ exports.transcode = transcode;
575
+ //# sourceMappingURL=index.cjs.map
576
+ //# sourceMappingURL=index.cjs.map