avbridge 2.1.0 → 2.1.2
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 +56 -0
- package/dist/{chunk-CUQD23WO.js → chunk-3AUGRKPY.js} +31 -10
- package/dist/chunk-3AUGRKPY.js.map +1 -0
- package/dist/{chunk-O34444ID.cjs → chunk-DPVIOYGC.cjs} +31 -10
- package/dist/chunk-DPVIOYGC.cjs.map +1 -0
- package/dist/element-browser.js +945 -17
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +7 -3
- package/dist/element.cjs.map +1 -1
- package/dist/element.js +6 -2
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +14 -14
- package/dist/index.d.cts +10 -6
- package/dist/index.d.ts +10 -6
- package/dist/index.js +2 -2
- package/package.json +1 -1
- package/src/element/avbridge-video.ts +12 -1
- package/src/probe/index.ts +30 -9
- package/src/strategies/fallback/video-renderer.ts +29 -4
- package/src/strategies/remux/pipeline.ts +10 -1
- package/dist/chunk-CUQD23WO.js.map +0 -1
- package/dist/chunk-O34444ID.cjs.map +0 -1
package/dist/index.d.cts
CHANGED
|
@@ -11,12 +11,16 @@ declare function classifyContext(ctx: MediaContext): Classification;
|
|
|
11
11
|
*
|
|
12
12
|
* Routing:
|
|
13
13
|
* 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.
|
|
14
|
-
* 2. If the container is one mediabunny supports → mediabunny
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* 2. If the container is one mediabunny supports → try mediabunny first
|
|
15
|
+
* (fast path — it's a single pass of WASM-free JS parsing). If mediabunny
|
|
16
|
+
* throws (e.g. an assertion on an unsupported sample entry like `mp4v`
|
|
17
|
+
* for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to
|
|
18
|
+
* libav.js which handles the long tail of codecs mediabunny doesn't.
|
|
19
|
+
* The combined-error case surfaces *both* failures so the user sees
|
|
20
|
+
* which path each step took.
|
|
21
|
+
* 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.
|
|
22
|
+
* mediabunny can't read those containers at all, so there's no fast path
|
|
23
|
+
* to try.
|
|
20
24
|
*/
|
|
21
25
|
declare function probe(source: MediaInput): Promise<MediaContext>;
|
|
22
26
|
|
package/dist/index.d.ts
CHANGED
|
@@ -11,12 +11,16 @@ declare function classifyContext(ctx: MediaContext): Classification;
|
|
|
11
11
|
*
|
|
12
12
|
* Routing:
|
|
13
13
|
* 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.
|
|
14
|
-
* 2. If the container is one mediabunny supports → mediabunny
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* 2. If the container is one mediabunny supports → try mediabunny first
|
|
15
|
+
* (fast path — it's a single pass of WASM-free JS parsing). If mediabunny
|
|
16
|
+
* throws (e.g. an assertion on an unsupported sample entry like `mp4v`
|
|
17
|
+
* for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to
|
|
18
|
+
* libav.js which handles the long tail of codecs mediabunny doesn't.
|
|
19
|
+
* The combined-error case surfaces *both* failures so the user sees
|
|
20
|
+
* which path each step took.
|
|
21
|
+
* 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.
|
|
22
|
+
* mediabunny can't read those containers at all, so there's no fast path
|
|
23
|
+
* to try.
|
|
20
24
|
*/
|
|
21
25
|
declare function probe(source: MediaInput): Promise<MediaContext>;
|
|
22
26
|
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-
|
|
2
|
-
export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-
|
|
1
|
+
import { probe, avbridgeVideoToMediabunny, avbridgeAudioToMediabunny, buildMediabunnySourceFromInput } from './chunk-3AUGRKPY.js';
|
|
2
|
+
export { UnifiedPlayer, classifyContext as classify, createPlayer, probe, srtToVtt } from './chunk-3AUGRKPY.js';
|
|
3
3
|
import { normalizeSource } from './chunk-PQTZS7OA.js';
|
|
4
4
|
import { prepareLibavInput } from './chunk-WD2ZNQA7.js';
|
|
5
5
|
import './chunk-EJH67FXG.js';
|
package/package.json
CHANGED
|
@@ -170,11 +170,22 @@ export class AvbridgeVideoElement extends HTMLElementCtor {
|
|
|
170
170
|
constructor() {
|
|
171
171
|
super();
|
|
172
172
|
const root = this.attachShadow({ mode: "open" });
|
|
173
|
+
|
|
174
|
+
// A positioned wrapper inside the shadow root. The fallback strategy
|
|
175
|
+
// overlays a canvas on top of the <video> via `target.parentNode` —
|
|
176
|
+
// that only works if the parent is a real Element with layout. Without
|
|
177
|
+
// this wrapper, `target.parentElement` would be null (ShadowRoot is
|
|
178
|
+
// not an Element) and the canvas would never attach to the DOM.
|
|
179
|
+
const stage = document.createElement("div");
|
|
180
|
+
stage.setAttribute("part", "stage");
|
|
181
|
+
stage.style.cssText = "position:relative;width:100%;height:100%;display:block;";
|
|
182
|
+
root.appendChild(stage);
|
|
183
|
+
|
|
173
184
|
this._videoEl = document.createElement("video");
|
|
174
185
|
this._videoEl.setAttribute("part", "video");
|
|
175
186
|
this._videoEl.style.cssText = "width:100%;height:100%;display:block;background:#000;";
|
|
176
187
|
this._videoEl.playsInline = true;
|
|
177
|
-
|
|
188
|
+
stage.appendChild(this._videoEl);
|
|
178
189
|
|
|
179
190
|
// Forward the underlying <video>'s `progress` event so consumers can
|
|
180
191
|
// observe buffered-range updates without reaching into the shadow DOM.
|
package/src/probe/index.ts
CHANGED
|
@@ -21,12 +21,16 @@ const MEDIABUNNY_CONTAINERS = new Set<ContainerKind>([
|
|
|
21
21
|
*
|
|
22
22
|
* Routing:
|
|
23
23
|
* 1. Sniff the magic header. Cheap, deterministic, ignores file extensions.
|
|
24
|
-
* 2. If the container is one mediabunny supports → mediabunny
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
24
|
+
* 2. If the container is one mediabunny supports → try mediabunny first
|
|
25
|
+
* (fast path — it's a single pass of WASM-free JS parsing). If mediabunny
|
|
26
|
+
* throws (e.g. an assertion on an unsupported sample entry like `mp4v`
|
|
27
|
+
* for MPEG-4 Part 2 in ISOBMFF, or an exotic MKV codec), fall through to
|
|
28
|
+
* libav.js which handles the long tail of codecs mediabunny doesn't.
|
|
29
|
+
* The combined-error case surfaces *both* failures so the user sees
|
|
30
|
+
* which path each step took.
|
|
31
|
+
* 3. If sniffing identifies AVI/ASF/FLV (or `unknown`) → libav.js directly.
|
|
32
|
+
* mediabunny can't read those containers at all, so there's no fast path
|
|
33
|
+
* to try.
|
|
30
34
|
*/
|
|
31
35
|
export async function probe(source: MediaInput): Promise<MediaContext> {
|
|
32
36
|
const normalized = await normalizeSource(source);
|
|
@@ -35,10 +39,27 @@ export async function probe(source: MediaInput): Promise<MediaContext> {
|
|
|
35
39
|
if (MEDIABUNNY_CONTAINERS.has(sniffed)) {
|
|
36
40
|
try {
|
|
37
41
|
return await probeWithMediabunny(normalized, sniffed);
|
|
38
|
-
} catch (
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
} catch (mediabunnyErr) {
|
|
43
|
+
// mediabunny rejected the file. Before giving up, try libav — it can
|
|
44
|
+
// demux a much wider range of codec combinations in ISOBMFF/MKV/etc.
|
|
45
|
+
// than mediabunny's pure-JS parser (e.g. mp4v, wmv3-in-asf, flac in
|
|
46
|
+
// an MP4 container). This is "escalation", not "masking": if libav
|
|
47
|
+
// also fails we surface both errors below.
|
|
48
|
+
// eslint-disable-next-line no-console
|
|
49
|
+
console.warn(
|
|
50
|
+
`[avbridge] mediabunny rejected ${sniffed} file, falling back to libav:`,
|
|
51
|
+
(mediabunnyErr as Error).message,
|
|
41
52
|
);
|
|
53
|
+
try {
|
|
54
|
+
const { probeWithLibav } = await import("./avi.js");
|
|
55
|
+
return await probeWithLibav(normalized, sniffed);
|
|
56
|
+
} catch (libavErr) {
|
|
57
|
+
const mbMsg = (mediabunnyErr as Error).message || String(mediabunnyErr);
|
|
58
|
+
const lvMsg = libavErr instanceof Error ? libavErr.message : String(libavErr);
|
|
59
|
+
throw new Error(
|
|
60
|
+
`failed to probe ${sniffed} file. mediabunny: ${mbMsg}. libav fallback: ${lvMsg}.`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
42
63
|
}
|
|
43
64
|
}
|
|
44
65
|
|
|
@@ -53,11 +53,36 @@ export class VideoRenderer {
|
|
|
53
53
|
this.canvas = document.createElement("canvas");
|
|
54
54
|
this.canvas.style.cssText =
|
|
55
55
|
"position:absolute;left:0;top:0;width:100%;height:100%;background:black;";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
|
|
57
|
+
// Attach the canvas next to the video. When the video lives inside an
|
|
58
|
+
// `<avbridge-video>` shadow root, `target.parentElement` is the
|
|
59
|
+
// positioned `<div part="stage">` wrapper the element created
|
|
60
|
+
// precisely for this purpose. When the video is used standalone
|
|
61
|
+
// (legacy `createPlayer({ target: videoEl })` path), we fall back to
|
|
62
|
+
// `parentNode` — which handles plain Elements, and also ShadowRoots
|
|
63
|
+
// if someone inserts a bare <video> inside their own shadow DOM
|
|
64
|
+
// without a wrapper.
|
|
65
|
+
const parent: ParentNode | null =
|
|
66
|
+
(target.parentElement as ParentNode | null) ?? target.parentNode;
|
|
67
|
+
if (parent && parent instanceof HTMLElement) {
|
|
68
|
+
if (getComputedStyle(parent).position === "static") {
|
|
69
|
+
parent.style.position = "relative";
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (parent) {
|
|
73
|
+
parent.insertBefore(this.canvas, target);
|
|
74
|
+
} else {
|
|
75
|
+
// No parent at all — the target is detached. Fall back to appending
|
|
76
|
+
// the canvas to document.body so at least the frames are visible
|
|
77
|
+
// somewhere while the consumer fixes their DOM layout. This is a
|
|
78
|
+
// loud fallback: log a warning so the misuse is obvious.
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.warn(
|
|
81
|
+
"[avbridge] fallback renderer: target <video> has no parent; " +
|
|
82
|
+
"appending canvas to document.body as a fallback.",
|
|
83
|
+
);
|
|
84
|
+
document.body.appendChild(this.canvas);
|
|
59
85
|
}
|
|
60
|
-
parent?.insertBefore(this.canvas, target);
|
|
61
86
|
target.style.visibility = "hidden";
|
|
62
87
|
|
|
63
88
|
const ctx = this.canvas.getContext("2d");
|
|
@@ -187,7 +187,16 @@ export async function createRemuxPipeline(
|
|
|
187
187
|
const vTs = !vNext.done ? vNext.value.timestamp : Number.POSITIVE_INFINITY;
|
|
188
188
|
const aTs = !aNext.done ? aNext.value.timestamp : Number.POSITIVE_INFINITY;
|
|
189
189
|
|
|
190
|
-
|
|
190
|
+
// Mediabunny's muxer requires the first packet on a fresh Output to
|
|
191
|
+
// be a key packet. We fetched `startVideoPacket` via
|
|
192
|
+
// `videoSink.getKeyPacket(fromTime)` so the first video packet is
|
|
193
|
+
// guaranteed to be a keyframe — but a demuxer can hand us an audio
|
|
194
|
+
// packet with a lower timestamp, which mediabunny rejects with
|
|
195
|
+
// "First packet must be a key packet." Force the first video
|
|
196
|
+
// packet out before we let any audio through.
|
|
197
|
+
const forceVideoFirst = firstVideo && !vNext.done;
|
|
198
|
+
|
|
199
|
+
if (!vNext.done && (forceVideoFirst || vTs <= aTs)) {
|
|
191
200
|
await videoSource.add(
|
|
192
201
|
vNext.value,
|
|
193
202
|
firstVideo && videoConfig ? { decoderConfig: videoConfig } : undefined,
|