@banou/media-player 0.8.20 → 0.9.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/README.md +62 -19
- package/dist/engine/index.js +1 -1
- package/dist/engine/picture-in-picture.d.ts +9 -2
- package/dist/engine/playback.d.ts +8 -1
- package/dist/engine/subtitles.d.ts +16 -5
- package/dist/{engine-B8CKGIng.js → engine-DZrYu66u.js} +209 -164
- package/dist/index.js +34 -14
- package/dist/react/components/chrome.d.ts +2 -2
- package/dist/react/components/overlay.d.ts +2 -2
- package/dist/react/hooks/use-picture-in-picture.d.ts +6 -1
- package/dist/react/hooks/use-playback.d.ts +1 -1
- package/dist/remote/index.d.ts +18 -0
- package/dist/remote/index.js +109 -75
- package/package.json +3 -4
- package/src/lib/engine/picture-in-picture.ts +24 -9
- package/src/lib/engine/playback.ts +10 -3
- package/src/lib/engine/subtitles.ts +213 -61
- package/src/lib/react/components/chrome.tsx +16 -3
- package/src/lib/react/components/overlay.tsx +27 -8
- package/src/lib/react/hooks/use-picture-in-picture.ts +10 -6
- package/src/lib/react/hooks/use-playback.ts +4 -4
- package/src/lib/react/video-player.tsx +4 -4
- package/src/lib/remote/index.ts +83 -3
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-
|
|
1
|
+
import { l as e, n as t, o as n, r, s as i, t as a, u as o } from "./engine-DZrYu66u.js";
|
|
2
2
|
import { Children as s, Fragment as c, isValidElement as l, useCallback as u, useEffect as d, useMemo as f, useRef as p, useState as m } from "react";
|
|
3
3
|
import { css as h, keyframes as g } from "@emotion/react";
|
|
4
4
|
import { createPlayer as _, useContainerAttach as v, useMediaAttach as y } from "@videojs/react";
|
|
@@ -179,7 +179,7 @@ var ee = {
|
|
|
179
179
|
try {
|
|
180
180
|
let n = await i({
|
|
181
181
|
videoElement: e,
|
|
182
|
-
|
|
182
|
+
subtitleContainer: t,
|
|
183
183
|
read: (e, t) => A.current(e, t),
|
|
184
184
|
length: s,
|
|
185
185
|
publicPath: c,
|
|
@@ -287,7 +287,7 @@ var ee = {
|
|
|
287
287
|
if (!e || !n || !s) return;
|
|
288
288
|
let t = a({
|
|
289
289
|
video: e,
|
|
290
|
-
|
|
290
|
+
subtitles: n,
|
|
291
291
|
mode: s,
|
|
292
292
|
onBurnedInChange: o
|
|
293
293
|
});
|
|
@@ -337,12 +337,18 @@ var ee = {
|
|
|
337
337
|
regular: "\n font-weight: 400;\n font-size: calc(0.8 * var(--mp-unit));\n line-height: calc(1.1 * var(--mp-unit));\n @media (min-width: 960px) {\n font-size: calc(1 * var(--mp-unit));\n line-height: calc(1.4 * var(--mp-unit));\n }\n "
|
|
338
338
|
}
|
|
339
339
|
}, ge = h`
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
340
|
+
display: flex;
|
|
341
|
+
justify-content: center;
|
|
342
|
+
align-items: center;
|
|
343
|
+
|
|
344
|
+
canvas {
|
|
345
|
+
top: unset !important;
|
|
346
|
+
left: unset !important;
|
|
347
|
+
width: 100%;
|
|
348
|
+
height: 100%;
|
|
349
|
+
margin: auto;
|
|
350
|
+
pointer-events: none;
|
|
351
|
+
}
|
|
346
352
|
`, _e = h`
|
|
347
353
|
position: absolute;
|
|
348
354
|
top: 0;
|
|
@@ -402,7 +408,7 @@ var ee = {
|
|
|
402
408
|
${H.bLarge.regular}
|
|
403
409
|
text-shadow: 0 0 4px rgba(0, 0, 0, 1);
|
|
404
410
|
pointer-events: none;
|
|
405
|
-
`, be = (e) => e instanceof Error ? e.message : typeof e == "string" ? e : "Playback failed", xe = ({
|
|
411
|
+
`, be = (e) => e instanceof Error ? e.message : typeof e == "string" ? e : "Playback failed", xe = ({ onSubtitleRef: e }) => {
|
|
406
412
|
let t = V((e) => e.title), n = V((e) => e.hideUI), r = V((e) => e.playbackError), i = V((e) => e.ready), a = V((e) => e.size), o = V((e) => e.waiting);
|
|
407
413
|
return /* @__PURE__ */ w(S, { children: [
|
|
408
414
|
t ? /* @__PURE__ */ C("div", {
|
|
@@ -419,7 +425,8 @@ var ee = {
|
|
|
419
425
|
css: ye,
|
|
420
426
|
children: be(r)
|
|
421
427
|
}) : void 0,
|
|
422
|
-
/* @__PURE__ */ C("
|
|
428
|
+
/* @__PURE__ */ C("div", {
|
|
429
|
+
className: "subtitles",
|
|
423
430
|
ref: e,
|
|
424
431
|
css: ge
|
|
425
432
|
})
|
|
@@ -2088,6 +2095,19 @@ ${X}
|
|
|
2088
2095
|
z-index: 2;
|
|
2089
2096
|
}
|
|
2090
2097
|
|
|
2098
|
+
/* The subtitle layer sits UNDER the title and the spinner, where the canvas itself used to sit.
|
|
2099
|
+
As a plain child div it is matched by the rule above and raised to the overlay items' level,
|
|
2100
|
+
which paints subtitles over the title's gradient, over the spinner and over the error text.
|
|
2101
|
+
Naming the type as well is what takes it back: :not() carries the specificity of its argument,
|
|
2102
|
+
so the rule above is (0,2,1) and a bare .subtitles at (0,2,0) loses to it whatever the source
|
|
2103
|
+
order, while div.subtitles ties at (0,2,1) and second place in the same block then wins.
|
|
2104
|
+
subtitle-layering.browser.test.tsx measures the computed value rather than trusting this. */
|
|
2105
|
+
& > div.subtitles {
|
|
2106
|
+
inset: 0;
|
|
2107
|
+
z-index: 1;
|
|
2108
|
+
pointer-events: none;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2091
2111
|
canvas {
|
|
2092
2112
|
height: 100%;
|
|
2093
2113
|
width: 100%;
|
|
@@ -2112,7 +2132,7 @@ ${X}
|
|
|
2112
2132
|
key: r,
|
|
2113
2133
|
item: e
|
|
2114
2134
|
}];
|
|
2115
|
-
}), ut = ({ ref: e, onVideoRef: t,
|
|
2135
|
+
}), ut = ({ ref: e, onVideoRef: t, onSubtitleRef: n, overlay: r, controls: i, children: a }) => {
|
|
2116
2136
|
let o = V(), s = V((e) => e.hideUI), c = V((e) => e.setHideUI), l = p(void 0), u = p("mouse"), f = p({
|
|
2117
2137
|
x: -1,
|
|
2118
2138
|
y: -1
|
|
@@ -2155,7 +2175,7 @@ ${X}
|
|
|
2155
2175
|
},
|
|
2156
2176
|
className: s ? "hide" : "",
|
|
2157
2177
|
children: [
|
|
2158
|
-
/* @__PURE__ */ C(xe, {
|
|
2178
|
+
/* @__PURE__ */ C(xe, { onSubtitleRef: n }),
|
|
2159
2179
|
lt(r).map(({ key: e, item: t }) => /* @__PURE__ */ C("div", {
|
|
2160
2180
|
css: st,
|
|
2161
2181
|
style: { ...s ? {
|
|
@@ -2265,7 +2285,7 @@ ${X}
|
|
|
2265
2285
|
}, [I, z]), /* @__PURE__ */ C(ut, {
|
|
2266
2286
|
ref: g,
|
|
2267
2287
|
onVideoRef: n ? void 0 : b,
|
|
2268
|
-
|
|
2288
|
+
onSubtitleRef: S,
|
|
2269
2289
|
overlay: e.overlay,
|
|
2270
2290
|
controls: e.controls,
|
|
2271
2291
|
children: t
|
|
@@ -3,12 +3,12 @@ export type ChromeProps = {
|
|
|
3
3
|
ref?: Ref<HTMLDivElement> | ((element: HTMLDivElement | null) => void);
|
|
4
4
|
/** Absent means render no video element: the media belongs to someone else and arrives as children. */
|
|
5
5
|
onVideoRef?: (element: HTMLVideoElement | null) => void;
|
|
6
|
-
|
|
6
|
+
onSubtitleRef: (element: HTMLDivElement | null) => void;
|
|
7
7
|
/** The app's own content, over the video and outside the click-to-pause region, unlike `children`. */
|
|
8
8
|
overlay?: ReactNode;
|
|
9
9
|
/** False draws no control bar at all, leaving the picture, the title and the overlay. */
|
|
10
10
|
controls?: boolean;
|
|
11
11
|
children?: ReactNode;
|
|
12
12
|
};
|
|
13
|
-
export declare const Chrome: ({ ref, onVideoRef,
|
|
13
|
+
export declare const Chrome: ({ ref, onVideoRef, onSubtitleRef, overlay, controls, children }: ChromeProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
14
14
|
export default Chrome;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const Overlay: ({
|
|
2
|
-
|
|
1
|
+
export declare const Overlay: ({ onSubtitleRef }: {
|
|
2
|
+
onSubtitleRef: (element: HTMLDivElement | null) => void;
|
|
3
3
|
}) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
4
4
|
export default Overlay;
|
|
@@ -13,5 +13,10 @@ export type PictureInPicture = {
|
|
|
13
13
|
* hidden mirror. Where it does not, and the engine is Gecko, the same composite becomes the picture
|
|
14
14
|
* in the page so that the BROWSER'S own picture in picture control carries the subtitles with it,
|
|
15
15
|
* which it otherwise cannot: it takes a video element, and the subtitles live on a canvas above one.
|
|
16
|
+
*
|
|
17
|
+
* The second argument is the subtitle LAYER, not that canvas. From jassub 2 the canvas belongs to the
|
|
18
|
+
* renderer and is replaced on every pipeline rebuild, so only the layer lives long enough to hold.
|
|
16
19
|
*/
|
|
17
|
-
export declare const usePictureInPicture: (video: HTMLVideoElement | null,
|
|
20
|
+
export declare const usePictureInPicture: (video: HTMLVideoElement | null,
|
|
21
|
+
/** The subtitle layer, not the canvas: see `PictureInPictureOptions.subtitles`. */
|
|
22
|
+
subtitles: HTMLElement | null) => PictureInPicture;
|
|
@@ -3,6 +3,6 @@ import type { MediaPlayerLocalOptions } from '../video-player';
|
|
|
3
3
|
* Owns the engine for the life of a source: start, teardown, and every piece of state the pipeline
|
|
4
4
|
* discovers. Nothing is mirrored in React state, so the store is the only place any of it lives.
|
|
5
5
|
*/
|
|
6
|
-
export declare const usePlayback: (video: HTMLVideoElement | null,
|
|
6
|
+
export declare const usePlayback: (video: HTMLVideoElement | null, subtitles: HTMLElement | null,
|
|
7
7
|
/** null when the media is remote: there are no bytes, so there is no pipeline to run. */
|
|
8
8
|
options: MediaPlayerLocalOptions | null) => void;
|
package/dist/remote/index.d.ts
CHANGED
|
@@ -83,6 +83,24 @@ export type MediaPlayerHandle = {
|
|
|
83
83
|
* lifetime or `Promise.race` for the wait alone.
|
|
84
84
|
*/
|
|
85
85
|
readonly ready: Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Start playing, muting first if that is what the far document requires, and say which happened.
|
|
88
|
+
*
|
|
89
|
+
* `play()` runs under the FAR document's autoplay policy, and no message carries a user gesture
|
|
90
|
+
* across a frame boundary: however deliberately somebody clicked over here, an unmuted element in
|
|
91
|
+
* a document nobody has touched refuses to start. Muted playback is always permitted, so this
|
|
92
|
+
* tries honestly first and falls back rather than leaving the player stopped.
|
|
93
|
+
*
|
|
94
|
+
* `muted` in the result is what the player ended up as, so an app that gets `true` can offer the
|
|
95
|
+
* viewer a way to turn sound on. That click is itself the gesture the document was missing, and
|
|
96
|
+
* clearing `muted` afterwards needs no permission from anyone.
|
|
97
|
+
*
|
|
98
|
+
* If muted playback is refused too, the FIRST error is what rejects, since a policy refusal says
|
|
99
|
+
* more than whatever the retry hit, and the player is left unmuted as it was found.
|
|
100
|
+
*/
|
|
101
|
+
autoplay: () => Promise<{
|
|
102
|
+
muted: boolean;
|
|
103
|
+
}>;
|
|
86
104
|
/** stop mirroring and release the channel; the far player keeps playing */
|
|
87
105
|
destroy: () => void;
|
|
88
106
|
};
|
package/dist/remote/index.js
CHANGED
|
@@ -90,29 +90,29 @@ var t = [
|
|
|
90
90
|
length: e.length,
|
|
91
91
|
start: (t) => e[t]?.[0] ?? 0,
|
|
92
92
|
end: (t) => e[t]?.[1] ?? 0
|
|
93
|
-
}), f = 1e4, p =
|
|
94
|
-
|
|
93
|
+
}), f = 1e4, p = 1500, m = () => new DOMException("the far player did not start playing", "NotAllowedError"), h = 8, g = 32, _ = (e, t = {}) => {
|
|
94
|
+
T(t.origin);
|
|
95
95
|
let n = typeof window < "u" && window.parent !== window;
|
|
96
|
-
return !t.transport && !n ? () => {} :
|
|
97
|
-
},
|
|
98
|
-
let a = i.channel ?? "banou-media-player", s = i.transport, l = `${a}|${i.origin ?? "*"}`, d = s ?
|
|
96
|
+
return !t.transport && !n ? () => {} : b(t).serve(e, t);
|
|
97
|
+
}, v = /* @__PURE__ */ new WeakMap(), y = /* @__PURE__ */ new Map(), b = (i) => {
|
|
98
|
+
let a = i.channel ?? "banou-media-player", s = i.transport, l = `${a}|${i.origin ?? "*"}`, d = s ? v.get(s) ?? (v.set(s, /* @__PURE__ */ new Map()), v.get(s)) : y, f = d.get(l);
|
|
99
99
|
if (f) return f;
|
|
100
|
-
let
|
|
101
|
-
|
|
100
|
+
let p = /* @__PURE__ */ new Map(), m = /* @__PURE__ */ new Map(), _ = (e) => m.get(e) ?? m.set(e, /* @__PURE__ */ new Set()).get(e), b = (e, t) => {
|
|
101
|
+
m.get(e)?.delete(t);
|
|
102
102
|
try {
|
|
103
103
|
t.onmessage = null, t.close();
|
|
104
104
|
} catch {}
|
|
105
105
|
}, x = (e, t, n) => {
|
|
106
|
-
for (let r of n ? [n] : [...
|
|
106
|
+
for (let r of n ? [n] : [..._(e)]) try {
|
|
107
107
|
r.postMessage(t);
|
|
108
108
|
} catch {
|
|
109
109
|
b(e, r);
|
|
110
110
|
}
|
|
111
111
|
}, S = (e) => {
|
|
112
|
-
let t =
|
|
112
|
+
let t = p.get(e);
|
|
113
113
|
return t ? c(t) : void 0;
|
|
114
114
|
}, C = (e, t) => {
|
|
115
|
-
if (!
|
|
115
|
+
if (!m.get(e)?.size) return;
|
|
116
116
|
let n = S(e);
|
|
117
117
|
n && x(e, {
|
|
118
118
|
event: t,
|
|
@@ -122,9 +122,9 @@ var t = [
|
|
|
122
122
|
subscribe: (e, t) => {
|
|
123
123
|
if (typeof e != "string") throw TypeError("a player id must be a string");
|
|
124
124
|
if (typeof MessagePort < "u" && !(t instanceof MessagePort)) throw TypeError("subscribe takes a MessagePort");
|
|
125
|
-
if (!
|
|
126
|
-
let n =
|
|
127
|
-
if (n.size >=
|
|
125
|
+
if (!m.has(e) && m.size >= g) throw RangeError("too many players on this channel");
|
|
126
|
+
let n = _(e);
|
|
127
|
+
if (n.size >= h) throw RangeError("too many embedders for this player");
|
|
128
128
|
n.add(t), t.onmessage = () => b(e, t), t.start?.();
|
|
129
129
|
let r = S(e);
|
|
130
130
|
r && x(e, {
|
|
@@ -133,24 +133,28 @@ var t = [
|
|
|
133
133
|
}, t);
|
|
134
134
|
},
|
|
135
135
|
set: (e, t, r) => {
|
|
136
|
-
let i =
|
|
136
|
+
let i = p.get(e);
|
|
137
137
|
if (!(!i || !n.includes(t))) try {
|
|
138
138
|
i[t] = r;
|
|
139
139
|
} catch {}
|
|
140
140
|
},
|
|
141
141
|
call: async (e, t) => {
|
|
142
|
-
let n =
|
|
143
|
-
!n || !r.includes(t)
|
|
142
|
+
let n = p.get(e);
|
|
143
|
+
if (!(!n || !r.includes(t))) try {
|
|
144
|
+
t === "play" ? await n.play() : t === "pause" ? n.pause() : n.load?.();
|
|
145
|
+
} finally {
|
|
146
|
+
C(e, "timeupdate");
|
|
147
|
+
}
|
|
144
148
|
}
|
|
145
149
|
}, T = new AbortController();
|
|
146
150
|
T.signal.addEventListener("abort", () => {
|
|
147
151
|
d.delete(l);
|
|
148
|
-
for (let [e, t] of
|
|
149
|
-
|
|
152
|
+
for (let [e, t] of m) for (let n of [...t]) b(e, n);
|
|
153
|
+
m.clear(), p.clear();
|
|
150
154
|
}, { once: !0 }), e(w, {
|
|
151
155
|
transport: i.transport ?? {
|
|
152
156
|
emit: window.parent,
|
|
153
|
-
receive:
|
|
157
|
+
receive: A(() => window.parent, i.origin)
|
|
154
158
|
},
|
|
155
159
|
origin: i.origin,
|
|
156
160
|
key: a,
|
|
@@ -158,18 +162,18 @@ var t = [
|
|
|
158
162
|
});
|
|
159
163
|
let E = { serve: (e, n) => {
|
|
160
164
|
let r = n.id ?? "default", i = t.map((t) => [t, () => {
|
|
161
|
-
|
|
165
|
+
p.get(r) === e && C(r, t);
|
|
162
166
|
}]);
|
|
163
167
|
for (let [t, n] of i) e.addEventListener(t, n);
|
|
164
|
-
let a =
|
|
165
|
-
|
|
168
|
+
let a = p.has(r);
|
|
169
|
+
p.set(r, e), a && x(r, {
|
|
166
170
|
event: "emptied",
|
|
167
171
|
snapshot: { ...u }
|
|
168
172
|
});
|
|
169
173
|
for (let t of o(c(e))) C(r, t);
|
|
170
174
|
let s = () => {
|
|
171
175
|
for (let [t, n] of i) e.removeEventListener(t, n);
|
|
172
|
-
|
|
176
|
+
p.get(r) === e && (p.delete(r), x(r, {
|
|
173
177
|
event: "emptied",
|
|
174
178
|
snapshot: { ...u }
|
|
175
179
|
}));
|
|
@@ -179,10 +183,10 @@ var t = [
|
|
|
179
183
|
return n.signal && (n.signal.aborted ? l() : n.signal.addEventListener("abort", l, { once: !0 })), s;
|
|
180
184
|
} };
|
|
181
185
|
return d.set(l, E), E;
|
|
182
|
-
},
|
|
183
|
-
let r = n.channel ?? "banou-media-player", i = `${r}|${n.origin ?? "*"}`, a =
|
|
186
|
+
}, x = /* @__PURE__ */ new WeakMap(), S = (t, n) => {
|
|
187
|
+
let r = n.channel ?? "banou-media-player", i = `${r}|${n.origin ?? "*"}`, a = x.get(t) ?? (x.set(t, /* @__PURE__ */ new Map()), x.get(t)), o = a.get(i);
|
|
184
188
|
if (o) return o;
|
|
185
|
-
let s =
|
|
189
|
+
let s = O(t, n.origin), c = /* @__PURE__ */ new Set(), l = new AbortController(), u, d = (e) => {
|
|
186
190
|
try {
|
|
187
191
|
e.port?.postMessage("close");
|
|
188
192
|
} catch {}
|
|
@@ -195,7 +199,7 @@ var t = [
|
|
|
195
199
|
let { port1: t, port2: n } = new MessageChannel();
|
|
196
200
|
e.port = t, t.onmessage = ({ data: t }) => {
|
|
197
201
|
l.signal.aborted || e.update(t);
|
|
198
|
-
}, await
|
|
202
|
+
}, await w(() => u.subscribe(e.id, n)).catch((t) => {
|
|
199
203
|
throw d(e), t;
|
|
200
204
|
});
|
|
201
205
|
}, p = () => {
|
|
@@ -232,24 +236,24 @@ var t = [
|
|
|
232
236
|
}
|
|
233
237
|
};
|
|
234
238
|
return a.set(i, m), m;
|
|
235
|
-
},
|
|
236
|
-
|
|
237
|
-
let r = new AbortController(), i = t.signal ? AbortSignal.any([t.signal, r.signal]) : r.signal, a = t.id ?? "default", s = { ...u }, c = new EventTarget(), l = (e) => c.dispatchEvent(new Event(e)), f,
|
|
238
|
-
f = e,
|
|
239
|
+
}, C = (e, t = {}) => {
|
|
240
|
+
T(t.origin);
|
|
241
|
+
let r = new AbortController(), i = t.signal ? AbortSignal.any([t.signal, r.signal]) : r.signal, a = t.id ?? "default", s = { ...u }, c = new EventTarget(), l = (e) => c.dispatchEvent(new Event(e)), f, h, g = new Promise((e, t) => {
|
|
242
|
+
f = e, h = t;
|
|
239
243
|
});
|
|
240
|
-
|
|
241
|
-
let
|
|
242
|
-
for (let e of [...
|
|
243
|
-
|
|
244
|
-
},
|
|
244
|
+
g.catch(() => {});
|
|
245
|
+
let _ = () => new DOMException("the remote player was closed", "AbortError"), v = /* @__PURE__ */ new Set(), y = () => {
|
|
246
|
+
for (let e of [...v]) e(_());
|
|
247
|
+
v.clear();
|
|
248
|
+
}, b = /* @__PURE__ */ new Map(), x = !1, C = S(e, t).attach({
|
|
245
249
|
id: a,
|
|
246
250
|
reset: () => {
|
|
247
|
-
|
|
251
|
+
x = !1, y();
|
|
248
252
|
},
|
|
249
253
|
update: ({ event: e, snapshot: t }) => {
|
|
250
|
-
let n = !
|
|
251
|
-
|
|
252
|
-
for (let [e, t] of
|
|
254
|
+
let n = !x;
|
|
255
|
+
x = !0, Object.assign(s, t);
|
|
256
|
+
for (let [e, t] of b) s[e] = t;
|
|
253
257
|
if (!n) {
|
|
254
258
|
l(e);
|
|
255
259
|
return;
|
|
@@ -257,11 +261,11 @@ var t = [
|
|
|
257
261
|
for (let e of o(s)) l(e);
|
|
258
262
|
f();
|
|
259
263
|
}
|
|
260
|
-
}),
|
|
261
|
-
|
|
264
|
+
}), w = () => {
|
|
265
|
+
C(), y(), h(_());
|
|
262
266
|
};
|
|
263
|
-
i.aborted ? queueMicrotask(
|
|
264
|
-
let
|
|
267
|
+
i.aborted ? queueMicrotask(w) : i.addEventListener("abort", w, { once: !0 });
|
|
268
|
+
let E = (e, t) => Object.defineProperty(c, e, {
|
|
265
269
|
get: () => t ? t(s[e]) : s[e],
|
|
266
270
|
enumerable: !0
|
|
267
271
|
});
|
|
@@ -276,40 +280,70 @@ var t = [
|
|
|
276
280
|
"error",
|
|
277
281
|
"videoWidth",
|
|
278
282
|
"videoHeight"
|
|
279
|
-
])
|
|
280
|
-
|
|
281
|
-
let
|
|
283
|
+
]) E(e);
|
|
284
|
+
E("buffered", d), E("seekable", d);
|
|
285
|
+
let D = (e) => g.then(() => i.aborted ? void 0 : e()).catch(() => {}), O = S(e, t);
|
|
282
286
|
for (let e of n) Object.defineProperty(c, e, {
|
|
283
287
|
get: () => s[e],
|
|
284
288
|
set: (t) => {
|
|
285
|
-
s[e] = t,
|
|
286
|
-
await
|
|
289
|
+
s[e] = t, b.set(e, t), D(async () => {
|
|
290
|
+
await O.set(a, e, t), b.get(e) === t && b.delete(e);
|
|
287
291
|
});
|
|
288
292
|
},
|
|
289
293
|
enumerable: !0
|
|
290
294
|
});
|
|
291
|
-
let
|
|
292
|
-
if (i.aborted) throw
|
|
293
|
-
let t =
|
|
295
|
+
let k = (e) => g.then(() => {
|
|
296
|
+
if (i.aborted) throw _();
|
|
297
|
+
let t = O.call(a, e);
|
|
294
298
|
return new Promise((e, n) => {
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
v.add(n), t.then(() => {
|
|
300
|
+
v.delete(n), e();
|
|
297
301
|
}, (e) => {
|
|
298
|
-
|
|
302
|
+
v.delete(n), n(i.aborted ? _() : e);
|
|
299
303
|
});
|
|
300
304
|
});
|
|
301
305
|
});
|
|
302
|
-
|
|
306
|
+
c.play = () => (s.paused = !1, k("play").catch((e) => {
|
|
303
307
|
throw s.paused = !0, e;
|
|
304
|
-
}))
|
|
305
|
-
|
|
308
|
+
}));
|
|
309
|
+
let A = () => new Promise((e) => {
|
|
310
|
+
let t = (t) => {
|
|
311
|
+
clearTimeout(r), c.removeEventListener("playing", n), c.removeEventListener("play", n), e(t);
|
|
312
|
+
}, n = () => t(!0);
|
|
313
|
+
c.addEventListener("playing", n), c.addEventListener("play", n);
|
|
314
|
+
let r = setTimeout(() => t(!s.paused), p);
|
|
315
|
+
}), j = async () => {
|
|
316
|
+
let e = !s.paused, t;
|
|
317
|
+
try {
|
|
318
|
+
await c.play();
|
|
319
|
+
} catch (e) {
|
|
320
|
+
t = e;
|
|
321
|
+
}
|
|
322
|
+
return e ? {
|
|
323
|
+
started: !0,
|
|
324
|
+
refusal: t
|
|
325
|
+
} : {
|
|
326
|
+
started: await A(),
|
|
327
|
+
refusal: t
|
|
328
|
+
};
|
|
329
|
+
};
|
|
330
|
+
return c.autoplay = async () => {
|
|
331
|
+
let e = await j();
|
|
332
|
+
if (e.started) return { muted: c.muted };
|
|
333
|
+
if (c.muted || i.aborted) throw e.refusal ?? m();
|
|
334
|
+
c.muted = !0;
|
|
335
|
+
let t = await j();
|
|
336
|
+
if (t.started) return { muted: !0 };
|
|
337
|
+
throw c.muted = !1, e.refusal ?? t.refusal ?? m();
|
|
338
|
+
}, c.pause = () => {
|
|
339
|
+
s.paused = !0, k("pause").catch(() => {});
|
|
306
340
|
}, c.load = () => {
|
|
307
|
-
|
|
341
|
+
k("load").catch(() => {});
|
|
308
342
|
}, Object.defineProperty(c, "ready", {
|
|
309
|
-
value:
|
|
343
|
+
value: g,
|
|
310
344
|
enumerable: !1
|
|
311
345
|
}), c.destroy = () => r.abort(), c;
|
|
312
|
-
},
|
|
346
|
+
}, w = async (e) => {
|
|
313
347
|
let t;
|
|
314
348
|
try {
|
|
315
349
|
return await Promise.race([e(), new Promise((e, n) => {
|
|
@@ -318,7 +352,7 @@ var t = [
|
|
|
318
352
|
} finally {
|
|
319
353
|
t && clearTimeout(t);
|
|
320
354
|
}
|
|
321
|
-
},
|
|
355
|
+
}, T = (e) => {
|
|
322
356
|
if (e === void 0 || e === "*") return;
|
|
323
357
|
if (e === "null") throw TypeError("an opaque origin (\"null\") cannot be spoken to; leave `origin` unset and the sender check stands alone");
|
|
324
358
|
let t;
|
|
@@ -328,47 +362,47 @@ var t = [
|
|
|
328
362
|
throw TypeError(`\`origin\` must be an origin like https://example.com, not ${e}`);
|
|
329
363
|
}
|
|
330
364
|
if (t.origin !== e) throw TypeError(`\`origin\` must be an origin like ${t.origin}, not ${e}`);
|
|
331
|
-
},
|
|
365
|
+
}, E = (e) => {
|
|
332
366
|
try {
|
|
333
367
|
return !!e && typeof e == "object" && e.window === e;
|
|
334
368
|
} catch {
|
|
335
369
|
return !1;
|
|
336
370
|
}
|
|
337
|
-
},
|
|
338
|
-
if (
|
|
371
|
+
}, D = (e) => {
|
|
372
|
+
if (E(e)) return !1;
|
|
339
373
|
try {
|
|
340
374
|
return !!e && typeof e == "object" && "contentWindow" in e;
|
|
341
375
|
} catch {
|
|
342
376
|
return !1;
|
|
343
377
|
}
|
|
344
|
-
},
|
|
345
|
-
if (
|
|
346
|
-
let n =
|
|
378
|
+
}, O = (e, t) => {
|
|
379
|
+
if (E(e)) {
|
|
380
|
+
let n = k(t);
|
|
347
381
|
return {
|
|
348
382
|
emit: (t, r) => {
|
|
349
383
|
e.postMessage(t, n.target(), r ?? []);
|
|
350
384
|
},
|
|
351
|
-
receive:
|
|
385
|
+
receive: A(() => e, t, n)
|
|
352
386
|
};
|
|
353
387
|
}
|
|
354
|
-
if (
|
|
388
|
+
if (D(e)) {
|
|
355
389
|
if (!e.contentWindow) throw TypeError("mediaPlayer: the iframe has no window yet; append it to a document first");
|
|
356
|
-
let n = () => e.contentWindow, r =
|
|
390
|
+
let n = () => e.contentWindow, r = k(t);
|
|
357
391
|
return {
|
|
358
392
|
emit: (e, t) => {
|
|
359
393
|
n()?.postMessage(e, r.target(), t ?? []);
|
|
360
394
|
},
|
|
361
|
-
receive:
|
|
395
|
+
receive: A(n, t, r)
|
|
362
396
|
};
|
|
363
397
|
}
|
|
364
398
|
return e;
|
|
365
|
-
},
|
|
399
|
+
}, k = (e) => {
|
|
366
400
|
let t = e && e !== "*" ? e : void 0;
|
|
367
401
|
return {
|
|
368
402
|
target: () => t && t !== "null" ? t : "*",
|
|
369
403
|
admits: (e) => t ? e === t : (e && (t = e), !0)
|
|
370
404
|
};
|
|
371
|
-
},
|
|
405
|
+
}, A = (e, t, n = k(t)) => (t) => {
|
|
372
406
|
let r = (r) => {
|
|
373
407
|
r.source === e() && n.admits(r.origin) && t(r.data, {
|
|
374
408
|
source: r.source,
|
|
@@ -379,4 +413,4 @@ var t = [
|
|
|
379
413
|
return window.addEventListener("message", r), () => window.removeEventListener("message", r);
|
|
380
414
|
};
|
|
381
415
|
//#endregion
|
|
382
|
-
export { a as DEFAULT_PLAYER_ID, i as PLAYER_CHANNEL, t as PLAYER_EVENTS,
|
|
416
|
+
export { a as DEFAULT_PLAYER_ID, i as PLAYER_CHANNEL, t as PLAYER_EVENTS, _ as exposePlayer, C as mediaPlayer };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@banou/media-player",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "A video player for containers and codecs the browser cannot play natively, remuxed on the fly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -34,9 +34,8 @@
|
|
|
34
34
|
"build-lib": "shx rm -rf dist/ && vp build -c vite.lib.config.ts && npm run types",
|
|
35
35
|
"prepublishOnly": "npm run build-lib",
|
|
36
36
|
"types": "tsc -p tsconfig.lib.json",
|
|
37
|
-
"copy-assets": "shx mkdir -p public && npm run copy-libav
|
|
37
|
+
"copy-assets": "shx mkdir -p public && npm run copy-libav",
|
|
38
38
|
"copy-libav": "shx cp node_modules/libav-wasm/build/worker.js public/libav-worker.js && shx cp node_modules/libav-wasm/build/libav.wasm public/libav.wasm && shx cp node_modules/libav-wasm/build/libav-jspi.wasm public/libav-jspi.wasm",
|
|
39
|
-
"copy-libass": "shx cp node_modules/jassub/dist/jassub-worker.js public/jassub-worker.js && shx cp node_modules/jassub/dist/jassub-worker.wasm public/jassub-worker.wasm && shx cp node_modules/jassub/dist/jassub-worker-modern.wasm public/jassub-worker-modern.wasm && shx cp node_modules/jassub/dist/default.woff2 public/default.woff2",
|
|
40
39
|
"test": "npm run fixtures && vp test run",
|
|
41
40
|
"fixtures": "node scripts/fixture.mjs"
|
|
42
41
|
},
|
|
@@ -45,7 +44,7 @@
|
|
|
45
44
|
"@videojs/core": "10.0.0-beta.26",
|
|
46
45
|
"@videojs/react": "10.0.0-beta.26",
|
|
47
46
|
"ass-compiler": "^0.1.16",
|
|
48
|
-
"jassub": "^
|
|
47
|
+
"jassub": "^2.5.16",
|
|
49
48
|
"libav-wasm": "^0.8.4",
|
|
50
49
|
"osra": "^0.6.12",
|
|
51
50
|
"react-feather": "^2.0.10",
|
|
@@ -27,8 +27,15 @@ export type PictureInPictureMode = 'window' | 'burn-in'
|
|
|
27
27
|
|
|
28
28
|
export type PictureInPictureOptions = {
|
|
29
29
|
video: HTMLVideoElement
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* The subtitle LAYER, not the canvas inside it.
|
|
32
|
+
*
|
|
33
|
+
* From jassub 2 the canvas is created and removed by the subtitle renderer, one per pipeline
|
|
34
|
+
* build, so an element captured here would stop being the one being painted the moment the audio
|
|
35
|
+
* track changed. The layer is what React owns for the life of the player, so hiding and restoring
|
|
36
|
+
* it stays correct across a rebuild, and the canvas is looked up per frame.
|
|
37
|
+
*/
|
|
38
|
+
subtitles: HTMLElement
|
|
32
39
|
maxWidth?: number
|
|
33
40
|
/** Where the mirror is mounted. It must be in the document. Defaults to the video's parent. */
|
|
34
41
|
container?: HTMLElement
|
|
@@ -87,7 +94,7 @@ export const pictureInPictureMode = (): PictureInPictureMode | null => {
|
|
|
87
94
|
|
|
88
95
|
/** Takes over the document's Media Session play/pause handlers while a session is open. */
|
|
89
96
|
export const createPictureInPicture = (options: PictureInPictureOptions): PictureInPictureController => {
|
|
90
|
-
const { video,
|
|
97
|
+
const { video, subtitles } = options
|
|
91
98
|
const mode = options.mode ?? pictureInPictureMode() ?? 'window'
|
|
92
99
|
const maxWidth = options.maxWidth ?? (mode === 'burn-in' ? BURN_IN_MAX_WIDTH : DEFAULT_MAX_WIDTH)
|
|
93
100
|
|
|
@@ -112,9 +119,12 @@ export const createPictureInPicture = (options: PictureInPictureOptions): Pictur
|
|
|
112
119
|
|
|
113
120
|
const draw = (composite: HTMLCanvasElement, context: CanvasRenderingContext2D) => {
|
|
114
121
|
context.drawImage(video, 0, 0, composite.width, composite.height)
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
// Resolved per frame: the renderer replaces this element on every pipeline rebuild. A canvas
|
|
123
|
+
// jassub has transferred reports the size of the last frame its worker committed, so the zero
|
|
124
|
+
// check still keeps drawImage off a source with no dimensions to read.
|
|
125
|
+
const surface = subtitles.querySelector('canvas')
|
|
126
|
+
if (surface && surface.width > 0 && surface.height > 0) {
|
|
127
|
+
context.drawImage(surface, 0, 0, composite.width, composite.height)
|
|
118
128
|
}
|
|
119
129
|
}
|
|
120
130
|
|
|
@@ -254,18 +264,23 @@ export const createPictureInPicture = (options: PictureInPictureOptions): Pictur
|
|
|
254
264
|
* The real element is dimmed, never `display: none`: jassub sizes the subtitle canvas from its
|
|
255
265
|
* `offsetWidth`/`offsetHeight`, and a collapsed box silently yields a composite with no subtitles
|
|
256
266
|
* in it, which is exactly the thing being asked for.
|
|
267
|
+
*
|
|
268
|
+
* The LAYER is what gets hidden, rather than the canvas inside it. The canvas is replaced whenever
|
|
269
|
+
* the pipeline is rebuilt, so hiding that would leave the replacement visible over the composite
|
|
270
|
+
* (doubled subtitles) and would restore `display` onto an element that is no longer in the tree
|
|
271
|
+
* (subtitles gone for the rest of the session). The layer outlives both.
|
|
257
272
|
*/
|
|
258
273
|
const present = (mirror: HTMLVideoElement) => {
|
|
259
274
|
const videoOpacity = video.style.opacity
|
|
260
|
-
const
|
|
275
|
+
const subtitlesDisplay = subtitles.style.display
|
|
261
276
|
mirror.style.cssText =
|
|
262
277
|
'position:absolute;inset:0;width:100%;height:100%;object-fit:contain;background:#000;z-index:1'
|
|
263
278
|
video.style.opacity = '0'
|
|
264
|
-
|
|
279
|
+
subtitles.style.display = 'none'
|
|
265
280
|
restore = () => {
|
|
266
281
|
restore = undefined
|
|
267
282
|
video.style.opacity = videoOpacity
|
|
268
|
-
|
|
283
|
+
subtitles.style.display = subtitlesDisplay
|
|
269
284
|
}
|
|
270
285
|
}
|
|
271
286
|
|