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.
- package/CHANGELOG.md +8 -2
- package/VERSION +1 -1
- package/dist/_esm5.processed/config.d.ts +2 -0
- package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +4 -2
- package/dist/_esm5.processed/core/adaptive/buffer_based_chooser.js +4 -4
- package/dist/_esm5.processed/core/adaptive/network_analyzer.js +8 -5
- package/dist/_esm5.processed/core/api/public_api.js +2 -2
- package/dist/_esm5.processed/core/init/media_source_content_initializer.js +6 -4
- package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.d.ts +28 -1
- package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.js +22 -9
- package/dist/_esm5.processed/core/init/utils/media_source_duration_updater.d.ts +58 -0
- package/dist/_esm5.processed/core/init/utils/{media_duration_updater.js → media_source_duration_updater.js} +71 -85
- package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +3 -3
- package/dist/_esm5.processed/default_config.d.ts +25 -0
- package/dist/_esm5.processed/default_config.js +27 -2
- package/dist/_esm5.processed/utils/is_null_or_undefined.d.ts +1 -1
- package/dist/_esm5.processed/utils/is_null_or_undefined.js +1 -1
- package/dist/rx-player.js +156 -115
- package/dist/rx-player.min.js +1 -1
- package/package.json +1 -1
- package/sonar-project.properties +1 -1
- package/src/core/adaptive/adaptive_representation_selector.ts +6 -2
- package/src/core/adaptive/buffer_based_chooser.ts +4 -4
- package/src/core/adaptive/network_analyzer.ts +9 -4
- package/src/core/api/public_api.ts +2 -2
- package/src/core/init/media_source_content_initializer.ts +7 -4
- package/src/core/init/utils/content_time_boundaries_observer.ts +46 -10
- package/src/core/init/utils/{media_duration_updater.ts → media_source_duration_updater.ts} +87 -111
- package/src/core/stream/orchestrator/stream_orchestrator.ts +4 -4
- package/src/default_config.ts +29 -2
- package/src/utils/is_null_or_undefined.ts +1 -1
- package/dist/_esm5.processed/core/init/utils/media_duration_updater.d.ts +0 -56
package/package.json
CHANGED
package/sonar-project.properties
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
|
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
|
|
69
|
+
if (currentScore !== undefined) {
|
|
70
70
|
scaledScore = speed === 0 ? currentScore : (currentScore / speed);
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
if (scaledScore
|
|
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
|
|
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
|
|
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 >
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
647
|
+
const mediaSourceDurationUpdater = new MediaSourceDurationUpdater(mediaSource);
|
|
648
648
|
cancelSignal.register(() => {
|
|
649
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
158
|
-
|
|
159
|
-
|
|
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 :
|
|
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
|
|
37
|
-
*
|
|
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
|
|
40
|
-
|
|
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
|
-
*
|
|
44
|
-
*
|
|
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
|
|
48
|
+
private _currentMediaSourceDurationUpdateCanceller : TaskCanceller | null;
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Create a new `
|
|
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
|
-
*
|
|
53
|
+
* played.
|
|
58
54
|
*/
|
|
59
|
-
constructor(
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
constructor(mediaSource : MediaSource) {
|
|
56
|
+
this._mediaSource = mediaSource;
|
|
57
|
+
this._currentMediaSourceDurationUpdateCanceller = null;
|
|
58
|
+
}
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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:
|
|
76
|
-
|
|
95
|
+
clearSignal: currentSignal });
|
|
77
96
|
|
|
78
97
|
function onMediaSourceOpenedStatusChanged() {
|
|
79
|
-
|
|
98
|
+
msOpenStatusCanceller.cancel();
|
|
80
99
|
if (!isMediaSourceOpened.getValue()) {
|
|
81
100
|
return;
|
|
82
101
|
}
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
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(
|
|
110
|
+
sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
|
|
111
|
+
|
|
117
112
|
return areSourceBuffersUpdating.onUpdate((areUpdating) => {
|
|
118
113
|
sourceBuffersUpdatingCanceller.cancel();
|
|
119
114
|
sourceBuffersUpdatingCanceller = new TaskCanceller();
|
|
120
|
-
sourceBuffersUpdatingCanceller.linkToSignal(
|
|
115
|
+
sourceBuffersUpdatingCanceller.linkToSignal(msOpenStatusCanceller.signal);
|
|
121
116
|
if (areUpdating) {
|
|
122
117
|
return;
|
|
123
118
|
}
|
|
119
|
+
|
|
124
120
|
recursivelyForceDurationUpdate(mediaSource,
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}, { clearSignal:
|
|
121
|
+
newDuration,
|
|
122
|
+
addTimeMargin,
|
|
123
|
+
sourceBuffersUpdatingCanceller.signal);
|
|
124
|
+
}, { clearSignal: msOpenStatusCanceller.signal, emitCurrentValue: true });
|
|
129
125
|
}
|
|
130
126
|
}
|
|
131
127
|
|
|
132
128
|
/**
|
|
133
|
-
*
|
|
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
|
|
153
|
-
this.
|
|
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 {
|
|
148
|
+
* @param {number} duration
|
|
149
|
+
* @param {boolean} addTimeMargin
|
|
167
150
|
* @returns {string}
|
|
168
151
|
*/
|
|
169
152
|
function setMediaSourceDuration(
|
|
170
153
|
mediaSource: MediaSource,
|
|
171
|
-
|
|
172
|
-
|
|
154
|
+
duration : number,
|
|
155
|
+
addTimeMargin : boolean
|
|
173
156
|
) : MediaSourceDurationUpdateStatus {
|
|
174
|
-
let newDuration
|
|
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 (
|
|
159
|
+
if (addTimeMargin) {
|
|
186
160
|
// Some targets poorly support setting a very high number for durations.
|
|
187
|
-
// Yet, in
|
|
188
|
-
//
|
|
189
|
-
// we want to
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
|
|
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
|
|
307
|
+
* one.
|
|
332
308
|
*
|
|
333
309
|
* If it fails, wait 2 seconds and retries.
|
|
334
310
|
*
|
|
335
311
|
* @param {MediaSource} mediaSource
|
|
336
|
-
* @param {
|
|
337
|
-
* @param {
|
|
312
|
+
* @param {number} duration
|
|
313
|
+
* @param {boolean} addTimeMargin
|
|
338
314
|
* @param {Object} cancelSignal
|
|
339
315
|
*/
|
|
340
316
|
function recursivelyForceDurationUpdate(
|
|
341
317
|
mediaSource : MediaSource,
|
|
342
|
-
|
|
343
|
-
|
|
318
|
+
duration : number,
|
|
319
|
+
addTimeMargin : boolean,
|
|
344
320
|
cancelSignal : CancellationSignal
|
|
345
321
|
) : void {
|
|
346
|
-
const res = setMediaSourceDuration(mediaSource,
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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();
|
package/src/default_config.ts
CHANGED
|
@@ -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.
|
|
529
|
-
LOW_LATENCY: 0.
|
|
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;
|