rx-player 4.2.0-dev.2024091600 → 4.2.0-dev.2024100200

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.
Files changed (161) hide show
  1. package/CHANGELOG.md +20 -11
  2. package/VERSION +1 -1
  3. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  5. package/dist/commonjs/compat/eme/close_session.js +2 -2
  6. package/dist/commonjs/compat/eme/load_session.js +2 -2
  7. package/dist/commonjs/compat/patch_webkit_source_buffer.d.ts.map +1 -1
  8. package/dist/commonjs/compat/patch_webkit_source_buffer.js +0 -3
  9. package/dist/commonjs/config.d.ts +7 -156
  10. package/dist/commonjs/config.d.ts.map +1 -1
  11. package/dist/commonjs/config.js +50 -3
  12. package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.js +2 -2
  13. package/dist/commonjs/core/fetchers/segment/segment_fetcher.js +2 -2
  14. package/dist/commonjs/core/fetchers/utils/schedule_request.js +2 -2
  15. package/dist/commonjs/core/main/worker/worker_main.d.ts.map +1 -1
  16. package/dist/commonjs/core/main/worker/worker_main.js +7 -0
  17. package/dist/commonjs/core/segment_sinks/garbage_collector.js +2 -2
  18. package/dist/commonjs/core/segment_sinks/implementations/audio_video/audio_video_segment_buffer.js +2 -2
  19. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_buffer.js +2 -2
  20. package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  21. package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +35 -9
  22. package/dist/commonjs/core/stream/adaptation/index.d.ts +1 -1
  23. package/dist/commonjs/core/stream/adaptation/index.d.ts.map +1 -1
  24. package/dist/commonjs/core/stream/adaptation/index.js +0 -15
  25. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +2 -2
  26. package/dist/commonjs/core/stream/period/index.d.ts +1 -1
  27. package/dist/commonjs/core/stream/period/index.d.ts.map +1 -1
  28. package/dist/commonjs/core/stream/period/index.js +0 -15
  29. package/dist/commonjs/core/stream/period/period_stream.js +2 -2
  30. package/dist/commonjs/core/stream/representation/index.d.ts +1 -1
  31. package/dist/commonjs/core/stream/representation/index.d.ts.map +1 -1
  32. package/dist/commonjs/core/stream/representation/index.js +0 -15
  33. package/dist/commonjs/core/stream/representation/representation_stream.d.ts.map +1 -1
  34. package/dist/commonjs/core/stream/representation/representation_stream.js +2 -0
  35. package/dist/commonjs/core/stream/representation/utils/append_segment_to_buffer.d.ts.map +1 -1
  36. package/dist/commonjs/core/stream/representation/utils/append_segment_to_buffer.js +16 -11
  37. package/dist/commonjs/core/stream/representation/utils/push_init_segment.js +2 -2
  38. package/dist/commonjs/core/stream/representation/utils/push_media_segment.js +2 -2
  39. package/dist/commonjs/default_config.js +4 -0
  40. package/dist/commonjs/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +2 -2
  41. package/dist/commonjs/experimental/tools/createMetaplaylist/get_duration_from_manifest.js +2 -2
  42. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  43. package/dist/commonjs/main_thread/api/public_api.js +19 -4
  44. package/dist/commonjs/main_thread/decrypt/attach_media_keys.js +2 -2
  45. package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  46. package/dist/commonjs/main_thread/decrypt/content_decryptor.js +4 -2
  47. package/dist/commonjs/main_thread/decrypt/create_or_load_session.js +2 -2
  48. package/dist/commonjs/main_thread/decrypt/create_session.js +2 -2
  49. package/dist/commonjs/main_thread/decrypt/dispose_decryption_resources.js +2 -2
  50. package/dist/commonjs/main_thread/decrypt/find_key_system.js +2 -2
  51. package/dist/commonjs/main_thread/decrypt/get_media_keys.d.ts.map +1 -1
  52. package/dist/commonjs/main_thread/decrypt/get_media_keys.js +3 -2
  53. package/dist/commonjs/main_thread/decrypt/init_media_keys.js +2 -2
  54. package/dist/commonjs/main_thread/decrypt/session_events_listener.js +2 -2
  55. package/dist/commonjs/main_thread/decrypt/set_server_certificate.js +2 -4
  56. package/dist/commonjs/main_thread/decrypt/utils/clean_old_loaded_sessions.js +2 -2
  57. package/dist/commonjs/main_thread/decrypt/utils/loaded_sessions_store.js +2 -2
  58. package/dist/commonjs/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  59. package/dist/commonjs/main_thread/init/directfile_content_initializer.js +14 -6
  60. package/dist/commonjs/main_thread/init/media_source_content_initializer.js +2 -2
  61. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +13 -0
  62. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -1
  63. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.js +96 -47
  64. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts +1 -1
  65. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  66. package/dist/commonjs/main_thread/init/utils/initial_seek_and_play.js +21 -5
  67. package/dist/commonjs/mse/main_media_source_interface.d.ts.map +1 -1
  68. package/dist/commonjs/mse/main_media_source_interface.js +21 -2
  69. package/dist/commonjs/multithread_types.d.ts +8 -1
  70. package/dist/commonjs/multithread_types.d.ts.map +1 -1
  71. package/dist/commonjs/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.js +2 -2
  72. package/dist/commonjs/parsers/manifest/index.d.ts +1 -1
  73. package/dist/commonjs/parsers/manifest/index.d.ts.map +1 -1
  74. package/dist/commonjs/parsers/manifest/index.js +0 -15
  75. package/dist/commonjs/transports/dash/integrity_checks.js +2 -2
  76. package/dist/commonjs/transports/dash/load_chunked_segment_data.js +2 -2
  77. package/dist/commonjs/transports/dash/segment_loader.js +2 -2
  78. package/dist/commonjs/transports/dash/text_loader.js +2 -2
  79. package/dist/commonjs/transports/smooth/pipelines.d.ts.map +1 -1
  80. package/dist/commonjs/transports/smooth/pipelines.js +1 -0
  81. package/dist/commonjs/transports/smooth/segment_loader.js +2 -2
  82. package/dist/commonjs/transports/utils/parse_text_track.d.ts.map +1 -1
  83. package/dist/commonjs/transports/utils/parse_text_track.js +1 -0
  84. package/dist/commonjs/utils/request/fetch.js +2 -2
  85. package/dist/commonjs/utils/retry_promise_with_backoff.js +2 -2
  86. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  87. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  88. package/dist/es2017/compat/patch_webkit_source_buffer.d.ts.map +1 -1
  89. package/dist/es2017/compat/patch_webkit_source_buffer.js +0 -3
  90. package/dist/es2017/config.d.ts +7 -156
  91. package/dist/es2017/config.d.ts.map +1 -1
  92. package/dist/es2017/config.js +6 -1
  93. package/dist/es2017/core/main/worker/worker_main.d.ts.map +1 -1
  94. package/dist/es2017/core/main/worker/worker_main.js +7 -0
  95. package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  96. package/dist/es2017/core/stream/adaptation/adaptation_stream.js +33 -7
  97. package/dist/es2017/core/stream/adaptation/index.d.ts +1 -1
  98. package/dist/es2017/core/stream/adaptation/index.d.ts.map +1 -1
  99. package/dist/es2017/core/stream/adaptation/index.js +0 -1
  100. package/dist/es2017/core/stream/period/index.d.ts +1 -1
  101. package/dist/es2017/core/stream/period/index.d.ts.map +1 -1
  102. package/dist/es2017/core/stream/period/index.js +0 -1
  103. package/dist/es2017/core/stream/representation/index.d.ts +1 -1
  104. package/dist/es2017/core/stream/representation/index.d.ts.map +1 -1
  105. package/dist/es2017/core/stream/representation/index.js +0 -1
  106. package/dist/es2017/core/stream/representation/representation_stream.d.ts.map +1 -1
  107. package/dist/es2017/core/stream/representation/representation_stream.js +2 -0
  108. package/dist/es2017/core/stream/representation/utils/append_segment_to_buffer.d.ts.map +1 -1
  109. package/dist/es2017/core/stream/representation/utils/append_segment_to_buffer.js +6 -2
  110. package/dist/es2017/default_config.js +4 -0
  111. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  112. package/dist/es2017/main_thread/api/public_api.js +17 -2
  113. package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  114. package/dist/es2017/main_thread/decrypt/content_decryptor.js +2 -0
  115. package/dist/es2017/main_thread/decrypt/get_media_keys.d.ts.map +1 -1
  116. package/dist/es2017/main_thread/decrypt/get_media_keys.js +1 -0
  117. package/dist/es2017/main_thread/init/directfile_content_initializer.d.ts.map +1 -1
  118. package/dist/es2017/main_thread/init/directfile_content_initializer.js +14 -6
  119. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +13 -0
  120. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -1
  121. package/dist/es2017/main_thread/init/multi_thread_content_initializer.js +78 -41
  122. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts +1 -1
  123. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.d.ts.map +1 -1
  124. package/dist/es2017/main_thread/init/utils/initial_seek_and_play.js +19 -3
  125. package/dist/es2017/mse/main_media_source_interface.d.ts.map +1 -1
  126. package/dist/es2017/mse/main_media_source_interface.js +19 -0
  127. package/dist/es2017/multithread_types.d.ts +8 -1
  128. package/dist/es2017/multithread_types.d.ts.map +1 -1
  129. package/dist/es2017/parsers/manifest/index.d.ts +1 -1
  130. package/dist/es2017/parsers/manifest/index.d.ts.map +1 -1
  131. package/dist/es2017/parsers/manifest/index.js +1 -1
  132. package/dist/es2017/transports/smooth/pipelines.d.ts.map +1 -1
  133. package/dist/es2017/transports/smooth/pipelines.js +1 -0
  134. package/dist/es2017/transports/utils/parse_text_track.d.ts.map +1 -1
  135. package/dist/es2017/transports/utils/parse_text_track.js +1 -0
  136. package/dist/rx-player.js +101 -23
  137. package/dist/rx-player.min.js +14 -14
  138. package/dist/worker.js +6 -6
  139. package/package.json +38 -21
  140. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  141. package/src/compat/patch_webkit_source_buffer.ts +0 -3
  142. package/src/config.ts +10 -2
  143. package/src/core/main/worker/worker_main.ts +8 -0
  144. package/src/core/stream/adaptation/adaptation_stream.ts +41 -8
  145. package/src/core/stream/adaptation/index.ts +1 -1
  146. package/src/core/stream/period/index.ts +1 -1
  147. package/src/core/stream/representation/index.ts +1 -1
  148. package/src/core/stream/representation/representation_stream.ts +11 -0
  149. package/src/core/stream/representation/utils/append_segment_to_buffer.ts +6 -2
  150. package/src/default_config.ts +21 -0
  151. package/src/main_thread/api/public_api.ts +19 -2
  152. package/src/main_thread/decrypt/content_decryptor.ts +2 -0
  153. package/src/main_thread/decrypt/get_media_keys.ts +1 -0
  154. package/src/main_thread/init/directfile_content_initializer.ts +20 -10
  155. package/src/main_thread/init/multi_thread_content_initializer.ts +94 -61
  156. package/src/main_thread/init/utils/initial_seek_and_play.ts +24 -5
  157. package/src/mse/main_media_source_interface.ts +20 -0
  158. package/src/multithread_types.ts +9 -0
  159. package/src/parsers/manifest/index.ts +1 -1
  160. package/src/transports/smooth/pipelines.ts +1 -0
  161. package/src/transports/utils/parse_text_track.ts +1 -0
@@ -85,10 +85,8 @@ export default function patchWebkitSourceBuffer(): void {
85
85
  val: unknown,
86
86
  ) {
87
87
  queueMicrotask(() => {
88
- // @ts-expect-error: trigger is normally protected
89
88
  this.trigger(eventName, val as Event);
90
89
  this.updating = false;
91
- // @ts-expect-error: trigger is normally protected
92
90
  this.trigger("updateend", new Event("updateend"));
93
91
  });
94
92
  };
@@ -97,7 +95,6 @@ export default function patchWebkitSourceBuffer(): void {
97
95
  if (this.updating) {
98
96
  throw new Error("updating");
99
97
  }
100
- // @ts-expect-error: trigger is normally protected
101
98
  this.trigger("updatestart", new Event("updatestart"));
102
99
  this.updating = true;
103
100
  try {
package/src/config.ts CHANGED
@@ -9,13 +9,21 @@
9
9
  import type { IDefaultConfig } from "./default_config";
10
10
  import DEFAULT_CONFIG from "./default_config";
11
11
  import deepMerge from "./utils/deep_merge";
12
+ import EventEmitter from "./utils/event_emitter";
12
13
 
13
- class ConfigHandler {
14
- _config = DEFAULT_CONFIG;
14
+ interface IConfigHandlerEvents {
15
+ update: Partial<IDefaultConfig>;
16
+ }
17
+
18
+ class ConfigHandler extends EventEmitter<IConfigHandlerEvents> {
19
+ public updated = false;
20
+ private _config = DEFAULT_CONFIG;
15
21
 
16
22
  update(config: Partial<IDefaultConfig>) {
17
23
  const newConfig = deepMerge(this._config, config);
18
24
  this._config = newConfig;
25
+ this.updated = true;
26
+ this.trigger("update", config);
19
27
  }
20
28
 
21
29
  getCurrent(): IDefaultConfig {
@@ -80,6 +80,9 @@ export default function initializeWorkerMain() {
80
80
  */
81
81
  let playbackObservationRef: SharedReference<IWorkerPlaybackObservation> | null = null;
82
82
 
83
+ onmessageerror = (_msg: MessageEvent) => {
84
+ log.error("MTCI: Error when receiving message from main thread.");
85
+ };
83
86
  onmessage = function (e: MessageEvent<IMainThreadMessage>) {
84
87
  log.debug("Worker: received message", e.data.type);
85
88
 
@@ -401,6 +404,11 @@ export default function initializeWorkerMain() {
401
404
  break;
402
405
  }
403
406
 
407
+ case MainThreadMessageType.ConfigUpdate: {
408
+ config.update(msg.value);
409
+ break;
410
+ }
411
+
404
412
  default:
405
413
  assertUnreachable(msg);
406
414
  }
@@ -376,15 +376,21 @@ export default function AdaptationStream(
376
376
  representationStreamCallbacks: IRepresentationStreamCallbacks,
377
377
  fnCancelSignal: CancellationSignal,
378
378
  ): void {
379
+ /** Set to `true` if we've encountered an error with this `RepresentationStream` */
380
+ let hasEncounteredError = false;
381
+
379
382
  const bufferGoalCanceller = new TaskCanceller();
380
383
  bufferGoalCanceller.linkToSignal(fnCancelSignal);
384
+
385
+ /** Actually built buffer size, in seconds. */
381
386
  const bufferGoal = createMappedReference(
382
387
  wantedBufferAhead,
383
388
  (prev) => {
384
- return prev * getBufferGoalRatio(representation);
389
+ return getBufferGoal(representation, prev);
385
390
  },
386
391
  bufferGoalCanceller.signal,
387
392
  );
393
+
388
394
  const maxBufferSize =
389
395
  adaptation.type === "video" ? maxVideoBufferSize : new SharedReference(Infinity);
390
396
  log.info(
@@ -394,7 +400,18 @@ export default function AdaptationStream(
394
400
  representation.bitrate,
395
401
  );
396
402
  const updatedCallbacks = objectAssign({}, representationStreamCallbacks, {
397
- error(err: unknown) {
403
+ error(err: Error) {
404
+ if (hasEncounteredError) {
405
+ // A RepresentationStream might trigger multiple Errors (for example
406
+ // multiple segments it tried to push at once led to errors).
407
+ // In that case, we'll only consider the first Error.
408
+ //
409
+ // That could mean that we're hiding legitimate issues but handling
410
+ // multiple of those errors at once is too hard a task for now.
411
+ log.warn("Stream: Ignoring RepresentationStream error", err);
412
+ return;
413
+ }
414
+ hasEncounteredError = true;
398
415
  const formattedError = formatError(err, {
399
416
  defaultCode: "NONE",
400
417
  defaultReason: "Unknown `RepresentationStream` error",
@@ -402,14 +419,20 @@ export default function AdaptationStream(
402
419
  if (formattedError.code !== "BUFFER_FULL_ERROR") {
403
420
  representationStreamCallbacks.error(err);
404
421
  } else {
422
+ log.warn(
423
+ "Stream: received BUFFER_FULL_ERROR",
424
+ adaptation.type,
425
+ representation.bitrate,
426
+ );
405
427
  const wba = wantedBufferAhead.getValue();
406
428
  const lastBufferGoalRatio = bufferGoalRatioMap.get(representation.id) ?? 1;
407
429
  // 70%, 49%, 34.3%, 24%, 16.81%, 11.76%, 8.24% and 5.76%
408
430
  const newBufferGoalRatio = lastBufferGoalRatio * 0.7;
409
- if (newBufferGoalRatio <= 0.05 || wba * newBufferGoalRatio <= 2) {
410
- throw formattedError;
411
- }
412
431
  bufferGoalRatioMap.set(representation.id, newBufferGoalRatio);
432
+ if (newBufferGoalRatio <= 0.05 || getBufferGoal(representation, wba) <= 2) {
433
+ representationStreamCallbacks.error(formattedError);
434
+ return;
435
+ }
413
436
 
414
437
  // We wait 4 seconds to let the situation evolve by itself before
415
438
  // retrying loading segments with a lower buffer goal
@@ -481,15 +504,25 @@ export default function AdaptationStream(
481
504
  }
482
505
 
483
506
  /**
484
- * @param {Object} representation
507
+ * Returns how much media data should be pre-buffered for this
508
+ * `Representation`, according to the `wantedBufferAhead` setting and previous
509
+ * issues encountered with that `Representation`.
510
+ * @param {Object} representation - The `Representation` you want to buffer.
511
+ * @param {number} wba - The value of `wantedBufferAhead` set by the user.
485
512
  * @returns {number}
486
513
  */
487
- function getBufferGoalRatio(representation: IRepresentation): number {
514
+ function getBufferGoal(representation: IRepresentation, wba: number): number {
488
515
  const oldBufferGoalRatio = bufferGoalRatioMap.get(representation.id);
489
516
  const bufferGoalRatio = oldBufferGoalRatio !== undefined ? oldBufferGoalRatio : 1;
490
517
  if (oldBufferGoalRatio === undefined) {
491
518
  bufferGoalRatioMap.set(representation.id, bufferGoalRatio);
492
519
  }
493
- return bufferGoalRatio;
520
+ if (bufferGoalRatio < 1 && wba === Infinity) {
521
+ // When `wba` is equal to `Infinity`, dividing it will still make it equal
522
+ // to `Infinity`. To make the `bufferGoalRatio` still have an effect, we
523
+ // just starts from a `wba` set to the high value of 5 minutes.
524
+ return 5 * 60 * 1000 * bufferGoalRatio;
525
+ }
526
+ return wba * bufferGoalRatio;
494
527
  }
495
528
  }
@@ -15,6 +15,6 @@
15
15
  */
16
16
 
17
17
  import AdaptationStream from "./adaptation_stream";
18
- export * from "./types";
18
+ export type * from "./types";
19
19
 
20
20
  export default AdaptationStream;
@@ -15,6 +15,6 @@
15
15
  */
16
16
 
17
17
  import PeriodStream from "./period_stream";
18
- export * from "./types";
18
+ export type * from "./types";
19
19
 
20
20
  export default PeriodStream;
@@ -15,6 +15,6 @@
15
15
  */
16
16
 
17
17
  import RepresentationStream from "./representation_stream";
18
- export * from "./types";
18
+ export type * from "./types";
19
19
 
20
20
  export default RepresentationStream;
@@ -90,6 +90,11 @@ export default function RepresentationStream<TSegmentDataType>(
90
90
  callbacks: IRepresentationStreamCallbacks,
91
91
  parentCancelSignal: CancellationSignal,
92
92
  ): void {
93
+ log.debug(
94
+ "Stream: Creating RepresentationStream",
95
+ content.adaptation.type,
96
+ content.representation.bitrate,
97
+ );
93
98
  const { period, adaptation, representation } = content;
94
99
  const { bufferGoal, maxBufferSize, drmSystemId, fastSwitchThreshold } = options;
95
100
  const bufferType = adaptation.type;
@@ -488,6 +493,12 @@ export default function RepresentationStream<TSegmentDataType>(
488
493
  // We can thus ignore it, it is very unlikely to lead to true buffer issues.
489
494
  return;
490
495
  }
496
+ log.warn(
497
+ "Stream: Received fatal buffer error",
498
+ adaptation.type,
499
+ representation.bitrate,
500
+ err instanceof Error ? err : null,
501
+ );
491
502
  globalCanceller.cancel();
492
503
  callbacks.error(err);
493
504
  }
@@ -72,8 +72,12 @@ export default async function appendSegmentToBuffer<T>(
72
72
  log.warn("Stream: Running garbage collector");
73
73
  const start = Math.max(currentPos - 5, 0);
74
74
  const end = currentPos + bufferGoal.getValue() + 12;
75
- await segmentSink.removeBuffer(0, start);
76
- await segmentSink.removeBuffer(end, Number.MAX_VALUE);
75
+ if (start > 0) {
76
+ await segmentSink.removeBuffer(0, start);
77
+ }
78
+ if (end < Number.MAX_VALUE) {
79
+ await segmentSink.removeBuffer(end, Number.MAX_VALUE);
80
+ }
77
81
  await sleep(200);
78
82
  if (cancellationSignal.cancellationError !== null) {
79
83
  throw cancellationSignal.cancellationError;
@@ -1187,3 +1187,24 @@ const DEFAULT_CONFIG = {
1187
1187
 
1188
1188
  export type IDefaultConfig = typeof DEFAULT_CONFIG;
1189
1189
  export default DEFAULT_CONFIG;
1190
+
1191
+ // NOTE Because the config may have to be serialized and shared between several
1192
+ // environments, we check here that some strict type is respected:
1193
+ // - only a subset of types are authorized for now, just make it easier to
1194
+ // reason about.
1195
+ // - Needs to make sense in JSON: no `function`, no `Date`, no `undefined`...
1196
+ interface IGenericConfigData {
1197
+ [key: string]:
1198
+ | string
1199
+ | number
1200
+ | boolean
1201
+ | number[]
1202
+ | string[]
1203
+ | Partial<Record<string, string[]>>
1204
+ | IGenericConfigData;
1205
+ }
1206
+
1207
+ function checkIsSerializable(_conf: IGenericConfigData): void {
1208
+ // noop
1209
+ }
1210
+ checkIsSerializable(DEFAULT_CONFIG);
@@ -32,6 +32,7 @@ import getStartDate from "../../compat/get_start_date";
32
32
  import hasMseInWorker from "../../compat/has_mse_in_worker";
33
33
  import hasWorkerApi from "../../compat/has_worker_api";
34
34
  import isDebugModeEnabled from "../../compat/is_debug_mode_enabled";
35
+ import config from "../../config";
35
36
  import type { ISegmentSinkMetrics } from "../../core/segment_sinks/segment_buffers_store";
36
37
  import type {
37
38
  IAdaptationChoice,
@@ -39,6 +40,7 @@ import type {
39
40
  IABRThrottlers,
40
41
  IBufferType,
41
42
  } from "../../core/types";
43
+ import type { IDefaultConfig } from "../../default_config";
42
44
  import type { IErrorCode, IErrorType } from "../../errors";
43
45
  import { ErrorCodes, ErrorTypes, formatError, MediaError } from "../../errors";
44
46
  import WorkerInitializationError from "../../errors/worker_initialization_error";
@@ -409,7 +411,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
409
411
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
410
412
  videoElement.preload = "auto";
411
413
 
412
- this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024091600";
414
+ this.version = /* PLAYER_VERSION */ "4.2.0-dev.2024100200";
413
415
  this.log = log;
414
416
  this.state = "STOPPED";
415
417
  this.videoElement = videoElement;
@@ -573,6 +575,21 @@ class Player extends EventEmitter<IPublicAPIEvent> {
573
575
  },
574
576
  this._destroyCanceller.signal,
575
577
  );
578
+
579
+ const sendConfigUpdates = (updates: Partial<IDefaultConfig>) => {
580
+ if (this._priv_worker === null) {
581
+ return;
582
+ }
583
+ log.debug("---> Sending To Worker:", MainThreadMessageType.ConfigUpdate);
584
+ this._priv_worker.postMessage({
585
+ type: MainThreadMessageType.ConfigUpdate,
586
+ value: updates,
587
+ });
588
+ };
589
+ if (config.updated) {
590
+ sendConfigUpdates(config.getCurrent());
591
+ }
592
+ config.addEventListener("update", sendConfigUpdates, this._destroyCanceller.signal);
576
593
  });
577
594
  }
578
595
 
@@ -3313,7 +3330,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3313
3330
  }
3314
3331
  }
3315
3332
  }
3316
- Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024091600";
3333
+ Player.version = /* PLAYER_VERSION */ "4.2.0-dev.2024100200";
3317
3334
 
3318
3335
  /** Every events sent by the RxPlayer's public API. */
3319
3336
  interface IPublicAPIEvent {
@@ -208,6 +208,7 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
208
208
 
209
209
  this.systemId = systemId;
210
210
  if (this._stateData.state === ContentDecryptorState.Initializing) {
211
+ log.debug("DRM: Waiting for attachment.");
211
212
  this._stateData = {
212
213
  state: ContentDecryptorState.WaitingForAttachment,
213
214
  isInitDataQueueLocked: true,
@@ -260,6 +261,7 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
260
261
  const shouldDisableLock = options.disableMediaKeysAttachmentLock === true;
261
262
 
262
263
  if (shouldDisableLock) {
264
+ log.debug("DRM: disabling MediaKeys attachment lock. Ready for content");
263
265
  this._stateData = {
264
266
  state: ContentDecryptorState.ReadyForContent,
265
267
  isInitDataQueueLocked: true,
@@ -102,6 +102,7 @@ export default async function getMediaKeysInfos(
102
102
  currentState !== null &&
103
103
  evt.type === "reuse-media-key-system-access"
104
104
  ) {
105
+ log.debug("DRM: Reusing already created MediaKeys");
105
106
  const { mediaKeys, loadedSessionsStore } = currentState;
106
107
 
107
108
  // We might just rely on the currently attached MediaKeys instance.
@@ -254,7 +254,7 @@ export default class DirectFileContentInitializer extends ContentInitializer {
254
254
  function getDirectFileInitialTime(
255
255
  mediaElement: IMediaElement,
256
256
  startAt?: IInitialTimeOptions,
257
- ): number {
257
+ ): number | undefined {
258
258
  if (isNullOrUndefined(startAt)) {
259
259
  return 0;
260
260
  }
@@ -268,28 +268,38 @@ function getDirectFileInitialTime(
268
268
  }
269
269
 
270
270
  const duration = mediaElement.duration;
271
-
272
271
  if (typeof startAt.fromLastPosition === "number") {
273
- if (isNullOrUndefined(duration) || !isFinite(duration)) {
274
- log.warn(
275
- "startAt.fromLastPosition set but no known duration, " + "beginning at 0.",
276
- );
277
- return 0;
272
+ if (!isNullOrUndefined(duration) && isFinite(duration)) {
273
+ return Math.max(0, duration + startAt.fromLastPosition);
278
274
  }
279
- return Math.max(0, duration + startAt.fromLastPosition);
275
+
276
+ if (mediaElement.seekable.length > 0) {
277
+ const lastSegmentEnd = mediaElement.seekable.end(mediaElement.seekable.length - 1);
278
+ if (isFinite(lastSegmentEnd)) {
279
+ return Math.max(0, lastSegmentEnd + startAt.fromLastPosition);
280
+ }
281
+ }
282
+ log.warn(
283
+ "Init: startAt.fromLastPosition set but no known duration, " +
284
+ "it may be too soon to seek",
285
+ );
286
+ return undefined;
280
287
  } else if (typeof startAt.fromLivePosition === "number") {
281
288
  const livePosition =
282
289
  mediaElement.seekable.length > 0 ? mediaElement.seekable.end(0) : duration;
283
290
  if (isNullOrUndefined(livePosition)) {
284
291
  log.warn(
285
- "startAt.fromLivePosition set but no known live position, " + "beginning at 0.",
292
+ "Init: startAt.fromLivePosition set but no known live position, " +
293
+ "beginning at 0.",
286
294
  );
287
295
  return 0;
288
296
  }
289
297
  return Math.max(0, livePosition + startAt.fromLivePosition);
290
298
  } else if (!isNullOrUndefined(startAt.percentage)) {
291
299
  if (isNullOrUndefined(duration) || !isFinite(duration)) {
292
- log.warn("startAt.percentage set but no known duration, " + "beginning at 0.");
300
+ log.warn(
301
+ "Init: startAt.percentage set but no known duration, " + "beginning at 0.",
302
+ );
293
303
  return 0;
294
304
  }
295
305
  const { percentage } = startAt;
@@ -78,6 +78,20 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
78
78
  /** Constructor settings associated to this `MultiThreadContentInitializer`. */
79
79
  private _settings: IInitializeArguments;
80
80
 
81
+ /**
82
+ * The WebWorker may be sending messages as soon as we're preparing the
83
+ * content but the `MultiThreadContentInitializer` is only able to handle all of
84
+ * them only once `start`ed.
85
+ *
86
+ * As such `_queuedWorkerMessages` is set to an Array when `prepare` has been
87
+ * called but not `start` yet, and contains all worker messages that have to
88
+ * be processed when `start` is called.
89
+ *
90
+ * It is set to `null` when there's no need to rely on that queue (either not
91
+ * yet `prepare`d or already `start`ed).
92
+ */
93
+ private _queuedWorkerMessages: MessageEvent[] | null;
94
+
81
95
  /**
82
96
  * Information relative to the current loaded content.
83
97
  *
@@ -123,6 +137,7 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
123
137
  lastMessageId: 0,
124
138
  resolvers: {},
125
139
  };
140
+ this._queuedWorkerMessages = null;
126
141
  }
127
142
 
128
143
  /**
@@ -176,6 +191,66 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
176
191
  if (this._initCanceller.isUsed()) {
177
192
  return;
178
193
  }
194
+ this._queuedWorkerMessages = [];
195
+ log.debug("MTCI: addEventListener prepare buffering worker messages");
196
+ const onmessage = (evt: MessageEvent): void => {
197
+ const msgData = evt.data as unknown as IWorkerMessage;
198
+ const type = msgData.type;
199
+ switch (type) {
200
+ case WorkerMessageType.LogMessage: {
201
+ const formatted = msgData.value.logs.map((l) => {
202
+ switch (typeof l) {
203
+ case "string":
204
+ case "number":
205
+ case "boolean":
206
+ case "undefined":
207
+ return l;
208
+ case "object":
209
+ if (l === null) {
210
+ return null;
211
+ }
212
+ return formatWorkerError(l);
213
+ default:
214
+ assertUnreachable(l);
215
+ }
216
+ });
217
+ switch (msgData.value.logLevel) {
218
+ case "NONE":
219
+ break;
220
+ case "ERROR":
221
+ log.error(...formatted);
222
+ break;
223
+ case "WARNING":
224
+ log.warn(...formatted);
225
+ break;
226
+ case "INFO":
227
+ log.info(...formatted);
228
+ break;
229
+ case "DEBUG":
230
+ log.debug(...formatted);
231
+ break;
232
+ default:
233
+ assertUnreachable(msgData.value.logLevel);
234
+ }
235
+ break;
236
+ }
237
+ default:
238
+ if (this._queuedWorkerMessages !== null) {
239
+ this._queuedWorkerMessages.push(evt);
240
+ }
241
+ break;
242
+ }
243
+ };
244
+ this._settings.worker.addEventListener("message", onmessage);
245
+ const onmessageerror = (_msg: MessageEvent) => {
246
+ log.error("MTCI: Error when receiving message from worker.");
247
+ };
248
+ this._settings.worker.addEventListener("messageerror", onmessageerror);
249
+ this._initCanceller.signal.register(() => {
250
+ log.debug("MTCI: removeEventListener prepare for worker message");
251
+ this._settings.worker.removeEventListener("message", onmessage);
252
+ this._settings.worker.removeEventListener("messageerror", onmessageerror);
253
+ });
179
254
 
180
255
  // Also bind all `SharedReference` objects:
181
256
 
@@ -1042,49 +1117,6 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
1042
1117
  }
1043
1118
  break;
1044
1119
 
1045
- case WorkerMessageType.LogMessage: {
1046
- const formatted = msgData.value.logs.map((l) => {
1047
- switch (typeof l) {
1048
- case "string":
1049
- case "number":
1050
- case "boolean":
1051
- case "undefined":
1052
- return l;
1053
- case "object":
1054
- if (l === null) {
1055
- return null;
1056
- }
1057
- return formatWorkerError(l);
1058
- default:
1059
- assertUnreachable(l);
1060
- }
1061
- });
1062
- switch (msgData.value.logLevel) {
1063
- case "NONE":
1064
- break;
1065
- case "ERROR":
1066
- log.error(...formatted);
1067
- break;
1068
- case "WARNING":
1069
- log.warn(...formatted);
1070
- break;
1071
- case "INFO":
1072
- log.info(...formatted);
1073
- break;
1074
- case "DEBUG":
1075
- log.debug(...formatted);
1076
- break;
1077
- default:
1078
- assertUnreachable(msgData.value.logLevel);
1079
- }
1080
- break;
1081
- }
1082
-
1083
- case WorkerMessageType.InitSuccess:
1084
- case WorkerMessageType.InitError:
1085
- // Should already be handled by the API
1086
- break;
1087
-
1088
1120
  case WorkerMessageType.SegmentSinkStoreUpdate: {
1089
1121
  if (this._currentContentInfo?.contentId !== msgData.contentId) {
1090
1122
  return;
@@ -1098,13 +1130,32 @@ export default class MultiThreadContentInitializer extends ContentInitializer {
1098
1130
  }
1099
1131
  break;
1100
1132
  }
1133
+
1134
+ case WorkerMessageType.InitSuccess:
1135
+ case WorkerMessageType.InitError:
1136
+ // Should already be handled by the API
1137
+ break;
1138
+
1139
+ case WorkerMessageType.LogMessage:
1140
+ // Already handled by prepare's handler
1141
+ break;
1101
1142
  default:
1102
1143
  assertUnreachable(msgData);
1103
1144
  }
1104
1145
  };
1105
1146
 
1147
+ log.debug("MTCI: addEventListener for worker message");
1148
+ if (this._queuedWorkerMessages !== null) {
1149
+ const bufferedMessages = this._queuedWorkerMessages.slice();
1150
+ log.debug("MTCI: Processing buffered messages", bufferedMessages.length);
1151
+ for (const message of bufferedMessages) {
1152
+ onmessage(message);
1153
+ }
1154
+ this._queuedWorkerMessages = null;
1155
+ }
1106
1156
  this._settings.worker.addEventListener("message", onmessage);
1107
1157
  this._initCanceller.signal.register(() => {
1158
+ log.debug("MTCI: removeEventListener for worker message");
1108
1159
  this._settings.worker.removeEventListener("message", onmessage);
1109
1160
  });
1110
1161
  }
@@ -2006,24 +2057,6 @@ type IDecryptionInitializationState =
2006
2057
  * `HTMLMediaElement` (such as linking a content / `MediaSource` to it).
2007
2058
  */
2008
2059
  | { type: "uninitialized"; value: null }
2009
- /**
2010
- * The `MediaSource` or media url has to be linked to the `HTMLMediaElement`
2011
- * before continuing.
2012
- * Once it has been linked with success (e.g. the `MediaSource` has "opened"),
2013
- * the `isMediaLinked` `SharedReference` should be set to `true`.
2014
- *
2015
- * In the `MediaSource` case, you should wait until the `"initialized"`
2016
- * state before pushing segment.
2017
- *
2018
- * Note that the `"awaiting-media-link"` is an optional state. It can be
2019
- * skipped to directly `"initialized"` instead.
2020
- */
2021
- | {
2022
- type: "awaiting-media-link";
2023
- value: {
2024
- isMediaLinked: SharedReference<boolean>;
2025
- };
2026
- }
2027
2060
  /**
2028
2061
  * The `MediaSource` or media url can be linked AND segments can be pushed to
2029
2062
  * the `HTMLMediaElement` on which decryption capabilities were wanted.
@@ -71,7 +71,7 @@ export default function performInitialSeekAndPlay(
71
71
  }: {
72
72
  mediaElement: IMediaElement;
73
73
  playbackObserver: IMediaElementPlaybackObserver;
74
- startTime: number | (() => number);
74
+ startTime: number | (() => number | undefined);
75
75
  mustAutoPlay: boolean;
76
76
  isDirectfile: boolean;
77
77
  onWarning: (err: IPlayerError) => void;
@@ -107,18 +107,37 @@ export default function performInitialSeekAndPlay(
107
107
  if (!isDirectfile || typeof startTime === "number") {
108
108
  const initiallySeekedTime =
109
109
  typeof startTime === "number" ? startTime : startTime();
110
- if (initiallySeekedTime !== 0) {
110
+ if (initiallySeekedTime !== 0 && initiallySeekedTime !== undefined) {
111
111
  performInitialSeek(initiallySeekedTime);
112
112
  }
113
113
  waitForSeekable();
114
114
  } else {
115
115
  playbackObserver.listen(
116
116
  (obs, stopListening) => {
117
+ const initiallySeekedTime =
118
+ typeof startTime === "number" ? startTime : startTime();
119
+ if (
120
+ initiallySeekedTime === undefined &&
121
+ obs.readyState < HTMLMediaElement.HAVE_CURRENT_DATA
122
+ ) {
123
+ /**
124
+ * On browser, such as Safari, the HTMLMediaElement.duration
125
+ * and HTMLMediaElement.buffered may not be initialized at readyState 1, leading
126
+ * to cases where it can be equal to `Infinity`.
127
+ * If so, the range in which it is possible to seek is not yet known.
128
+ * To solve this, the seek should be done after readyState HAVE_CURRENT_DATA (2),
129
+ * at that time the previously mentioned attributes are correctly initialized and
130
+ * the range in which it is possible to seek is correctly known.
131
+ * If the initiallySeekedTime is still `undefined` when the readyState is >= 2,
132
+ * let assume that the initiallySeekedTime will never be known and continue
133
+ * the logic without seeking.
134
+ */
135
+ return;
136
+ }
117
137
  if (obs.readyState >= 1) {
118
138
  stopListening();
119
- const initiallySeekedTime =
120
- typeof startTime === "number" ? startTime : startTime();
121
- if (initiallySeekedTime !== 0) {
139
+
140
+ if (initiallySeekedTime !== 0 && initiallySeekedTime !== undefined) {
122
141
  if (canSeekDirectlyAfterLoadedMetadata) {
123
142
  performInitialSeek(initiallySeekedTime);
124
143
  } else {