@banou/media-player 0.8.21 → 0.10.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 +69 -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/thumbnails.d.ts +11 -0
- package/dist/{engine-B8CKGIng.js → engine-YeMWShCv.js} +243 -182
- package/dist/index.js +235 -205
- 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/react/hooks/use-thumbnails.d.ts +12 -1
- package/dist/react/player.d.ts +1 -0
- package/dist/react/source-feature.d.ts +22 -0
- 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/engine/thumbnails.ts +80 -21
- package/src/lib/react/components/chrome.tsx +16 -3
- package/src/lib/react/components/overlay.tsx +27 -8
- package/src/lib/react/components/progress-bar.tsx +17 -4
- 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/hooks/use-thumbnails.ts +19 -3
- package/src/lib/react/source-feature.ts +12 -0
- package/src/lib/react/video-player.tsx +8 -6
package/README.md
CHANGED
|
@@ -22,9 +22,9 @@ import MediaPlayer from '@banou/media-player'
|
|
|
22
22
|
publicPath="/"
|
|
23
23
|
libavWorkerUrl="/libav-worker.js"
|
|
24
24
|
jassubWorkerUrl={jassubWorkerUrl}
|
|
25
|
-
jassubWasmUrl=
|
|
26
|
-
jassubLegacyWasmUrl=
|
|
27
|
-
defaultFontUrl=
|
|
25
|
+
jassubWasmUrl={jassubWasmUrl}
|
|
26
|
+
jassubLegacyWasmUrl={jassubLegacyWasmUrl}
|
|
27
|
+
defaultFontUrl={defaultFontUrl}
|
|
28
28
|
title="episode.mkv"
|
|
29
29
|
autoplay
|
|
30
30
|
/>
|
|
@@ -58,15 +58,23 @@ player.play()
|
|
|
58
58
|
|
|
59
59
|
`useSeekThumbnails` and `usePictureInPicture` are exported for reuse outside the bundled chrome.
|
|
60
60
|
|
|
61
|
+
`useSeekThumbnails` returns `{ thumbnails, requestThumbnail }`. Previews are generated from the start
|
|
62
|
+
of the file to the end, which is right until somebody points at the seekbar: `requestThumbnail(time)`
|
|
63
|
+
moves the preview covering `time` to the front of that queue, and the walk carries on behind it from
|
|
64
|
+
wherever it had got to. Pass `undefined` when the pointer leaves. The bundled seekbar calls it for
|
|
65
|
+
you, so this is only for a chrome you draw yourself. It cannot interrupt a decode already in flight,
|
|
66
|
+
so the wait is the tail of that one rather than the whole backlog.
|
|
67
|
+
|
|
61
68
|
`downloadedRanges` paints byte spans you already hold onto the seekbar, mapped through the keyframe
|
|
62
69
|
index rather than by percentage, because a file's download progress is not its playback progress:
|
|
63
70
|
containers carry headers, fonts and attachments that occupy no time at all.
|
|
64
71
|
|
|
65
72
|
### The assets your app has to serve
|
|
66
73
|
|
|
67
|
-
|
|
68
|
-
to
|
|
69
|
-
`copy-assets` script
|
|
74
|
+
The workers and the wasm are fetched at runtime from urls you provide, never from anything this
|
|
75
|
+
package resolves for itself. libav's are plain files to copy out of `node_modules`, which is what the
|
|
76
|
+
`copy-assets` script does. jassub's cannot be copied and have to be built by your bundler; see below.
|
|
77
|
+
`src/asset-urls.ts` is the worked example for both.
|
|
70
78
|
|
|
71
79
|
`publicPath` is the directory **libav's two wasm files** are served from, and both have to be there:
|
|
72
80
|
|
|
@@ -79,25 +87,62 @@ libav-wasm picks between them at runtime on `typeof WebAssembly.Suspending === '
|
|
|
79
87
|
only one does not fail everywhere: it fails on exactly the browsers that pick the missing file, which
|
|
80
88
|
reads as a browser bug rather than a missing asset. Serve both.
|
|
81
89
|
|
|
82
|
-
|
|
83
|
-
|
|
90
|
+
`libavWorkerUrl` (`libav-wasm/build/worker.js`) is named individually and still copies fine.
|
|
91
|
+
|
|
92
|
+
### jassub's assets are imported, not copied
|
|
93
|
+
|
|
94
|
+
jassub 2's worker is an ES module that imports `abslink` and `lfa-ponyfill` by bare specifier plus
|
|
95
|
+
five relative siblings, and it spawns a nested worker of its own. Copying one file out of
|
|
96
|
+
`node_modules` produces something no browser can load, so the bundler has to build that graph. With
|
|
97
|
+
vite that is one query each, in your own source:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import jassubWorkerUrl from 'jassub/dist/worker/worker.js?worker&url'
|
|
101
|
+
import jassubWasmUrl from 'jassub/dist/wasm/jassub-worker-modern.wasm?no-inline&url'
|
|
102
|
+
import jassubLegacyWasmUrl from 'jassub/dist/wasm/jassub-worker.wasm?no-inline&url'
|
|
103
|
+
import defaultFontUrl from 'jassub/dist/default.woff2?no-inline&url'
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`?no-inline` is load bearing for anything built with `build.lib` set: vite inlines every asset as a
|
|
107
|
+
base64 data url in library mode and ignores `assetsInlineLimit` while doing it, so without it the two
|
|
108
|
+
wasm builds and the font land in your entry chunk as about 5.9 MB of base64. `src/asset-urls.ts` in
|
|
109
|
+
this repo is the same four lines and is meant to be copied.
|
|
110
|
+
|
|
111
|
+
**If you build with `build.lib` set, build these four in a separate pass with it UNSET.** `?no-inline`
|
|
112
|
+
covers the assets you name, and it cannot cover the ones jassub names for itself: its emscripten glue
|
|
113
|
+
resolves the wasm with `new URL('./wasm/...', import.meta.url)` inside the worker, and lib mode inlines
|
|
114
|
+
those too. Measured on this version, one entry importing exactly the four lines above:
|
|
115
|
+
|
|
116
|
+
| build | worker chunk | base64 wasm blobs |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `build.lib` set | 2,922,207 bytes | 2 |
|
|
119
|
+
| no `build.lib` | 110,525 bytes | 0 |
|
|
120
|
+
|
|
121
|
+
`assetsInlineLimit: 0` and the function form both make no difference, because lib mode decides to
|
|
122
|
+
inline before it consults either. A second vite config with a single JS entry, no `lib`, and the same
|
|
123
|
+
`outDir` produces the 110 KB worker, the 30 KB pthread glue and both wasm files as real files.
|
|
124
|
+
|
|
125
|
+
`?no-inline&url` needs a type declaration, because vite's own ambient `*?url` only matches a specifier
|
|
126
|
+
ENDING in `?url`. See `src/vite-env.d.ts`.
|
|
127
|
+
|
|
128
|
+
The two wasm builds are picked at runtime:
|
|
84
129
|
|
|
85
130
|
| option | file | when it is used |
|
|
86
131
|
| --- | --- | --- |
|
|
87
|
-
| `jassubWasmUrl` | `jassub/dist/jassub-worker-modern.wasm` | wherever
|
|
88
|
-
| `jassubLegacyWasmUrl` | `jassub/dist/jassub-worker.wasm` |
|
|
132
|
+
| `jassubWasmUrl` | `jassub/dist/wasm/jassub-worker-modern.wasm` | wherever RELAXED SIMD exists |
|
|
133
|
+
| `jassubLegacyWasmUrl` | `jassub/dist/wasm/jassub-worker.wasm` | everywhere else |
|
|
89
134
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
135
|
+
The test is RELAXED simd, not baseline simd: jassub validates a module using `i8x16.relaxed_swizzle`.
|
|
136
|
+
That is a much later feature than plain SIMD, so the second file is not a museum piece for ancient
|
|
137
|
+
Safari the way it was under jassub 1. Serve both.
|
|
93
138
|
|
|
94
|
-
|
|
139
|
+
Serve the wasm as `application/wasm`: jassub instantiates it with `instantiateStreaming`, which
|
|
140
|
+
refuses any other content type.
|
|
95
141
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
142
|
+
No cross-origin isolation is needed. jassub allocates a shared `WebAssembly.Memory` unconditionally,
|
|
143
|
+
which constructs on a plain origin in both engines (measured on Chrome 152 and Firefox 154), and its
|
|
144
|
+
own thread count is gated on `crossOriginIsolated`, so without COOP and COEP it simply runs single
|
|
145
|
+
threaded.
|
|
101
146
|
|
|
102
147
|
The chrome brings its own scale and needs nothing from the host page's root font. Everything is sized
|
|
103
148
|
against `--mp-unit`, which defaults to `10px` on the player element; set it there to rescale the whole
|
|
@@ -116,6 +161,11 @@ Subtitles are painted by jassub onto a canvas over the video, and picture in pic
|
|
|
116
161
|
element and nothing else, so the browser has no way to composite the two: a plain
|
|
117
162
|
`requestPictureInPicture()` puts the bare video in the window and leaves the subtitles on the page.
|
|
118
163
|
|
|
164
|
+
That canvas belongs to jassub from version 2 on: the constructor transfers it to a worker, which an
|
|
165
|
+
element accepts exactly once, and `destroy()` removes it from the document. So the player renders a
|
|
166
|
+
subtitle LAYER and the engine mounts one canvas inside it per pipeline build. Anything reaching for
|
|
167
|
+
the surface has to look it up through the layer each time rather than hold it.
|
|
168
|
+
|
|
119
169
|
So the player composites them itself. Every presented frame is drawn to an offscreen canvas with the
|
|
120
170
|
subtitle canvas on top, and `captureStream()` turns that into a MediaStream backing a hidden video
|
|
121
171
|
element, which is the one that enters the window. The original element keeps playing and stays the
|
package/dist/engine/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-
|
|
1
|
+
import { a as e, c as t, d as n, f as r, i, l as a, n as o, o as s, r as c, s as l, t as u, u as d } from "../engine-YeMWShCv.js";
|
|
2
2
|
export { i as DEFAULT_BUFFER_SIZE, e as MediaElementError, a as SUBTITLES_OFF, u as createPictureInPicture, d as createSubtitleRenderer, c as createThumbnailGenerator, n as getTimeRanges, s as isMediaElementError, o as pictureInPictureMode, l as startPlayback, t as terminateRemuxer, r as updateSourceBuffer };
|
|
@@ -14,8 +14,15 @@
|
|
|
14
14
|
export type PictureInPictureMode = 'window' | 'burn-in';
|
|
15
15
|
export type PictureInPictureOptions = {
|
|
16
16
|
video: HTMLVideoElement;
|
|
17
|
-
/**
|
|
18
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The subtitle LAYER, not the canvas inside it.
|
|
19
|
+
*
|
|
20
|
+
* From jassub 2 the canvas is created and removed by the subtitle renderer, one per pipeline
|
|
21
|
+
* build, so an element captured here would stop being the one being painted the moment the audio
|
|
22
|
+
* track changed. The layer is what React owns for the life of the player, so hiding and restoring
|
|
23
|
+
* it stays correct across a rebuild, and the canvas is looked up per frame.
|
|
24
|
+
*/
|
|
25
|
+
subtitles: HTMLElement;
|
|
19
26
|
maxWidth?: number;
|
|
20
27
|
/** Where the mirror is mounted. It must be in the document. Defaults to the video's parent. */
|
|
21
28
|
container?: HTMLElement;
|
|
@@ -8,7 +8,14 @@ export type MediaIndex = {
|
|
|
8
8
|
};
|
|
9
9
|
export type PlaybackOptions = {
|
|
10
10
|
videoElement: HTMLVideoElement;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* The subtitle layer. The renderer creates its own canvas inside it, one per jassub instance.
|
|
13
|
+
*
|
|
14
|
+
* Renamed from `canvasElement` in the jassub 2 migration, deliberately: passing a canvas here now
|
|
15
|
+
* means jassub transfers it to a worker and deletes it on teardown, so a caller that kept handing
|
|
16
|
+
* over an element it owns has to see a type error rather than a runtime one.
|
|
17
|
+
*/
|
|
18
|
+
subtitleContainer: HTMLElement;
|
|
12
19
|
read: (offset: number, size: number) => Promise<ArrayBuffer>;
|
|
13
20
|
length: number;
|
|
14
21
|
/** serves BOTH `libav.wasm` and `libav-jspi.wasm`; libav-wasm picks one on `WebAssembly.Suspending` */
|
|
@@ -8,16 +8,27 @@ export type SubtitleStream = {
|
|
|
8
8
|
export declare const SUBTITLES_OFF = -1;
|
|
9
9
|
export type SubtitleRendererOptions = {
|
|
10
10
|
video: HTMLVideoElement;
|
|
11
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Where the subtitle surface is mounted.
|
|
13
|
+
*
|
|
14
|
+
* A container, not the canvas itself. From jassub 2 the canvas belongs to the renderer: the
|
|
15
|
+
* constructor transfers it to a worker, which an element accepts exactly once for its whole life,
|
|
16
|
+
* and `destroy()` removes it from the document. Neither is survivable for an element someone else
|
|
17
|
+
* owns, and this pipeline is rebuilt in place on an audio track change and on an element recovery.
|
|
18
|
+
* So one canvas is created here per jassub instance, inside this container.
|
|
19
|
+
*
|
|
20
|
+
* The live canvas is `container.querySelector('canvas')`, and it is replaced on every rebuild.
|
|
21
|
+
*/
|
|
22
|
+
container: HTMLElement;
|
|
12
23
|
workerUrl: string;
|
|
13
24
|
/** The SIMD build, `jassub-worker-modern.wasm`. Used wherever WebAssembly SIMD is available. */
|
|
14
25
|
wasmUrl: string;
|
|
15
26
|
/**
|
|
16
|
-
* The non-SIMD build, `jassub-worker.wasm`, for
|
|
27
|
+
* The non-SIMD build, `jassub-worker.wasm`, for anything without WebAssembly SIMD.
|
|
17
28
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
29
|
+
* Optional in practice as well as in the type, unlike under jassub 1: v2 defaults both wasm urls to
|
|
30
|
+
* its own files resolved against `import.meta.url`, so leaving this unset degrades to whatever the
|
|
31
|
+
* bundler emitted rather than failing outright.
|
|
21
32
|
*/
|
|
22
33
|
legacyWasmUrl?: string;
|
|
23
34
|
/**
|
|
@@ -17,6 +17,17 @@ export type ThumbnailGeneratorOptions = {
|
|
|
17
17
|
export type ThumbnailGenerator = {
|
|
18
18
|
/** Report which byte ranges are readable. Called with no argument when the whole file is. */
|
|
19
19
|
update: (ranges?: [number, number][]) => void;
|
|
20
|
+
/**
|
|
21
|
+
* Where the viewer is pointing, so that preview is decoded next. `undefined` when they stop.
|
|
22
|
+
*
|
|
23
|
+
* Only the slot covering `time` jumps the queue, and only for as long as it is still waiting, so
|
|
24
|
+
* this moves one preview forward rather than re-ordering the run. Everything behind it keeps the
|
|
25
|
+
* order it was claimed in and carries on the moment the jumped slot is done.
|
|
26
|
+
*
|
|
27
|
+
* It cannot interrupt a decode that has already started, so the wait is the tail of the one in
|
|
28
|
+
* flight and not the whole backlog.
|
|
29
|
+
*/
|
|
30
|
+
prioritize: (time: number | undefined) => void;
|
|
20
31
|
destroy: () => void;
|
|
21
32
|
};
|
|
22
33
|
export declare const createThumbnailGenerator: (options: ThumbnailGeneratorOptions) => Promise<ThumbnailGenerator>;
|