rx-player 3.30.1-dev.2023032301 → 3.30.1-dev.2023032800

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 (32) hide show
  1. package/CHANGELOG.md +8 -2
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/config.d.ts +2 -0
  4. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +4 -2
  5. package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.js +4 -4
  6. package/dist/_esm5.processed/core/adaptive/network_analyzer.js +8 -5
  7. package/dist/_esm5.processed/core/api/public_api.js +2 -2
  8. package/dist/_esm5.processed/core/init/media_source_content_initializer.js +6 -4
  9. package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.d.ts +28 -1
  10. package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.js +22 -9
  11. package/dist/_esm5.processed/core/init/utils/media_source_duration_updater.d.ts +58 -0
  12. package/dist/_esm5.processed/core/init/utils/{media_duration_updater.js → media_source_duration_updater.js} +71 -85
  13. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +3 -3
  14. package/dist/_esm5.processed/default_config.d.ts +25 -0
  15. package/dist/_esm5.processed/default_config.js +27 -2
  16. package/dist/_esm5.processed/utils/is_null_or_undefined.d.ts +1 -1
  17. package/dist/_esm5.processed/utils/is_null_or_undefined.js +1 -1
  18. package/dist/rx-player.js +156 -115
  19. package/dist/rx-player.min.js +1 -1
  20. package/package.json +1 -1
  21. package/sonar-project.properties +1 -1
  22. package/src/core/adaptive/adaptive_representation_selector.ts +6 -2
  23. package/src/core/adaptive/buffer_based_chooser.ts +4 -4
  24. package/src/core/adaptive/network_analyzer.ts +9 -4
  25. package/src/core/api/public_api.ts +2 -2
  26. package/src/core/init/media_source_content_initializer.ts +7 -4
  27. package/src/core/init/utils/content_time_boundaries_observer.ts +46 -10
  28. package/src/core/init/utils/{media_duration_updater.ts → media_source_duration_updater.ts} +87 -111
  29. package/src/core/stream/orchestrator/stream_orchestrator.ts +4 -4
  30. package/src/default_config.ts +29 -2
  31. package/src/utils/is_null_or_undefined.ts +1 -1
  32. package/dist/_esm5.processed/core/init/utils/media_duration_updater.d.ts +0 -56
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.30.1-dev.2023032301",
4
+ "version": "3.30.1-dev.2023032800",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -1,7 +1,7 @@
1
1
  sonar.projectKey=rx-player
2
2
  sonar.organization=rx-player
3
3
  sonar.projectName=rx-player
4
- sonar.projectVersion=3.30.1-dev.2023032301
4
+ sonar.projectVersion=3.30.1-dev.2023032800
5
5
  sonar.sources=./src,./demo,./tests
6
6
  sonar.exclusions=demo/full/bundle.js,demo/standalone/lib.js,demo/bundle.js
7
7
  sonar.host.url=https://sonarcloud.io
@@ -14,6 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
+ import config from "../../config";
17
18
  import log from "../../log";
18
19
  import Manifest, {
19
20
  Adaptation,
@@ -362,11 +363,14 @@ function getEstimateReference(
362
363
  lastPlaybackObservation.speed :
363
364
  1);
364
365
 
365
- if (allowBufferBasedEstimates && bufferGap <= 5) {
366
+ const { ABR_ENTER_BUFFER_BASED_ALGO,
367
+ ABR_EXIT_BUFFER_BASED_ALGO } = config.getCurrent();
368
+
369
+ if (allowBufferBasedEstimates && bufferGap <= ABR_EXIT_BUFFER_BASED_ALGO) {
366
370
  allowBufferBasedEstimates = false;
367
371
  } else if (!allowBufferBasedEstimates &&
368
372
  isFinite(bufferGap) &&
369
- bufferGap > 10)
373
+ bufferGap >= ABR_ENTER_BUFFER_BASED_ALGO)
370
374
  {
371
375
  allowBufferBasedEstimates = true;
372
376
  }
@@ -66,11 +66,11 @@ export default class BufferBasedChooser {
66
66
  }
67
67
 
68
68
  let scaledScore : number|undefined;
69
- if (currentScore != null) {
69
+ if (currentScore !== undefined) {
70
70
  scaledScore = speed === 0 ? currentScore : (currentScore / speed);
71
71
  }
72
72
 
73
- if (scaledScore != null && scaledScore > 1) {
73
+ if (scaledScore !== undefined && scaledScore > 1) {
74
74
  const currentBufferLevel = bufferLevels[currentBitrateIndex];
75
75
  const nextIndex = (() => {
76
76
  for (let i = currentBitrateIndex + 1; i < bufferLevels.length; i++) {
@@ -79,7 +79,7 @@ export default class BufferBasedChooser {
79
79
  }
80
80
  }
81
81
  })();
82
- if (nextIndex != null) {
82
+ if (nextIndex !== undefined) {
83
83
  const nextBufferLevel = bufferLevels[nextIndex];
84
84
  if (bufferGap >= nextBufferLevel) {
85
85
  return bitrates[nextIndex];
@@ -87,7 +87,7 @@ export default class BufferBasedChooser {
87
87
  }
88
88
  }
89
89
 
90
- if (scaledScore == null || scaledScore < 1.15) {
90
+ if (scaledScore === undefined || scaledScore < 1.15) {
91
91
  const currentBufferLevel = bufferLevels[currentBitrateIndex];
92
92
  if (bufferGap < currentBufferLevel) {
93
93
  for (let i = currentBitrateIndex - 1; i >= 0; i--) {
@@ -162,6 +162,13 @@ function estimateStarvationModeBitrate(
162
162
 
163
163
  const concernedRequest = concernedRequests[0];
164
164
  const now = performance.now();
165
+
166
+ let minimumRequestTime = concernedRequest.content.segment.duration * 1.5;
167
+ minimumRequestTime = Math.min(minimumRequestTime, 3000);
168
+ minimumRequestTime = Math.max(minimumRequestTime, 12000);
169
+ if (now - concernedRequest.requestTimestamp < minimumRequestTime) {
170
+ return undefined;
171
+ }
165
172
  const lastProgressEvent = concernedRequest.progress.length > 0 ?
166
173
  concernedRequest.progress[concernedRequest.progress.length - 1] :
167
174
  undefined;
@@ -178,7 +185,7 @@ function estimateStarvationModeBitrate(
178
185
  // Calculate estimated time spent rebuffering if we continue doing that request.
179
186
  const expectedRebufferingTime = remainingTime -
180
187
  (realBufferGap / speed);
181
- if (expectedRebufferingTime > 2000) {
188
+ if (expectedRebufferingTime > 2500) {
182
189
  return bandwidthEstimate;
183
190
  }
184
191
  }
@@ -402,10 +409,8 @@ export default class NetworkAnalyzer {
402
409
  ) : boolean {
403
410
  if (currentRepresentation === null) {
404
411
  return true;
405
- } else if (bitrate === currentRepresentation.bitrate) {
412
+ } else if (bitrate >= currentRepresentation.bitrate) {
406
413
  return false;
407
- } else if (bitrate > currentRepresentation.bitrate) {
408
- return !this._inStarvationMode;
409
414
  }
410
415
  return shouldDirectlySwitchToLowBitrate(playbackInfo,
411
416
  currentRequests,
@@ -364,7 +364,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
364
364
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
365
365
  videoElement.preload = "auto";
366
366
 
367
- this.version = /* PLAYER_VERSION */"3.30.1-dev.2023032301";
367
+ this.version = /* PLAYER_VERSION */"3.30.1-dev.2023032800";
368
368
  this.log = log;
369
369
  this.state = "STOPPED";
370
370
  this.videoElement = videoElement;
@@ -2986,7 +2986,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
2986
2986
  return mediaElementTrackChoiceManager;
2987
2987
  }
2988
2988
  }
2989
- Player.version = /* PLAYER_VERSION */"3.30.1-dev.2023032301";
2989
+ Player.version = /* PLAYER_VERSION */"3.30.1-dev.2023032800";
2990
2990
 
2991
2991
  /** Every events sent by the RxPlayer's public API. */
2992
2992
  interface IPublicAPIEvent {
@@ -68,7 +68,7 @@ import getInitialTime, {
68
68
  import getLoadedReference from "./utils/get_loaded_reference";
69
69
  import performInitialSeekAndPlay from "./utils/initial_seek_and_play";
70
70
  import initializeContentDecryption from "./utils/initialize_content_decryption";
71
- import MediaDurationUpdater from "./utils/media_duration_updater";
71
+ import MediaSourceDurationUpdater from "./utils/media_source_duration_updater";
72
72
  import RebufferingController from "./utils/rebuffering_controller";
73
73
  import streamEventsEmitter from "./utils/stream_events_emitter";
74
74
  import listenToMediaError from "./utils/throw_on_media_error";
@@ -644,9 +644,9 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
644
644
  cancelSignal : CancellationSignal
645
645
  ) : ContentTimeBoundariesObserver {
646
646
  /** Maintains the MediaSource's duration up-to-date with the Manifest */
647
- const mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource);
647
+ const mediaSourceDurationUpdater = new MediaSourceDurationUpdater(mediaSource);
648
648
  cancelSignal.register(() => {
649
- mediaDurationUpdater.stop();
649
+ mediaSourceDurationUpdater.stopUpdating();
650
650
  });
651
651
  /** Allows to cancel a pending `end-of-stream` operation. */
652
652
  let endOfStreamCanceller : TaskCanceller | null = null;
@@ -664,7 +664,7 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
664
664
  this.trigger("activePeriodChanged", { period });
665
665
  });
666
666
  contentTimeBoundariesObserver.addEventListener("durationUpdate", (newDuration) => {
667
- mediaDurationUpdater.updateKnownDuration(newDuration);
667
+ mediaSourceDurationUpdater.updateDuration(newDuration.duration, !newDuration.isEnd);
668
668
  });
669
669
  contentTimeBoundariesObserver.addEventListener("endOfStream", () => {
670
670
  if (endOfStreamCanceller === null) {
@@ -681,6 +681,9 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
681
681
  endOfStreamCanceller = null;
682
682
  }
683
683
  });
684
+ const currentDuration = contentTimeBoundariesObserver.getCurrentDuration();
685
+ mediaSourceDurationUpdater.updateDuration(currentDuration.duration,
686
+ !currentDuration.isEnd);
684
687
  return contentTimeBoundariesObserver;
685
688
  }
686
689
 
@@ -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
@@ -20,10 +20,8 @@ import {
20
20
  onSourceClose,
21
21
  } from "../../../compat/event_listeners";
22
22
  import log from "../../../log";
23
- import Manifest from "../../../manifest";
24
23
  import createSharedReference, {
25
24
  IReadOnlySharedReference,
26
- ISharedReference,
27
25
  } from "../../../utils/reference";
28
26
  import TaskCanceller, {
29
27
  CancellationSignal,
@@ -33,124 +31,108 @@ import TaskCanceller, {
33
31
  const YEAR_IN_SECONDS = 365 * 24 * 3600;
34
32
 
35
33
  /**
36
- * Keep the MediaSource's duration up-to-date with what is being played.
37
- * @class MediaDurationUpdater
34
+ * Keep the MediaSource's `duration` attribute up-to-date with the duration of
35
+ * the content played on it.
36
+ * @class MediaSourceDurationUpdater
38
37
  */
39
- export default class MediaDurationUpdater {
40
- private _canceller : TaskCanceller;
38
+ export default class MediaSourceDurationUpdater {
39
+ /**
40
+ * `MediaSource` on which we're going to update the `duration` attribute.
41
+ */
42
+ private _mediaSource : MediaSource;
41
43
 
42
44
  /**
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.
45
+ * Abort the current duration-setting logic.
46
+ * `null` if no such logic is pending.
47
47
  */
48
- private _currentKnownDuration : ISharedReference<number | undefined>;
48
+ private _currentMediaSourceDurationUpdateCanceller : TaskCanceller | null;
49
49
 
50
50
  /**
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`.
51
+ * Create a new `MediaSourceDurationUpdater`,
56
52
  * @param {MediaSource} mediaSource - The MediaSource on which the content is
57
- * pushed.
53
+ * played.
58
54
  */
59
- constructor(manifest : Manifest, mediaSource : MediaSource) {
60
- const canceller = new TaskCanceller();
61
- const currentKnownDuration = createSharedReference(undefined, canceller.signal);
55
+ constructor(mediaSource : MediaSource) {
56
+ this._mediaSource = mediaSource;
57
+ this._currentMediaSourceDurationUpdateCanceller = null;
58
+ }
62
59
 
63
- this._canceller = canceller;
64
- this._currentKnownDuration = currentKnownDuration;
60
+ /**
61
+ * Indicate to the `MediaSourceDurationUpdater` the currently known duration
62
+ * of the content.
63
+ *
64
+ * The `MediaSourceDurationUpdater` will then use that value to determine
65
+ * which `duration` attribute should be set on the `MediaSource` associated
66
+ *
67
+ * @param {number} newDuration
68
+ * @param {boolean} addTimeMargin - If set to `true`, the current content is
69
+ * a dynamic content (it might evolve in the future) and the `newDuration`
70
+ * communicated might be greater still. In effect the
71
+ * `MediaSourceDurationUpdater` will actually set a much higher value to the
72
+ * `MediaSource`'s duration to prevent being annoyed by the HTML-related
73
+ * side-effects of having a too low duration (such as the impossibility to
74
+ * seek over that value).
75
+ */
76
+ public updateDuration(
77
+ newDuration : number,
78
+ addTimeMargin : boolean
79
+ ) : void {
80
+ if (this._currentMediaSourceDurationUpdateCanceller !== null) {
81
+ this._currentMediaSourceDurationUpdateCanceller.cancel();
82
+ }
83
+ this._currentMediaSourceDurationUpdateCanceller = new TaskCanceller();
65
84
 
85
+ const mediaSource = this._mediaSource;
86
+ const currentSignal = this._currentMediaSourceDurationUpdateCanceller.signal;
66
87
  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);
88
+ currentSignal);
72
89
 
90
+ /** TaskCanceller triggered each time the MediaSource switches to and from "open". */
91
+ let msOpenStatusCanceller = new TaskCanceller();
92
+ msOpenStatusCanceller.linkToSignal(currentSignal);
73
93
  isMediaSourceOpened.onUpdate(onMediaSourceOpenedStatusChanged,
74
94
  { emitCurrentValue: true,
75
- clearSignal: this._canceller.signal });
76
-
95
+ clearSignal: currentSignal });
77
96
 
78
97
  function onMediaSourceOpenedStatusChanged() {
79
- msUpdateCanceller.cancel();
98
+ msOpenStatusCanceller.cancel();
80
99
  if (!isMediaSourceOpened.getValue()) {
81
100
  return;
82
101
  }
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) {
102
+ msOpenStatusCanceller = new TaskCanceller();
103
+ msOpenStatusCanceller.linkToSignal(currentSignal);
109
104
  const areSourceBuffersUpdating = createSourceBuffersUpdatingReference(
110
105
  mediaSource.sourceBuffers,
111
- cancelSignal
106
+ msOpenStatusCanceller.signal
112
107
  );
113
-
114
108
  /** TaskCanceller triggered each time SourceBuffers' updating status changes */
115
109
  let sourceBuffersUpdatingCanceller = new TaskCanceller();
116
- sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal);
110
+ sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
111
+
117
112
  return areSourceBuffersUpdating.onUpdate((areUpdating) => {
118
113
  sourceBuffersUpdatingCanceller.cancel();
119
114
  sourceBuffersUpdatingCanceller = new TaskCanceller();
120
- sourceBuffersUpdatingCanceller.linkToSignal(cancelSignal);
115
+ sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
121
116
  if (areUpdating) {
122
117
  return;
123
118
  }
119
+
124
120
  recursivelyForceDurationUpdate(mediaSource,
125
- manifest,
126
- currentKnownDuration.getValue(),
127
- cancelSignal);
128
- }, { clearSignal: cancelSignal, emitCurrentValue: true });
121
+ newDuration,
122
+ addTimeMargin,
123
+ sourceBuffersUpdatingCanceller.signal);
124
+ }, { clearSignal: msOpenStatusCanceller.signal, emitCurrentValue: true });
129
125
  }
130
126
  }
131
127
 
132
128
  /**
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`.
129
+ * Abort the last duration-setting operation and free its resources.
151
130
  */
152
- public stop() {
153
- this._canceller.cancel();
131
+ public stopUpdating() {
132
+ if (this._currentMediaSourceDurationUpdateCanceller !== null) {
133
+ this._currentMediaSourceDurationUpdateCanceller.cancel();
134
+ this._currentMediaSourceDurationUpdateCanceller = null;
135
+ }
154
136
  }
155
137
  }
156
138
 
@@ -163,33 +145,27 @@ export default class MediaDurationUpdater {
163
145
  * - `null` if it hasn'nt been updated
164
146
  *
165
147
  * @param {MediaSource} mediaSource
166
- * @param {Object} manifest
148
+ * @param {number} duration
149
+ * @param {boolean} addTimeMargin
167
150
  * @returns {string}
168
151
  */
169
152
  function setMediaSourceDuration(
170
153
  mediaSource: MediaSource,
171
- manifest: Manifest,
172
- knownDuration : number | undefined
154
+ duration : number,
155
+ addTimeMargin : boolean
173
156
  ) : 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
- }
157
+ let newDuration = duration;
184
158
 
185
- if (manifest.isDynamic) {
159
+ if (addTimeMargin) {
186
160
  // 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
+ // Yet, in contents whose end is not yet known (e.g. live contents), we
162
+ // would prefer setting a value as high as possible to still be able to
163
+ // seek anywhere we want to (even ahead of the Manifest if we want to).
164
+ // As such, we put it at a safe default value of 2^32 excepted when the
165
+ // maximum position is already relatively close to that value, where we
166
+ // authorize exceptionally going over it.
167
+ newDuration = Math.max(Math.pow(2, 32),
168
+ newDuration + YEAR_IN_SECONDS);
193
169
  }
194
170
 
195
171
  let maxBufferedEnd : number = 0;
@@ -328,28 +304,28 @@ 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} addTimeMargin
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
+ addTimeMargin : boolean,
344
320
  cancelSignal : CancellationSignal
345
321
  ) : void {
346
- const res = setMediaSourceDuration(mediaSource, manifest, duration);
322
+ const res = setMediaSourceDuration(mediaSource, duration, addTimeMargin);
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, addTimeMargin, cancelSignal);
353
329
  }, 2000);
354
330
  const unregisterClear = cancelSignal.register(() => {
355
331
  clearTimeout(timeoutId);
@@ -466,7 +466,7 @@ export default function StreamOrchestrator(
466
466
  const nextPeriod = manifest.getPeriodAfter(basePeriod);
467
467
  if (nextPeriod !== null) {
468
468
  // current Stream is full, create the next one if not
469
- createNextPeriodStream(nextPeriod);
469
+ checkOrCreateNextPeriodStream(nextPeriod);
470
470
  }
471
471
  } else if (nextStreamInfo !== null) {
472
472
  // current Stream is active, destroy next Stream if created
@@ -495,13 +495,13 @@ export default function StreamOrchestrator(
495
495
  * Create `PeriodStream` for the next Period, specified under `nextPeriod`.
496
496
  * @param {Object} nextPeriod
497
497
  */
498
- function createNextPeriodStream(nextPeriod : Period) : void {
498
+ function checkOrCreateNextPeriodStream(nextPeriod : Period) : void {
499
499
  if (nextStreamInfo !== null) {
500
500
  if (nextStreamInfo.period.id === nextPeriod.id) {
501
501
  return;
502
502
  }
503
- log.warn("Stream: Creating next `PeriodStream` while it was already created.",
504
- bufferType);
503
+ log.warn("Stream: Creating next `PeriodStream` while one was already created.",
504
+ bufferType, nextPeriod.id, nextStreamInfo.period.id);
505
505
  consecutivePeriodStreamCb.periodStreamCleared({ type: bufferType,
506
506
  period: nextStreamInfo.period });
507
507
  nextStreamInfo.canceller.cancel();
@@ -487,6 +487,33 @@ const DEFAULT_CONFIG = {
487
487
  */
488
488
  SAMPLING_INTERVAL_NO_MEDIASOURCE: 500,
489
489
 
490
+ /**
491
+ * Amount of buffer to have ahead of the current position before we may
492
+ * consider buffer-based adaptive estimates, in seconds.
493
+ *
494
+ * For example setting it to `10` means that we need to have ten seconds of
495
+ * buffer ahead of the current position before relying on buffer-based
496
+ * adaptive estimates.
497
+ *
498
+ * To avoid getting in-and-out of the buffer-based logic all the time, it
499
+ * should be set higher than `ABR_EXIT_BUFFER_BASED_ALGO`.
500
+ */
501
+ ABR_ENTER_BUFFER_BASED_ALGO: 10,
502
+
503
+ /**
504
+ * Below this amount of buffer ahead of the current position, in seconds, we
505
+ * will stop using buffer-based estimate in our adaptive logic to select a
506
+ * quality.
507
+ *
508
+ * For example setting it to `5` means that if we have less than 5 seconds of
509
+ * buffer ahead of the current position, we should stop relying on
510
+ * buffer-based estimates to choose a quality.
511
+ *
512
+ * To avoid getting in-and-out of the buffer-based logic all the time, it
513
+ * should be set lower than `ABR_ENTER_BUFFER_BASED_ALGO`.
514
+ */
515
+ ABR_EXIT_BUFFER_BASED_ALGO: 5,
516
+
490
517
  /**
491
518
  * Minimum number of bytes sampled before we trust the estimate.
492
519
  * If we have not sampled much data, our estimate may not be accurate
@@ -525,8 +552,8 @@ const DEFAULT_CONFIG = {
525
552
  * @type {Object}
526
553
  */
527
554
  ABR_REGULAR_FACTOR: {
528
- DEFAULT: 0.8,
529
- LOW_LATENCY: 0.8,
555
+ DEFAULT: 0.72,
556
+ LOW_LATENCY: 0.72,
530
557
  },
531
558
 
532
559
  /**
@@ -20,7 +20,7 @@
20
20
  * not always understood by newcomers to the code, and which can be overused when
21
21
  * only one of the possibility can arise.
22
22
  * @param {*} x
23
- * @returns {*}
23
+ * @returns {boolean}
24
24
  */
25
25
  export default function isNullOrUndefined(x : unknown) : x is null | undefined | void {
26
26
  return x === null || x === undefined;