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
@@ -111,18 +111,20 @@ export default class ContentTimeBoundariesObserver
111
111
  }, { includeLastObservation: true, clearSignal: cancelSignal });
112
112
 
113
113
  manifest.addEventListener("manifestUpdate", () => {
114
- this.trigger("durationUpdate", getManifestDuration());
114
+ this.trigger("durationUpdate", this._getManifestDuration());
115
115
  if (cancelSignal.isCancelled()) {
116
116
  return;
117
117
  }
118
118
  this._checkEndOfStream();
119
119
  }, cancelSignal);
120
+ }
120
121
 
121
- function getManifestDuration() : number | undefined {
122
- return manifest.isDynamic ?
123
- maximumPositionCalculator.getMaximumAvailablePosition() :
124
- maximumPositionCalculator.getEndingPosition();
125
- }
122
+ /**
123
+ * Returns an estimate of the current duration of the content.
124
+ * @returns {Object}
125
+ */
126
+ public getCurrentDuration() : IDurationItem {
127
+ return this._getManifestDuration();
126
128
  }
127
129
 
128
130
  /**
@@ -154,9 +156,12 @@ export default class ContentTimeBoundariesObserver
154
156
  this._maximumPositionCalculator
155
157
  .updateLastVideoAdaptation(adaptation);
156
158
  }
157
- const newDuration = this._manifest.isDynamic ?
158
- this._maximumPositionCalculator.getMaximumAvailablePosition() :
159
- this._maximumPositionCalculator.getEndingPosition();
159
+ const endingPosition = this._maximumPositionCalculator.getEndingPosition();
160
+ const newDuration = endingPosition !== undefined ?
161
+ { isEnd: true,
162
+ duration: endingPosition } :
163
+ { isEnd: false,
164
+ duration: this._maximumPositionCalculator.getMaximumAvailablePosition() };
160
165
  this.trigger("durationUpdate", newDuration);
161
166
  }
162
167
  }
@@ -306,6 +311,15 @@ export default class ContentTimeBoundariesObserver
306
311
  }
307
312
  }
308
313
 
314
+ private _getManifestDuration() : IDurationItem {
315
+ const endingPosition = this._maximumPositionCalculator.getEndingPosition();
316
+ return endingPosition !== undefined ?
317
+ { isEnd: true,
318
+ duration: endingPosition } :
319
+ { isEnd: false,
320
+ duration: this._maximumPositionCalculator.getMaximumAvailablePosition() };
321
+ }
322
+
309
323
  private _lazilyCreateActiveStreamInfo(bufferType : IBufferType) : IActiveStreamsInfo {
310
324
  let streamInfo = this._activeStreams.get(bufferType);
311
325
  if (streamInfo === undefined) {
@@ -334,6 +348,28 @@ export default class ContentTimeBoundariesObserver
334
348
  }
335
349
  }
336
350
 
351
+ export interface IDurationItem {
352
+ /**
353
+ * The new maximum known position (note that this is the ending position
354
+ * currently known of the current content, it might be superior to the last
355
+ * position at which segments are available and it might also evolve over
356
+ * time), in seconds.
357
+ */
358
+ duration : number;
359
+ /**
360
+ * If `true`, the communicated `duration` is the actual end of the content.
361
+ * It may still be updated due to a track change or to add precision, but it
362
+ * is still a (rough) estimate of the maximum position that content should
363
+ * have.
364
+ *
365
+ * If `false`, this is the currently known maximum position associated to
366
+ * the content, but the content is still evolving (typically, new media
367
+ * segments are still being generated) and as such it can still have a
368
+ * longer duration in the future.
369
+ */
370
+ isEnd : boolean;
371
+ }
372
+
337
373
  /**
338
374
  * Events triggered by a `ContentTimeBoundariesObserver` where the keys are the
339
375
  * event names and the value is the payload of those events.
@@ -347,7 +383,7 @@ export interface IContentTimeBoundariesObserverEvent {
347
383
  * Triggered when the duration of the currently-playing content became known
348
384
  * or changed.
349
385
  */
350
- durationUpdate : number | undefined;
386
+ durationUpdate : IDurationItem;
351
387
  /**
352
388
  * Triggered when the last possible chronological segment for all types of
353
389
  * buffers has either been pushed or is being pushed to the corresponding
@@ -19,11 +19,11 @@ import {
19
19
  onSourceEnded,
20
20
  onSourceClose,
21
21
  } from "../../../compat/event_listeners";
22
+ /* eslint-disable-next-line max-len */
23
+ import hasIssuesWithHighMediaSourceDuration from "../../../compat/has_issues_with_high_media_source_duration";
22
24
  import log from "../../../log";
23
- import Manifest from "../../../manifest";
24
25
  import createSharedReference, {
25
26
  IReadOnlySharedReference,
26
- ISharedReference,
27
27
  } from "../../../utils/reference";
28
28
  import TaskCanceller, {
29
29
  CancellationSignal,
@@ -33,124 +33,108 @@ import TaskCanceller, {
33
33
  const YEAR_IN_SECONDS = 365 * 24 * 3600;
34
34
 
35
35
  /**
36
- * Keep the MediaSource's duration up-to-date with what is being played.
37
- * @class MediaDurationUpdater
36
+ * Keep the MediaSource's `duration` attribute up-to-date with the duration of
37
+ * the content played on it.
38
+ * @class MediaSourceDurationUpdater
38
39
  */
39
- export default class MediaDurationUpdater {
40
- private _canceller : TaskCanceller;
40
+ export default class MediaSourceDurationUpdater {
41
+ /**
42
+ * `MediaSource` on which we're going to update the `duration` attribute.
43
+ */
44
+ private _mediaSource : MediaSource;
41
45
 
42
46
  /**
43
- * The last known audio Adaptation (i.e. track) chosen for the last Period.
44
- * Useful to determinate the duration of the current content.
45
- * `undefined` if the audio track for the last Period has never been known yet.
46
- * `null` if there are no chosen audio Adaptation.
47
+ * Abort the current duration-setting logic.
48
+ * `null` if no such logic is pending.
47
49
  */
48
- private _currentKnownDuration : ISharedReference<number | undefined>;
50
+ private _currentMediaSourceDurationUpdateCanceller : TaskCanceller | null;
49
51
 
50
52
  /**
51
- * Create a new `MediaDurationUpdater` that will keep the given MediaSource's
52
- * duration as soon as possible.
53
- * This duration will be updated until the `stop` method is called.
54
- * @param {Object} manifest - The Manifest currently played.
55
- * For another content, you will have to create another `MediaDurationUpdater`.
53
+ * Create a new `MediaSourceDurationUpdater`,
56
54
  * @param {MediaSource} mediaSource - The MediaSource on which the content is
57
- * pushed.
55
+ * played.
58
56
  */
59
- constructor(manifest : Manifest, mediaSource : MediaSource) {
60
- const canceller = new TaskCanceller();
61
- const currentKnownDuration = createSharedReference(undefined, canceller.signal);
57
+ constructor(mediaSource : MediaSource) {
58
+ this._mediaSource = mediaSource;
59
+ this._currentMediaSourceDurationUpdateCanceller = null;
60
+ }
62
61
 
63
- this._canceller = canceller;
64
- this._currentKnownDuration = currentKnownDuration;
62
+ /**
63
+ * Indicate to the `MediaSourceDurationUpdater` the currently known duration
64
+ * of the content.
65
+ *
66
+ * The `MediaSourceDurationUpdater` will then use that value to determine
67
+ * which `duration` attribute should be set on the `MediaSource` associated
68
+ *
69
+ * @param {number} newDuration
70
+ * @param {boolean} isRealEndKnown - If set to `false`, the current content is
71
+ * a dynamic content (it might evolve in the future) and the `newDuration`
72
+ * communicated might be greater still. In effect the
73
+ * `MediaSourceDurationUpdater` will actually set a much higher value to the
74
+ * `MediaSource`'s duration to prevent being annoyed by the HTML-related
75
+ * side-effects of having a too low duration (such as the impossibility to
76
+ * seek over that value).
77
+ */
78
+ public updateDuration(
79
+ newDuration : number,
80
+ isRealEndKnown : boolean
81
+ ) : void {
82
+ if (this._currentMediaSourceDurationUpdateCanceller !== null) {
83
+ this._currentMediaSourceDurationUpdateCanceller.cancel();
84
+ }
85
+ this._currentMediaSourceDurationUpdateCanceller = new TaskCanceller();
65
86
 
87
+ const mediaSource = this._mediaSource;
88
+ const currentSignal = this._currentMediaSourceDurationUpdateCanceller.signal;
66
89
  const isMediaSourceOpened = createMediaSourceOpenReference(mediaSource,
67
- this._canceller.signal);
68
-
69
- /** TaskCanceller triggered each time the MediaSource open status changes. */
70
- let msUpdateCanceller = new TaskCanceller();
71
- msUpdateCanceller.linkToSignal(this._canceller.signal);
90
+ currentSignal);
72
91
 
92
+ /** TaskCanceller triggered each time the MediaSource switches to and from "open". */
93
+ let msOpenStatusCanceller = new TaskCanceller();
94
+ msOpenStatusCanceller.linkToSignal(currentSignal);
73
95
  isMediaSourceOpened.onUpdate(onMediaSourceOpenedStatusChanged,
74
96
  { emitCurrentValue: true,
75
- clearSignal: this._canceller.signal });
76
-
97
+ clearSignal: currentSignal });
77
98
 
78
99
  function onMediaSourceOpenedStatusChanged() {
79
- msUpdateCanceller.cancel();
100
+ msOpenStatusCanceller.cancel();
80
101
  if (!isMediaSourceOpened.getValue()) {
81
102
  return;
82
103
  }
83
- msUpdateCanceller = new TaskCanceller();
84
- msUpdateCanceller.linkToSignal(canceller.signal);
85
-
86
- /** TaskCanceller triggered each time the content's duration may have changed */
87
- let durationChangeCanceller = new TaskCanceller();
88
- durationChangeCanceller.linkToSignal(msUpdateCanceller.signal);
89
-
90
- const reSetDuration = () => {
91
- durationChangeCanceller.cancel();
92
- durationChangeCanceller = new TaskCanceller();
93
- durationChangeCanceller.linkToSignal(msUpdateCanceller.signal);
94
- onDurationMayHaveChanged(durationChangeCanceller.signal);
95
- };
96
-
97
- currentKnownDuration.onUpdate(reSetDuration,
98
- { emitCurrentValue: false,
99
- clearSignal: msUpdateCanceller.signal });
100
-
101
- manifest.addEventListener("manifestUpdate",
102
- reSetDuration,
103
- msUpdateCanceller.signal);
104
-
105
- onDurationMayHaveChanged(durationChangeCanceller.signal);
106
- }
107
-
108
- function onDurationMayHaveChanged(cancelSignal : CancellationSignal) {
104
+ msOpenStatusCanceller = new TaskCanceller();
105
+ msOpenStatusCanceller.linkToSignal(currentSignal);
109
106
  const areSourceBuffersUpdating = createSourceBuffersUpdatingReference(
110
107
  mediaSource.sourceBuffers,
111
- cancelSignal
108
+ msOpenStatusCanceller.signal
112
109
  );
113
-
114
110
  /** TaskCanceller triggered each time SourceBuffers' updating status changes */
115
111
  let sourceBuffersUpdatingCanceller = new TaskCanceller();
116
- sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal);
112
+ sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
113
+
117
114
  return areSourceBuffersUpdating.onUpdate((areUpdating) => {
118
115
  sourceBuffersUpdatingCanceller.cancel();
119
116
  sourceBuffersUpdatingCanceller = new TaskCanceller();
120
- sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal);
117
+ sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
121
118
  if (areUpdating) {
122
119
  return;
123
120
  }
121
+
124
122
  recursivelyForceDurationUpdate(mediaSource,
125
- manifest,
126
- currentKnownDuration.getValue(),
127
- cancelSignal);
128
- }, { clearSignal: cancelSignal, emitCurrentValue: true });
123
+ newDuration,
124
+ isRealEndKnown,
125
+ sourceBuffersUpdatingCanceller.signal);
126
+ }, { clearSignal: msOpenStatusCanceller.signal, emitCurrentValue: true });
129
127
  }
130
128
  }
131
129
 
132
130
  /**
133
- * By default, the `MediaDurationUpdater` only set a safe estimate for the
134
- * MediaSource's duration.
135
- * A more precize duration can be set by communicating to it a more precize
136
- * media duration through `updateKnownDuration`.
137
- * If the duration becomes unknown, `undefined` can be given to it so the
138
- * `MediaDurationUpdater` goes back to a safe estimate.
139
- * @param {number | undefined} newDuration
140
- */
141
- public updateKnownDuration(
142
- newDuration : number | undefined
143
- ) : void {
144
- this._currentKnownDuration.setValueIfChanged(newDuration);
145
- }
146
-
147
- /**
148
- * Stop the `MediaDurationUpdater` from updating and free its resources.
149
- * Once stopped, it is not possible to start it again, beside creating another
150
- * `MediaDurationUpdater`.
131
+ * Abort the last duration-setting operation and free its resources.
151
132
  */
152
- public stop() {
153
- this._canceller.cancel();
133
+ public stopUpdating() {
134
+ if (this._currentMediaSourceDurationUpdateCanceller !== null) {
135
+ this._currentMediaSourceDurationUpdateCanceller.cancel();
136
+ this._currentMediaSourceDurationUpdateCanceller = null;
137
+ }
154
138
  }
155
139
  }
156
140
 
@@ -163,33 +147,21 @@ export default class MediaDurationUpdater {
163
147
  * - `null` if it hasn'nt been updated
164
148
  *
165
149
  * @param {MediaSource} mediaSource
166
- * @param {Object} manifest
150
+ * @param {number} duration
151
+ * @param {boolean} isRealEndKnown
167
152
  * @returns {string}
168
153
  */
169
154
  function setMediaSourceDuration(
170
155
  mediaSource: MediaSource,
171
- manifest: Manifest,
172
- knownDuration : number | undefined
156
+ duration : number,
157
+ isRealEndKnown : boolean
173
158
  ) : MediaSourceDurationUpdateStatus {
174
- let newDuration = knownDuration;
175
-
176
- if (newDuration === undefined) {
177
- if (manifest.isDynamic) {
178
- newDuration = manifest.getLivePosition() ??
179
- manifest.getMaximumSafePosition();
180
- } else {
181
- newDuration = manifest.getMaximumSafePosition();
182
- }
183
- }
159
+ let newDuration = duration;
184
160
 
185
- if (manifest.isDynamic) {
186
- // Some targets poorly support setting a very high number for durations.
187
- // Yet, in dynamic contents, we would prefer setting a value as high as possible
188
- // to still be able to seek anywhere we want to (even ahead of the Manifest if
189
- // we want to). As such, we put it at a safe default value of 2^32 excepted
190
- // when the maximum position is already relatively close to that value, where
191
- // we authorize exceptionally going over it.
192
- newDuration = Math.max(Math.pow(2, 32), newDuration + YEAR_IN_SECONDS);
161
+ if (!isRealEndKnown) {
162
+ newDuration = hasIssuesWithHighMediaSourceDuration() ?
163
+ Infinity :
164
+ getMaximumLiveSeekablePosition(duration);
193
165
  }
194
166
 
195
167
  let maxBufferedEnd : number = 0;
@@ -222,6 +194,10 @@ function setMediaSourceDuration(
222
194
  try {
223
195
  log.info("Init: Updating duration", newDuration);
224
196
  mediaSource.duration = newDuration;
197
+ if (mediaSource.readyState === "open" && !isFinite(newDuration)) {
198
+ mediaSource.setLiveSeekableRange(0,
199
+ getMaximumLiveSeekablePosition(duration));
200
+ }
225
201
  } catch (err) {
226
202
  log.warn("Duration Updater: Can't update duration on the MediaSource.",
227
203
  err instanceof Error ? err : "");
@@ -328,30 +304,42 @@ function createMediaSourceOpenReference(
328
304
 
329
305
  /**
330
306
  * Immediately tries to set the MediaSource's duration to the most appropriate
331
- * one according to the Manifest and duration given.
307
+ * one.
332
308
  *
333
309
  * If it fails, wait 2 seconds and retries.
334
310
  *
335
311
  * @param {MediaSource} mediaSource
336
- * @param {Object} manifest
337
- * @param {number|undefined} duration
312
+ * @param {number} duration
313
+ * @param {boolean} isRealEndKnown
338
314
  * @param {Object} cancelSignal
339
315
  */
340
316
  function recursivelyForceDurationUpdate(
341
317
  mediaSource : MediaSource,
342
- manifest : Manifest,
343
- duration : number | undefined,
318
+ duration : number,
319
+ isRealEndKnown : boolean,
344
320
  cancelSignal : CancellationSignal
345
321
  ) : void {
346
- const res = setMediaSourceDuration(mediaSource, manifest, duration);
322
+ const res = setMediaSourceDuration(mediaSource, duration, isRealEndKnown);
347
323
  if (res === MediaSourceDurationUpdateStatus.Success) {
348
324
  return ;
349
325
  }
350
326
  const timeoutId = setTimeout(() => {
351
327
  unregisterClear();
352
- recursivelyForceDurationUpdate(mediaSource, manifest, duration, cancelSignal);
328
+ recursivelyForceDurationUpdate(mediaSource, duration, isRealEndKnown, cancelSignal);
353
329
  }, 2000);
354
330
  const unregisterClear = cancelSignal.register(() => {
355
331
  clearTimeout(timeoutId);
356
332
  });
357
333
  }
334
+
335
+ function getMaximumLiveSeekablePosition(contentLastPosition : number) : number {
336
+ // Some targets poorly support setting a very high number for seekable
337
+ // ranges.
338
+ // Yet, in contents whose end is not yet known (e.g. live contents), we
339
+ // would prefer setting a value as high as possible to still be able to
340
+ // seek anywhere we want to (even ahead of the Manifest if we want to).
341
+ // As such, we put it at a safe default value of 2^32 excepted when the
342
+ // maximum position is already relatively close to that value, where we
343
+ // authorize exceptionally going over it.
344
+ return Math.max(Math.pow(2, 32), contentLastPosition + YEAR_IN_SECONDS);
345
+ }
@@ -30,7 +30,7 @@ import {
30
30
  IPlaybackObservation,
31
31
  PlaybackObserver,
32
32
  } from "../../api";
33
- import { IBufferType } from "../../segment_buffers";
33
+ import SegmentBuffersStore, { IBufferType } from "../../segment_buffers";
34
34
  import { IStallingSituation } from "../types";
35
35
 
36
36
 
@@ -54,6 +54,7 @@ export default class RebufferingController
54
54
  /** Emit the current playback conditions */
55
55
  private _playbackObserver : PlaybackObserver;
56
56
  private _manifest : Manifest | null;
57
+ private _segmentBuffersStore : SegmentBuffersStore | null;
57
58
  private _speed : IReadOnlySharedReference<number>;
58
59
  private _isStarted : boolean;
59
60
 
@@ -65,6 +66,18 @@ export default class RebufferingController
65
66
 
66
67
  private _canceller : TaskCanceller;
67
68
 
69
+ /**
70
+ * If set to something else than `null`, this is the DOMHighResTimestamp as
71
+ * outputed by `performance.now()` when playback begin to seem to not start
72
+ * despite having decipherable data in the buffer(s).
73
+ *
74
+ * If enough time in that condition is spent, special considerations are
75
+ * taken at which point `_currentFreezeTimestamp` is reset to `null`.
76
+ *
77
+ * It is also reset to `null` when and if there is no such issue anymore.
78
+ */
79
+ private _currentFreezeTimestamp : number | null;
80
+
68
81
  /**
69
82
  * @param {object} playbackObserver - emit the current playback conditions.
70
83
  * @param {Object} manifest - The Manifest of the currently-played content.
@@ -72,16 +85,19 @@ export default class RebufferingController
72
85
  */
73
86
  constructor(
74
87
  playbackObserver : PlaybackObserver,
75
- manifest: Manifest | null,
88
+ manifest : Manifest | null,
89
+ segmentBuffersStore : SegmentBuffersStore | null,
76
90
  speed : IReadOnlySharedReference<number>
77
91
  ) {
78
92
  super();
79
93
  this._playbackObserver = playbackObserver;
80
94
  this._manifest = manifest;
95
+ this._segmentBuffersStore = segmentBuffersStore;
81
96
  this._speed = speed;
82
97
  this._discontinuitiesStore = [];
83
98
  this._isStarted = false;
84
99
  this._canceller = new TaskCanceller();
100
+ this._currentFreezeTimestamp = null;
85
101
  }
86
102
 
87
103
  public start() : void {
@@ -154,6 +170,10 @@ export default class RebufferingController
154
170
  Math.max(observation.pendingInternalSeek ?? 0, observation.position) :
155
171
  null;
156
172
 
173
+ if (this._checkDecipherabilityFreeze(observation)) {
174
+ return ;
175
+ }
176
+
157
177
  if (freezing !== null) {
158
178
  const now = performance.now();
159
179
 
@@ -215,7 +235,7 @@ export default class RebufferingController
215
235
  this.trigger("stalled", stalledReason);
216
236
  return ;
217
237
  } else {
218
- log.warn("Init: ignored stall for too long, checking discontinuity",
238
+ log.warn("Init: ignored stall for too long, considering it",
219
239
  now - ignoredStallTimeStamp);
220
240
  }
221
241
  }
@@ -358,6 +378,96 @@ export default class RebufferingController
358
378
  public destroy() : void {
359
379
  this._canceller.cancel();
360
380
  }
381
+
382
+ /**
383
+ * Support of contents with DRM on all the platforms out there is a pain in
384
+ * the *ss considering all the DRM-related bugs there are.
385
+ *
386
+ * We found out a frequent issue which is to be unable to play despite having
387
+ * all the decryption keys to play what is currently buffered.
388
+ * When this happens, re-creating the buffers from scratch, with a reload, is
389
+ * usually sufficient to unlock the situation.
390
+ *
391
+ * Although we prefer providing more targeted fixes or telling to platform
392
+ * developpers to fix their implementation, it's not always possible.
393
+ * We thus resorted to developping an heuristic which detects such situation
394
+ * and reload in that case.
395
+ *
396
+ * @param {Object} observation - The last playback observation produced, it
397
+ * has to be recent (just triggered for example).
398
+ * @returns {boolean} - Returns `true` if it seems to be such kind of
399
+ * decipherability freeze, in which case this method already performed the
400
+ * right handling steps.
401
+ */
402
+ private _checkDecipherabilityFreeze(
403
+ observation : IPlaybackObservation
404
+ ): boolean {
405
+ const { readyState,
406
+ rebuffering,
407
+ freezing } = observation;
408
+ const bufferGap = observation.bufferGap !== undefined &&
409
+ isFinite(observation.bufferGap) ? observation.bufferGap :
410
+ 0;
411
+ if (
412
+ this._segmentBuffersStore === null ||
413
+ bufferGap < 6 ||
414
+ (rebuffering === null && freezing === null) ||
415
+ readyState > 1
416
+ ) {
417
+ this._currentFreezeTimestamp = null;
418
+ return false;
419
+ }
420
+
421
+ const now = performance.now();
422
+ if (this._currentFreezeTimestamp === null) {
423
+ this._currentFreezeTimestamp = now;
424
+ }
425
+ const rebufferingForTooLong =
426
+ rebuffering !== null && now - rebuffering.timestamp > 4000;
427
+ const frozenForTooLong =
428
+ freezing !== null && now - freezing.timestamp > 4000;
429
+
430
+ if (
431
+ (rebufferingForTooLong || frozenForTooLong) &&
432
+ performance.now() - this._currentFreezeTimestamp > 4000
433
+ ) {
434
+ const statusAudio = this._segmentBuffersStore.getStatus("audio");
435
+ const statusVideo = this._segmentBuffersStore.getStatus("video");
436
+ let hasOnlyDecipherableSegments = true;
437
+ let isClear = true;
438
+ for (const status of [statusAudio, statusVideo]) {
439
+ if (status.type === "initialized") {
440
+ for (const segment of status.value.getInventory()) {
441
+ const { representation } = segment.infos;
442
+ if (representation.decipherable === false) {
443
+ log.warn(
444
+ "Init: we have undecipherable segments left in the buffer, reloading"
445
+ );
446
+ this._currentFreezeTimestamp = null;
447
+ this.trigger("needsReload", null);
448
+ return true;
449
+ } else if (representation.contentProtections !== undefined) {
450
+ isClear = false;
451
+ if (representation.decipherable !== true) {
452
+ hasOnlyDecipherableSegments = false;
453
+ }
454
+ }
455
+ }
456
+ }
457
+ }
458
+
459
+ if (!isClear && hasOnlyDecipherableSegments) {
460
+ log.warn(
461
+ "Init: we are frozen despite only having decipherable " +
462
+ "segments left in the buffer, reloading"
463
+ );
464
+ this._currentFreezeTimestamp = null;
465
+ this.trigger("needsReload", null);
466
+ return true;
467
+ }
468
+ }
469
+ return false;
470
+ }
361
471
  }
362
472
 
363
473
  /**
@@ -581,6 +691,7 @@ class PlaybackRateUpdater {
581
691
  export interface IRebufferingControllerEvent {
582
692
  stalled : IStallingSituation;
583
693
  unstalled : null;
694
+ needsReload : null;
584
695
  warning : IPlayerError;
585
696
  }
586
697