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.
- package/CHANGELOG.md +125 -1
- package/NOTICE.md +2 -2
- package/README.md +100 -74
- package/THIRD_PARTY_LICENSES.md +2 -2
- package/dist/avi-2JPBSHGA.js +183 -0
- package/dist/avi-2JPBSHGA.js.map +1 -0
- package/dist/avi-F6WZJK5T.cjs +185 -0
- package/dist/avi-F6WZJK5T.cjs.map +1 -0
- package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
- package/dist/avi-NJXAXUXK.js.map +1 -0
- package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
- package/dist/avi-W6L3BTWU.cjs.map +1 -0
- package/dist/{chunk-ILKDNBSE.js → chunk-2PGRFCWB.js} +59 -10
- package/dist/chunk-2PGRFCWB.js.map +1 -0
- package/dist/chunk-5YAWWKA3.js +18 -0
- package/dist/chunk-5YAWWKA3.js.map +1 -0
- package/dist/chunk-6UUT4BEA.cjs +219 -0
- package/dist/chunk-6UUT4BEA.cjs.map +1 -0
- package/dist/{chunk-OE66B34H.cjs → chunk-7RGG6ME7.cjs} +562 -94
- package/dist/chunk-7RGG6ME7.cjs.map +1 -0
- package/dist/{chunk-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
- package/dist/chunk-DCSOQH2N.js.map +1 -0
- package/dist/chunk-F3LQJKXK.cjs +20 -0
- package/dist/chunk-F3LQJKXK.cjs.map +1 -0
- package/dist/chunk-IAYKFGFG.js +200 -0
- package/dist/chunk-IAYKFGFG.js.map +1 -0
- package/dist/chunk-NNVOHKXJ.cjs +204 -0
- package/dist/chunk-NNVOHKXJ.cjs.map +1 -0
- package/dist/{chunk-C5VA5U5O.js → chunk-NV7ILLWH.js} +556 -92
- package/dist/chunk-NV7ILLWH.js.map +1 -0
- package/dist/{chunk-HZLQNKFN.cjs → chunk-QQXBPW72.js} +54 -15
- package/dist/chunk-QQXBPW72.js.map +1 -0
- package/dist/chunk-XKPSTC34.cjs +210 -0
- package/dist/chunk-XKPSTC34.cjs.map +1 -0
- package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
- package/dist/chunk-Z33SBWL5.cjs.map +1 -0
- package/dist/element-browser.js +631 -103
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +4 -4
- package/dist/element.d.cts +1 -1
- package/dist/element.d.ts +1 -1
- package/dist/element.js +3 -3
- package/dist/index.cjs +174 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -4
- package/dist/index.d.ts +48 -4
- package/dist/index.js +93 -12
- package/dist/index.js.map +1 -1
- package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
- package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-http-reader-AZLE7YFS.cjs.map} +1 -1
- package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
- package/dist/{libav-http-reader-NQJVY273.js.map → libav-http-reader-WXG3Z7AI.js.map} +1 -1
- package/dist/{player-DUyvltvy.d.cts → player-B6WB74RD.d.cts} +63 -3
- package/dist/{player-DUyvltvy.d.ts → player-B6WB74RD.d.ts} +63 -3
- package/dist/player.cjs +5500 -0
- package/dist/player.cjs.map +1 -0
- package/dist/player.d.cts +649 -0
- package/dist/player.d.ts +649 -0
- package/dist/player.js +5498 -0
- package/dist/player.js.map +1 -0
- package/dist/source-73CAH6HW.cjs +28 -0
- package/dist/{source-CN43EI7Z.cjs.map → source-73CAH6HW.cjs.map} +1 -1
- package/dist/source-F656KYYV.js +3 -0
- package/dist/{source-FFZ7TW2B.js.map → source-F656KYYV.js.map} +1 -1
- package/dist/source-QJR3OHTW.js +3 -0
- package/dist/source-QJR3OHTW.js.map +1 -0
- package/dist/source-VB74JQ7Z.cjs +28 -0
- package/dist/source-VB74JQ7Z.cjs.map +1 -0
- package/dist/variant-routing-434STYAB.js +3 -0
- package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
- package/dist/variant-routing-HONNAA6R.cjs +12 -0
- package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
- package/package.json +9 -1
- package/src/classify/rules.ts +27 -5
- package/src/convert/remux.ts +8 -0
- package/src/convert/transcode.ts +41 -8
- package/src/element/avbridge-player.ts +845 -0
- package/src/element/player-icons.ts +25 -0
- package/src/element/player-styles.ts +472 -0
- package/src/errors.ts +47 -0
- package/src/index.ts +23 -0
- package/src/player-element.ts +18 -0
- package/src/player.ts +127 -27
- package/src/plugins/builtin.ts +2 -2
- package/src/probe/avi.ts +4 -0
- package/src/probe/index.ts +40 -10
- package/src/strategies/fallback/audio-output.ts +31 -0
- package/src/strategies/fallback/decoder.ts +83 -2
- package/src/strategies/fallback/index.ts +34 -1
- package/src/strategies/fallback/variant-routing.ts +7 -13
- package/src/strategies/fallback/video-renderer.ts +129 -33
- package/src/strategies/hybrid/decoder.ts +131 -20
- package/src/strategies/hybrid/index.ts +36 -2
- package/src/strategies/remux/index.ts +13 -1
- package/src/strategies/remux/mse.ts +12 -2
- package/src/strategies/remux/pipeline.ts +6 -0
- package/src/subtitles/index.ts +7 -3
- package/src/types.ts +53 -1
- package/src/util/libav-http-reader.ts +5 -1
- package/src/util/source.ts +28 -8
- package/src/util/transport.ts +26 -0
- package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
- package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
- package/dist/avi-6SJLWIWW.cjs.map +0 -1
- package/dist/avi-GCGM7OJI.js.map +0 -1
- package/dist/chunk-C5VA5U5O.js.map +0 -1
- package/dist/chunk-HZLQNKFN.cjs.map +0 -1
- package/dist/chunk-ILKDNBSE.js.map +0 -1
- package/dist/chunk-J5MCMN3S.js +0 -27
- package/dist/chunk-J5MCMN3S.js.map +0 -1
- package/dist/chunk-L4NPOJ36.cjs.map +0 -1
- package/dist/chunk-NZU7W256.cjs +0 -29
- package/dist/chunk-NZU7W256.cjs.map +0 -1
- package/dist/chunk-OE66B34H.cjs.map +0 -1
- package/dist/chunk-WD2ZNQA7.js.map +0 -1
- package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
- package/dist/libav-http-reader-NQJVY273.js +0 -3
- package/dist/source-CN43EI7Z.cjs +0 -28
- package/dist/source-FFZ7TW2B.js +0 -3
- package/dist/variant-routing-GOHB2RZN.cjs +0 -12
- 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-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"source-73CAH6HW.cjs"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"source-
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"source-F656KYYV.js"}
|
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[],"names":[],"mappings":"","file":"variant-routing-
|
|
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-
|
|
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.
|
|
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"
|
package/src/classify/rules.ts
CHANGED
|
@@ -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
|
-
|
|
17
|
-
const
|
|
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
|
-
|
|
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",
|
package/src/convert/remux.ts
CHANGED
|
@@ -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,
|
package/src/convert/transcode.ts
CHANGED
|
@@ -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
|
-
|
|
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 (
|
|
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
|
|
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 {
|