avbridge 2.1.1 → 2.2.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 (68) hide show
  1. package/CHANGELOG.md +136 -0
  2. package/dist/{avi-GNTV5ZOH.cjs → avi-6SJLWIWW.cjs} +19 -4
  3. package/dist/avi-6SJLWIWW.cjs.map +1 -0
  4. package/dist/{avi-V6HYQVR2.js → avi-GCGM7OJI.js} +18 -3
  5. package/dist/avi-GCGM7OJI.js.map +1 -0
  6. package/dist/{chunk-EJH67FXG.js → chunk-5DMTJVIU.js} +99 -3
  7. package/dist/chunk-5DMTJVIU.js.map +1 -0
  8. package/dist/{chunk-CUQD23WO.js → chunk-C5VA5U5O.js} +122 -23
  9. package/dist/chunk-C5VA5U5O.js.map +1 -0
  10. package/dist/{chunk-JQH6D4OE.cjs → chunk-G4APZMCP.cjs} +100 -3
  11. package/dist/chunk-G4APZMCP.cjs.map +1 -0
  12. package/dist/{chunk-Y5FYF5KG.cjs → chunk-HZLQNKFN.cjs} +5 -2
  13. package/dist/chunk-HZLQNKFN.cjs.map +1 -0
  14. package/dist/{chunk-PQTZS7OA.js → chunk-ILKDNBSE.js} +5 -2
  15. package/dist/chunk-ILKDNBSE.js.map +1 -0
  16. package/dist/{chunk-O34444ID.cjs → chunk-OE66B34H.cjs} +126 -27
  17. package/dist/chunk-OE66B34H.cjs.map +1 -0
  18. package/dist/element-browser.js +244 -19
  19. package/dist/element-browser.js.map +1 -1
  20. package/dist/element.cjs +9 -5
  21. package/dist/element.cjs.map +1 -1
  22. package/dist/element.d.cts +1 -1
  23. package/dist/element.d.ts +1 -1
  24. package/dist/element.js +8 -4
  25. package/dist/element.js.map +1 -1
  26. package/dist/index.cjs +18 -18
  27. package/dist/index.d.cts +12 -8
  28. package/dist/index.d.ts +12 -8
  29. package/dist/index.js +5 -5
  30. package/dist/libav-loader-27RDIN2I.js +3 -0
  31. package/dist/{libav-loader-XKH2TKUW.js.map → libav-loader-27RDIN2I.js.map} +1 -1
  32. package/dist/libav-loader-IV4AJ2HW.cjs +12 -0
  33. package/dist/{libav-loader-6APXVNIV.cjs.map → libav-loader-IV4AJ2HW.cjs.map} +1 -1
  34. package/dist/{player-BdtUG4rh.d.cts → player-DUyvltvy.d.cts} +3 -3
  35. package/dist/{player-BdtUG4rh.d.ts → player-DUyvltvy.d.ts} +3 -3
  36. package/dist/source-CN43EI7Z.cjs +28 -0
  37. package/dist/{source-SC6ZEQYR.cjs.map → source-CN43EI7Z.cjs.map} +1 -1
  38. package/dist/source-FFZ7TW2B.js +3 -0
  39. package/dist/{source-ZFS4H7J3.js.map → source-FFZ7TW2B.js.map} +1 -1
  40. package/package.json +1 -1
  41. package/src/classify/rules.ts +9 -2
  42. package/src/element/avbridge-video.ts +12 -1
  43. package/src/player.ts +22 -1
  44. package/src/probe/avi.ts +8 -1
  45. package/src/probe/index.ts +30 -9
  46. package/src/strategies/fallback/audio-output.ts +25 -3
  47. package/src/strategies/fallback/decoder.ts +96 -8
  48. package/src/strategies/fallback/index.ts +90 -6
  49. package/src/strategies/fallback/libav-loader.ts +12 -0
  50. package/src/strategies/fallback/video-renderer.ts +29 -4
  51. package/src/strategies/remux/pipeline.ts +10 -1
  52. package/src/types.ts +10 -1
  53. package/src/util/debug.ts +131 -0
  54. package/src/util/source.ts +4 -0
  55. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  56. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  57. package/dist/avi-GNTV5ZOH.cjs.map +0 -1
  58. package/dist/avi-V6HYQVR2.js.map +0 -1
  59. package/dist/chunk-CUQD23WO.js.map +0 -1
  60. package/dist/chunk-EJH67FXG.js.map +0 -1
  61. package/dist/chunk-JQH6D4OE.cjs.map +0 -1
  62. package/dist/chunk-O34444ID.cjs.map +0 -1
  63. package/dist/chunk-PQTZS7OA.js.map +0 -1
  64. package/dist/chunk-Y5FYF5KG.cjs.map +0 -1
  65. package/dist/libav-loader-6APXVNIV.cjs +0 -12
  66. package/dist/libav-loader-XKH2TKUW.js +0 -3
  67. package/dist/source-SC6ZEQYR.cjs +0 -28
  68. package/dist/source-ZFS4H7J3.js +0 -3
package/dist/index.cjs CHANGED
@@ -1,9 +1,9 @@
1
1
  'use strict';
2
2
 
3
- var chunkO34444ID_cjs = require('./chunk-O34444ID.cjs');
4
- var chunkY5FYF5KG_cjs = require('./chunk-Y5FYF5KG.cjs');
3
+ var chunkOE66B34H_cjs = require('./chunk-OE66B34H.cjs');
4
+ var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
5
5
  var chunkL4NPOJ36_cjs = require('./chunk-L4NPOJ36.cjs');
6
- require('./chunk-JQH6D4OE.cjs');
6
+ require('./chunk-G4APZMCP.cjs');
7
7
  require('./chunk-NZU7W256.cjs');
8
8
 
9
9
  // src/convert/remux.ts
@@ -21,7 +21,7 @@ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
21
21
  async function remux(source, options = {}) {
22
22
  const outputFormat = options.outputFormat ?? "mp4";
23
23
  options.signal?.throwIfAborted();
24
- const ctx = await chunkO34444ID_cjs.probe(source);
24
+ const ctx = await chunkOE66B34H_cjs.probe(source);
25
25
  options.signal?.throwIfAborted();
26
26
  validateRemuxEligibility(ctx, options.strict ?? false);
27
27
  if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
@@ -33,7 +33,7 @@ function validateRemuxEligibility(ctx, strict) {
33
33
  const video = ctx.videoTracks[0];
34
34
  const audio = ctx.audioTracks[0];
35
35
  if (video) {
36
- const mbCodec = chunkO34444ID_cjs.avbridgeVideoToMediabunny(video.codec);
36
+ const mbCodec = chunkOE66B34H_cjs.avbridgeVideoToMediabunny(video.codec);
37
37
  if (!mbCodec) {
38
38
  throw new Error(
39
39
  `Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
@@ -41,7 +41,7 @@ function validateRemuxEligibility(ctx, strict) {
41
41
  }
42
42
  }
43
43
  if (audio) {
44
- const mbCodec = chunkO34444ID_cjs.avbridgeAudioToMediabunny(audio.codec);
44
+ const mbCodec = chunkOE66B34H_cjs.avbridgeAudioToMediabunny(audio.codec);
45
45
  if (!mbCodec) {
46
46
  throw new Error(
47
47
  `Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
@@ -60,7 +60,7 @@ function validateRemuxEligibility(ctx, strict) {
60
60
  async function remuxViaMediAbunny(ctx, outputFormat, options) {
61
61
  const mb = await import('mediabunny');
62
62
  const input = new mb.Input({
63
- source: await chunkO34444ID_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
63
+ source: await chunkOE66B34H_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
64
64
  formats: mb.ALL_FORMATS
65
65
  });
66
66
  const target = new mb.BufferTarget();
@@ -117,7 +117,7 @@ async function remuxViaLibav(ctx, outputFormat, options) {
117
117
  let loadLibav;
118
118
  let pickLibavVariant;
119
119
  try {
120
- const loader = await import('./libav-loader-6APXVNIV.cjs');
120
+ const loader = await import('./libav-loader-IV4AJ2HW.cjs');
121
121
  const routing = await import('./variant-routing-GOHB2RZN.cjs');
122
122
  loadLibav = loader.loadLibav;
123
123
  pickLibavVariant = routing.pickLibavVariant;
@@ -128,7 +128,7 @@ async function remuxViaLibav(ctx, outputFormat, options) {
128
128
  }
129
129
  const variant = pickLibavVariant(ctx);
130
130
  const libav = await loadLibav(variant);
131
- const normalized = await chunkY5FYF5KG_cjs.normalizeSource(ctx.source);
131
+ const normalized = await chunkHZLQNKFN_cjs.normalizeSource(ctx.source);
132
132
  const filename = ctx.name ?? `remux-input-${Date.now()}`;
133
133
  const handle = await chunkL4NPOJ36_cjs.prepareLibavInput(libav, filename, normalized);
134
134
  try {
@@ -146,8 +146,8 @@ async function doLibavRemux(libav, filename, ctx, outputFormat, options) {
146
146
  const audioStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
147
147
  const videoTrackInfo = ctx.videoTracks[0];
148
148
  const audioTrackInfo = ctx.audioTracks[0];
149
- const mbVideoCodec = videoTrackInfo ? chunkO34444ID_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
- const mbAudioCodec = audioTrackInfo ? chunkO34444ID_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
149
+ const mbVideoCodec = videoTrackInfo ? chunkOE66B34H_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec) : null;
150
+ const mbAudioCodec = audioTrackInfo ? chunkOE66B34H_cjs.avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
151
151
  const target = new mb.BufferTarget();
152
152
  const output = new mb.Output({
153
153
  format: createOutputFormat(mb, outputFormat),
@@ -358,7 +358,7 @@ async function transcode(source, options = {}) {
358
358
  const quality = options.quality ?? "medium";
359
359
  validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
360
360
  options.signal?.throwIfAborted();
361
- const ctx = await chunkO34444ID_cjs.probe(source);
361
+ const ctx = await chunkOE66B34H_cjs.probe(source);
362
362
  options.signal?.throwIfAborted();
363
363
  if (!MEDIABUNNY_CONTAINERS2.has(ctx.container)) {
364
364
  throw new Error(
@@ -370,7 +370,7 @@ async function transcode(source, options = {}) {
370
370
  async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
371
371
  const mb = await import('mediabunny');
372
372
  const input = new mb.Input({
373
- source: await chunkO34444ID_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
373
+ source: await chunkOE66B34H_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
374
374
  formats: mb.ALL_FORMATS
375
375
  });
376
376
  const target = new mb.BufferTarget();
@@ -552,23 +552,23 @@ function qualityToMediabunny(mb, quality) {
552
552
 
553
553
  Object.defineProperty(exports, "UnifiedPlayer", {
554
554
  enumerable: true,
555
- get: function () { return chunkO34444ID_cjs.UnifiedPlayer; }
555
+ get: function () { return chunkOE66B34H_cjs.UnifiedPlayer; }
556
556
  });
557
557
  Object.defineProperty(exports, "classify", {
558
558
  enumerable: true,
559
- get: function () { return chunkO34444ID_cjs.classifyContext; }
559
+ get: function () { return chunkOE66B34H_cjs.classifyContext; }
560
560
  });
561
561
  Object.defineProperty(exports, "createPlayer", {
562
562
  enumerable: true,
563
- get: function () { return chunkO34444ID_cjs.createPlayer; }
563
+ get: function () { return chunkOE66B34H_cjs.createPlayer; }
564
564
  });
565
565
  Object.defineProperty(exports, "probe", {
566
566
  enumerable: true,
567
- get: function () { return chunkO34444ID_cjs.probe; }
567
+ get: function () { return chunkOE66B34H_cjs.probe; }
568
568
  });
569
569
  Object.defineProperty(exports, "srtToVtt", {
570
570
  enumerable: true,
571
- get: function () { return chunkO34444ID_cjs.srtToVtt; }
571
+ get: function () { return chunkOE66B34H_cjs.srtToVtt; }
572
572
  });
573
573
  exports.remux = remux;
574
574
  exports.transcode = transcode;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-BdtUG4rh.cjs';
2
- export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-BdtUG4rh.cjs';
1
+ import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-DUyvltvy.cjs';
2
+ export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-DUyvltvy.cjs';
3
3
 
4
4
  /**
5
5
  * Pure classification — no I/O, no async. Test-friendly.
@@ -11,12 +11,16 @@ declare function classifyContext(ctx: MediaContext): Classification;
11
11
  *
12
12
  * Routing:
13
13
  * 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.
14
- * 2. If the container is one mediabunny supports → mediabunny. If mediabunny
15
- * rejects, surface the real error rather than blindly falling through to
16
- * libav (which would mask the real failure with a confusing libav error).
17
- * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) libav.js, lazy-loaded.
18
- * `unknown` is included so genuinely unfamiliar files at least get a shot
19
- * at the broader libav demuxer set.
14
+ * 2. If the container is one mediabunny supports → try mediabunny first
15
+ * (fast path it's a single pass of WASM-free JS parsing). If mediabunny
16
+ * throws (e.g. an assertion on an unsupported sample entry like `mp4v`
17
+ * for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to
18
+ * libav.js which handles the long tail of codecs mediabunny doesn't.
19
+ * The combined-error case surfaces *both* failures so the user sees
20
+ * which path each step took.
21
+ * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.
22
+ * mediabunny can't read those containers at all, so there's no fast path
23
+ * to try.
20
24
  */
21
25
  declare function probe(source: MediaInput): Promise<MediaContext>;
22
26
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-BdtUG4rh.js';
2
- export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-BdtUG4rh.js';
1
+ import { M as MediaContext, C as Classification, a as MediaInput, b as ConvertOptions, c as ConvertResult, T as TranscodeOptions } from './player-DUyvltvy.js';
2
+ export { A as AudioCodec, d as AudioTrackInfo, e as ContainerKind, f as CreatePlayerOptions, D as DiagnosticsSnapshot, H as HardwareAccelerationHint, O as OutputAudioCodec, g as OutputFormat, h as OutputVideoCodec, P as PlaybackSession, i as PlayerEventMap, j as PlayerEventName, k as Plugin, l as ProgressInfo, S as StrategyClass, m as StrategyName, n as SubtitleTrackInfo, o as TranscodeQuality, U as UnifiedPlayer, V as VideoCodec, p as VideoTrackInfo, q as createPlayer } from './player-DUyvltvy.js';
3
3
 
4
4
  /**
5
5
  * Pure classification — no I/O, no async. Test-friendly.
@@ -11,12 +11,16 @@ declare function classifyContext(ctx: MediaContext): Classification;
11
11
  *
12
12
  * Routing:
13
13
  * 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.
14
- * 2. If the container is one mediabunny supports → mediabunny. If mediabunny
15
- * rejects, surface the real error rather than blindly falling through to
16
- * libav (which would mask the real failure with a confusing libav error).
17
- * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) libav.js, lazy-loaded.
18
- * `unknown` is included so genuinely unfamiliar files at least get a shot
19
- * at the broader libav demuxer set.
14
+ * 2. If the container is one mediabunny supports → try mediabunny first
15
+ * (fast path it's a single pass of WASM-free JS parsing). If mediabunny
16
+ * throws (e.g. an assertion on an unsupported sample entry like `mp4v`
17
+ * for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to
18
+ * libav.js which handles the long tail of codecs mediabunny doesn't.
19
+ * The combined-error case surfaces *both* failures so the user sees
20
+ * which path each step took.
21
+ * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.
22
+ * mediabunny can't read those containers at all, so there's no fast path
23
+ * to try.
20
24
  */
21
25
  declare function probe(source: MediaInput): Promise<MediaContext>;
22
26
 
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-CUQD23WO.js';
2
- export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-CUQD23WO.js';
3
- import { normalizeSource } from './chunk-PQTZS7OA.js';
1
+ import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-C5VA5U5O.js';
2
+ export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-C5VA5U5O.js';
3
+ import { normalizeSource } from './chunk-ILKDNBSE.js';
4
4
  import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
5
- import './chunk-EJH67FXG.js';
5
+ import './chunk-5DMTJVIU.js';
6
6
  import './chunk-J5MCMN3S.js';
7
7
 
8
8
  // src/convert/remux.ts
@@ -116,7 +116,7 @@ async function remuxViaLibav(ctx, outputFormat, options) {
116
116
  let loadLibav;
117
117
  let pickLibavVariant;
118
118
  try {
119
- const loader = await import('./libav-loader-XKH2TKUW.js');
119
+ const loader = await import('./libav-loader-27RDIN2I.js');
120
120
  const routing = await import('./variant-routing-JOBWXYKD.js');
121
121
  loadLibav = loader.loadLibav;
122
122
  pickLibavVariant = routing.pickLibavVariant;
@@ -0,0 +1,3 @@
1
+ export { loadLibav } from './chunk-5DMTJVIU.js';
2
+ //# sourceMappingURL=libav-loader-27RDIN2I.js.map
3
+ //# sourceMappingURL=libav-loader-27RDIN2I.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-XKH2TKUW.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-27RDIN2I.js"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "loadLibav", {
8
+ enumerable: true,
9
+ get: function () { return chunkG4APZMCP_cjs.loadLibav; }
10
+ });
11
+ //# sourceMappingURL=libav-loader-IV4AJ2HW.cjs.map
12
+ //# sourceMappingURL=libav-loader-IV4AJ2HW.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-6APXVNIV.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"libav-loader-IV4AJ2HW.cjs"}
@@ -13,11 +13,11 @@
13
13
  */
14
14
  type MediaInput = File | Blob | string | URL | ArrayBuffer | Uint8Array;
15
15
  /** Container format families we know about. */
16
- type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
16
+ type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "rm" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
17
17
  /** Video codec families. Strings, not enums, so plugins can extend. */
18
- type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
18
+ type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv10" | "rv20" | "rv30" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
19
19
  /** Audio codec families. */
20
- type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | (string & {});
20
+ type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | "cook" | "ra_144" | "ra_288" | "sipr" | "atrac3" | (string & {});
21
21
  interface VideoTrackInfo {
22
22
  id: number;
23
23
  codec: VideoCodec;
@@ -13,11 +13,11 @@
13
13
  */
14
14
  type MediaInput = File | Blob | string | URL | ArrayBuffer | Uint8Array;
15
15
  /** Container format families we know about. */
16
- type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
16
+ type ContainerKind = "mp4" | "mov" | "mkv" | "webm" | "avi" | "asf" | "flv" | "rm" | "ogg" | "wav" | "mp3" | "flac" | "adts" | "mpegts" | "unknown";
17
17
  /** Video codec families. Strings, not enums, so plugins can extend. */
18
- type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
18
+ type VideoCodec = "h264" | "h265" | "vp8" | "vp9" | "av1" | "mpeg4" | "wmv3" | "vc1" | "rv10" | "rv20" | "rv30" | "rv40" | "mpeg2" | "mpeg1" | "theora" | (string & {});
19
19
  /** Audio codec families. */
20
- type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | (string & {});
20
+ type AudioCodec = "aac" | "mp3" | "opus" | "vorbis" | "flac" | "pcm" | "ac3" | "eac3" | "wmav2" | "wmapro" | "alac" | "cook" | "ra_144" | "ra_288" | "sipr" | "atrac3" | (string & {});
21
21
  interface VideoTrackInfo {
22
22
  id: number;
23
23
  codec: VideoCodec;
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var chunkHZLQNKFN_cjs = require('./chunk-HZLQNKFN.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "isInMemorySource", {
8
+ enumerable: true,
9
+ get: function () { return chunkHZLQNKFN_cjs.isInMemorySource; }
10
+ });
11
+ Object.defineProperty(exports, "normalizeSource", {
12
+ enumerable: true,
13
+ get: function () { return chunkHZLQNKFN_cjs.normalizeSource; }
14
+ });
15
+ Object.defineProperty(exports, "sniffContainer", {
16
+ enumerable: true,
17
+ get: function () { return chunkHZLQNKFN_cjs.sniffContainer; }
18
+ });
19
+ Object.defineProperty(exports, "sniffContainerFromBytes", {
20
+ enumerable: true,
21
+ get: function () { return chunkHZLQNKFN_cjs.sniffContainerFromBytes; }
22
+ });
23
+ Object.defineProperty(exports, "sniffNormalizedSource", {
24
+ enumerable: true,
25
+ get: function () { return chunkHZLQNKFN_cjs.sniffNormalizedSource; }
26
+ });
27
+ //# sourceMappingURL=source-CN43EI7Z.cjs.map
28
+ //# sourceMappingURL=source-CN43EI7Z.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"source-SC6ZEQYR.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-CN43EI7Z.cjs"}
@@ -0,0 +1,3 @@
1
+ export { isInMemorySource, normalizeSource, sniffContainer, sniffContainerFromBytes, sniffNormalizedSource } from './chunk-ILKDNBSE.js';
2
+ //# sourceMappingURL=source-FFZ7TW2B.js.map
3
+ //# sourceMappingURL=source-FFZ7TW2B.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"source-ZFS4H7J3.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-FFZ7TW2B.js"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avbridge",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "Play and convert arbitrary video files in the browser. Native, remux, hybrid, fallback, and transcode — one API.",
5
5
  "license": "MIT",
6
6
  "author": "Keishi Hattori",
@@ -25,8 +25,15 @@ const NATIVE_AUDIO_CODECS = new Set<AudioCodec>([
25
25
  /**
26
26
  * Codecs no major browser plays, period. These force the WASM fallback.
27
27
  */
28
- const FALLBACK_VIDEO_CODECS = new Set<VideoCodec>(["wmv3", "vc1", "mpeg4", "rv40", "mpeg2", "mpeg1", "theora"]);
29
- const FALLBACK_AUDIO_CODECS = new Set<AudioCodec>(["wmav2", "wmapro", "ac3", "eac3"]);
28
+ const FALLBACK_VIDEO_CODECS = new Set<VideoCodec>([
29
+ "wmv3", "vc1", "mpeg4",
30
+ "rv10", "rv20", "rv30", "rv40",
31
+ "mpeg2", "mpeg1", "theora",
32
+ ]);
33
+ const FALLBACK_AUDIO_CODECS = new Set<AudioCodec>([
34
+ "wmav2", "wmapro", "ac3", "eac3",
35
+ "cook", "ra_144", "ra_288", "sipr", "atrac3",
36
+ ]);
30
37
 
31
38
  /**
32
39
  * Containers `<video>` plays directly. Anything else with otherwise-supported
@@ -170,11 +170,22 @@ export class AvbridgeVideoElement extends HTMLElementCtor {
170
170
  constructor() {
171
171
  super();
172
172
  const root = this.attachShadow({ mode: "open" });
173
+
174
+ // A positioned wrapper inside the shadow root. The fallback strategy
175
+ // overlays a canvas on top of the <video> via `target.parentNode` —
176
+ // that only works if the parent is a real Element with layout. Without
177
+ // this wrapper, `target.parentElement` would be null (ShadowRoot is
178
+ // not an Element) and the canvas would never attach to the DOM.
179
+ const stage = document.createElement("div");
180
+ stage.setAttribute("part", "stage");
181
+ stage.style.cssText = "position:relative;width:100%;height:100%;display:block;";
182
+ root.appendChild(stage);
183
+
173
184
  this._videoEl = document.createElement("video");
174
185
  this._videoEl.setAttribute("part", "video");
175
186
  this._videoEl.style.cssText = "width:100%;height:100%;display:block;background:#000;";
176
187
  this._videoEl.playsInline = true;
177
- root.appendChild(this._videoEl);
188
+ stage.appendChild(this._videoEl);
178
189
 
179
190
  // Forward the underlying <video>'s `progress` event so consumers can
180
191
  // observe buffered-range updates without reaching into the shadow DOM.
package/src/player.ts CHANGED
@@ -5,6 +5,7 @@ import { Diagnostics } from "./diagnostics.js";
5
5
  import { PluginRegistry } from "./plugins/registry.js";
6
6
  import { registerBuiltins } from "./plugins/builtin.js";
7
7
  import { discoverSidecars, attachSubtitleTracks, SubtitleResourceBag } from "./subtitles/index.js";
8
+ import { dbg } from "./util/debug.js";
8
9
  import type {
9
10
  Classification,
10
11
  CreatePlayerOptions,
@@ -65,8 +66,14 @@ export class UnifiedPlayer {
65
66
  }
66
67
 
67
68
  private async bootstrap(): Promise<void> {
69
+ const bootstrapStart = performance.now();
68
70
  try {
69
- const ctx = await probe(this.options.source);
71
+ dbg.info("bootstrap", "start");
72
+ const ctx = await dbg.timed("probe", "probe", 3000, () => probe(this.options.source));
73
+ dbg.info("probe",
74
+ `container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} ` +
75
+ `audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`,
76
+ );
70
77
  this.diag.recordProbe(ctx);
71
78
  this.mediaContext = ctx;
72
79
 
@@ -99,6 +106,10 @@ export class UnifiedPlayer {
99
106
  const decision = this.options.initialStrategy
100
107
  ? buildInitialDecision(this.options.initialStrategy, ctx)
101
108
  : classify(ctx);
109
+ dbg.info("classify",
110
+ `strategy=${decision.strategy} class=${decision.class} reason="${decision.reason}"` +
111
+ (decision.fallbackChain ? ` fallback=${decision.fallbackChain.join("→")}` : ""),
112
+ );
102
113
  this.classification = decision;
103
114
  this.diag.recordClassification(decision);
104
115
 
@@ -137,6 +148,16 @@ export class UnifiedPlayer {
137
148
  this.startTimeupdateLoop();
138
149
  this.options.target.addEventListener("ended", () => this.emitter.emit("ended", undefined));
139
150
  this.emitter.emitSticky("ready", undefined);
151
+ const bootstrapElapsed = performance.now() - bootstrapStart;
152
+ dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
153
+ if (bootstrapElapsed > 5000) {
154
+ // eslint-disable-next-line no-console
155
+ console.warn(
156
+ "[avbridge:bootstrap]",
157
+ `total bootstrap time ${bootstrapElapsed.toFixed(0)}ms — unusually slow. ` +
158
+ `Enable globalThis.AVBRIDGE_DEBUG for a per-phase breakdown.`,
159
+ );
160
+ }
140
161
  } catch (err) {
141
162
  const e = err instanceof Error ? err : new Error(String(err));
142
163
  this.diag.recordError(e);
package/src/probe/avi.ts CHANGED
@@ -177,7 +177,9 @@ function ffmpegToAvbridgeVideo(name: string): VideoCodec {
177
177
  case "mpeg2video": return "mpeg2";
178
178
  case "mpeg1video": return "mpeg1";
179
179
  case "theora": return "theora";
180
- case "rv30":
180
+ case "rv10": return "rv10";
181
+ case "rv20": return "rv20";
182
+ case "rv30": return "rv30";
181
183
  case "rv40": return "rv40";
182
184
  default: return name as VideoCodec;
183
185
  }
@@ -198,6 +200,11 @@ function ffmpegToAvbridgeAudio(name: string): AudioCodec {
198
200
  case "wmav2": return "wmav2";
199
201
  case "wmapro": return "wmapro";
200
202
  case "alac": return "alac";
203
+ case "cook": return "cook";
204
+ case "ra_144": return "ra_144";
205
+ case "ra_288": return "ra_288";
206
+ case "sipr": return "sipr";
207
+ case "atrac3": return "atrac3";
201
208
  default: return name as AudioCodec;
202
209
  }
203
210
  }
@@ -21,12 +21,16 @@ const MEDIABUNNY_CONTAINERS = new Set<ContainerKind>([
21
21
  *
22
22
  * Routing:
23
23
  * 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.
24
- * 2. If the container is one mediabunny supports → mediabunny. If mediabunny
25
- * rejects, surface the real error rather than blindly falling through to
26
- * libav (which would mask the real failure with a confusing libav error).
27
- * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) libav.js, lazy-loaded.
28
- * `unknown` is included so genuinely unfamiliar files at least get a shot
29
- * at the broader libav demuxer set.
24
+ * 2. If the container is one mediabunny supports → try mediabunny first
25
+ * (fast path it's a single pass of WASM-free JS parsing). If mediabunny
26
+ * throws (e.g. an assertion on an unsupported sample entry like `mp4v`
27
+ * for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to
28
+ * libav.js which handles the long tail of codecs mediabunny doesn't.
29
+ * The combined-error case surfaces *both* failures so the user sees
30
+ * which path each step took.
31
+ * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.
32
+ * mediabunny can't read those containers at all, so there's no fast path
33
+ * to try.
30
34
  */
31
35
  export async function probe(source: MediaInput): Promise<MediaContext> {
32
36
  const normalized = await normalizeSource(source);
@@ -35,10 +39,27 @@ export async function probe(source: MediaInput): Promise<MediaContext> {
35
39
  if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
36
40
  try {
37
41
  return await probeWithMediabunny(normalized, sniffed);
38
- } catch (err) {
39
- throw new Error(
40
- `mediabunny failed to probe a ${sniffed} file: ${(err as Error).message}`,
42
+ } catch (mediabunnyErr) {
43
+ // mediabunny rejected the file. Before giving up, try libav — it can
44
+ // demux a much wider range of codec combinations in ISOBMFF/MKV/etc.
45
+ // than mediabunny's pure-JS parser (e.g. mp4v, wmv3-in-asf, flac in
46
+ // an MP4 container). This is "escalation", not "masking": if libav
47
+ // also fails we surface both errors below.
48
+ // eslint-disable-next-line no-console
49
+ console.warn(
50
+ `[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,
51
+ (mediabunnyErr as Error).message,
41
52
  );
53
+ try {
54
+ const { probeWithLibav } = await import("./avi.js");
55
+ return await probeWithLibav(normalized, sniffed);
56
+ } catch (libavErr) {
57
+ const mbMsg = (mediabunnyErr as Error).message || String(mediabunnyErr);
58
+ const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);
59
+ throw new Error(
60
+ `failed to probe ${sniffed} file. mediabunny: ${mbMsg}. libav fallback: ${lvMsg}.`,
61
+ );
62
+ }
42
63
  }
43
64
  }
44
65
 
@@ -172,9 +172,31 @@ export class AudioOutput implements ClockSource {
172
172
  node.connect(this.gain);
173
173
 
174
174
  // Convert media time → ctx time using the anchor.
175
- const ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
176
- const safeStart = Math.max(ctxStart, this.ctx.currentTime);
177
- node.start(safeStart);
175
+ let ctxStart = this.ctxTimeAtAnchor + (this.mediaTimeOfNext - this.mediaTimeOfAnchor);
176
+
177
+ // When the decoder is slower than realtime, `ctxStart` falls into
178
+ // the past (ctx.currentTime has already passed it). Clamping each
179
+ // sample to `ctx.currentTime` individually (the old behavior)
180
+ // caused every stale sample in a burst to start at *the same
181
+ // instant*, stacking them on top of each other — the audible
182
+ // symptom was a series of clicks / a chord of stuttering cook
183
+ // packets.
184
+ //
185
+ // Correct behavior: when the first sample of a burst is behind,
186
+ // *rebase the anchor forward* so ctxStart = ctx.currentTime now.
187
+ // Subsequent samples in the same burst then schedule at
188
+ // ctxStart + offset as usual, laying out sequentially on the
189
+ // timeline instead of piling up. The downside is a visible jump
190
+ // in the audio clock — but the alternative was silent corruption.
191
+ // `now()` readers (the video renderer) just see the clock step
192
+ // forward and drop any frames older than the new time.
193
+ if (ctxStart < this.ctx.currentTime) {
194
+ this.ctxTimeAtAnchor = this.ctx.currentTime;
195
+ this.mediaTimeOfAnchor = this.mediaTimeOfNext;
196
+ ctxStart = this.ctx.currentTime;
197
+ }
198
+
199
+ node.start(ctxStart);
178
200
 
179
201
  this.mediaTimeOfNext += frameCount / sampleRate;
180
202
  this.framesScheduled++;