avbridge 2.3.0 → 2.5.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 +73 -0
- package/dist/{chunk-6UUT4BEA.cjs → chunk-2IJ66NTD.cjs} +13 -20
- package/dist/chunk-2IJ66NTD.cjs.map +1 -0
- package/dist/{chunk-XKPSTC34.cjs → chunk-2XW2O3YI.cjs} +5 -20
- package/dist/chunk-2XW2O3YI.cjs.map +1 -0
- package/dist/chunk-5KVLE6YI.js +167 -0
- package/dist/chunk-5KVLE6YI.js.map +1 -0
- package/dist/{chunk-2PGRFCWB.js → chunk-CPJLFFCC.js} +8 -18
- package/dist/chunk-CPJLFFCC.js.map +1 -0
- package/dist/chunk-CPZ7PXAM.cjs +240 -0
- package/dist/chunk-CPZ7PXAM.cjs.map +1 -0
- package/dist/{chunk-QQXBPW72.js → chunk-E76AMWI4.js} +4 -18
- package/dist/chunk-E76AMWI4.js.map +1 -0
- package/dist/{chunk-NV7ILLWH.js → chunk-KY2GPCT7.js} +347 -665
- package/dist/chunk-KY2GPCT7.js.map +1 -0
- package/dist/chunk-LUFA47FP.js +19 -0
- package/dist/chunk-LUFA47FP.js.map +1 -0
- package/dist/chunk-Q2VUO52Z.cjs +374 -0
- package/dist/chunk-Q2VUO52Z.cjs.map +1 -0
- package/dist/chunk-QDJLQR53.cjs +22 -0
- package/dist/chunk-QDJLQR53.cjs.map +1 -0
- package/dist/chunk-S4WAZC2T.cjs +173 -0
- package/dist/chunk-S4WAZC2T.cjs.map +1 -0
- package/dist/chunk-SMH6IOP2.js +368 -0
- package/dist/chunk-SMH6IOP2.js.map +1 -0
- package/dist/chunk-SR3MPV4D.js +237 -0
- package/dist/chunk-SR3MPV4D.js.map +1 -0
- package/dist/{chunk-7RGG6ME7.cjs → chunk-TBW26OPP.cjs} +365 -688
- package/dist/chunk-TBW26OPP.cjs.map +1 -0
- package/dist/chunk-X2K3GIWE.js +235 -0
- package/dist/chunk-X2K3GIWE.js.map +1 -0
- package/dist/chunk-ZCUXHW55.cjs +242 -0
- package/dist/chunk-ZCUXHW55.cjs.map +1 -0
- package/dist/element-browser.js +799 -493
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +58 -4
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +38 -0
- package/dist/element.d.ts +38 -0
- package/dist/element.js +57 -3
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +523 -393
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +494 -366
- package/dist/index.js.map +1 -1
- package/dist/libav-demux-H2GS46GH.cjs +27 -0
- package/dist/libav-demux-H2GS46GH.cjs.map +1 -0
- package/dist/libav-demux-OWZ4T2YW.js +6 -0
- package/dist/libav-demux-OWZ4T2YW.js.map +1 -0
- package/dist/{libav-import-GST2AMPL.cjs → libav-import-2ZVKV2E7.cjs} +2 -2
- package/dist/{libav-import-GST2AMPL.cjs.map → libav-import-2ZVKV2E7.cjs.map} +1 -1
- package/dist/{libav-import-2JURFHEW.js → libav-import-6MGLCXVQ.js} +2 -2
- package/dist/{libav-import-2JURFHEW.js.map → libav-import-6MGLCXVQ.js.map} +1 -1
- package/dist/player.cjs +601 -470
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +50 -0
- package/dist/player.d.ts +50 -0
- package/dist/player.js +580 -449
- package/dist/player.js.map +1 -1
- package/dist/remux-OBSMIENG.cjs +35 -0
- package/dist/remux-OBSMIENG.cjs.map +1 -0
- package/dist/remux-WBYIZBBX.js +10 -0
- package/dist/remux-WBYIZBBX.js.map +1 -0
- package/dist/source-4TZ6KMNV.js +4 -0
- package/dist/{source-F656KYYV.js.map → source-4TZ6KMNV.js.map} +1 -1
- package/dist/source-7YLO6E7X.cjs +29 -0
- package/dist/{source-73CAH6HW.cjs.map → source-7YLO6E7X.cjs.map} +1 -1
- package/dist/source-MTX5ELUZ.js +4 -0
- package/dist/{source-QJR3OHTW.js.map → source-MTX5ELUZ.js.map} +1 -1
- package/dist/source-VFLXLOCN.cjs +29 -0
- package/dist/{source-VB74JQ7Z.cjs.map → source-VFLXLOCN.cjs.map} +1 -1
- package/dist/subtitles-4T74JRGT.js +4 -0
- package/dist/subtitles-4T74JRGT.js.map +1 -0
- package/dist/subtitles-QUH4LPI4.cjs +29 -0
- package/dist/subtitles-QUH4LPI4.cjs.map +1 -0
- package/package.json +1 -1
- package/src/convert/remux.ts +1 -35
- package/src/convert/transcode-libav.ts +691 -0
- package/src/convert/transcode.ts +12 -4
- package/src/element/avbridge-player.ts +16 -0
- package/src/element/avbridge-video.ts +54 -0
- package/src/errors.ts +6 -0
- package/src/player.ts +15 -16
- package/src/strategies/fallback/decoder.ts +96 -173
- package/src/strategies/fallback/index.ts +19 -2
- package/src/strategies/fallback/libav-import.ts +9 -1
- package/src/strategies/fallback/video-renderer.ts +107 -0
- package/src/strategies/hybrid/decoder.ts +88 -180
- package/src/strategies/hybrid/index.ts +17 -2
- package/src/strategies/native.ts +6 -3
- package/src/strategies/remux/index.ts +14 -2
- package/src/strategies/remux/pipeline.ts +72 -12
- package/src/subtitles/render.ts +8 -0
- package/src/util/libav-demux.ts +405 -0
- package/dist/chunk-2PGRFCWB.js.map +0 -1
- package/dist/chunk-6UUT4BEA.cjs.map +0 -1
- package/dist/chunk-7RGG6ME7.cjs.map +0 -1
- package/dist/chunk-NV7ILLWH.js.map +0 -1
- package/dist/chunk-QQXBPW72.js.map +0 -1
- package/dist/chunk-XKPSTC34.cjs.map +0 -1
- package/dist/source-73CAH6HW.cjs +0 -28
- package/dist/source-F656KYYV.js +0 -3
- package/dist/source-QJR3OHTW.js +0 -3
- package/dist/source-VB74JQ7Z.cjs +0 -28
|
@@ -1,241 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkS4WAZC2T_cjs = require('./chunk-S4WAZC2T.cjs');
|
|
4
|
+
var chunkZCUXHW55_cjs = require('./chunk-ZCUXHW55.cjs');
|
|
5
|
+
var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
|
|
6
|
+
var chunkCPZ7PXAM_cjs = require('./chunk-CPZ7PXAM.cjs');
|
|
4
7
|
var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
|
|
5
8
|
var chunkF3LQJKXK_cjs = require('./chunk-F3LQJKXK.cjs');
|
|
6
9
|
|
|
7
|
-
// src/probe/mediabunny.ts
|
|
8
|
-
async function probeWithMediabunny(source, sniffedContainer) {
|
|
9
|
-
const mb = await import('mediabunny');
|
|
10
|
-
const input = new mb.Input({
|
|
11
|
-
source: await buildMediabunnySource(mb, source),
|
|
12
|
-
formats: mb.ALL_FORMATS
|
|
13
|
-
});
|
|
14
|
-
const allTracks = await input.getTracks();
|
|
15
|
-
const duration = await safeNumber(() => input.computeDuration());
|
|
16
|
-
const videoTracks = [];
|
|
17
|
-
const audioTracks = [];
|
|
18
|
-
for (const track of allTracks) {
|
|
19
|
-
if (track.isVideoTrack()) {
|
|
20
|
-
const codecParam = await safe(() => track.getCodecParameterString());
|
|
21
|
-
videoTracks.push({
|
|
22
|
-
id: track.id,
|
|
23
|
-
codec: mediabunnyVideoToAvbridge(track.codec),
|
|
24
|
-
width: track.displayWidth ?? track.codedWidth ?? 0,
|
|
25
|
-
height: track.displayHeight ?? track.codedHeight ?? 0,
|
|
26
|
-
codecString: codecParam ?? void 0
|
|
27
|
-
});
|
|
28
|
-
} else if (track.isAudioTrack()) {
|
|
29
|
-
const codecParam = await safe(() => track.getCodecParameterString());
|
|
30
|
-
audioTracks.push({
|
|
31
|
-
id: track.id,
|
|
32
|
-
codec: mediabunnyAudioToAvbridge(track.codec),
|
|
33
|
-
channels: track.numberOfChannels ?? 0,
|
|
34
|
-
sampleRate: track.sampleRate ?? 0,
|
|
35
|
-
language: track.languageCode,
|
|
36
|
-
codecString: codecParam ?? void 0
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
const format = await safe(() => input.getFormat());
|
|
41
|
-
const container = resolveContainer(format?.name, sniffedContainer);
|
|
42
|
-
return {
|
|
43
|
-
source: source.original,
|
|
44
|
-
name: source.name,
|
|
45
|
-
byteLength: source.byteLength,
|
|
46
|
-
container,
|
|
47
|
-
videoTracks,
|
|
48
|
-
audioTracks,
|
|
49
|
-
subtitleTracks: [],
|
|
50
|
-
probedBy: "mediabunny",
|
|
51
|
-
duration
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
async function buildMediabunnySource(mb, source) {
|
|
55
|
-
if (source.kind === "url") {
|
|
56
|
-
return new mb.UrlSource(source.url);
|
|
57
|
-
}
|
|
58
|
-
return new mb.BlobSource(source.blob);
|
|
59
|
-
}
|
|
60
|
-
async function buildMediabunnySourceFromInput(mb, source) {
|
|
61
|
-
if (typeof source === "string") return new mb.UrlSource(source);
|
|
62
|
-
if (source instanceof URL) return new mb.UrlSource(source.toString());
|
|
63
|
-
if (source instanceof Blob) return new mb.BlobSource(source);
|
|
64
|
-
if (source instanceof ArrayBuffer) return new mb.BlobSource(new Blob([source]));
|
|
65
|
-
if (source instanceof Uint8Array) return new mb.BlobSource(new Blob([source]));
|
|
66
|
-
throw new TypeError("unsupported source type for mediabunny");
|
|
67
|
-
}
|
|
68
|
-
function resolveContainer(formatName, sniffed) {
|
|
69
|
-
const name = (formatName ?? "").toLowerCase();
|
|
70
|
-
if (name.includes("matroska") || name.includes("mkv")) return "mkv";
|
|
71
|
-
if (name.includes("webm")) return "webm";
|
|
72
|
-
if (name.includes("mp4") || name.includes("isom")) return "mp4";
|
|
73
|
-
if (name.includes("mov") || name.includes("quicktime")) return "mov";
|
|
74
|
-
if (name.includes("ogg")) return "ogg";
|
|
75
|
-
if (name.includes("wav")) return "wav";
|
|
76
|
-
if (name.includes("flac")) return "flac";
|
|
77
|
-
if (name.includes("mp3")) return "mp3";
|
|
78
|
-
if (name.includes("adts") || name.includes("aac")) return "adts";
|
|
79
|
-
if (name.includes("mpegts") || name.includes("mpeg-ts") || name.includes("transport")) return "mpegts";
|
|
80
|
-
return sniffed;
|
|
81
|
-
}
|
|
82
|
-
function mediabunnyVideoToAvbridge(c) {
|
|
83
|
-
switch (c) {
|
|
84
|
-
case "avc":
|
|
85
|
-
return "h264";
|
|
86
|
-
case "hevc":
|
|
87
|
-
return "h265";
|
|
88
|
-
case "vp8":
|
|
89
|
-
return "vp8";
|
|
90
|
-
case "vp9":
|
|
91
|
-
return "vp9";
|
|
92
|
-
case "av1":
|
|
93
|
-
return "av1";
|
|
94
|
-
default:
|
|
95
|
-
return c ? c : "unknown";
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
function avbridgeVideoToMediabunny(c) {
|
|
99
|
-
switch (c) {
|
|
100
|
-
case "h264":
|
|
101
|
-
return "avc";
|
|
102
|
-
case "h265":
|
|
103
|
-
return "hevc";
|
|
104
|
-
case "vp8":
|
|
105
|
-
return "vp8";
|
|
106
|
-
case "vp9":
|
|
107
|
-
return "vp9";
|
|
108
|
-
case "av1":
|
|
109
|
-
return "av1";
|
|
110
|
-
default:
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function mediabunnyAudioToAvbridge(c) {
|
|
115
|
-
switch (c) {
|
|
116
|
-
case "aac":
|
|
117
|
-
return "aac";
|
|
118
|
-
case "mp3":
|
|
119
|
-
return "mp3";
|
|
120
|
-
case "opus":
|
|
121
|
-
return "opus";
|
|
122
|
-
case "vorbis":
|
|
123
|
-
return "vorbis";
|
|
124
|
-
case "flac":
|
|
125
|
-
return "flac";
|
|
126
|
-
case "ac3":
|
|
127
|
-
return "ac3";
|
|
128
|
-
case "eac3":
|
|
129
|
-
return "eac3";
|
|
130
|
-
default:
|
|
131
|
-
return c ? c : "unknown";
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
function avbridgeAudioToMediabunny(c) {
|
|
135
|
-
switch (c) {
|
|
136
|
-
case "aac":
|
|
137
|
-
return "aac";
|
|
138
|
-
case "mp3":
|
|
139
|
-
return "mp3";
|
|
140
|
-
case "opus":
|
|
141
|
-
return "opus";
|
|
142
|
-
case "vorbis":
|
|
143
|
-
return "vorbis";
|
|
144
|
-
case "flac":
|
|
145
|
-
return "flac";
|
|
146
|
-
case "ac3":
|
|
147
|
-
return "ac3";
|
|
148
|
-
case "eac3":
|
|
149
|
-
return "eac3";
|
|
150
|
-
default:
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
async function safeNumber(fn) {
|
|
155
|
-
try {
|
|
156
|
-
const v = await fn();
|
|
157
|
-
return typeof v === "number" && Number.isFinite(v) ? v : void 0;
|
|
158
|
-
} catch {
|
|
159
|
-
return void 0;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
async function safe(fn) {
|
|
163
|
-
try {
|
|
164
|
-
return await fn();
|
|
165
|
-
} catch {
|
|
166
|
-
return void 0;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// src/probe/index.ts
|
|
171
|
-
var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
172
|
-
"mp4",
|
|
173
|
-
"mov",
|
|
174
|
-
"mkv",
|
|
175
|
-
"webm",
|
|
176
|
-
"ogg",
|
|
177
|
-
"wav",
|
|
178
|
-
"mp3",
|
|
179
|
-
"flac",
|
|
180
|
-
"adts",
|
|
181
|
-
"mpegts"
|
|
182
|
-
]);
|
|
183
|
-
async function probe(source, transport) {
|
|
184
|
-
const normalized = await chunk6UUT4BEA_cjs.normalizeSource(source, transport);
|
|
185
|
-
const sniffed = await chunk6UUT4BEA_cjs.sniffNormalizedSource(normalized);
|
|
186
|
-
if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
|
|
187
|
-
try {
|
|
188
|
-
const result = await probeWithMediabunny(normalized, sniffed);
|
|
189
|
-
const hasUnknownCodec = result.videoTracks.some((t) => t.codec === "unknown") || result.audioTracks.some((t) => t.codec === "unknown");
|
|
190
|
-
if (hasUnknownCodec) {
|
|
191
|
-
try {
|
|
192
|
-
const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
|
|
193
|
-
return await probeWithLibav(normalized, sniffed);
|
|
194
|
-
} catch {
|
|
195
|
-
return result;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
return result;
|
|
199
|
-
} catch (mediabunnyErr) {
|
|
200
|
-
console.warn(
|
|
201
|
-
`[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,
|
|
202
|
-
mediabunnyErr.message
|
|
203
|
-
);
|
|
204
|
-
try {
|
|
205
|
-
const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
|
|
206
|
-
return await probeWithLibav(normalized, sniffed);
|
|
207
|
-
} catch (libavErr) {
|
|
208
|
-
const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
|
|
209
|
-
const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);
|
|
210
|
-
throw new chunk6UUT4BEA_cjs.AvbridgeError(
|
|
211
|
-
chunk6UUT4BEA_cjs.ERR_PROBE_FAILED,
|
|
212
|
-
`Failed to probe ${sniffed.toUpperCase()} file. mediabunny: ${mbMsg}. libav: ${lvMsg}.`,
|
|
213
|
-
"The file may be corrupt, truncated, or in an unsupported format. Enable AVBRIDGE_DEBUG for detailed logs."
|
|
214
|
-
);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
try {
|
|
219
|
-
const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
|
|
220
|
-
return await probeWithLibav(normalized, sniffed);
|
|
221
|
-
} catch (err) {
|
|
222
|
-
const inner = err instanceof Error ? err.message : String(err);
|
|
223
|
-
console.error("[avbridge] libav probe failed for", sniffed, "file:", err);
|
|
224
|
-
if (sniffed === "unknown") {
|
|
225
|
-
throw new chunk6UUT4BEA_cjs.AvbridgeError(
|
|
226
|
-
chunk6UUT4BEA_cjs.ERR_PROBE_UNKNOWN_CONTAINER,
|
|
227
|
-
`Unable to probe source: container format could not be identified. libav fallback: ${inner || "(no details)"}`,
|
|
228
|
-
"The file may be corrupt or in a format avbridge doesn't recognize. Check the file plays in VLC or ffprobe."
|
|
229
|
-
);
|
|
230
|
-
}
|
|
231
|
-
throw new chunk6UUT4BEA_cjs.AvbridgeError(
|
|
232
|
-
chunk6UUT4BEA_cjs.ERR_LIBAV_NOT_REACHABLE,
|
|
233
|
-
`${sniffed.toUpperCase()} files require libav.js, which failed to load: ${inner || "(no details)"}`,
|
|
234
|
-
"Install @libav.js/variant-webcodecs, or check that AVBRIDGE_LIBAV_BASE points to the correct path."
|
|
235
|
-
);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
10
|
// src/util/codec-strings.ts
|
|
240
11
|
function videoCodecString(track) {
|
|
241
12
|
if (track.codecString) return track.codecString;
|
|
@@ -485,36 +256,6 @@ function isRiskyNative(video) {
|
|
|
485
256
|
return false;
|
|
486
257
|
}
|
|
487
258
|
|
|
488
|
-
// src/subtitles/srt.ts
|
|
489
|
-
function srtToVtt(srt) {
|
|
490
|
-
if (srt.charCodeAt(0) === 65279) srt = srt.slice(1);
|
|
491
|
-
const normalized = srt.replace(/\r\n/g, "\n").replace(/\r/g, "\n").trim();
|
|
492
|
-
const blocks = normalized.split(/\n{2,}/);
|
|
493
|
-
const out = ["WEBVTT", ""];
|
|
494
|
-
for (const block of blocks) {
|
|
495
|
-
const lines = block.split("\n");
|
|
496
|
-
if (lines.length > 0 && /^\d+$/.test(lines[0].trim())) {
|
|
497
|
-
lines.shift();
|
|
498
|
-
}
|
|
499
|
-
if (lines.length === 0) continue;
|
|
500
|
-
const timing = lines.shift();
|
|
501
|
-
const vttTiming = convertTiming(timing);
|
|
502
|
-
if (!vttTiming) continue;
|
|
503
|
-
out.push(vttTiming);
|
|
504
|
-
for (const l of lines) out.push(l);
|
|
505
|
-
out.push("");
|
|
506
|
-
}
|
|
507
|
-
return out.join("\n");
|
|
508
|
-
}
|
|
509
|
-
function convertTiming(line) {
|
|
510
|
-
const m = /^(\d{1,2}):(\d{2}):(\d{2})[,.](\d{1,3})\s*-->\s*(\d{1,2}):(\d{2}):(\d{2})[,.](\d{1,3})(.*)$/.exec(
|
|
511
|
-
line.trim()
|
|
512
|
-
);
|
|
513
|
-
if (!m) return null;
|
|
514
|
-
const fmt = (h, mm, s, ms) => `${h.padStart(2, "0")}:${mm}:${s}.${ms.padEnd(3, "0").slice(0, 3)}`;
|
|
515
|
-
return `${fmt(m[1], m[2], m[3], m[4])} --> ${fmt(m[5], m[6], m[7], m[8])}${m[9] ?? ""}`;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
259
|
// src/events.ts
|
|
519
260
|
var TypedEmitter = class {
|
|
520
261
|
listeners = {};
|
|
@@ -720,7 +461,7 @@ async function createNativeSession(context, video) {
|
|
|
720
461
|
},
|
|
721
462
|
async setAudioTrack(id) {
|
|
722
463
|
const tracks = video.audioTracks;
|
|
723
|
-
if (!tracks) return;
|
|
464
|
+
if (!tracks || tracks.length === 0) return;
|
|
724
465
|
for (let i = 0; i < tracks.length; i++) {
|
|
725
466
|
tracks[i].enabled = tracks[i].id === String(id) || i === id;
|
|
726
467
|
}
|
|
@@ -772,15 +513,15 @@ var MseSink = class {
|
|
|
772
513
|
constructor(options) {
|
|
773
514
|
this.options = options;
|
|
774
515
|
if (typeof MediaSource === "undefined") {
|
|
775
|
-
throw new
|
|
776
|
-
|
|
516
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
517
|
+
chunk2IJ66NTD_cjs.ERR_MSE_NOT_SUPPORTED,
|
|
777
518
|
"MediaSource Extensions (MSE) are not supported in this environment.",
|
|
778
519
|
"MSE is required for the remux strategy. Use a browser that supports MSE, or try the fallback strategy."
|
|
779
520
|
);
|
|
780
521
|
}
|
|
781
522
|
if (!MediaSource.isTypeSupported(options.mime)) {
|
|
782
|
-
throw new
|
|
783
|
-
|
|
523
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
524
|
+
chunk2IJ66NTD_cjs.ERR_MSE_CODEC_NOT_SUPPORTED,
|
|
784
525
|
`This browser's MSE does not support "${options.mime}".`,
|
|
785
526
|
"The codec combination can't be played via remux in this browser. The player will try the next strategy automatically."
|
|
786
527
|
);
|
|
@@ -968,30 +709,49 @@ var MseSink = class {
|
|
|
968
709
|
async function createRemuxPipeline(ctx, video) {
|
|
969
710
|
const mb = await import('mediabunny');
|
|
970
711
|
const videoTrackInfo = ctx.videoTracks[0];
|
|
971
|
-
const audioTrackInfo = ctx.audioTracks[0];
|
|
972
712
|
if (!videoTrackInfo) throw new Error("remux: source has no video track");
|
|
973
|
-
const mbVideoCodec = avbridgeVideoToMediabunny(videoTrackInfo.codec);
|
|
713
|
+
const mbVideoCodec = chunkZCUXHW55_cjs.avbridgeVideoToMediabunny(videoTrackInfo.codec);
|
|
974
714
|
if (!mbVideoCodec) {
|
|
975
715
|
throw new Error(`remux: video codec "${videoTrackInfo.codec}" is not supported by mediabunny output`);
|
|
976
716
|
}
|
|
977
|
-
const mbAudioCodec = audioTrackInfo ? avbridgeAudioToMediabunny(audioTrackInfo.codec) : null;
|
|
978
717
|
const input = new mb.Input({
|
|
979
|
-
source: await buildMediabunnySourceFromInput(mb, ctx.source),
|
|
718
|
+
source: await chunkZCUXHW55_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
|
|
980
719
|
formats: mb.ALL_FORMATS
|
|
981
720
|
});
|
|
982
721
|
const allTracks = await input.getTracks();
|
|
983
722
|
const inputVideo = allTracks.find((t) => t.id === videoTrackInfo.id && t.isVideoTrack());
|
|
984
|
-
const inputAudio = audioTrackInfo ? allTracks.find((t) => t.id === audioTrackInfo.id && t.isAudioTrack()) : null;
|
|
985
723
|
if (!inputVideo || !inputVideo.isVideoTrack()) {
|
|
986
724
|
throw new Error("remux: video track not found in input");
|
|
987
725
|
}
|
|
988
|
-
if (audioTrackInfo && (!inputAudio || !inputAudio.isAudioTrack())) {
|
|
989
|
-
throw new Error("remux: audio track not found in input");
|
|
990
|
-
}
|
|
991
726
|
const videoConfig = await inputVideo.getDecoderConfig();
|
|
992
|
-
const audioConfig = inputAudio && inputAudio.isAudioTrack() ? await inputAudio.getDecoderConfig() : null;
|
|
993
727
|
const videoSink = new mb.EncodedPacketSink(inputVideo);
|
|
994
|
-
|
|
728
|
+
let selectedAudioTrackId = ctx.audioTracks[0]?.id ?? null;
|
|
729
|
+
let inputAudio = null;
|
|
730
|
+
let mbAudioCodec = null;
|
|
731
|
+
let audioSink = null;
|
|
732
|
+
let audioConfig = null;
|
|
733
|
+
async function rebuildAudio() {
|
|
734
|
+
if (selectedAudioTrackId == null) {
|
|
735
|
+
inputAudio = null;
|
|
736
|
+
mbAudioCodec = null;
|
|
737
|
+
audioSink = null;
|
|
738
|
+
audioConfig = null;
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
const trackInfo = ctx.audioTracks.find((t) => t.id === selectedAudioTrackId);
|
|
742
|
+
if (!trackInfo) {
|
|
743
|
+
throw new Error(`remux: no audio track with id ${selectedAudioTrackId}`);
|
|
744
|
+
}
|
|
745
|
+
const newInput = allTracks.find((t) => t.id === trackInfo.id && t.isAudioTrack());
|
|
746
|
+
if (!newInput || !newInput.isAudioTrack()) {
|
|
747
|
+
throw new Error("remux: audio track not found in input");
|
|
748
|
+
}
|
|
749
|
+
inputAudio = newInput;
|
|
750
|
+
mbAudioCodec = chunkZCUXHW55_cjs.avbridgeAudioToMediabunny(trackInfo.codec);
|
|
751
|
+
audioSink = new mb.EncodedPacketSink(newInput);
|
|
752
|
+
audioConfig = await newInput.getDecoderConfig();
|
|
753
|
+
}
|
|
754
|
+
await rebuildAudio();
|
|
995
755
|
let sink = null;
|
|
996
756
|
const stats = { videoPackets: 0, audioPackets: 0, bytesWritten: 0, fragments: 0 };
|
|
997
757
|
let destroyed = false;
|
|
@@ -1116,6 +876,30 @@ async function createRemuxPipeline(ctx, video) {
|
|
|
1116
876
|
pendingAutoPlay = autoPlay;
|
|
1117
877
|
if (sink) sink.setPlayOnSeek(autoPlay);
|
|
1118
878
|
},
|
|
879
|
+
async setAudioTrack(trackId, time, autoPlay) {
|
|
880
|
+
if (selectedAudioTrackId === trackId) return;
|
|
881
|
+
if (!ctx.audioTracks.some((t) => t.id === trackId)) {
|
|
882
|
+
console.warn("[avbridge] remux: setAudioTrack \u2014 unknown track id", trackId);
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
pumpToken++;
|
|
886
|
+
selectedAudioTrackId = trackId;
|
|
887
|
+
await rebuildAudio().catch((err) => {
|
|
888
|
+
console.warn("[avbridge] remux: rebuildAudio failed:", err.message);
|
|
889
|
+
});
|
|
890
|
+
if (sink) {
|
|
891
|
+
try {
|
|
892
|
+
sink.destroy();
|
|
893
|
+
} catch {
|
|
894
|
+
}
|
|
895
|
+
sink = null;
|
|
896
|
+
}
|
|
897
|
+
pendingAutoPlay = autoPlay;
|
|
898
|
+
pendingStartTime = time;
|
|
899
|
+
pumpLoop(++pumpToken, time).catch((err) => {
|
|
900
|
+
console.error("[avbridge] remux pipeline setAudioTrack pump failed:", err);
|
|
901
|
+
});
|
|
902
|
+
},
|
|
1119
903
|
async destroy() {
|
|
1120
904
|
destroyed = true;
|
|
1121
905
|
pumpToken++;
|
|
@@ -1175,7 +959,19 @@ async function createRemuxSession(context, video) {
|
|
|
1175
959
|
const wasPlaying = !video.paused;
|
|
1176
960
|
await pipeline.seek(time, wasPlaying || wantPlay);
|
|
1177
961
|
},
|
|
1178
|
-
async setAudioTrack(
|
|
962
|
+
async setAudioTrack(id) {
|
|
963
|
+
if (!context.audioTracks.some((t) => t.id === id)) {
|
|
964
|
+
console.warn("[avbridge] remux: setAudioTrack \u2014 unknown track id", id);
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
const wasPlaying = !video.paused;
|
|
968
|
+
const time = video.currentTime || 0;
|
|
969
|
+
if (!started) {
|
|
970
|
+
started = true;
|
|
971
|
+
await pipeline.setAudioTrack(id, time, wantPlay || wasPlaying);
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
await pipeline.setAudioTrack(id, time, wasPlaying || wantPlay);
|
|
1179
975
|
},
|
|
1180
976
|
async setSubtitleTrack(id) {
|
|
1181
977
|
const tracks = video.textTracks;
|
|
@@ -1228,6 +1024,9 @@ var VideoRenderer = class {
|
|
|
1228
1024
|
document.body.appendChild(this.canvas);
|
|
1229
1025
|
}
|
|
1230
1026
|
target.style.visibility = "hidden";
|
|
1027
|
+
const overlayParent = parent instanceof HTMLElement ? parent : document.body;
|
|
1028
|
+
this.subtitleOverlay = new chunkS4WAZC2T_cjs.SubtitleOverlay(overlayParent);
|
|
1029
|
+
this.watchTextTracks(target);
|
|
1231
1030
|
const ctx = this.canvas.getContext("2d");
|
|
1232
1031
|
if (!ctx) throw new Error("video renderer: failed to acquire 2D context");
|
|
1233
1032
|
this.ctx = ctx;
|
|
@@ -1253,6 +1052,15 @@ var VideoRenderer = class {
|
|
|
1253
1052
|
ticksWaiting = 0;
|
|
1254
1053
|
/** Cumulative count of ticks where PTS mode painted a frame. */
|
|
1255
1054
|
ticksPainted = 0;
|
|
1055
|
+
/**
|
|
1056
|
+
* Subtitle overlay div attached to the stage wrapper alongside the
|
|
1057
|
+
* canvas. Created lazily when subtitle tracks are attached via the
|
|
1058
|
+
* target's `<track>` children. Canvas strategies (hybrid, fallback)
|
|
1059
|
+
* hide the <video>, so we can't rely on the browser's native cue
|
|
1060
|
+
* rendering; we read TextTrack.cues and render into this overlay.
|
|
1061
|
+
*/
|
|
1062
|
+
subtitleOverlay = null;
|
|
1063
|
+
subtitleTrack = null;
|
|
1256
1064
|
/**
|
|
1257
1065
|
* Calibration offset (microseconds) between video PTS and audio clock.
|
|
1258
1066
|
* Video PTS and AudioContext.currentTime can drift ~0.1% relative to
|
|
@@ -1296,9 +1104,80 @@ var VideoRenderer = class {
|
|
|
1296
1104
|
this.framesDroppedOverflow++;
|
|
1297
1105
|
}
|
|
1298
1106
|
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Watch the target <video>'s textTracks list. When a track is added,
|
|
1109
|
+
* grab it and start polling cues on each render tick. Existing tracks
|
|
1110
|
+
* (if any) are picked up immediately.
|
|
1111
|
+
*/
|
|
1112
|
+
watchTextTracks(target) {
|
|
1113
|
+
const pick = () => {
|
|
1114
|
+
if (this.subtitleTrack) return;
|
|
1115
|
+
const tracks = target.textTracks;
|
|
1116
|
+
if (isDebug()) {
|
|
1117
|
+
console.log(`[avbridge:subs] watchTextTracks pick() \u2014 ${tracks.length} tracks`);
|
|
1118
|
+
}
|
|
1119
|
+
for (let i = 0; i < tracks.length; i++) {
|
|
1120
|
+
const t = tracks[i];
|
|
1121
|
+
if (isDebug()) {
|
|
1122
|
+
console.log(`[avbridge:subs] track ${i}: kind=${t.kind} mode=${t.mode} cues=${t.cues?.length ?? 0}`);
|
|
1123
|
+
}
|
|
1124
|
+
if (t.kind === "subtitles" || t.kind === "captions") {
|
|
1125
|
+
this.subtitleTrack = t;
|
|
1126
|
+
t.mode = "hidden";
|
|
1127
|
+
if (isDebug()) {
|
|
1128
|
+
console.log(`[avbridge:subs] picked track, mode=hidden`);
|
|
1129
|
+
}
|
|
1130
|
+
const trackEl = target.querySelector(`track[srclang="${t.language}"]`);
|
|
1131
|
+
if (trackEl) {
|
|
1132
|
+
trackEl.addEventListener("load", () => {
|
|
1133
|
+
if (isDebug()) {
|
|
1134
|
+
console.log(`[avbridge:subs] track element loaded, cues=${t.cues?.length ?? 0}`);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
trackEl.addEventListener("error", (ev) => {
|
|
1138
|
+
console.warn(`[avbridge:subs] track element error:`, ev);
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
pick();
|
|
1146
|
+
if (typeof target.textTracks.addEventListener === "function") {
|
|
1147
|
+
target.textTracks.addEventListener("addtrack", (e) => {
|
|
1148
|
+
if (isDebug()) {
|
|
1149
|
+
console.log("[avbridge:subs] addtrack event fired");
|
|
1150
|
+
}
|
|
1151
|
+
pick();
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
_loggedCues = false;
|
|
1156
|
+
/** Find the active cue (if any) for the given media time. */
|
|
1157
|
+
updateSubtitles() {
|
|
1158
|
+
if (!this.subtitleOverlay || !this.subtitleTrack) return;
|
|
1159
|
+
const cues = this.subtitleTrack.cues;
|
|
1160
|
+
if (!cues || cues.length === 0) return;
|
|
1161
|
+
if (isDebug() && !this._loggedCues) {
|
|
1162
|
+
this._loggedCues = true;
|
|
1163
|
+
console.log(`[avbridge:subs] cues available: ${cues.length}, first start=${cues[0].startTime}, last end=${cues[cues.length - 1].endTime}`);
|
|
1164
|
+
}
|
|
1165
|
+
const t = this.clock.now();
|
|
1166
|
+
let activeText = "";
|
|
1167
|
+
for (let i = 0; i < cues.length; i++) {
|
|
1168
|
+
const c = cues[i];
|
|
1169
|
+
if (t >= c.startTime && t <= c.endTime) {
|
|
1170
|
+
const vttCue = c;
|
|
1171
|
+
activeText = vttCue.text ?? "";
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
this.subtitleOverlay.setText(activeText.replace(/<[^>]+>/g, ""));
|
|
1176
|
+
}
|
|
1299
1177
|
tick() {
|
|
1300
1178
|
if (this.destroyed) return;
|
|
1301
1179
|
this.rafHandle = requestAnimationFrame(this.tick);
|
|
1180
|
+
this.updateSubtitles();
|
|
1302
1181
|
if (this.queue.length === 0) return;
|
|
1303
1182
|
const playing = this.clock.isPlaying();
|
|
1304
1183
|
if (!playing) {
|
|
@@ -1427,6 +1306,11 @@ var VideoRenderer = class {
|
|
|
1427
1306
|
this.destroyed = true;
|
|
1428
1307
|
if (this.rafHandle != null) cancelAnimationFrame(this.rafHandle);
|
|
1429
1308
|
this.flush();
|
|
1309
|
+
if (this.subtitleOverlay) {
|
|
1310
|
+
this.subtitleOverlay.destroy();
|
|
1311
|
+
this.subtitleOverlay = null;
|
|
1312
|
+
}
|
|
1313
|
+
this.subtitleTrack = null;
|
|
1430
1314
|
this.canvas.remove();
|
|
1431
1315
|
this.target.style.visibility = "";
|
|
1432
1316
|
}
|
|
@@ -1676,7 +1560,8 @@ async function startHybridDecoder(opts) {
|
|
|
1676
1560
|
const readPkt = await libav.av_packet_alloc();
|
|
1677
1561
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
1678
1562
|
const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
|
|
1679
|
-
const
|
|
1563
|
+
const firstAudioTrackId = opts.context.audioTracks[0]?.id;
|
|
1564
|
+
let audioStream = (firstAudioTrackId != null ? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === firstAudioTrackId) : void 0) ?? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
|
|
1680
1565
|
if (!videoStream && !audioStream) {
|
|
1681
1566
|
throw new Error("hybrid decoder: file has no decodable streams");
|
|
1682
1567
|
}
|
|
@@ -1830,7 +1715,7 @@ async function startHybridDecoder(opts) {
|
|
|
1830
1715
|
const processed = await applyBSF(videoPackets);
|
|
1831
1716
|
for (const pkt of processed) {
|
|
1832
1717
|
if (myToken !== pumpToken || destroyed) return;
|
|
1833
|
-
sanitizePacketTimestamp(pkt, () => {
|
|
1718
|
+
chunkCPZ7PXAM_cjs.sanitizePacketTimestamp(pkt, () => {
|
|
1834
1719
|
const ts = syntheticVideoUs;
|
|
1835
1720
|
syntheticVideoUs += videoFrameStepUs;
|
|
1836
1721
|
return ts;
|
|
@@ -1909,7 +1794,7 @@ async function startHybridDecoder(opts) {
|
|
|
1909
1794
|
const frames = allFrames;
|
|
1910
1795
|
for (const f of frames) {
|
|
1911
1796
|
if (myToken !== pumpToken || destroyed) return;
|
|
1912
|
-
sanitizeFrameTimestamp(
|
|
1797
|
+
chunkCPZ7PXAM_cjs.sanitizeFrameTimestamp(
|
|
1913
1798
|
f,
|
|
1914
1799
|
() => {
|
|
1915
1800
|
const ts = syntheticAudioUs;
|
|
@@ -1920,7 +1805,7 @@ async function startHybridDecoder(opts) {
|
|
|
1920
1805
|
},
|
|
1921
1806
|
audioTimeBase
|
|
1922
1807
|
);
|
|
1923
|
-
const samples = libavFrameToInterleavedFloat32(f);
|
|
1808
|
+
const samples = chunkCPZ7PXAM_cjs.libavFrameToInterleavedFloat32(f);
|
|
1924
1809
|
if (samples) {
|
|
1925
1810
|
opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
|
|
1926
1811
|
audioFramesDecoded++;
|
|
@@ -1972,6 +1857,71 @@ async function startHybridDecoder(opts) {
|
|
|
1972
1857
|
} catch {
|
|
1973
1858
|
}
|
|
1974
1859
|
},
|
|
1860
|
+
async setAudioTrack(trackId, timeSec) {
|
|
1861
|
+
if (audioStream && audioStream.index === trackId) return;
|
|
1862
|
+
const newStream = streams.find(
|
|
1863
|
+
(s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === trackId
|
|
1864
|
+
);
|
|
1865
|
+
if (!newStream) {
|
|
1866
|
+
console.warn("[avbridge] hybrid: setAudioTrack \u2014 no stream with id", trackId);
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
const newToken = ++pumpToken;
|
|
1870
|
+
if (pumpRunning) {
|
|
1871
|
+
try {
|
|
1872
|
+
await pumpRunning;
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
if (destroyed) return;
|
|
1877
|
+
if (audioDec) {
|
|
1878
|
+
try {
|
|
1879
|
+
await libav.ff_free_decoder?.(audioDec.c, audioDec.pkt, audioDec.frame);
|
|
1880
|
+
} catch {
|
|
1881
|
+
}
|
|
1882
|
+
audioDec = null;
|
|
1883
|
+
}
|
|
1884
|
+
try {
|
|
1885
|
+
const [, c, pkt, frame] = await libav.ff_init_decoder(newStream.codec_id, {
|
|
1886
|
+
codecpar: newStream.codecpar
|
|
1887
|
+
});
|
|
1888
|
+
audioDec = { c, pkt, frame };
|
|
1889
|
+
audioTimeBase = newStream.time_base_num && newStream.time_base_den ? [newStream.time_base_num, newStream.time_base_den] : void 0;
|
|
1890
|
+
} catch (err) {
|
|
1891
|
+
console.warn(
|
|
1892
|
+
"[avbridge] hybrid: setAudioTrack init failed \u2014 switching to no-audio:",
|
|
1893
|
+
err.message
|
|
1894
|
+
);
|
|
1895
|
+
audioDec = null;
|
|
1896
|
+
opts.audio.setNoAudio();
|
|
1897
|
+
}
|
|
1898
|
+
audioStream = newStream;
|
|
1899
|
+
try {
|
|
1900
|
+
const tsUs = Math.floor(timeSec * 1e6);
|
|
1901
|
+
const [tsLo, tsHi] = libav.f64toi64 ? libav.f64toi64(tsUs) : [tsUs | 0, Math.floor(tsUs / 4294967296)];
|
|
1902
|
+
await libav.av_seek_frame(
|
|
1903
|
+
fmt_ctx,
|
|
1904
|
+
-1,
|
|
1905
|
+
tsLo,
|
|
1906
|
+
tsHi,
|
|
1907
|
+
libav.AVSEEK_FLAG_BACKWARD ?? 0
|
|
1908
|
+
);
|
|
1909
|
+
} catch (err) {
|
|
1910
|
+
console.warn("[avbridge] hybrid: setAudioTrack seek failed:", err);
|
|
1911
|
+
}
|
|
1912
|
+
try {
|
|
1913
|
+
if (videoDecoder && videoDecoder.state === "configured") {
|
|
1914
|
+
await videoDecoder.flush();
|
|
1915
|
+
}
|
|
1916
|
+
} catch {
|
|
1917
|
+
}
|
|
1918
|
+
await flushBSF();
|
|
1919
|
+
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
1920
|
+
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
1921
|
+
pumpRunning = pumpLoop(newToken).catch(
|
|
1922
|
+
(err) => console.error("[avbridge] hybrid pump failed (post-setAudioTrack):", err)
|
|
1923
|
+
);
|
|
1924
|
+
},
|
|
1975
1925
|
async seek(timeSec) {
|
|
1976
1926
|
const newToken = ++pumpToken;
|
|
1977
1927
|
if (pumpRunning) {
|
|
@@ -2029,161 +1979,9 @@ async function startHybridDecoder(opts) {
|
|
|
2029
1979
|
}
|
|
2030
1980
|
};
|
|
2031
1981
|
}
|
|
2032
|
-
function sanitizePacketTimestamp(pkt, nextUs, fallbackTimeBase) {
|
|
2033
|
-
const lo = pkt.pts ?? 0;
|
|
2034
|
-
const hi = pkt.ptshi ?? 0;
|
|
2035
|
-
const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
|
|
2036
|
-
if (isInvalid) {
|
|
2037
|
-
const us2 = nextUs();
|
|
2038
|
-
pkt.pts = us2;
|
|
2039
|
-
pkt.ptshi = 0;
|
|
2040
|
-
pkt.time_base_num = 1;
|
|
2041
|
-
pkt.time_base_den = 1e6;
|
|
2042
|
-
return;
|
|
2043
|
-
}
|
|
2044
|
-
const tb = fallbackTimeBase ?? [1, 1e6];
|
|
2045
|
-
const pts64 = hi * 4294967296 + lo;
|
|
2046
|
-
const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
|
|
2047
|
-
if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
|
|
2048
|
-
pkt.pts = us;
|
|
2049
|
-
pkt.ptshi = us < 0 ? -1 : 0;
|
|
2050
|
-
pkt.time_base_num = 1;
|
|
2051
|
-
pkt.time_base_den = 1e6;
|
|
2052
|
-
return;
|
|
2053
|
-
}
|
|
2054
|
-
const fallback = nextUs();
|
|
2055
|
-
pkt.pts = fallback;
|
|
2056
|
-
pkt.ptshi = 0;
|
|
2057
|
-
pkt.time_base_num = 1;
|
|
2058
|
-
pkt.time_base_den = 1e6;
|
|
2059
|
-
}
|
|
2060
|
-
function sanitizeFrameTimestamp(frame, nextUs, fallbackTimeBase) {
|
|
2061
|
-
const lo = frame.pts ?? 0;
|
|
2062
|
-
const hi = frame.ptshi ?? 0;
|
|
2063
|
-
const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
|
|
2064
|
-
if (isInvalid) {
|
|
2065
|
-
const us2 = nextUs();
|
|
2066
|
-
frame.pts = us2;
|
|
2067
|
-
frame.ptshi = 0;
|
|
2068
|
-
return;
|
|
2069
|
-
}
|
|
2070
|
-
const tb = fallbackTimeBase ?? [1, 1e6];
|
|
2071
|
-
const pts64 = hi * 4294967296 + lo;
|
|
2072
|
-
const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
|
|
2073
|
-
if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
|
|
2074
|
-
frame.pts = us;
|
|
2075
|
-
frame.ptshi = us < 0 ? -1 : 0;
|
|
2076
|
-
return;
|
|
2077
|
-
}
|
|
2078
|
-
const fallback = nextUs();
|
|
2079
|
-
frame.pts = fallback;
|
|
2080
|
-
frame.ptshi = 0;
|
|
2081
|
-
}
|
|
2082
|
-
var AV_SAMPLE_FMT_U8 = 0;
|
|
2083
|
-
var AV_SAMPLE_FMT_S16 = 1;
|
|
2084
|
-
var AV_SAMPLE_FMT_S32 = 2;
|
|
2085
|
-
var AV_SAMPLE_FMT_FLT = 3;
|
|
2086
|
-
var AV_SAMPLE_FMT_U8P = 5;
|
|
2087
|
-
var AV_SAMPLE_FMT_S16P = 6;
|
|
2088
|
-
var AV_SAMPLE_FMT_S32P = 7;
|
|
2089
|
-
var AV_SAMPLE_FMT_FLTP = 8;
|
|
2090
|
-
function libavFrameToInterleavedFloat32(frame) {
|
|
2091
|
-
const channels = frame.channels ?? frame.ch_layout_nb_channels ?? 1;
|
|
2092
|
-
const sampleRate = frame.sample_rate ?? 44100;
|
|
2093
|
-
const nbSamples = frame.nb_samples ?? 0;
|
|
2094
|
-
if (nbSamples === 0) return null;
|
|
2095
|
-
const out = new Float32Array(nbSamples * channels);
|
|
2096
|
-
switch (frame.format) {
|
|
2097
|
-
case AV_SAMPLE_FMT_FLTP: {
|
|
2098
|
-
const planes = ensurePlanes(frame.data, channels);
|
|
2099
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2100
|
-
const plane = asFloat32(planes[ch]);
|
|
2101
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i];
|
|
2102
|
-
}
|
|
2103
|
-
return { data: out, channels, sampleRate };
|
|
2104
|
-
}
|
|
2105
|
-
case AV_SAMPLE_FMT_FLT: {
|
|
2106
|
-
const flat = asFloat32(frame.data);
|
|
2107
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i];
|
|
2108
|
-
return { data: out, channels, sampleRate };
|
|
2109
|
-
}
|
|
2110
|
-
case AV_SAMPLE_FMT_S16P: {
|
|
2111
|
-
const planes = ensurePlanes(frame.data, channels);
|
|
2112
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2113
|
-
const plane = asInt16(planes[ch]);
|
|
2114
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 32768;
|
|
2115
|
-
}
|
|
2116
|
-
return { data: out, channels, sampleRate };
|
|
2117
|
-
}
|
|
2118
|
-
case AV_SAMPLE_FMT_S16: {
|
|
2119
|
-
const flat = asInt16(frame.data);
|
|
2120
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 32768;
|
|
2121
|
-
return { data: out, channels, sampleRate };
|
|
2122
|
-
}
|
|
2123
|
-
case AV_SAMPLE_FMT_S32P: {
|
|
2124
|
-
const planes = ensurePlanes(frame.data, channels);
|
|
2125
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2126
|
-
const plane = asInt32(planes[ch]);
|
|
2127
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 2147483648;
|
|
2128
|
-
}
|
|
2129
|
-
return { data: out, channels, sampleRate };
|
|
2130
|
-
}
|
|
2131
|
-
case AV_SAMPLE_FMT_S32: {
|
|
2132
|
-
const flat = asInt32(frame.data);
|
|
2133
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 2147483648;
|
|
2134
|
-
return { data: out, channels, sampleRate };
|
|
2135
|
-
}
|
|
2136
|
-
case AV_SAMPLE_FMT_U8P: {
|
|
2137
|
-
const planes = ensurePlanes(frame.data, channels);
|
|
2138
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2139
|
-
const plane = asUint8(planes[ch]);
|
|
2140
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = (plane[i] - 128) / 128;
|
|
2141
|
-
}
|
|
2142
|
-
return { data: out, channels, sampleRate };
|
|
2143
|
-
}
|
|
2144
|
-
case AV_SAMPLE_FMT_U8: {
|
|
2145
|
-
const flat = asUint8(frame.data);
|
|
2146
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = (flat[i] - 128) / 128;
|
|
2147
|
-
return { data: out, channels, sampleRate };
|
|
2148
|
-
}
|
|
2149
|
-
default:
|
|
2150
|
-
return null;
|
|
2151
|
-
}
|
|
2152
|
-
}
|
|
2153
|
-
function ensurePlanes(data, channels) {
|
|
2154
|
-
if (Array.isArray(data)) return data;
|
|
2155
|
-
const arr = data;
|
|
2156
|
-
const len = arr.length;
|
|
2157
|
-
const perChannel = Math.floor(len / channels);
|
|
2158
|
-
const planes = [];
|
|
2159
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2160
|
-
planes.push(arr.subarray ? arr.subarray(ch * perChannel, (ch + 1) * perChannel) : arr);
|
|
2161
|
-
}
|
|
2162
|
-
return planes;
|
|
2163
|
-
}
|
|
2164
|
-
function asFloat32(x) {
|
|
2165
|
-
if (x instanceof Float32Array) return x;
|
|
2166
|
-
const ta = x;
|
|
2167
|
-
return new Float32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
|
|
2168
|
-
}
|
|
2169
|
-
function asInt16(x) {
|
|
2170
|
-
if (x instanceof Int16Array) return x;
|
|
2171
|
-
const ta = x;
|
|
2172
|
-
return new Int16Array(ta.buffer, ta.byteOffset, ta.byteLength / 2);
|
|
2173
|
-
}
|
|
2174
|
-
function asInt32(x) {
|
|
2175
|
-
if (x instanceof Int32Array) return x;
|
|
2176
|
-
const ta = x;
|
|
2177
|
-
return new Int32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
|
|
2178
|
-
}
|
|
2179
|
-
function asUint8(x) {
|
|
2180
|
-
if (x instanceof Uint8Array) return x;
|
|
2181
|
-
const ta = x;
|
|
2182
|
-
return new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
|
2183
|
-
}
|
|
2184
1982
|
async function loadBridge() {
|
|
2185
1983
|
try {
|
|
2186
|
-
const wrapper = await import('./libav-import-
|
|
1984
|
+
const wrapper = await import('./libav-import-2ZVKV2E7.cjs');
|
|
2187
1985
|
return wrapper.libavBridge;
|
|
2188
1986
|
} catch (err) {
|
|
2189
1987
|
throw new Error(
|
|
@@ -2196,8 +1994,8 @@ async function loadBridge() {
|
|
|
2196
1994
|
var READY_AUDIO_BUFFER_SECONDS = 0.3;
|
|
2197
1995
|
var READY_TIMEOUT_SECONDS = 10;
|
|
2198
1996
|
async function createHybridSession(ctx, target, transport) {
|
|
2199
|
-
const { normalizeSource
|
|
2200
|
-
const source = await
|
|
1997
|
+
const { normalizeSource } = await import('./source-VFLXLOCN.cjs');
|
|
1998
|
+
const source = await normalizeSource(ctx.source);
|
|
2201
1999
|
const fps = ctx.videoTracks[0]?.fps ?? 30;
|
|
2202
2000
|
const audio = new AudioOutput();
|
|
2203
2001
|
const renderer = new VideoRenderer(target, audio, fps);
|
|
@@ -2293,7 +2091,24 @@ async function createHybridSession(ctx, target, transport) {
|
|
|
2293
2091
|
async seek(time) {
|
|
2294
2092
|
await doSeek(time);
|
|
2295
2093
|
},
|
|
2296
|
-
async setAudioTrack(
|
|
2094
|
+
async setAudioTrack(id) {
|
|
2095
|
+
if (!ctx.audioTracks.some((t) => t.id === id)) {
|
|
2096
|
+
console.warn("[avbridge] hybrid: setAudioTrack \u2014 unknown track id", id);
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
const wasPlaying = audio.isPlaying();
|
|
2100
|
+
const currentTime = audio.now();
|
|
2101
|
+
await audio.pause().catch(() => {
|
|
2102
|
+
});
|
|
2103
|
+
await handles.setAudioTrack(id, currentTime).catch(
|
|
2104
|
+
(err) => console.warn("[avbridge] hybrid: handles.setAudioTrack failed:", err)
|
|
2105
|
+
);
|
|
2106
|
+
await audio.reset(currentTime);
|
|
2107
|
+
renderer.flush();
|
|
2108
|
+
if (wasPlaying) {
|
|
2109
|
+
await waitForBuffer();
|
|
2110
|
+
await audio.start();
|
|
2111
|
+
}
|
|
2297
2112
|
},
|
|
2298
2113
|
async setSubtitleTrack(_id) {
|
|
2299
2114
|
},
|
|
@@ -2332,7 +2147,8 @@ async function startDecoder(opts) {
|
|
|
2332
2147
|
const readPkt = await libav.av_packet_alloc();
|
|
2333
2148
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
2334
2149
|
const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
|
|
2335
|
-
const
|
|
2150
|
+
const firstAudioTrackId = opts.context.audioTracks[0]?.id;
|
|
2151
|
+
let audioStream = (firstAudioTrackId != null ? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === firstAudioTrackId) : void 0) ?? streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO) ?? null;
|
|
2336
2152
|
if (!videoStream && !audioStream) {
|
|
2337
2153
|
throw new Error("fallback decoder: file has no decodable streams");
|
|
2338
2154
|
}
|
|
@@ -2548,7 +2364,7 @@ async function startDecoder(opts) {
|
|
|
2548
2364
|
if (myToken !== pumpToken || destroyed) return;
|
|
2549
2365
|
for (const f of frames) {
|
|
2550
2366
|
if (myToken !== pumpToken || destroyed) return;
|
|
2551
|
-
|
|
2367
|
+
chunkCPZ7PXAM_cjs.sanitizeFrameTimestamp(
|
|
2552
2368
|
f,
|
|
2553
2369
|
() => {
|
|
2554
2370
|
const ts = syntheticVideoUs;
|
|
@@ -2558,7 +2374,7 @@ async function startDecoder(opts) {
|
|
|
2558
2374
|
videoTimeBase
|
|
2559
2375
|
);
|
|
2560
2376
|
try {
|
|
2561
|
-
const vf = bridge.laFrameToVideoFrame(f,
|
|
2377
|
+
const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
|
|
2562
2378
|
opts.renderer.enqueue(vf);
|
|
2563
2379
|
videoFramesDecoded++;
|
|
2564
2380
|
} catch (err) {
|
|
@@ -2586,7 +2402,7 @@ async function startDecoder(opts) {
|
|
|
2586
2402
|
if (myToken !== pumpToken || destroyed) return;
|
|
2587
2403
|
for (const f of frames) {
|
|
2588
2404
|
if (myToken !== pumpToken || destroyed) return;
|
|
2589
|
-
|
|
2405
|
+
chunkCPZ7PXAM_cjs.sanitizeFrameTimestamp(
|
|
2590
2406
|
f,
|
|
2591
2407
|
() => {
|
|
2592
2408
|
const ts = syntheticAudioUs;
|
|
@@ -2597,7 +2413,7 @@ async function startDecoder(opts) {
|
|
|
2597
2413
|
},
|
|
2598
2414
|
audioTimeBase
|
|
2599
2415
|
);
|
|
2600
|
-
const samples =
|
|
2416
|
+
const samples = chunkCPZ7PXAM_cjs.libavFrameToInterleavedFloat32(f);
|
|
2601
2417
|
if (samples) {
|
|
2602
2418
|
opts.audio.schedule(samples.data, samples.channels, samples.sampleRate);
|
|
2603
2419
|
audioFramesDecoded++;
|
|
@@ -2645,6 +2461,69 @@ async function startDecoder(opts) {
|
|
|
2645
2461
|
} catch {
|
|
2646
2462
|
}
|
|
2647
2463
|
},
|
|
2464
|
+
async setAudioTrack(trackId, timeSec) {
|
|
2465
|
+
if (audioStream && audioStream.index === trackId) return;
|
|
2466
|
+
const newStream = streams.find(
|
|
2467
|
+
(s) => s.codec_type === libav.AVMEDIA_TYPE_AUDIO && s.index === trackId
|
|
2468
|
+
);
|
|
2469
|
+
if (!newStream) {
|
|
2470
|
+
console.warn("[avbridge] fallback: setAudioTrack \u2014 no stream with id", trackId);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
const newToken = ++pumpToken;
|
|
2474
|
+
if (pumpRunning) {
|
|
2475
|
+
try {
|
|
2476
|
+
await pumpRunning;
|
|
2477
|
+
} catch {
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
if (destroyed) return;
|
|
2481
|
+
if (audioDec) {
|
|
2482
|
+
try {
|
|
2483
|
+
await libav.ff_free_decoder?.(audioDec.c, audioDec.pkt, audioDec.frame);
|
|
2484
|
+
} catch {
|
|
2485
|
+
}
|
|
2486
|
+
audioDec = null;
|
|
2487
|
+
}
|
|
2488
|
+
try {
|
|
2489
|
+
const [, c, pkt, frame] = await libav.ff_init_decoder(newStream.codec_id, {
|
|
2490
|
+
codecpar: newStream.codecpar
|
|
2491
|
+
});
|
|
2492
|
+
audioDec = { c, pkt, frame };
|
|
2493
|
+
audioTimeBase = newStream.time_base_num && newStream.time_base_den ? [newStream.time_base_num, newStream.time_base_den] : void 0;
|
|
2494
|
+
} catch (err) {
|
|
2495
|
+
console.warn(
|
|
2496
|
+
"[avbridge] fallback: setAudioTrack init failed \u2014 falling back to no-audio mode:",
|
|
2497
|
+
err.message
|
|
2498
|
+
);
|
|
2499
|
+
audioDec = null;
|
|
2500
|
+
opts.audio.setNoAudio();
|
|
2501
|
+
}
|
|
2502
|
+
audioStream = newStream;
|
|
2503
|
+
try {
|
|
2504
|
+
const tsUs = Math.floor(timeSec * 1e6);
|
|
2505
|
+
const [tsLo, tsHi] = libav.f64toi64 ? libav.f64toi64(tsUs) : [tsUs | 0, Math.floor(tsUs / 4294967296)];
|
|
2506
|
+
await libav.av_seek_frame(
|
|
2507
|
+
fmt_ctx,
|
|
2508
|
+
-1,
|
|
2509
|
+
tsLo,
|
|
2510
|
+
tsHi,
|
|
2511
|
+
libav.AVSEEK_FLAG_BACKWARD ?? 0
|
|
2512
|
+
);
|
|
2513
|
+
} catch (err) {
|
|
2514
|
+
console.warn("[avbridge] fallback: setAudioTrack seek failed:", err);
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
if (videoDec) await libav.avcodec_flush_buffers?.(videoDec.c);
|
|
2518
|
+
} catch {
|
|
2519
|
+
}
|
|
2520
|
+
await flushBSF();
|
|
2521
|
+
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
2522
|
+
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
2523
|
+
pumpRunning = pumpLoop(newToken).catch(
|
|
2524
|
+
(err) => console.error("[avbridge] fallback pump failed (post-setAudioTrack):", err)
|
|
2525
|
+
);
|
|
2526
|
+
},
|
|
2648
2527
|
async seek(timeSec) {
|
|
2649
2528
|
const newToken = ++pumpToken;
|
|
2650
2529
|
if (pumpRunning) {
|
|
@@ -2701,138 +2580,9 @@ async function startDecoder(opts) {
|
|
|
2701
2580
|
}
|
|
2702
2581
|
};
|
|
2703
2582
|
}
|
|
2704
|
-
function sanitizeFrameTimestamp2(frame, nextUs, fallbackTimeBase) {
|
|
2705
|
-
const lo = frame.pts ?? 0;
|
|
2706
|
-
const hi = frame.ptshi ?? 0;
|
|
2707
|
-
const isInvalid = hi === -2147483648 && lo === 0 || !Number.isFinite(lo);
|
|
2708
|
-
if (isInvalid) {
|
|
2709
|
-
const us2 = nextUs();
|
|
2710
|
-
frame.pts = us2;
|
|
2711
|
-
frame.ptshi = 0;
|
|
2712
|
-
return { timeBase: [1, 1e6] };
|
|
2713
|
-
}
|
|
2714
|
-
const tb = fallbackTimeBase ?? [1, 1e6];
|
|
2715
|
-
const pts64 = hi * 4294967296 + lo;
|
|
2716
|
-
const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
|
|
2717
|
-
if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
|
|
2718
|
-
frame.pts = us;
|
|
2719
|
-
frame.ptshi = us < 0 ? -1 : 0;
|
|
2720
|
-
return { timeBase: [1, 1e6] };
|
|
2721
|
-
}
|
|
2722
|
-
const fallback = nextUs();
|
|
2723
|
-
frame.pts = fallback;
|
|
2724
|
-
frame.ptshi = 0;
|
|
2725
|
-
return { timeBase: [1, 1e6] };
|
|
2726
|
-
}
|
|
2727
|
-
var AV_SAMPLE_FMT_U82 = 0;
|
|
2728
|
-
var AV_SAMPLE_FMT_S162 = 1;
|
|
2729
|
-
var AV_SAMPLE_FMT_S322 = 2;
|
|
2730
|
-
var AV_SAMPLE_FMT_FLT2 = 3;
|
|
2731
|
-
var AV_SAMPLE_FMT_U8P2 = 5;
|
|
2732
|
-
var AV_SAMPLE_FMT_S16P2 = 6;
|
|
2733
|
-
var AV_SAMPLE_FMT_S32P2 = 7;
|
|
2734
|
-
var AV_SAMPLE_FMT_FLTP2 = 8;
|
|
2735
|
-
function libavFrameToInterleavedFloat322(frame) {
|
|
2736
|
-
const channels = frame.channels ?? frame.ch_layout_nb_channels ?? 1;
|
|
2737
|
-
const sampleRate = frame.sample_rate ?? 44100;
|
|
2738
|
-
const nbSamples = frame.nb_samples ?? 0;
|
|
2739
|
-
if (nbSamples === 0) return null;
|
|
2740
|
-
const out = new Float32Array(nbSamples * channels);
|
|
2741
|
-
switch (frame.format) {
|
|
2742
|
-
case AV_SAMPLE_FMT_FLTP2: {
|
|
2743
|
-
const planes = ensurePlanes2(frame.data, channels);
|
|
2744
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2745
|
-
const plane = asFloat322(planes[ch]);
|
|
2746
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i];
|
|
2747
|
-
}
|
|
2748
|
-
return { data: out, channels, sampleRate };
|
|
2749
|
-
}
|
|
2750
|
-
case AV_SAMPLE_FMT_FLT2: {
|
|
2751
|
-
const flat = asFloat322(frame.data);
|
|
2752
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i];
|
|
2753
|
-
return { data: out, channels, sampleRate };
|
|
2754
|
-
}
|
|
2755
|
-
case AV_SAMPLE_FMT_S16P2: {
|
|
2756
|
-
const planes = ensurePlanes2(frame.data, channels);
|
|
2757
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2758
|
-
const plane = asInt162(planes[ch]);
|
|
2759
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 32768;
|
|
2760
|
-
}
|
|
2761
|
-
return { data: out, channels, sampleRate };
|
|
2762
|
-
}
|
|
2763
|
-
case AV_SAMPLE_FMT_S162: {
|
|
2764
|
-
const flat = asInt162(frame.data);
|
|
2765
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 32768;
|
|
2766
|
-
return { data: out, channels, sampleRate };
|
|
2767
|
-
}
|
|
2768
|
-
case AV_SAMPLE_FMT_S32P2: {
|
|
2769
|
-
const planes = ensurePlanes2(frame.data, channels);
|
|
2770
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2771
|
-
const plane = asInt322(planes[ch]);
|
|
2772
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = plane[i] / 2147483648;
|
|
2773
|
-
}
|
|
2774
|
-
return { data: out, channels, sampleRate };
|
|
2775
|
-
}
|
|
2776
|
-
case AV_SAMPLE_FMT_S322: {
|
|
2777
|
-
const flat = asInt322(frame.data);
|
|
2778
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = flat[i] / 2147483648;
|
|
2779
|
-
return { data: out, channels, sampleRate };
|
|
2780
|
-
}
|
|
2781
|
-
case AV_SAMPLE_FMT_U8P2: {
|
|
2782
|
-
const planes = ensurePlanes2(frame.data, channels);
|
|
2783
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2784
|
-
const plane = asUint82(planes[ch]);
|
|
2785
|
-
for (let i = 0; i < nbSamples; i++) out[i * channels + ch] = (plane[i] - 128) / 128;
|
|
2786
|
-
}
|
|
2787
|
-
return { data: out, channels, sampleRate };
|
|
2788
|
-
}
|
|
2789
|
-
case AV_SAMPLE_FMT_U82: {
|
|
2790
|
-
const flat = asUint82(frame.data);
|
|
2791
|
-
for (let i = 0; i < nbSamples * channels; i++) out[i] = (flat[i] - 128) / 128;
|
|
2792
|
-
return { data: out, channels, sampleRate };
|
|
2793
|
-
}
|
|
2794
|
-
default:
|
|
2795
|
-
if (!globalThis.__avbridgeLoggedSampleFmt) {
|
|
2796
|
-
globalThis.__avbridgeLoggedSampleFmt = frame.format;
|
|
2797
|
-
console.warn(`[avbridge] unsupported audio sample format from libav: ${frame.format}`);
|
|
2798
|
-
}
|
|
2799
|
-
return null;
|
|
2800
|
-
}
|
|
2801
|
-
}
|
|
2802
|
-
function ensurePlanes2(data, channels) {
|
|
2803
|
-
if (Array.isArray(data)) return data;
|
|
2804
|
-
const arr = data;
|
|
2805
|
-
const len = arr.length;
|
|
2806
|
-
const perChannel = Math.floor(len / channels);
|
|
2807
|
-
const planes = [];
|
|
2808
|
-
for (let ch = 0; ch < channels; ch++) {
|
|
2809
|
-
planes.push(arr.subarray ? arr.subarray(ch * perChannel, (ch + 1) * perChannel) : arr);
|
|
2810
|
-
}
|
|
2811
|
-
return planes;
|
|
2812
|
-
}
|
|
2813
|
-
function asFloat322(x) {
|
|
2814
|
-
if (x instanceof Float32Array) return x;
|
|
2815
|
-
const ta = x;
|
|
2816
|
-
return new Float32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
|
|
2817
|
-
}
|
|
2818
|
-
function asInt162(x) {
|
|
2819
|
-
if (x instanceof Int16Array) return x;
|
|
2820
|
-
const ta = x;
|
|
2821
|
-
return new Int16Array(ta.buffer, ta.byteOffset, ta.byteLength / 2);
|
|
2822
|
-
}
|
|
2823
|
-
function asInt322(x) {
|
|
2824
|
-
if (x instanceof Int32Array) return x;
|
|
2825
|
-
const ta = x;
|
|
2826
|
-
return new Int32Array(ta.buffer, ta.byteOffset, ta.byteLength / 4);
|
|
2827
|
-
}
|
|
2828
|
-
function asUint82(x) {
|
|
2829
|
-
if (x instanceof Uint8Array) return x;
|
|
2830
|
-
const ta = x;
|
|
2831
|
-
return new Uint8Array(ta.buffer, ta.byteOffset, ta.byteLength);
|
|
2832
|
-
}
|
|
2833
2583
|
async function loadBridge2() {
|
|
2834
2584
|
try {
|
|
2835
|
-
const wrapper = await import('./libav-import-
|
|
2585
|
+
const wrapper = await import('./libav-import-2ZVKV2E7.cjs');
|
|
2836
2586
|
return wrapper.libavBridge;
|
|
2837
2587
|
} catch (err) {
|
|
2838
2588
|
throw new Error(
|
|
@@ -2845,8 +2595,8 @@ async function loadBridge2() {
|
|
|
2845
2595
|
var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
|
|
2846
2596
|
var READY_TIMEOUT_SECONDS2 = 3;
|
|
2847
2597
|
async function createFallbackSession(ctx, target, transport) {
|
|
2848
|
-
const { normalizeSource
|
|
2849
|
-
const source = await
|
|
2598
|
+
const { normalizeSource } = await import('./source-VFLXLOCN.cjs');
|
|
2599
|
+
const source = await normalizeSource(ctx.source);
|
|
2850
2600
|
const fps = ctx.videoTracks[0]?.fps ?? 30;
|
|
2851
2601
|
const audio = new AudioOutput();
|
|
2852
2602
|
const renderer = new VideoRenderer(target, audio, fps);
|
|
@@ -2966,7 +2716,24 @@ async function createFallbackSession(ctx, target, transport) {
|
|
|
2966
2716
|
async seek(time) {
|
|
2967
2717
|
await doSeek(time);
|
|
2968
2718
|
},
|
|
2969
|
-
async setAudioTrack(
|
|
2719
|
+
async setAudioTrack(id) {
|
|
2720
|
+
if (!ctx.audioTracks.some((t) => t.id === id)) {
|
|
2721
|
+
console.warn("[avbridge] fallback: setAudioTrack \u2014 unknown track id", id);
|
|
2722
|
+
return;
|
|
2723
|
+
}
|
|
2724
|
+
const wasPlaying = audio.isPlaying();
|
|
2725
|
+
const currentTime = audio.now();
|
|
2726
|
+
await audio.pause().catch(() => {
|
|
2727
|
+
});
|
|
2728
|
+
await handles.setAudioTrack(id, currentTime).catch(
|
|
2729
|
+
(err) => console.warn("[avbridge] fallback: handles.setAudioTrack failed:", err)
|
|
2730
|
+
);
|
|
2731
|
+
await audio.reset(currentTime);
|
|
2732
|
+
renderer.flush();
|
|
2733
|
+
if (wasPlaying) {
|
|
2734
|
+
await waitForBuffer();
|
|
2735
|
+
await audio.start();
|
|
2736
|
+
}
|
|
2970
2737
|
},
|
|
2971
2738
|
async setSubtitleTrack(_id) {
|
|
2972
2739
|
},
|
|
@@ -3020,89 +2787,6 @@ function registerBuiltins(registry) {
|
|
|
3020
2787
|
registry.register(fallbackPlugin);
|
|
3021
2788
|
}
|
|
3022
2789
|
|
|
3023
|
-
// src/subtitles/vtt.ts
|
|
3024
|
-
function isVtt(text) {
|
|
3025
|
-
const trimmed = text.replace(/^\ufeff/, "").trimStart();
|
|
3026
|
-
return trimmed.startsWith("WEBVTT");
|
|
3027
|
-
}
|
|
3028
|
-
|
|
3029
|
-
// src/subtitles/index.ts
|
|
3030
|
-
async function discoverSidecars(file, directory) {
|
|
3031
|
-
const baseName = file.name.replace(/\.[^.]+$/, "");
|
|
3032
|
-
const found = [];
|
|
3033
|
-
for await (const [name, handle] of directory) {
|
|
3034
|
-
if (handle.kind !== "file") continue;
|
|
3035
|
-
if (!name.startsWith(baseName)) continue;
|
|
3036
|
-
const lower = name.toLowerCase();
|
|
3037
|
-
let format = null;
|
|
3038
|
-
if (lower.endsWith(".srt")) format = "srt";
|
|
3039
|
-
else if (lower.endsWith(".vtt")) format = "vtt";
|
|
3040
|
-
if (!format) continue;
|
|
3041
|
-
const sidecarFile = await handle.getFile();
|
|
3042
|
-
const url = URL.createObjectURL(sidecarFile);
|
|
3043
|
-
const langMatch = name.slice(baseName.length).match(/[._-]([a-z]{2,3})(?:[._-]|\.)/i);
|
|
3044
|
-
found.push({
|
|
3045
|
-
url,
|
|
3046
|
-
format,
|
|
3047
|
-
language: langMatch?.[1]
|
|
3048
|
-
});
|
|
3049
|
-
}
|
|
3050
|
-
return found;
|
|
3051
|
-
}
|
|
3052
|
-
var SubtitleResourceBag = class {
|
|
3053
|
-
urls = /* @__PURE__ */ new Set();
|
|
3054
|
-
/** Track an externally-created blob URL (e.g. from `discoverSidecars`). */
|
|
3055
|
-
track(url) {
|
|
3056
|
-
this.urls.add(url);
|
|
3057
|
-
}
|
|
3058
|
-
/** Convenience: create a blob URL and track it in one call. */
|
|
3059
|
-
createObjectURL(blob) {
|
|
3060
|
-
const url = URL.createObjectURL(blob);
|
|
3061
|
-
this.urls.add(url);
|
|
3062
|
-
return url;
|
|
3063
|
-
}
|
|
3064
|
-
/** Revoke every tracked URL. Idempotent — safe to call multiple times. */
|
|
3065
|
-
revokeAll() {
|
|
3066
|
-
for (const u of this.urls) URL.revokeObjectURL(u);
|
|
3067
|
-
this.urls.clear();
|
|
3068
|
-
}
|
|
3069
|
-
};
|
|
3070
|
-
async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
|
|
3071
|
-
const doFetch = chunk6UUT4BEA_cjs.fetchWith(transport);
|
|
3072
|
-
for (const t of Array.from(video.querySelectorAll("track[data-avbridge]"))) {
|
|
3073
|
-
t.remove();
|
|
3074
|
-
}
|
|
3075
|
-
for (const t of tracks) {
|
|
3076
|
-
if (!t.sidecarUrl) continue;
|
|
3077
|
-
try {
|
|
3078
|
-
let url = t.sidecarUrl;
|
|
3079
|
-
if (t.format === "srt") {
|
|
3080
|
-
const res = await doFetch(t.sidecarUrl, transport?.requestInit);
|
|
3081
|
-
const text = await res.text();
|
|
3082
|
-
const vtt = srtToVtt(text);
|
|
3083
|
-
const blob = new Blob([vtt], { type: "text/vtt" });
|
|
3084
|
-
url = bag ? bag.createObjectURL(blob) : URL.createObjectURL(blob);
|
|
3085
|
-
} else if (t.format === "vtt") {
|
|
3086
|
-
const res = await doFetch(t.sidecarUrl, transport?.requestInit);
|
|
3087
|
-
const text = await res.text();
|
|
3088
|
-
if (!isVtt(text)) {
|
|
3089
|
-
console.warn("[avbridge] subtitle missing WEBVTT header:", t.sidecarUrl);
|
|
3090
|
-
}
|
|
3091
|
-
}
|
|
3092
|
-
const trackEl = document.createElement("track");
|
|
3093
|
-
trackEl.kind = "subtitles";
|
|
3094
|
-
trackEl.src = url;
|
|
3095
|
-
trackEl.srclang = t.language ?? "und";
|
|
3096
|
-
trackEl.label = t.language ?? `Subtitle ${t.id}`;
|
|
3097
|
-
trackEl.dataset.avbridge = "true";
|
|
3098
|
-
video.appendChild(trackEl);
|
|
3099
|
-
} catch (err) {
|
|
3100
|
-
const e = err instanceof Error ? err : new Error(String(err));
|
|
3101
|
-
onError?.(e, t);
|
|
3102
|
-
}
|
|
3103
|
-
}
|
|
3104
|
-
}
|
|
3105
|
-
|
|
3106
2790
|
// src/player.ts
|
|
3107
2791
|
var UnifiedPlayer = class _UnifiedPlayer {
|
|
3108
2792
|
/**
|
|
@@ -3146,7 +2830,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3146
2830
|
switchingPromise = Promise.resolve();
|
|
3147
2831
|
// Owns blob URLs created during sidecar discovery + SRT->VTT conversion.
|
|
3148
2832
|
// Revoked at destroy() so repeated source swaps don't leak.
|
|
3149
|
-
subtitleResources = new SubtitleResourceBag();
|
|
2833
|
+
subtitleResources = new chunkS4WAZC2T_cjs.SubtitleResourceBag();
|
|
3150
2834
|
// Transport config extracted from CreatePlayerOptions. Threaded to probe,
|
|
3151
2835
|
// subtitle fetches, and strategy session creators. Not stored on MediaContext
|
|
3152
2836
|
// because it's runtime config, not media analysis.
|
|
@@ -3174,7 +2858,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3174
2858
|
const bootstrapStart = performance.now();
|
|
3175
2859
|
try {
|
|
3176
2860
|
chunkG4APZMCP_cjs.dbg.info("bootstrap", "start");
|
|
3177
|
-
const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => probe(this.options.source, this.transport));
|
|
2861
|
+
const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => chunkZCUXHW55_cjs.probe(this.options.source, this.transport));
|
|
3178
2862
|
chunkG4APZMCP_cjs.dbg.info(
|
|
3179
2863
|
"probe",
|
|
3180
2864
|
`container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
|
|
@@ -3192,7 +2876,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3192
2876
|
}
|
|
3193
2877
|
}
|
|
3194
2878
|
if (this.options.directory && this.options.source instanceof File) {
|
|
3195
|
-
const found = await discoverSidecars(this.options.source, this.options.directory);
|
|
2879
|
+
const found = await chunkS4WAZC2T_cjs.discoverSidecars(this.options.source, this.options.directory);
|
|
3196
2880
|
for (const s of found) {
|
|
3197
2881
|
this.subtitleResources.track(s.url);
|
|
3198
2882
|
ctx.subtitleTracks.push({
|
|
@@ -3215,17 +2899,15 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3215
2899
|
reason: decision.reason
|
|
3216
2900
|
});
|
|
3217
2901
|
await this.startSession(decision.strategy, decision.reason);
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
(
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
);
|
|
3228
|
-
}
|
|
2902
|
+
await chunkS4WAZC2T_cjs.attachSubtitleTracks(
|
|
2903
|
+
this.options.target,
|
|
2904
|
+
ctx.subtitleTracks,
|
|
2905
|
+
this.subtitleResources,
|
|
2906
|
+
(err, track) => {
|
|
2907
|
+
console.warn(`[avbridge] subtitle ${track.id} failed: ${err.message}`);
|
|
2908
|
+
},
|
|
2909
|
+
this.transport
|
|
2910
|
+
);
|
|
3229
2911
|
this.emitter.emitSticky("tracks", {
|
|
3230
2912
|
video: ctx.videoTracks,
|
|
3231
2913
|
audio: ctx.audioTracks,
|
|
@@ -3360,8 +3042,8 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3360
3042
|
}
|
|
3361
3043
|
return;
|
|
3362
3044
|
}
|
|
3363
|
-
this.emitter.emit("error", new
|
|
3364
|
-
|
|
3045
|
+
this.emitter.emit("error", new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
3046
|
+
chunk2IJ66NTD_cjs.ERR_ALL_STRATEGIES_EXHAUSTED,
|
|
3365
3047
|
`All playback strategies failed: ${errors.join("; ")}`,
|
|
3366
3048
|
"This file may require a codec or container that isn't available in this browser. Try the fallback strategy or check browser codec support."
|
|
3367
3049
|
));
|
|
@@ -3415,7 +3097,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3415
3097
|
// ── Public: manual strategy switch ────────────────────────────────────
|
|
3416
3098
|
/** Manually switch to a different playback strategy. Preserves current position and play/pause state. Concurrent calls are serialized. */
|
|
3417
3099
|
async setStrategy(strategy, reason) {
|
|
3418
|
-
if (!this.mediaContext) throw new
|
|
3100
|
+
if (!this.mediaContext) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
|
|
3419
3101
|
if (this.session?.strategy === strategy) return;
|
|
3420
3102
|
this.switchingPromise = this.switchingPromise.then(
|
|
3421
3103
|
() => this.doSetStrategy(strategy, reason)
|
|
@@ -3478,7 +3160,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3478
3160
|
}
|
|
3479
3161
|
/** Begin or resume playback. Throws if the player is not ready. */
|
|
3480
3162
|
async play() {
|
|
3481
|
-
if (!this.session) throw new
|
|
3163
|
+
if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
|
|
3482
3164
|
this.userIntent = "play";
|
|
3483
3165
|
this.autoPausedForVisibility = false;
|
|
3484
3166
|
await this.session.play();
|
|
@@ -3517,17 +3199,17 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3517
3199
|
}
|
|
3518
3200
|
/** Seek to the given time in seconds. Throws if the player is not ready. */
|
|
3519
3201
|
async seek(time) {
|
|
3520
|
-
if (!this.session) throw new
|
|
3202
|
+
if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
|
|
3521
3203
|
await this.session.seek(time);
|
|
3522
3204
|
}
|
|
3523
3205
|
/** Switch the active audio track by track ID. Throws if the player is not ready. */
|
|
3524
3206
|
async setAudioTrack(id) {
|
|
3525
|
-
if (!this.session) throw new
|
|
3207
|
+
if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
|
|
3526
3208
|
await this.session.setAudioTrack(id);
|
|
3527
3209
|
}
|
|
3528
3210
|
/** Switch the active subtitle track by track ID, or pass `null` to disable subtitles. */
|
|
3529
3211
|
async setSubtitleTrack(id) {
|
|
3530
|
-
if (!this.session) throw new
|
|
3212
|
+
if (!this.session) throw new chunk2IJ66NTD_cjs.AvbridgeError(chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY, "Player not ready \u2014 wait for the 'ready' event before calling playback methods.", "Await the 'ready' event or check player.readyState before calling play/pause/seek.");
|
|
3531
3213
|
await this.session.setSubtitleTrack(id);
|
|
3532
3214
|
}
|
|
3533
3215
|
/** Return a snapshot of current diagnostics: container, codecs, strategy, runtime stats, and strategy history. */
|
|
@@ -3625,12 +3307,7 @@ exports.FALLBACK_VIDEO_CODECS = FALLBACK_VIDEO_CODECS;
|
|
|
3625
3307
|
exports.NATIVE_AUDIO_CODECS = NATIVE_AUDIO_CODECS;
|
|
3626
3308
|
exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
|
|
3627
3309
|
exports.UnifiedPlayer = UnifiedPlayer;
|
|
3628
|
-
exports.avbridgeAudioToMediabunny = avbridgeAudioToMediabunny;
|
|
3629
|
-
exports.avbridgeVideoToMediabunny = avbridgeVideoToMediabunny;
|
|
3630
|
-
exports.buildMediabunnySourceFromInput = buildMediabunnySourceFromInput;
|
|
3631
3310
|
exports.classifyContext = classifyContext;
|
|
3632
3311
|
exports.createPlayer = createPlayer;
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
//# sourceMappingURL=chunk-7RGG6ME7.cjs.map
|
|
3636
|
-
//# sourceMappingURL=chunk-7RGG6ME7.cjs.map
|
|
3312
|
+
//# sourceMappingURL=chunk-TBW26OPP.cjs.map
|
|
3313
|
+
//# sourceMappingURL=chunk-TBW26OPP.cjs.map
|