avbridge 2.12.0 → 2.12.1
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 +76 -0
- package/dist/{avi-EQE6AR75.cjs → avi-32UABODO.cjs} +12 -4
- package/dist/avi-32UABODO.cjs.map +1 -0
- package/dist/{avi-Y3N325WZ.cjs → avi-5BPR6QUX.cjs} +12 -4
- package/dist/avi-5BPR6QUX.cjs.map +1 -0
- package/dist/{avi-NNHH4AAA.js → avi-BLIH7KKV.js} +12 -4
- package/dist/avi-BLIH7KKV.js.map +1 -0
- package/dist/{avi-S7EY54YA.js → avi-GX2H34IQ.js} +12 -4
- package/dist/avi-GX2H34IQ.js.map +1 -0
- package/dist/{chunk-2LNXMGT6.js → chunk-5CX7BVVV.js} +5 -5
- package/dist/{chunk-2LNXMGT6.js.map → chunk-5CX7BVVV.js.map} +1 -1
- package/dist/{chunk-5Y5BTB5D.js → chunk-B76QWPFM.js} +3 -3
- package/dist/{chunk-5Y5BTB5D.js.map → chunk-B76QWPFM.js.map} +1 -1
- package/dist/{chunk-Z26PXRUY.js → chunk-BN7BRTLY.js} +137 -26
- package/dist/chunk-BN7BRTLY.js.map +1 -0
- package/dist/{chunk-GJBNLPGI.cjs → chunk-E5MAM2P4.cjs} +9 -9
- package/dist/{chunk-GJBNLPGI.cjs.map → chunk-E5MAM2P4.cjs.map} +1 -1
- package/dist/{chunk-7EF4VTUS.cjs → chunk-UM6WCSGL.cjs} +141 -30
- package/dist/chunk-UM6WCSGL.cjs.map +1 -0
- package/dist/{chunk-HBHSUGNI.cjs → chunk-VLI3Y6IJ.cjs} +5 -5
- package/dist/{chunk-HBHSUGNI.cjs.map → chunk-VLI3Y6IJ.cjs.map} +1 -1
- package/dist/element-browser.js +144 -25
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +3 -3
- package/dist/element.js +2 -2
- package/dist/index.cjs +18 -18
- package/dist/index.js +6 -6
- package/dist/player.cjs +207 -41
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +1 -0
- package/dist/player.d.ts +1 -0
- package/dist/player.js +207 -41
- package/dist/player.js.map +1 -1
- package/dist/{remux-VPKCLHHM.cjs → remux-NSBJFMLG.cjs} +9 -9
- package/dist/{remux-VPKCLHHM.cjs.map → remux-NSBJFMLG.cjs.map} +1 -1
- package/dist/{remux-7TA4FKTY.js → remux-PHUHO3VV.js} +4 -4
- package/dist/{remux-7TA4FKTY.js.map → remux-PHUHO3VV.js.map} +1 -1
- package/package.json +1 -1
- package/src/element/avbridge-player.ts +87 -15
- package/src/probe/avi.ts +34 -2
- package/src/strategies/fallback/decoder.ts +148 -19
- package/src/strategies/fallback/video-renderer.ts +41 -3
- package/src/strategies/hybrid/decoder.ts +34 -9
- 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/vendor/libav/avbridge/libav-avbridge.mjs +1 -1
- package/dist/avi-EQE6AR75.cjs.map +0 -1
- package/dist/avi-NNHH4AAA.js.map +0 -1
- package/dist/avi-S7EY54YA.js.map +0 -1
- package/dist/avi-Y3N325WZ.cjs.map +0 -1
- package/dist/chunk-7EF4VTUS.cjs.map +0 -1
- package/dist/chunk-Z26PXRUY.js.map +0 -1
|
@@ -187,7 +187,7 @@ async function probe(source, transport) {
|
|
|
187
187
|
const hasUnknownCodec = result.videoTracks.some((t) => t.codec === "unknown") || result.audioTracks.some((t) => t.codec === "unknown");
|
|
188
188
|
if (hasUnknownCodec) {
|
|
189
189
|
try {
|
|
190
|
-
const { probeWithLibav } = await import('./avi-
|
|
190
|
+
const { probeWithLibav } = await import('./avi-32UABODO.cjs');
|
|
191
191
|
return await probeWithLibav(normalized, sniffed);
|
|
192
192
|
} catch {
|
|
193
193
|
return result;
|
|
@@ -200,7 +200,7 @@ async function probe(source, transport) {
|
|
|
200
200
|
mediabunnyErr.message
|
|
201
201
|
);
|
|
202
202
|
try {
|
|
203
|
-
const { probeWithLibav } = await import('./avi-
|
|
203
|
+
const { probeWithLibav } = await import('./avi-32UABODO.cjs');
|
|
204
204
|
return await probeWithLibav(normalized, sniffed);
|
|
205
205
|
} catch (libavErr) {
|
|
206
206
|
const mbMsg = mediabunnyErr.message || String(mediabunnyErr);
|
|
@@ -214,7 +214,7 @@ async function probe(source, transport) {
|
|
|
214
214
|
}
|
|
215
215
|
}
|
|
216
216
|
try {
|
|
217
|
-
const { probeWithLibav } = await import('./avi-
|
|
217
|
+
const { probeWithLibav } = await import('./avi-32UABODO.cjs');
|
|
218
218
|
return await probeWithLibav(normalized, sniffed);
|
|
219
219
|
} catch (err) {
|
|
220
220
|
const inner = err instanceof Error ? err.message : String(err);
|
|
@@ -238,5 +238,5 @@ exports.avbridgeAudioToMediabunny = avbridgeAudioToMediabunny;
|
|
|
238
238
|
exports.avbridgeVideoToMediabunny = avbridgeVideoToMediabunny;
|
|
239
239
|
exports.buildMediabunnySourceFromInput = buildMediabunnySourceFromInput;
|
|
240
240
|
exports.probe = probe;
|
|
241
|
-
//# sourceMappingURL=chunk-
|
|
242
|
-
//# sourceMappingURL=chunk-
|
|
241
|
+
//# sourceMappingURL=chunk-VLI3Y6IJ.cjs.map
|
|
242
|
+
//# sourceMappingURL=chunk-VLI3Y6IJ.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/probe/mediabunny.ts","../src/probe/index.ts"],"names":["normalizeSource","sniffNormalizedSource","AvbridgeError","ERR_PROBE_FAILED","ERR_PROBE_UNKNOWN_CONTAINER","ERR_LIBAV_NOT_REACHABLE"],"mappings":";;;;;AAwBA,eAAsB,mBAAA,CACpB,QACA,gBAAA,EACuB;AACvB,EAAA,MAAM,EAAA,GAAK,MAAM,OAAO,YAAY,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,IAAI,EAAA,CAAG,KAAA,CAAM;AAAA,IACzB,MAAA,EAAQ,MAAM,qBAAA,CAAsB,EAAA,EAAI,MAAM,CAAA;AAAA,IAC9C,SAAS,EAAA,CAAG;AAAA,GACb,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,SAAA,EAAU;AACxC,EAAA,MAAM,WAAW,MAAM,UAAA,CAAW,MAAM,KAAA,CAAM,iBAAiB,CAAA;AAE/D,EAAA,MAAM,cAAgC,EAAC;AACvC,EAAA,MAAM,cAAgC,EAAC;AAEvC,EAAA,KAAA,MAAW,SAAS,SAAA,EAAW;AAC7B,IAAA,IAAI,KAAA,CAAM,cAAa,EAAG;AACxB,MAAA,MAAM,aAAa,MAAM,IAAA,CAAK,MAAM,KAAA,CAAM,yBAAyB,CAAA;AACnE,MAAA,WAAA,CAAY,IAAA,CAAK;AAAA,QACf,IAAI,KAAA,CAAM,EAAA;AAAA,QACV,KAAA,EAAO,yBAAA,CAA0B,KAAA,CAAM,KAAK,CAAA;AAAA,QAC5C,KAAA,EAAO,KAAA,CAAM,YAAA,IAAgB,KAAA,CAAM,UAAA,IAAc,CAAA;AAAA,QACjD,MAAA,EAAQ,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,WAAA,IAAe,CAAA;AAAA,QACpD,aAAa,UAAA,IAAc;AAAA,OAC5B,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,KAAA,CAAM,YAAA,EAAa,EAAG;AAC/B,MAAA,MAAM,aAAa,MAAM,IAAA,CAAK,MAAM,KAAA,CAAM,yBAAyB,CAAA;AACnE,MAAA,WAAA,CAAY,IAAA,CAAK;AAAA,QACf,IAAI,KAAA,CAAM,EAAA;AAAA,QACV,KAAA,EAAO,yBAAA,CAA0B,KAAA,CAAM,KAAK,CAAA;AAAA,QAC5C,QAAA,EAAU,MAAM,gBAAA,IAAoB,CAAA;AAAA,QACpC,UAAA,EAAY,MAAM,UAAA,IAAc,CAAA;AAAA,QAChC,UAAU,KAAA,CAAM,YAAA;AAAA,QAChB,aAAa,UAAA,IAAc;AAAA,OAC5B,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,MAAM,IAAA,CAAK,MAAM,KAAA,CAAM,WAAW,CAAA;AACjD,EAAA,MAAM,SAAA,GAAY,gBAAA,CAAiB,MAAA,EAAQ,IAAA,EAAM,gBAAgB,CAAA;AAEjE,EAAA,OAAO;AAAA,IACL,QAAQ,MAAA,CAAO,QAAA;AAAA,IACf,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,SAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,gBAAgB,EAAC;AAAA,IACjB,QAAA,EAAU,YAAA;AAAA,IACV;AAAA,GACF;AACF;AASA,eAAsB,qBAAA,CACpB,IACA,MAAA,EACiF;AACjF,EAAA,IAAI,MAAA,CAAO,SAAS,KAAA,EAAO;AACzB,IAAA,OAAO,IAAI,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,GAAG,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,IAAI,EAAA,CAAG,UAAA,CAAW,MAAA,CAAO,IAAI,CAAA;AACtC;AAWA,eAAsB,8BAAA,CACpB,IACA,MAAA,EACiF;AACjF,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,SAAiB,IAAI,EAAA,CAAG,UAAU,MAAM,CAAA;AAC9D,EAAA,IAAI,MAAA,YAAkB,KAAK,OAAO,IAAI,GAAG,SAAA,CAAU,MAAA,CAAO,UAAU,CAAA;AACpE,EAAA,IAAI,kBAAkB,IAAA,EAAM,OAAO,IAAI,EAAA,CAAG,WAAW,MAAM,CAAA;AAC3D,EAAA,IAAI,MAAA,YAAkB,WAAA,EAAa,OAAO,IAAI,EAAA,CAAG,UAAA,CAAW,IAAI,IAAA,CAAK,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9E,EAAA,IAAI,MAAA,YAAkB,UAAA,EAAY,OAAO,IAAI,EAAA,CAAG,UAAA,CAAW,IAAI,IAAA,CAAK,CAAC,MAAkB,CAAC,CAAC,CAAA;AACzF,EAAA,MAAM,IAAI,UAAU,wCAAwC,CAAA;AAC9D;AAEA,SAAS,gBAAA,CAAiB,YAAgC,OAAA,EAAuC;AAC/F,EAAA,MAAM,IAAA,GAAA,CAAQ,UAAA,IAAc,EAAA,EAAI,WAAA,EAAY;AAC5C,EAAA,IAAI,IAAA,CAAK,SAAS,UAAU,CAAA,IAAK,KAAK,QAAA,CAAS,KAAK,GAAG,OAAO,KAAA;AAC9D,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAClC,EAAA,IAAI,IAAA,CAAK,SAAS,KAAK,CAAA,IAAK,KAAK,QAAA,CAAS,MAAM,GAAG,OAAO,KAAA;AAC1D,EAAA,IAAI,IAAA,CAAK,SAAS,KAAK,CAAA,IAAK,KAAK,QAAA,CAAS,WAAW,GAAG,OAAO,KAAA;AAC/D,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAClC,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,CAAK,SAAS,MAAM,CAAA,IAAK,KAAK,QAAA,CAAS,KAAK,GAAG,OAAO,MAAA;AAC1D,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,QAAA;AAC9F,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,0BAA0B,CAAA,EAA0C;AAClF,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,KAAA;AAAQ,MAAA,OAAO,MAAA;AAAA,IACpB,KAAK,MAAA;AAAQ,MAAA,OAAO,MAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB;AAME,MAAA,OAAO,IAAK,CAAA,GAAmB,SAAA;AAAA;AAErC;AAGO,SAAS,0BAA0B,CAAA,EAA8D;AACtG,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,MAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,MAAA;AAAQ,MAAA,OAAO,MAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB;AAAa,MAAA,OAAO,IAAA;AAAA;AAExB;AAEO,SAAS,0BAA0B,CAAA,EAA0C;AAClF,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,QAAA;AAAU,MAAA,OAAO,QAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB;AAAe,MAAA,OAAO,IAAK,CAAA,GAAmB,SAAA;AAAA;AAElD;AAEO,SAAS,0BAA0B,CAAA,EAA8B;AACtE,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,QAAA;AAAU,MAAA,OAAO,QAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB;AAAe,MAAA,OAAO,IAAA;AAAA;AAE1B;AAEA,eAAe,WAAW,EAAA,EAAiE;AACzF,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,MAAM,EAAA,EAAG;AACnB,IAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,QAAA,CAAS,CAAC,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,EAC3D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,eAAe,KAAQ,EAAA,EAAkD;AACvE,EAAA,IAAI;AAAE,IAAA,OAAO,MAAM,EAAA,EAAG;AAAA,EAAG,CAAA,CAAA,MAAQ;AAAE,IAAA,OAAO,MAAA;AAAA,EAAW;AACvD;;;ACjMA,IAAM,qBAAA,uBAA4B,GAAA,CAAmB;AAAA,EACnD,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAC,CAAA;AAkBD,eAAsB,KAAA,CACpB,QACA,SAAA,EACuB;AACvB,EAAA,MAAM,UAAA,GAAa,MAAMA,iCAAA,CAAgB,MAAA,EAAQ,SAAS,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,MAAMC,uCAAA,CAAsB,UAAU,CAAA;AAEtD,EAAA,IAAI,qBAAA,CAAsB,GAAA,CAAI,OAAO,CAAA,EAAG;AACtC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,CAAoB,UAAA,EAAY,OAAO,CAAA;AAI5D,MAAA,MAAM,kBACJ,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,KAAA,KAAU,SAAS,CAAA,IACpD,OAAO,WAAA,CAAY,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,SAAS,CAAA;AACtD,MAAA,IAAI,eAAA,EAAiB;AACnB,QAAA,IAAI;AACF,UAAA,MAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,oBAAU,CAAA;AAClD,UAAA,OAAO,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAAA,QACjD,CAAA,CAAA,MAAQ;AAGN,UAAA,OAAO,MAAA;AAAA,QACT;AAAA,MACF;AACA,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,aAAA,EAAe;AAOtB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,kCAAkC,OAAO,CAAA,6BAAA,CAAA;AAAA,QACxC,aAAA,CAAwB;AAAA,OAC3B;AACA,MAAA,IAAI;AACF,QAAA,MAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,oBAAU,CAAA;AAClD,QAAA,OAAO,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAAA,MACjD,SAAS,QAAA,EAAU;AACjB,QAAA,MAAM,KAAA,GAAS,aAAA,CAAwB,OAAA,IAAW,MAAA,CAAO,aAAa,CAAA;AACtE,QAAA,MAAM,QAAQ,QAAA,YAAoB,KAAA,GAAQ,QAAA,CAAS,OAAA,GAAU,OAAO,QAAQ,CAAA;AAC5E,QAAA,MAAM,IAAIC,+BAAA;AAAA,UACRC,kCAAA;AAAA,UACA,mBAAmB,OAAA,CAAQ,WAAA,EAAa,CAAA,mBAAA,EAAsB,KAAK,YAAY,KAAK,CAAA,CAAA,CAAA;AAAA,UACpF;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,oBAAU,CAAA;AAClD,IAAA,OAAO,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAAA,EACjD,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,QAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAE7D,IAAA,OAAA,CAAQ,KAAA,CAAM,mCAAA,EAAqC,OAAA,EAAS,OAAA,EAAS,GAAG,CAAA;AACxE,IAAA,IAAI,YAAY,SAAA,EAAW;AACzB,MAAA,MAAM,IAAID,+BAAA;AAAA,QACRE,6CAAA;AAAA,QACA,CAAA,kFAAA,EAAqF,SAAS,cAAc,CAAA,CAAA;AAAA,QAC5G;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,IAAIF,+BAAA;AAAA,MACRG,yCAAA;AAAA,MACA,GAAG,OAAA,CAAQ,WAAA,EAAa,CAAA,+CAAA,EAAkD,SAAS,cAAc,CAAA,CAAA;AAAA,MACjG;AAAA,KACF;AAAA,EACF;AACF","file":"chunk-HBHSUGNI.cjs","sourcesContent":["import type {\n AudioCodec,\n AudioTrackInfo,\n ContainerKind,\n MediaContext,\n VideoCodec,\n VideoTrackInfo,\n} from \"../types.js\";\nimport type { NormalizedSource } from \"../util/source.js\";\n\n/**\n * Probe via mediabunny. Built against the real (typed) API exported by\n * `mediabunny.d.ts`:\n *\n * - `Input.getTracks()` returns `InputTrack[]`; each track has `isVideoTrack()`\n * / `isAudioTrack()` type guards plus a `codec` getter that returns one of\n * the enum strings (`\"avc\"|\"hevc\"|\"vp9\"|\"vp8\"|\"av1\"` for video,\n * `\"aac\"|\"opus\"|...` for audio).\n * - For decoder metadata + codec parameter strings we call\n * `getDecoderConfig()` and `getCodecParameterString()` on the typed track.\n *\n * The bridging back to avbridge's own codec naming (`h264` instead of mediabunny's\n * `avc`) happens here so the rest of the codebase keeps a single vocabulary.\n */\nexport async function probeWithMediabunny(\n source: NormalizedSource,\n sniffedContainer: ContainerKind,\n): Promise<MediaContext> {\n const mb = await import(\"mediabunny\");\n const input = new mb.Input({\n source: await buildMediabunnySource(mb, source),\n formats: mb.ALL_FORMATS,\n });\n\n const allTracks = await input.getTracks();\n const duration = await safeNumber(() => input.computeDuration());\n\n const videoTracks: VideoTrackInfo[] = [];\n const audioTracks: AudioTrackInfo[] = [];\n\n for (const track of allTracks) {\n if (track.isVideoTrack()) {\n const codecParam = await safe(() => track.getCodecParameterString());\n videoTracks.push({\n id: track.id,\n codec: mediabunnyVideoToAvbridge(track.codec),\n width: track.displayWidth ?? track.codedWidth ?? 0,\n height: track.displayHeight ?? track.codedHeight ?? 0,\n codecString: codecParam ?? undefined,\n });\n } else if (track.isAudioTrack()) {\n const codecParam = await safe(() => track.getCodecParameterString());\n audioTracks.push({\n id: track.id,\n codec: mediabunnyAudioToAvbridge(track.codec),\n channels: track.numberOfChannels ?? 0,\n sampleRate: track.sampleRate ?? 0,\n language: track.languageCode,\n codecString: codecParam ?? undefined,\n });\n }\n }\n\n const format = await safe(() => input.getFormat());\n const container = resolveContainer(format?.name, sniffedContainer);\n\n return {\n source: source.original,\n name: source.name,\n byteLength: source.byteLength,\n container,\n videoTracks,\n audioTracks,\n subtitleTracks: [],\n probedBy: \"mediabunny\",\n duration,\n };\n}\n\n/**\n * Build the right mediabunny `Source` for a normalized input. URL sources\n * use `UrlSource` (Range requests, prefetch, parallelism) so we don't\n * buffer the whole file into memory. Blob/File sources use `BlobSource`.\n *\n * Exported so the remux strategy can use the same routing logic.\n */\nexport async function buildMediabunnySource(\n mb: typeof import(\"mediabunny\"),\n source: NormalizedSource,\n): Promise<InstanceType<typeof mb.BlobSource> | InstanceType<typeof mb.UrlSource>> {\n if (source.kind === \"url\") {\n return new mb.UrlSource(source.url);\n }\n return new mb.BlobSource(source.blob);\n}\n\n/**\n * Build a mediabunny `Source` directly from a raw `MediaInput`, bypassing\n * `normalizeSource`. Used by strategies that already have the original\n * input on hand (via `MediaContext.source`) and don't need a sniff window.\n *\n * This is the routing point that decides \"stream from URL via Range\n * requests\" vs \"wrap in-memory bytes as BlobSource\". Always prefer\n * `UrlSource` for URL inputs so we don't accidentally buffer the file.\n */\nexport async function buildMediabunnySourceFromInput(\n mb: typeof import(\"mediabunny\"),\n source: import(\"../types.js\").MediaInput,\n): Promise<InstanceType<typeof mb.BlobSource> | InstanceType<typeof mb.UrlSource>> {\n if (typeof source === \"string\") return new mb.UrlSource(source);\n if (source instanceof URL) return new mb.UrlSource(source.toString());\n if (source instanceof Blob) return new mb.BlobSource(source);\n if (source instanceof ArrayBuffer) return new mb.BlobSource(new Blob([source]));\n if (source instanceof Uint8Array) return new mb.BlobSource(new Blob([source as BlobPart]));\n throw new TypeError(\"unsupported source type for mediabunny\");\n}\n\nfunction resolveContainer(formatName: string | undefined, sniffed: ContainerKind): ContainerKind {\n const name = (formatName ?? \"\").toLowerCase();\n if (name.includes(\"matroska\") || name.includes(\"mkv\")) return \"mkv\";\n if (name.includes(\"webm\")) return \"webm\";\n if (name.includes(\"mp4\") || name.includes(\"isom\")) return \"mp4\";\n if (name.includes(\"mov\") || name.includes(\"quicktime\")) return \"mov\";\n if (name.includes(\"ogg\")) return \"ogg\";\n if (name.includes(\"wav\")) return \"wav\";\n if (name.includes(\"flac\")) return \"flac\";\n if (name.includes(\"mp3\")) return \"mp3\";\n if (name.includes(\"adts\") || name.includes(\"aac\")) return \"adts\";\n if (name.includes(\"mpegts\") || name.includes(\"mpeg-ts\") || name.includes(\"transport\")) return \"mpegts\";\n return sniffed;\n}\n\n/** Mediabunny video codec → avbridge video codec. */\nexport function mediabunnyVideoToAvbridge(c: string | null | undefined): VideoCodec {\n switch (c) {\n case \"avc\": return \"h264\";\n case \"hevc\": return \"h265\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n default:\n // Preserve the original codec string when mediabunny gave us something\n // we don't recognize. The classifier checks `NATIVE_VIDEO_CODECS.has()`\n // and routes anything outside that set through the fallback chain — so\n // returning the unknown name (instead of silently relabeling it as\n // \"h264\") gets correct routing AND honest diagnostics.\n return c ? (c as VideoCodec) : \"unknown\";\n }\n}\n\n/** avbridge video codec → mediabunny video codec (for output sources). */\nexport function avbridgeVideoToMediabunny(c: VideoCodec): \"avc\" | \"hevc\" | \"vp9\" | \"vp8\" | \"av1\" | null {\n switch (c) {\n case \"h264\": return \"avc\";\n case \"h265\": return \"hevc\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n default: return null;\n }\n}\n\nexport function mediabunnyAudioToAvbridge(c: string | null | undefined): AudioCodec {\n switch (c) {\n case \"aac\": return \"aac\";\n case \"mp3\": return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n default: return c ? (c as AudioCodec) : \"unknown\";\n }\n}\n\nexport function avbridgeAudioToMediabunny(c: AudioCodec): string | null {\n switch (c) {\n case \"aac\": return \"aac\";\n case \"mp3\": return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n default: return null;\n }\n}\n\nasync function safeNumber(fn: () => Promise<number> | number): Promise<number | undefined> {\n try {\n const v = await fn();\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function safe<T>(fn: () => Promise<T> | T): Promise<T | undefined> {\n try { return await fn(); } catch { return undefined; }\n}\n","import type { ContainerKind, MediaContext, MediaInput, TransportConfig } from \"../types.js\";\nimport { normalizeSource, sniffNormalizedSource } from \"../util/source.js\";\nimport { probeWithMediabunny } from \"./mediabunny.js\";\nimport { AvbridgeError, ERR_PROBE_FAILED, ERR_PROBE_UNKNOWN_CONTAINER, ERR_LIBAV_NOT_REACHABLE } from \"../errors.js\";\n\n/** Containers mediabunny can demux. Sniff results outside this set go straight to libav. */\nconst MEDIABUNNY_CONTAINERS = new Set<ContainerKind>([\n \"mp4\",\n \"mov\",\n \"mkv\",\n \"webm\",\n \"ogg\",\n \"wav\",\n \"mp3\",\n \"flac\",\n \"adts\",\n \"mpegts\",\n]);\n\n/**\n * Probe a source and produce a {@link MediaContext}.\n *\n * Routing:\n * 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.\n * 2. If the container is one mediabunny supports → try mediabunny first\n * (fast path — it's a single pass of WASM-free JS parsing). If mediabunny\n * throws (e.g. an assertion on an unsupported sample entry like `mp4v`\n * for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to\n * libav.js which handles the long tail of codecs mediabunny doesn't.\n * The combined-error case surfaces *both* failures so the user sees\n * which path each step took.\n * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.\n * mediabunny can't read those containers at all, so there's no fast path\n * to try.\n */\nexport async function probe(\n source: MediaInput,\n transport?: TransportConfig,\n): Promise<MediaContext> {\n const normalized = await normalizeSource(source, transport);\n const sniffed = await sniffNormalizedSource(normalized);\n\n if (MEDIABUNNY_CONTAINERS.has(sniffed)) {\n try {\n const result = await probeWithMediabunny(normalized, sniffed);\n // If mediabunny returned unknown codecs, try libav which recognizes\n // a wider range (DTS, TrueHD, etc.). Only escalate if there ARE\n // tracks with unknown codecs — otherwise mediabunny's result is fine.\n const hasUnknownCodec =\n result.videoTracks.some((t) => t.codec === \"unknown\") ||\n result.audioTracks.some((t) => t.codec === \"unknown\");\n if (hasUnknownCodec) {\n try {\n const { probeWithLibav } = await import(\"./avi.js\");\n return await probeWithLibav(normalized, sniffed);\n } catch {\n // libav also failed — return mediabunny's result (unknown codecs\n // are better than no result at all)\n return result;\n }\n }\n return result;\n } catch (mediabunnyErr) {\n // mediabunny rejected the file. Before giving up, try libav — it can\n // demux a much wider range of codec combinations in ISOBMFF/MKV/etc.\n // than mediabunny's pure-JS parser (e.g. mp4v, wmv3-in-asf, flac in\n // an MP4 container). This is \"escalation\", not \"masking\": if libav\n // also fails we surface both errors below.\n // eslint-disable-next-line no-console\n console.warn(\n `[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,\n (mediabunnyErr as Error).message,\n );\n try {\n const { probeWithLibav } = await import(\"./avi.js\");\n return await probeWithLibav(normalized, sniffed);\n } catch (libavErr) {\n const mbMsg = (mediabunnyErr as Error).message || String(mediabunnyErr);\n const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);\n throw new AvbridgeError(\n ERR_PROBE_FAILED,\n `Failed to probe ${sniffed.toUpperCase()} file. mediabunny: ${mbMsg}. libav: ${lvMsg}.`,\n \"The file may be corrupt, truncated, or in an unsupported format. Enable AVBRIDGE_DEBUG for detailed logs.\",\n );\n }\n }\n }\n\n // sniffed === avi | asf | flv | unknown — try libav.\n try {\n const { probeWithLibav } = await import(\"./avi.js\");\n return await probeWithLibav(normalized, sniffed);\n } catch (err) {\n const inner = err instanceof Error ? err.message : String(err);\n // eslint-disable-next-line no-console\n console.error(\"[avbridge] libav probe failed for\", sniffed, \"file:\", err);\n if (sniffed === \"unknown\") {\n throw new AvbridgeError(\n ERR_PROBE_UNKNOWN_CONTAINER,\n `Unable to probe source: container format could not be identified. libav fallback: ${inner || \"(no details)\"}`,\n \"The file may be corrupt or in a format avbridge doesn't recognize. Check the file plays in VLC or ffprobe.\",\n );\n }\n throw new AvbridgeError(\n ERR_LIBAV_NOT_REACHABLE,\n `${sniffed.toUpperCase()} files require libav.js, which failed to load: ${inner || \"(no details)\"}`,\n \"Install @libav.js/variant-webcodecs, or check that AVBRIDGE_LIBAV_BASE points to the correct path.\",\n );\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/probe/mediabunny.ts","../src/probe/index.ts"],"names":["normalizeSource","sniffNormalizedSource","AvbridgeError","ERR_PROBE_FAILED","ERR_PROBE_UNKNOWN_CONTAINER","ERR_LIBAV_NOT_REACHABLE"],"mappings":";;;;;AAwBA,eAAsB,mBAAA,CACpB,QACA,gBAAA,EACuB;AACvB,EAAA,MAAM,EAAA,GAAK,MAAM,OAAO,YAAY,CAAA;AACpC,EAAA,MAAM,KAAA,GAAQ,IAAI,EAAA,CAAG,KAAA,CAAM;AAAA,IACzB,MAAA,EAAQ,MAAM,qBAAA,CAAsB,EAAA,EAAI,MAAM,CAAA;AAAA,IAC9C,SAAS,EAAA,CAAG;AAAA,GACb,CAAA;AAED,EAAA,MAAM,SAAA,GAAY,MAAM,KAAA,CAAM,SAAA,EAAU;AACxC,EAAA,MAAM,WAAW,MAAM,UAAA,CAAW,MAAM,KAAA,CAAM,iBAAiB,CAAA;AAE/D,EAAA,MAAM,cAAgC,EAAC;AACvC,EAAA,MAAM,cAAgC,EAAC;AAEvC,EAAA,KAAA,MAAW,SAAS,SAAA,EAAW;AAC7B,IAAA,IAAI,KAAA,CAAM,cAAa,EAAG;AACxB,MAAA,MAAM,aAAa,MAAM,IAAA,CAAK,MAAM,KAAA,CAAM,yBAAyB,CAAA;AACnE,MAAA,WAAA,CAAY,IAAA,CAAK;AAAA,QACf,IAAI,KAAA,CAAM,EAAA;AAAA,QACV,KAAA,EAAO,yBAAA,CAA0B,KAAA,CAAM,KAAK,CAAA;AAAA,QAC5C,KAAA,EAAO,KAAA,CAAM,YAAA,IAAgB,KAAA,CAAM,UAAA,IAAc,CAAA;AAAA,QACjD,MAAA,EAAQ,KAAA,CAAM,aAAA,IAAiB,KAAA,CAAM,WAAA,IAAe,CAAA;AAAA,QACpD,aAAa,UAAA,IAAc;AAAA,OAC5B,CAAA;AAAA,IACH,CAAA,MAAA,IAAW,KAAA,CAAM,YAAA,EAAa,EAAG;AAC/B,MAAA,MAAM,aAAa,MAAM,IAAA,CAAK,MAAM,KAAA,CAAM,yBAAyB,CAAA;AACnE,MAAA,WAAA,CAAY,IAAA,CAAK;AAAA,QACf,IAAI,KAAA,CAAM,EAAA;AAAA,QACV,KAAA,EAAO,yBAAA,CAA0B,KAAA,CAAM,KAAK,CAAA;AAAA,QAC5C,QAAA,EAAU,MAAM,gBAAA,IAAoB,CAAA;AAAA,QACpC,UAAA,EAAY,MAAM,UAAA,IAAc,CAAA;AAAA,QAChC,UAAU,KAAA,CAAM,YAAA;AAAA,QAChB,aAAa,UAAA,IAAc;AAAA,OAC5B,CAAA;AAAA,IACH;AAAA,EACF;AAEA,EAAA,MAAM,SAAS,MAAM,IAAA,CAAK,MAAM,KAAA,CAAM,WAAW,CAAA;AACjD,EAAA,MAAM,SAAA,GAAY,gBAAA,CAAiB,MAAA,EAAQ,IAAA,EAAM,gBAAgB,CAAA;AAEjE,EAAA,OAAO;AAAA,IACL,QAAQ,MAAA,CAAO,QAAA;AAAA,IACf,MAAM,MAAA,CAAO,IAAA;AAAA,IACb,YAAY,MAAA,CAAO,UAAA;AAAA,IACnB,SAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,gBAAgB,EAAC;AAAA,IACjB,QAAA,EAAU,YAAA;AAAA,IACV;AAAA,GACF;AACF;AASA,eAAsB,qBAAA,CACpB,IACA,MAAA,EACiF;AACjF,EAAA,IAAI,MAAA,CAAO,SAAS,KAAA,EAAO;AACzB,IAAA,OAAO,IAAI,EAAA,CAAG,SAAA,CAAU,MAAA,CAAO,GAAG,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,IAAI,EAAA,CAAG,UAAA,CAAW,MAAA,CAAO,IAAI,CAAA;AACtC;AAWA,eAAsB,8BAAA,CACpB,IACA,MAAA,EACiF;AACjF,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,SAAiB,IAAI,EAAA,CAAG,UAAU,MAAM,CAAA;AAC9D,EAAA,IAAI,MAAA,YAAkB,KAAK,OAAO,IAAI,GAAG,SAAA,CAAU,MAAA,CAAO,UAAU,CAAA;AACpE,EAAA,IAAI,kBAAkB,IAAA,EAAM,OAAO,IAAI,EAAA,CAAG,WAAW,MAAM,CAAA;AAC3D,EAAA,IAAI,MAAA,YAAkB,WAAA,EAAa,OAAO,IAAI,EAAA,CAAG,UAAA,CAAW,IAAI,IAAA,CAAK,CAAC,MAAM,CAAC,CAAC,CAAA;AAC9E,EAAA,IAAI,MAAA,YAAkB,UAAA,EAAY,OAAO,IAAI,EAAA,CAAG,UAAA,CAAW,IAAI,IAAA,CAAK,CAAC,MAAkB,CAAC,CAAC,CAAA;AACzF,EAAA,MAAM,IAAI,UAAU,wCAAwC,CAAA;AAC9D;AAEA,SAAS,gBAAA,CAAiB,YAAgC,OAAA,EAAuC;AAC/F,EAAA,MAAM,IAAA,GAAA,CAAQ,UAAA,IAAc,EAAA,EAAI,WAAA,EAAY;AAC5C,EAAA,IAAI,IAAA,CAAK,SAAS,UAAU,CAAA,IAAK,KAAK,QAAA,CAAS,KAAK,GAAG,OAAO,KAAA;AAC9D,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAClC,EAAA,IAAI,IAAA,CAAK,SAAS,KAAK,CAAA,IAAK,KAAK,QAAA,CAAS,MAAM,GAAG,OAAO,KAAA;AAC1D,EAAA,IAAI,IAAA,CAAK,SAAS,KAAK,CAAA,IAAK,KAAK,QAAA,CAAS,WAAW,GAAG,OAAO,KAAA;AAC/D,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,EAAG,OAAO,MAAA;AAClC,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,KAAK,CAAA,EAAG,OAAO,KAAA;AACjC,EAAA,IAAI,IAAA,CAAK,SAAS,MAAM,CAAA,IAAK,KAAK,QAAA,CAAS,KAAK,GAAG,OAAO,MAAA;AAC1D,EAAA,IAAI,IAAA,CAAK,QAAA,CAAS,QAAQ,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,SAAS,CAAA,IAAK,IAAA,CAAK,QAAA,CAAS,WAAW,CAAA,EAAG,OAAO,QAAA;AAC9F,EAAA,OAAO,OAAA;AACT;AAGO,SAAS,0BAA0B,CAAA,EAA0C;AAClF,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,KAAA;AAAQ,MAAA,OAAO,MAAA;AAAA,IACpB,KAAK,MAAA;AAAQ,MAAA,OAAO,MAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB;AAME,MAAA,OAAO,IAAK,CAAA,GAAmB,SAAA;AAAA;AAErC;AAGO,SAAS,0BAA0B,CAAA,EAA8D;AACtG,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,MAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,MAAA;AAAQ,MAAA,OAAO,MAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB,KAAK,KAAA;AAAQ,MAAA,OAAO,KAAA;AAAA,IACpB;AAAa,MAAA,OAAO,IAAA;AAAA;AAExB;AAEO,SAAS,0BAA0B,CAAA,EAA0C;AAClF,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,QAAA;AAAU,MAAA,OAAO,QAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB;AAAe,MAAA,OAAO,IAAK,CAAA,GAAmB,SAAA;AAAA;AAElD;AAEO,SAAS,0BAA0B,CAAA,EAA8B;AACtE,EAAA,QAAQ,CAAA;AAAG,IACT,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,QAAA;AAAU,MAAA,OAAO,QAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB,KAAK,KAAA;AAAU,MAAA,OAAO,KAAA;AAAA,IACtB,KAAK,MAAA;AAAU,MAAA,OAAO,MAAA;AAAA,IACtB;AAAe,MAAA,OAAO,IAAA;AAAA;AAE1B;AAEA,eAAe,WAAW,EAAA,EAAiE;AACzF,EAAA,IAAI;AACF,IAAA,MAAM,CAAA,GAAI,MAAM,EAAA,EAAG;AACnB,IAAA,OAAO,OAAO,CAAA,KAAM,QAAA,IAAY,OAAO,QAAA,CAAS,CAAC,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,EAC3D,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF;AAEA,eAAe,KAAQ,EAAA,EAAkD;AACvE,EAAA,IAAI;AAAE,IAAA,OAAO,MAAM,EAAA,EAAG;AAAA,EAAG,CAAA,CAAA,MAAQ;AAAE,IAAA,OAAO,MAAA;AAAA,EAAW;AACvD;;;ACjMA,IAAM,qBAAA,uBAA4B,GAAA,CAAmB;AAAA,EACnD,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,KAAA;AAAA,EACA,MAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAC,CAAA;AAkBD,eAAsB,KAAA,CACpB,QACA,SAAA,EACuB;AACvB,EAAA,MAAM,UAAA,GAAa,MAAMA,iCAAA,CAAgB,MAAA,EAAQ,SAAS,CAAA;AAC1D,EAAA,MAAM,OAAA,GAAU,MAAMC,uCAAA,CAAsB,UAAU,CAAA;AAEtD,EAAA,IAAI,qBAAA,CAAsB,GAAA,CAAI,OAAO,CAAA,EAAG;AACtC,IAAA,IAAI;AACF,MAAA,MAAM,MAAA,GAAS,MAAM,mBAAA,CAAoB,UAAA,EAAY,OAAO,CAAA;AAI5D,MAAA,MAAM,kBACJ,MAAA,CAAO,WAAA,CAAY,IAAA,CAAK,CAAC,MAAM,CAAA,CAAE,KAAA,KAAU,SAAS,CAAA,IACpD,OAAO,WAAA,CAAY,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,UAAU,SAAS,CAAA;AACtD,MAAA,IAAI,eAAA,EAAiB;AACnB,QAAA,IAAI;AACF,UAAA,MAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,oBAAU,CAAA;AAClD,UAAA,OAAO,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAAA,QACjD,CAAA,CAAA,MAAQ;AAGN,UAAA,OAAO,MAAA;AAAA,QACT;AAAA,MACF;AACA,MAAA,OAAO,MAAA;AAAA,IACT,SAAS,aAAA,EAAe;AAOtB,MAAA,OAAA,CAAQ,IAAA;AAAA,QACN,kCAAkC,OAAO,CAAA,6BAAA,CAAA;AAAA,QACxC,aAAA,CAAwB;AAAA,OAC3B;AACA,MAAA,IAAI;AACF,QAAA,MAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,oBAAU,CAAA;AAClD,QAAA,OAAO,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAAA,MACjD,SAAS,QAAA,EAAU;AACjB,QAAA,MAAM,KAAA,GAAS,aAAA,CAAwB,OAAA,IAAW,MAAA,CAAO,aAAa,CAAA;AACtE,QAAA,MAAM,QAAQ,QAAA,YAAoB,KAAA,GAAQ,QAAA,CAAS,OAAA,GAAU,OAAO,QAAQ,CAAA;AAC5E,QAAA,MAAM,IAAIC,+BAAA;AAAA,UACRC,kCAAA;AAAA,UACA,mBAAmB,OAAA,CAAQ,WAAA,EAAa,CAAA,mBAAA,EAAsB,KAAK,YAAY,KAAK,CAAA,CAAA,CAAA;AAAA,UACpF;AAAA,SACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI;AACF,IAAA,MAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,oBAAU,CAAA;AAClD,IAAA,OAAO,MAAM,cAAA,CAAe,UAAA,EAAY,OAAO,CAAA;AAAA,EACjD,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,QAAQ,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAE7D,IAAA,OAAA,CAAQ,KAAA,CAAM,mCAAA,EAAqC,OAAA,EAAS,OAAA,EAAS,GAAG,CAAA;AACxE,IAAA,IAAI,YAAY,SAAA,EAAW;AACzB,MAAA,MAAM,IAAID,+BAAA;AAAA,QACRE,6CAAA;AAAA,QACA,CAAA,kFAAA,EAAqF,SAAS,cAAc,CAAA,CAAA;AAAA,QAC5G;AAAA,OACF;AAAA,IACF;AACA,IAAA,MAAM,IAAIF,+BAAA;AAAA,MACRG,yCAAA;AAAA,MACA,GAAG,OAAA,CAAQ,WAAA,EAAa,CAAA,+CAAA,EAAkD,SAAS,cAAc,CAAA,CAAA;AAAA,MACjG;AAAA,KACF;AAAA,EACF;AACF","file":"chunk-VLI3Y6IJ.cjs","sourcesContent":["import type {\n AudioCodec,\n AudioTrackInfo,\n ContainerKind,\n MediaContext,\n VideoCodec,\n VideoTrackInfo,\n} from \"../types.js\";\nimport type { NormalizedSource } from \"../util/source.js\";\n\n/**\n * Probe via mediabunny. Built against the real (typed) API exported by\n * `mediabunny.d.ts`:\n *\n * - `Input.getTracks()` returns `InputTrack[]`; each track has `isVideoTrack()`\n * / `isAudioTrack()` type guards plus a `codec` getter that returns one of\n * the enum strings (`\"avc\"|\"hevc\"|\"vp9\"|\"vp8\"|\"av1\"` for video,\n * `\"aac\"|\"opus\"|...` for audio).\n * - For decoder metadata + codec parameter strings we call\n * `getDecoderConfig()` and `getCodecParameterString()` on the typed track.\n *\n * The bridging back to avbridge's own codec naming (`h264` instead of mediabunny's\n * `avc`) happens here so the rest of the codebase keeps a single vocabulary.\n */\nexport async function probeWithMediabunny(\n source: NormalizedSource,\n sniffedContainer: ContainerKind,\n): Promise<MediaContext> {\n const mb = await import(\"mediabunny\");\n const input = new mb.Input({\n source: await buildMediabunnySource(mb, source),\n formats: mb.ALL_FORMATS,\n });\n\n const allTracks = await input.getTracks();\n const duration = await safeNumber(() => input.computeDuration());\n\n const videoTracks: VideoTrackInfo[] = [];\n const audioTracks: AudioTrackInfo[] = [];\n\n for (const track of allTracks) {\n if (track.isVideoTrack()) {\n const codecParam = await safe(() => track.getCodecParameterString());\n videoTracks.push({\n id: track.id,\n codec: mediabunnyVideoToAvbridge(track.codec),\n width: track.displayWidth ?? track.codedWidth ?? 0,\n height: track.displayHeight ?? track.codedHeight ?? 0,\n codecString: codecParam ?? undefined,\n });\n } else if (track.isAudioTrack()) {\n const codecParam = await safe(() => track.getCodecParameterString());\n audioTracks.push({\n id: track.id,\n codec: mediabunnyAudioToAvbridge(track.codec),\n channels: track.numberOfChannels ?? 0,\n sampleRate: track.sampleRate ?? 0,\n language: track.languageCode,\n codecString: codecParam ?? undefined,\n });\n }\n }\n\n const format = await safe(() => input.getFormat());\n const container = resolveContainer(format?.name, sniffedContainer);\n\n return {\n source: source.original,\n name: source.name,\n byteLength: source.byteLength,\n container,\n videoTracks,\n audioTracks,\n subtitleTracks: [],\n probedBy: \"mediabunny\",\n duration,\n };\n}\n\n/**\n * Build the right mediabunny `Source` for a normalized input. URL sources\n * use `UrlSource` (Range requests, prefetch, parallelism) so we don't\n * buffer the whole file into memory. Blob/File sources use `BlobSource`.\n *\n * Exported so the remux strategy can use the same routing logic.\n */\nexport async function buildMediabunnySource(\n mb: typeof import(\"mediabunny\"),\n source: NormalizedSource,\n): Promise<InstanceType<typeof mb.BlobSource> | InstanceType<typeof mb.UrlSource>> {\n if (source.kind === \"url\") {\n return new mb.UrlSource(source.url);\n }\n return new mb.BlobSource(source.blob);\n}\n\n/**\n * Build a mediabunny `Source` directly from a raw `MediaInput`, bypassing\n * `normalizeSource`. Used by strategies that already have the original\n * input on hand (via `MediaContext.source`) and don't need a sniff window.\n *\n * This is the routing point that decides \"stream from URL via Range\n * requests\" vs \"wrap in-memory bytes as BlobSource\". Always prefer\n * `UrlSource` for URL inputs so we don't accidentally buffer the file.\n */\nexport async function buildMediabunnySourceFromInput(\n mb: typeof import(\"mediabunny\"),\n source: import(\"../types.js\").MediaInput,\n): Promise<InstanceType<typeof mb.BlobSource> | InstanceType<typeof mb.UrlSource>> {\n if (typeof source === \"string\") return new mb.UrlSource(source);\n if (source instanceof URL) return new mb.UrlSource(source.toString());\n if (source instanceof Blob) return new mb.BlobSource(source);\n if (source instanceof ArrayBuffer) return new mb.BlobSource(new Blob([source]));\n if (source instanceof Uint8Array) return new mb.BlobSource(new Blob([source as BlobPart]));\n throw new TypeError(\"unsupported source type for mediabunny\");\n}\n\nfunction resolveContainer(formatName: string | undefined, sniffed: ContainerKind): ContainerKind {\n const name = (formatName ?? \"\").toLowerCase();\n if (name.includes(\"matroska\") || name.includes(\"mkv\")) return \"mkv\";\n if (name.includes(\"webm\")) return \"webm\";\n if (name.includes(\"mp4\") || name.includes(\"isom\")) return \"mp4\";\n if (name.includes(\"mov\") || name.includes(\"quicktime\")) return \"mov\";\n if (name.includes(\"ogg\")) return \"ogg\";\n if (name.includes(\"wav\")) return \"wav\";\n if (name.includes(\"flac\")) return \"flac\";\n if (name.includes(\"mp3\")) return \"mp3\";\n if (name.includes(\"adts\") || name.includes(\"aac\")) return \"adts\";\n if (name.includes(\"mpegts\") || name.includes(\"mpeg-ts\") || name.includes(\"transport\")) return \"mpegts\";\n return sniffed;\n}\n\n/** Mediabunny video codec → avbridge video codec. */\nexport function mediabunnyVideoToAvbridge(c: string | null | undefined): VideoCodec {\n switch (c) {\n case \"avc\": return \"h264\";\n case \"hevc\": return \"h265\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n default:\n // Preserve the original codec string when mediabunny gave us something\n // we don't recognize. The classifier checks `NATIVE_VIDEO_CODECS.has()`\n // and routes anything outside that set through the fallback chain — so\n // returning the unknown name (instead of silently relabeling it as\n // \"h264\") gets correct routing AND honest diagnostics.\n return c ? (c as VideoCodec) : \"unknown\";\n }\n}\n\n/** avbridge video codec → mediabunny video codec (for output sources). */\nexport function avbridgeVideoToMediabunny(c: VideoCodec): \"avc\" | \"hevc\" | \"vp9\" | \"vp8\" | \"av1\" | null {\n switch (c) {\n case \"h264\": return \"avc\";\n case \"h265\": return \"hevc\";\n case \"vp8\": return \"vp8\";\n case \"vp9\": return \"vp9\";\n case \"av1\": return \"av1\";\n default: return null;\n }\n}\n\nexport function mediabunnyAudioToAvbridge(c: string | null | undefined): AudioCodec {\n switch (c) {\n case \"aac\": return \"aac\";\n case \"mp3\": return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n default: return c ? (c as AudioCodec) : \"unknown\";\n }\n}\n\nexport function avbridgeAudioToMediabunny(c: AudioCodec): string | null {\n switch (c) {\n case \"aac\": return \"aac\";\n case \"mp3\": return \"mp3\";\n case \"opus\": return \"opus\";\n case \"vorbis\": return \"vorbis\";\n case \"flac\": return \"flac\";\n case \"ac3\": return \"ac3\";\n case \"eac3\": return \"eac3\";\n default: return null;\n }\n}\n\nasync function safeNumber(fn: () => Promise<number> | number): Promise<number | undefined> {\n try {\n const v = await fn();\n return typeof v === \"number\" && Number.isFinite(v) ? v : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function safe<T>(fn: () => Promise<T> | T): Promise<T | undefined> {\n try { return await fn(); } catch { return undefined; }\n}\n","import type { ContainerKind, MediaContext, MediaInput, TransportConfig } from \"../types.js\";\nimport { normalizeSource, sniffNormalizedSource } from \"../util/source.js\";\nimport { probeWithMediabunny } from \"./mediabunny.js\";\nimport { AvbridgeError, ERR_PROBE_FAILED, ERR_PROBE_UNKNOWN_CONTAINER, ERR_LIBAV_NOT_REACHABLE } from \"../errors.js\";\n\n/** Containers mediabunny can demux. Sniff results outside this set go straight to libav. */\nconst MEDIABUNNY_CONTAINERS = new Set<ContainerKind>([\n \"mp4\",\n \"mov\",\n \"mkv\",\n \"webm\",\n \"ogg\",\n \"wav\",\n \"mp3\",\n \"flac\",\n \"adts\",\n \"mpegts\",\n]);\n\n/**\n * Probe a source and produce a {@link MediaContext}.\n *\n * Routing:\n * 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.\n * 2. If the container is one mediabunny supports → try mediabunny first\n * (fast path — it's a single pass of WASM-free JS parsing). If mediabunny\n * throws (e.g. an assertion on an unsupported sample entry like `mp4v`\n * for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to\n * libav.js which handles the long tail of codecs mediabunny doesn't.\n * The combined-error case surfaces *both* failures so the user sees\n * which path each step took.\n * 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.\n * mediabunny can't read those containers at all, so there's no fast path\n * to try.\n */\nexport async function probe(\n source: MediaInput,\n transport?: TransportConfig,\n): Promise<MediaContext> {\n const normalized = await normalizeSource(source, transport);\n const sniffed = await sniffNormalizedSource(normalized);\n\n if (MEDIABUNNY_CONTAINERS.has(sniffed)) {\n try {\n const result = await probeWithMediabunny(normalized, sniffed);\n // If mediabunny returned unknown codecs, try libav which recognizes\n // a wider range (DTS, TrueHD, etc.). Only escalate if there ARE\n // tracks with unknown codecs — otherwise mediabunny's result is fine.\n const hasUnknownCodec =\n result.videoTracks.some((t) => t.codec === \"unknown\") ||\n result.audioTracks.some((t) => t.codec === \"unknown\");\n if (hasUnknownCodec) {\n try {\n const { probeWithLibav } = await import(\"./avi.js\");\n return await probeWithLibav(normalized, sniffed);\n } catch {\n // libav also failed — return mediabunny's result (unknown codecs\n // are better than no result at all)\n return result;\n }\n }\n return result;\n } catch (mediabunnyErr) {\n // mediabunny rejected the file. Before giving up, try libav — it can\n // demux a much wider range of codec combinations in ISOBMFF/MKV/etc.\n // than mediabunny's pure-JS parser (e.g. mp4v, wmv3-in-asf, flac in\n // an MP4 container). This is \"escalation\", not \"masking\": if libav\n // also fails we surface both errors below.\n // eslint-disable-next-line no-console\n console.warn(\n `[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,\n (mediabunnyErr as Error).message,\n );\n try {\n const { probeWithLibav } = await import(\"./avi.js\");\n return await probeWithLibav(normalized, sniffed);\n } catch (libavErr) {\n const mbMsg = (mediabunnyErr as Error).message || String(mediabunnyErr);\n const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);\n throw new AvbridgeError(\n ERR_PROBE_FAILED,\n `Failed to probe ${sniffed.toUpperCase()} file. mediabunny: ${mbMsg}. libav: ${lvMsg}.`,\n \"The file may be corrupt, truncated, or in an unsupported format. Enable AVBRIDGE_DEBUG for detailed logs.\",\n );\n }\n }\n }\n\n // sniffed === avi | asf | flv | unknown — try libav.\n try {\n const { probeWithLibav } = await import(\"./avi.js\");\n return await probeWithLibav(normalized, sniffed);\n } catch (err) {\n const inner = err instanceof Error ? err.message : String(err);\n // eslint-disable-next-line no-console\n console.error(\"[avbridge] libav probe failed for\", sniffed, \"file:\", err);\n if (sniffed === \"unknown\") {\n throw new AvbridgeError(\n ERR_PROBE_UNKNOWN_CONTAINER,\n `Unable to probe source: container format could not be identified. libav fallback: ${inner || \"(no details)\"}`,\n \"The file may be corrupt or in a format avbridge doesn't recognize. Check the file plays in VLC or ffprobe.\",\n );\n }\n throw new AvbridgeError(\n ERR_LIBAV_NOT_REACHABLE,\n `${sniffed.toUpperCase()} files require libav.js, which failed to load: ${inner || \"(no details)\"}`,\n \"Install @libav.js/variant-webcodecs, or check that AVBRIDGE_LIBAV_BASE points to the correct path.\",\n );\n }\n}\n"]}
|
package/dist/element-browser.js
CHANGED
|
@@ -29957,7 +29957,7 @@ async function probeWithLibav(source, sniffed) {
|
|
|
29957
29957
|
codec: ffmpegToAvbridgeVideo(codecName),
|
|
29958
29958
|
width: codecpar?.width ?? 0,
|
|
29959
29959
|
height: codecpar?.height ?? 0,
|
|
29960
|
-
fps: framerate(stream)
|
|
29960
|
+
fps: await framerate(libav, stream)
|
|
29961
29961
|
});
|
|
29962
29962
|
} else if (stream.codec_type === libav.AVMEDIA_TYPE_AUDIO) {
|
|
29963
29963
|
audioTracks.push({
|
|
@@ -29985,7 +29985,7 @@ async function probeWithLibav(source, sniffed) {
|
|
|
29985
29985
|
duration
|
|
29986
29986
|
};
|
|
29987
29987
|
}
|
|
29988
|
-
function framerate(stream) {
|
|
29988
|
+
async function framerate(libav, stream) {
|
|
29989
29989
|
if (typeof stream.avg_frame_rate_num === "number" && stream.avg_frame_rate_den) {
|
|
29990
29990
|
return stream.avg_frame_rate_num / stream.avg_frame_rate_den;
|
|
29991
29991
|
}
|
|
@@ -29993,6 +29993,14 @@ function framerate(stream) {
|
|
|
29993
29993
|
if (stream.avg_frame_rate.den === 0) return void 0;
|
|
29994
29994
|
return stream.avg_frame_rate.num / stream.avg_frame_rate.den;
|
|
29995
29995
|
}
|
|
29996
|
+
try {
|
|
29997
|
+
const num = await libav.AVCodecParameters_framerate_num?.(stream.codecpar);
|
|
29998
|
+
const den = await libav.AVCodecParameters_framerate_den?.(stream.codecpar);
|
|
29999
|
+
if (typeof num === "number" && typeof den === "number" && den > 0 && num > 0) {
|
|
30000
|
+
return num / den;
|
|
30001
|
+
}
|
|
30002
|
+
} catch {
|
|
30003
|
+
}
|
|
29996
30004
|
return void 0;
|
|
29997
30005
|
}
|
|
29998
30006
|
async function safeDuration(libav, fmt_ctx) {
|
|
@@ -32561,10 +32569,20 @@ var VideoRenderer = class {
|
|
|
32561
32569
|
/** Resolves once the first decoded frame has been enqueued. */
|
|
32562
32570
|
firstFrameReady;
|
|
32563
32571
|
resolveFirstFrame;
|
|
32564
|
-
/**
|
|
32572
|
+
/**
|
|
32573
|
+
* True once at least one frame has been enqueued *since the last flush*.
|
|
32574
|
+
* Used by `readyState` — initial cold-start reports HAVE_NOTHING until
|
|
32575
|
+
* any frame has arrived, and after a seek we want the same semantics
|
|
32576
|
+
* (HAVE_NOTHING until post-seek frames arrive), so the cumulative
|
|
32577
|
+
* `framesPainted > 0` that used to live here was wrong: it kept the
|
|
32578
|
+
* state "true forever" after the first frame ever, so post-seek
|
|
32579
|
+
* `waitForBuffer()` would exit immediately with an empty queue and
|
|
32580
|
+
* leave video frozen while audio kept going.
|
|
32581
|
+
*/
|
|
32565
32582
|
hasFrames() {
|
|
32566
|
-
return this.queue.length > 0 || this.
|
|
32583
|
+
return this.queue.length > 0 || this.hasEverEnqueuedSinceFlush;
|
|
32567
32584
|
}
|
|
32585
|
+
hasEverEnqueuedSinceFlush = false;
|
|
32568
32586
|
/** Current depth of the frame queue. Used by the decoder for backpressure. */
|
|
32569
32587
|
queueDepth() {
|
|
32570
32588
|
return this.queue.length;
|
|
@@ -32583,6 +32601,7 @@ var VideoRenderer = class {
|
|
|
32583
32601
|
return;
|
|
32584
32602
|
}
|
|
32585
32603
|
this.queue.push(frame);
|
|
32604
|
+
this.hasEverEnqueuedSinceFlush = true;
|
|
32586
32605
|
if (this.queue.length === 1 && this.framesPainted === 0) {
|
|
32587
32606
|
this.resolveFirstFrame();
|
|
32588
32607
|
}
|
|
@@ -32716,7 +32735,8 @@ var VideoRenderer = class {
|
|
|
32716
32735
|
}
|
|
32717
32736
|
return;
|
|
32718
32737
|
}
|
|
32719
|
-
const
|
|
32738
|
+
const _relaxDrop = globalThis.AVBRIDGE_RELAX_DROP === true;
|
|
32739
|
+
const dropThresholdUs = _relaxDrop ? audioNowUs - 60 * 1e6 : audioNowUs - frameDurationUs * 2;
|
|
32720
32740
|
let dropped = 0;
|
|
32721
32741
|
while (bestIdx > 0) {
|
|
32722
32742
|
const ts = this.queue[0].timestamp ?? 0;
|
|
@@ -32777,16 +32797,28 @@ var VideoRenderer = class {
|
|
|
32777
32797
|
while (this.queue.length > 0) this.queue.shift()?.close();
|
|
32778
32798
|
this.prerolled = false;
|
|
32779
32799
|
this.ptsCalibrated = false;
|
|
32800
|
+
this.hasEverEnqueuedSinceFlush = false;
|
|
32780
32801
|
if (isDebug() && count > 0) {
|
|
32781
32802
|
console.log(`[avbridge:renderer] FLUSH discarded=${count} painted=${this.framesPainted} drops=${this.framesDroppedLate}`);
|
|
32782
32803
|
}
|
|
32783
32804
|
}
|
|
32784
32805
|
stats() {
|
|
32806
|
+
let queueSpanMs = 0;
|
|
32807
|
+
let queueHeadMs = 0;
|
|
32808
|
+
let queueTailMs = 0;
|
|
32809
|
+
if (this.queue.length > 0) {
|
|
32810
|
+
queueHeadMs = Math.round((this.queue[0].timestamp ?? 0) / 1e3);
|
|
32811
|
+
queueTailMs = Math.round((this.queue[this.queue.length - 1].timestamp ?? 0) / 1e3);
|
|
32812
|
+
queueSpanMs = Math.max(0, queueTailMs - queueHeadMs);
|
|
32813
|
+
}
|
|
32785
32814
|
return {
|
|
32786
32815
|
framesPainted: this.framesPainted,
|
|
32787
32816
|
framesDroppedLate: this.framesDroppedLate,
|
|
32788
32817
|
framesDroppedOverflow: this.framesDroppedOverflow,
|
|
32789
|
-
queueDepth: this.queue.length
|
|
32818
|
+
queueDepth: this.queue.length,
|
|
32819
|
+
queueHeadMs,
|
|
32820
|
+
queueTailMs,
|
|
32821
|
+
queueSpanMs
|
|
32790
32822
|
};
|
|
32791
32823
|
}
|
|
32792
32824
|
destroy() {
|
|
@@ -33328,6 +33360,7 @@ async function startHybridDecoder(opts) {
|
|
|
33328
33360
|
}
|
|
33329
33361
|
let bsfCtx = null;
|
|
33330
33362
|
let bsfPkt = null;
|
|
33363
|
+
let bsfRequiredButMissing = false;
|
|
33331
33364
|
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
33332
33365
|
try {
|
|
33333
33366
|
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
@@ -33338,13 +33371,19 @@ async function startHybridDecoder(opts) {
|
|
|
33338
33371
|
bsfPkt = await libav.av_packet_alloc();
|
|
33339
33372
|
dbg.info("bsf", "mpeg4_unpack_bframes BSF active (hybrid)");
|
|
33340
33373
|
} else {
|
|
33341
|
-
|
|
33374
|
+
bsfRequiredButMissing = true;
|
|
33342
33375
|
bsfCtx = null;
|
|
33343
33376
|
}
|
|
33344
33377
|
} catch (err) {
|
|
33345
|
-
|
|
33378
|
+
bsfRequiredButMissing = true;
|
|
33346
33379
|
bsfCtx = null;
|
|
33347
33380
|
bsfPkt = null;
|
|
33381
|
+
dbg.warn("bsf", `hybrid: mpeg4_unpack_bframes BSF init failed: ${err.message}`);
|
|
33382
|
+
}
|
|
33383
|
+
if (bsfRequiredButMissing) {
|
|
33384
|
+
console.error(
|
|
33385
|
+
"[avbridge] MPEG-4 Part 2 (DivX/Xvid) detected but mpeg4_unpack_bframes BSF is unavailable in this libav variant. Files with packed B-frames will play with incorrect frame ordering. Rebuild the libav variant with the `avbsf` fragment included."
|
|
33386
|
+
);
|
|
33348
33387
|
}
|
|
33349
33388
|
}
|
|
33350
33389
|
async function applyBSF(packets) {
|
|
@@ -33354,7 +33393,6 @@ async function startHybridDecoder(opts) {
|
|
|
33354
33393
|
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
33355
33394
|
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
33356
33395
|
if (sendErr < 0) {
|
|
33357
|
-
out.push(pkt);
|
|
33358
33396
|
continue;
|
|
33359
33397
|
}
|
|
33360
33398
|
while (true) {
|
|
@@ -33368,10 +33406,13 @@ async function startHybridDecoder(opts) {
|
|
|
33368
33406
|
async function flushBSF() {
|
|
33369
33407
|
if (!bsfCtx || !bsfPkt) return;
|
|
33370
33408
|
try {
|
|
33371
|
-
|
|
33372
|
-
|
|
33373
|
-
|
|
33374
|
-
|
|
33409
|
+
if (libav.av_bsf_flush) {
|
|
33410
|
+
await libav.av_bsf_flush(bsfCtx);
|
|
33411
|
+
} else {
|
|
33412
|
+
while (true) {
|
|
33413
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
33414
|
+
if (err < 0) break;
|
|
33415
|
+
}
|
|
33375
33416
|
}
|
|
33376
33417
|
} catch {
|
|
33377
33418
|
}
|
|
@@ -33683,6 +33724,7 @@ async function startHybridDecoder(opts) {
|
|
|
33683
33724
|
videoChunksFed,
|
|
33684
33725
|
audioFramesDecoded,
|
|
33685
33726
|
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
33727
|
+
bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
|
|
33686
33728
|
videoDecodeQueueSize: videoDecoder?.decodeQueueSize ?? 0,
|
|
33687
33729
|
// Confirmed transport info — see fallback decoder for the pattern.
|
|
33688
33730
|
_transport: inputHandle.transport === "http-range" ? "http-range" : "memory",
|
|
@@ -33984,6 +34026,7 @@ async function startDecoder(opts) {
|
|
|
33984
34026
|
}
|
|
33985
34027
|
let bsfCtx = null;
|
|
33986
34028
|
let bsfPkt = null;
|
|
34029
|
+
let bsfRequiredButMissing = false;
|
|
33987
34030
|
if (videoStream && opts.context.videoTracks[0]?.codec === "mpeg4") {
|
|
33988
34031
|
try {
|
|
33989
34032
|
bsfCtx = await libav.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
@@ -33994,13 +34037,19 @@ async function startDecoder(opts) {
|
|
|
33994
34037
|
bsfPkt = await libav.av_packet_alloc();
|
|
33995
34038
|
dbg.info("bsf", "mpeg4_unpack_bframes BSF active");
|
|
33996
34039
|
} else {
|
|
33997
|
-
|
|
34040
|
+
bsfRequiredButMissing = true;
|
|
33998
34041
|
bsfCtx = null;
|
|
33999
34042
|
}
|
|
34000
34043
|
} catch (err) {
|
|
34001
|
-
|
|
34044
|
+
bsfRequiredButMissing = true;
|
|
34002
34045
|
bsfCtx = null;
|
|
34003
34046
|
bsfPkt = null;
|
|
34047
|
+
dbg.warn("bsf", `mpeg4_unpack_bframes BSF init failed: ${err.message}`);
|
|
34048
|
+
}
|
|
34049
|
+
if (bsfRequiredButMissing) {
|
|
34050
|
+
console.error(
|
|
34051
|
+
"[avbridge] MPEG-4 Part 2 (DivX/Xvid) detected but mpeg4_unpack_bframes BSF is unavailable in this libav variant. Files with packed B-frames will play with incorrect frame ordering (backwards PTS jumps, heavy late-drop stuttering). Rebuild the libav variant with the `avbsf` fragment included. See docs/dev/POSTMORTEMS.md for details."
|
|
34052
|
+
);
|
|
34004
34053
|
}
|
|
34005
34054
|
}
|
|
34006
34055
|
async function applyBSF(packets) {
|
|
@@ -34010,7 +34059,6 @@ async function startDecoder(opts) {
|
|
|
34010
34059
|
await libav.ff_copyin_packet(bsfPkt, pkt);
|
|
34011
34060
|
const sendErr = await libav.av_bsf_send_packet(bsfCtx, bsfPkt);
|
|
34012
34061
|
if (sendErr < 0) {
|
|
34013
|
-
out.push(pkt);
|
|
34014
34062
|
continue;
|
|
34015
34063
|
}
|
|
34016
34064
|
while (true) {
|
|
@@ -34024,10 +34072,13 @@ async function startDecoder(opts) {
|
|
|
34024
34072
|
async function flushBSF() {
|
|
34025
34073
|
if (!bsfCtx || !bsfPkt) return;
|
|
34026
34074
|
try {
|
|
34027
|
-
|
|
34028
|
-
|
|
34029
|
-
|
|
34030
|
-
|
|
34075
|
+
if (libav.av_bsf_flush) {
|
|
34076
|
+
await libav.av_bsf_flush(bsfCtx);
|
|
34077
|
+
} else {
|
|
34078
|
+
while (true) {
|
|
34079
|
+
const err = await libav.av_bsf_receive_packet(bsfCtx, bsfPkt);
|
|
34080
|
+
if (err < 0) break;
|
|
34081
|
+
}
|
|
34031
34082
|
}
|
|
34032
34083
|
} catch {
|
|
34033
34084
|
}
|
|
@@ -34045,6 +34096,19 @@ async function startDecoder(opts) {
|
|
|
34045
34096
|
let watchdogOverflowWarned = false;
|
|
34046
34097
|
let syntheticVideoUs = 0;
|
|
34047
34098
|
let syntheticAudioUs = 0;
|
|
34099
|
+
let videoDecodeMsTotal = 0;
|
|
34100
|
+
let audioDecodeMsTotal = 0;
|
|
34101
|
+
let videoDecodeBatches = 0;
|
|
34102
|
+
let audioDecodeBatches = 0;
|
|
34103
|
+
let readMsTotal = 0;
|
|
34104
|
+
let readBatches = 0;
|
|
34105
|
+
let pumpThrottleMsTotal = 0;
|
|
34106
|
+
let pumpThrottleEntries = 0;
|
|
34107
|
+
let slowestVideoBatchMs = 0;
|
|
34108
|
+
let newestVideoPtsUs = 0;
|
|
34109
|
+
let lastEmittedPtsUs = -1;
|
|
34110
|
+
let ptsRegressions = 0;
|
|
34111
|
+
let worstPtsRegressionMs = 0;
|
|
34048
34112
|
const videoTrackInfo = opts.context.videoTracks.find((t) => t.id === videoStream?.index);
|
|
34049
34113
|
const videoFps = videoTrackInfo?.fps && videoTrackInfo.fps > 0 ? videoTrackInfo.fps : 30;
|
|
34050
34114
|
const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
|
|
@@ -34053,9 +34117,12 @@ async function startDecoder(opts) {
|
|
|
34053
34117
|
let readErr;
|
|
34054
34118
|
let packets;
|
|
34055
34119
|
try {
|
|
34120
|
+
const _readStart = performance.now();
|
|
34056
34121
|
[readErr, packets] = await libav.ff_read_frame_multi(fmt_ctx, readPkt, {
|
|
34057
34122
|
limit: 16 * 1024
|
|
34058
34123
|
});
|
|
34124
|
+
readMsTotal += performance.now() - _readStart;
|
|
34125
|
+
readBatches++;
|
|
34059
34126
|
} catch (err) {
|
|
34060
34127
|
console.error("[avbridge] ff_read_frame_multi failed:", err);
|
|
34061
34128
|
return;
|
|
@@ -34117,8 +34184,17 @@ async function startDecoder(opts) {
|
|
|
34117
34184
|
}
|
|
34118
34185
|
}
|
|
34119
34186
|
}
|
|
34120
|
-
|
|
34121
|
-
|
|
34187
|
+
{
|
|
34188
|
+
const _throttleStart = performance.now();
|
|
34189
|
+
let _throttled = false;
|
|
34190
|
+
while (!destroyed && myToken === pumpToken && (opts.audio.bufferAhead() > 2 || opts.renderer.queueDepth() >= opts.renderer.queueHighWater)) {
|
|
34191
|
+
_throttled = true;
|
|
34192
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
34193
|
+
}
|
|
34194
|
+
if (_throttled) {
|
|
34195
|
+
pumpThrottleMsTotal += performance.now() - _throttleStart;
|
|
34196
|
+
pumpThrottleEntries++;
|
|
34197
|
+
}
|
|
34122
34198
|
}
|
|
34123
34199
|
if (readErr === libav.AVERROR_EOF) {
|
|
34124
34200
|
if (videoDec) await decodeVideoBatch(
|
|
@@ -34144,6 +34220,7 @@ async function startDecoder(opts) {
|
|
|
34144
34220
|
async function decodeVideoBatch(pkts, myToken, flush = false) {
|
|
34145
34221
|
if (!videoDec || destroyed || myToken !== pumpToken) return;
|
|
34146
34222
|
let frames;
|
|
34223
|
+
const _t0 = performance.now();
|
|
34147
34224
|
try {
|
|
34148
34225
|
frames = await libav.ff_decode_multi(
|
|
34149
34226
|
videoDec.c,
|
|
@@ -34156,18 +34233,38 @@ async function startDecoder(opts) {
|
|
|
34156
34233
|
console.error("[avbridge] video decode batch failed:", err);
|
|
34157
34234
|
return;
|
|
34158
34235
|
}
|
|
34236
|
+
{
|
|
34237
|
+
const _dt = performance.now() - _t0;
|
|
34238
|
+
videoDecodeMsTotal += _dt;
|
|
34239
|
+
videoDecodeBatches++;
|
|
34240
|
+
if (_dt > slowestVideoBatchMs) slowestVideoBatchMs = _dt;
|
|
34241
|
+
}
|
|
34159
34242
|
if (myToken !== pumpToken || destroyed) return;
|
|
34160
34243
|
for (const f of frames) {
|
|
34161
34244
|
if (myToken !== pumpToken || destroyed) return;
|
|
34162
34245
|
sanitizeFrameTimestamp(
|
|
34163
34246
|
f,
|
|
34164
34247
|
() => {
|
|
34165
|
-
const
|
|
34166
|
-
syntheticVideoUs
|
|
34167
|
-
return
|
|
34248
|
+
const base = lastEmittedPtsUs >= 0 ? lastEmittedPtsUs + videoFrameStepUs : syntheticVideoUs;
|
|
34249
|
+
syntheticVideoUs = base + videoFrameStepUs;
|
|
34250
|
+
return base;
|
|
34168
34251
|
},
|
|
34169
34252
|
videoTimeBase
|
|
34170
34253
|
);
|
|
34254
|
+
const _fPts = (f.ptshi ?? 0) * 4294967296 + (f.pts ?? 0);
|
|
34255
|
+
if (_fPts > newestVideoPtsUs) newestVideoPtsUs = _fPts;
|
|
34256
|
+
if (lastEmittedPtsUs >= 0 && _fPts < lastEmittedPtsUs) {
|
|
34257
|
+
ptsRegressions++;
|
|
34258
|
+
const regressMs = (lastEmittedPtsUs - _fPts) / 1e3;
|
|
34259
|
+
if (regressMs > worstPtsRegressionMs) worstPtsRegressionMs = regressMs;
|
|
34260
|
+
if (ptsRegressions <= 10) {
|
|
34261
|
+
console.warn(
|
|
34262
|
+
`[avbridge:decoder] dropped out-of-order frame #${ptsRegressions}: pts=${(_fPts / 1e3).toFixed(1)}ms < previous=${(lastEmittedPtsUs / 1e3).toFixed(1)}ms (regression=${regressMs.toFixed(1)}ms). Typically a post-seek B-frame reorder tail.`
|
|
34263
|
+
);
|
|
34264
|
+
}
|
|
34265
|
+
continue;
|
|
34266
|
+
}
|
|
34267
|
+
lastEmittedPtsUs = _fPts;
|
|
34171
34268
|
try {
|
|
34172
34269
|
const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
|
|
34173
34270
|
opts.renderer.enqueue(vf);
|
|
@@ -34182,6 +34279,7 @@ async function startDecoder(opts) {
|
|
|
34182
34279
|
async function decodeAudioBatch(pkts, myToken, flush = false) {
|
|
34183
34280
|
if (!audioDec || destroyed || myToken !== pumpToken) return;
|
|
34184
34281
|
let frames;
|
|
34282
|
+
const _t0 = performance.now();
|
|
34185
34283
|
try {
|
|
34186
34284
|
frames = await libav.ff_decode_multi(
|
|
34187
34285
|
audioDec.c,
|
|
@@ -34194,6 +34292,8 @@ async function startDecoder(opts) {
|
|
|
34194
34292
|
console.error("[avbridge] audio decode batch failed:", err);
|
|
34195
34293
|
return;
|
|
34196
34294
|
}
|
|
34295
|
+
audioDecodeMsTotal += performance.now() - _t0;
|
|
34296
|
+
audioDecodeBatches++;
|
|
34197
34297
|
if (myToken !== pumpToken || destroyed) return;
|
|
34198
34298
|
for (const f of frames) {
|
|
34199
34299
|
if (myToken !== pumpToken || destroyed) return;
|
|
@@ -34315,6 +34415,7 @@ async function startDecoder(opts) {
|
|
|
34315
34415
|
await flushBSF();
|
|
34316
34416
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
34317
34417
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
34418
|
+
lastEmittedPtsUs = -1;
|
|
34318
34419
|
pumpRunning = pumpLoop(newToken).catch(
|
|
34319
34420
|
(err) => console.error("[avbridge] fallback pump failed (post-setAudioTrack):", err)
|
|
34320
34421
|
);
|
|
@@ -34352,6 +34453,7 @@ async function startDecoder(opts) {
|
|
|
34352
34453
|
await flushBSF();
|
|
34353
34454
|
syntheticVideoUs = Math.round(timeSec * 1e6);
|
|
34354
34455
|
syntheticAudioUs = Math.round(timeSec * 1e6);
|
|
34456
|
+
lastEmittedPtsUs = -1;
|
|
34355
34457
|
pumpRunning = pumpLoop(newToken).catch(
|
|
34356
34458
|
(err) => console.error("[avbridge] decoder pump failed (post-seek):", err)
|
|
34357
34459
|
);
|
|
@@ -34365,7 +34467,24 @@ async function startDecoder(opts) {
|
|
|
34365
34467
|
packetsRead,
|
|
34366
34468
|
videoFramesDecoded,
|
|
34367
34469
|
audioFramesDecoded,
|
|
34470
|
+
// Throughput instrumentation — the stats panel turns these into
|
|
34471
|
+
// "decode fps actual / realtime target" and shows slowest batch
|
|
34472
|
+
// + producer throttle share.
|
|
34473
|
+
videoDecodeMsTotal,
|
|
34474
|
+
videoDecodeBatches,
|
|
34475
|
+
audioDecodeMsTotal,
|
|
34476
|
+
audioDecodeBatches,
|
|
34477
|
+
readMsTotal,
|
|
34478
|
+
readBatches,
|
|
34479
|
+
pumpThrottleMsTotal,
|
|
34480
|
+
pumpThrottleEntries,
|
|
34481
|
+
slowestVideoBatchMs,
|
|
34482
|
+
newestVideoPtsMs: Math.round(newestVideoPtsUs / 1e3),
|
|
34483
|
+
ptsRegressions,
|
|
34484
|
+
worstPtsRegressionMs,
|
|
34485
|
+
sourceFps: videoFps,
|
|
34368
34486
|
bsfApplied: bsfCtx ? ["mpeg4_unpack_bframes"] : [],
|
|
34487
|
+
bsfMissing: bsfRequiredButMissing ? ["mpeg4_unpack_bframes"] : [],
|
|
34369
34488
|
// Confirmed transport info: once prepareLibavInput returns
|
|
34370
34489
|
// successfully, we *know* whether the source is http-range (probe
|
|
34371
34490
|
// succeeded and returned 206) or in-memory blob. Diagnostics hoists
|