@banou/media-player 0.8.3 → 0.8.4
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/dist/index.js +198 -146
- package/dist/react/components/chrome.d.ts +3 -1
- package/dist/react/player.d.ts +3 -2
- package/dist/react/source-feature.d.ts +33 -4
- package/dist/react/video-player.d.ts +30 -1
- package/package.json +1 -1
- package/src/lib/react/components/chrome.tsx +4 -2
- package/src/lib/react/components/settings.tsx +117 -39
- package/src/lib/react/source-feature.ts +20 -2
- package/src/lib/react/video-player.tsx +38 -3
package/dist/index.js
CHANGED
|
@@ -4,10 +4,10 @@ import { css as p, keyframes as m } from "@emotion/react";
|
|
|
4
4
|
import { createPlayer as h, useContainerAttach as g, useMediaAttach as _ } from "@videojs/react";
|
|
5
5
|
import { definePlayerFeature as v, videoFeatures as y } from "@videojs/core/dom";
|
|
6
6
|
import { Fragment as b, jsx as x, jsxs as S } from "@emotion/react/jsx-runtime";
|
|
7
|
-
import { ChevronLeft as C, ChevronRight as w, Maximize as T, Minimize as E, Pause as D, Play as O, RotateCcw as k, Settings as A, Volume1 as j, Volume2 as
|
|
8
|
-
import { Tooltip as
|
|
7
|
+
import { ChevronLeft as C, ChevronRight as w, Maximize as T, Minimize as E, Pause as D, Play as O, RotateCcw as k, Settings as A, Volume1 as j, Volume2 as M, VolumeX as ee } from "react-feather";
|
|
8
|
+
import { Tooltip as te } from "react-tooltip";
|
|
9
9
|
//#region src/lib/react/source-feature.ts
|
|
10
|
-
var
|
|
10
|
+
var ne = {
|
|
11
11
|
indexes: [],
|
|
12
12
|
thumbnails: [],
|
|
13
13
|
subtitleTracks: [],
|
|
@@ -22,19 +22,19 @@ var re = {
|
|
|
22
22
|
playbackError: null,
|
|
23
23
|
ready: !1,
|
|
24
24
|
setSourceState: () => {}
|
|
25
|
-
},
|
|
25
|
+
}, N = v({
|
|
26
26
|
name: "source",
|
|
27
|
-
state: () => ({ ...
|
|
27
|
+
state: () => ({ ...ne }),
|
|
28
28
|
attach({ set: e }) {
|
|
29
29
|
e({
|
|
30
30
|
setSourceState: (t) => e(t),
|
|
31
31
|
setHideUI: (t) => e({ hideUI: t })
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
}),
|
|
35
|
-
features: [...y,
|
|
34
|
+
}), P = h({
|
|
35
|
+
features: [...y, N],
|
|
36
36
|
displayName: "MediaPlayer"
|
|
37
|
-
}),
|
|
37
|
+
}), F = P.usePlayer, re = (e) => {
|
|
38
38
|
if (!(!e || e === "und")) try {
|
|
39
39
|
return new Intl.DisplayNames([navigator.language || "en"], {
|
|
40
40
|
type: "language",
|
|
@@ -43,23 +43,23 @@ var re = {
|
|
|
43
43
|
} catch {
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
},
|
|
47
|
-
let t = e.map((e) =>
|
|
46
|
+
}, I = (e) => e?.replace(/_/g, " ").trim() || void 0, L = (e) => {
|
|
47
|
+
let t = e.map((e) => re(e.language) ?? I(e.title) ?? `Track ${e.streamIndex}`), n = t.reduce((e, t) => ({
|
|
48
48
|
...e,
|
|
49
49
|
[t]: (e[t] ?? 0) + 1
|
|
50
50
|
}), {});
|
|
51
51
|
return e.map((e, r) => {
|
|
52
|
-
let i = t[r], a =
|
|
52
|
+
let i = t[r], a = I(e.title);
|
|
53
53
|
return {
|
|
54
54
|
track: e,
|
|
55
55
|
label: (n[i] ?? 0) > 1 && a && a !== i ? `${i} (${a})` : i
|
|
56
56
|
};
|
|
57
57
|
});
|
|
58
|
-
},
|
|
58
|
+
}, R = (e) => L(e).map(({ track: e, label: t }) => ({
|
|
59
59
|
id: e.streamIndex,
|
|
60
60
|
label: t
|
|
61
|
-
})),
|
|
62
|
-
let i =
|
|
61
|
+
})), ie = (t, n, r) => {
|
|
62
|
+
let i = F(), { read: a, size: o, publicPath: s = "", libavWorkerUrl: u = "", jassubWorkerUrl: p = "", jassubWasmUrl: m = "", jassubLegacyWasmUrl: h, defaultFontUrl: g, bufferSize: _, autoplay: v = !1 } = r ?? {}, [y, b] = f(void 0), x = d(null), S = d(0), C = d(!1), w = d(a);
|
|
63
63
|
w.current = a;
|
|
64
64
|
let T = d(r?.onSeek);
|
|
65
65
|
T.current = r?.onSeek;
|
|
@@ -70,7 +70,7 @@ var re = {
|
|
|
70
70
|
C.current = !0, i.setSourceState({ selectedSubtitleTrack: t }), x.current?.selectSubtitleStream(t);
|
|
71
71
|
}, [i]), O = c((e) => {
|
|
72
72
|
typeof e == "number" && (i.setSourceState({ selectedAudioTrack: e }), b(e));
|
|
73
|
-
}, [i]), k =
|
|
73
|
+
}, [i]), k = F((e) => e.setSourceState);
|
|
74
74
|
l(() => {
|
|
75
75
|
k({
|
|
76
76
|
selectSubtitleTrack: D,
|
|
@@ -115,13 +115,13 @@ var re = {
|
|
|
115
115
|
onSeek: (e) => T.current?.(e),
|
|
116
116
|
onSubtitleStreams: (e) => {
|
|
117
117
|
r || i.setSourceState({
|
|
118
|
-
subtitleTracks:
|
|
118
|
+
subtitleTracks: R(e),
|
|
119
119
|
...C.current ? {} : { selectedSubtitleTrack: e[0]?.streamIndex }
|
|
120
120
|
});
|
|
121
121
|
},
|
|
122
122
|
onAudioStreams: (e, t) => {
|
|
123
123
|
r || i.setSourceState({
|
|
124
|
-
audioTracks:
|
|
124
|
+
audioTracks: R(e),
|
|
125
125
|
selectedAudioTrack: t
|
|
126
126
|
});
|
|
127
127
|
}
|
|
@@ -154,13 +154,13 @@ var re = {
|
|
|
154
154
|
y,
|
|
155
155
|
v
|
|
156
156
|
]);
|
|
157
|
-
},
|
|
157
|
+
}, ae = 5e3, oe = 6e4, z = ({ publicPath: e, workerUrl: n, length: r, read: i, downloadedRanges: a }) => {
|
|
158
158
|
let [o, s] = f([]), c = d(null), p = d(i);
|
|
159
159
|
p.current = i;
|
|
160
160
|
let m = u(() => a?.map(({ startByteOffset: e, endByteOffset: t }) => [e, t]), [a?.map(({ startByteOffset: e, endByteOffset: t }) => `${e}-${t}`).join(",")]), h = d(m);
|
|
161
161
|
return h.current = m, l(() => {
|
|
162
162
|
if (!r || !i || !e || !n) return;
|
|
163
|
-
let a = !1, o = null, l, u =
|
|
163
|
+
let a = !1, o = null, l, u = ae, d = () => {
|
|
164
164
|
t({
|
|
165
165
|
publicPath: e,
|
|
166
166
|
workerUrl: n,
|
|
@@ -176,7 +176,7 @@ var re = {
|
|
|
176
176
|
}
|
|
177
177
|
o = e, c.current = e, e.update(h.current);
|
|
178
178
|
}, () => {
|
|
179
|
-
a || (l = setTimeout(d, u), u = Math.min(u * 2,
|
|
179
|
+
a || (l = setTimeout(d, u), u = Math.min(u * 2, oe));
|
|
180
180
|
});
|
|
181
181
|
};
|
|
182
182
|
return d(), () => {
|
|
@@ -189,8 +189,8 @@ var re = {
|
|
|
189
189
|
]), l(() => {
|
|
190
190
|
c.current?.update(m);
|
|
191
191
|
}, [m]), o;
|
|
192
|
-
},
|
|
193
|
-
let n =
|
|
192
|
+
}, B = (e, t) => {
|
|
193
|
+
let n = F(), r = d(null);
|
|
194
194
|
l(() => {
|
|
195
195
|
if (!e || !t) return;
|
|
196
196
|
let a = i({
|
|
@@ -212,7 +212,7 @@ var re = {
|
|
|
212
212
|
});
|
|
213
213
|
}, []);
|
|
214
214
|
return e && t ? a : null;
|
|
215
|
-
},
|
|
215
|
+
}, V = {
|
|
216
216
|
headings: {
|
|
217
217
|
large: "\n font-weight: 600;\n font-size: calc(2.8 * var(--mp-unit));\n line-height: calc(3.4 * var(--mp-unit));\n @media (min-width: 960px) {\n font-size: calc(3.4 * var(--mp-unit));\n line-height: calc(4.1 * var(--mp-unit));\n }\n ",
|
|
218
218
|
medium: "\n font-weight: 600;\n font-size: calc(2.4 * var(--mp-unit));\n line-height: calc(2.9 * var(--mp-unit));\n @media (min-width: 960px) {\n font-size: calc(2.8 * var(--mp-unit));\n line-height: calc(3.4 * var(--mp-unit));\n }\n ",
|
|
@@ -239,14 +239,14 @@ var re = {
|
|
|
239
239
|
medium: "\n font-weight: 500;\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 ",
|
|
240
240
|
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 "
|
|
241
241
|
}
|
|
242
|
-
},
|
|
242
|
+
}, se = p`
|
|
243
243
|
top: unset !important;
|
|
244
244
|
left: unset !important;
|
|
245
245
|
width: 100%;
|
|
246
246
|
height: 100%;
|
|
247
247
|
margin: auto;
|
|
248
248
|
pointer-events: none;
|
|
249
|
-
`,
|
|
249
|
+
`, ce = p`
|
|
250
250
|
position: absolute;
|
|
251
251
|
top: 0;
|
|
252
252
|
left: 0;
|
|
@@ -254,7 +254,7 @@ var re = {
|
|
|
254
254
|
padding: calc(1.2 * var(--mp-unit)) calc(1.6 * var(--mp-unit));
|
|
255
255
|
/* clears a notch once the player is fullscreen; resolves to zero everywhere else */
|
|
256
256
|
padding-top: calc(calc(1.2 * var(--mp-unit)) + env(safe-area-inset-top, 0px));
|
|
257
|
-
${
|
|
257
|
+
${V.headings.small}
|
|
258
258
|
color: white;
|
|
259
259
|
text-shadow: 0 0 4px rgba(0, 0, 0, 1);
|
|
260
260
|
z-index: 2;
|
|
@@ -275,7 +275,7 @@ var re = {
|
|
|
275
275
|
|
|
276
276
|
background: linear-gradient(180deg, rgba(0,0,0,0.4) 0%, rgba(0,0,0,0.3) 30%, rgba(0,0,0,0.2) 60%, rgba(0,0,0,0.1) 80%, transparent 100%);
|
|
277
277
|
transition: opacity 0.1s cubic-bezier(.4,0,1,1);
|
|
278
|
-
`,
|
|
278
|
+
`, le = p`
|
|
279
279
|
position: absolute;
|
|
280
280
|
inset: 0;
|
|
281
281
|
z-index: 2;
|
|
@@ -295,43 +295,43 @@ var re = {
|
|
|
295
295
|
to { transform: rotate(360deg); }
|
|
296
296
|
`} 0.8s linear infinite;
|
|
297
297
|
}
|
|
298
|
-
`,
|
|
298
|
+
`, ue = p`
|
|
299
299
|
position: absolute;
|
|
300
300
|
inset: auto 0 20%;
|
|
301
301
|
z-index: 3;
|
|
302
302
|
padding: 0 calc(2 * var(--mp-unit));
|
|
303
303
|
text-align: center;
|
|
304
304
|
color: #fff;
|
|
305
|
-
${
|
|
305
|
+
${V.bLarge.regular}
|
|
306
306
|
text-shadow: 0 0 4px rgba(0, 0, 0, 1);
|
|
307
307
|
pointer-events: none;
|
|
308
|
-
`,
|
|
309
|
-
let t =
|
|
308
|
+
`, de = (e) => e instanceof Error ? e.message : typeof e == "string" ? e : "Playback failed", fe = ({ onCanvasRef: e }) => {
|
|
309
|
+
let t = F((e) => e.title), n = F((e) => e.hideUI), r = F((e) => e.playbackError), i = F((e) => e.ready), a = F((e) => e.size), o = F((e) => e.waiting);
|
|
310
310
|
return /* @__PURE__ */ S(b, { children: [
|
|
311
311
|
t ? /* @__PURE__ */ x("div", {
|
|
312
312
|
className: "title",
|
|
313
|
-
css:
|
|
313
|
+
css: ce,
|
|
314
314
|
style: { ...n ? {
|
|
315
315
|
opacity: "0",
|
|
316
316
|
pointerEvents: "none"
|
|
317
317
|
} : {} },
|
|
318
318
|
children: t
|
|
319
319
|
}) : void 0,
|
|
320
|
-
(a ? !i : o) && !r ? /* @__PURE__ */ x("div", { css:
|
|
320
|
+
(a ? !i : o) && !r ? /* @__PURE__ */ x("div", { css: le }) : void 0,
|
|
321
321
|
r ? /* @__PURE__ */ x("div", {
|
|
322
|
-
css:
|
|
323
|
-
children:
|
|
322
|
+
css: ue,
|
|
323
|
+
children: de(r)
|
|
324
324
|
}) : void 0,
|
|
325
325
|
/* @__PURE__ */ x("canvas", {
|
|
326
326
|
ref: e,
|
|
327
|
-
css:
|
|
327
|
+
css: se
|
|
328
328
|
})
|
|
329
329
|
] });
|
|
330
|
-
},
|
|
330
|
+
}, H = 1e-4, U = 1, W = (e) => Math.max(H, Math.min(U, e)) ** 2, G = (e) => Math.max(H, Math.min(U, e)) ** (U / 2), K = (e) => {
|
|
331
331
|
if (e === void 0) return "0:00";
|
|
332
332
|
let t = Math.floor(e / 3600), n = Math.floor(e % 3600 / 60), r = Math.floor(e % 60);
|
|
333
333
|
return t > 0 ? `${t}:${n < 10 ? "0" : ""}${n}:${r < 10 ? "0" : ""}${r}` : `${n}:${r < 10 ? "0" : ""}${r}`;
|
|
334
|
-
},
|
|
334
|
+
}, pe = (e, t) => `${K(e)} / ${K(t)}`, me = (e) => p`
|
|
335
335
|
display: flex;
|
|
336
336
|
justify-content: flex-end;
|
|
337
337
|
|
|
@@ -341,7 +341,7 @@ var re = {
|
|
|
341
341
|
z-index: 3;
|
|
342
342
|
|
|
343
343
|
* {
|
|
344
|
-
${
|
|
344
|
+
${V.bMedium.regular}
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
${e === "sm" && p`
|
|
@@ -353,7 +353,7 @@ var re = {
|
|
|
353
353
|
${e === "lg" && p`
|
|
354
354
|
padding: calc(1.2 * var(--mp-unit))!important;
|
|
355
355
|
`}
|
|
356
|
-
`,
|
|
356
|
+
`, q = ({ id: e, toolTipText: t, text: n, delayShow: r = 0, closeDelay: i = 0, offset: a = 20, tooltipPlace: o = "top", disabled: s = !1, size: c = "md" }) => /* @__PURE__ */ S(b, { children: [/* @__PURE__ */ x("div", {
|
|
357
357
|
"data-tooltip-id": e,
|
|
358
358
|
"data-open": !0,
|
|
359
359
|
"data-tooltip-offset": a,
|
|
@@ -361,12 +361,12 @@ var re = {
|
|
|
361
361
|
"data-tooltip-delay-hide": i,
|
|
362
362
|
"data-tooltip-place": o,
|
|
363
363
|
children: n
|
|
364
|
-
}), !s && /* @__PURE__ */ x(
|
|
365
|
-
css:
|
|
364
|
+
}), !s && /* @__PURE__ */ x(te, {
|
|
365
|
+
css: me(c),
|
|
366
366
|
id: e,
|
|
367
367
|
noArrow: !0,
|
|
368
368
|
children: t
|
|
369
|
-
})] }),
|
|
369
|
+
})] }), J = ({ ref: e, onChange: t, orientation: n = "horizontal", disabled: r = !1 }) => {
|
|
370
370
|
let [i, a] = f(!1), o = d(t);
|
|
371
371
|
o.current = t;
|
|
372
372
|
let s = d(null), u = c((t, r) => {
|
|
@@ -393,7 +393,7 @@ var re = {
|
|
|
393
393
|
style: { touchAction: "none" }
|
|
394
394
|
}
|
|
395
395
|
};
|
|
396
|
-
},
|
|
396
|
+
}, he = p`
|
|
397
397
|
position: relative;
|
|
398
398
|
height: calc(.4 * var(--mp-unit));
|
|
399
399
|
|
|
@@ -438,7 +438,7 @@ var re = {
|
|
|
438
438
|
justify-content: center;
|
|
439
439
|
|
|
440
440
|
text-shadow: 0 0 4px rgba(0, 0, 0, 1);
|
|
441
|
-
${
|
|
441
|
+
${V.bMedium.bold}
|
|
442
442
|
|
|
443
443
|
position: absolute;
|
|
444
444
|
top: calc(-2.5 * var(--mp-unit));
|
|
@@ -550,8 +550,8 @@ var re = {
|
|
|
550
550
|
--thumbnail-width: calc(18 * var(--mp-unit));
|
|
551
551
|
--thumbnail-half: calc(9 * var(--mp-unit));
|
|
552
552
|
}
|
|
553
|
-
`,
|
|
554
|
-
let e =
|
|
553
|
+
`, ge = () => {
|
|
554
|
+
let e = F(), t = F((e) => e.currentTime), n = F((e) => e.duration), r = F((e) => e.size), i = F((e) => e.downloadedRanges), a = F((e) => e.indexes), o = F((e) => e.thumbnails), s = F((e) => e.thumbnailAt), c = d(null), [p, m] = f(void 0), [h, g] = f(void 0), _ = d(void 0), { dragging: v, handlers: y } = J({
|
|
555
555
|
ref: c,
|
|
556
556
|
onChange: (e) => {
|
|
557
557
|
m(e), _.current !== "mouse" && g(e * n);
|
|
@@ -609,7 +609,7 @@ var re = {
|
|
|
609
609
|
h
|
|
610
610
|
]);
|
|
611
611
|
return /* @__PURE__ */ S("div", {
|
|
612
|
-
css:
|
|
612
|
+
css: he,
|
|
613
613
|
ref: c,
|
|
614
614
|
className: v ? "progress-bar dragging" : "progress-bar",
|
|
615
615
|
onMouseMove: T,
|
|
@@ -704,7 +704,7 @@ width: 100%;
|
|
|
704
704
|
}
|
|
705
705
|
}
|
|
706
706
|
`, ye = () => {
|
|
707
|
-
let e =
|
|
707
|
+
let e = F(), t = F((e) => e.playbackRate), n = d(null), r = d(null), i = .25, a = 3 - i, o = (e, t = .05) => Math.round(e / t) * t, s = o(t || 1), c = (e) => (e - i) / a * 100, u = (e) => o(i + e * a), { handlers: f } = J({
|
|
708
708
|
ref: r,
|
|
709
709
|
onChange: (t) => e.setPlaybackRate(u(t))
|
|
710
710
|
}), p = (t) => {
|
|
@@ -776,7 +776,7 @@ position: relative;
|
|
|
776
776
|
|
|
777
777
|
border-radius: 8px;
|
|
778
778
|
background-color: rgba(28,28,28,0.95);
|
|
779
|
-
${
|
|
779
|
+
${V.bMedium.regular}
|
|
780
780
|
|
|
781
781
|
z-index: 4;
|
|
782
782
|
|
|
@@ -819,7 +819,7 @@ position: relative;
|
|
|
819
819
|
|
|
820
820
|
.secondary {
|
|
821
821
|
color: #eee;
|
|
822
|
-
${
|
|
822
|
+
${V.bSmall.regular}
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
825
|
}
|
|
@@ -886,72 +886,116 @@ position: relative;
|
|
|
886
886
|
.description {
|
|
887
887
|
word-break: break-word;
|
|
888
888
|
}
|
|
889
|
+
|
|
890
|
+
/* Dimmed and inert, but still listed. A source that names a track it cannot serve right now is
|
|
891
|
+
saying the track exists, and dropping the row would read as the source having nothing. */
|
|
892
|
+
.unavailable {
|
|
893
|
+
opacity: 0.5;
|
|
894
|
+
cursor: default;
|
|
895
|
+
:hover {
|
|
896
|
+
background-color: transparent;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
.failed {
|
|
901
|
+
border-bottom: 1px solid #4D4D4E;
|
|
902
|
+
color: #f66;
|
|
903
|
+
${V.bSmall.regular}
|
|
904
|
+
}
|
|
889
905
|
}
|
|
890
|
-
`, Y = ({ title: e, tracks: t, selected: n, onSelect: r, onBack: i,
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
/* @__PURE__ */ S("div", {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
}),
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
906
|
+
`, Y = ({ title: e, tracks: t, selected: n, onSelect: r, onBack: i, offLabel: a, pending: o, failed: s }) => {
|
|
907
|
+
let c = o !== void 0, l = (e, i, a) => {
|
|
908
|
+
let s = c && o === e, l = c || t.find((t) => t.id === e)?.disabled;
|
|
909
|
+
return /* @__PURE__ */ S("div", {
|
|
910
|
+
onClick: () => {
|
|
911
|
+
l || r(e ?? void 0);
|
|
912
|
+
},
|
|
913
|
+
className: [a, l ? "unavailable" : null].filter(Boolean).join(" ") || void 0,
|
|
914
|
+
children: [/* @__PURE__ */ x("span", { children: i }), /* @__PURE__ */ x("span", { children: s ? "…" : (n ?? null) === e ? "✓" : "" })]
|
|
915
|
+
}, e ?? "__off__");
|
|
916
|
+
};
|
|
917
|
+
return /* @__PURE__ */ S("div", {
|
|
918
|
+
className: "popover track-list",
|
|
919
|
+
children: [
|
|
920
|
+
/* @__PURE__ */ S("div", {
|
|
921
|
+
className: "back",
|
|
922
|
+
onClick: i,
|
|
923
|
+
children: [/* @__PURE__ */ x(C, {}), /* @__PURE__ */ x("span", { children: e })]
|
|
924
|
+
}),
|
|
925
|
+
s ? /* @__PURE__ */ x("div", {
|
|
926
|
+
className: "no-hover failed",
|
|
927
|
+
children: "Could not switch. Try again."
|
|
928
|
+
}) : null,
|
|
929
|
+
a ? l(null, a) : null,
|
|
930
|
+
t.map(({ id: e, label: t }) => l(e, t, "description"))
|
|
931
|
+
]
|
|
932
|
+
});
|
|
933
|
+
}, xe = () => {
|
|
934
|
+
let e = F(), t = F((e) => e.playbackRate), n = F((e) => e.subtitleTracks), r = F((e) => e.selectedSubtitleTrack), i = F((e) => e.selectSubtitleTrack), a = F((e) => e.audioTracks), o = F((e) => e.selectedAudioTrack), s = F((e) => e.selectAudioTrack), c = F((e) => e.subtitleOffLabel), [u, p] = f(!1), [m, h] = f(0), g = d(null), [_, v] = f(void 0), [y, b] = f(!1), T = () => {
|
|
935
|
+
p(!u), h(0), b(!1);
|
|
936
|
+
}, E = () => {
|
|
937
|
+
p(!1), h(0);
|
|
938
|
+
}, D = (t) => {
|
|
939
|
+
e.setPlaybackRate(t), h(0);
|
|
913
940
|
};
|
|
914
941
|
l(() => {
|
|
915
942
|
let e = (e) => {
|
|
916
|
-
|
|
943
|
+
u && g.current && !g.current.contains(e.target) && T();
|
|
917
944
|
};
|
|
918
945
|
return document.addEventListener("pointerdown", e), () => document.removeEventListener("pointerdown", e);
|
|
919
|
-
}, [
|
|
920
|
-
let
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
946
|
+
}, [u]);
|
|
947
|
+
let O = (e, t) => {
|
|
948
|
+
if (_ !== void 0) return;
|
|
949
|
+
b(!1);
|
|
950
|
+
let n;
|
|
951
|
+
try {
|
|
952
|
+
n = t();
|
|
953
|
+
} catch (e) {
|
|
954
|
+
console.warn("[media-player] track selection failed:", e), b(!0);
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (!(n instanceof Promise)) {
|
|
958
|
+
E();
|
|
959
|
+
return;
|
|
960
|
+
}
|
|
961
|
+
v(e), n.then(() => {
|
|
962
|
+
v(void 0), E();
|
|
963
|
+
}, (e) => {
|
|
964
|
+
console.warn("[media-player] track selection failed:", e), v(void 0), b(!0);
|
|
965
|
+
});
|
|
966
|
+
}, k = (e) => O(e ?? null, () => i(e)), j = (e) => {
|
|
967
|
+
e !== void 0 && O(e, () => s(e));
|
|
968
|
+
}, M = (e) => () => {
|
|
969
|
+
h(e);
|
|
926
970
|
};
|
|
927
971
|
return /* @__PURE__ */ S("div", {
|
|
928
972
|
css: be,
|
|
929
|
-
ref:
|
|
973
|
+
ref: g,
|
|
930
974
|
children: [
|
|
931
|
-
/* @__PURE__ */ x(
|
|
975
|
+
/* @__PURE__ */ x(q, {
|
|
932
976
|
id: "settings",
|
|
933
|
-
disabled:
|
|
977
|
+
disabled: u,
|
|
934
978
|
text: /* @__PURE__ */ x("button", {
|
|
935
979
|
className: "settings",
|
|
936
980
|
type: "button",
|
|
937
|
-
onClick:
|
|
981
|
+
onClick: T,
|
|
938
982
|
children: /* @__PURE__ */ x(A, {})
|
|
939
983
|
}),
|
|
940
984
|
toolTipText: /* @__PURE__ */ x("span", { children: "Settings" })
|
|
941
985
|
}),
|
|
942
|
-
|
|
986
|
+
u && m === 0 && /* @__PURE__ */ S("div", {
|
|
943
987
|
className: "popover menu",
|
|
944
988
|
children: [
|
|
945
989
|
a.length > 1 ? /* @__PURE__ */ S("div", {
|
|
946
|
-
onClick:
|
|
990
|
+
onClick: M(3),
|
|
947
991
|
children: [/* @__PURE__ */ x("div", { children: "Audio" }), /* @__PURE__ */ x("div", { children: /* @__PURE__ */ x(w, {}) })]
|
|
948
992
|
}) : null,
|
|
949
993
|
n.length > 0 ? /* @__PURE__ */ S("div", {
|
|
950
|
-
onClick:
|
|
994
|
+
onClick: M(2),
|
|
951
995
|
children: [/* @__PURE__ */ x("div", { children: "Subtitles" }), /* @__PURE__ */ x("div", { children: /* @__PURE__ */ x(w, {}) })]
|
|
952
996
|
}) : null,
|
|
953
997
|
/* @__PURE__ */ S("div", {
|
|
954
|
-
onClick:
|
|
998
|
+
onClick: M(1),
|
|
955
999
|
children: [/* @__PURE__ */ x("div", { children: "Playback speed" }), /* @__PURE__ */ S("div", { children: [/* @__PURE__ */ x("span", {
|
|
956
1000
|
className: "secondary",
|
|
957
1001
|
children: t === 1 ? "(Default)" : t.toFixed(2)
|
|
@@ -959,12 +1003,12 @@ position: relative;
|
|
|
959
1003
|
})
|
|
960
1004
|
]
|
|
961
1005
|
}),
|
|
962
|
-
|
|
1006
|
+
u && m === 1 && /* @__PURE__ */ S("div", {
|
|
963
1007
|
className: "popover playback-rate",
|
|
964
1008
|
children: [
|
|
965
1009
|
/* @__PURE__ */ S("div", {
|
|
966
1010
|
className: "back",
|
|
967
|
-
onClick:
|
|
1011
|
+
onClick: M(0),
|
|
968
1012
|
children: [/* @__PURE__ */ x(C, {}), /* @__PURE__ */ x("span", { children: "Playback speed" })]
|
|
969
1013
|
}),
|
|
970
1014
|
/* @__PURE__ */ x("div", {
|
|
@@ -975,39 +1019,43 @@ position: relative;
|
|
|
975
1019
|
className: "options no-hover",
|
|
976
1020
|
children: [
|
|
977
1021
|
/* @__PURE__ */ x("div", {
|
|
978
|
-
onClick: () =>
|
|
1022
|
+
onClick: () => D(.5),
|
|
979
1023
|
children: "0.5x"
|
|
980
1024
|
}),
|
|
981
1025
|
/* @__PURE__ */ x("div", {
|
|
982
|
-
onClick: () =>
|
|
1026
|
+
onClick: () => D(1),
|
|
983
1027
|
children: "1x"
|
|
984
1028
|
}),
|
|
985
1029
|
/* @__PURE__ */ x("div", {
|
|
986
|
-
onClick: () =>
|
|
1030
|
+
onClick: () => D(1.5),
|
|
987
1031
|
children: "1.5x"
|
|
988
1032
|
}),
|
|
989
1033
|
/* @__PURE__ */ x("div", {
|
|
990
|
-
onClick: () =>
|
|
1034
|
+
onClick: () => D(2),
|
|
991
1035
|
children: "2x"
|
|
992
1036
|
})
|
|
993
1037
|
]
|
|
994
1038
|
})
|
|
995
1039
|
]
|
|
996
1040
|
}),
|
|
997
|
-
|
|
1041
|
+
u && m === 2 && /* @__PURE__ */ x(Y, {
|
|
998
1042
|
title: "Subtitles",
|
|
999
1043
|
tracks: n,
|
|
1000
1044
|
selected: r,
|
|
1001
|
-
onSelect:
|
|
1002
|
-
onBack:
|
|
1003
|
-
|
|
1045
|
+
onSelect: k,
|
|
1046
|
+
onBack: M(0),
|
|
1047
|
+
offLabel: c ?? "Disable",
|
|
1048
|
+
pending: _,
|
|
1049
|
+
failed: y
|
|
1004
1050
|
}),
|
|
1005
|
-
|
|
1051
|
+
u && m === 3 && /* @__PURE__ */ x(Y, {
|
|
1006
1052
|
title: "Audio",
|
|
1007
1053
|
tracks: a,
|
|
1008
1054
|
selected: o,
|
|
1009
|
-
onSelect:
|
|
1010
|
-
onBack:
|
|
1055
|
+
onSelect: j,
|
|
1056
|
+
onBack: M(0),
|
|
1057
|
+
pending: _,
|
|
1058
|
+
failed: y
|
|
1011
1059
|
})
|
|
1012
1060
|
]
|
|
1013
1061
|
});
|
|
@@ -1061,7 +1109,7 @@ position: relative;
|
|
|
1061
1109
|
height: 10px;
|
|
1062
1110
|
}
|
|
1063
1111
|
`, we = ({ value: e, onChange: t }) => {
|
|
1064
|
-
let n =
|
|
1112
|
+
let n = F((e) => e.volume), r = F((e) => e.muted), i = d(null), a = d(null), { handlers: o } = J({
|
|
1065
1113
|
ref: a,
|
|
1066
1114
|
onChange: t
|
|
1067
1115
|
}), s = (n) => {
|
|
@@ -1075,8 +1123,8 @@ position: relative;
|
|
|
1075
1123
|
e && e.removeEventListener("wheel", s);
|
|
1076
1124
|
};
|
|
1077
1125
|
}, [e]);
|
|
1078
|
-
let c = e * 100, f = u(() => Math.round(r ? 0 :
|
|
1079
|
-
return /* @__PURE__ */ x(
|
|
1126
|
+
let c = e * 100, f = u(() => Math.round(r ? 0 : G(n) * 100), [n, r]);
|
|
1127
|
+
return /* @__PURE__ */ x(q, {
|
|
1080
1128
|
id: "volume-slider",
|
|
1081
1129
|
text: /* @__PURE__ */ x("div", {
|
|
1082
1130
|
ref: i,
|
|
@@ -1124,7 +1172,7 @@ position: relative;
|
|
|
1124
1172
|
border-radius: 4px;
|
|
1125
1173
|
|
|
1126
1174
|
.volume-value {
|
|
1127
|
-
${
|
|
1175
|
+
${V.bMedium.regular};
|
|
1128
1176
|
color: #ffffff;
|
|
1129
1177
|
}
|
|
1130
1178
|
}
|
|
@@ -1163,17 +1211,17 @@ position: relative;
|
|
|
1163
1211
|
}
|
|
1164
1212
|
|
|
1165
1213
|
`, Ee = ({ ref: e }) => {
|
|
1166
|
-
let t =
|
|
1214
|
+
let t = F(), n = F((e) => e.volume), r = F((e) => e.muted), i = u(() => G(n), [n]);
|
|
1167
1215
|
return /* @__PURE__ */ S("div", {
|
|
1168
1216
|
css: Te,
|
|
1169
|
-
children: [/* @__PURE__ */ x(
|
|
1217
|
+
children: [/* @__PURE__ */ x(q, {
|
|
1170
1218
|
id: "sound",
|
|
1171
1219
|
text: /* @__PURE__ */ x("button", {
|
|
1172
1220
|
className: "sound",
|
|
1173
1221
|
type: "button",
|
|
1174
1222
|
onClick: () => t.toggleMuted(),
|
|
1175
1223
|
ref: e,
|
|
1176
|
-
children: x(r || n === 0 ?
|
|
1224
|
+
children: x(r || n === 0 ? ee : n <= .5 ? j : M, {
|
|
1177
1225
|
size: 18,
|
|
1178
1226
|
color: "#fff"
|
|
1179
1227
|
})
|
|
@@ -1186,7 +1234,7 @@ position: relative;
|
|
|
1186
1234
|
children: /* @__PURE__ */ x(we, {
|
|
1187
1235
|
value: r ? 0 : i,
|
|
1188
1236
|
onChange: (e) => {
|
|
1189
|
-
t.setVolume(
|
|
1237
|
+
t.setVolume(W(e));
|
|
1190
1238
|
}
|
|
1191
1239
|
})
|
|
1192
1240
|
})
|
|
@@ -1293,7 +1341,7 @@ position: relative;
|
|
|
1293
1341
|
|
|
1294
1342
|
.left {
|
|
1295
1343
|
.time {
|
|
1296
|
-
${
|
|
1344
|
+
${V.bMedium.regular}
|
|
1297
1345
|
text-shadow: 0 0 4px rgba(0, 0, 0, 1);
|
|
1298
1346
|
}
|
|
1299
1347
|
}
|
|
@@ -1315,9 +1363,9 @@ position: relative;
|
|
|
1315
1363
|
}
|
|
1316
1364
|
}
|
|
1317
1365
|
`, Oe = () => {
|
|
1318
|
-
let e =
|
|
1319
|
-
let r =
|
|
1320
|
-
e.setVolume(
|
|
1366
|
+
let e = F(), t = F((e) => e.paused), n = F((e) => e.currentTime), r = F((e) => e.duration), i = F((e) => e.fullscreen), a = F((e) => e.hideUI), o = F((e) => e.togglePictureInPicture), [s, u] = f(null), d = r > 0 && n === r, p = c(({ direction: t, stepSize: n }) => {
|
|
1367
|
+
let r = G(e.volume), i = t === "up" ? n : -n, a = Math.max(0, Math.min(1, r + i)), o = e.muted;
|
|
1368
|
+
e.setVolume(W(a)), o && !e.muted && e.toggleMuted();
|
|
1321
1369
|
}, [e]);
|
|
1322
1370
|
l(() => {
|
|
1323
1371
|
let t = (t) => {
|
|
@@ -1361,12 +1409,12 @@ position: relative;
|
|
|
1361
1409
|
opacity: "0",
|
|
1362
1410
|
pointerEvents: "none"
|
|
1363
1411
|
} : {} },
|
|
1364
|
-
children: [/* @__PURE__ */ x(
|
|
1412
|
+
children: [/* @__PURE__ */ x(ge, {}), /* @__PURE__ */ S("div", {
|
|
1365
1413
|
className: "actions",
|
|
1366
1414
|
children: [/* @__PURE__ */ S("div", {
|
|
1367
1415
|
className: "left",
|
|
1368
1416
|
children: [
|
|
1369
|
-
/* @__PURE__ */ x(
|
|
1417
|
+
/* @__PURE__ */ x(q, {
|
|
1370
1418
|
id: "play",
|
|
1371
1419
|
tooltipPlace: "top-start",
|
|
1372
1420
|
text: /* @__PURE__ */ x("button", {
|
|
@@ -1380,14 +1428,14 @@ position: relative;
|
|
|
1380
1428
|
/* @__PURE__ */ x(Ee, { ref: u }),
|
|
1381
1429
|
/* @__PURE__ */ x("div", {
|
|
1382
1430
|
className: "time",
|
|
1383
|
-
children:
|
|
1431
|
+
children: pe(n, r)
|
|
1384
1432
|
})
|
|
1385
1433
|
]
|
|
1386
1434
|
}), /* @__PURE__ */ S("div", {
|
|
1387
1435
|
className: "right",
|
|
1388
1436
|
children: [
|
|
1389
1437
|
/* @__PURE__ */ x(xe, {}),
|
|
1390
|
-
o ? /* @__PURE__ */ x(
|
|
1438
|
+
o ? /* @__PURE__ */ x(q, {
|
|
1391
1439
|
id: "picture-in-picture",
|
|
1392
1440
|
text: /* @__PURE__ */ x("button", {
|
|
1393
1441
|
className: "picture-in-picture",
|
|
@@ -1397,7 +1445,7 @@ position: relative;
|
|
|
1397
1445
|
}),
|
|
1398
1446
|
toolTipText: /* @__PURE__ */ x("span", { children: "Picture in picture" })
|
|
1399
1447
|
}) : null,
|
|
1400
|
-
/* @__PURE__ */ x(
|
|
1448
|
+
/* @__PURE__ */ x(q, {
|
|
1401
1449
|
id: "full-screen",
|
|
1402
1450
|
tooltipPlace: "top-end",
|
|
1403
1451
|
text: /* @__PURE__ */ x("button", {
|
|
@@ -1461,66 +1509,66 @@ position: relative;
|
|
|
1461
1509
|
key: r,
|
|
1462
1510
|
item: e
|
|
1463
1511
|
}];
|
|
1464
|
-
}), Me = ({ ref: e, onVideoRef: t, onCanvasRef: n, overlay: r,
|
|
1465
|
-
let
|
|
1466
|
-
l(() => () => clearTimeout(
|
|
1467
|
-
let
|
|
1468
|
-
|
|
1512
|
+
}), Me = ({ ref: e, onVideoRef: t, onCanvasRef: n, overlay: r, controls: i, children: a }) => {
|
|
1513
|
+
let o = F(), s = F((e) => e.hideUI), c = F((e) => e.setHideUI), u = d(void 0), f = d("mouse");
|
|
1514
|
+
l(() => () => clearTimeout(u.current), []);
|
|
1515
|
+
let p = () => {
|
|
1516
|
+
c(!1), clearTimeout(u.current), u.current = setTimeout(() => c(!0), ke);
|
|
1469
1517
|
};
|
|
1470
1518
|
return /* @__PURE__ */ S("div", {
|
|
1471
1519
|
css: je,
|
|
1472
1520
|
ref: e,
|
|
1473
1521
|
onPointerMove: (e) => {
|
|
1474
|
-
|
|
1522
|
+
f.current = e.pointerType, e.pointerType === "mouse" && p();
|
|
1475
1523
|
},
|
|
1476
1524
|
onPointerDown: (e) => {
|
|
1477
|
-
|
|
1525
|
+
f.current = e.pointerType;
|
|
1478
1526
|
},
|
|
1479
1527
|
onMouseOut: (e) => {
|
|
1480
1528
|
let t = e.relatedTarget;
|
|
1481
1529
|
if (t && e.currentTarget.contains(t)) return;
|
|
1482
1530
|
let { left: n, right: r, top: i, bottom: a } = e.currentTarget.getBoundingClientRect();
|
|
1483
|
-
e.clientX >= n && e.clientX < r && e.clientY >= i && e.clientY < a || (clearTimeout(
|
|
1531
|
+
e.clientX >= n && e.clientX < r && e.clientY >= i && e.clientY < a || (clearTimeout(u.current), c(!0));
|
|
1484
1532
|
},
|
|
1485
|
-
className:
|
|
1533
|
+
className: s ? "hide" : "",
|
|
1486
1534
|
children: [
|
|
1487
|
-
/* @__PURE__ */ x(
|
|
1535
|
+
/* @__PURE__ */ x(fe, { onCanvasRef: n }),
|
|
1488
1536
|
Q(r).map(({ key: e, item: t }) => /* @__PURE__ */ x("div", {
|
|
1489
1537
|
css: Ae,
|
|
1490
|
-
style: { ...
|
|
1538
|
+
style: { ...s ? {
|
|
1491
1539
|
opacity: "0",
|
|
1492
1540
|
visibility: "hidden",
|
|
1493
1541
|
pointerEvents: "none"
|
|
1494
1542
|
} : {} },
|
|
1495
1543
|
children: t
|
|
1496
1544
|
}, e)),
|
|
1497
|
-
/* @__PURE__ */ x(Oe, {}),
|
|
1545
|
+
i === !1 ? null : /* @__PURE__ */ x(Oe, {}),
|
|
1498
1546
|
/* @__PURE__ */ S("div", {
|
|
1499
1547
|
className: "video",
|
|
1500
1548
|
onClick: () => {
|
|
1501
|
-
if (
|
|
1502
|
-
|
|
1549
|
+
if (f.current === "mouse") {
|
|
1550
|
+
o.togglePaused();
|
|
1503
1551
|
return;
|
|
1504
1552
|
}
|
|
1505
|
-
|
|
1553
|
+
s ? p() : (clearTimeout(u.current), c(!0));
|
|
1506
1554
|
},
|
|
1507
1555
|
children: [t ? /* @__PURE__ */ x("video", {
|
|
1508
1556
|
ref: t,
|
|
1509
1557
|
playsInline: !0
|
|
1510
|
-
}) : null,
|
|
1558
|
+
}) : null, a]
|
|
1511
1559
|
})
|
|
1512
1560
|
]
|
|
1513
1561
|
});
|
|
1514
1562
|
}, Ne = ({ options: e, children: t }) => {
|
|
1515
1563
|
let n = "media" in e ? e : null, r = n ? null : e, { title: i } = e, { size: a, downloadedRanges: o, publicPath: s, libavWorkerUrl: c, read: u, thumbnailRead: d, thumbnailsEnabled: p } = r ?? {}, m = _(), h = g(), [v, y] = f(null), [b, S] = f(null), C = n?.media ?? v;
|
|
1516
|
-
l(() => (m?.(C), () => m?.(null)), [C, m]),
|
|
1517
|
-
let w =
|
|
1564
|
+
l(() => (m?.(C), () => m?.(null)), [C, m]), ie(v, b, r);
|
|
1565
|
+
let w = z({
|
|
1518
1566
|
publicPath: s,
|
|
1519
1567
|
workerUrl: c,
|
|
1520
1568
|
length: p === !1 ? void 0 : a,
|
|
1521
1569
|
read: d ?? u,
|
|
1522
1570
|
downloadedRanges: o
|
|
1523
|
-
}), T = n?.thumbnails?.all ?? w, E =
|
|
1571
|
+
}), T = n?.thumbnails?.all ?? w, E = B(v, b), D = F((e) => e.setSourceState);
|
|
1524
1572
|
l(() => {
|
|
1525
1573
|
D({
|
|
1526
1574
|
title: i,
|
|
@@ -1549,18 +1597,21 @@ position: relative;
|
|
|
1549
1597
|
let k = n?.subtitles?.selection, A = n?.audioTracks?.selection;
|
|
1550
1598
|
return l(() => {
|
|
1551
1599
|
k && D({
|
|
1552
|
-
subtitleTracks: k.options.map(({ id: e, label: t }) => ({
|
|
1600
|
+
subtitleTracks: k.options.map(({ id: e, label: t, disabled: n }) => ({
|
|
1553
1601
|
id: e,
|
|
1554
|
-
label: t
|
|
1602
|
+
label: t,
|
|
1603
|
+
disabled: n
|
|
1555
1604
|
})),
|
|
1556
1605
|
selectedSubtitleTrack: k.selectedId ?? void 0,
|
|
1557
|
-
selectSubtitleTrack: (e) => k.select(e == null ? null : String(e))
|
|
1606
|
+
selectSubtitleTrack: (e) => k.select(e == null ? null : String(e)),
|
|
1607
|
+
subtitleOffLabel: k.offLabel
|
|
1558
1608
|
});
|
|
1559
1609
|
}, [D, k]), l(() => {
|
|
1560
1610
|
A && D({
|
|
1561
|
-
audioTracks: A.options.map(({ id: e, label: t }) => ({
|
|
1611
|
+
audioTracks: A.options.map(({ id: e, label: t, disabled: n }) => ({
|
|
1562
1612
|
id: e,
|
|
1563
|
-
label: t
|
|
1613
|
+
label: t,
|
|
1614
|
+
disabled: n
|
|
1564
1615
|
})),
|
|
1565
1616
|
selectedAudioTrack: A.selectedId ?? void 0,
|
|
1566
1617
|
selectAudioTrack: (e) => A.select(String(e))
|
|
@@ -1570,6 +1621,7 @@ position: relative;
|
|
|
1570
1621
|
onVideoRef: n ? void 0 : y,
|
|
1571
1622
|
onCanvasRef: S,
|
|
1572
1623
|
overlay: e.overlay,
|
|
1624
|
+
controls: e.controls,
|
|
1573
1625
|
children: t
|
|
1574
1626
|
});
|
|
1575
1627
|
}, Pe = p`
|
|
@@ -1605,7 +1657,7 @@ position: relative;
|
|
|
1605
1657
|
overflow: hidden;
|
|
1606
1658
|
`, $ = (e) => /* @__PURE__ */ x("div", {
|
|
1607
1659
|
css: Pe,
|
|
1608
|
-
children: /* @__PURE__ */ x(
|
|
1660
|
+
children: /* @__PURE__ */ x(P.Provider, { children: /* @__PURE__ */ x(Ne, {
|
|
1609
1661
|
options: e,
|
|
1610
1662
|
children: e.children
|
|
1611
1663
|
}) })
|
|
@@ -1648,4 +1700,4 @@ position: relative;
|
|
|
1648
1700
|
throw Error(`Unknown source type: ${e}`);
|
|
1649
1701
|
};
|
|
1650
1702
|
//#endregion
|
|
1651
|
-
export { $ as MediaPlayer, $ as default,
|
|
1703
|
+
export { $ as MediaPlayer, $ as default, P as Player, n as SUBTITLES_OFF, i as createPictureInPicture, r as createSubtitleRenderer, t as createThumbnailGenerator, Be as inputToRemuxerInput, Ie as isDelegatedTracks, Fe as isExternalThumbnails, N as sourceFeature, e as startPlayback, B as usePictureInPicture, F as usePlayer, z as useSeekThumbnails };
|
|
@@ -6,7 +6,9 @@ export type ChromeProps = {
|
|
|
6
6
|
onCanvasRef: (element: HTMLCanvasElement | 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
|
+
/** False draws no control bar at all, leaving the picture, the title and the overlay. */
|
|
10
|
+
controls?: boolean;
|
|
9
11
|
children?: ReactNode;
|
|
10
12
|
};
|
|
11
|
-
export declare const Chrome: ({ ref, onVideoRef, onCanvasRef, overlay, children }: ChromeProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
13
|
+
export declare const Chrome: ({ ref, onVideoRef, onCanvasRef, overlay, controls, children }: ChromeProps) => import("@emotion/react/jsx-runtime").JSX.Element;
|
|
12
14
|
export default Chrome;
|
package/dist/react/player.d.ts
CHANGED
|
@@ -7,10 +7,11 @@ export declare const Player: import("@videojs/react").CreatePlayerResult<import(
|
|
|
7
7
|
thumbnailAt?: (time: number) => import("..").ThumbnailImage | undefined;
|
|
8
8
|
subtitleTracks: import("./source-feature").TrackChoice[];
|
|
9
9
|
selectedSubtitleTrack: string | number | undefined;
|
|
10
|
-
selectSubtitleTrack: (id: string | number | undefined) => void
|
|
10
|
+
selectSubtitleTrack: (id: string | number | undefined) => void | Promise<void>;
|
|
11
|
+
subtitleOffLabel?: string;
|
|
11
12
|
audioTracks: import("./source-feature").TrackChoice[];
|
|
12
13
|
selectedAudioTrack: string | number | undefined;
|
|
13
|
-
selectAudioTrack: (id: string | number) => void
|
|
14
|
+
selectAudioTrack: (id: string | number) => void | Promise<void>;
|
|
14
15
|
hideUI: boolean;
|
|
15
16
|
setHideUI: (hide: boolean) => void;
|
|
16
17
|
togglePictureInPicture: (() => void) | null;
|
|
@@ -10,6 +10,13 @@ import type { MediaIndex, ThumbnailImage } from '../engine';
|
|
|
10
10
|
export type TrackChoice = {
|
|
11
11
|
id: string | number;
|
|
12
12
|
label: string;
|
|
13
|
+
/**
|
|
14
|
+
* Offered but not selectable, so the menu shows it and refuses the click.
|
|
15
|
+
*
|
|
16
|
+
* Hiding it instead would be worse: a source that lists a dub it cannot currently serve is telling
|
|
17
|
+
* the viewer the dub exists, and a menu that silently omits it looks like the source has nothing.
|
|
18
|
+
*/
|
|
19
|
+
disabled?: boolean;
|
|
13
20
|
};
|
|
14
21
|
/**
|
|
15
22
|
* A byte span of the file the consumer has in hand, mapped onto the timeline through the keyframe
|
|
@@ -40,13 +47,24 @@ export type SourceState = {
|
|
|
40
47
|
* Falls back to scanning `thumbnails` when absent, which is what the engine's generator fills.
|
|
41
48
|
*/
|
|
42
49
|
thumbnailAt?: (time: number) => ThumbnailImage | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Both selectors may answer with a promise, and the menu waits on it.
|
|
52
|
+
*
|
|
53
|
+
* The engine switches a track by pointing the pipeline at another stream, which is immediate and
|
|
54
|
+
* cannot fail, so locally these return nothing. A source that owns its own player is the opposite
|
|
55
|
+
* case: the switch is a round trip through somebody else's UI and takes seconds, and it can lose.
|
|
56
|
+
* A menu that closed on the click would report a selection that has not happened, and a rejection
|
|
57
|
+
* with nothing awaiting it is an unhandled rejection rather than an error the viewer ever sees.
|
|
58
|
+
*/
|
|
43
59
|
subtitleTracks: TrackChoice[];
|
|
44
60
|
/** undefined means subtitles are off. */
|
|
45
61
|
selectedSubtitleTrack: string | number | undefined;
|
|
46
|
-
selectSubtitleTrack: (id: string | number | undefined) => void
|
|
62
|
+
selectSubtitleTrack: (id: string | number | undefined) => void | Promise<void>;
|
|
63
|
+
/** What the row that turns subtitles off is called, when the source would rather name it itself. */
|
|
64
|
+
subtitleOffLabel?: string;
|
|
47
65
|
audioTracks: TrackChoice[];
|
|
48
66
|
selectedAudioTrack: string | number | undefined;
|
|
49
|
-
selectAudioTrack: (id: string | number) => void
|
|
67
|
+
selectAudioTrack: (id: string | number) => void | Promise<void>;
|
|
50
68
|
/** Chrome auto-hide. True means the controls, title and cursor are hidden. */
|
|
51
69
|
hideUI: boolean;
|
|
52
70
|
setHideUI: (hide: boolean) => void;
|
|
@@ -87,13 +105,24 @@ export declare const sourceFeature: import("@videojs/react").PlayerFeature<{
|
|
|
87
105
|
* Falls back to scanning `thumbnails` when absent, which is what the engine's generator fills.
|
|
88
106
|
*/
|
|
89
107
|
thumbnailAt?: (time: number) => ThumbnailImage | undefined;
|
|
108
|
+
/**
|
|
109
|
+
* Both selectors may answer with a promise, and the menu waits on it.
|
|
110
|
+
*
|
|
111
|
+
* The engine switches a track by pointing the pipeline at another stream, which is immediate and
|
|
112
|
+
* cannot fail, so locally these return nothing. A source that owns its own player is the opposite
|
|
113
|
+
* case: the switch is a round trip through somebody else's UI and takes seconds, and it can lose.
|
|
114
|
+
* A menu that closed on the click would report a selection that has not happened, and a rejection
|
|
115
|
+
* with nothing awaiting it is an unhandled rejection rather than an error the viewer ever sees.
|
|
116
|
+
*/
|
|
90
117
|
subtitleTracks: TrackChoice[];
|
|
91
118
|
/** undefined means subtitles are off. */
|
|
92
119
|
selectedSubtitleTrack: string | number | undefined;
|
|
93
|
-
selectSubtitleTrack: (id: string | number | undefined) => void
|
|
120
|
+
selectSubtitleTrack: (id: string | number | undefined) => void | Promise<void>;
|
|
121
|
+
/** What the row that turns subtitles off is called, when the source would rather name it itself. */
|
|
122
|
+
subtitleOffLabel?: string;
|
|
94
123
|
audioTracks: TrackChoice[];
|
|
95
124
|
selectedAudioTrack: string | number | undefined;
|
|
96
|
-
selectAudioTrack: (id: string | number) => void
|
|
125
|
+
selectAudioTrack: (id: string | number) => void | Promise<void>;
|
|
97
126
|
/** Chrome auto-hide. True means the controls, title and cursor are hidden. */
|
|
98
127
|
hideUI: boolean;
|
|
99
128
|
setHideUI: (hide: boolean) => void;
|
|
@@ -14,8 +14,28 @@ export type MediaPlayerSource = {
|
|
|
14
14
|
};
|
|
15
15
|
/** Shared by both arms: nothing here depends on who owns the media. */
|
|
16
16
|
type CommonOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* Drawn across the top of the picture, in a layer of its own.
|
|
19
|
+
*
|
|
20
|
+
* That layer cannot see an `overlay` item, so an app that also places something along the top has
|
|
21
|
+
* two elements competing for one band of screen: on a narrow viewport the title ellipsizes against
|
|
22
|
+
* the FULL width and then runs underneath whatever is painted over it. An app in that position
|
|
23
|
+
* should leave this unset and draw the title as part of its own overlay row, where the two can
|
|
24
|
+
* share a flex line and the filename can be the one that gives way.
|
|
25
|
+
*/
|
|
17
26
|
title?: string;
|
|
18
27
|
autoplay?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Draw the control bar. Defaults to true.
|
|
30
|
+
*
|
|
31
|
+
* False leaves the picture, the title and the overlay and takes away only the chrome, for a host
|
|
32
|
+
* that has to put its own interactive UI over the media for a while. A source whose sign-in form
|
|
33
|
+
* lives inside its own document is the case this exists for: the form is the thing the viewer has
|
|
34
|
+
* to reach, and a control bar for a media that has not loaded yet sits on top of it and eats the
|
|
35
|
+
* clicks. Hiding it from the host side is not an option, since `setHideUI` is installed by the
|
|
36
|
+
* store's `attach` and does nothing at all until a media arrives.
|
|
37
|
+
*/
|
|
38
|
+
controls?: boolean;
|
|
19
39
|
/**
|
|
20
40
|
* The app's own content over the video: a download readout, a badge, a logo, anything the player
|
|
21
41
|
* itself has no opinion about.
|
|
@@ -85,7 +105,16 @@ export type MediaPlayerLocalOptions = CommonOptions & MediaPlayerSource & {
|
|
|
85
105
|
* works: the key session belongs to whoever owns the element.
|
|
86
106
|
*/
|
|
87
107
|
export type MediaPlayerRemoteOptions = CommonOptions & {
|
|
88
|
-
|
|
108
|
+
/**
|
|
109
|
+
* Null means the media has not been found yet: the chrome renders and attaches when it arrives.
|
|
110
|
+
*
|
|
111
|
+
* A source usually has to mount its own document before it can hand over an element, and that
|
|
112
|
+
* document is passed as `children`, so it can only exist once this component has rendered. The
|
|
113
|
+
* key is still REQUIRED even when the value is null, because its presence is what selects this
|
|
114
|
+
* arm: omitting it falls through to the local arm, which draws an idle `<video>` over whatever
|
|
115
|
+
* the children put there.
|
|
116
|
+
*/
|
|
117
|
+
media: PlayerMedia | null;
|
|
89
118
|
read?: never;
|
|
90
119
|
size?: never;
|
|
91
120
|
/** The source's own storyboard, since there are no bytes to generate previews from. */
|
package/package.json
CHANGED
|
@@ -95,10 +95,12 @@ export type ChromeProps = {
|
|
|
95
95
|
onCanvasRef: (element: HTMLCanvasElement | null) => void
|
|
96
96
|
/** The app's own content, over the video and outside the click-to-pause region, unlike `children`. */
|
|
97
97
|
overlay?: ReactNode
|
|
98
|
+
/** False draws no control bar at all, leaving the picture, the title and the overlay. */
|
|
99
|
+
controls?: boolean
|
|
98
100
|
children?: ReactNode
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
export const Chrome = ({ ref, onVideoRef, onCanvasRef, overlay, children }: ChromeProps) => {
|
|
103
|
+
export const Chrome = ({ ref, onVideoRef, onCanvasRef, overlay, controls, children }: ChromeProps) => {
|
|
102
104
|
const player = usePlayer()
|
|
103
105
|
const hideUI = usePlayer((state) => state.hideUI)
|
|
104
106
|
const setHideUI = usePlayer((state) => state.setHideUI)
|
|
@@ -170,7 +172,7 @@ export const Chrome = ({ ref, onVideoRef, onCanvasRef, overlay, children }: Chro
|
|
|
170
172
|
{item}
|
|
171
173
|
</div>
|
|
172
174
|
))}
|
|
173
|
-
<ControlBar />
|
|
175
|
+
{controls === false ? null : <ControlBar />}
|
|
174
176
|
<div className="video" onClick={onVideoClick}>
|
|
175
177
|
{onVideoRef ? <video ref={onVideoRef} playsInline /> : null}
|
|
176
178
|
{children}
|
|
@@ -158,6 +158,22 @@ position: relative;
|
|
|
158
158
|
.description {
|
|
159
159
|
word-break: break-word;
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
/* Dimmed and inert, but still listed. A source that names a track it cannot serve right now is
|
|
163
|
+
saying the track exists, and dropping the row would read as the source having nothing. */
|
|
164
|
+
.unavailable {
|
|
165
|
+
opacity: 0.5;
|
|
166
|
+
cursor: default;
|
|
167
|
+
:hover {
|
|
168
|
+
background-color: transparent;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.failed {
|
|
173
|
+
border-bottom: 1px solid #4D4D4E;
|
|
174
|
+
color: #f66;
|
|
175
|
+
${fonts.bSmall.regular}
|
|
176
|
+
}
|
|
161
177
|
}
|
|
162
178
|
`
|
|
163
179
|
|
|
@@ -168,46 +184,55 @@ enum PopoverContent {
|
|
|
168
184
|
Audio
|
|
169
185
|
}
|
|
170
186
|
|
|
171
|
-
/**
|
|
187
|
+
/**
|
|
188
|
+
* One track picker, shared by the subtitle and audio menus.
|
|
189
|
+
*
|
|
190
|
+
* `pending` and `failed` describe the switch the viewer just asked for, not the menu: while a source
|
|
191
|
+
* is working, every row is inert and the one being switched to says so, because the selection has
|
|
192
|
+
* not moved yet and a tick next to it would be a lie.
|
|
193
|
+
*/
|
|
172
194
|
const TrackMenu = (
|
|
173
|
-
{ title, tracks, selected, onSelect, onBack,
|
|
195
|
+
{ title, tracks, selected, onSelect, onBack, offLabel, pending, failed }: {
|
|
174
196
|
title: string
|
|
175
197
|
tracks: TrackChoice[]
|
|
176
198
|
selected: string | number | undefined
|
|
177
199
|
onSelect: (id: string | number | undefined) => void
|
|
178
200
|
onBack: () => void
|
|
179
|
-
|
|
201
|
+
/** Absent means the menu offers no way off at all, which is what audio wants. */
|
|
202
|
+
offLabel?: string
|
|
203
|
+
/** The id currently being switched to, where `null` is the off row and undefined means idle. */
|
|
204
|
+
pending?: string | number | null
|
|
205
|
+
failed?: boolean
|
|
206
|
+
}
|
|
207
|
+
) => {
|
|
208
|
+
const busy = pending !== undefined
|
|
209
|
+
const row = (id: string | number | null, label: string, className?: string) => {
|
|
210
|
+
const isPending = busy && pending === id
|
|
211
|
+
const disabled = busy || tracks.find((track) => track.id === id)?.disabled
|
|
212
|
+
return (
|
|
213
|
+
<div
|
|
214
|
+
key={id ?? '__off__'}
|
|
215
|
+
onClick={() => { if (!disabled) onSelect(id ?? undefined) }}
|
|
216
|
+
className={[className, disabled ? 'unavailable' : null].filter(Boolean).join(' ') || undefined}
|
|
217
|
+
>
|
|
218
|
+
<span>{label}</span>
|
|
219
|
+
<span>{isPending ? '…' : (selected ?? null) === id ? '✓' : ''}</span>
|
|
220
|
+
</div>
|
|
221
|
+
)
|
|
180
222
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
<div className=
|
|
184
|
-
<
|
|
185
|
-
|
|
223
|
+
|
|
224
|
+
return (
|
|
225
|
+
<div className='popover track-list'>
|
|
226
|
+
<div className="back" onClick={onBack}>
|
|
227
|
+
<ChevronLeft />
|
|
228
|
+
<span>{title}</span>
|
|
229
|
+
</div>
|
|
230
|
+
{failed ? <div className="no-hover failed">Could not switch. Try again.</div> : null}
|
|
231
|
+
{offLabel ? row(null, offLabel) : null}
|
|
232
|
+
{tracks.map(({ id, label }) => row(id, label, 'description'))}
|
|
186
233
|
</div>
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
? (
|
|
190
|
-
<div onClick={() => onSelect(undefined)}>
|
|
191
|
-
<span>Disable</span>
|
|
192
|
-
<span>{selected === undefined ? '✓' : ''}</span>
|
|
193
|
-
</div>
|
|
194
|
-
)
|
|
195
|
-
: null
|
|
196
|
-
}
|
|
197
|
-
{
|
|
198
|
-
tracks.map(({ id, label }) => (
|
|
199
|
-
<div
|
|
200
|
-
key={id}
|
|
201
|
-
onClick={() => onSelect(id)}
|
|
202
|
-
className="description"
|
|
203
|
-
>
|
|
204
|
-
<span>{label}</span>
|
|
205
|
-
<span>{selected === id ? '✓' : ''}</span>
|
|
206
|
-
</div>
|
|
207
|
-
))
|
|
208
|
-
}
|
|
209
|
-
</div>
|
|
210
|
-
)
|
|
234
|
+
)
|
|
235
|
+
}
|
|
211
236
|
|
|
212
237
|
export const SettingsAction = () => {
|
|
213
238
|
const player = usePlayer()
|
|
@@ -219,13 +244,27 @@ export const SettingsAction = () => {
|
|
|
219
244
|
const selectedAudioTrack = usePlayer((state) => state.selectedAudioTrack)
|
|
220
245
|
const selectAudioTrack = usePlayer((state) => state.selectAudioTrack)
|
|
221
246
|
|
|
247
|
+
const subtitleOffLabel = usePlayer((state) => state.subtitleOffLabel)
|
|
248
|
+
|
|
222
249
|
const [isOpenPopover, setIsOpenPopover] = useState(false)
|
|
223
250
|
const [popoverContent, setPopoverContent] = useState(PopoverContent.Default)
|
|
224
251
|
const settingsContainerRef = useRef<HTMLDivElement>(null)
|
|
252
|
+
// The id being switched to, or undefined when nothing is in flight. Null is the row that turns
|
|
253
|
+
// subtitles off, so this cannot be a plain id: undefined has to keep meaning idle.
|
|
254
|
+
const [pending, setPending] = useState<string | number | null | undefined>(undefined)
|
|
255
|
+
const [failed, setFailed] = useState(false)
|
|
225
256
|
|
|
226
257
|
const togglePopover = () => {
|
|
227
258
|
setIsOpenPopover(!isOpenPopover)
|
|
228
259
|
setPopoverContent(PopoverContent.Default)
|
|
260
|
+
setFailed(false)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Not `togglePopover`: this runs from a promise, where the captured `isOpenPopover` is whatever it
|
|
264
|
+
// was at click time, so a toggle could reopen a menu the viewer has already dismissed.
|
|
265
|
+
const closePopover = () => {
|
|
266
|
+
setIsOpenPopover(false)
|
|
267
|
+
setPopoverContent(PopoverContent.Default)
|
|
229
268
|
}
|
|
230
269
|
|
|
231
270
|
const setPlaybackRate = (rate: number) => {
|
|
@@ -248,15 +287,50 @@ export const SettingsAction = () => {
|
|
|
248
287
|
return () => document.removeEventListener('pointerdown', handleClickOutside)
|
|
249
288
|
}, [isOpenPopover])
|
|
250
289
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
290
|
+
/**
|
|
291
|
+
* Runs a track switch and decides when the menu may close.
|
|
292
|
+
*
|
|
293
|
+
* A selector that returns nothing switched synchronously, so the click closes the menu as it always
|
|
294
|
+
* has. One that returns a promise owns a player this one cannot reach, and the menu stays open and
|
|
295
|
+
* inert until it answers: closing first would show a selection that has not happened yet, and
|
|
296
|
+
* dropping the promise would turn a failed switch into an unhandled rejection nobody ever sees.
|
|
297
|
+
*/
|
|
298
|
+
const runSelect = (id: string | number | null, select: () => void | Promise<void>) => {
|
|
299
|
+
if (pending !== undefined) return
|
|
300
|
+
setFailed(false)
|
|
301
|
+
let result: void | Promise<void>
|
|
302
|
+
try {
|
|
303
|
+
result = select()
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.warn('[media-player] track selection failed:', err)
|
|
306
|
+
setFailed(true)
|
|
307
|
+
return
|
|
308
|
+
}
|
|
309
|
+
if (!(result instanceof Promise)) {
|
|
310
|
+
closePopover()
|
|
311
|
+
return
|
|
312
|
+
}
|
|
313
|
+
setPending(id)
|
|
314
|
+
result.then(
|
|
315
|
+
() => {
|
|
316
|
+
setPending(undefined)
|
|
317
|
+
closePopover()
|
|
318
|
+
},
|
|
319
|
+
(err) => {
|
|
320
|
+
console.warn('[media-player] track selection failed:', err)
|
|
321
|
+
setPending(undefined)
|
|
322
|
+
setFailed(true)
|
|
323
|
+
},
|
|
324
|
+
)
|
|
254
325
|
}
|
|
255
326
|
|
|
327
|
+
const chooseSubtitle = (id: string | number | undefined) =>
|
|
328
|
+
runSelect(id ?? null, () => selectSubtitleTrack(id))
|
|
329
|
+
|
|
330
|
+
// the audio menu offers no way off, so there is no undefined case to answer for
|
|
256
331
|
const chooseAudio = (id: string | number | undefined) => {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
togglePopover()
|
|
332
|
+
if (id === undefined) return
|
|
333
|
+
runSelect(id, () => selectAudioTrack(id))
|
|
260
334
|
}
|
|
261
335
|
|
|
262
336
|
const changePopoverContent = (newPopoverContent: PopoverContent) => () => {
|
|
@@ -362,7 +436,9 @@ export const SettingsAction = () => {
|
|
|
362
436
|
selected={selectedSubtitleTrack}
|
|
363
437
|
onSelect={chooseSubtitle}
|
|
364
438
|
onBack={changePopoverContent(PopoverContent.Default)}
|
|
365
|
-
|
|
439
|
+
offLabel={subtitleOffLabel ?? 'Disable'}
|
|
440
|
+
pending={pending}
|
|
441
|
+
failed={failed}
|
|
366
442
|
/>
|
|
367
443
|
)
|
|
368
444
|
}
|
|
@@ -374,6 +450,8 @@ export const SettingsAction = () => {
|
|
|
374
450
|
selected={selectedAudioTrack}
|
|
375
451
|
onSelect={chooseAudio}
|
|
376
452
|
onBack={changePopoverContent(PopoverContent.Default)}
|
|
453
|
+
pending={pending}
|
|
454
|
+
failed={failed}
|
|
377
455
|
/>
|
|
378
456
|
)
|
|
379
457
|
}
|
|
@@ -13,6 +13,13 @@ import { definePlayerFeature } from '@videojs/core/dom'
|
|
|
13
13
|
export type TrackChoice = {
|
|
14
14
|
id: string | number
|
|
15
15
|
label: string
|
|
16
|
+
/**
|
|
17
|
+
* Offered but not selectable, so the menu shows it and refuses the click.
|
|
18
|
+
*
|
|
19
|
+
* Hiding it instead would be worse: a source that lists a dub it cannot currently serve is telling
|
|
20
|
+
* the viewer the dub exists, and a menu that silently omits it looks like the source has nothing.
|
|
21
|
+
*/
|
|
22
|
+
disabled?: boolean
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
/**
|
|
@@ -47,14 +54,25 @@ export type SourceState = {
|
|
|
47
54
|
*/
|
|
48
55
|
thumbnailAt?: (time: number) => ThumbnailImage | undefined
|
|
49
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Both selectors may answer with a promise, and the menu waits on it.
|
|
59
|
+
*
|
|
60
|
+
* The engine switches a track by pointing the pipeline at another stream, which is immediate and
|
|
61
|
+
* cannot fail, so locally these return nothing. A source that owns its own player is the opposite
|
|
62
|
+
* case: the switch is a round trip through somebody else's UI and takes seconds, and it can lose.
|
|
63
|
+
* A menu that closed on the click would report a selection that has not happened, and a rejection
|
|
64
|
+
* with nothing awaiting it is an unhandled rejection rather than an error the viewer ever sees.
|
|
65
|
+
*/
|
|
50
66
|
subtitleTracks: TrackChoice[]
|
|
51
67
|
/** undefined means subtitles are off. */
|
|
52
68
|
selectedSubtitleTrack: string | number | undefined
|
|
53
|
-
selectSubtitleTrack: (id: string | number | undefined) => void
|
|
69
|
+
selectSubtitleTrack: (id: string | number | undefined) => void | Promise<void>
|
|
70
|
+
/** What the row that turns subtitles off is called, when the source would rather name it itself. */
|
|
71
|
+
subtitleOffLabel?: string
|
|
54
72
|
|
|
55
73
|
audioTracks: TrackChoice[]
|
|
56
74
|
selectedAudioTrack: string | number | undefined
|
|
57
|
-
selectAudioTrack: (id: string | number) => void
|
|
75
|
+
selectAudioTrack: (id: string | number) => void | Promise<void>
|
|
58
76
|
|
|
59
77
|
/** Chrome auto-hide. True means the controls, title and cursor are hidden. */
|
|
60
78
|
hideUI: boolean
|
|
@@ -23,9 +23,30 @@ export type MediaPlayerSource =
|
|
|
23
23
|
|
|
24
24
|
/** Shared by both arms: nothing here depends on who owns the media. */
|
|
25
25
|
type CommonOptions = {
|
|
26
|
+
/**
|
|
27
|
+
* Drawn across the top of the picture, in a layer of its own.
|
|
28
|
+
*
|
|
29
|
+
* That layer cannot see an `overlay` item, so an app that also places something along the top has
|
|
30
|
+
* two elements competing for one band of screen: on a narrow viewport the title ellipsizes against
|
|
31
|
+
* the FULL width and then runs underneath whatever is painted over it. An app in that position
|
|
32
|
+
* should leave this unset and draw the title as part of its own overlay row, where the two can
|
|
33
|
+
* share a flex line and the filename can be the one that gives way.
|
|
34
|
+
*/
|
|
26
35
|
title?: string
|
|
27
36
|
autoplay?: boolean
|
|
28
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Draw the control bar. Defaults to true.
|
|
40
|
+
*
|
|
41
|
+
* False leaves the picture, the title and the overlay and takes away only the chrome, for a host
|
|
42
|
+
* that has to put its own interactive UI over the media for a while. A source whose sign-in form
|
|
43
|
+
* lives inside its own document is the case this exists for: the form is the thing the viewer has
|
|
44
|
+
* to reach, and a control bar for a media that has not loaded yet sits on top of it and eats the
|
|
45
|
+
* clicks. Hiding it from the host side is not an option, since `setHideUI` is installed by the
|
|
46
|
+
* store's `attach` and does nothing at all until a media arrives.
|
|
47
|
+
*/
|
|
48
|
+
controls?: boolean
|
|
49
|
+
|
|
29
50
|
/**
|
|
30
51
|
* The app's own content over the video: a download readout, a badge, a logo, anything the player
|
|
31
52
|
* itself has no opinion about.
|
|
@@ -105,7 +126,16 @@ export type MediaPlayerLocalOptions =
|
|
|
105
126
|
export type MediaPlayerRemoteOptions =
|
|
106
127
|
& CommonOptions
|
|
107
128
|
& {
|
|
108
|
-
|
|
129
|
+
/**
|
|
130
|
+
* Null means the media has not been found yet: the chrome renders and attaches when it arrives.
|
|
131
|
+
*
|
|
132
|
+
* A source usually has to mount its own document before it can hand over an element, and that
|
|
133
|
+
* document is passed as `children`, so it can only exist once this component has rendered. The
|
|
134
|
+
* key is still REQUIRED even when the value is null, because its presence is what selects this
|
|
135
|
+
* arm: omitting it falls through to the local arm, which draws an idle `<video>` over whatever
|
|
136
|
+
* the children put there.
|
|
137
|
+
*/
|
|
138
|
+
media: PlayerMedia | null
|
|
109
139
|
read?: never
|
|
110
140
|
size?: never
|
|
111
141
|
|
|
@@ -177,19 +207,23 @@ const PlayerRoot = ({ options, children }: { options: MediaPlayerOptions, childr
|
|
|
177
207
|
// the document, and it renders the result itself.
|
|
178
208
|
const subtitles = remote?.subtitles?.selection
|
|
179
209
|
const audio = remote?.audioTracks?.selection
|
|
210
|
+
// `select` is published as-is rather than wrapped, so its promise reaches the menu: the menu is the
|
|
211
|
+
// only place that can hold the popover open, keep the tick off an unfinished switch, and show a
|
|
212
|
+
// failure, and a promise nothing awaits is an unhandled rejection.
|
|
180
213
|
useEffect(() => {
|
|
181
214
|
if (!subtitles) return
|
|
182
215
|
setSourceState({
|
|
183
|
-
subtitleTracks: subtitles.options.map(({ id, label }) => ({ id, label })),
|
|
216
|
+
subtitleTracks: subtitles.options.map(({ id, label, disabled }) => ({ id, label, disabled })),
|
|
184
217
|
selectedSubtitleTrack: subtitles.selectedId ?? undefined,
|
|
185
218
|
selectSubtitleTrack: (id) => subtitles.select(id == null ? null : String(id)),
|
|
219
|
+
subtitleOffLabel: subtitles.offLabel,
|
|
186
220
|
})
|
|
187
221
|
}, [setSourceState, subtitles])
|
|
188
222
|
|
|
189
223
|
useEffect(() => {
|
|
190
224
|
if (!audio) return
|
|
191
225
|
setSourceState({
|
|
192
|
-
audioTracks: audio.options.map(({ id, label }) => ({ id, label })),
|
|
226
|
+
audioTracks: audio.options.map(({ id, label, disabled }) => ({ id, label, disabled })),
|
|
193
227
|
selectedAudioTrack: audio.selectedId ?? undefined,
|
|
194
228
|
selectAudioTrack: (id) => audio.select(String(id)),
|
|
195
229
|
})
|
|
@@ -203,6 +237,7 @@ const PlayerRoot = ({ options, children }: { options: MediaPlayerOptions, childr
|
|
|
203
237
|
onVideoRef={remote ? undefined : setVideo}
|
|
204
238
|
onCanvasRef={setCanvas}
|
|
205
239
|
overlay={options.overlay}
|
|
240
|
+
controls={options.controls}
|
|
206
241
|
>
|
|
207
242
|
{children}
|
|
208
243
|
</Chrome>
|