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
@@ -20,6 +20,7 @@
20
20
  */
21
21
 
22
22
  import config from "../../../config";
23
+ import { MediaError } from "../../../errors";
23
24
  import log from "../../../log";
24
25
  import Manifest, {
25
26
  Adaptation,
@@ -27,7 +28,6 @@ import Manifest, {
27
28
  Representation,
28
29
  } from "../../../manifest";
29
30
  import {
30
- IAudioRepresentation,
31
31
  IAudioRepresentationsSwitchingMode,
32
32
  IAudioTrack,
33
33
  IAudioTrackSwitchingMode,
@@ -38,18 +38,19 @@ import {
38
38
  IBrokenRepresentationsLockContext,
39
39
  IPeriod,
40
40
  ITextTrack,
41
- IVideoRepresentation,
42
41
  IVideoRepresentationsSwitchingMode,
43
42
  IVideoTrack,
44
43
  IVideoTrackSwitchingMode,
44
+ IPlayerError,
45
45
  } from "../../../public_types";
46
46
  import arrayFind from "../../../utils/array_find";
47
47
  import assert from "../../../utils/assert";
48
48
  import EventEmitter from "../../../utils/event_emitter";
49
+ import isNullOrUndefined from "../../../utils/is_null_or_undefined";
50
+ import objectAssign from "../../../utils/object_assign";
49
51
  import createSharedReference, {
50
52
  ISharedReference,
51
53
  } from "../../../utils/reference";
52
- import takeFirstSet from "../../../utils/take_first_set";
53
54
  import {
54
55
  IAdaptationChoice,
55
56
  IRepresentationsChoice,
@@ -156,7 +157,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
156
157
  const stillHere = textAdaptations
157
158
  .some(a => a.id === curWantedTextTrack.adaptation.id);
158
159
  if (!stillHere) {
159
- log.warn("TracksStore: Chosen text Adaptation not available anymore");
160
+ log.warn("TS: Chosen text Adaptation not available anymore");
160
161
  const periodInfo = this._storedPeriodInfo[i];
161
162
  periodInfo.text.storedSettings = null;
162
163
  this.trigger("trackUpdate", { period: toExposedPeriod(newPeriod),
@@ -182,7 +183,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
182
183
  const stillHere = videoAdaptations
183
184
  .some(a => a.id === curWantedVideoTrack.adaptation.id);
184
185
  if (!stillHere) {
185
- log.warn("TracksStore: Chosen video Adaptation not available anymore");
186
+ log.warn("TS: Chosen video Adaptation not available anymore");
186
187
  const periodItem = this._storedPeriodInfo[i];
187
188
  let storedSettings : IVideoStoredSettings;
188
189
  if (videoAdaptations.length === 0) {
@@ -224,7 +225,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
224
225
  const stillHere = audioAdaptations
225
226
  .some(a => a.id === curWantedAudioTrack.adaptation.id);
226
227
  if (!stillHere) {
227
- log.warn("TracksStore: Chosen audio Adaptation not available anymore");
228
+ log.warn("TS: Chosen audio Adaptation not available anymore");
228
229
  const periodItem = this._storedPeriodInfo[i];
229
230
  const storedSettings = audioAdaptations.length === 0 ?
230
231
  null :
@@ -323,20 +324,62 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
323
324
  }
324
325
 
325
326
  if (periodObj[bufferType].dispatcher !== null) {
326
- log.error(`TracksStore: Subject already added for ${bufferType} ` +
327
+ log.error(`TS: Subject already added for ${bufferType} ` +
327
328
  `and Period ${period.start}`);
328
329
  return;
329
330
  }
330
331
  const trackSetting = periodObj[bufferType].storedSettings;
331
- const dispatcher = new TrackDispatcher(manifest, adaptationRef, trackSetting);
332
+ const dispatcher = new TrackDispatcher(manifest, adaptationRef);
332
333
  periodObj[bufferType].dispatcher = dispatcher;
334
+ dispatcher.addEventListener("noPlayableRepresentation", () => {
335
+ const nextAdaptation = arrayFind(period.getAdaptationsForType(bufferType), (a) => {
336
+ const playableRepresentations = a.getPlayableRepresentations();
337
+ return playableRepresentations.length > 0;
338
+ });
339
+ if (nextAdaptation === undefined) {
340
+ const noRepErr = new MediaError("NO_PLAYABLE_REPRESENTATION",
341
+ `No ${bufferType} Representation can be played`,
342
+ { adaptation: undefined });
343
+ this.trigger("error", noRepErr);
344
+ this.dispose();
345
+ return;
346
+ }
347
+ let typeInfo = getPeriodItem(this._storedPeriodInfo, period.id)?.[bufferType];
348
+ if (isNullOrUndefined(typeInfo)) {
349
+ return;
350
+ }
351
+ const switchingMode = bufferType === "audio" ?
352
+ this._defaultAudioTrackSwitchingMode :
353
+ "reload";
354
+ const storedSettings = { adaptation: nextAdaptation,
355
+ switchingMode,
356
+ lockedRepresentations: createSharedReference(null) };
357
+ typeInfo.storedSettings = storedSettings;
358
+ this.trigger("trackUpdate", { period: toExposedPeriod(period),
359
+ trackType: bufferType,
360
+ reason: "no-playable-representation" });
361
+
362
+ // The previous event trigger could have had side-effects, so we
363
+ // re-check if we're still mostly in the same state
364
+ if (this._isDisposed) {
365
+ return; // Someone disposed the `TracksStore` on the previous side-effect
366
+ }
367
+ typeInfo = getPeriodItem(this._storedPeriodInfo, period.id)?.[bufferType];
368
+ if (isNullOrUndefined(typeInfo) || typeInfo.storedSettings !== storedSettings) {
369
+ return;
370
+ }
371
+ typeInfo.dispatcher?.updateTrack(storedSettings);
372
+ });
333
373
  dispatcher.addEventListener("noPlayableLockedRepresentation", () => {
374
+ // TODO check that it doesn't already lead to segment loading or MediaSource
375
+ // reloading
334
376
  trackSetting?.lockedRepresentations.setValue(null);
335
377
  this.trigger("brokenRepresentationsLock", { period: { id: period.id,
336
378
  start: period.start,
337
379
  end: period.end },
338
380
  trackType: bufferType });
339
381
  });
382
+ dispatcher.start(trackSetting);
340
383
  }
341
384
 
342
385
  /**
@@ -351,7 +394,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
351
394
  ) : void {
352
395
  const periodIndex = findPeriodIndex(this._storedPeriodInfo, period);
353
396
  if (periodIndex === undefined) {
354
- log.warn(`TracksStore: ${bufferType} not found for period`,
397
+ log.warn(`TS: ${bufferType} not found for period`,
355
398
  period.start);
356
399
  return;
357
400
  }
@@ -359,7 +402,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
359
402
  const periodObj = this._storedPeriodInfo[periodIndex];
360
403
  const choiceItem = periodObj[bufferType];
361
404
  if (choiceItem?.dispatcher === null) {
362
- log.warn(`TracksStore: TrackDispatcher already removed for ${bufferType} ` +
405
+ log.warn(`TS: TrackDispatcher already removed for ${bufferType} ` +
363
406
  `and Period ${period.start}`);
364
407
  return;
365
408
  }
@@ -684,10 +727,12 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
684
727
  * `null` if audio tracks were disabled and `undefined` if the Period is not
685
728
  * known.
686
729
  */
687
- public getChosenAudioTrack(periodObj : ITMPeriodObject) : IAudioTrack | null {
730
+ public getChosenAudioTrack(
731
+ periodObj : ITMPeriodObject
732
+ ) : IAudioTrack | null {
688
733
  return periodObj.audio.storedSettings === null ?
689
734
  null :
690
- toAudioTrack(periodObj.audio.storedSettings.adaptation);
735
+ periodObj.audio.storedSettings.adaptation.toAudioTrack(true);
691
736
  }
692
737
 
693
738
  /**
@@ -705,7 +750,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
705
750
  ) : ITextTrack | null {
706
751
  return periodObj.text.storedSettings === null ?
707
752
  null :
708
- toTextTrack(periodObj.text.storedSettings.adaptation);
753
+ periodObj.text.storedSettings.adaptation.toTextTrack();
709
754
  }
710
755
 
711
756
  /**
@@ -725,7 +770,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
725
770
  return null;
726
771
  }
727
772
 
728
- return toVideoTrack(periodObj.video.storedSettings.adaptation);
773
+ return periodObj.video.storedSettings.adaptation.toVideoTrack(true);
729
774
  }
730
775
 
731
776
  /**
@@ -746,21 +791,9 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
746
791
  null;
747
792
  return periodObj.period.getSupportedAdaptations("audio")
748
793
  .map((adaptation) => {
749
- const formatted : IAvailableAudioTrack = {
750
- language: takeFirstSet<string>(adaptation.language, ""),
751
- normalized: takeFirstSet<string>(adaptation.normalizedLanguage, ""),
752
- audioDescription: adaptation.isAudioDescription === true,
753
- id: adaptation.id,
754
- active: currentId === null ? false :
755
- currentId === adaptation.id,
756
- representations: adaptation.getPlayableRepresentations()
757
- .map(parseAudioRepresentation),
758
- label: adaptation.label,
759
- };
760
- if (adaptation.isDub === true) {
761
- formatted.dub = true;
762
- }
763
- return formatted;
794
+ const active = currentId === null ? false :
795
+ currentId === adaptation.id;
796
+ return objectAssign(adaptation.toAudioTrack(true), { active });
764
797
  });
765
798
  }
766
799
 
@@ -783,19 +816,9 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
783
816
 
784
817
  return periodObj.period.getSupportedAdaptations("text")
785
818
  .map((adaptation) => {
786
- const formatted : IAvailableTextTrack = {
787
- language: takeFirstSet<string>(adaptation.language, ""),
788
- normalized: takeFirstSet<string>(adaptation.normalizedLanguage, ""),
789
- closedCaption: adaptation.isClosedCaption === true,
790
- id: adaptation.id,
791
- active: currentId === null ? false :
792
- currentId === adaptation.id,
793
- label: adaptation.label,
794
- };
795
- if (adaptation.isForcedSubtitles !== undefined) {
796
- formatted.forced = adaptation.isForcedSubtitles;
797
- }
798
- return formatted;
819
+ const active = currentId === null ? false :
820
+ currentId === adaptation.id;
821
+ return objectAssign(adaptation.toTextTrack(), { active });
799
822
  });
800
823
  }
801
824
 
@@ -818,38 +841,21 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
818
841
 
819
842
  return periodObj.period.getSupportedAdaptations("video")
820
843
  .map((adaptation) => {
821
- const trickModeTracks = adaptation.trickModeTracks !== undefined ?
822
- adaptation.trickModeTracks.map((trickModeAdaptation) => {
844
+ const active = currentId === null ? false :
845
+ currentId === adaptation.id;
846
+ const track = adaptation.toVideoTrack(true);
847
+ const trickModeTracks = track.trickModeTracks !== undefined ?
848
+ track.trickModeTracks.map((trickModeAdaptation) => {
823
849
  const isActive = currentId === null ? false :
824
850
  currentId === trickModeAdaptation.id;
825
- const representations = trickModeAdaptation.getPlayableRepresentations()
826
- .map(parseVideoRepresentation);
827
- const trickMode : IAvailableVideoTrack = { id: trickModeAdaptation.id,
828
- representations,
829
- isTrickModeTrack: true,
830
- active: isActive };
831
- if (trickModeAdaptation.isSignInterpreted === true) {
832
- trickMode.signInterpreted = true;
833
- }
834
- return trickMode;
851
+ return objectAssign(trickModeAdaptation, { active: isActive });
835
852
  }) :
836
- undefined;
837
-
838
- const formatted: IAvailableVideoTrack = {
839
- id: adaptation.id,
840
- active: currentId === null ? false :
841
- currentId === adaptation.id,
842
- representations: adaptation.getPlayableRepresentations()
843
- .map(parseVideoRepresentation),
844
- label: adaptation.label,
845
- };
846
- if (adaptation.isSignInterpreted === true) {
847
- formatted.signInterpreted = true;
848
- }
853
+ [];
854
+ const availableTrack = objectAssign(track, { active });
849
855
  if (trickModeTracks !== undefined) {
850
- formatted.trickModeTracks = trickModeTracks;
856
+ availableTrack.trickModeTracks = trickModeTracks;
851
857
  }
852
- return formatted;
858
+ return availableTrack;
853
859
  });
854
860
  }
855
861
 
@@ -1082,17 +1088,6 @@ function getPeriodItem(
1082
1088
  }
1083
1089
  }
1084
1090
 
1085
- /**
1086
- * Parse video Representation into a IVideoRepresentation.
1087
- * @param {Object} representation
1088
- * @returns {Object}
1089
- */
1090
- function parseVideoRepresentation(
1091
- { id, bitrate, frameRate, width, height, codec, hdrInfo } : Representation
1092
- ) : IVideoRepresentation {
1093
- return { id, bitrate, frameRate, width, height, codec, hdrInfo };
1094
- }
1095
-
1096
1091
  /**
1097
1092
  * A `ITMPeriodObject` should only be removed once all References linked to it
1098
1093
  * do not exist anymore, to keep the possibility of making track choices.
@@ -1108,17 +1103,6 @@ function isPeriodItemRemovable(
1108
1103
  periodObj.video?.dispatcher === null;
1109
1104
  }
1110
1105
 
1111
- /**
1112
- * Parse audio Representation into a ITMAudioRepresentation.
1113
- * @param {Object} representation
1114
- * @returns {Object}
1115
- */
1116
- function parseAudioRepresentation(
1117
- { id, bitrate, codec } : Representation
1118
- ) : IAudioRepresentation {
1119
- return { id, bitrate, codec };
1120
- }
1121
-
1122
1106
  function getRightVideoTrack(
1123
1107
  adaptation : Adaptation,
1124
1108
  isTrickModeEnabled : boolean
@@ -1197,82 +1181,6 @@ function generatePeriodInfo(
1197
1181
  dispatcher: null } };
1198
1182
  }
1199
1183
 
1200
- /**
1201
- * Format Adaptation structure into the format awaited by the API.
1202
- * @param {Object} a
1203
- * @returns {Object}
1204
- */
1205
- function toTextTrack(a : Adaptation) : ITextTrack {
1206
- const formatted : ITextTrack = {
1207
- language: a.language ?? "",
1208
- normalized: a.normalizedLanguage ?? "",
1209
- closedCaption: a.isClosedCaption === true,
1210
- id: a.id,
1211
- label: a.label,
1212
- };
1213
- if (a.isForcedSubtitles !== undefined) {
1214
- formatted.forced = a.isForcedSubtitles;
1215
- }
1216
- return formatted;
1217
- }
1218
-
1219
- /**
1220
- * Format Adaptation structure into the format awaited by the API.
1221
- * @param {Object} a
1222
- * @returns {Object}
1223
- */
1224
- function toVideoTrack(a : Adaptation) : IVideoTrack {
1225
- const trickModeTracks = a.trickModeTracks !== undefined ?
1226
- a.trickModeTracks.map((trickModeAdaptation) => {
1227
- const representations = trickModeAdaptation.getPlayableRepresentations()
1228
- .map(parseVideoRepresentation);
1229
- const trickMode : IVideoTrack = { id: trickModeAdaptation.id,
1230
- representations,
1231
- isTrickModeTrack: true };
1232
- if (trickModeAdaptation.isSignInterpreted === true) {
1233
- trickMode.signInterpreted = true;
1234
- }
1235
- return trickMode;
1236
- }) :
1237
- undefined;
1238
-
1239
- const videoTrack: IVideoTrack = {
1240
- id: a.id,
1241
- representations: a.getPlayableRepresentations().map(parseVideoRepresentation),
1242
- label: a.label,
1243
- };
1244
- if (a.isSignInterpreted === true) {
1245
- videoTrack.signInterpreted = true;
1246
- }
1247
- if (a.isTrickModeTrack === true) {
1248
- videoTrack.isTrickModeTrack = true;
1249
- }
1250
- if (trickModeTracks !== undefined) {
1251
- videoTrack.trickModeTracks = trickModeTracks;
1252
- }
1253
- return videoTrack;
1254
- }
1255
-
1256
- /**
1257
- * Convert an audio Adaptation into an audio track.
1258
- * @param {object|null} adaptation - Audio adaptation
1259
- * @returns {object|null} - corresponding audio track object.
1260
- */
1261
- function toAudioTrack(adaptation : Adaptation) : IAudioTrack {
1262
- const audioTrack : IAudioTrack = {
1263
- language: takeFirstSet<string>(adaptation.language, ""),
1264
- normalized: takeFirstSet<string>(adaptation.normalizedLanguage, ""),
1265
- audioDescription: adaptation.isAudioDescription === true,
1266
- id: adaptation.id,
1267
- representations: adaptation.representations.map(parseAudioRepresentation),
1268
- label: adaptation.label,
1269
- };
1270
- if (adaptation.isDub === true) {
1271
- audioTrack.dub = true;
1272
- }
1273
- return audioTrack;
1274
- }
1275
-
1276
1184
  function toExposedPeriod(p: Period) : IPeriod {
1277
1185
  return { start: p.start, end: p.end, id: p.id };
1278
1186
  }
@@ -1435,6 +1343,8 @@ interface ITracksStoreEvents {
1435
1343
  newAvailablePeriods : IPeriod[];
1436
1344
  brokenRepresentationsLock : IBrokenRepresentationsLockContext;
1437
1345
  trackUpdate : ITrackUpdateEventPayload;
1346
+ error : unknown;
1347
+ warning : IPlayerError;
1438
1348
  }
1439
1349
 
1440
1350
  export interface IAudioRepresentationsLockSettings {
@@ -70,6 +70,32 @@ export function emitSeekEvents(
70
70
  }, { includeLastObservation: true, clearSignal: cancelSignal });
71
71
  }
72
72
 
73
+ /**
74
+ * @param {HTMLMediaElement} mediaElement
75
+ * @param {function} onPlay - Callback called when a play operation has started
76
+ * on `mediaElement`.
77
+ * @param {function} onPause - Callback called when a pause operation has
78
+ * started on `mediaElement`.
79
+ * @param {Object} cancelSignal - When triggered, stop calling callbacks and
80
+ * remove all listeners this function has registered.
81
+ */
82
+ export function emitPlayPauseEvents(
83
+ mediaElement : HTMLMediaElement | null,
84
+ onPlay: () => void,
85
+ onPause: () => void,
86
+ cancelSignal : CancellationSignal
87
+ ) : void {
88
+ if (cancelSignal.isCancelled() || mediaElement === null) {
89
+ return ;
90
+ }
91
+ mediaElement.addEventListener("play", onPlay);
92
+ mediaElement.addEventListener("pause", onPause);
93
+ cancelSignal.register(() => {
94
+ mediaElement.removeEventListener("play", onPlay);
95
+ mediaElement.removeEventListener("pause", onPause);
96
+ });
97
+ }
98
+
73
99
  /** Player state dictionnary. */
74
100
  export const enum PLAYER_STATES {
75
101
  STOPPED = "STOPPED",
@@ -113,7 +113,12 @@ export default function SessionEventsListener(
113
113
  if (isNullOrUndefined(licenseObject)) {
114
114
  log.info("DRM: No license given, skipping session.update");
115
115
  } else {
116
- return updateSessionWithMessage(session, licenseObject);
116
+ try {
117
+ return updateSessionWithMessage(session, licenseObject);
118
+ } catch (err) {
119
+ manualCanceller.cancel();
120
+ callbacks.onError(err);
121
+ }
117
122
  }
118
123
  })
119
124
  .catch((err : unknown) => {
@@ -14,6 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import log from "../../../log";
17
18
  import LoadedSessionsStore from "./loaded_sessions_store";
18
19
 
19
20
  /**
@@ -32,7 +33,7 @@ export default async function cleanOldLoadedSessions(
32
33
  if (limit < 0 || limit >= loadedSessionsStore.getLength()) {
33
34
  return ;
34
35
  }
35
-
36
+ log.info("DRM: LSS cache limit exceeded", limit, loadedSessionsStore.getLength());
36
37
  const proms : Array<Promise<unknown>> = [];
37
38
  const entries = loadedSessionsStore.getAll().slice(); // clone
38
39
  const toDelete = entries.length - limit;
@@ -63,6 +63,7 @@ export default class LoadedSessionsStore {
63
63
  sessionType : MediaKeySessionType
64
64
  ) : IStoredSessionEntry {
65
65
  const keySessionRecord = new KeySessionRecord(initData);
66
+ log.debug("DRM-LSS: calling `createSession`", sessionType);
66
67
  const mediaKeySession = this._mediaKeys.createSession(sessionType);
67
68
  const entry = { mediaKeySession,
68
69
  sessionType,
@@ -73,6 +74,8 @@ export default class LoadedSessionsStore {
73
74
  if (!isNullOrUndefined(mediaKeySession.closed)) {
74
75
  mediaKeySession.closed
75
76
  .then(() => {
77
+ log.info("DRM-LSS: session was closed, removing it.",
78
+ mediaKeySession.sessionId);
76
79
  const index = this.getIndex(keySessionRecord);
77
80
  if (index >= 0 &&
78
81
  this._storage[index].mediaKeySession === mediaKeySession)
@@ -86,8 +89,8 @@ export default class LoadedSessionsStore {
86
89
  });
87
90
  }
88
91
 
89
- log.debug("DRM-LSS: Add MediaKeySession", entry.sessionType);
90
92
  this._storage.push({ ...entry });
93
+ log.debug("DRM-LSS: MediaKeySession added", entry.sessionType, this._storage.length);
91
94
  return entry;
92
95
  }
93
96
 
@@ -111,6 +114,8 @@ export default class LoadedSessionsStore {
111
114
  if (stored.keySessionRecord.isCompatibleWith(initializationData)) {
112
115
  this._storage.splice(i, 1);
113
116
  this._storage.push(stored);
117
+ log.debug("DRM-LSS: Reusing session:",
118
+ stored.mediaKeySession.sessionId, stored.sessionType);
114
119
  return { ...stored };
115
120
  }
116
121
  }
@@ -332,6 +337,8 @@ export default class LoadedSessionsStore {
332
337
  for (let i = this._storage.length - 1; i >= 0; i--) {
333
338
  const stored = this._storage[i];
334
339
  if (stored.mediaKeySession === mediaKeySession) {
340
+ log.debug("DRM-LSS: Removing session without closing it",
341
+ mediaKeySession.sessionId);
335
342
  this._storage.splice(i, 1);
336
343
  return true;
337
344
  }
@@ -84,6 +84,7 @@ export default class DirectFileContentInitializer extends ContentInitializer {
84
84
  * events when it cannot, as well as "unstalled" events when it get out of one.
85
85
  */
86
86
  const rebufferingController = new RebufferingController(playbackObserver,
87
+ null,
87
88
  null,
88
89
  speed);
89
90
  rebufferingController.addEventListener("stalled", (evt) =>
@@ -67,7 +67,7 @@ import getInitialTime, {
67
67
  import getLoadedReference from "./utils/get_loaded_reference";
68
68
  import performInitialSeekAndPlay from "./utils/initial_seek_and_play";
69
69
  import initializeContentDecryption from "./utils/initialize_content_decryption";
70
- import MediaDurationUpdater from "./utils/media_duration_updater";
70
+ import MediaSourceDurationUpdater from "./utils/media_source_duration_updater";
71
71
  import RebufferingController from "./utils/rebuffering_controller";
72
72
  import streamEventsEmitter from "./utils/stream_events_emitter";
73
73
  import listenToMediaError from "./utils/throw_on_media_error";
@@ -352,7 +352,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
352
352
  if (initCanceller.isUsed()) {
353
353
  return;
354
354
  }
355
- triggerEvent("reloadingMediaSource", null);
355
+ triggerEvent("reloadingMediaSource", reloadOrder);
356
356
  if (initCanceller.isUsed()) {
357
357
  return;
358
358
  }
@@ -448,9 +448,20 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
448
448
 
449
449
  const rebufferingController = this._createRebufferingController(playbackObserver,
450
450
  manifest,
451
+ segmentBuffersStore,
451
452
  speed,
452
453
  cancelSignal);
453
-
454
+ rebufferingController.addEventListener("needsReload", () => {
455
+ // NOTE couldn't both be always calculated at event destination?
456
+ // Maybe there are exceptions?
457
+ const position = initialSeekPerformed.getValue() ?
458
+ playbackObserver.getCurrentTime() :
459
+ initialTime;
460
+ const autoplay = initialPlayPerformed.getValue() ?
461
+ !playbackObserver.getIsPaused() :
462
+ autoPlay;
463
+ onReloadOrder({ position, autoPlay: autoplay });
464
+ }, cancelSignal);
454
465
  const contentTimeBoundariesObserver = this
455
466
  ._createContentTimeBoundariesObserver(manifest,
456
467
  mediaSource,
@@ -498,8 +509,36 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
498
509
  */
499
510
  function handleStreamOrchestratorCallbacks() : IStreamOrchestratorCallbacks {
500
511
  return {
501
- needsBufferFlush: () =>
502
- playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001),
512
+ needsBufferFlush: () => {
513
+ const seekedTime = mediaElement.currentTime + 0.001;
514
+ playbackObserver.setCurrentTime(seekedTime);
515
+
516
+ // Seek again once data begins to be buffered.
517
+ // This is sadly necessary on some browsers to avoid decoding
518
+ // issues after a flush.
519
+ //
520
+ // NOTE: there's in theory a potential race condition in the following
521
+ // logic as the callback could be called when media data is still
522
+ // being removed by the browser - which is an asynchronous process.
523
+ // The following condition checking for buffered data could thus lead
524
+ // to a false positive where we're actually checking previous data.
525
+ // For now, such scenario is avoided by setting the
526
+ // `includeLastObservation` option to `false` and calling
527
+ // `needsBufferFlush` once MSE media removal operations have been
528
+ // explicitely validated by the browser, but that's a complex and easy
529
+ // to break system.
530
+ playbackObserver.listen((obs, stopListening) => {
531
+ if (
532
+ // Data is buffered around the current position
533
+ obs.currentRange !== null ||
534
+ // Or, for whatever reason, playback is already advancing
535
+ obs.position > seekedTime + 0.1
536
+ ) {
537
+ stopListening();
538
+ playbackObserver.setCurrentTime(obs.position + 0.001);
539
+ }
540
+ }, { includeLastObservation: false, clearSignal: cancelSignal });
541
+ },
503
542
 
504
543
  streamStatusUpdate(value) {
505
544
  // Announce discontinuities if found
@@ -640,9 +679,9 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
640
679
  cancelSignal : CancellationSignal
641
680
  ) : ContentTimeBoundariesObserver {
642
681
  /** Maintains the MediaSource's duration up-to-date with the Manifest */
643
- const mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource);
682
+ const mediaSourceDurationUpdater = new MediaSourceDurationUpdater(mediaSource);
644
683
  cancelSignal.register(() => {
645
- mediaDurationUpdater.stop();
684
+ mediaSourceDurationUpdater.stopUpdating();
646
685
  });
647
686
  /** Allows to cancel a pending `end-of-stream` operation. */
648
687
  let endOfStreamCanceller : TaskCanceller | null = null;
@@ -660,8 +699,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
660
699
  this.trigger("activePeriodChanged", { period });
661
700
  });
662
701
  contentTimeBoundariesObserver.addEventListener("durationUpdate", (newDuration) => {
663
- log.debug("Init: Duration has to be updated.", newDuration);
664
- mediaDurationUpdater.updateKnownDuration(newDuration);
702
+ mediaSourceDurationUpdater.updateDuration(newDuration.duration, newDuration.isEnd);
665
703
  });
666
704
  contentTimeBoundariesObserver.addEventListener("endOfStream", () => {
667
705
  if (endOfStreamCanceller === null) {
@@ -678,6 +716,9 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
678
716
  endOfStreamCanceller = null;
679
717
  }
680
718
  });
719
+ const currentDuration = contentTimeBoundariesObserver.getCurrentDuration();
720
+ mediaSourceDurationUpdater.updateDuration(currentDuration.duration,
721
+ currentDuration.isEnd);
681
722
  return contentTimeBoundariesObserver;
682
723
  }
683
724
 
@@ -702,11 +743,13 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
702
743
  private _createRebufferingController(
703
744
  playbackObserver : PlaybackObserver,
704
745
  manifest : Manifest,
746
+ segmentBuffersStore : SegmentBuffersStore,
705
747
  speed : IReadOnlySharedReference<number>,
706
748
  cancelSignal : CancellationSignal
707
749
  ) : RebufferingController {
708
750
  const rebufferingController = new RebufferingController(playbackObserver,
709
751
  manifest,
752
+ segmentBuffersStore,
710
753
  speed);
711
754
  // Bubble-up events
712
755
  rebufferingController.addEventListener("stalled",
@@ -120,7 +120,15 @@ export interface IContentInitializerEvents {
120
120
  * Event sent when we're starting attach a new MediaSource to the media element
121
121
  * (after removing the previous one).
122
122
  */
123
- reloadingMediaSource: null;
123
+ reloadingMediaSource: {
124
+ /** The position we're reloading at, in seconds. */
125
+ position: number;
126
+ /**
127
+ * If `true`, we'll play directly after finishing the reloading operation.
128
+ * If `false`, we'll be paused after it.
129
+ */
130
+ autoPlay: boolean;
131
+ };
124
132
  /** Event sent after the player stalled. */
125
133
  stalled : IStallingSituation;
126
134
  /** Event sent when the player goes out of a stalling situation. */