@banou/media-player 0.8.3 → 0.8.5
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 +407 -203
- package/dist/react/components/chrome.d.ts +3 -1
- package/dist/react/components/icons.d.ts +4 -0
- package/dist/react/components/subtitles.d.ts +9 -0
- package/dist/react/components/track-menu.d.ts +47 -0
- 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/control-bar.tsx +4 -2
- package/src/lib/react/components/icons.tsx +38 -0
- package/src/lib/react/components/progress-bar.tsx +20 -4
- package/src/lib/react/components/settings.tsx +26 -206
- package/src/lib/react/components/subtitles.tsx +80 -0
- package/src/lib/react/components/track-menu.tsx +277 -0
- package/src/lib/react/source-feature.ts +20 -2
- package/src/lib/react/video-player.tsx +77 -3
|
@@ -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>
|
|
@@ -222,6 +257,45 @@ const rootStyle = css`
|
|
|
222
257
|
*/
|
|
223
258
|
--mp-unit: 10px;
|
|
224
259
|
|
|
260
|
+
/**
|
|
261
|
+
* The chrome's own typeface, for exactly the reason the unit above exists.
|
|
262
|
+
*
|
|
263
|
+
* \`font-family\` inherits, and this library declared it nowhere, so every label drew in whatever the
|
|
264
|
+
* embedding document happened to set, and in the UA's serif where it set nothing. Not hypothetical:
|
|
265
|
+
* stub mounts the player in a second document, \`embed.html\`, which sets no font and loads no face,
|
|
266
|
+
* and the entire settings menu came out in Times New Roman.
|
|
267
|
+
*
|
|
268
|
+
* Named faces only, no bundled webfont. The chrome is a handful of short labels, so a font request
|
|
269
|
+
* on the critical path of a document whose job is to paint video buys a round trip and a flash of
|
|
270
|
+
* unstyled text for nothing.
|
|
271
|
+
*
|
|
272
|
+
* Read through \`var()\` so a host with a brand face sets \`--mp-font-family\` on any ancestor and it
|
|
273
|
+
* inherits in. A bare \`font-family\` on the host can no longer reach the chrome: this declaration is
|
|
274
|
+
* on the player element itself and beats anything inherited.
|
|
275
|
+
*/
|
|
276
|
+
font-family: var(
|
|
277
|
+
--mp-font-family,
|
|
278
|
+
system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif,
|
|
279
|
+
'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji'
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* The chrome's default ink, same argument again.
|
|
284
|
+
*
|
|
285
|
+
* Most text here names its own colour, but the two time readouts do not: the elapsed/duration pair
|
|
286
|
+
* and the seekbar's hover time carry a font token and a black text-shadow and nothing else.
|
|
287
|
+
* Inherited from a host that declares none that resolves to \`canvastext\`, which is black text under
|
|
288
|
+
* a black halo on a dark gradient.
|
|
289
|
+
*/
|
|
290
|
+
color: #fff;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* The settings menu scrolls, and a UA that has not been told the surface is dark paints that
|
|
294
|
+
* scrollbar in its light theme: a white track down the side of a #1c1c1c menu. Declared here rather
|
|
295
|
+
* than asked of the host, so it reaches the menu and nothing outside the player.
|
|
296
|
+
*/
|
|
297
|
+
color-scheme: dark;
|
|
298
|
+
|
|
225
299
|
/**
|
|
226
300
|
* The other half of owning the chrome's own scale.
|
|
227
301
|
*
|