avbridge 2.2.1 → 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 +80 -1
- package/NOTICE.md +2 -2
- package/README.md +2 -3
- 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-UF2N5L63.cjs → chunk-7RGG6ME7.cjs} +489 -76
- 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-DMWARSEF.js → chunk-NV7ILLWH.js} +483 -74
- 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 +558 -85
- 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-U2NPmFvA.d.cts → player-B6WB74RD.d.cts} +62 -3
- package/dist/{player-U2NPmFvA.d.ts → player-B6WB74RD.d.ts} +62 -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 +104 -12
- 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 +29 -4
- package/src/strategies/fallback/variant-routing.ts +7 -13
- package/src/strategies/fallback/video-renderer.ts +124 -32
- package/src/strategies/hybrid/decoder.ts +131 -20
- package/src/strategies/hybrid/index.ts +31 -5
- package/src/strategies/remux/mse.ts +12 -2
- 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-DMWARSEF.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-UF2N5L63.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
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunk6UUT4BEA_cjs = require('./chunk-6UUT4BEA.cjs');
|
|
4
4
|
var chunkG4APZMCP_cjs = require('./chunk-G4APZMCP.cjs');
|
|
5
|
-
var
|
|
5
|
+
var chunkF3LQJKXK_cjs = require('./chunk-F3LQJKXK.cjs');
|
|
6
6
|
|
|
7
7
|
// src/probe/mediabunny.ts
|
|
8
8
|
async function probeWithMediabunny(source, sniffedContainer) {
|
|
@@ -180,37 +180,58 @@ var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
|
180
180
|
"adts",
|
|
181
181
|
"mpegts"
|
|
182
182
|
]);
|
|
183
|
-
async function probe(source) {
|
|
184
|
-
const normalized = await
|
|
185
|
-
const sniffed = await
|
|
183
|
+
async function probe(source, transport) {
|
|
184
|
+
const normalized = await chunk6UUT4BEA_cjs.normalizeSource(source, transport);
|
|
185
|
+
const sniffed = await chunk6UUT4BEA_cjs.sniffNormalizedSource(normalized);
|
|
186
186
|
if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
|
|
187
187
|
try {
|
|
188
|
-
|
|
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;
|
|
189
199
|
} catch (mediabunnyErr) {
|
|
190
200
|
console.warn(
|
|
191
201
|
`[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,
|
|
192
202
|
mediabunnyErr.message
|
|
193
203
|
);
|
|
194
204
|
try {
|
|
195
|
-
const { probeWithLibav } = await import('./avi-
|
|
205
|
+
const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
|
|
196
206
|
return await probeWithLibav(normalized, sniffed);
|
|
197
207
|
} catch (libavErr) {
|
|
198
208
|
const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
|
|
199
209
|
const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);
|
|
200
|
-
throw new
|
|
201
|
-
|
|
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."
|
|
202
214
|
);
|
|
203
215
|
}
|
|
204
216
|
}
|
|
205
217
|
}
|
|
206
218
|
try {
|
|
207
|
-
const { probeWithLibav } = await import('./avi-
|
|
219
|
+
const { probeWithLibav } = await import('./avi-W6L3BTWU.cjs');
|
|
208
220
|
return await probeWithLibav(normalized, sniffed);
|
|
209
221
|
} catch (err) {
|
|
210
222
|
const inner = err instanceof Error ? err.message : String(err);
|
|
211
223
|
console.error("[avbridge] libav probe failed for", sniffed, "file:", err);
|
|
212
|
-
|
|
213
|
-
|
|
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."
|
|
214
235
|
);
|
|
215
236
|
}
|
|
216
237
|
}
|
|
@@ -311,7 +332,9 @@ var FALLBACK_AUDIO_CODECS = /* @__PURE__ */ new Set([
|
|
|
311
332
|
"ra_144",
|
|
312
333
|
"ra_288",
|
|
313
334
|
"sipr",
|
|
314
|
-
"atrac3"
|
|
335
|
+
"atrac3",
|
|
336
|
+
"dts",
|
|
337
|
+
"truehd"
|
|
315
338
|
]);
|
|
316
339
|
var NATIVE_CONTAINERS = /* @__PURE__ */ new Set([
|
|
317
340
|
"mp4",
|
|
@@ -373,7 +396,16 @@ function classifyContext(ctx) {
|
|
|
373
396
|
reason: `video codec "${video.codec}" has no browser decoder; WASM fallback required`
|
|
374
397
|
};
|
|
375
398
|
}
|
|
376
|
-
|
|
399
|
+
const audioNeedsFallback = audio && (FALLBACK_AUDIO_CODECS.has(audio.codec) || !NATIVE_AUDIO_CODECS.has(audio.codec));
|
|
400
|
+
if (audioNeedsFallback) {
|
|
401
|
+
if (NATIVE_VIDEO_CODECS.has(video.codec) && webCodecsAvailable()) {
|
|
402
|
+
return {
|
|
403
|
+
class: "HYBRID_CANDIDATE",
|
|
404
|
+
strategy: "hybrid",
|
|
405
|
+
reason: `video "${video.codec}" is hardware-decodable via WebCodecs; audio "${audio.codec}" decoded in software by libav`,
|
|
406
|
+
fallbackChain: ["fallback"]
|
|
407
|
+
};
|
|
408
|
+
}
|
|
377
409
|
return {
|
|
378
410
|
class: "FALLBACK_REQUIRED",
|
|
379
411
|
strategy: "fallback",
|
|
@@ -740,10 +772,18 @@ var MseSink = class {
|
|
|
740
772
|
constructor(options) {
|
|
741
773
|
this.options = options;
|
|
742
774
|
if (typeof MediaSource === "undefined") {
|
|
743
|
-
throw new
|
|
775
|
+
throw new chunk6UUT4BEA_cjs.AvbridgeError(
|
|
776
|
+
chunk6UUT4BEA_cjs.ERR_MSE_NOT_SUPPORTED,
|
|
777
|
+
"MediaSource Extensions (MSE) are not supported in this environment.",
|
|
778
|
+
"MSE is required for the remux strategy. Use a browser that supports MSE, or try the fallback strategy."
|
|
779
|
+
);
|
|
744
780
|
}
|
|
745
781
|
if (!MediaSource.isTypeSupported(options.mime)) {
|
|
746
|
-
throw new
|
|
782
|
+
throw new chunk6UUT4BEA_cjs.AvbridgeError(
|
|
783
|
+
chunk6UUT4BEA_cjs.ERR_MSE_CODEC_NOT_SUPPORTED,
|
|
784
|
+
`This browser's MSE does not support "${options.mime}".`,
|
|
785
|
+
"The codec combination can't be played via remux in this browser. The player will try the next strategy automatically."
|
|
786
|
+
);
|
|
747
787
|
}
|
|
748
788
|
this.mediaSource = new MediaSource();
|
|
749
789
|
this.objectUrl = URL.createObjectURL(this.mediaSource);
|
|
@@ -1159,6 +1199,10 @@ async function createRemuxSession(context, video) {
|
|
|
1159
1199
|
}
|
|
1160
1200
|
|
|
1161
1201
|
// src/strategies/fallback/video-renderer.ts
|
|
1202
|
+
function isDebug() {
|
|
1203
|
+
return typeof globalThis !== "undefined" && !!globalThis.AVBRIDGE_DEBUG;
|
|
1204
|
+
}
|
|
1205
|
+
var lastDebugLog = 0;
|
|
1162
1206
|
var VideoRenderer = class {
|
|
1163
1207
|
constructor(target, clock, fps = 30) {
|
|
1164
1208
|
this.target = target;
|
|
@@ -1205,6 +1249,20 @@ var VideoRenderer = class {
|
|
|
1205
1249
|
lastPaintWall = 0;
|
|
1206
1250
|
/** Minimum ms between paints — paces video at roughly source fps. */
|
|
1207
1251
|
paintIntervalMs;
|
|
1252
|
+
/** Cumulative count of frames skipped because all PTS are in the future. */
|
|
1253
|
+
ticksWaiting = 0;
|
|
1254
|
+
/** Cumulative count of ticks where PTS mode painted a frame. */
|
|
1255
|
+
ticksPainted = 0;
|
|
1256
|
+
/**
|
|
1257
|
+
* Calibration offset (microseconds) between video PTS and audio clock.
|
|
1258
|
+
* Video PTS and AudioContext.currentTime can drift ~0.1% relative to
|
|
1259
|
+
* each other (different clock domains). Over 45 minutes that's 2.6s.
|
|
1260
|
+
* We measure the offset on the first painted frame and update it
|
|
1261
|
+
* periodically so the PTS comparison stays calibrated.
|
|
1262
|
+
*/
|
|
1263
|
+
ptsCalibrationUs = 0;
|
|
1264
|
+
ptsCalibrated = false;
|
|
1265
|
+
lastCalibrationWall = 0;
|
|
1208
1266
|
/** Resolves once the first decoded frame has been enqueued. */
|
|
1209
1267
|
firstFrameReady;
|
|
1210
1268
|
resolveFirstFrame;
|
|
@@ -1253,21 +1311,81 @@ var VideoRenderer = class {
|
|
|
1253
1311
|
}
|
|
1254
1312
|
return;
|
|
1255
1313
|
}
|
|
1256
|
-
const
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
if (
|
|
1260
|
-
const
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
this.
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1314
|
+
const rawAudioNowUs = this.clock.now() * 1e6;
|
|
1315
|
+
const headTs = this.queue[0].timestamp ?? 0;
|
|
1316
|
+
const hasPts = headTs > 0 || this.queue.length > 1;
|
|
1317
|
+
if (hasPts) {
|
|
1318
|
+
const wallNow2 = performance.now();
|
|
1319
|
+
if (!this.ptsCalibrated || wallNow2 - this.lastCalibrationWall > 1e4) {
|
|
1320
|
+
this.ptsCalibrationUs = headTs - rawAudioNowUs;
|
|
1321
|
+
this.ptsCalibrated = true;
|
|
1322
|
+
this.lastCalibrationWall = wallNow2;
|
|
1323
|
+
}
|
|
1324
|
+
const audioNowUs = rawAudioNowUs + this.ptsCalibrationUs;
|
|
1325
|
+
const frameDurationUs = this.paintIntervalMs * 1e3;
|
|
1326
|
+
const deadlineUs = audioNowUs + frameDurationUs;
|
|
1327
|
+
let bestIdx = -1;
|
|
1328
|
+
for (let i = 0; i < this.queue.length; i++) {
|
|
1329
|
+
const ts = this.queue[i].timestamp ?? 0;
|
|
1330
|
+
if (ts <= deadlineUs) {
|
|
1331
|
+
bestIdx = i;
|
|
1332
|
+
} else {
|
|
1333
|
+
break;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
if (bestIdx < 0) {
|
|
1337
|
+
this.ticksWaiting++;
|
|
1338
|
+
if (isDebug()) {
|
|
1339
|
+
const now = performance.now();
|
|
1340
|
+
if (now - lastDebugLog > 1e3) {
|
|
1341
|
+
const headPtsMs = (headTs / 1e3).toFixed(1);
|
|
1342
|
+
const audioMs = (audioNowUs / 1e3).toFixed(1);
|
|
1343
|
+
const rawDriftMs = ((headTs - rawAudioNowUs) / 1e3).toFixed(1);
|
|
1344
|
+
const calibMs = (this.ptsCalibrationUs / 1e3).toFixed(1);
|
|
1345
|
+
console.log(
|
|
1346
|
+
`[avbridge:renderer] WAIT q=${this.queue.length} headPTS=${headPtsMs}ms calibAudio=${audioMs}ms rawDrift=${rawDriftMs}ms calib=${calibMs}ms painted=${this.framesPainted} dropped=${this.framesDroppedLate}`
|
|
1347
|
+
);
|
|
1348
|
+
lastDebugLog = now;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1268
1351
|
return;
|
|
1269
1352
|
}
|
|
1353
|
+
const dropThresholdUs = audioNowUs - frameDurationUs * 2;
|
|
1354
|
+
let dropped = 0;
|
|
1355
|
+
while (bestIdx > 0) {
|
|
1356
|
+
const ts = this.queue[0].timestamp ?? 0;
|
|
1357
|
+
if (ts < dropThresholdUs) {
|
|
1358
|
+
this.queue.shift()?.close();
|
|
1359
|
+
this.framesDroppedLate++;
|
|
1360
|
+
bestIdx--;
|
|
1361
|
+
dropped++;
|
|
1362
|
+
} else {
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
this.ticksPainted++;
|
|
1367
|
+
if (isDebug()) {
|
|
1368
|
+
const now = performance.now();
|
|
1369
|
+
if (now - lastDebugLog > 1e3) {
|
|
1370
|
+
const paintedTs = this.queue[0]?.timestamp ?? 0;
|
|
1371
|
+
const audioMs = (audioNowUs / 1e3).toFixed(1);
|
|
1372
|
+
const ptsMs = (paintedTs / 1e3).toFixed(1);
|
|
1373
|
+
const rawDriftMs = ((paintedTs - rawAudioNowUs) / 1e3).toFixed(1);
|
|
1374
|
+
const calibMs = (this.ptsCalibrationUs / 1e3).toFixed(1);
|
|
1375
|
+
console.log(
|
|
1376
|
+
`[avbridge:renderer] PAINT q=${this.queue.length} calibAudio=${audioMs}ms nextPTS=${ptsMs}ms rawDrift=${rawDriftMs}ms calib=${calibMs}ms dropped=${dropped} total_drops=${this.framesDroppedLate} painted=${this.framesPainted}`
|
|
1377
|
+
);
|
|
1378
|
+
lastDebugLog = now;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
const frame2 = this.queue.shift();
|
|
1382
|
+
this.paint(frame2);
|
|
1383
|
+
frame2.close();
|
|
1384
|
+
this.lastPaintWall = performance.now();
|
|
1385
|
+
return;
|
|
1270
1386
|
}
|
|
1387
|
+
const wallNow = performance.now();
|
|
1388
|
+
if (wallNow - this.lastPaintWall < this.paintIntervalMs - 2) return;
|
|
1271
1389
|
const frame = this.queue.shift();
|
|
1272
1390
|
this.paint(frame);
|
|
1273
1391
|
frame.close();
|
|
@@ -1289,8 +1407,13 @@ var VideoRenderer = class {
|
|
|
1289
1407
|
}
|
|
1290
1408
|
/** Discard all queued frames. Used by seek to drop stale buffers. */
|
|
1291
1409
|
flush() {
|
|
1410
|
+
const count = this.queue.length;
|
|
1292
1411
|
while (this.queue.length > 0) this.queue.shift()?.close();
|
|
1293
1412
|
this.prerolled = false;
|
|
1413
|
+
this.ptsCalibrated = false;
|
|
1414
|
+
if (isDebug() && count > 0) {
|
|
1415
|
+
console.log(`[avbridge:renderer] FLUSH discarded=${count} painted=${this.framesPainted} drops=${this.framesDroppedLate}`);
|
|
1416
|
+
}
|
|
1294
1417
|
}
|
|
1295
1418
|
stats() {
|
|
1296
1419
|
return {
|
|
@@ -1335,11 +1458,38 @@ var AudioOutput = class {
|
|
|
1335
1458
|
pendingQueue = [];
|
|
1336
1459
|
framesScheduled = 0;
|
|
1337
1460
|
destroyed = false;
|
|
1461
|
+
/** User-set volume (0..1). Applied to the gain node. */
|
|
1462
|
+
_volume = 1;
|
|
1463
|
+
/** User-set muted flag. When true, gain is forced to 0. */
|
|
1464
|
+
_muted = false;
|
|
1338
1465
|
constructor() {
|
|
1339
1466
|
this.ctx = new AudioContext();
|
|
1340
1467
|
this.gain = this.ctx.createGain();
|
|
1341
1468
|
this.gain.connect(this.ctx.destination);
|
|
1342
1469
|
}
|
|
1470
|
+
/** Set volume (0..1). Applied immediately to the gain node. */
|
|
1471
|
+
setVolume(v) {
|
|
1472
|
+
this._volume = Math.max(0, Math.min(1, v));
|
|
1473
|
+
this.applyGain();
|
|
1474
|
+
}
|
|
1475
|
+
getVolume() {
|
|
1476
|
+
return this._volume;
|
|
1477
|
+
}
|
|
1478
|
+
/** Set muted. When true, output is silenced regardless of volume. */
|
|
1479
|
+
setMuted(m) {
|
|
1480
|
+
this._muted = m;
|
|
1481
|
+
this.applyGain();
|
|
1482
|
+
}
|
|
1483
|
+
getMuted() {
|
|
1484
|
+
return this._muted;
|
|
1485
|
+
}
|
|
1486
|
+
applyGain() {
|
|
1487
|
+
const target = this._muted ? 0 : this._volume;
|
|
1488
|
+
try {
|
|
1489
|
+
this.gain.gain.value = target;
|
|
1490
|
+
} catch {
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1343
1493
|
/**
|
|
1344
1494
|
* Switch into wall-clock fallback mode. Called by the decoder when no
|
|
1345
1495
|
* audio decoder could be initialized for the source. Once set, this
|
|
@@ -1489,6 +1639,7 @@ var AudioOutput = class {
|
|
|
1489
1639
|
}
|
|
1490
1640
|
this.gain = this.ctx.createGain();
|
|
1491
1641
|
this.gain.connect(this.ctx.destination);
|
|
1642
|
+
this.applyGain();
|
|
1492
1643
|
this.pendingQueue = [];
|
|
1493
1644
|
this.mediaTimeOfAnchor = newMediaTime;
|
|
1494
1645
|
this.mediaTimeOfNext = newMediaTime;
|
|
@@ -1517,11 +1668,11 @@ var AudioOutput = class {
|
|
|
1517
1668
|
|
|
1518
1669
|
// src/strategies/hybrid/decoder.ts
|
|
1519
1670
|
async function startHybridDecoder(opts) {
|
|
1520
|
-
const variant =
|
|
1671
|
+
const variant = chunkF3LQJKXK_cjs.pickLibavVariant(opts.context);
|
|
1521
1672
|
const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
|
|
1522
1673
|
const bridge = await loadBridge();
|
|
1523
|
-
const { prepareLibavInput } = await import('./libav-http-reader-
|
|
1524
|
-
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source);
|
|
1674
|
+
const { prepareLibavInput } = await import('./libav-http-reader-AZLE7YFS.cjs');
|
|
1675
|
+
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
|
|
1525
1676
|
const readPkt = await libav.av_packet_alloc();
|
|
1526
1677
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
1527
1678
|
const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
|
|
@@ -1592,6 +1743,56 @@ async function startHybridDecoder(opts) {
|
|
|
1592
1743
|
});
|
|
1593
1744
|
throw new Error("hybrid decoder: could not initialize any decoders");
|
|
1594
1745
|
}
|
|
1746
|
+
let bsfCtx = null;
|
|
1747
|
+
let bsfPkt = null;
|
|
1748
|
+
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
1749
|
+
try {
|
|
1750
|
+
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
1751
|
+
if (bsfCtx != null && bsfCtx >= 0) {
|
|
1752
|
+
const parIn = await libav.AVBSFContext_par_in(bsfCtx);
|
|
1753
|
+
await libav.avcodec_parameters_copy(parIn, videoStream.codecpar);
|
|
1754
|
+
await libav.av_bsf_init(bsfCtx);
|
|
1755
|
+
bsfPkt = await libav.av_packet_alloc();
|
|
1756
|
+
chunkG4APZMCP_cjs.dbg.info("bsf", "mpeg4_unpack_bframes BSF active (hybrid)");
|
|
1757
|
+
} else {
|
|
1758
|
+
console.warn("[avbridge] mpeg4_unpack_bframes BSF not available in hybrid decoder");
|
|
1759
|
+
bsfCtx = null;
|
|
1760
|
+
}
|
|
1761
|
+
} catch (err) {
|
|
1762
|
+
console.warn("[avbridge] hybrid: failed to init BSF:", err.message);
|
|
1763
|
+
bsfCtx = null;
|
|
1764
|
+
bsfPkt = null;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
async function applyBSF(packets) {
|
|
1768
|
+
if (!bsfCtx || !bsfPkt) return packets;
|
|
1769
|
+
const out = [];
|
|
1770
|
+
for (const pkt of packets) {
|
|
1771
|
+
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
1772
|
+
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
1773
|
+
if (sendErr < 0) {
|
|
1774
|
+
out.push(pkt);
|
|
1775
|
+
continue;
|
|
1776
|
+
}
|
|
1777
|
+
while (true) {
|
|
1778
|
+
const recvErr = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
1779
|
+
if (recvErr < 0) break;
|
|
1780
|
+
out.push(await libav.ff_copyout_packet(bsfPkt));
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return out;
|
|
1784
|
+
}
|
|
1785
|
+
async function flushBSF() {
|
|
1786
|
+
if (!bsfCtx || !bsfPkt) return;
|
|
1787
|
+
try {
|
|
1788
|
+
await libav.av_bsf_send_packet(bsfCtx, 0);
|
|
1789
|
+
while (true) {
|
|
1790
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
1791
|
+
if (err < 0) break;
|
|
1792
|
+
}
|
|
1793
|
+
} catch {
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1595
1796
|
let destroyed = false;
|
|
1596
1797
|
let pumpToken = 0;
|
|
1597
1798
|
let pumpRunning = null;
|
|
@@ -1619,8 +1820,15 @@ async function startHybridDecoder(opts) {
|
|
|
1619
1820
|
if (myToken !== pumpToken || destroyed) return;
|
|
1620
1821
|
const videoPackets = videoStream ? packets[videoStream.index] : void 0;
|
|
1621
1822
|
const audioPackets = audioStream ? packets[audioStream.index] : void 0;
|
|
1823
|
+
if (audioDec && audioPackets && audioPackets.length > 0) {
|
|
1824
|
+
await decodeAudioBatch(audioPackets, myToken);
|
|
1825
|
+
}
|
|
1826
|
+
if (myToken !== pumpToken || destroyed) return;
|
|
1827
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
1828
|
+
if (myToken !== pumpToken || destroyed) return;
|
|
1622
1829
|
if (videoDecoder && videoPackets && videoPackets.length > 0) {
|
|
1623
|
-
|
|
1830
|
+
const processed = await applyBSF(videoPackets);
|
|
1831
|
+
for (const pkt of processed) {
|
|
1624
1832
|
if (myToken !== pumpToken || destroyed) return;
|
|
1625
1833
|
sanitizePacketTimestamp(pkt, () => {
|
|
1626
1834
|
const ts = syntheticVideoUs;
|
|
@@ -1640,9 +1848,6 @@ async function startHybridDecoder(opts) {
|
|
|
1640
1848
|
}
|
|
1641
1849
|
}
|
|
1642
1850
|
}
|
|
1643
|
-
if (audioDec && audioPackets && audioPackets.length > 0) {
|
|
1644
|
-
await decodeAudioBatch(audioPackets, myToken);
|
|
1645
|
-
}
|
|
1646
1851
|
packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
|
|
1647
1852
|
while (!destroyed && myToken === pumpToken && (videoDecoder && videoDecoder.decodeQueueSize > 10 || opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
|
|
1648
1853
|
await new Promise((r) => setTimeout(r, 50));
|
|
@@ -1665,20 +1870,43 @@ async function startHybridDecoder(opts) {
|
|
|
1665
1870
|
}
|
|
1666
1871
|
async function decodeAudioBatch(pkts, myToken, flush = false) {
|
|
1667
1872
|
if (!audioDec || destroyed || myToken !== pumpToken) return;
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1873
|
+
const AUDIO_SUB_BATCH = 4;
|
|
1874
|
+
let allFrames = [];
|
|
1875
|
+
for (let i = 0; i < pkts.length; i += AUDIO_SUB_BATCH) {
|
|
1876
|
+
if (myToken !== pumpToken || destroyed) return;
|
|
1877
|
+
const slice = pkts.slice(i, i + AUDIO_SUB_BATCH);
|
|
1878
|
+
const isLast = i + AUDIO_SUB_BATCH >= pkts.length;
|
|
1879
|
+
try {
|
|
1880
|
+
const frames2 = await libav.ff_decode_multi(
|
|
1881
|
+
audioDec.c,
|
|
1882
|
+
audioDec.pkt,
|
|
1883
|
+
audioDec.frame,
|
|
1884
|
+
slice,
|
|
1885
|
+
isLast && flush ? { fin: true, ignoreErrors: true } : { ignoreErrors: true }
|
|
1886
|
+
);
|
|
1887
|
+
allFrames = allFrames.concat(frames2);
|
|
1888
|
+
} catch (err) {
|
|
1889
|
+
console.error("[avbridge] hybrid audio decode failed:", err);
|
|
1890
|
+
return;
|
|
1891
|
+
}
|
|
1892
|
+
if (!isLast) await new Promise((r) => setTimeout(r, 0));
|
|
1893
|
+
}
|
|
1894
|
+
if (pkts.length === 0 && flush) {
|
|
1895
|
+
try {
|
|
1896
|
+
allFrames = await libav.ff_decode_multi(
|
|
1897
|
+
audioDec.c,
|
|
1898
|
+
audioDec.pkt,
|
|
1899
|
+
audioDec.frame,
|
|
1900
|
+
[],
|
|
1901
|
+
{ fin: true, ignoreErrors: true }
|
|
1902
|
+
);
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
console.error("[avbridge] hybrid audio flush failed:", err);
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1680
1907
|
}
|
|
1681
1908
|
if (myToken !== pumpToken || destroyed) return;
|
|
1909
|
+
const frames = allFrames;
|
|
1682
1910
|
for (const f of frames) {
|
|
1683
1911
|
if (myToken !== pumpToken || destroyed) return;
|
|
1684
1912
|
sanitizeFrameTimestamp(
|
|
@@ -1715,6 +1943,14 @@ async function startHybridDecoder(opts) {
|
|
|
1715
1943
|
await pumpRunning;
|
|
1716
1944
|
} catch {
|
|
1717
1945
|
}
|
|
1946
|
+
try {
|
|
1947
|
+
if (bsfCtx) await libav.av_bsf_free(bsfCtx);
|
|
1948
|
+
} catch {
|
|
1949
|
+
}
|
|
1950
|
+
try {
|
|
1951
|
+
if (bsfPkt) await libav.av_packet_free?.(bsfPkt);
|
|
1952
|
+
} catch {
|
|
1953
|
+
}
|
|
1718
1954
|
try {
|
|
1719
1955
|
if (videoDecoder && videoDecoder.state !== "closed") videoDecoder.close();
|
|
1720
1956
|
} catch {
|
|
@@ -1768,6 +2004,7 @@ async function startHybridDecoder(opts) {
|
|
|
1768
2004
|
if (audioDec) await libav.avcodec_flush_buffers?.(audioDec.c);
|
|
1769
2005
|
} catch {
|
|
1770
2006
|
}
|
|
2007
|
+
await flushBSF();
|
|
1771
2008
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
1772
2009
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
1773
2010
|
pumpRunning = pumpLoop(newToken).catch(
|
|
@@ -1781,6 +2018,7 @@ async function startHybridDecoder(opts) {
|
|
|
1781
2018
|
videoFramesDecoded,
|
|
1782
2019
|
videoChunksFed,
|
|
1783
2020
|
audioFramesDecoded,
|
|
2021
|
+
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
1784
2022
|
videoDecodeQueueSize: videoDecoder?.decodeQueueSize ?? 0,
|
|
1785
2023
|
// Confirmed transport info — see fallback decoder for the pattern.
|
|
1786
2024
|
_transport: inputHandle.transport === "http-range" ? "http-range" : "memory",
|
|
@@ -1957,8 +2195,8 @@ async function loadBridge() {
|
|
|
1957
2195
|
// src/strategies/hybrid/index.ts
|
|
1958
2196
|
var READY_AUDIO_BUFFER_SECONDS = 0.3;
|
|
1959
2197
|
var READY_TIMEOUT_SECONDS = 10;
|
|
1960
|
-
async function createHybridSession(ctx, target) {
|
|
1961
|
-
const { normalizeSource: normalizeSource2 } = await import('./source-
|
|
2198
|
+
async function createHybridSession(ctx, target, transport) {
|
|
2199
|
+
const { normalizeSource: normalizeSource2 } = await import('./source-73CAH6HW.cjs');
|
|
1962
2200
|
const source = await normalizeSource2(ctx.source);
|
|
1963
2201
|
const fps = ctx.videoTracks[0]?.fps ?? 30;
|
|
1964
2202
|
const audio = new AudioOutput();
|
|
@@ -1970,7 +2208,8 @@ async function createHybridSession(ctx, target) {
|
|
|
1970
2208
|
filename: ctx.name ?? "input.bin",
|
|
1971
2209
|
context: ctx,
|
|
1972
2210
|
renderer,
|
|
1973
|
-
audio
|
|
2211
|
+
audio,
|
|
2212
|
+
transport
|
|
1974
2213
|
});
|
|
1975
2214
|
} catch (err) {
|
|
1976
2215
|
audio.destroy();
|
|
@@ -1988,6 +2227,22 @@ async function createHybridSession(ctx, target) {
|
|
|
1988
2227
|
configurable: true,
|
|
1989
2228
|
get: () => !audio.isPlaying()
|
|
1990
2229
|
});
|
|
2230
|
+
Object.defineProperty(target, "volume", {
|
|
2231
|
+
configurable: true,
|
|
2232
|
+
get: () => audio.getVolume(),
|
|
2233
|
+
set: (v) => {
|
|
2234
|
+
audio.setVolume(v);
|
|
2235
|
+
target.dispatchEvent(new Event("volumechange"));
|
|
2236
|
+
}
|
|
2237
|
+
});
|
|
2238
|
+
Object.defineProperty(target, "muted", {
|
|
2239
|
+
configurable: true,
|
|
2240
|
+
get: () => audio.getMuted(),
|
|
2241
|
+
set: (m) => {
|
|
2242
|
+
audio.setMuted(m);
|
|
2243
|
+
target.dispatchEvent(new Event("volumechange"));
|
|
2244
|
+
}
|
|
2245
|
+
});
|
|
1991
2246
|
if (ctx.duration && Number.isFinite(ctx.duration)) {
|
|
1992
2247
|
Object.defineProperty(target, "duration", {
|
|
1993
2248
|
configurable: true,
|
|
@@ -2027,10 +2282,13 @@ async function createHybridSession(ctx, target) {
|
|
|
2027
2282
|
if (!audio.isPlaying()) {
|
|
2028
2283
|
await waitForBuffer();
|
|
2029
2284
|
await audio.start();
|
|
2285
|
+
target.dispatchEvent(new Event("play"));
|
|
2286
|
+
target.dispatchEvent(new Event("playing"));
|
|
2030
2287
|
}
|
|
2031
2288
|
},
|
|
2032
2289
|
pause() {
|
|
2033
2290
|
void audio.pause();
|
|
2291
|
+
target.dispatchEvent(new Event("pause"));
|
|
2034
2292
|
},
|
|
2035
2293
|
async seek(time) {
|
|
2036
2294
|
await doSeek(time);
|
|
@@ -2053,6 +2311,8 @@ async function createHybridSession(ctx, target) {
|
|
|
2053
2311
|
delete target.currentTime;
|
|
2054
2312
|
delete target.duration;
|
|
2055
2313
|
delete target.paused;
|
|
2314
|
+
delete target.volume;
|
|
2315
|
+
delete target.muted;
|
|
2056
2316
|
} catch {
|
|
2057
2317
|
}
|
|
2058
2318
|
},
|
|
@@ -2064,11 +2324,11 @@ async function createHybridSession(ctx, target) {
|
|
|
2064
2324
|
|
|
2065
2325
|
// src/strategies/fallback/decoder.ts
|
|
2066
2326
|
async function startDecoder(opts) {
|
|
2067
|
-
const variant =
|
|
2327
|
+
const variant = chunkF3LQJKXK_cjs.pickLibavVariant(opts.context);
|
|
2068
2328
|
const libav = await chunkG4APZMCP_cjs.loadLibav(variant);
|
|
2069
2329
|
const bridge = await loadBridge2();
|
|
2070
|
-
const { prepareLibavInput } = await import('./libav-http-reader-
|
|
2071
|
-
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source);
|
|
2330
|
+
const { prepareLibavInput } = await import('./libav-http-reader-AZLE7YFS.cjs');
|
|
2331
|
+
const inputHandle = await prepareLibavInput(libav, opts.filename, opts.source, opts.transport);
|
|
2072
2332
|
const readPkt = await libav.av_packet_alloc();
|
|
2073
2333
|
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(opts.filename);
|
|
2074
2334
|
const videoStream = streams.find((s) => s.codec_type === libav.AVMEDIA_TYPE_VIDEO) ?? null;
|
|
@@ -2124,6 +2384,56 @@ async function startDecoder(opts) {
|
|
|
2124
2384
|
`fallback decoder: could not initialize any libav decoders (${codecs}).${hint}`
|
|
2125
2385
|
);
|
|
2126
2386
|
}
|
|
2387
|
+
let bsfCtx = null;
|
|
2388
|
+
let bsfPkt = null;
|
|
2389
|
+
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
2390
|
+
try {
|
|
2391
|
+
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
2392
|
+
if (bsfCtx != null && bsfCtx >= 0) {
|
|
2393
|
+
const parIn = await libav.AVBSFContext_par_in(bsfCtx);
|
|
2394
|
+
await libav.avcodec_parameters_copy(parIn, videoStream.codecpar);
|
|
2395
|
+
await libav.av_bsf_init(bsfCtx);
|
|
2396
|
+
bsfPkt = await libav.av_packet_alloc();
|
|
2397
|
+
chunkG4APZMCP_cjs.dbg.info("bsf", "mpeg4_unpack_bframes BSF active");
|
|
2398
|
+
} else {
|
|
2399
|
+
console.warn("[avbridge] mpeg4_unpack_bframes BSF not available \u2014 decoding without it");
|
|
2400
|
+
bsfCtx = null;
|
|
2401
|
+
}
|
|
2402
|
+
} catch (err) {
|
|
2403
|
+
console.warn("[avbridge] failed to init mpeg4_unpack_bframes BSF:", err.message);
|
|
2404
|
+
bsfCtx = null;
|
|
2405
|
+
bsfPkt = null;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
async function applyBSF(packets) {
|
|
2409
|
+
if (!bsfCtx || !bsfPkt) return packets;
|
|
2410
|
+
const out = [];
|
|
2411
|
+
for (const pkt of packets) {
|
|
2412
|
+
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
2413
|
+
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
2414
|
+
if (sendErr < 0) {
|
|
2415
|
+
out.push(pkt);
|
|
2416
|
+
continue;
|
|
2417
|
+
}
|
|
2418
|
+
while (true) {
|
|
2419
|
+
const recvErr = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
2420
|
+
if (recvErr < 0) break;
|
|
2421
|
+
out.push(await libav.ff_copyout_packet(bsfPkt));
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
return out;
|
|
2425
|
+
}
|
|
2426
|
+
async function flushBSF() {
|
|
2427
|
+
if (!bsfCtx || !bsfPkt) return;
|
|
2428
|
+
try {
|
|
2429
|
+
await libav.av_bsf_send_packet(bsfCtx, 0);
|
|
2430
|
+
while (true) {
|
|
2431
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
2432
|
+
if (err < 0) break;
|
|
2433
|
+
}
|
|
2434
|
+
} catch {
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2127
2437
|
let destroyed = false;
|
|
2128
2438
|
let pumpToken = 0;
|
|
2129
2439
|
let pumpRunning = null;
|
|
@@ -2159,7 +2469,8 @@ async function startDecoder(opts) {
|
|
|
2159
2469
|
}
|
|
2160
2470
|
if (myToken !== pumpToken || destroyed) return;
|
|
2161
2471
|
if (videoDec && videoPackets && videoPackets.length > 0) {
|
|
2162
|
-
await
|
|
2472
|
+
const processed = await applyBSF(videoPackets);
|
|
2473
|
+
await decodeVideoBatch(processed, myToken);
|
|
2163
2474
|
}
|
|
2164
2475
|
packetsRead += (videoPackets?.length ?? 0) + (audioPackets?.length ?? 0);
|
|
2165
2476
|
if (videoFramesDecoded > 0) {
|
|
@@ -2305,6 +2616,14 @@ async function startDecoder(opts) {
|
|
|
2305
2616
|
await pumpRunning;
|
|
2306
2617
|
} catch {
|
|
2307
2618
|
}
|
|
2619
|
+
try {
|
|
2620
|
+
if (bsfCtx) await libav.av_bsf_free(bsfCtx);
|
|
2621
|
+
} catch {
|
|
2622
|
+
}
|
|
2623
|
+
try {
|
|
2624
|
+
if (bsfPkt) await libav.av_packet_free?.(bsfPkt);
|
|
2625
|
+
} catch {
|
|
2626
|
+
}
|
|
2308
2627
|
try {
|
|
2309
2628
|
if (videoDec) await libav.ff_free_decoder?.(videoDec.c, videoDec.pkt, videoDec.frame);
|
|
2310
2629
|
} catch {
|
|
@@ -2356,6 +2675,7 @@ async function startDecoder(opts) {
|
|
|
2356
2675
|
if (audioDec) await libav.avcodec_flush_buffers?.(audioDec.c);
|
|
2357
2676
|
} catch {
|
|
2358
2677
|
}
|
|
2678
|
+
await flushBSF();
|
|
2359
2679
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
2360
2680
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
2361
2681
|
pumpRunning = pumpLoop(newToken).catch(
|
|
@@ -2368,6 +2688,7 @@ async function startDecoder(opts) {
|
|
|
2368
2688
|
packetsRead,
|
|
2369
2689
|
videoFramesDecoded,
|
|
2370
2690
|
audioFramesDecoded,
|
|
2691
|
+
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
2371
2692
|
// Confirmed transport info: once prepareLibavInput returns
|
|
2372
2693
|
// successfully, we *know* whether the source is http-range (probe
|
|
2373
2694
|
// succeeded and returned 206) or in-memory blob. Diagnostics hoists
|
|
@@ -2523,8 +2844,8 @@ async function loadBridge2() {
|
|
|
2523
2844
|
// src/strategies/fallback/index.ts
|
|
2524
2845
|
var READY_AUDIO_BUFFER_SECONDS2 = 0.04;
|
|
2525
2846
|
var READY_TIMEOUT_SECONDS2 = 3;
|
|
2526
|
-
async function createFallbackSession(ctx, target) {
|
|
2527
|
-
const { normalizeSource: normalizeSource2 } = await import('./source-
|
|
2847
|
+
async function createFallbackSession(ctx, target, transport) {
|
|
2848
|
+
const { normalizeSource: normalizeSource2 } = await import('./source-73CAH6HW.cjs');
|
|
2528
2849
|
const source = await normalizeSource2(ctx.source);
|
|
2529
2850
|
const fps = ctx.videoTracks[0]?.fps ?? 30;
|
|
2530
2851
|
const audio = new AudioOutput();
|
|
@@ -2536,7 +2857,8 @@ async function createFallbackSession(ctx, target) {
|
|
|
2536
2857
|
filename: ctx.name ?? "input.bin",
|
|
2537
2858
|
context: ctx,
|
|
2538
2859
|
renderer,
|
|
2539
|
-
audio
|
|
2860
|
+
audio,
|
|
2861
|
+
transport
|
|
2540
2862
|
});
|
|
2541
2863
|
} catch (err) {
|
|
2542
2864
|
audio.destroy();
|
|
@@ -2554,6 +2876,22 @@ async function createFallbackSession(ctx, target) {
|
|
|
2554
2876
|
configurable: true,
|
|
2555
2877
|
get: () => !audio.isPlaying()
|
|
2556
2878
|
});
|
|
2879
|
+
Object.defineProperty(target, "volume", {
|
|
2880
|
+
configurable: true,
|
|
2881
|
+
get: () => audio.getVolume(),
|
|
2882
|
+
set: (v) => {
|
|
2883
|
+
audio.setVolume(v);
|
|
2884
|
+
target.dispatchEvent(new Event("volumechange"));
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
Object.defineProperty(target, "muted", {
|
|
2888
|
+
configurable: true,
|
|
2889
|
+
get: () => audio.getMuted(),
|
|
2890
|
+
set: (m) => {
|
|
2891
|
+
audio.setMuted(m);
|
|
2892
|
+
target.dispatchEvent(new Event("volumechange"));
|
|
2893
|
+
}
|
|
2894
|
+
});
|
|
2557
2895
|
if (ctx.duration && Number.isFinite(ctx.duration)) {
|
|
2558
2896
|
Object.defineProperty(target, "duration", {
|
|
2559
2897
|
configurable: true,
|
|
@@ -2617,10 +2955,13 @@ async function createFallbackSession(ctx, target) {
|
|
|
2617
2955
|
if (!audio.isPlaying()) {
|
|
2618
2956
|
await waitForBuffer();
|
|
2619
2957
|
await audio.start();
|
|
2958
|
+
target.dispatchEvent(new Event("play"));
|
|
2959
|
+
target.dispatchEvent(new Event("playing"));
|
|
2620
2960
|
}
|
|
2621
2961
|
},
|
|
2622
2962
|
pause() {
|
|
2623
2963
|
void audio.pause();
|
|
2964
|
+
target.dispatchEvent(new Event("pause"));
|
|
2624
2965
|
},
|
|
2625
2966
|
async seek(time) {
|
|
2626
2967
|
await doSeek(time);
|
|
@@ -2640,6 +2981,8 @@ async function createFallbackSession(ctx, target) {
|
|
|
2640
2981
|
delete target.currentTime;
|
|
2641
2982
|
delete target.duration;
|
|
2642
2983
|
delete target.paused;
|
|
2984
|
+
delete target.volume;
|
|
2985
|
+
delete target.muted;
|
|
2643
2986
|
} catch {
|
|
2644
2987
|
}
|
|
2645
2988
|
},
|
|
@@ -2663,12 +3006,12 @@ var remuxPlugin = {
|
|
|
2663
3006
|
var hybridPlugin = {
|
|
2664
3007
|
name: "hybrid",
|
|
2665
3008
|
canHandle: () => typeof VideoDecoder !== "undefined",
|
|
2666
|
-
execute: (ctx, video) => createHybridSession(ctx, video)
|
|
3009
|
+
execute: (ctx, video, transport) => createHybridSession(ctx, video, transport)
|
|
2667
3010
|
};
|
|
2668
3011
|
var fallbackPlugin = {
|
|
2669
3012
|
name: "fallback",
|
|
2670
3013
|
canHandle: () => true,
|
|
2671
|
-
execute: (ctx, video) => createFallbackSession(ctx, video)
|
|
3014
|
+
execute: (ctx, video, transport) => createFallbackSession(ctx, video, transport)
|
|
2672
3015
|
};
|
|
2673
3016
|
function registerBuiltins(registry) {
|
|
2674
3017
|
registry.register(nativePlugin);
|
|
@@ -2724,7 +3067,8 @@ var SubtitleResourceBag = class {
|
|
|
2724
3067
|
this.urls.clear();
|
|
2725
3068
|
}
|
|
2726
3069
|
};
|
|
2727
|
-
async function attachSubtitleTracks(video, tracks, bag, onError) {
|
|
3070
|
+
async function attachSubtitleTracks(video, tracks, bag, onError, transport) {
|
|
3071
|
+
const doFetch = chunk6UUT4BEA_cjs.fetchWith(transport);
|
|
2728
3072
|
for (const t of Array.from(video.querySelectorAll("track[data-avbridge]"))) {
|
|
2729
3073
|
t.remove();
|
|
2730
3074
|
}
|
|
@@ -2733,13 +3077,13 @@ async function attachSubtitleTracks(video, tracks, bag, onError) {
|
|
|
2733
3077
|
try {
|
|
2734
3078
|
let url = t.sidecarUrl;
|
|
2735
3079
|
if (t.format === "srt") {
|
|
2736
|
-
const res = await
|
|
3080
|
+
const res = await doFetch(t.sidecarUrl, transport?.requestInit);
|
|
2737
3081
|
const text = await res.text();
|
|
2738
3082
|
const vtt = srtToVtt(text);
|
|
2739
3083
|
const blob = new Blob([vtt], { type: "text/vtt" });
|
|
2740
3084
|
url = bag ? bag.createObjectURL(blob) : URL.createObjectURL(blob);
|
|
2741
3085
|
} else if (t.format === "vtt") {
|
|
2742
|
-
const res = await
|
|
3086
|
+
const res = await doFetch(t.sidecarUrl, transport?.requestInit);
|
|
2743
3087
|
const text = await res.text();
|
|
2744
3088
|
if (!isVtt(text)) {
|
|
2745
3089
|
console.warn("[avbridge] subtitle missing WEBVTT header:", t.sidecarUrl);
|
|
@@ -2767,6 +3111,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2767
3111
|
constructor(options, registry) {
|
|
2768
3112
|
this.options = options;
|
|
2769
3113
|
this.registry = registry;
|
|
3114
|
+
const { requestInit, fetchFn } = options;
|
|
3115
|
+
if (requestInit || fetchFn) {
|
|
3116
|
+
this.transport = { requestInit, fetchFn };
|
|
3117
|
+
}
|
|
2770
3118
|
}
|
|
2771
3119
|
options;
|
|
2772
3120
|
registry;
|
|
@@ -2786,11 +3134,23 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2786
3134
|
// listener outlives the player and accumulates on elements that swap
|
|
2787
3135
|
// source (e.g. <avbridge-video>).
|
|
2788
3136
|
endedListener = null;
|
|
3137
|
+
// Background tab handling. userIntent is what the user last asked for
|
|
3138
|
+
// (play vs pause) — used to decide whether to auto-resume on visibility
|
|
3139
|
+
// return. autoPausedForVisibility tracks whether we paused because the
|
|
3140
|
+
// tab was hidden, so we don't resume playback the user deliberately
|
|
3141
|
+
// paused (e.g. via media keys while hidden).
|
|
3142
|
+
userIntent = "pause";
|
|
3143
|
+
autoPausedForVisibility = false;
|
|
3144
|
+
visibilityListener = null;
|
|
2789
3145
|
// Serializes escalation / setStrategy calls
|
|
2790
3146
|
switchingPromise = Promise.resolve();
|
|
2791
3147
|
// Owns blob URLs created during sidecar discovery + SRT->VTT conversion.
|
|
2792
3148
|
// Revoked at destroy() so repeated source swaps don't leak.
|
|
2793
3149
|
subtitleResources = new SubtitleResourceBag();
|
|
3150
|
+
// Transport config extracted from CreatePlayerOptions. Threaded to probe,
|
|
3151
|
+
// subtitle fetches, and strategy session creators. Not stored on MediaContext
|
|
3152
|
+
// because it's runtime config, not media analysis.
|
|
3153
|
+
transport;
|
|
2794
3154
|
static async create(options) {
|
|
2795
3155
|
const registry = new PluginRegistry();
|
|
2796
3156
|
registerBuiltins(registry);
|
|
@@ -2814,7 +3174,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2814
3174
|
const bootstrapStart = performance.now();
|
|
2815
3175
|
try {
|
|
2816
3176
|
chunkG4APZMCP_cjs.dbg.info("bootstrap", "start");
|
|
2817
|
-
const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => probe(this.options.source));
|
|
3177
|
+
const ctx = await chunkG4APZMCP_cjs.dbg.timed("probe", "probe", 3e3, () => probe(this.options.source, this.transport));
|
|
2818
3178
|
chunkG4APZMCP_cjs.dbg.info(
|
|
2819
3179
|
"probe",
|
|
2820
3180
|
`container=${ctx.container} video=${ctx.videoTracks[0]?.codec ?? "-"} audio=${ctx.audioTracks[0]?.codec ?? "-"} probedBy=${ctx.probedBy}`
|
|
@@ -2862,7 +3222,8 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2862
3222
|
this.subtitleResources,
|
|
2863
3223
|
(err, track) => {
|
|
2864
3224
|
console.warn(`[avbridge] subtitle ${track.id} failed: ${err.message}`);
|
|
2865
|
-
}
|
|
3225
|
+
},
|
|
3226
|
+
this.transport
|
|
2866
3227
|
);
|
|
2867
3228
|
}
|
|
2868
3229
|
this.emitter.emitSticky("tracks", {
|
|
@@ -2873,6 +3234,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2873
3234
|
this.startTimeupdateLoop();
|
|
2874
3235
|
this.endedListener = () => this.emitter.emit("ended", void 0);
|
|
2875
3236
|
this.options.target.addEventListener("ended", this.endedListener);
|
|
3237
|
+
if (this.options.backgroundBehavior !== "continue" && typeof document !== "undefined") {
|
|
3238
|
+
this.visibilityListener = () => this.onVisibilityChange();
|
|
3239
|
+
document.addEventListener("visibilitychange", this.visibilityListener);
|
|
3240
|
+
}
|
|
2876
3241
|
this.emitter.emitSticky("ready", void 0);
|
|
2877
3242
|
const bootstrapElapsed = performance.now() - bootstrapStart;
|
|
2878
3243
|
chunkG4APZMCP_cjs.dbg.info("bootstrap", `ready in ${bootstrapElapsed.toFixed(0)}ms`);
|
|
@@ -2899,7 +3264,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2899
3264
|
throw new Error(`no plugin available for strategy "${strategy}"`);
|
|
2900
3265
|
}
|
|
2901
3266
|
try {
|
|
2902
|
-
this.session = await plugin.execute(this.mediaContext, this.options.target);
|
|
3267
|
+
this.session = await plugin.execute(this.mediaContext, this.options.target, this.transport);
|
|
2903
3268
|
} catch (err) {
|
|
2904
3269
|
const chain = this.classification?.fallbackChain;
|
|
2905
3270
|
if (chain && chain.length > 0) {
|
|
@@ -2972,7 +3337,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2972
3337
|
continue;
|
|
2973
3338
|
}
|
|
2974
3339
|
try {
|
|
2975
|
-
this.session = await plugin.execute(this.mediaContext, this.options.target);
|
|
3340
|
+
this.session = await plugin.execute(this.mediaContext, this.options.target, this.transport);
|
|
2976
3341
|
} catch (err) {
|
|
2977
3342
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2978
3343
|
errors.push(`${nextStrategy}: ${msg}`);
|
|
@@ -2995,8 +3360,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
2995
3360
|
}
|
|
2996
3361
|
return;
|
|
2997
3362
|
}
|
|
2998
|
-
this.emitter.emit("error", new
|
|
2999
|
-
|
|
3363
|
+
this.emitter.emit("error", new chunk6UUT4BEA_cjs.AvbridgeError(
|
|
3364
|
+
chunk6UUT4BEA_cjs.ERR_ALL_STRATEGIES_EXHAUSTED,
|
|
3365
|
+
`All playback strategies failed: ${errors.join("; ")}`,
|
|
3366
|
+
"This file may require a codec or container that isn't available in this browser. Try the fallback strategy or check browser codec support."
|
|
3000
3367
|
));
|
|
3001
3368
|
}
|
|
3002
3369
|
// ── Stall supervision ─────────────────────────────────────────────────
|
|
@@ -3048,7 +3415,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3048
3415
|
// ── Public: manual strategy switch ────────────────────────────────────
|
|
3049
3416
|
/** Manually switch to a different playback strategy. Preserves current position and play/pause state. Concurrent calls are serialized. */
|
|
3050
3417
|
async setStrategy(strategy, reason) {
|
|
3051
|
-
if (!this.mediaContext) throw new
|
|
3418
|
+
if (!this.mediaContext) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_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.");
|
|
3052
3419
|
if (this.session?.strategy === strategy) return;
|
|
3053
3420
|
this.switchingPromise = this.switchingPromise.then(
|
|
3054
3421
|
() => this.doSetStrategy(strategy, reason)
|
|
@@ -3077,7 +3444,7 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3077
3444
|
}
|
|
3078
3445
|
const plugin = this.registry.findFor(this.mediaContext, strategy);
|
|
3079
3446
|
if (!plugin) throw new Error(`no plugin available for strategy "${strategy}"`);
|
|
3080
|
-
this.session = await plugin.execute(this.mediaContext, this.options.target);
|
|
3447
|
+
this.session = await plugin.execute(this.mediaContext, this.options.target, this.transport);
|
|
3081
3448
|
this.emitter.emitSticky("strategy", {
|
|
3082
3449
|
strategy,
|
|
3083
3450
|
reason: switchReason
|
|
@@ -3111,26 +3478,56 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3111
3478
|
}
|
|
3112
3479
|
/** Begin or resume playback. Throws if the player is not ready. */
|
|
3113
3480
|
async play() {
|
|
3114
|
-
if (!this.session) throw new
|
|
3481
|
+
if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_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
|
+
this.userIntent = "play";
|
|
3483
|
+
this.autoPausedForVisibility = false;
|
|
3115
3484
|
await this.session.play();
|
|
3116
3485
|
}
|
|
3117
3486
|
/** Pause playback. No-op if the player is not ready or already paused. */
|
|
3118
3487
|
pause() {
|
|
3488
|
+
this.userIntent = "pause";
|
|
3489
|
+
this.autoPausedForVisibility = false;
|
|
3119
3490
|
this.session?.pause();
|
|
3120
3491
|
}
|
|
3492
|
+
/**
|
|
3493
|
+
* Handle browser tab visibility changes. On hide: pause if the user
|
|
3494
|
+
* had been playing. On show: resume if we were the one who paused.
|
|
3495
|
+
* Skips when `backgroundBehavior: "continue"` is set (listener isn't
|
|
3496
|
+
* installed in that case).
|
|
3497
|
+
*/
|
|
3498
|
+
onVisibilityChange() {
|
|
3499
|
+
if (!this.session) return;
|
|
3500
|
+
const action = decideVisibilityAction({
|
|
3501
|
+
hidden: document.hidden,
|
|
3502
|
+
userIntent: this.userIntent,
|
|
3503
|
+
sessionIsPlaying: !this.options.target.paused,
|
|
3504
|
+
autoPausedForVisibility: this.autoPausedForVisibility
|
|
3505
|
+
});
|
|
3506
|
+
if (action === "pause") {
|
|
3507
|
+
this.autoPausedForVisibility = true;
|
|
3508
|
+
chunkG4APZMCP_cjs.dbg.info("visibility", "tab hidden \u2014 auto-paused");
|
|
3509
|
+
this.session.pause();
|
|
3510
|
+
} else if (action === "resume") {
|
|
3511
|
+
this.autoPausedForVisibility = false;
|
|
3512
|
+
chunkG4APZMCP_cjs.dbg.info("visibility", "tab visible \u2014 auto-resuming");
|
|
3513
|
+
void this.session.play().catch((err) => {
|
|
3514
|
+
console.warn("[avbridge] auto-resume after tab return failed:", err);
|
|
3515
|
+
});
|
|
3516
|
+
}
|
|
3517
|
+
}
|
|
3121
3518
|
/** Seek to the given time in seconds. Throws if the player is not ready. */
|
|
3122
3519
|
async seek(time) {
|
|
3123
|
-
if (!this.session) throw new
|
|
3520
|
+
if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_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.");
|
|
3124
3521
|
await this.session.seek(time);
|
|
3125
3522
|
}
|
|
3126
3523
|
/** Switch the active audio track by track ID. Throws if the player is not ready. */
|
|
3127
3524
|
async setAudioTrack(id) {
|
|
3128
|
-
if (!this.session) throw new
|
|
3525
|
+
if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_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.");
|
|
3129
3526
|
await this.session.setAudioTrack(id);
|
|
3130
3527
|
}
|
|
3131
3528
|
/** Switch the active subtitle track by track ID, or pass `null` to disable subtitles. */
|
|
3132
3529
|
async setSubtitleTrack(id) {
|
|
3133
|
-
if (!this.session) throw new
|
|
3530
|
+
if (!this.session) throw new chunk6UUT4BEA_cjs.AvbridgeError(chunk6UUT4BEA_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.");
|
|
3134
3531
|
await this.session.setSubtitleTrack(id);
|
|
3135
3532
|
}
|
|
3136
3533
|
/** Return a snapshot of current diagnostics: container, codecs, strategy, runtime stats, and strategy history. */
|
|
@@ -3162,6 +3559,10 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3162
3559
|
this.options.target.removeEventListener("ended", this.endedListener);
|
|
3163
3560
|
this.endedListener = null;
|
|
3164
3561
|
}
|
|
3562
|
+
if (this.visibilityListener) {
|
|
3563
|
+
document.removeEventListener("visibilitychange", this.visibilityListener);
|
|
3564
|
+
this.visibilityListener = null;
|
|
3565
|
+
}
|
|
3165
3566
|
if (this.session) {
|
|
3166
3567
|
await this.session.destroy();
|
|
3167
3568
|
this.session = null;
|
|
@@ -3173,6 +3574,14 @@ var UnifiedPlayer = class _UnifiedPlayer {
|
|
|
3173
3574
|
async function createPlayer(options) {
|
|
3174
3575
|
return UnifiedPlayer.create(options);
|
|
3175
3576
|
}
|
|
3577
|
+
function decideVisibilityAction(state) {
|
|
3578
|
+
if (state.hidden) {
|
|
3579
|
+
if (state.userIntent === "play" && state.sessionIsPlaying) return "pause";
|
|
3580
|
+
return "noop";
|
|
3581
|
+
}
|
|
3582
|
+
if (state.autoPausedForVisibility) return "resume";
|
|
3583
|
+
return "noop";
|
|
3584
|
+
}
|
|
3176
3585
|
function buildInitialDecision(initial, ctx) {
|
|
3177
3586
|
const natural = classifyContext(ctx);
|
|
3178
3587
|
const cls = strategyToClass(initial, natural);
|
|
@@ -3211,6 +3620,10 @@ function defaultFallbackChain(strategy) {
|
|
|
3211
3620
|
}
|
|
3212
3621
|
}
|
|
3213
3622
|
|
|
3623
|
+
exports.FALLBACK_AUDIO_CODECS = FALLBACK_AUDIO_CODECS;
|
|
3624
|
+
exports.FALLBACK_VIDEO_CODECS = FALLBACK_VIDEO_CODECS;
|
|
3625
|
+
exports.NATIVE_AUDIO_CODECS = NATIVE_AUDIO_CODECS;
|
|
3626
|
+
exports.NATIVE_VIDEO_CODECS = NATIVE_VIDEO_CODECS;
|
|
3214
3627
|
exports.UnifiedPlayer = UnifiedPlayer;
|
|
3215
3628
|
exports.avbridgeAudioToMediabunny = avbridgeAudioToMediabunny;
|
|
3216
3629
|
exports.avbridgeVideoToMediabunny = avbridgeVideoToMediabunny;
|
|
@@ -3219,5 +3632,5 @@ exports.classifyContext = classifyContext;
|
|
|
3219
3632
|
exports.createPlayer = createPlayer;
|
|
3220
3633
|
exports.probe = probe;
|
|
3221
3634
|
exports.srtToVtt = srtToVtt;
|
|
3222
|
-
//# sourceMappingURL=chunk-
|
|
3223
|
-
//# sourceMappingURL=chunk-
|
|
3635
|
+
//# sourceMappingURL=chunk-7RGG6ME7.cjs.map
|
|
3636
|
+
//# sourceMappingURL=chunk-7RGG6ME7.cjs.map
|