@banou/media-player 0.8.21 → 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 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="/jassub-worker-modern.wasm"
26
- jassubLegacyWasmUrl="/jassub-worker.wasm"
27
- defaultFontUrl="/default.woff2"
25
+ jassubWasmUrl={jassubWasmUrl}
26
+ jassubLegacyWasmUrl={jassubLegacyWasmUrl}
27
+ defaultFontUrl={defaultFontUrl}
28
28
  title="episode.mkv"
29
29
  autoplay
30
30
  />
@@ -64,9 +64,10 @@ containers carry headers, fonts and attachments that occupy no time at all.
64
64
 
65
65
  ### The assets your app has to serve
66
66
 
67
- Nothing is bundled: the workers and the wasm are fetched at runtime from urls you provide, so they have
68
- to be copied out of `node_modules` and hosted. `src/asset-urls.ts` is a worked example, and the
69
- `copy-assets` script is what puts them in `public/`.
67
+ The workers and the wasm are fetched at runtime from urls you provide, never from anything this
68
+ package resolves for itself. libav's are plain files to copy out of `node_modules`, which is what the
69
+ `copy-assets` script does. jassub's cannot be copied and have to be built by your bundler; see below.
70
+ `src/asset-urls.ts` is the worked example for both.
70
71
 
71
72
  `publicPath` is the directory **libav's two wasm files** are served from, and both have to be there:
72
73
 
@@ -79,25 +80,62 @@ libav-wasm picks between them at runtime on `typeof WebAssembly.Suspending === '
79
80
  only one does not fail everywhere: it fails on exactly the browsers that pick the missing file, which
80
81
  reads as a browser bug rather than a missing asset. Serve both.
81
82
 
82
- The rest are named individually: `libavWorkerUrl` (`libav-wasm/build/worker.js`) and the optional
83
- `defaultFontUrl`. jassub also has two builds, and the same warning applies:
83
+ `libavWorkerUrl` (`libav-wasm/build/worker.js`) is named individually and still copies fine.
84
+
85
+ ### jassub's assets are imported, not copied
86
+
87
+ jassub 2's worker is an ES module that imports `abslink` and `lfa-ponyfill` by bare specifier plus
88
+ five relative siblings, and it spawns a nested worker of its own. Copying one file out of
89
+ `node_modules` produces something no browser can load, so the bundler has to build that graph. With
90
+ vite that is one query each, in your own source:
91
+
92
+ ```ts
93
+ import jassubWorkerUrl from 'jassub/dist/worker/worker.js?worker&url'
94
+ import jassubWasmUrl from 'jassub/dist/wasm/jassub-worker-modern.wasm?no-inline&url'
95
+ import jassubLegacyWasmUrl from 'jassub/dist/wasm/jassub-worker.wasm?no-inline&url'
96
+ import defaultFontUrl from 'jassub/dist/default.woff2?no-inline&url'
97
+ ```
98
+
99
+ `?no-inline` is load bearing for anything built with `build.lib` set: vite inlines every asset as a
100
+ base64 data url in library mode and ignores `assetsInlineLimit` while doing it, so without it the two
101
+ wasm builds and the font land in your entry chunk as about 5.9 MB of base64. `src/asset-urls.ts` in
102
+ this repo is the same four lines and is meant to be copied.
103
+
104
+ **If you build with `build.lib` set, build these four in a separate pass with it UNSET.** `?no-inline`
105
+ covers the assets you name, and it cannot cover the ones jassub names for itself: its emscripten glue
106
+ resolves the wasm with `new URL('./wasm/...', import.meta.url)` inside the worker, and lib mode inlines
107
+ those too. Measured on this version, one entry importing exactly the four lines above:
108
+
109
+ | build | worker chunk | base64 wasm blobs |
110
+ | --- | --- | --- |
111
+ | `build.lib` set | 2,922,207 bytes | 2 |
112
+ | no `build.lib` | 110,525 bytes | 0 |
113
+
114
+ `assetsInlineLimit: 0` and the function form both make no difference, because lib mode decides to
115
+ inline before it consults either. A second vite config with a single JS entry, no `lib`, and the same
116
+ `outDir` produces the 110 KB worker, the 30 KB pthread glue and both wasm files as real files.
117
+
118
+ `?no-inline&url` needs a type declaration, because vite's own ambient `*?url` only matches a specifier
119
+ ENDING in `?url`. See `src/vite-env.d.ts`.
120
+
121
+ The two wasm builds are picked at runtime:
84
122
 
85
123
  | option | file | when it is used |
86
124
  | --- | --- | --- |
87
- | `jassubWasmUrl` | `jassub/dist/jassub-worker-modern.wasm` | wherever WebAssembly SIMD exists |
88
- | `jassubLegacyWasmUrl` | `jassub/dist/jassub-worker.wasm` | Safari before 16.4, and anything else without SIMD |
125
+ | `jassubWasmUrl` | `jassub/dist/wasm/jassub-worker-modern.wasm` | wherever RELAXED SIMD exists |
126
+ | `jassubLegacyWasmUrl` | `jassub/dist/wasm/jassub-worker.wasm` | everywhere else |
89
127
 
90
- `jassubLegacyWasmUrl` is optional in the type and not in practice: jassub falls back to a bare
91
- `'jassub-worker.wasm'`, which it resolves against the `blob:` url its worker is built from, and that
92
- throws. Leaving it unset does not fall back to the slower build, it loses subtitles entirely.
128
+ The test is RELAXED simd, not baseline simd: jassub validates a module using `i8x16.relaxed_swizzle`.
129
+ That is a much later feature than plain SIMD, so the second file is not a museum piece for ancient
130
+ Safari the way it was under jassub 1. Serve both.
93
131
 
94
- jassub ships a classic worker script, so wrap it:
132
+ Serve the wasm as `application/wasm`: jassub instantiates it with `instantiateStreaming`, which
133
+ refuses any other content type.
95
134
 
96
- ```ts
97
- const jassubWorkerUrl = URL.createObjectURL(
98
- new Blob([`importScripts("/jassub-worker.js")`], { type: 'application/javascript' }),
99
- )
100
- ```
135
+ No cross-origin isolation is needed. jassub allocates a shared `WebAssembly.Memory` unconditionally,
136
+ which constructs on a plain origin in both engines (measured on Chrome 152 and Firefox 154), and its
137
+ own thread count is gated on `crossOriginIsolated`, so without COOP and COEP it simply runs single
138
+ threaded.
101
139
 
102
140
  The chrome brings its own scale and needs nothing from the host page's root font. Everything is sized
103
141
  against `--mp-unit`, which defaults to `10px` on the player element; set it there to rescale the whole
@@ -116,6 +154,11 @@ Subtitles are painted by jassub onto a canvas over the video, and picture in pic
116
154
  element and nothing else, so the browser has no way to composite the two: a plain
117
155
  `requestPictureInPicture()` puts the bare video in the window and leaves the subtitles on the page.
118
156
 
157
+ That canvas belongs to jassub from version 2 on: the constructor transfers it to a worker, which an
158
+ element accepts exactly once, and `destroy()` removes it from the document. So the player renders a
159
+ subtitle LAYER and the engine mounts one canvas inside it per pipeline build. Anything reaching for
160
+ the surface has to look it up through the layer each time rather than hold it.
161
+
119
162
  So the player composites them itself. Every presented frame is drawn to an offscreen canvas with the
120
163
  subtitle canvas on top, and `captureStream()` turns that into a MediaStream backing a hidden video
121
164
  element, which is the one that enters the window. The original element keeps playing and stays the
@@ -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-B8CKGIng.js";
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-DZrYu66u.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
- /** The subtitle canvas. jassub sizes it to the video's content rect, so it maps 1:1. */
18
- canvas: HTMLCanvasElement;
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
- canvasElement: HTMLCanvasElement;
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
- canvas: HTMLCanvasElement;
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 Safari before 16.4 and anything else without SIMD.
27
+ * The non-SIMD build, `jassub-worker.wasm`, for anything without WebAssembly SIMD.
17
28
  *
18
- * Not optional in practice, only in the type. jassub picks `wasmUrl ?? 'jassub-worker.wasm'` when SIMD
19
- * is missing, and that bare relative name resolves against the blob: url the worker is built from,
20
- * which throws. So leaving this unset does not degrade to the slower build, it fails outright.
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
  /**