rivmux 0.2.0 → 0.4.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
@@ -6,6 +6,8 @@ Public browser player facade for Rivmux low-latency live playback.
6
6
  audio/video into fragmented MP4, and attaches the resulting media stream to a
7
7
  browser `<video>` element.
8
8
 
9
+ M1 supports only the dedicated-worker MSE runtime path. Main-thread MSE is not available.
10
+
9
11
  ## Install
10
12
 
11
13
  ```sh
@@ -98,10 +100,6 @@ const player = new RivmuxPlayer('https://example.com/live.flv', {
98
100
  backoffMs: 500,
99
101
  },
100
102
  },
101
- runtime: {
102
- workerUrl: '/assets/rivmux-runtime-worker.js',
103
- wasmUrl: '/assets/rivmux-transmux-core.wasm',
104
- },
105
103
  diagnostics: {
106
104
  statsIntervalMs: 1000,
107
105
  debug: false,
@@ -139,12 +137,35 @@ Values are in seconds.
139
137
 
140
138
  ### `runtime`
141
139
 
142
- | Option | Default | Description |
143
- | ----------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
144
- | `preferWorkerMse` | `true` | Prefers the worker-backed MSE pipeline when the browser supports it. |
145
- | `workerUrl` | bundled worker URL | Overrides the worker script URL. Use this when serving worker assets from a fixed public path. |
146
- | `wasmUrl` | bundled WASM module | Overrides the WASM asset URL. If provided, the matching wasm-bindgen JS glue file must be available at the same path with `.js` instead of `.wasm`. |
147
- | `wasmModule` | `undefined` | Reserved for custom runtime integrations that provide a precompiled `WebAssembly.Module`. |
140
+ | Option | Default | Description |
141
+ | ----------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------- |
142
+ | `preferWorkerMse` | `true` | Must remain `true` in M1. Main-thread MSE fallback is not implemented. |
143
+ | `workerUrl` | bundled worker URL | Advanced override for the packaged Dedicated Worker script URL. |
144
+ | `wasmUrl` | bundled WASM module | Advanced override for the packaged WASM binary URL. It must match the Worker release that contains the wasm-bindgen glue. |
145
+
146
+ ### Advanced Asset Deployment
147
+
148
+ Most applications should omit `runtime.workerUrl` and `runtime.wasmUrl`. The
149
+ default package assets are tracked by Vite and emitted with the application
150
+ build.
151
+
152
+ Set these options only when your deployment serves Rivmux assets from a fixed
153
+ public path or CDN:
154
+
155
+ ```ts
156
+ const player = new RivmuxPlayer('https://example.com/live.flv', {
157
+ runtime: {
158
+ workerUrl: 'https://cdn.example.com/rivmux/0.4.0/rivmux-runtime-worker.js',
159
+ wasmUrl: 'https://cdn.example.com/rivmux/0.4.0/rivmux-transmux-core.wasm',
160
+ },
161
+ })
162
+ ```
163
+
164
+ `workerUrl` does not derive `wasmUrl`; when you override both assets, publish
165
+ them as a matching release pair. Treat both URLs as trusted executable asset
166
+ locations and never construct them from untrusted input. The host application
167
+ is responsible for compatible CSP and CORS policies, serving the WASM asset as
168
+ `application/wasm`, and cache-busting the Worker/WASM pair together.
148
169
 
149
170
  ### `diagnostics`
150
171
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { RuntimeWorkerFactory } from "@rivmux/runtime-worker";
2
2
  import { DiagnosticsOptions, LatencyOptions, MediaInfo, NetworkOptions, NormalizedRivmuxPlayerOptions, NormalizedRivmuxPlayerOptions as NormalizedRivmuxPlayerOptions$1, PlaybackOptions, PlayerError, PlayerError as PlayerError$1, PlayerErrorKind, PlayerErrorKind as PlayerErrorKind$1, PlayerEventListener, PlayerEventListener as PlayerEventListener$1, PlayerEventMap, PlayerEventType, PlayerEventType as PlayerEventType$1, PlayerStats, PlayerWarning, RivmuxPlayerOptions, RivmuxPlayerOptions as RivmuxPlayerOptions$1, RuntimeOptions } from "@rivmux/protocol";
3
-
4
3
  //#region src/player.d.ts
5
4
  type RivmuxPlayerInternals = {
6
5
  workerFactory?: RuntimeWorkerFactory;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/options.ts","../src/errors.ts"],"mappings":";;;;KAmBY,qBAAA;EACV,aAAA,GAAgB,oBAAA;EAChB,aAAA,SAAsB,aAAW;EACjC,SAAA;AAAA;;;;;;;cASW,YAAA;EATF;EAAA,SAWA,GAAA;EAFc;EAAA,SAKd,OAAA,EAAS,+BAAA;EAAA,iBACD,EAAA;EAAA,iBACA,MAAA;EAAA,iBACA,aAAA;EAAA,iBACA,aAAA;EAAA,QACT,YAAA;EAAA,QACA,KAAA;EAAA,QACA,eAAA;EAAA,QACA,KAAA;EA4HK;;;;;cArHD,GAAA,UAAa,OAAA,GAAU,qBAAA,EAAqB,SAAA,GAAW,qBAAA;EA0HG;;;;;EA7GhE,MAAA,CAAO,KAAA,EAAO,gBAAA,GAAmB,OAAA;EA5BrB;;;;;EAqDZ,KAAA,IAAS,OAAA;EA/CP;;;;;;EA0EF,IAAA,IAAQ,OAAA;EAjEqD;;;;;;EAwF7D,OAAA,IAAW,OAAA;EAlDF;EA+Ef,EAAA,WAAa,iBAAA,EAAiB,IAAA,EAAM,CAAA,EAAG,QAAA,EAAU,qBAAA,CAAoB,CAAA;EApDvD;EAyDd,GAAA,WAAc,iBAAA,EAAiB,IAAA,EAAM,CAAA,EAAG,QAAA,EAAU,qBAAA,CAAoB,CAAA;EAAA,QAI9D,kBAAA;EAAA,QAaA,mBAAA;EAAA,QAoCA,uBAAA;EAAA,QAeA,oBAAA;EAAA,QAKA,wBAAA;EAAA,QAUA,uBAAA;EAAA,QASA,cAAA;EAAA,QAoBM,oBAAA;EAAA,QAkCN,iBAAA;EAAA,QAYA,kBAAA;AAAA;;;cCvUG,6BAAA,EAA+B,+BA2B3C;AAAA,iBAEe,sBAAA,CAAuB,OAAA,GAAS,qBAAA,GAA2B,+BAA6B;;;iBC/BxF,iBAAA,CAAkB,IAAA,EAAM,iBAAA,EAAiB,IAAA,UAAc,OAAA,UAAiB,QAAA,WAAmB,KAAA,aAAkB,aAAW"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/options.ts","../src/errors.ts"],"mappings":";;;KAmBY;EACV,gBAAgB;EAChB,sBAAsB;EACtB;;;;;;;;cASW;;WAEF;;WAGA,SAAS;mBACD;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;;;;;;cAOI,aAAa,UAAU,uBAAqB,YAAW;;;;;;EAa7D,OAAO,OAAO,mBAAmB;;;;;;EAyBjC,SAAS;;;;;;;EA2BT,QAAQ;;;;;;;EAuBR,WAAW;;EA6BjB,GAAG,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;;EAKrE,IAAI,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;UAI9D;UAaA;UAoCA;UAeA;UAKA;UAUA;UASA;UAoBM;UAkCN;UAYA;;;;cCvUG,+BAA+B;iBA6B5B,uBAAuB,UAAS,wBAA2B;;;iBC/B3D,kBAAkB,MAAM,mBAAiB,cAAc,iBAAiB,mBAAmB,kBAAkB"}
package/dist/index.js CHANGED
@@ -1,9 +1,7 @@
1
- import { WorkerClient, createRuntimeWorker } from "@rivmux/runtime-worker";
1
+ import { WorkerClient, createRuntimeWorker, getBundledWasmUrl } from "@rivmux/runtime-worker";
2
2
  //#region src/events.ts
3
3
  var PlayerEventEmitter = class {
4
- constructor() {
5
- this.listeners = /* @__PURE__ */ new Map();
6
- }
4
+ listeners = /* @__PURE__ */ new Map();
7
5
  on(type, listener) {
8
6
  const typedListener = listener;
9
7
  const listeners = this.listeners.get(type);
@@ -49,8 +47,15 @@ function playerErrorToException(error) {
49
47
  }
50
48
  //#endregion
51
49
  //#region src/feature-detect.ts
52
- const M1_VIDEO_MIME = "video/mp4; codecs=\"avc1.42C01E\"";
53
- const M1_AUDIO_MIME = "audio/mp4; codecs=\"mp4a.40.2\"";
50
+ const REQUIRED_MSE_MIME_TYPES = [{
51
+ mediaType: "video",
52
+ mimeType: createMp4Mime("video", "avc1.42C01E"),
53
+ unsupportedCode: "RIVMUX_UNSUPPORTED_MSE_VIDEO_MIME"
54
+ }, {
55
+ mediaType: "audio",
56
+ mimeType: createMp4Mime("audio", "mp4a.40.2"),
57
+ unsupportedCode: "RIVMUX_UNSUPPORTED_MSE_AUDIO_MIME"
58
+ }];
54
59
  function detectMainThreadRuntime() {
55
60
  if (typeof Worker === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER", "Dedicated Worker is not available in this runtime.", true);
56
61
  if (typeof fetch !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this runtime.", true);
@@ -59,8 +64,10 @@ function detectMainThreadRuntime() {
59
64
  if (typeof MediaSource === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this runtime.", true);
60
65
  if (MediaSource.canConstructInDedicatedWorker !== true) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in a dedicated worker in this runtime.", true);
61
66
  if (typeof MediaSource.isTypeSupported !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this runtime.", true);
62
- if (!MediaSource.isTypeSupported("video/mp4; codecs=\"avc1.42C01E\"")) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_M1_VIDEO_MIME", `MSE does not support ${M1_VIDEO_MIME}.`, true);
63
- if (!MediaSource.isTypeSupported("audio/mp4; codecs=\"mp4a.40.2\"")) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_M1_AUDIO_MIME", `MSE does not support ${M1_AUDIO_MIME}.`, true);
67
+ for (const requirement of REQUIRED_MSE_MIME_TYPES) if (!MediaSource.isTypeSupported(requirement.mimeType)) return createPlayerError("unsupported", requirement.unsupportedCode, `MSE does not support ${requirement.mimeType}.`, true);
68
+ }
69
+ function createMp4Mime(mediaType, codec) {
70
+ return `${mediaType}/mp4; codecs="${codec}"`;
64
71
  }
65
72
  //#endregion
66
73
  //#region src/options.ts
@@ -112,10 +119,7 @@ function normalizePlayerOptions(options = {}) {
112
119
  ...options.network?.retry
113
120
  }
114
121
  },
115
- runtime: {
116
- ...DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime,
117
- ...options.runtime
118
- },
122
+ runtime: normalizeRuntimeOptions(options),
119
123
  diagnostics: {
120
124
  ...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,
121
125
  ...options.diagnostics
@@ -140,7 +144,13 @@ function validateLatencyOptions(options) {
140
144
  }
141
145
  function validateRuntimeOptions(options) {
142
146
  if (options.runtime.preferWorkerMse !== true) throwOptionError("unsupported", "RIVMUX_UNSUPPORTED_MAIN_THREAD_MSE_FALLBACK", "Main-thread MSE fallback is not implemented; runtime.preferWorkerMse must remain true.");
143
- if (options.runtime.wasmModule !== void 0) throwOptionError("unsupported", "RIVMUX_UNSUPPORTED_WASM_MODULE_OPTION", "runtime.wasmModule is reserved and is not implemented by the default runtime.");
147
+ }
148
+ function normalizeRuntimeOptions(options) {
149
+ return {
150
+ preferWorkerMse: options.runtime?.preferWorkerMse ?? DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime.preferWorkerMse,
151
+ ...options.runtime?.workerUrl === void 0 ? {} : { workerUrl: options.runtime.workerUrl },
152
+ ...options.runtime?.wasmUrl === void 0 ? {} : { wasmUrl: options.runtime.wasmUrl }
153
+ };
144
154
  }
145
155
  function assertFiniteNonNegativeLatency(value, field) {
146
156
  if (!Number.isFinite(value) || value < 0) throwOptionError("runtime", "RIVMUX_INVALID_LATENCY_OPTION", `${field} must be a finite number greater than or equal to 0.`);
@@ -160,14 +170,24 @@ function throwOptionError(kind, code, message) {
160
170
  * `start()`. Call `destroy()` when the instance is no longer needed.
161
171
  */
162
172
  var RivmuxPlayer = class {
173
+ /** Original stream URL passed to the constructor. */
174
+ url;
175
+ /** Fully normalized options with defaults applied. */
176
+ options;
177
+ id;
178
+ events = new PlayerEventEmitter();
179
+ workerFactory;
180
+ detectRuntime;
181
+ workerClient;
182
+ video;
183
+ videoStateTimer;
184
+ state = "idle";
163
185
  /**
164
186
  * Creates a player instance for one stream URL.
165
187
  *
166
188
  * The instance does not start network loading until `start()` is called.
167
189
  */
168
190
  constructor(url, options, internals = {}) {
169
- this.events = new PlayerEventEmitter();
170
- this.state = "idle";
171
191
  this.url = url;
172
192
  this.options = normalizePlayerOptions(options);
173
193
  this.id = internals.idFactory?.() ?? createPlayerId();
@@ -271,7 +291,7 @@ var RivmuxPlayer = class {
271
291
  type: "init",
272
292
  id: this.id,
273
293
  url: this.url,
274
- options: this.options
294
+ options: withBundledWasmUrl(this.options)
275
295
  });
276
296
  }
277
297
  handleWorkerMessage(message) {
@@ -404,6 +424,16 @@ function getDroppedFrames(video) {
404
424
  const webkitDroppedFrameCount = video.webkitDroppedFrameCount;
405
425
  return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : void 0;
406
426
  }
427
+ function withBundledWasmUrl(options) {
428
+ if (options.runtime.wasmUrl !== void 0) return options;
429
+ return {
430
+ ...options,
431
+ runtime: {
432
+ ...options.runtime,
433
+ wasmUrl: getBundledWasmUrl()
434
+ }
435
+ };
436
+ }
407
437
  //#endregion
408
438
  export { DEFAULT_RIVMUX_PLAYER_OPTIONS, RivmuxPlayer, createPlayerError, normalizePlayerOptions };
409
439
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/events.ts","../src/errors.ts","../src/feature-detect.ts","../src/options.ts","../src/player.ts"],"sourcesContent":["import type { PlayerEventListener, PlayerEventMap, PlayerEventType } from '@rivmux/protocol'\n\nexport class PlayerEventEmitter {\n private readonly listeners = new Map<PlayerEventType, Set<PlayerEventListener<PlayerEventType>>>()\n\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n const typedListener = listener as PlayerEventListener<PlayerEventType>\n const listeners = this.listeners.get(type)\n\n if (listeners === undefined) {\n this.listeners.set(type, new Set([typedListener]))\n return\n }\n\n listeners.add(typedListener)\n }\n\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.listeners.get(type)?.delete(listener as PlayerEventListener<PlayerEventType>)\n }\n\n emit<T extends PlayerEventType>(type: T, payload: PlayerEventMap[T]): void {\n for (const listener of this.listeners.get(type) ?? []) {\n listener(payload)\n }\n }\n\n clear(): void {\n this.listeners.clear()\n }\n}\n","import type { PlayerError, PlayerErrorKind } from '@rivmux/protocol'\n\nexport function createPlayerError(kind: PlayerErrorKind, code: string, message: string, terminal: boolean, cause?: unknown): PlayerError {\n return cause === undefined ? { kind, code, message, terminal } : { kind, code, message, terminal, cause }\n}\n\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function playerErrorToException(error: PlayerError): Error {\n const exception = toError(error.cause ?? error.message)\n exception.name = error.code\n return exception\n}\n","import { createPlayerError } from './errors'\n\nimport type { PlayerError } from '@rivmux/protocol'\n\nexport const M1_VIDEO_MIME = 'video/mp4; codecs=\"avc1.42C01E\"'\nexport const M1_AUDIO_MIME = 'audio/mp4; codecs=\"mp4a.40.2\"'\n\nexport function detectMainThreadRuntime(): PlayerError | undefined {\n if (typeof Worker === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER', 'Dedicated Worker is not available in this runtime.', true)\n }\n\n if (typeof fetch !== 'function') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_FETCH', 'Fetch is not available in this runtime.', true)\n }\n\n if (typeof ReadableStream === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_READABLE_STREAM', 'ReadableStream is not available in this runtime.', true)\n }\n\n if (typeof WebAssembly === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WASM', 'WebAssembly is not available in this runtime.', true)\n }\n\n if (typeof MediaSource === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE', 'MediaSource is not available in this runtime.', true)\n }\n\n if (MediaSource.canConstructInDedicatedWorker !== true) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER_MSE', 'MediaSource cannot be constructed in a dedicated worker in this runtime.', true)\n }\n\n if (typeof MediaSource.isTypeSupported !== 'function') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK', 'MediaSource.isTypeSupported is not available in this runtime.', true)\n }\n\n if (!MediaSource.isTypeSupported(M1_VIDEO_MIME)) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_M1_VIDEO_MIME', `MSE does not support ${M1_VIDEO_MIME}.`, true)\n }\n\n if (!MediaSource.isTypeSupported(M1_AUDIO_MIME)) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_M1_AUDIO_MIME', `MSE does not support ${M1_AUDIO_MIME}.`, true)\n }\n\n return undefined\n}\n","import { createPlayerError, playerErrorToException } from './errors'\n\nimport type { NormalizedRivmuxPlayerOptions, PlayerErrorKind, RivmuxPlayerOptions } from '@rivmux/protocol'\n\nexport const DEFAULT_RIVMUX_PLAYER_OPTIONS: NormalizedRivmuxPlayerOptions = {\n playback: {\n autoPlay: true,\n muted: false,\n },\n latency: {\n startupBuffer: 0.35,\n target: 1.2,\n max: 2.5,\n maxForwardBuffer: 4,\n backwardBuffer: 1.5,\n },\n network: {\n headers: {},\n credentials: 'same-origin',\n retry: {\n maxAttempts: 3,\n backoffMs: 500,\n },\n },\n runtime: {\n preferWorkerMse: true,\n },\n diagnostics: {\n statsIntervalMs: 1000,\n debug: false,\n },\n}\n\nexport function normalizePlayerOptions(options: RivmuxPlayerOptions = {}): NormalizedRivmuxPlayerOptions {\n const normalizedOptions = {\n playback: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.playback,\n ...options.playback,\n },\n latency: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.latency,\n ...options.latency,\n },\n network: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network,\n ...options.network,\n headers: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.headers,\n ...options.network?.headers,\n },\n retry: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.retry,\n ...options.network?.retry,\n },\n },\n runtime: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime,\n ...options.runtime,\n },\n diagnostics: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,\n ...options.diagnostics,\n },\n }\n\n validateNormalizedOptions(normalizedOptions)\n return normalizedOptions\n}\n\nfunction validateNormalizedOptions(options: NormalizedRivmuxPlayerOptions): void {\n validateLatencyOptions(options)\n validateRuntimeOptions(options)\n}\n\nfunction validateLatencyOptions(options: NormalizedRivmuxPlayerOptions): void {\n const latency = options.latency\n\n assertFiniteNonNegativeLatency(latency.startupBuffer, 'latency.startupBuffer')\n assertFinitePositiveLatency(latency.target, 'latency.target')\n assertFiniteNonNegativeLatency(latency.max, 'latency.max')\n assertFiniteNonNegativeLatency(latency.maxForwardBuffer, 'latency.maxForwardBuffer')\n assertFiniteNonNegativeLatency(latency.backwardBuffer, 'latency.backwardBuffer')\n\n if (latency.max < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.max must be greater than or equal to latency.target.')\n }\n\n if (latency.maxForwardBuffer < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.maxForwardBuffer must be greater than or equal to latency.target.')\n }\n}\n\nfunction validateRuntimeOptions(options: NormalizedRivmuxPlayerOptions): void {\n if (options.runtime.preferWorkerMse !== true) {\n throwOptionError(\n 'unsupported',\n 'RIVMUX_UNSUPPORTED_MAIN_THREAD_MSE_FALLBACK',\n 'Main-thread MSE fallback is not implemented; runtime.preferWorkerMse must remain true.'\n )\n }\n\n if (options.runtime.wasmModule !== undefined) {\n throwOptionError('unsupported', 'RIVMUX_UNSUPPORTED_WASM_MODULE_OPTION', 'runtime.wasmModule is reserved and is not implemented by the default runtime.')\n }\n}\n\nfunction assertFiniteNonNegativeLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than or equal to 0.`)\n }\n}\n\nfunction assertFinitePositiveLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than 0.`)\n }\n}\n\nfunction throwOptionError(kind: PlayerErrorKind, code: string, message: string): never {\n throw playerErrorToException(createPlayerError(kind, code, message, true))\n}\n","import { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { createRuntimeWorker, WorkerClient } from '@rivmux/runtime-worker'\n\nimport type {\n NormalizedRivmuxPlayerOptions,\n PlaybackControlAction,\n PlayerError,\n PlayerEventListener,\n PlayerEventType,\n RivmuxPlayerOptions,\n WorkerMessage,\n} from '@rivmux/protocol'\nimport type { RuntimeWorkerFactory } from '@rivmux/runtime-worker'\n\ntype PlayerState = 'idle' | 'attached' | 'started' | 'stopped' | 'destroyed'\n\nexport type RivmuxPlayerInternals = {\n workerFactory?: RuntimeWorkerFactory\n detectRuntime?: () => PlayerError | undefined\n idFactory?: () => string\n}\n\n/**\n * Public browser player facade for one HTTP-FLV stream.\n *\n * Create one instance per stream URL, call `attach(video)` first, then\n * `start()`. Call `destroy()` when the instance is no longer needed.\n */\nexport class RivmuxPlayer {\n /** Original stream URL passed to the constructor. */\n readonly url: string\n\n /** Fully normalized options with defaults applied. */\n readonly options: NormalizedRivmuxPlayerOptions\n private readonly id: string\n private readonly events = new PlayerEventEmitter()\n private readonly workerFactory: RuntimeWorkerFactory\n private readonly detectRuntime: () => PlayerError | undefined\n private workerClient?: WorkerClient\n private video?: HTMLVideoElement\n private videoStateTimer?: ReturnType<typeof setInterval>\n private state: PlayerState = 'idle'\n\n /**\n * Creates a player instance for one stream URL.\n *\n * The instance does not start network loading until `start()` is called.\n */\n constructor(url: string, options?: RivmuxPlayerOptions, internals: RivmuxPlayerInternals = {}) {\n this.url = url\n this.options = normalizePlayerOptions(options)\n this.id = internals.idFactory?.() ?? createPlayerId()\n this.workerFactory = internals.workerFactory ?? createRuntimeWorker\n this.detectRuntime = internals.detectRuntime ?? detectMainThreadRuntime\n }\n\n /**\n * Attaches this player to a video element and prepares the worker/MSE pipe.\n *\n * Await this method before calling `start()`.\n */\n async attach(video: HTMLVideoElement): Promise<void> {\n this.assertNotDestroyed('attach')\n\n if (this.video !== undefined && this.video !== video) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_ALREADY_ATTACHED', 'This player is already attached to a video element.', false))\n }\n\n const runtimeError = this.detectRuntime()\n if (runtimeError !== undefined) {\n this.events.emit('error', runtimeError)\n throw playerErrorToException(runtimeError)\n }\n\n this.video = video\n this.applyPlaybackOptions(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\n this.state = 'attached'\n }\n\n /**\n * Starts loading, transmuxing, buffering, and playback control.\n *\n * Requires a successful `attach(video)` call first.\n */\n async start(): Promise<void> {\n this.assertNotDestroyed('start')\n\n if (this.video === undefined || this.workerClient === undefined) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_START_REQUIRES_ATTACH', 'start() requires a previously attached video element.', false))\n }\n\n if (this.state === 'started') {\n return\n }\n\n if (this.state === 'stopped') {\n await this.workerClient.waitForMediaSourceHandle({ type: 'attach-media-source' })\n this.state = 'attached'\n }\n\n this.workerClient.post({ type: 'start' })\n this.state = 'started'\n this.startVideoStateReporting()\n }\n\n /**\n * Stops loading and detaches the current media source.\n *\n * The instance remains reusable; call `start()` again to restart the same\n * stream after the player has stopped.\n */\n async stop(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n if (this.workerClient === undefined) {\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n return\n }\n\n this.stopVideoStateReporting()\n await this.workerClient.waitForStopped({ type: 'stop' })\n this.detachVideoSource()\n this.state = 'stopped'\n }\n\n /**\n * Releases worker resources, timers, listeners, and the attached video source.\n *\n * The instance is terminal after destroy and cannot be attached or started\n * again.\n */\n async destroy(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n const workerClient = this.workerClient\n this.workerClient = undefined\n this.stopVideoStateReporting()\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n if (this.video !== undefined) {\n this.detachVideoSource()\n this.video = undefined\n }\n\n this.state = 'destroyed'\n this.events.clear()\n }\n\n /** Registers an event listener for a typed player event. */\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.on(type, listener)\n }\n\n /** Removes a previously registered event listener. */\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.off(type, listener)\n }\n\n private ensureWorkerClient(): void {\n if (this.workerClient !== undefined) {\n return\n }\n\n const worker = this.workerFactory(this.options)\n this.workerClient = new WorkerClient(worker, {\n onMessage: (message) => this.handleWorkerMessage(message),\n onError: (error) => this.events.emit('error', error),\n })\n this.workerClient.post({ type: 'init', id: this.id, url: this.url, options: this.options })\n }\n\n private handleWorkerMessage(message: WorkerMessage): void {\n if (this.state === 'destroyed') {\n return\n }\n\n switch (message.type) {\n case 'ready':\n this.events.emit('ready', undefined)\n return\n case 'media-source-handle':\n this.attachMediaSourceHandle(message.handle)\n return\n case 'media-info':\n this.events.emit('mediaInfo', message.mediaInfo)\n return\n case 'stats':\n this.events.emit('stats', message.stats)\n return\n case 'warning':\n this.events.emit('warning', message.warning)\n return\n case 'error':\n this.events.emit('error', message.error)\n return\n case 'playback-control':\n void this.applyPlaybackControl(message.action)\n return\n case 'stopped':\n this.events.emit('stopped', undefined)\n return\n case 'destroyed':\n this.events.emit('destroyed', undefined)\n return\n }\n }\n\n private attachMediaSourceHandle(handle: MediaSourceHandle): void {\n if (this.video === undefined) {\n const error = createPlayerError(\n 'runtime',\n 'RIVMUX_ATTACH_HANDLE_WITHOUT_VIDEO',\n 'Worker returned a MediaSourceHandle before a video element was attached.',\n true\n )\n this.events.emit('error', error)\n throw playerErrorToException(error)\n }\n\n Reflect.set(this.video, 'srcObject', handle)\n }\n\n private applyPlaybackOptions(video: HTMLVideoElement): void {\n video.muted = this.options.playback.muted\n video.autoplay = this.options.playback.autoPlay\n }\n\n private startVideoStateReporting(): void {\n this.stopVideoStateReporting()\n this.postVideoState()\n\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n this.videoStateTimer = setInterval(() => {\n this.postVideoState()\n }, intervalMs)\n }\n\n private stopVideoStateReporting(): void {\n if (this.videoStateTimer === undefined) {\n return\n }\n\n clearInterval(this.videoStateTimer)\n this.videoStateTimer = undefined\n }\n\n private postVideoState(): void {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const droppedFrames = getDroppedFrames(video)\n workerClient.post({\n type: 'video-state',\n state: {\n currentTime: Number.isFinite(video.currentTime) ? video.currentTime : 0,\n readyState: video.readyState,\n playbackRate: video.playbackRate,\n paused: video.paused,\n ...(droppedFrames === undefined ? {} : { droppedFrames }),\n },\n })\n }\n\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n try {\n switch (action.type) {\n case 'play':\n await video.play()\n break\n case 'set-playback-rate':\n video.playbackRate = action.playbackRate\n break\n case 'seek':\n video.currentTime = action.targetTime\n break\n }\n\n workerClient.post({ type: 'playback-control-result', result: { type: action.type, accepted: true } })\n this.postVideoState()\n } catch (cause) {\n workerClient.post({\n type: 'playback-control-result',\n result: {\n type: action.type,\n accepted: false,\n message: cause instanceof Error ? cause.message : String(cause),\n },\n })\n }\n }\n\n private detachVideoSource(): void {\n if (this.video === undefined) {\n return\n }\n\n this.video.pause()\n this.video.playbackRate = 1\n this.video.removeAttribute('src')\n this.video.srcObject = null\n this.video.load()\n }\n\n private assertNotDestroyed(method: string): void {\n if (this.state !== 'destroyed') {\n return\n }\n\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_PLAYER_DESTROYED', `RivmuxPlayer.${method}() cannot be called after destroy().`, true))\n }\n}\n\nfunction createPlayerId(): string {\n const random = globalThis.crypto?.randomUUID?.()\n return random ?? `rivmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n}\n\nfunction getDroppedFrames(video: HTMLVideoElement): number | undefined {\n const quality = video.getVideoPlaybackQuality?.()\n if (quality !== undefined && Number.isFinite(quality.droppedVideoFrames)) {\n return quality.droppedVideoFrames\n }\n\n const webkitDroppedFrameCount = (video as HTMLVideoElement & { webkitDroppedFrameCount?: number }).webkitDroppedFrameCount\n return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : undefined\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/events.ts","../src/errors.ts","../src/feature-detect.ts","../src/options.ts","../src/player.ts"],"sourcesContent":["import type { PlayerEventListener, PlayerEventMap, PlayerEventType } from '@rivmux/protocol'\n\nexport class PlayerEventEmitter {\n private readonly listeners = new Map<PlayerEventType, Set<PlayerEventListener<PlayerEventType>>>()\n\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n const typedListener = listener as PlayerEventListener<PlayerEventType>\n const listeners = this.listeners.get(type)\n\n if (listeners === undefined) {\n this.listeners.set(type, new Set([typedListener]))\n return\n }\n\n listeners.add(typedListener)\n }\n\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.listeners.get(type)?.delete(listener as PlayerEventListener<PlayerEventType>)\n }\n\n emit<T extends PlayerEventType>(type: T, payload: PlayerEventMap[T]): void {\n for (const listener of this.listeners.get(type) ?? []) {\n listener(payload)\n }\n }\n\n clear(): void {\n this.listeners.clear()\n }\n}\n","import type { PlayerError, PlayerErrorKind } from '@rivmux/protocol'\n\nexport function createPlayerError(kind: PlayerErrorKind, code: string, message: string, terminal: boolean, cause?: unknown): PlayerError {\n return cause === undefined ? { kind, code, message, terminal } : { kind, code, message, terminal, cause }\n}\n\nexport function toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value))\n}\n\nexport function playerErrorToException(error: PlayerError): Error {\n const exception = toError(error.cause ?? error.message)\n exception.name = error.code\n return exception\n}\n","import { createPlayerError } from './errors'\n\nimport type { PlayerError } from '@rivmux/protocol'\n\nexport type RequiredMseMimeType = {\n readonly mediaType: 'video' | 'audio'\n readonly mimeType: string\n readonly unsupportedCode: string\n}\n\nexport const REQUIRED_MSE_MIME_TYPES = [\n {\n mediaType: 'video',\n mimeType: createMp4Mime('video', 'avc1.42C01E'),\n unsupportedCode: 'RIVMUX_UNSUPPORTED_MSE_VIDEO_MIME',\n },\n {\n mediaType: 'audio',\n mimeType: createMp4Mime('audio', 'mp4a.40.2'),\n unsupportedCode: 'RIVMUX_UNSUPPORTED_MSE_AUDIO_MIME',\n },\n] as const satisfies readonly RequiredMseMimeType[]\n\nexport function detectMainThreadRuntime(): PlayerError | undefined {\n if (typeof Worker === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER', 'Dedicated Worker is not available in this runtime.', true)\n }\n\n if (typeof fetch !== 'function') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_FETCH', 'Fetch is not available in this runtime.', true)\n }\n\n if (typeof ReadableStream === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_READABLE_STREAM', 'ReadableStream is not available in this runtime.', true)\n }\n\n if (typeof WebAssembly === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WASM', 'WebAssembly is not available in this runtime.', true)\n }\n\n if (typeof MediaSource === 'undefined') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE', 'MediaSource is not available in this runtime.', true)\n }\n\n if (MediaSource.canConstructInDedicatedWorker !== true) {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_WORKER_MSE', 'MediaSource cannot be constructed in a dedicated worker in this runtime.', true)\n }\n\n if (typeof MediaSource.isTypeSupported !== 'function') {\n return createPlayerError('unsupported', 'RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK', 'MediaSource.isTypeSupported is not available in this runtime.', true)\n }\n\n for (const requirement of REQUIRED_MSE_MIME_TYPES) {\n if (!MediaSource.isTypeSupported(requirement.mimeType)) {\n return createPlayerError('unsupported', requirement.unsupportedCode, `MSE does not support ${requirement.mimeType}.`, true)\n }\n }\n\n return undefined\n}\n\nfunction createMp4Mime(mediaType: RequiredMseMimeType['mediaType'], codec: string): string {\n return `${mediaType}/mp4; codecs=\"${codec}\"`\n}\n","import { createPlayerError, playerErrorToException } from './errors'\n\nimport type { NormalizedRivmuxPlayerOptions, PlayerErrorKind, RivmuxPlayerOptions } from '@rivmux/protocol'\n\nexport const DEFAULT_RIVMUX_PLAYER_OPTIONS: NormalizedRivmuxPlayerOptions = {\n playback: {\n autoPlay: true,\n muted: false,\n },\n latency: {\n startupBuffer: 0.35,\n target: 1.2,\n max: 2.5,\n maxForwardBuffer: 4,\n backwardBuffer: 1.5,\n },\n network: {\n headers: {},\n credentials: 'same-origin',\n retry: {\n maxAttempts: 3,\n backoffMs: 500,\n },\n },\n runtime: {\n preferWorkerMse: true,\n },\n diagnostics: {\n statsIntervalMs: 1000,\n debug: false,\n },\n}\n\nexport function normalizePlayerOptions(options: RivmuxPlayerOptions = {}): NormalizedRivmuxPlayerOptions {\n const normalizedOptions = {\n playback: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.playback,\n ...options.playback,\n },\n latency: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.latency,\n ...options.latency,\n },\n network: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network,\n ...options.network,\n headers: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.headers,\n ...options.network?.headers,\n },\n retry: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.network.retry,\n ...options.network?.retry,\n },\n },\n runtime: normalizeRuntimeOptions(options),\n diagnostics: {\n ...DEFAULT_RIVMUX_PLAYER_OPTIONS.diagnostics,\n ...options.diagnostics,\n },\n }\n\n validateNormalizedOptions(normalizedOptions)\n return normalizedOptions\n}\n\nfunction validateNormalizedOptions(options: NormalizedRivmuxPlayerOptions): void {\n validateLatencyOptions(options)\n validateRuntimeOptions(options)\n}\n\nfunction validateLatencyOptions(options: NormalizedRivmuxPlayerOptions): void {\n const latency = options.latency\n\n assertFiniteNonNegativeLatency(latency.startupBuffer, 'latency.startupBuffer')\n assertFinitePositiveLatency(latency.target, 'latency.target')\n assertFiniteNonNegativeLatency(latency.max, 'latency.max')\n assertFiniteNonNegativeLatency(latency.maxForwardBuffer, 'latency.maxForwardBuffer')\n assertFiniteNonNegativeLatency(latency.backwardBuffer, 'latency.backwardBuffer')\n\n if (latency.max < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.max must be greater than or equal to latency.target.')\n }\n\n if (latency.maxForwardBuffer < latency.target) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', 'latency.maxForwardBuffer must be greater than or equal to latency.target.')\n }\n}\n\nfunction validateRuntimeOptions(options: NormalizedRivmuxPlayerOptions): void {\n if (options.runtime.preferWorkerMse !== true) {\n throwOptionError(\n 'unsupported',\n 'RIVMUX_UNSUPPORTED_MAIN_THREAD_MSE_FALLBACK',\n 'Main-thread MSE fallback is not implemented; runtime.preferWorkerMse must remain true.'\n )\n }\n}\n\nfunction normalizeRuntimeOptions(options: RivmuxPlayerOptions): NormalizedRivmuxPlayerOptions['runtime'] {\n return {\n preferWorkerMse: options.runtime?.preferWorkerMse ?? DEFAULT_RIVMUX_PLAYER_OPTIONS.runtime.preferWorkerMse,\n ...(options.runtime?.workerUrl === undefined ? {} : { workerUrl: options.runtime.workerUrl }),\n ...(options.runtime?.wasmUrl === undefined ? {} : { wasmUrl: options.runtime.wasmUrl }),\n }\n}\n\nfunction assertFiniteNonNegativeLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value < 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than or equal to 0.`)\n }\n}\n\nfunction assertFinitePositiveLatency(value: number, field: string): void {\n if (!Number.isFinite(value) || value <= 0) {\n throwOptionError('runtime', 'RIVMUX_INVALID_LATENCY_OPTION', `${field} must be a finite number greater than 0.`)\n }\n}\n\nfunction throwOptionError(kind: PlayerErrorKind, code: string, message: string): never {\n throw playerErrorToException(createPlayerError(kind, code, message, true))\n}\n","import { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { createRuntimeWorker, getBundledWasmUrl, WorkerClient } from '@rivmux/runtime-worker'\n\nimport type {\n NormalizedRivmuxPlayerOptions,\n PlaybackControlAction,\n PlayerError,\n PlayerEventListener,\n PlayerEventType,\n RivmuxPlayerOptions,\n WorkerMessage,\n} from '@rivmux/protocol'\nimport type { RuntimeWorkerFactory } from '@rivmux/runtime-worker'\n\ntype PlayerState = 'idle' | 'attached' | 'started' | 'stopped' | 'destroyed'\n\nexport type RivmuxPlayerInternals = {\n workerFactory?: RuntimeWorkerFactory\n detectRuntime?: () => PlayerError | undefined\n idFactory?: () => string\n}\n\n/**\n * Public browser player facade for one HTTP-FLV stream.\n *\n * Create one instance per stream URL, call `attach(video)` first, then\n * `start()`. Call `destroy()` when the instance is no longer needed.\n */\nexport class RivmuxPlayer {\n /** Original stream URL passed to the constructor. */\n readonly url: string\n\n /** Fully normalized options with defaults applied. */\n readonly options: NormalizedRivmuxPlayerOptions\n private readonly id: string\n private readonly events = new PlayerEventEmitter()\n private readonly workerFactory: RuntimeWorkerFactory\n private readonly detectRuntime: () => PlayerError | undefined\n private workerClient?: WorkerClient\n private video?: HTMLVideoElement\n private videoStateTimer?: ReturnType<typeof setInterval>\n private state: PlayerState = 'idle'\n\n /**\n * Creates a player instance for one stream URL.\n *\n * The instance does not start network loading until `start()` is called.\n */\n constructor(url: string, options?: RivmuxPlayerOptions, internals: RivmuxPlayerInternals = {}) {\n this.url = url\n this.options = normalizePlayerOptions(options)\n this.id = internals.idFactory?.() ?? createPlayerId()\n this.workerFactory = internals.workerFactory ?? createRuntimeWorker\n this.detectRuntime = internals.detectRuntime ?? detectMainThreadRuntime\n }\n\n /**\n * Attaches this player to a video element and prepares the worker/MSE pipe.\n *\n * Await this method before calling `start()`.\n */\n async attach(video: HTMLVideoElement): Promise<void> {\n this.assertNotDestroyed('attach')\n\n if (this.video !== undefined && this.video !== video) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_ALREADY_ATTACHED', 'This player is already attached to a video element.', false))\n }\n\n const runtimeError = this.detectRuntime()\n if (runtimeError !== undefined) {\n this.events.emit('error', runtimeError)\n throw playerErrorToException(runtimeError)\n }\n\n this.video = video\n this.applyPlaybackOptions(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\n this.state = 'attached'\n }\n\n /**\n * Starts loading, transmuxing, buffering, and playback control.\n *\n * Requires a successful `attach(video)` call first.\n */\n async start(): Promise<void> {\n this.assertNotDestroyed('start')\n\n if (this.video === undefined || this.workerClient === undefined) {\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_START_REQUIRES_ATTACH', 'start() requires a previously attached video element.', false))\n }\n\n if (this.state === 'started') {\n return\n }\n\n if (this.state === 'stopped') {\n await this.workerClient.waitForMediaSourceHandle({ type: 'attach-media-source' })\n this.state = 'attached'\n }\n\n this.workerClient.post({ type: 'start' })\n this.state = 'started'\n this.startVideoStateReporting()\n }\n\n /**\n * Stops loading and detaches the current media source.\n *\n * The instance remains reusable; call `start()` again to restart the same\n * stream after the player has stopped.\n */\n async stop(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n if (this.workerClient === undefined) {\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n return\n }\n\n this.stopVideoStateReporting()\n await this.workerClient.waitForStopped({ type: 'stop' })\n this.detachVideoSource()\n this.state = 'stopped'\n }\n\n /**\n * Releases worker resources, timers, listeners, and the attached video source.\n *\n * The instance is terminal after destroy and cannot be attached or started\n * again.\n */\n async destroy(): Promise<void> {\n if (this.state === 'destroyed') {\n return\n }\n\n const workerClient = this.workerClient\n this.workerClient = undefined\n this.stopVideoStateReporting()\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n if (this.video !== undefined) {\n this.detachVideoSource()\n this.video = undefined\n }\n\n this.state = 'destroyed'\n this.events.clear()\n }\n\n /** Registers an event listener for a typed player event. */\n on<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.on(type, listener)\n }\n\n /** Removes a previously registered event listener. */\n off<T extends PlayerEventType>(type: T, listener: PlayerEventListener<T>): void {\n this.events.off(type, listener)\n }\n\n private ensureWorkerClient(): void {\n if (this.workerClient !== undefined) {\n return\n }\n\n const worker = this.workerFactory(this.options)\n this.workerClient = new WorkerClient(worker, {\n onMessage: (message) => this.handleWorkerMessage(message),\n onError: (error) => this.events.emit('error', error),\n })\n this.workerClient.post({ type: 'init', id: this.id, url: this.url, options: withBundledWasmUrl(this.options) })\n }\n\n private handleWorkerMessage(message: WorkerMessage): void {\n if (this.state === 'destroyed') {\n return\n }\n\n switch (message.type) {\n case 'ready':\n this.events.emit('ready', undefined)\n return\n case 'media-source-handle':\n this.attachMediaSourceHandle(message.handle)\n return\n case 'media-info':\n this.events.emit('mediaInfo', message.mediaInfo)\n return\n case 'stats':\n this.events.emit('stats', message.stats)\n return\n case 'warning':\n this.events.emit('warning', message.warning)\n return\n case 'error':\n this.events.emit('error', message.error)\n return\n case 'playback-control':\n void this.applyPlaybackControl(message.action)\n return\n case 'stopped':\n this.events.emit('stopped', undefined)\n return\n case 'destroyed':\n this.events.emit('destroyed', undefined)\n return\n }\n }\n\n private attachMediaSourceHandle(handle: MediaSourceHandle): void {\n if (this.video === undefined) {\n const error = createPlayerError(\n 'runtime',\n 'RIVMUX_ATTACH_HANDLE_WITHOUT_VIDEO',\n 'Worker returned a MediaSourceHandle before a video element was attached.',\n true\n )\n this.events.emit('error', error)\n throw playerErrorToException(error)\n }\n\n Reflect.set(this.video, 'srcObject', handle)\n }\n\n private applyPlaybackOptions(video: HTMLVideoElement): void {\n video.muted = this.options.playback.muted\n video.autoplay = this.options.playback.autoPlay\n }\n\n private startVideoStateReporting(): void {\n this.stopVideoStateReporting()\n this.postVideoState()\n\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n this.videoStateTimer = setInterval(() => {\n this.postVideoState()\n }, intervalMs)\n }\n\n private stopVideoStateReporting(): void {\n if (this.videoStateTimer === undefined) {\n return\n }\n\n clearInterval(this.videoStateTimer)\n this.videoStateTimer = undefined\n }\n\n private postVideoState(): void {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const droppedFrames = getDroppedFrames(video)\n workerClient.post({\n type: 'video-state',\n state: {\n currentTime: Number.isFinite(video.currentTime) ? video.currentTime : 0,\n readyState: video.readyState,\n playbackRate: video.playbackRate,\n paused: video.paused,\n ...(droppedFrames === undefined ? {} : { droppedFrames }),\n },\n })\n }\n\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const video = this.video\n const workerClient = this.workerClient\n if (video === undefined || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n try {\n switch (action.type) {\n case 'play':\n await video.play()\n break\n case 'set-playback-rate':\n video.playbackRate = action.playbackRate\n break\n case 'seek':\n video.currentTime = action.targetTime\n break\n }\n\n workerClient.post({ type: 'playback-control-result', result: { type: action.type, accepted: true } })\n this.postVideoState()\n } catch (cause) {\n workerClient.post({\n type: 'playback-control-result',\n result: {\n type: action.type,\n accepted: false,\n message: cause instanceof Error ? cause.message : String(cause),\n },\n })\n }\n }\n\n private detachVideoSource(): void {\n if (this.video === undefined) {\n return\n }\n\n this.video.pause()\n this.video.playbackRate = 1\n this.video.removeAttribute('src')\n this.video.srcObject = null\n this.video.load()\n }\n\n private assertNotDestroyed(method: string): void {\n if (this.state !== 'destroyed') {\n return\n }\n\n throw playerErrorToException(createPlayerError('runtime', 'RIVMUX_PLAYER_DESTROYED', `RivmuxPlayer.${method}() cannot be called after destroy().`, true))\n }\n}\n\nfunction createPlayerId(): string {\n const random = globalThis.crypto?.randomUUID?.()\n return random ?? `rivmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`\n}\n\nfunction getDroppedFrames(video: HTMLVideoElement): number | undefined {\n const quality = video.getVideoPlaybackQuality?.()\n if (quality !== undefined && Number.isFinite(quality.droppedVideoFrames)) {\n return quality.droppedVideoFrames\n }\n\n const webkitDroppedFrameCount = (video as HTMLVideoElement & { webkitDroppedFrameCount?: number }).webkitDroppedFrameCount\n return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : undefined\n}\n\nfunction withBundledWasmUrl(options: NormalizedRivmuxPlayerOptions): NormalizedRivmuxPlayerOptions {\n if (options.runtime.wasmUrl !== undefined) {\n return options\n }\n\n return {\n ...options,\n runtime: {\n ...options.runtime,\n wasmUrl: getBundledWasmUrl(),\n },\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,16 @@
1
1
  {
2
2
  "name": "rivmux",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Public player package for Rivmux.",
5
+ "homepage": "https://www.npmjs.com/package/rivmux",
6
+ "bugs": {
7
+ "url": "https://github.com/yangxu52/Rivmux/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/yangxu52/Rivmux.git",
12
+ "directory": "packages/player"
13
+ },
5
14
  "license": "Apache-2.0",
6
15
  "type": "module",
7
16
  "sideEffects": false,
@@ -21,13 +30,13 @@
21
30
  "module": "./dist/index.js",
22
31
  "types": "./dist/index.d.ts",
23
32
  "dependencies": {
24
- "@rivmux/runtime-worker": "0.2.0",
25
- "@rivmux/protocol": "0.2.0"
33
+ "@rivmux/runtime-worker": "0.4.0",
34
+ "@rivmux/protocol": "0.4.0"
26
35
  },
27
36
  "devDependencies": {
28
- "tsdown": "^0.22.3",
29
- "typescript": "~5.9.3",
30
- "vitest": "^4.1.9"
37
+ "tsdown": "^0.22.7",
38
+ "typescript": "~6.0.3",
39
+ "vitest": "^4.1.10"
31
40
  },
32
41
  "engines": {
33
42
  "node": "^22.18.0 || >=24.11.0",