avbridge 2.2.0 → 2.3.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 (121) hide show
  1. package/CHANGELOG.md +125 -1
  2. package/NOTICE.md +2 -2
  3. package/README.md +100 -74
  4. package/THIRD_PARTY_LICENSES.md +2 -2
  5. package/dist/avi-2JPBSHGA.js +183 -0
  6. package/dist/avi-2JPBSHGA.js.map +1 -0
  7. package/dist/avi-F6WZJK5T.cjs +185 -0
  8. package/dist/avi-F6WZJK5T.cjs.map +1 -0
  9. package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
  10. package/dist/avi-NJXAXUXK.js.map +1 -0
  11. package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
  12. package/dist/avi-W6L3BTWU.cjs.map +1 -0
  13. package/dist/{chunk-ILKDNBSE.js → chunk-2PGRFCWB.js} +59 -10
  14. package/dist/chunk-2PGRFCWB.js.map +1 -0
  15. package/dist/chunk-5YAWWKA3.js +18 -0
  16. package/dist/chunk-5YAWWKA3.js.map +1 -0
  17. package/dist/chunk-6UUT4BEA.cjs +219 -0
  18. package/dist/chunk-6UUT4BEA.cjs.map +1 -0
  19. package/dist/{chunk-OE66B34H.cjs → chunk-7RGG6ME7.cjs} +562 -94
  20. package/dist/chunk-7RGG6ME7.cjs.map +1 -0
  21. package/dist/{chunk-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
  22. package/dist/chunk-DCSOQH2N.js.map +1 -0
  23. package/dist/chunk-F3LQJKXK.cjs +20 -0
  24. package/dist/chunk-F3LQJKXK.cjs.map +1 -0
  25. package/dist/chunk-IAYKFGFG.js +200 -0
  26. package/dist/chunk-IAYKFGFG.js.map +1 -0
  27. package/dist/chunk-NNVOHKXJ.cjs +204 -0
  28. package/dist/chunk-NNVOHKXJ.cjs.map +1 -0
  29. package/dist/{chunk-C5VA5U5O.js → chunk-NV7ILLWH.js} +556 -92
  30. package/dist/chunk-NV7ILLWH.js.map +1 -0
  31. package/dist/{chunk-HZLQNKFN.cjs → chunk-QQXBPW72.js} +54 -15
  32. package/dist/chunk-QQXBPW72.js.map +1 -0
  33. package/dist/chunk-XKPSTC34.cjs +210 -0
  34. package/dist/chunk-XKPSTC34.cjs.map +1 -0
  35. package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
  36. package/dist/chunk-Z33SBWL5.cjs.map +1 -0
  37. package/dist/element-browser.js +631 -103
  38. package/dist/element-browser.js.map +1 -1
  39. package/dist/element.cjs +4 -4
  40. package/dist/element.d.cts +1 -1
  41. package/dist/element.d.ts +1 -1
  42. package/dist/element.js +3 -3
  43. package/dist/index.cjs +174 -26
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.d.cts +48 -4
  46. package/dist/index.d.ts +48 -4
  47. package/dist/index.js +93 -12
  48. package/dist/index.js.map +1 -1
  49. package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
  50. package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-http-reader-AZLE7YFS.cjs.map} +1 -1
  51. package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
  52. package/dist/{libav-http-reader-NQJVY273.js.map → libav-http-reader-WXG3Z7AI.js.map} +1 -1
  53. package/dist/{player-DUyvltvy.d.cts → player-B6WB74RD.d.cts} +63 -3
  54. package/dist/{player-DUyvltvy.d.ts → player-B6WB74RD.d.ts} +63 -3
  55. package/dist/player.cjs +5500 -0
  56. package/dist/player.cjs.map +1 -0
  57. package/dist/player.d.cts +649 -0
  58. package/dist/player.d.ts +649 -0
  59. package/dist/player.js +5498 -0
  60. package/dist/player.js.map +1 -0
  61. package/dist/source-73CAH6HW.cjs +28 -0
  62. package/dist/{source-CN43EI7Z.cjs.map → source-73CAH6HW.cjs.map} +1 -1
  63. package/dist/source-F656KYYV.js +3 -0
  64. package/dist/{source-FFZ7TW2B.js.map → source-F656KYYV.js.map} +1 -1
  65. package/dist/source-QJR3OHTW.js +3 -0
  66. package/dist/source-QJR3OHTW.js.map +1 -0
  67. package/dist/source-VB74JQ7Z.cjs +28 -0
  68. package/dist/source-VB74JQ7Z.cjs.map +1 -0
  69. package/dist/variant-routing-434STYAB.js +3 -0
  70. package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
  71. package/dist/variant-routing-HONNAA6R.cjs +12 -0
  72. package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
  73. package/package.json +9 -1
  74. package/src/classify/rules.ts +27 -5
  75. package/src/convert/remux.ts +8 -0
  76. package/src/convert/transcode.ts +41 -8
  77. package/src/element/avbridge-player.ts +845 -0
  78. package/src/element/player-icons.ts +25 -0
  79. package/src/element/player-styles.ts +472 -0
  80. package/src/errors.ts +47 -0
  81. package/src/index.ts +23 -0
  82. package/src/player-element.ts +18 -0
  83. package/src/player.ts +127 -27
  84. package/src/plugins/builtin.ts +2 -2
  85. package/src/probe/avi.ts +4 -0
  86. package/src/probe/index.ts +40 -10
  87. package/src/strategies/fallback/audio-output.ts +31 -0
  88. package/src/strategies/fallback/decoder.ts +83 -2
  89. package/src/strategies/fallback/index.ts +34 -1
  90. package/src/strategies/fallback/variant-routing.ts +7 -13
  91. package/src/strategies/fallback/video-renderer.ts +129 -33
  92. package/src/strategies/hybrid/decoder.ts +131 -20
  93. package/src/strategies/hybrid/index.ts +36 -2
  94. package/src/strategies/remux/index.ts +13 -1
  95. package/src/strategies/remux/mse.ts +12 -2
  96. package/src/strategies/remux/pipeline.ts +6 -0
  97. package/src/subtitles/index.ts +7 -3
  98. package/src/types.ts +53 -1
  99. package/src/util/libav-http-reader.ts +5 -1
  100. package/src/util/source.ts +28 -8
  101. package/src/util/transport.ts +26 -0
  102. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
  103. package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
  104. package/dist/avi-6SJLWIWW.cjs.map +0 -1
  105. package/dist/avi-GCGM7OJI.js.map +0 -1
  106. package/dist/chunk-C5VA5U5O.js.map +0 -1
  107. package/dist/chunk-HZLQNKFN.cjs.map +0 -1
  108. package/dist/chunk-ILKDNBSE.js.map +0 -1
  109. package/dist/chunk-J5MCMN3S.js +0 -27
  110. package/dist/chunk-J5MCMN3S.js.map +0 -1
  111. package/dist/chunk-L4NPOJ36.cjs.map +0 -1
  112. package/dist/chunk-NZU7W256.cjs +0 -29
  113. package/dist/chunk-NZU7W256.cjs.map +0 -1
  114. package/dist/chunk-OE66B34H.cjs.map +0 -1
  115. package/dist/chunk-WD2ZNQA7.js.map +0 -1
  116. package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
  117. package/dist/libav-http-reader-NQJVY273.js +0 -3
  118. package/dist/source-CN43EI7Z.cjs +0 -28
  119. package/dist/source-FFZ7TW2B.js +0 -3
  120. package/dist/variant-routing-GOHB2RZN.cjs +0 -12
  121. package/dist/variant-routing-JOBWXYKD.js +0 -3
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var chunk6UUT4BEA_cjs = require('./chunk-6UUT4BEA.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "isInMemorySource", {
8
+ enumerable: true,
9
+ get: function () { return chunk6UUT4BEA_cjs.isInMemorySource; }
10
+ });
11
+ Object.defineProperty(exports, "normalizeSource", {
12
+ enumerable: true,
13
+ get: function () { return chunk6UUT4BEA_cjs.normalizeSource; }
14
+ });
15
+ Object.defineProperty(exports, "sniffContainer", {
16
+ enumerable: true,
17
+ get: function () { return chunk6UUT4BEA_cjs.sniffContainer; }
18
+ });
19
+ Object.defineProperty(exports, "sniffContainerFromBytes", {
20
+ enumerable: true,
21
+ get: function () { return chunk6UUT4BEA_cjs.sniffContainerFromBytes; }
22
+ });
23
+ Object.defineProperty(exports, "sniffNormalizedSource", {
24
+ enumerable: true,
25
+ get: function () { return chunk6UUT4BEA_cjs.sniffNormalizedSource; }
26
+ });
27
+ //# sourceMappingURL=source-73CAH6HW.cjs.map
28
+ //# sourceMappingURL=source-73CAH6HW.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"source-CN43EI7Z.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-73CAH6HW.cjs"}
@@ -0,0 +1,3 @@
1
+ export { isInMemorySource, normalizeSource, sniffContainer, sniffContainerFromBytes, sniffNormalizedSource } from './chunk-QQXBPW72.js';
2
+ //# sourceMappingURL=source-F656KYYV.js.map
3
+ //# sourceMappingURL=source-F656KYYV.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"source-FFZ7TW2B.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-F656KYYV.js"}
@@ -0,0 +1,3 @@
1
+ export { isInMemorySource, normalizeSource, sniffContainer, sniffContainerFromBytes, sniffNormalizedSource } from './chunk-2PGRFCWB.js';
2
+ //# sourceMappingURL=source-QJR3OHTW.js.map
3
+ //# sourceMappingURL=source-QJR3OHTW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-QJR3OHTW.js"}
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var chunkXKPSTC34_cjs = require('./chunk-XKPSTC34.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "isInMemorySource", {
8
+ enumerable: true,
9
+ get: function () { return chunkXKPSTC34_cjs.isInMemorySource; }
10
+ });
11
+ Object.defineProperty(exports, "normalizeSource", {
12
+ enumerable: true,
13
+ get: function () { return chunkXKPSTC34_cjs.normalizeSource; }
14
+ });
15
+ Object.defineProperty(exports, "sniffContainer", {
16
+ enumerable: true,
17
+ get: function () { return chunkXKPSTC34_cjs.sniffContainer; }
18
+ });
19
+ Object.defineProperty(exports, "sniffContainerFromBytes", {
20
+ enumerable: true,
21
+ get: function () { return chunkXKPSTC34_cjs.sniffContainerFromBytes; }
22
+ });
23
+ Object.defineProperty(exports, "sniffNormalizedSource", {
24
+ enumerable: true,
25
+ get: function () { return chunkXKPSTC34_cjs.sniffNormalizedSource; }
26
+ });
27
+ //# sourceMappingURL=source-VB74JQ7Z.cjs.map
28
+ //# sourceMappingURL=source-VB74JQ7Z.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"source-VB74JQ7Z.cjs"}
@@ -0,0 +1,3 @@
1
+ export { pickLibavVariant } from './chunk-5YAWWKA3.js';
2
+ //# sourceMappingURL=variant-routing-434STYAB.js.map
3
+ //# sourceMappingURL=variant-routing-434STYAB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"variant-routing-JOBWXYKD.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"variant-routing-434STYAB.js"}
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ var chunkF3LQJKXK_cjs = require('./chunk-F3LQJKXK.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "pickLibavVariant", {
8
+ enumerable: true,
9
+ get: function () { return chunkF3LQJKXK_cjs.pickLibavVariant; }
10
+ });
11
+ //# sourceMappingURL=variant-routing-HONNAA6R.cjs.map
12
+ //# sourceMappingURL=variant-routing-HONNAA6R.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"variant-routing-GOHB2RZN.cjs"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"variant-routing-HONNAA6R.cjs"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "avbridge",
3
- "version": "2.2.0",
3
+ "version": "2.3.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",
@@ -35,6 +35,8 @@
35
35
  "sideEffects": [
36
36
  "./dist/element.js",
37
37
  "./dist/element.cjs",
38
+ "./dist/player.js",
39
+ "./dist/player.cjs",
38
40
  "./dist/element-browser.js"
39
41
  ],
40
42
  "main": "./dist/index.cjs",
@@ -51,6 +53,11 @@
51
53
  "import": "./dist/element.js",
52
54
  "require": "./dist/element.cjs"
53
55
  },
56
+ "./player": {
57
+ "types": "./dist/player.d.ts",
58
+ "import": "./dist/player.js",
59
+ "require": "./dist/player.cjs"
60
+ },
54
61
  "./element-browser": {
55
62
  "types": "./dist/element.d.ts",
56
63
  "import": "./dist/element-browser.js"
@@ -83,6 +90,7 @@
83
90
  "test:playback": "node scripts/playback-test.mjs",
84
91
  "test:convert": "node scripts/convert-test.mjs",
85
92
  "test:element": "node scripts/element-test.mjs",
93
+ "test:player-controls": "node scripts/player-controls-test.mjs",
86
94
  "test:url-streaming": "node scripts/url-streaming-test.mjs",
87
95
  "fixtures": "node scripts/generate-fixtures.mjs",
88
96
  "audit:bundle": "node scripts/bundle-audit.mjs"
@@ -13,8 +13,9 @@ import { mp4MimeFor, mseSupports } from "../util/codec-strings.js";
13
13
  * Codecs we know `<video>` and MSE support across modern desktop + Android.
14
14
  * The decision to remux instead of decode hinges on this list.
15
15
  */
16
- const NATIVE_VIDEO_CODECS = new Set<VideoCodec>(["h264", "h265", "vp8", "vp9", "av1"]);
17
- const NATIVE_AUDIO_CODECS = new Set<AudioCodec>([
16
+ /** Codecs the browser can decode natively (also the set WebCodecs can transcode). */
17
+ export const NATIVE_VIDEO_CODECS = new Set<VideoCodec>(["h264", "h265", "vp8", "vp9", "av1"]);
18
+ export const NATIVE_AUDIO_CODECS = new Set<AudioCodec>([
18
19
  "aac",
19
20
  "mp3",
20
21
  "opus",
@@ -25,14 +26,15 @@ const NATIVE_AUDIO_CODECS = new Set<AudioCodec>([
25
26
  /**
26
27
  * Codecs no major browser plays, period. These force the WASM fallback.
27
28
  */
28
- const FALLBACK_VIDEO_CODECS = new Set<VideoCodec>([
29
+ export const FALLBACK_VIDEO_CODECS = new Set<VideoCodec>([
29
30
  "wmv3", "vc1", "mpeg4",
30
31
  "rv10", "rv20", "rv30", "rv40",
31
32
  "mpeg2", "mpeg1", "theora",
32
33
  ]);
33
- const FALLBACK_AUDIO_CODECS = new Set<AudioCodec>([
34
+ export const FALLBACK_AUDIO_CODECS = new Set<AudioCodec>([
34
35
  "wmav2", "wmapro", "ac3", "eac3",
35
36
  "cook", "ra_144", "ra_288", "sipr", "atrac3",
37
+ "dts", "truehd",
36
38
  ]);
37
39
 
38
40
  /**
@@ -120,7 +122,27 @@ export function classifyContext(ctx: MediaContext): Classification {
120
122
  reason: `video codec "${video.codec}" has no browser decoder; WASM fallback required`,
121
123
  };
122
124
  }
123
- if (audio && FALLBACK_AUDIO_CODECS.has(audio.codec)) {
125
+ // Audio codec needs WASM decode — either it's in the known fallback set
126
+ // or it's unrecognized ("unknown" / not in native set). Unknown codecs
127
+ // definitely can't play natively, so they get the same treatment.
128
+ const audioNeedsFallback = audio && (
129
+ FALLBACK_AUDIO_CODECS.has(audio.codec) ||
130
+ !NATIVE_AUDIO_CODECS.has(audio.codec)
131
+ );
132
+ if (audioNeedsFallback) {
133
+ // If the VIDEO codec is native, prefer hybrid (WebCodecs hardware video
134
+ // decode + libav software audio decode) over full WASM fallback. This is
135
+ // critical for Blu-ray MKVs: H.264 1080p in WASM is unwatchably slow,
136
+ // but WebCodecs decodes it at full speed while libav handles the DTS/AC3
137
+ // audio in software.
138
+ if (NATIVE_VIDEO_CODECS.has(video.codec) && webCodecsAvailable()) {
139
+ return {
140
+ class: "HYBRID_CANDIDATE",
141
+ strategy: "hybrid",
142
+ reason: `video "${video.codec}" is hardware-decodable via WebCodecs; audio "${audio.codec}" decoded in software by libav`,
143
+ fallbackChain: ["fallback"],
144
+ };
145
+ }
124
146
  return {
125
147
  class: "FALLBACK_REQUIRED",
126
148
  strategy: "fallback",
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import { probe } from "../probe/index.js";
14
+ import { isAnnexB, annexBToAvcc } from "../strategies/remux/annexb.js";
14
15
  import {
15
16
  avbridgeVideoToMediabunny,
16
17
  avbridgeAudioToMediabunny,
@@ -304,6 +305,13 @@ async function doLibavRemux(
304
305
  return ts;
305
306
  }, videoTimeBase);
306
307
 
308
+ // libav demuxes AVI/ASF/FLV H.264 as Annex B (start-code framed),
309
+ // but mediabunny's fMP4 muxer expects AVCC (length-prefixed). Convert
310
+ // on the fly. The check is cheap: isAnnexB reads 4 bytes at the head.
311
+ if (videoTrackInfo && (videoTrackInfo.codec === "h264" || videoTrackInfo.codec === "h265") && isAnnexB(pkt.data)) {
312
+ pkt.data = annexBToAvcc(pkt.data);
313
+ }
314
+
307
315
  const mbPacket = libavPacketToMediAbunny(mb, pkt);
308
316
  await videoSource.add(
309
317
  mbPacket,
@@ -79,7 +79,7 @@ async function attemptTranscode(
79
79
  audioCodec: OutputAudioCodec,
80
80
  quality: TranscodeQuality,
81
81
  options: TranscodeOptions,
82
- ): Promise<ArrayBuffer> {
82
+ ): Promise<ArrayBuffer | null> {
83
83
  const mb = await import("mediabunny");
84
84
 
85
85
  const input = new mb.Input({
@@ -87,10 +87,25 @@ async function attemptTranscode(
87
87
  formats: mb.ALL_FORMATS,
88
88
  });
89
89
 
90
- const target = new mb.BufferTarget();
90
+ // When outputStream is provided, pipe chunks directly to the stream
91
+ // instead of accumulating into a buffer. This keeps memory usage flat
92
+ // regardless of file size.
93
+ let bytesWritten = 0;
94
+ const useStream = !!options.outputStream;
95
+ const bufferTarget = useStream ? null : new mb.BufferTarget();
96
+ const streamTarget = useStream
97
+ ? new mb.StreamTarget(new WritableStream({
98
+ write(chunk: { type: string; data: Uint8Array; position: number }) {
99
+ bytesWritten += chunk.data.byteLength;
100
+ const writer = options.outputStream!.getWriter();
101
+ return writer.write(chunk.data).then(() => writer.releaseLock());
102
+ },
103
+ }))
104
+ : null;
105
+
91
106
  const output = new mb.Output({
92
107
  format: createOutputFormat(mb, outputFormat),
93
- target,
108
+ target: (streamTarget ?? bufferTarget)!,
94
109
  });
95
110
 
96
111
  // Build mediabunny ConversionVideoOptions
@@ -160,10 +175,13 @@ async function attemptTranscode(
160
175
  }
161
176
  }
162
177
 
163
- if (!target.buffer) {
178
+ if (useStream) {
179
+ return null; // data already written to outputStream
180
+ }
181
+ if (!bufferTarget!.buffer) {
164
182
  throw new Error("Transcode failed: mediabunny produced no output buffer.");
165
183
  }
166
- return target.buffer;
184
+ return bufferTarget!.buffer;
167
185
  }
168
186
 
169
187
  /**
@@ -234,14 +252,29 @@ async function doTranscode(
234
252
  }
235
253
  }
236
254
 
255
+ const mimeType = mimeForFormat(outputFormat);
256
+ const filename = generateFilename(ctx.name, outputFormat);
257
+
258
+ if (options.outputStream) {
259
+ // Streaming mode — data already written to the stream. Return empty blob.
260
+ options.onProgress?.({ percent: 100, bytesWritten: 0 });
261
+ return {
262
+ blob: new Blob([], { type: mimeType }),
263
+ mimeType,
264
+ container: outputFormat,
265
+ videoCodec: options.dropVideo ? undefined : videoCodec,
266
+ audioCodec: options.dropAudio ? undefined : audioCodec,
267
+ duration: ctx.duration,
268
+ filename,
269
+ ...(notes.length > 0 ? { notes } : {}),
270
+ };
271
+ }
272
+
237
273
  if (!buffer) {
238
274
  throw new Error("Transcode failed: no buffer produced (this should be unreachable).");
239
275
  }
240
276
 
241
- const mimeType = mimeForFormat(outputFormat);
242
277
  const blob = new Blob([buffer], { type: mimeType });
243
- const filename = generateFilename(ctx.name, outputFormat);
244
-
245
278
  options.onProgress?.({ percent: 100, bytesWritten: blob.size });
246
279
 
247
280
  return {