rivmux 0.4.0 → 0.5.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
@@ -8,6 +8,10 @@ browser `<video>` element.
8
8
 
9
9
  M1 supports only the dedicated-worker MSE runtime path. Main-thread MSE is not available.
10
10
 
11
+ The first-release support contract is HTTP-FLV with H.264/AVC video and optional
12
+ AAC-LC audio. MPEG-TS is roadmap work. Existing HEVC, AV1, and Opus core paths
13
+ are experimental internals and are not supported public inputs.
14
+
11
15
  ## Install
12
16
 
13
17
  ```sh
package/dist/index.d.ts CHANGED
@@ -21,10 +21,13 @@ declare class RivmuxPlayer {
21
21
  private readonly events;
22
22
  private readonly workerFactory;
23
23
  private readonly detectRuntime;
24
+ private readonly playback;
24
25
  private workerClient?;
25
- private video?;
26
- private videoStateTimer?;
27
26
  private state;
27
+ private lifecycleGeneration;
28
+ private terminalError?;
29
+ private stopPromise?;
30
+ private destroyPromise?;
28
31
  /**
29
32
  * Creates a player instance for one stream URL.
30
33
  *
@@ -50,6 +53,7 @@ declare class RivmuxPlayer {
50
53
  * stream after the player has stopped.
51
54
  */
52
55
  stop(): Promise<void>;
56
+ private performStop;
53
57
  /**
54
58
  * Releases worker resources, timers, listeners, and the attached video source.
55
59
  *
@@ -57,6 +61,7 @@ declare class RivmuxPlayer {
57
61
  * again.
58
62
  */
59
63
  destroy(): Promise<void>;
64
+ private performDestroy;
60
65
  /** Registers an event listener for a typed player event. */
61
66
  on<T extends PlayerEventType$1>(type: T, listener: PlayerEventListener$1<T>): void;
62
67
  /** Removes a previously registered event listener. */
@@ -64,13 +69,11 @@ declare class RivmuxPlayer {
64
69
  private ensureWorkerClient;
65
70
  private handleWorkerMessage;
66
71
  private attachMediaSourceHandle;
67
- private applyPlaybackOptions;
68
72
  private startVideoStateReporting;
69
- private stopVideoStateReporting;
70
- private postVideoState;
71
73
  private applyPlaybackControl;
72
- private detachVideoSource;
73
- private assertNotDestroyed;
74
+ private enterFatalErrorState;
75
+ private assertOperational;
76
+ private isLifecycleOperationCurrent;
74
77
  }
75
78
  //#endregion
76
79
  //#region src/options.d.ts
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/player.ts","../src/options.ts","../src/errors.ts"],"mappings":";;;KAoBY;EACV,gBAAgB;EAChB,sBAAsB;EACtB;;;;;;;;cASW;;WAEF;;WAGA,SAAS;mBACD;mBACA;mBACA;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;;;;;;cAOI,aAAa,UAAU,uBAAqB,YAAW;;;;;;EAc7D,OAAO,OAAO,mBAAmB;;;;;;EA4BjC,SAAS;;;;;;;EAkCf,QAAQ;UAyBM;;;;;;;EAgCR,WAAW;UAeH;;EA6Bd,GAAG,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;;EAKrE,IAAI,UAAU,mBAAiB,MAAM,GAAG,UAAU,sBAAoB;UAI9D;UAgBA;UA2CA;UAaA;UAcM;UAcN;UAWA;UAmBA;;;;cC/WG,+BAA+B;iBA6B5B,uBAAuB,UAAS,wBAA2B;;;iBC/B3D,kBAAkB,MAAM,mBAAiB,cAAc,iBAAiB,mBAAmB,kBAAkB"}
package/dist/index.js CHANGED
@@ -37,25 +37,13 @@ function createPlayerError(kind, code, message, terminal, cause) {
37
37
  cause
38
38
  };
39
39
  }
40
- function toError(value) {
41
- return value instanceof Error ? value : new Error(String(value));
42
- }
43
40
  function playerErrorToException(error) {
44
- const exception = toError(error.cause ?? error.message);
41
+ const exception = error.cause instanceof Error ? error.cause : new Error(error.message);
45
42
  exception.name = error.code;
46
43
  return exception;
47
44
  }
48
45
  //#endregion
49
46
  //#region src/feature-detect.ts
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
- }];
59
47
  function detectMainThreadRuntime() {
60
48
  if (typeof Worker === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER", "Dedicated Worker is not available in this runtime.", true);
61
49
  if (typeof fetch !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_FETCH", "Fetch is not available in this runtime.", true);
@@ -64,10 +52,6 @@ function detectMainThreadRuntime() {
64
52
  if (typeof MediaSource === "undefined") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE", "MediaSource is not available in this runtime.", true);
65
53
  if (MediaSource.canConstructInDedicatedWorker !== true) return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_WORKER_MSE", "MediaSource cannot be constructed in a dedicated worker in this runtime.", true);
66
54
  if (typeof MediaSource.isTypeSupported !== "function") return createPlayerError("unsupported", "RIVMUX_UNSUPPORTED_MSE_TYPE_CHECK", "MediaSource.isTypeSupported is not available in this runtime.", 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}"`;
71
55
  }
72
56
  //#endregion
73
57
  //#region src/options.ts
@@ -162,6 +146,109 @@ function throwOptionError(kind, code, message) {
162
146
  throw playerErrorToException(createPlayerError(kind, code, message, true));
163
147
  }
164
148
  //#endregion
149
+ //#region src/playback-controller.ts
150
+ var PlaybackController = class {
151
+ options;
152
+ video;
153
+ stateTimer;
154
+ constructor(options) {
155
+ this.options = options;
156
+ }
157
+ get attached() {
158
+ return this.video !== void 0;
159
+ }
160
+ isAttachedTo(video) {
161
+ return this.video === video;
162
+ }
163
+ attach(video) {
164
+ this.video = video;
165
+ video.muted = this.options.muted;
166
+ video.autoplay = this.options.autoPlay;
167
+ }
168
+ attachMediaSourceHandle(handle) {
169
+ if (this.video === void 0) return false;
170
+ Reflect.set(this.video, "srcObject", handle);
171
+ return true;
172
+ }
173
+ startStateReporting(intervalMs, report) {
174
+ this.stopStateReporting();
175
+ const reportCurrentState = () => {
176
+ const state = this.snapshot();
177
+ if (state !== void 0) report(state);
178
+ };
179
+ reportCurrentState();
180
+ this.stateTimer = setInterval(reportCurrentState, intervalMs);
181
+ }
182
+ stopStateReporting() {
183
+ if (this.stateTimer !== void 0) {
184
+ clearInterval(this.stateTimer);
185
+ this.stateTimer = void 0;
186
+ }
187
+ }
188
+ async applyControl(action) {
189
+ const video = this.video;
190
+ if (video === void 0) return {
191
+ type: action.type,
192
+ accepted: false,
193
+ message: "No video element is attached."
194
+ };
195
+ try {
196
+ switch (action.type) {
197
+ case "play":
198
+ await video.play();
199
+ break;
200
+ case "set-playback-rate":
201
+ video.playbackRate = action.playbackRate;
202
+ break;
203
+ case "seek":
204
+ video.currentTime = action.targetTime;
205
+ break;
206
+ }
207
+ return {
208
+ type: action.type,
209
+ accepted: true
210
+ };
211
+ } catch (cause) {
212
+ return {
213
+ type: action.type,
214
+ accepted: false,
215
+ message: cause instanceof Error ? cause.message : String(cause)
216
+ };
217
+ }
218
+ }
219
+ detachSource() {
220
+ if (this.video === void 0) return;
221
+ this.video.pause();
222
+ this.video.playbackRate = 1;
223
+ this.video.removeAttribute("src");
224
+ this.video.srcObject = null;
225
+ this.video.load();
226
+ }
227
+ release() {
228
+ this.stopStateReporting();
229
+ this.detachSource();
230
+ this.video = void 0;
231
+ }
232
+ snapshot() {
233
+ const video = this.video;
234
+ if (video === void 0) return;
235
+ const droppedFrames = getDroppedFrames(video);
236
+ return {
237
+ currentTime: Number.isFinite(video.currentTime) ? video.currentTime : 0,
238
+ readyState: video.readyState,
239
+ playbackRate: video.playbackRate,
240
+ paused: video.paused,
241
+ ...droppedFrames === void 0 ? {} : { droppedFrames }
242
+ };
243
+ }
244
+ };
245
+ function getDroppedFrames(video) {
246
+ const quality = video.getVideoPlaybackQuality?.();
247
+ if (quality !== void 0 && Number.isFinite(quality.droppedVideoFrames)) return quality.droppedVideoFrames;
248
+ const webkitDroppedFrameCount = video.webkitDroppedFrameCount;
249
+ return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : void 0;
250
+ }
251
+ //#endregion
165
252
  //#region src/player.ts
166
253
  /**
167
254
  * Public browser player facade for one HTTP-FLV stream.
@@ -178,10 +265,13 @@ var RivmuxPlayer = class {
178
265
  events = new PlayerEventEmitter();
179
266
  workerFactory;
180
267
  detectRuntime;
268
+ playback;
181
269
  workerClient;
182
- video;
183
- videoStateTimer;
184
270
  state = "idle";
271
+ lifecycleGeneration = 0;
272
+ terminalError;
273
+ stopPromise;
274
+ destroyPromise;
185
275
  /**
186
276
  * Creates a player instance for one stream URL.
187
277
  *
@@ -193,6 +283,7 @@ var RivmuxPlayer = class {
193
283
  this.id = internals.idFactory?.() ?? createPlayerId();
194
284
  this.workerFactory = internals.workerFactory ?? createRuntimeWorker;
195
285
  this.detectRuntime = internals.detectRuntime ?? detectMainThreadRuntime;
286
+ this.playback = new PlaybackController(this.options.playback);
196
287
  }
197
288
  /**
198
289
  * Attaches this player to a video element and prepares the worker/MSE pipe.
@@ -200,17 +291,18 @@ var RivmuxPlayer = class {
200
291
  * Await this method before calling `start()`.
201
292
  */
202
293
  async attach(video) {
203
- this.assertNotDestroyed("attach");
204
- if (this.video !== void 0 && this.video !== video) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_ALREADY_ATTACHED", "This player is already attached to a video element.", false));
294
+ this.assertOperational("attach");
295
+ const lifecycleGeneration = this.lifecycleGeneration;
296
+ if (this.playback.attached && !this.playback.isAttachedTo(video)) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_ALREADY_ATTACHED", "This player is already attached to a video element.", false));
205
297
  const runtimeError = this.detectRuntime();
206
298
  if (runtimeError !== void 0) {
207
299
  this.events.emit("error", runtimeError);
208
300
  throw playerErrorToException(runtimeError);
209
301
  }
210
- this.video = video;
211
- this.applyPlaybackOptions(video);
302
+ this.playback.attach(video);
212
303
  this.ensureWorkerClient();
213
304
  await this.workerClient?.waitForMediaSourceHandle({ type: "attach-media-source" });
305
+ if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
214
306
  this.state = "attached";
215
307
  }
216
308
  /**
@@ -219,13 +311,16 @@ var RivmuxPlayer = class {
219
311
  * Requires a successful `attach(video)` call first.
220
312
  */
221
313
  async start() {
222
- this.assertNotDestroyed("start");
223
- if (this.video === void 0 || this.workerClient === void 0) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_START_REQUIRES_ATTACH", "start() requires a previously attached video element.", false));
314
+ this.assertOperational("start");
315
+ const lifecycleGeneration = this.lifecycleGeneration;
316
+ if (!this.playback.attached || this.workerClient === void 0) throw playerErrorToException(createPlayerError("runtime", "RIVMUX_START_REQUIRES_ATTACH", "start() requires a previously attached video element.", false));
224
317
  if (this.state === "started") return;
225
318
  if (this.state === "stopped") {
226
319
  await this.workerClient.waitForMediaSourceHandle({ type: "attach-media-source" });
320
+ if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
227
321
  this.state = "attached";
228
322
  }
323
+ if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
229
324
  this.workerClient.post({ type: "start" });
230
325
  this.state = "started";
231
326
  this.startVideoStateReporting();
@@ -236,16 +331,37 @@ var RivmuxPlayer = class {
236
331
  * The instance remains reusable; call `start()` again to restart the same
237
332
  * stream after the player has stopped.
238
333
  */
239
- async stop() {
240
- if (this.state === "destroyed") return;
241
- if (this.workerClient === void 0) {
242
- this.state = "stopped";
243
- this.events.emit("stopped", void 0);
334
+ stop() {
335
+ if (this.state === "destroyed" || this.state === "destroying") return Promise.resolve();
336
+ if (this.state === "fatal-error") return Promise.resolve();
337
+ if (this.stopPromise !== void 0) return this.stopPromise;
338
+ this.lifecycleGeneration += 1;
339
+ this.state = "stopping";
340
+ const lifecycleGeneration = this.lifecycleGeneration;
341
+ const stopping = this.performStop(lifecycleGeneration).finally(() => {
342
+ if (this.stopPromise === stopping) this.stopPromise = void 0;
343
+ });
344
+ this.stopPromise = stopping;
345
+ return stopping;
346
+ }
347
+ async performStop(lifecycleGeneration) {
348
+ const workerClient = this.workerClient;
349
+ if (workerClient === void 0) {
350
+ if (this.isLifecycleOperationCurrent(lifecycleGeneration)) {
351
+ this.state = "stopped";
352
+ this.events.emit("stopped", void 0);
353
+ }
244
354
  return;
245
355
  }
246
- this.stopVideoStateReporting();
247
- await this.workerClient.waitForStopped({ type: "stop" });
248
- this.detachVideoSource();
356
+ this.playback.stopStateReporting();
357
+ try {
358
+ await workerClient.waitForStopped({ type: "stop" });
359
+ } catch (error) {
360
+ if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
361
+ throw error;
362
+ }
363
+ if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) return;
364
+ this.playback.detachSource();
249
365
  this.state = "stopped";
250
366
  }
251
367
  /**
@@ -256,21 +372,29 @@ var RivmuxPlayer = class {
256
372
  */
257
373
  async destroy() {
258
374
  if (this.state === "destroyed") return;
375
+ if (this.destroyPromise !== void 0) return this.destroyPromise;
376
+ this.lifecycleGeneration += 1;
377
+ this.state = "destroying";
378
+ this.destroyPromise = this.performDestroy();
379
+ return this.destroyPromise;
380
+ }
381
+ async performDestroy() {
259
382
  const workerClient = this.workerClient;
260
383
  this.workerClient = void 0;
261
- this.stopVideoStateReporting();
384
+ this.playback.stopStateReporting();
385
+ let destroyError;
262
386
  if (workerClient !== void 0) try {
263
387
  await workerClient.waitForDestroyed({ type: "destroy" });
388
+ } catch (error) {
389
+ destroyError = error;
264
390
  } finally {
265
391
  workerClient.dispose();
266
392
  }
267
393
  else this.events.emit("destroyed", void 0);
268
- if (this.video !== void 0) {
269
- this.detachVideoSource();
270
- this.video = void 0;
271
- }
394
+ this.playback.release();
272
395
  this.state = "destroyed";
273
396
  this.events.clear();
397
+ if (destroyError !== void 0) throw destroyError;
274
398
  }
275
399
  /** Registers an event listener for a typed player event. */
276
400
  on(type, listener) {
@@ -285,7 +409,10 @@ var RivmuxPlayer = class {
285
409
  const worker = this.workerFactory(this.options);
286
410
  this.workerClient = new WorkerClient(worker, {
287
411
  onMessage: (message) => this.handleWorkerMessage(message),
288
- onError: (error) => this.events.emit("error", error)
412
+ onError: (error) => {
413
+ this.enterFatalErrorState(error);
414
+ this.events.emit("error", error);
415
+ }
289
416
  });
290
417
  this.workerClient.post({
291
418
  type: "init",
@@ -296,6 +423,7 @@ var RivmuxPlayer = class {
296
423
  }
297
424
  handleWorkerMessage(message) {
298
425
  if (this.state === "destroyed") return;
426
+ if (this.state === "destroying" && message.type !== "destroyed" || this.state === "fatal-error") return;
299
427
  switch (message.type) {
300
428
  case "ready":
301
429
  this.events.emit("ready", void 0);
@@ -313,6 +441,7 @@ var RivmuxPlayer = class {
313
441
  this.events.emit("warning", message.warning);
314
442
  return;
315
443
  case "error":
444
+ if (message.error.terminal) this.enterFatalErrorState(message.error);
316
445
  this.events.emit("error", message.error);
317
446
  return;
318
447
  case "playback-control":
@@ -327,103 +456,54 @@ var RivmuxPlayer = class {
327
456
  }
328
457
  }
329
458
  attachMediaSourceHandle(handle) {
330
- if (this.video === void 0) {
459
+ if (!this.playback.attachMediaSourceHandle(handle)) {
331
460
  const error = createPlayerError("runtime", "RIVMUX_ATTACH_HANDLE_WITHOUT_VIDEO", "Worker returned a MediaSourceHandle before a video element was attached.", true);
332
461
  this.events.emit("error", error);
333
462
  throw playerErrorToException(error);
334
463
  }
335
- Reflect.set(this.video, "srcObject", handle);
336
- }
337
- applyPlaybackOptions(video) {
338
- video.muted = this.options.playback.muted;
339
- video.autoplay = this.options.playback.autoPlay;
340
464
  }
341
465
  startVideoStateReporting() {
342
- this.stopVideoStateReporting();
343
- this.postVideoState();
344
466
  const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250));
345
- this.videoStateTimer = setInterval(() => {
346
- this.postVideoState();
347
- }, intervalMs);
348
- }
349
- stopVideoStateReporting() {
350
- if (this.videoStateTimer === void 0) return;
351
- clearInterval(this.videoStateTimer);
352
- this.videoStateTimer = void 0;
353
- }
354
- postVideoState() {
355
- const video = this.video;
356
467
  const workerClient = this.workerClient;
357
- if (video === void 0 || workerClient === void 0 || this.state !== "started") return;
358
- const droppedFrames = getDroppedFrames(video);
359
- workerClient.post({
360
- type: "video-state",
361
- state: {
362
- currentTime: Number.isFinite(video.currentTime) ? video.currentTime : 0,
363
- readyState: video.readyState,
364
- playbackRate: video.playbackRate,
365
- paused: video.paused,
366
- ...droppedFrames === void 0 ? {} : { droppedFrames }
367
- }
468
+ if (workerClient === void 0) return;
469
+ this.playback.startStateReporting(intervalMs, (state) => {
470
+ if (this.state === "started" && this.workerClient === workerClient) workerClient.post({
471
+ type: "video-state",
472
+ state
473
+ });
368
474
  });
369
475
  }
370
476
  async applyPlaybackControl(action) {
371
- const video = this.video;
372
477
  const workerClient = this.workerClient;
373
- if (video === void 0 || workerClient === void 0 || this.state !== "started") return;
374
- try {
375
- switch (action.type) {
376
- case "play":
377
- await video.play();
378
- break;
379
- case "set-playback-rate":
380
- video.playbackRate = action.playbackRate;
381
- break;
382
- case "seek":
383
- video.currentTime = action.targetTime;
384
- break;
385
- }
386
- workerClient.post({
387
- type: "playback-control-result",
388
- result: {
389
- type: action.type,
390
- accepted: true
391
- }
392
- });
393
- this.postVideoState();
394
- } catch (cause) {
395
- workerClient.post({
396
- type: "playback-control-result",
397
- result: {
398
- type: action.type,
399
- accepted: false,
400
- message: cause instanceof Error ? cause.message : String(cause)
401
- }
402
- });
403
- }
478
+ const lifecycleGeneration = this.lifecycleGeneration;
479
+ if (!this.playback.attached || workerClient === void 0 || this.state !== "started") return;
480
+ const result = await this.playback.applyControl(action);
481
+ if (!this.isLifecycleOperationCurrent(lifecycleGeneration) || this.state !== "started" || this.workerClient !== workerClient) return;
482
+ workerClient.post({
483
+ type: "playback-control-result",
484
+ result
485
+ });
404
486
  }
405
- detachVideoSource() {
406
- if (this.video === void 0) return;
407
- this.video.pause();
408
- this.video.playbackRate = 1;
409
- this.video.removeAttribute("src");
410
- this.video.srcObject = null;
411
- this.video.load();
487
+ enterFatalErrorState(error) {
488
+ if (this.state === "destroyed" || this.state === "destroying" || this.state === "fatal-error") return;
489
+ this.lifecycleGeneration += 1;
490
+ this.terminalError = error;
491
+ this.state = "fatal-error";
492
+ this.playback.release();
493
+ }
494
+ assertOperational(method) {
495
+ if (this.state === "fatal-error" && this.terminalError !== void 0) throw playerErrorToException(this.terminalError);
496
+ if (this.state === "stopping") throw playerErrorToException(createPlayerError("runtime", "RIVMUX_PLAYER_STOPPING", `RivmuxPlayer.${method}() cannot be called while stop() is pending.`, false));
497
+ if (this.state !== "destroyed" && this.state !== "destroying") return;
498
+ throw playerErrorToException(createPlayerError("runtime", this.state === "destroying" ? "RIVMUX_PLAYER_DESTROYING" : "RIVMUX_PLAYER_DESTROYED", `RivmuxPlayer.${method}() cannot be called after destroy() begins.`, true));
412
499
  }
413
- assertNotDestroyed(method) {
414
- if (this.state !== "destroyed") return;
415
- throw playerErrorToException(createPlayerError("runtime", "RIVMUX_PLAYER_DESTROYED", `RivmuxPlayer.${method}() cannot be called after destroy().`, true));
500
+ isLifecycleOperationCurrent(generation) {
501
+ return generation === this.lifecycleGeneration && this.state !== "fatal-error" && this.state !== "destroying" && this.state !== "destroyed";
416
502
  }
417
503
  };
418
504
  function createPlayerId() {
419
505
  return globalThis.crypto?.randomUUID?.() ?? `rivmux-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
420
506
  }
421
- function getDroppedFrames(video) {
422
- const quality = video.getVideoPlaybackQuality?.();
423
- if (quality !== void 0 && Number.isFinite(quality.droppedVideoFrames)) return quality.droppedVideoFrames;
424
- const webkitDroppedFrameCount = video.webkitDroppedFrameCount;
425
- return Number.isFinite(webkitDroppedFrameCount) ? webkitDroppedFrameCount : void 0;
426
- }
427
507
  function withBundledWasmUrl(options) {
428
508
  if (options.runtime.wasmUrl !== void 0) return options;
429
509
  return {
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 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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/events.ts","../src/errors.ts","../src/feature-detect.ts","../src/options.ts","../src/playback-controller.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 = error.cause instanceof Error ? error.cause : new Error(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 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 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: 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 type { NormalizedPlaybackOptions, PlaybackControlAction, PlaybackControlResult, VideoElementState } from '@rivmux/protocol'\n\nexport class PlaybackController {\n private readonly options: NormalizedPlaybackOptions\n private video?: HTMLVideoElement\n private stateTimer?: ReturnType<typeof setInterval>\n\n constructor(options: NormalizedPlaybackOptions) {\n this.options = options\n }\n\n get attached(): boolean {\n return this.video !== undefined\n }\n\n isAttachedTo(video: HTMLVideoElement): boolean {\n return this.video === video\n }\n\n attach(video: HTMLVideoElement): void {\n this.video = video\n video.muted = this.options.muted\n video.autoplay = this.options.autoPlay\n }\n\n attachMediaSourceHandle(handle: MediaSourceHandle): boolean {\n if (this.video === undefined) {\n return false\n }\n\n Reflect.set(this.video, 'srcObject', handle)\n return true\n }\n\n startStateReporting(intervalMs: number, report: (state: VideoElementState) => void): void {\n this.stopStateReporting()\n const reportCurrentState = (): void => {\n const state = this.snapshot()\n if (state !== undefined) {\n report(state)\n }\n }\n\n reportCurrentState()\n this.stateTimer = setInterval(reportCurrentState, intervalMs)\n }\n\n stopStateReporting(): void {\n if (this.stateTimer !== undefined) {\n clearInterval(this.stateTimer)\n this.stateTimer = undefined\n }\n }\n\n async applyControl(action: PlaybackControlAction): Promise<PlaybackControlResult> {\n const video = this.video\n if (video === undefined) {\n return { type: action.type, accepted: false, message: 'No video element is attached.' }\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 return { type: action.type, accepted: true }\n } catch (cause) {\n return {\n type: action.type,\n accepted: false,\n message: cause instanceof Error ? cause.message : String(cause),\n }\n }\n }\n\n detachSource(): 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 release(): void {\n this.stopStateReporting()\n this.detachSource()\n this.video = undefined\n }\n\n private snapshot(): VideoElementState | undefined {\n const video = this.video\n if (video === undefined) {\n return undefined\n }\n\n const droppedFrames = getDroppedFrames(video)\n return {\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\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","import { PlayerEventEmitter } from './events'\nimport { createPlayerError, playerErrorToException } from './errors'\nimport { detectMainThreadRuntime } from './feature-detect'\nimport { normalizePlayerOptions } from './options'\nimport { PlaybackController } from './playback-controller'\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' | 'stopping' | 'stopped' | 'fatal-error' | 'destroying' | '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 readonly playback: PlaybackController\n private workerClient?: WorkerClient\n private state: PlayerState = 'idle'\n private lifecycleGeneration = 0\n private terminalError?: PlayerError\n private stopPromise?: Promise<void>\n private destroyPromise?: Promise<void>\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 this.playback = new PlaybackController(this.options.playback)\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.assertOperational('attach')\n const lifecycleGeneration = this.lifecycleGeneration\n\n if (this.playback.attached && !this.playback.isAttachedTo(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.playback.attach(video)\n this.ensureWorkerClient()\n await this.workerClient?.waitForMediaSourceHandle({ type: 'attach-media-source' })\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\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.assertOperational('start')\n const lifecycleGeneration = this.lifecycleGeneration\n\n if (!this.playback.attached || 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 if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.state = 'attached'\n }\n\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\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 stop(): Promise<void> {\n if (this.state === 'destroyed' || this.state === 'destroying') {\n return Promise.resolve()\n }\n\n if (this.state === 'fatal-error') {\n return Promise.resolve()\n }\n\n if (this.stopPromise !== undefined) {\n return this.stopPromise\n }\n\n this.lifecycleGeneration += 1\n this.state = 'stopping'\n const lifecycleGeneration = this.lifecycleGeneration\n const stopping = this.performStop(lifecycleGeneration).finally(() => {\n if (this.stopPromise === stopping) {\n this.stopPromise = undefined\n }\n })\n this.stopPromise = stopping\n return stopping\n }\n\n private async performStop(lifecycleGeneration: number): Promise<void> {\n const workerClient = this.workerClient\n if (workerClient === undefined) {\n if (this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n this.state = 'stopped'\n this.events.emit('stopped', undefined)\n }\n return\n }\n\n this.playback.stopStateReporting()\n try {\n await workerClient.waitForStopped({ type: 'stop' })\n } catch (error) {\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n throw error\n }\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration)) {\n return\n }\n this.playback.detachSource()\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 if (this.destroyPromise !== undefined) {\n return this.destroyPromise\n }\n\n this.lifecycleGeneration += 1\n this.state = 'destroying'\n this.destroyPromise = this.performDestroy()\n return this.destroyPromise\n }\n\n private async performDestroy(): Promise<void> {\n const workerClient = this.workerClient\n this.workerClient = undefined\n this.playback.stopStateReporting()\n let destroyError: unknown\n\n if (workerClient !== undefined) {\n try {\n await workerClient.waitForDestroyed({ type: 'destroy' })\n } catch (error) {\n destroyError = error\n } finally {\n workerClient.dispose()\n }\n } else {\n this.events.emit('destroyed', undefined)\n }\n\n this.playback.release()\n\n this.state = 'destroyed'\n this.events.clear()\n\n if (destroyError !== undefined) {\n throw destroyError\n }\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) => {\n this.enterFatalErrorState(error)\n this.events.emit('error', error)\n },\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 if ((this.state === 'destroying' && message.type !== 'destroyed') || this.state === 'fatal-error') {\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 if (message.error.terminal) {\n this.enterFatalErrorState(message.error)\n }\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.playback.attachMediaSourceHandle(handle)) {\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\n private startVideoStateReporting(): void {\n const intervalMs = Math.max(100, Math.min(this.options.diagnostics.statsIntervalMs, 250))\n const workerClient = this.workerClient\n if (workerClient === undefined) {\n return\n }\n\n this.playback.startStateReporting(intervalMs, (state) => {\n if (this.state === 'started' && this.workerClient === workerClient) {\n workerClient.post({ type: 'video-state', state })\n }\n })\n }\n\n private async applyPlaybackControl(action: PlaybackControlAction): Promise<void> {\n const workerClient = this.workerClient\n const lifecycleGeneration = this.lifecycleGeneration\n if (!this.playback.attached || workerClient === undefined || this.state !== 'started') {\n return\n }\n\n const result = await this.playback.applyControl(action)\n if (!this.isLifecycleOperationCurrent(lifecycleGeneration) || this.state !== 'started' || this.workerClient !== workerClient) {\n return\n }\n workerClient.post({ type: 'playback-control-result', result })\n }\n\n private enterFatalErrorState(error: PlayerError): void {\n if (this.state === 'destroyed' || this.state === 'destroying' || this.state === 'fatal-error') {\n return\n }\n\n this.lifecycleGeneration += 1\n this.terminalError = error\n this.state = 'fatal-error'\n this.playback.release()\n }\n\n private assertOperational(method: string): void {\n if (this.state === 'fatal-error' && this.terminalError !== undefined) {\n throw playerErrorToException(this.terminalError)\n }\n\n if (this.state === 'stopping') {\n throw playerErrorToException(\n createPlayerError('runtime', 'RIVMUX_PLAYER_STOPPING', `RivmuxPlayer.${method}() cannot be called while stop() is pending.`, false)\n )\n }\n\n if (this.state !== 'destroyed' && this.state !== 'destroying') {\n return\n }\n\n const code = this.state === 'destroying' ? 'RIVMUX_PLAYER_DESTROYING' : 'RIVMUX_PLAYER_DESTROYED'\n throw playerErrorToException(createPlayerError('runtime', code, `RivmuxPlayer.${method}() cannot be called after destroy() begins.`, true))\n }\n\n private isLifecycleOperationCurrent(generation: number): boolean {\n return generation === this.lifecycleGeneration && this.state !== 'fatal-error' && this.state !== 'destroying' && this.state !== 'destroyed'\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 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,6 +1,6 @@
1
1
  {
2
2
  "name": "rivmux",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Public player package for Rivmux.",
5
5
  "homepage": "https://www.npmjs.com/package/rivmux",
6
6
  "bugs": {
@@ -30,8 +30,8 @@
30
30
  "module": "./dist/index.js",
31
31
  "types": "./dist/index.d.ts",
32
32
  "dependencies": {
33
- "@rivmux/runtime-worker": "0.4.0",
34
- "@rivmux/protocol": "0.4.0"
33
+ "@rivmux/protocol": "0.5.0",
34
+ "@rivmux/runtime-worker": "0.5.0"
35
35
  },
36
36
  "devDependencies": {
37
37
  "tsdown": "^0.22.7",