rx-player 4.0.0-beta.1 → 4.0.0-beta.2

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 (169) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/CONTRIBUTING.md +48 -168
  3. package/FILES.md +40 -92
  4. package/VERSION +1 -1
  5. package/dist/_esm5.processed/compat/browser_detection.d.ts +3 -1
  6. package/dist/_esm5.processed/compat/browser_detection.js +7 -2
  7. package/dist/_esm5.processed/compat/eme/load_session.js +1 -1
  8. package/dist/_esm5.processed/compat/has_issues_with_high_media_source_duration.d.ts +21 -0
  9. package/dist/_esm5.processed/compat/has_issues_with_high_media_source_duration.js +26 -0
  10. package/dist/_esm5.processed/config.d.ts +2 -0
  11. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +5 -4
  12. package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.d.ts +18 -1
  13. package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.js +106 -25
  14. package/dist/_esm5.processed/core/adaptive/guess_based_chooser.js +6 -6
  15. package/dist/_esm5.processed/core/adaptive/network_analyzer.js +8 -5
  16. package/dist/_esm5.processed/core/adaptive/utils/representation_score_calculator.d.ts +19 -1
  17. package/dist/_esm5.processed/core/adaptive/utils/representation_score_calculator.js +1 -1
  18. package/dist/_esm5.processed/core/api/debug/render.js +1 -1
  19. package/dist/_esm5.processed/core/api/playback_observer.js +1 -0
  20. package/dist/_esm5.processed/core/api/public_api.d.ts +54 -1
  21. package/dist/_esm5.processed/core/api/public_api.js +232 -35
  22. package/dist/_esm5.processed/core/api/track_management/media_element_tracks_store.js +10 -1
  23. package/dist/_esm5.processed/core/api/track_management/track_dispatcher.d.ts +13 -1
  24. package/dist/_esm5.processed/core/api/track_management/track_dispatcher.js +30 -15
  25. package/dist/_esm5.processed/core/api/track_management/tracks_store.d.ts +3 -1
  26. package/dist/_esm5.processed/core/api/track_management/tracks_store.js +67 -152
  27. package/dist/_esm5.processed/core/api/utils.d.ts +10 -0
  28. package/dist/_esm5.processed/core/api/utils.js +20 -0
  29. package/dist/_esm5.processed/core/decrypt/session_events_listener.js +7 -1
  30. package/dist/_esm5.processed/core/decrypt/utils/clean_old_loaded_sessions.js +2 -0
  31. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +5 -1
  32. package/dist/_esm5.processed/core/init/directfile_content_initializer.js +1 -1
  33. package/dist/_esm5.processed/core/init/media_source_content_initializer.js +47 -10
  34. package/dist/_esm5.processed/core/init/types.d.ts +9 -1
  35. package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.d.ts +28 -1
  36. package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.js +22 -9
  37. package/dist/_esm5.processed/core/init/utils/media_source_duration_updater.d.ts +58 -0
  38. package/dist/_esm5.processed/core/init/utils/{media_duration_updater.js → media_source_duration_updater.js} +84 -87
  39. package/dist/_esm5.processed/core/init/utils/rebuffering_controller.d.ts +36 -2
  40. package/dist/_esm5.processed/core/init/utils/rebuffering_controller.js +82 -2
  41. package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.d.ts +18 -7
  42. package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.js +31 -40
  43. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.d.ts +8 -0
  44. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.js +12 -0
  45. package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.d.ts +8 -0
  46. package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.js +12 -0
  47. package/dist/_esm5.processed/core/segment_buffers/implementations/types.d.ts +11 -4
  48. package/dist/_esm5.processed/core/segment_buffers/index.d.ts +2 -2
  49. package/dist/_esm5.processed/core/stream/adaptation/utils/create_representation_estimator.d.ts +47 -0
  50. package/dist/_esm5.processed/core/stream/adaptation/utils/create_representation_estimator.js +70 -0
  51. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +15 -8
  52. package/dist/_esm5.processed/core/stream/period/period_stream.js +1 -1
  53. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +22 -13
  54. package/dist/_esm5.processed/core/stream/representation/utils/append_segment_to_buffer.d.ts +4 -2
  55. package/dist/_esm5.processed/core/stream/representation/utils/append_segment_to_buffer.js +2 -2
  56. package/dist/_esm5.processed/core/stream/representation/utils/push_init_segment.d.ts +3 -2
  57. package/dist/_esm5.processed/core/stream/representation/utils/push_init_segment.js +8 -8
  58. package/dist/_esm5.processed/core/stream/representation/utils/push_media_segment.d.ts +2 -2
  59. package/dist/_esm5.processed/core/stream/representation/utils/push_media_segment.js +2 -3
  60. package/dist/_esm5.processed/default_config.d.ts +25 -0
  61. package/dist/_esm5.processed/default_config.js +27 -2
  62. package/dist/_esm5.processed/errors/index.d.ts +2 -2
  63. package/dist/_esm5.processed/errors/media_error.d.ts +23 -1
  64. package/dist/_esm5.processed/errors/media_error.js +18 -5
  65. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/load_and_push_segment.d.ts +1 -1
  66. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/load_and_push_segment.js +8 -7
  67. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +17 -9
  68. package/dist/_esm5.processed/experimental/tools/mediaCapabilitiesProber/index.js +0 -2
  69. package/dist/_esm5.processed/manifest/adaptation.d.ts +21 -2
  70. package/dist/_esm5.processed/manifest/adaptation.js +76 -1
  71. package/dist/_esm5.processed/manifest/manifest.js +1 -1
  72. package/dist/_esm5.processed/manifest/period.js +2 -2
  73. package/dist/_esm5.processed/manifest/representation.d.ts +33 -2
  74. package/dist/_esm5.processed/manifest/representation.js +21 -0
  75. package/dist/_esm5.processed/manifest/utils.js +1 -3
  76. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/parse_from_document.d.ts +1 -1
  77. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/parse_from_document.js +1 -1
  78. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.js +1 -0
  79. package/dist/_esm5.processed/public_types.d.ts +13 -3
  80. package/dist/_esm5.processed/tools/TextTrackRenderer/text_track_renderer.js +1 -1
  81. package/dist/_esm5.processed/transports/smooth/isobmff/create_boxes.d.ts +4 -6
  82. package/dist/_esm5.processed/transports/smooth/isobmff/create_boxes.js +4 -6
  83. package/dist/_esm5.processed/utils/is_null_or_undefined.d.ts +1 -1
  84. package/dist/_esm5.processed/utils/is_null_or_undefined.js +1 -1
  85. package/dist/mpd-parser.wasm +0 -0
  86. package/dist/rx-player.js +4709 -4218
  87. package/dist/rx-player.min.js +1 -1
  88. package/package.json +42 -36
  89. package/scripts/build/generate_build.js +1 -1
  90. package/scripts/fast_demo_build.js +4 -3
  91. package/scripts/generate_full_demo.js +1 -1
  92. package/sonar-project.properties +1 -1
  93. package/src/compat/browser_detection.ts +7 -1
  94. package/src/compat/eme/load_session.ts +1 -1
  95. package/src/compat/has_issues_with_high_media_source_duration.ts +27 -0
  96. package/src/core/adaptive/__tests__/buffer_based_chooser.test.ts +147 -48
  97. package/src/core/adaptive/adaptive_representation_selector.ts +7 -4
  98. package/src/core/adaptive/buffer_based_chooser.ts +144 -26
  99. package/src/core/adaptive/guess_based_chooser.ts +9 -8
  100. package/src/core/adaptive/network_analyzer.ts +9 -4
  101. package/src/core/adaptive/utils/representation_score_calculator.ts +22 -2
  102. package/src/core/api/debug/render.ts +1 -1
  103. package/src/core/api/playback_observer.ts +1 -0
  104. package/src/core/api/public_api.ts +277 -44
  105. package/src/core/api/track_management/media_element_tracks_store.ts +17 -8
  106. package/src/core/api/track_management/track_dispatcher.ts +37 -14
  107. package/src/core/api/track_management/tracks_store.ts +77 -167
  108. package/src/core/api/utils.ts +26 -0
  109. package/src/core/decrypt/session_events_listener.ts +6 -1
  110. package/src/core/decrypt/utils/clean_old_loaded_sessions.ts +2 -1
  111. package/src/core/decrypt/utils/loaded_sessions_store.ts +8 -1
  112. package/src/core/init/directfile_content_initializer.ts +1 -0
  113. package/src/core/init/media_source_content_initializer.ts +52 -9
  114. package/src/core/init/types.ts +9 -1
  115. package/src/core/init/utils/content_time_boundaries_observer.ts +46 -10
  116. package/src/core/init/utils/{media_duration_updater.ts → media_source_duration_updater.ts} +100 -112
  117. package/src/core/init/utils/rebuffering_controller.ts +114 -3
  118. package/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts +56 -55
  119. package/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts +16 -0
  120. package/src/core/segment_buffers/implementations/text/native/native_text_segment_buffer.ts +16 -0
  121. package/src/core/segment_buffers/implementations/types.ts +16 -4
  122. package/src/core/segment_buffers/index.ts +2 -0
  123. package/src/core/stream/adaptation/utils/create_representation_estimator.ts +114 -0
  124. package/src/core/stream/orchestrator/stream_orchestrator.ts +16 -8
  125. package/src/core/stream/period/period_stream.ts +2 -1
  126. package/src/core/stream/representation/representation_stream.ts +34 -22
  127. package/src/core/stream/representation/utils/append_segment_to_buffer.ts +8 -3
  128. package/src/core/stream/representation/utils/push_init_segment.ts +11 -6
  129. package/src/core/stream/representation/utils/push_media_segment.ts +3 -3
  130. package/src/default_config.ts +29 -2
  131. package/src/errors/__tests__/media_error.test.ts +6 -6
  132. package/src/errors/index.ts +4 -1
  133. package/src/errors/media_error.ts +67 -1
  134. package/src/experimental/tools/VideoThumbnailLoader/load_and_push_segment.ts +10 -7
  135. package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +17 -6
  136. package/src/experimental/tools/mediaCapabilitiesProber/index.ts +0 -4
  137. package/src/manifest/__tests__/manifest.test.ts +7 -7
  138. package/src/manifest/__tests__/period.test.ts +90 -45
  139. package/src/manifest/adaptation.ts +89 -1
  140. package/src/manifest/manifest.ts +1 -1
  141. package/src/manifest/period.ts +4 -2
  142. package/src/manifest/representation.ts +67 -1
  143. package/src/manifest/utils.ts +1 -3
  144. package/src/parsers/manifest/dash/js-parser/parse_from_document.ts +1 -1
  145. package/src/parsers/manifest/dash/wasm-parser/ts/dash-wasm-parser.ts +1 -0
  146. package/src/parsers/texttracks/ttml/parse_ttml.ts +1 -1
  147. package/src/public_types.ts +16 -1
  148. package/src/tools/TextTrackRenderer/text_track_renderer.ts +1 -1
  149. package/src/transports/smooth/isobmff/create_boxes.ts +4 -6
  150. package/src/typings/globals.d.ts +20 -20
  151. package/src/utils/is_null_or_undefined.ts +1 -1
  152. package/dist/_esm5.processed/core/init/utils/media_duration_updater.d.ts +0 -56
  153. package/scripts/doc-generator/construct_table_of_contents.js +0 -76
  154. package/scripts/doc-generator/convert_MD_to_HMTL.js +0 -26
  155. package/scripts/doc-generator/create_documentation.js +0 -331
  156. package/scripts/doc-generator/create_documentation_page.js +0 -209
  157. package/scripts/doc-generator/create_page.js +0 -210
  158. package/scripts/doc-generator/generate_header_html.js +0 -147
  159. package/scripts/doc-generator/generate_page_html.js +0 -115
  160. package/scripts/doc-generator/generate_page_list_html.js +0 -92
  161. package/scripts/doc-generator/generate_sidebar_html.js +0 -85
  162. package/scripts/doc-generator/get_search_data_for_content.js +0 -137
  163. package/scripts/doc-generator/index.js +0 -34
  164. package/scripts/doc-generator/parse_doc_configs.js +0 -327
  165. package/scripts/doc-generator/scripts/lunr.js +0 -10
  166. package/scripts/doc-generator/scripts/script.js +0 -451
  167. package/scripts/doc-generator/styles/code.css +0 -99
  168. package/scripts/doc-generator/styles/style.css +0 -835
  169. package/scripts/doc-generator/utils.js +0 -74
@@ -57,6 +57,7 @@ import {
57
57
  ILockedAudioRepresentationsSettings,
58
58
  ILockedVideoRepresentationsSettings,
59
59
  ITrackUpdateEventPayload,
60
+ IRepresentationListUpdateContext,
60
61
  IPeriod,
61
62
  IPeriodChangeEvent,
62
63
  IPlayerError,
@@ -70,7 +71,10 @@ import {
70
71
  IVideoTrack,
71
72
  IVideoTrackSetting,
72
73
  IVideoTrackSwitchingMode,
74
+ ITrackType,
73
75
  } from "../../public_types";
76
+ import arrayFind from "../../utils/array_find";
77
+ import arrayIncludes from "../../utils/array_includes";
74
78
  import assert from "../../utils/assert";
75
79
  import assertUnreachable from "../../utils/assert_unreachable";
76
80
  import EventEmitter, {
@@ -123,9 +127,9 @@ import TracksStore, {
123
127
  } from "./track_management/tracks_store";
124
128
  import {
125
129
  constructPlayerStateReference,
130
+ emitPlayPauseEvents,
126
131
  emitSeekEvents,
127
132
  isLoadedState,
128
- // emitSeekEvents,
129
133
  PLAYER_STATES,
130
134
  } from "./utils";
131
135
 
@@ -280,6 +284,11 @@ class Player extends EventEmitter<IPublicAPIEvent> {
280
284
  reloadPosition?: number;
281
285
  };
282
286
 
287
+ /**
288
+ * Store last value of autoPlay, from the last load or reload.
289
+ */
290
+ private _priv_lastAutoPlay: boolean;
291
+
283
292
  /** All possible Error types emitted by the RxPlayer. */
284
293
  static get ErrorTypes() : Record<IErrorType, IErrorType> {
285
294
  return ErrorTypes;
@@ -327,7 +336,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
327
336
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
328
337
  videoElement.preload = "auto";
329
338
 
330
- this.version = /* PLAYER_VERSION */"4.0.0-beta.1";
339
+ this.version = /* PLAYER_VERSION */"4.0.0-beta.2";
331
340
  this.log = log;
332
341
  this.state = "STOPPED";
333
342
  this.videoElement = videoElement;
@@ -372,6 +381,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
372
381
  this._priv_setPlayerState(PLAYER_STATES.STOPPED);
373
382
 
374
383
  this._priv_reloadingMetadata = {};
384
+
385
+ this._priv_lastAutoPlay = false;
375
386
  }
376
387
 
377
388
  /**
@@ -442,6 +453,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
442
453
  log.info("API: Calling loadvideo", options.url, options.transport);
443
454
  this._priv_reloadingMetadata = { options };
444
455
  this._priv_initializeContentPlayback(options);
456
+ this._priv_lastAutoPlay = options.autoPlay;
445
457
  }
446
458
 
447
459
  /**
@@ -683,26 +695,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
683
695
 
684
696
  // Bind events
685
697
  initializer.addEventListener("error", (error) => {
686
- const formattedError = formatError(error, {
687
- defaultCode: "NONE",
688
- defaultReason: "An unknown error stopped content playback.",
689
- });
690
- formattedError.fatal = true;
691
-
692
- contentInfos.currentContentCanceller.cancel();
693
- this._priv_cleanUpCurrentContentState();
694
- this._priv_currentError = formattedError;
695
- log.error("API: The player stopped because of an error",
696
- error instanceof Error ? error : "");
697
- this._priv_setPlayerState(PLAYER_STATES.STOPPED);
698
-
699
- // TODO This condition is here because the eventual callback called when the
700
- // player state is updated can launch a new content, thus the error will not
701
- // be here anymore, in which case triggering the "error" event is unwanted.
702
- // This is very ugly though, and we should probable have a better solution
703
- if (this._priv_currentError === formattedError) {
704
- this.trigger("error", formattedError);
705
- }
698
+ this._priv_onFatalError(error, contentInfos);
706
699
  });
707
700
  initializer.addEventListener("warning", (error) => {
708
701
  const formattedError = formatError(error, {
@@ -712,11 +705,12 @@ class Player extends EventEmitter<IPublicAPIEvent> {
712
705
  log.warn("API: Sending warning:", formattedError);
713
706
  this.trigger("warning", formattedError);
714
707
  });
715
- initializer.addEventListener("reloadingMediaSource", () => {
708
+ initializer.addEventListener("reloadingMediaSource", (payload) => {
716
709
  contentInfos.segmentBuffersStore = null;
717
710
  if (contentInfos.tracksStore !== null) {
718
711
  contentInfos.tracksStore.resetPeriodObjects();
719
712
  }
713
+ this._priv_lastAutoPlay = payload.autoPlay;
720
714
  });
721
715
  initializer.addEventListener("inbandEvents", (inbandEvents) =>
722
716
  this.trigger("inbandEvents", inbandEvents));
@@ -800,6 +794,53 @@ class Player extends EventEmitter<IPublicAPIEvent> {
800
794
  }
801
795
  };
802
796
 
797
+ /**
798
+ * `TaskCanceller` allowing to stop emitting `"play"` and `"pause"`
799
+ * events.
800
+ * `null` when such events are not emitted currently.
801
+ */
802
+ let playPauseEventsCanceller : TaskCanceller | null = null;
803
+
804
+ /**
805
+ * Callback emitting `"play"` and `"pause`" events once the content is
806
+ * loaded, starting from the state indicated in argument.
807
+ * @param {boolean} willAutoPlay - If `false`, we're currently paused.
808
+ */
809
+ const triggerPlayPauseEventsWhenReady = (willAutoPlay: boolean) => {
810
+ if (playPauseEventsCanceller !== null) {
811
+ playPauseEventsCanceller.cancel(); // cancel previous logic
812
+ playPauseEventsCanceller = null;
813
+ }
814
+ playerStateRef.onUpdate((val, stopListeningToStateUpdates) => {
815
+ if (!isLoadedState(val)) {
816
+ return; // content not loaded yet: no event
817
+ }
818
+ stopListeningToStateUpdates();
819
+ if (playPauseEventsCanceller !== null) {
820
+ playPauseEventsCanceller.cancel();
821
+ }
822
+ playPauseEventsCanceller = new TaskCanceller();
823
+ playPauseEventsCanceller.linkToSignal(currentContentCanceller.signal);
824
+ if (willAutoPlay !== !videoElement.paused) {
825
+ // paused status is not at the expected value on load: emit event
826
+ if (videoElement.paused) {
827
+ this.trigger("pause", null);
828
+ } else {
829
+ this.trigger("play", null);
830
+ }
831
+ }
832
+ emitPlayPauseEvents(videoElement,
833
+ () => this.trigger("play", null),
834
+ () => this.trigger("pause", null),
835
+ currentContentCanceller.signal);
836
+ }, { emitCurrentValue: false, clearSignal: currentContentCanceller.signal });
837
+ };
838
+
839
+ triggerPlayPauseEventsWhenReady(autoPlay);
840
+ initializer.addEventListener("reloadingMediaSource", (payload) => {
841
+ triggerPlayPauseEventsWhenReady(payload.autoPlay);
842
+ });
843
+
803
844
  /**
804
845
  * `TaskCanceller` allowing to stop emitting `"seeking"` and `"seeked"`
805
846
  * events.
@@ -884,6 +925,42 @@ class Player extends EventEmitter<IPublicAPIEvent> {
884
925
  return this.state;
885
926
  }
886
927
 
928
+ /**
929
+ * Returns true if a content is loaded.
930
+ * @returns {Boolean} - `true` if a content is loaded, `false` otherwise.
931
+ */
932
+ isContentLoaded() : boolean {
933
+ return !arrayIncludes(["LOADING", "RELOADING", "STOPPED"], this.state);
934
+ }
935
+
936
+ /**
937
+ * Returns true if the player is buffering.
938
+ * @returns {Boolean} - `true` if the player is buffering, `false` otherwise.
939
+ */
940
+ isBuffering() : boolean {
941
+ return arrayIncludes(["BUFFERING", "SEEKING", "LOADING", "RELOADING"], this.state);
942
+ }
943
+
944
+ /**
945
+ * Returns the play/pause status of the player :
946
+ * - when `LOADING` or `RELOADING`, returns the scheduled play/pause condition
947
+ * for when loading is over,
948
+ * - in other states, returns the `<video>` element .paused value,
949
+ * - if the player is disposed, returns `true`.
950
+ * @returns {Boolean} - `true` if the player is paused or will be after loading,
951
+ * `false` otherwise.
952
+ */
953
+ isPaused() : boolean {
954
+ if (this.videoElement) {
955
+ if (arrayIncludes(["LOADING", "RELOADING"], this.state)) {
956
+ return !this._priv_lastAutoPlay;
957
+ } else {
958
+ return this.videoElement.paused;
959
+ }
960
+ }
961
+ return true;
962
+ }
963
+
887
964
  /**
888
965
  * Returns true if both:
889
966
  * - a content is loaded
@@ -1036,6 +1113,15 @@ class Player extends EventEmitter<IPublicAPIEvent> {
1036
1113
  return this.videoElement.currentTime;
1037
1114
  }
1038
1115
 
1116
+ /**
1117
+ * Returns the last stored content position, in seconds.
1118
+ *
1119
+ * @returns {number|undefined}
1120
+ */
1121
+ getLastStoredContentPosition() : number|undefined {
1122
+ return this._priv_reloadingMetadata.reloadPosition;
1123
+ }
1124
+
1039
1125
  /**
1040
1126
  * Returns the current playback rate at which the video plays.
1041
1127
  * @returns {Number}
@@ -1251,6 +1337,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
1251
1337
  if (positionWanted === undefined) {
1252
1338
  throw new Error("invalid time given");
1253
1339
  }
1340
+ log.info("API: API Seek to", positionWanted);
1254
1341
  this.videoElement.currentTime = positionWanted;
1255
1342
  return positionWanted;
1256
1343
  }
@@ -2001,21 +2088,34 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2001
2088
  return; // Event for another content
2002
2089
  }
2003
2090
  contentInfos.manifest = manifest;
2004
- const cancelSignal = contentInfos.currentContentCanceller.signal;
2005
2091
  this._priv_reloadingMetadata.manifest = manifest;
2006
2092
 
2007
- contentInfos.tracksStore = new TracksStore({
2093
+ const tracksStore = new TracksStore({
2008
2094
  preferTrickModeTracks: this._priv_preferTrickModeTracks,
2009
2095
  defaultAudioTrackSwitchingMode: contentInfos.defaultAudioTrackSwitchingMode,
2010
2096
  });
2011
- contentInfos.tracksStore.addEventListener("newAvailablePeriods", (p) => {
2097
+ contentInfos.tracksStore = tracksStore;
2098
+ tracksStore.addEventListener("newAvailablePeriods", (p) => {
2012
2099
  this.trigger("newAvailablePeriods", p);
2013
2100
  });
2014
- contentInfos.tracksStore.addEventListener("brokenRepresentationsLock", (e) => {
2101
+ tracksStore.addEventListener("brokenRepresentationsLock", (e) => {
2015
2102
  this.trigger("brokenRepresentationsLock", e);
2016
2103
  });
2017
- contentInfos.tracksStore.addEventListener("trackUpdate", (e) => {
2104
+ tracksStore.addEventListener("trackUpdate", (e) => {
2018
2105
  this.trigger("trackUpdate", e);
2106
+
2107
+ const currentPeriod = this._priv_contentInfos?.currentPeriod ?? undefined;
2108
+ if (e.reason === "no-playable-representation" &&
2109
+ e.period.id === currentPeriod?.id)
2110
+ {
2111
+ this._priv_onAvailableTracksMayHaveChanged(e.trackType);
2112
+ }
2113
+ });
2114
+ contentInfos.tracksStore.addEventListener("warning", (err) => {
2115
+ this.trigger("warning", err);
2116
+ });
2117
+ contentInfos.tracksStore.addEventListener("error", (err) => {
2118
+ this._priv_onFatalError(err, contentInfos);
2019
2119
  });
2020
2120
 
2021
2121
  contentInfos.tracksStore.updatePeriodList(manifest);
@@ -2026,8 +2126,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2026
2126
  contentInfos.tracksStore.updatePeriodList(manifest);
2027
2127
  }
2028
2128
  const currentPeriod = this._priv_contentInfos?.currentPeriod ?? undefined;
2029
- const tracksStore = this._priv_contentInfos?.tracksStore;
2030
- if (currentPeriod === undefined || isNullOrUndefined(tracksStore)) {
2129
+ const currTracksStore = this._priv_contentInfos?.tracksStore;
2130
+ if (currentPeriod === undefined || isNullOrUndefined(currTracksStore)) {
2031
2131
  return;
2032
2132
  }
2033
2133
  for (const update of updates.updatedPeriods) {
@@ -2036,27 +2136,72 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2036
2136
  update.result.removedAdaptations.length > 0)
2037
2137
  {
2038
2138
  // We might have new (or less) tracks, send events just to be sure
2039
- const periodRef = tracksStore.getPeriodObjectFromPeriod(currentPeriod);
2139
+ const periodRef = currTracksStore.getPeriodObjectFromPeriod(currentPeriod);
2040
2140
  if (periodRef === undefined) {
2041
2141
  return;
2042
2142
  }
2043
- const audioTracks = tracksStore.getAvailableAudioTracks(periodRef);
2044
- this._priv_triggerEventIfNotStopped("availableAudioTracksChange",
2045
- audioTracks ?? [],
2046
- cancelSignal);
2047
- const textTracks = tracksStore.getAvailableTextTracks(periodRef);
2048
- this._priv_triggerEventIfNotStopped("availableTextTracksChange",
2049
- textTracks ?? [],
2050
- cancelSignal);
2051
- const videoTracks = tracksStore.getAvailableVideoTracks(periodRef);
2052
- this._priv_triggerEventIfNotStopped("availableVideoTracksChange",
2053
- videoTracks ?? [],
2054
- cancelSignal);
2143
+ this._priv_onAvailableTracksMayHaveChanged("audio");
2144
+ this._priv_onAvailableTracksMayHaveChanged("text");
2145
+ this._priv_onAvailableTracksMayHaveChanged("video");
2055
2146
  }
2056
2147
  }
2057
2148
  return;
2058
2149
  }
2059
2150
  }, contentInfos.currentContentCanceller.signal);
2151
+
2152
+ manifest.addEventListener("decipherabilityUpdate", (elts) => {
2153
+ /**
2154
+ * Array of tuples only including once the Period/Track combination, and
2155
+ * only when it concerns the currently-selected track.
2156
+ */
2157
+ const periodsAndTrackTypes = elts.reduce(
2158
+ (acc: Array<[Period, ITrackType]>, elt) => {
2159
+ const isFound = arrayFind(
2160
+ acc,
2161
+ (x) => x[0].id === elt.period.id &&
2162
+ x[1] === elt.adaptation.type
2163
+ ) === undefined;
2164
+
2165
+ if (!isFound) {
2166
+ // Only consider the currently-selected tracks.
2167
+ // NOTE: Maybe there's room for optimizations? Unclear.
2168
+ const tStore = contentInfos.tracksStore;
2169
+ if (tStore === null) {
2170
+ return acc;
2171
+ }
2172
+ let isCurrent = false;
2173
+ const periodRef = tStore.getPeriodObjectFromPeriod(elt.period);
2174
+ if (periodRef === undefined) {
2175
+ return acc;
2176
+ }
2177
+ switch (elt.adaptation.type) {
2178
+ case "audio":
2179
+ isCurrent = tStore
2180
+ .getChosenAudioTrack(periodRef)?.id === elt.adaptation.id;
2181
+ break;
2182
+ case "video":
2183
+ isCurrent = tStore
2184
+ .getChosenVideoTrack(periodRef)?.id === elt.adaptation.id;
2185
+ break;
2186
+ case "text":
2187
+ isCurrent = tStore
2188
+ .getChosenTextTrack(periodRef)?.id === elt.adaptation.id;
2189
+ break;
2190
+ }
2191
+ if (isCurrent) {
2192
+ acc.push([elt.period, elt.adaptation.type]);
2193
+ }
2194
+ }
2195
+ return acc;
2196
+ }, []);
2197
+ for (const [period, trackType] of periodsAndTrackTypes) {
2198
+ this._priv_triggerEventIfNotStopped(
2199
+ "representationListUpdate",
2200
+ { period, trackType, reason: "decipherability-update" },
2201
+ contentInfos.currentContentCanceller.signal
2202
+ );
2203
+ }
2204
+ }, contentInfos.currentContentCanceller.signal);
2060
2205
  }
2061
2206
 
2062
2207
  /**
@@ -2525,8 +2670,93 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2525
2670
  }
2526
2671
  return cb(tracksStore, periodRef);
2527
2672
  }
2673
+
2674
+ /**
2675
+ * Method to call when some event lead to a high for possibility that the
2676
+ * available tracks for the given type have changed.
2677
+ * Send the corresponding `available*Tracks` change event with the last
2678
+ * available tracks.
2679
+ *
2680
+ * @param {string} trackType
2681
+ * @param {Object|undefined} [oPeriodRef] - optional period object used by the
2682
+ * `tracksStore` API, allows to optimize the method by bypassing this step.
2683
+ */
2684
+ private _priv_onAvailableTracksMayHaveChanged(
2685
+ trackType : IBufferType,
2686
+ oPeriodRef? : ITMPeriodObject
2687
+ ): void {
2688
+ const contentInfos = this._priv_contentInfos;
2689
+ if (contentInfos === null) {
2690
+ return;
2691
+ }
2692
+ const { currentPeriod,
2693
+ tracksStore,
2694
+ currentContentCanceller } = contentInfos;
2695
+ const cancelSignal = currentContentCanceller.signal;
2696
+ if (isNullOrUndefined(currentPeriod) || tracksStore === null) {
2697
+ return;
2698
+ }
2699
+ const periodRef = oPeriodRef ?? tracksStore.getPeriodObjectFromPeriod(currentPeriod);
2700
+ if (periodRef === undefined) {
2701
+ return;
2702
+ }
2703
+ switch (trackType) {
2704
+ case "video":
2705
+ const videoTracks = tracksStore.getAvailableVideoTracks(periodRef);
2706
+ this._priv_triggerEventIfNotStopped("availableVideoTracksChange",
2707
+ videoTracks ?? [],
2708
+ cancelSignal);
2709
+ break;
2710
+ case "audio":
2711
+ const audioTracks = tracksStore.getAvailableAudioTracks(periodRef);
2712
+ this._priv_triggerEventIfNotStopped("availableAudioTracksChange",
2713
+ audioTracks ?? [],
2714
+ cancelSignal);
2715
+ break;
2716
+ case "text":
2717
+ const textTracks = tracksStore.getAvailableTextTracks(periodRef);
2718
+ this._priv_triggerEventIfNotStopped("availableTextTracksChange",
2719
+ textTracks ?? [],
2720
+ cancelSignal);
2721
+ break;
2722
+ default:
2723
+ assertUnreachable(trackType);
2724
+ }
2725
+ }
2726
+
2727
+ /**
2728
+ * Method to call when a fatal error lead to the stopping of the current
2729
+ * content.
2730
+ *
2731
+ * @param {*} err - The error encountered.
2732
+ * @param {Object} contentInfos - The `IPublicApiContentInfos` object linked
2733
+ * to the content for which the error was received.
2734
+ */
2735
+ private _priv_onFatalError(
2736
+ err : unknown,
2737
+ contentInfos : IPublicApiContentInfos
2738
+ ): void {
2739
+ const formattedError = formatError(err, {
2740
+ defaultCode: "NONE",
2741
+ defaultReason: "An unknown error stopped content playback.",
2742
+ });
2743
+ formattedError.fatal = true;
2744
+ contentInfos.currentContentCanceller.cancel();
2745
+ this._priv_cleanUpCurrentContentState();
2746
+ this._priv_currentError = formattedError;
2747
+ log.error("API: The player stopped because of an error", formattedError);
2748
+ this._priv_setPlayerState(PLAYER_STATES.STOPPED);
2749
+
2750
+ // TODO This condition is here because the eventual callback called when the
2751
+ // player state is updated can launch a new content, thus the error will not
2752
+ // be here anymore, in which case triggering the "error" event is unwanted.
2753
+ // This is very ugly though, and we should probable have a better solution
2754
+ if (this._priv_currentError === formattedError) {
2755
+ this.trigger("error", formattedError);
2756
+ }
2757
+ }
2528
2758
  }
2529
- Player.version = /* PLAYER_VERSION */"4.0.0-beta.1";
2759
+ Player.version = /* PLAYER_VERSION */"4.0.0-beta.2";
2530
2760
 
2531
2761
  /** Every events sent by the RxPlayer's public API. */
2532
2762
  interface IPublicAPIEvent {
@@ -2544,9 +2774,12 @@ interface IPublicAPIEvent {
2544
2774
  availableAudioTracksChange : IAvailableAudioTrack[];
2545
2775
  availableTextTracksChange : IAvailableTextTrack[];
2546
2776
  availableVideoTracksChange : IAvailableVideoTrack[];
2777
+ play: null;
2778
+ pause: null;
2547
2779
  newAvailablePeriods : IPeriod[];
2548
2780
  brokenRepresentationsLock : IBrokenRepresentationsLockContext;
2549
2781
  trackUpdate : ITrackUpdateEventPayload;
2782
+ representationListUpdate : IRepresentationListUpdateContext;
2550
2783
  seeking : null;
2551
2784
  seeked : null;
2552
2785
  streamEvent : IStreamEvent;
@@ -118,10 +118,7 @@ function createAudioTracks(
118
118
  */
119
119
  function createTextTracks(
120
120
  textTracks: ICompatTextTrackList
121
- ): Array<{ track: { id: string;
122
- normalized: string;
123
- language: string;
124
- closedCaption: boolean; };
121
+ ): Array<{ track: ITextTrack;
125
122
  nativeTrack: TextTrack; }> {
126
123
  const newTextTracks = [];
127
124
  const languagesOccurences: Partial<Record<string, number>> = {};
@@ -135,10 +132,20 @@ function createTextTracks(
135
132
  "_" +
136
133
  occurences.toString();
137
134
  languagesOccurences[language] = occurences + 1;
138
- const track = { language: textTrack.language,
139
- id,
140
- normalized: normalizeLanguage(textTrack.language),
141
- closedCaption: textTrack.kind === "captions" };
135
+
136
+ // Safari seems to be indicating that the subtitles track is a forced
137
+ // subtitles track by setting the `kind` attribute to `"forced"`.
138
+ // As of now (2023-04-04), this is not standard.
139
+ // @see https://github.com/whatwg/html/issues/4472
140
+ const forced = (textTrack.kind as string) === "forced" ?
141
+ true :
142
+ undefined;
143
+ const track = { language: textTrack.language,
144
+ forced,
145
+ label: textTrack.label,
146
+ id,
147
+ normalized: normalizeLanguage(textTrack.language),
148
+ closedCaption: textTrack.kind === "captions" };
142
149
  newTextTracks.push({ track,
143
150
  nativeTrack: textTrack });
144
151
  }
@@ -393,6 +400,8 @@ export default class MediaElementTracksStore
393
400
  public getAvailableTextTracks(): IAvailableTextTrack[] {
394
401
  return this._textTracks.map(({ track, nativeTrack }) => {
395
402
  return { id: track.id,
403
+ label: track.label,
404
+ forced: track.forced,
396
405
  language: track.language,
397
406
  normalized: track.normalized,
398
407
  closedCaption: track.closedCaption,
@@ -1,4 +1,3 @@
1
- import { MediaError } from "../../../errors";
2
1
  import Manifest, {
3
2
  Adaptation,
4
3
  Representation,
@@ -63,6 +62,15 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
63
62
  /** Interface allowing to clean-up resources when they are not needed anymore. */
64
63
  private _canceller : TaskCanceller;
65
64
 
65
+ /**
66
+ * Boolean set to `true` when a track-updating method is called and to `false`
67
+ * just before it performs the actual track change to allow checking for
68
+ * re-entrancy: if the token is already reset to `false` before the
69
+ * track change is officialized, then another track update has already been
70
+ * performed in the meantime.
71
+ */
72
+ private _updateToken: boolean;
73
+
66
74
  /**
67
75
  * Create a new `TrackDispatcher` by giving its Reference and an initial track
68
76
  * setting.
@@ -70,30 +78,40 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
70
78
  * synchronously.
71
79
  * @param {Object} manifest
72
80
  * @param {Object} adaptationRef
73
- * @param {Object|null} initialTrackInfo
74
81
  */
75
82
  constructor(
76
83
  manifest : Manifest,
77
- adaptationRef : ISharedReference<IAdaptationChoice | null | undefined>,
78
- initialTrackInfo : ITrackSetting | null
84
+ adaptationRef : ISharedReference<IAdaptationChoice | null | undefined>
79
85
  ) {
80
86
  super();
81
87
  this._canceller = new TaskCanceller();
82
88
  this._manifest = manifest;
83
89
  this._adaptationRef = adaptationRef;
90
+ this._updateToken = false;
91
+ }
84
92
 
93
+ /**
94
+ * @param {Object|null} initialTrackInfo
95
+ */
96
+ public start(initialTrackInfo : ITrackSetting | null) : void {
97
+ this._updateToken = true;
85
98
  if (initialTrackInfo === null) {
86
- this._lastEmitted = initialTrackInfo;
87
- adaptationRef.setValue(null);
99
+ this._lastEmitted = null;
100
+ this._updateToken = false;
101
+ this._adaptationRef.setValue(null);
88
102
  return;
89
103
  }
90
104
  const reference = this._constructLockedRepresentationsReference(initialTrackInfo);
105
+ if (!this._updateToken) {
106
+ return;
107
+ }
91
108
  this._lastEmitted = { adaptation: initialTrackInfo.adaptation,
92
109
  switchingMode: initialTrackInfo.switchingMode,
93
110
  lockedRepresentations: null };
94
- adaptationRef.setValue({ adaptation: initialTrackInfo.adaptation,
95
- switchingMode: initialTrackInfo.switchingMode,
96
- representations: reference });
111
+ this._updateToken = false;
112
+ this._adaptationRef.setValue({ adaptation: initialTrackInfo.adaptation,
113
+ switchingMode: initialTrackInfo.switchingMode,
114
+ representations: reference });
97
115
  }
98
116
 
99
117
  /**
@@ -101,10 +119,12 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
101
119
  * @param {Object|null} newTrackInfo
102
120
  */
103
121
  public updateTrack(newTrackInfo : ITrackSetting | null) : void {
122
+ this._updateToken = true;
104
123
  if (newTrackInfo === null) {
105
124
  if (this._lastEmitted === null) {
106
125
  return;
107
126
  }
127
+ this._updateToken = false;
108
128
  this._canceller.cancel();
109
129
 
110
130
  // has no point but let's still create one for simplicity sake
@@ -117,7 +137,11 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
117
137
  this._canceller.cancel();
118
138
  this._canceller = new TaskCanceller();
119
139
  const reference = this._constructLockedRepresentationsReference(newTrackInfo);
140
+ if (!this._updateToken) {
141
+ return;
142
+ }
120
143
  this._lastEmitted = { adaptation, switchingMode, lockedRepresentations: null };
144
+ this._updateToken = false;
121
145
  this._adaptationRef.setValue({ adaptation,
122
146
  switchingMode,
123
147
  representations: reference });
@@ -176,11 +200,9 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
176
200
  }
177
201
  }
178
202
  if (playableRepresentations.length <= 0) {
179
- const adaptationType = trackInfo.adaptation.type;
180
- const noRepErr = new MediaError("NO_PLAYABLE_REPRESENTATION",
181
- "No Representation in the chosen " +
182
- adaptationType + " Adaptation can be played");
183
- throw noRepErr;
203
+ trackInfo.adaptation.isSupported = false;
204
+ self.trigger("noPlayableRepresentation", null);
205
+ return;
184
206
  }
185
207
 
186
208
  // Check if Locked Representations have changed
@@ -221,6 +243,7 @@ export interface ITrackDispatcherEvent {
221
243
  * none of them are currently "playable".
222
244
  */
223
245
  noPlayableLockedRepresentation : null;
246
+ noPlayableRepresentation: null;
224
247
  }
225
248
 
226
249
  /** Define a new Track preference given to the `TrackDispatcher`. */