rx-player 3.27.0 → 3.27.1-dev.2022041500
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 +12 -0
- package/VERSION +1 -1
- package/dist/_esm5.processed/core/abr/abr_manager.d.ts +7 -3
- package/dist/_esm5.processed/core/abr/abr_manager.js +4 -3
- package/dist/_esm5.processed/core/abr/representation_estimator.d.ts +13 -8
- package/dist/_esm5.processed/core/abr/representation_estimator.js +4 -4
- package/dist/_esm5.processed/core/api/public_api.js +13 -14
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.d.ts +52 -0
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +223 -0
- package/dist/_esm5.processed/core/init/create_stream_playback_observer.js +17 -6
- package/dist/_esm5.processed/core/init/get_initial_time.d.ts +30 -5
- package/dist/_esm5.processed/core/init/get_initial_time.js +20 -13
- package/dist/_esm5.processed/core/init/load_on_media_source.js +16 -3
- package/dist/_esm5.processed/core/init/media_duration_updater.d.ts +56 -0
- package/dist/_esm5.processed/core/init/media_duration_updater.js +188 -0
- package/dist/_esm5.processed/core/stream/adaptation/adaptation_stream.d.ts +2 -5
- package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.d.ts +3 -2
- package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.js +3 -3
- package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -22
- package/dist/_esm5.processed/core/stream/period/period_stream.d.ts +2 -5
- package/dist/_esm5.processed/core/stream/period/period_stream.js +6 -4
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +4 -2
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +85 -42
- package/dist/_esm5.processed/core/stream/representation/representation_stream.js +3 -3
- package/dist/_esm5.processed/manifest/manifest.d.ts +14 -5
- package/dist/_esm5.processed/manifest/manifest.js +31 -12
- package/dist/_esm5.processed/manifest/period.d.ts +6 -0
- package/dist/_esm5.processed/manifest/period.js +9 -0
- package/dist/_esm5.processed/manifest/representation_index/types.d.ts +7 -2
- package/dist/_esm5.processed/parsers/manifest/dash/common/get_minimum_and_maximum_positions.d.ts +5 -1
- package/dist/_esm5.processed/parsers/manifest/dash/common/get_minimum_and_maximum_positions.js +6 -4
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +2 -1
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +4 -0
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +4 -2
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +4 -2
- package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +3 -3
- package/dist/_esm5.processed/parsers/manifest/dash/common/parse_mpd.js +28 -20
- package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +3 -2
- package/dist/_esm5.processed/parsers/manifest/metaplaylist/metaplaylist_parser.js +9 -3
- package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +26 -17
- package/dist/_esm5.processed/parsers/manifest/types.d.ts +45 -17
- package/dist/_esm5.processed/parsers/manifest/utils/{get_maximum_position.d.ts → get_maximum_positions.d.ts} +4 -1
- package/dist/_esm5.processed/parsers/manifest/utils/{get_maximum_position.js → get_maximum_positions.js} +10 -6
- package/dist/rx-player.js +854 -351
- package/dist/rx-player.min.js +1 -1
- package/package.json +1 -1
- package/sonar-project.properties +1 -1
- package/src/core/abr/abr_manager.ts +11 -3
- package/src/core/abr/representation_estimator.ts +17 -10
- package/src/core/api/public_api.ts +13 -15
- package/src/core/init/content_time_boundaries_observer.ts +312 -0
- package/src/core/init/create_stream_playback_observer.ts +18 -7
- package/src/core/init/get_initial_time.ts +52 -19
- package/src/core/init/load_on_media_source.ts +22 -5
- package/src/core/init/media_duration_updater.ts +268 -0
- package/src/core/stream/adaptation/adaptation_stream.ts +2 -5
- package/src/core/stream/adaptation/create_representation_estimator.ts +6 -3
- package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -29
- package/src/core/stream/period/period_stream.ts +12 -9
- package/src/core/stream/representation/get_buffer_status.ts +102 -41
- package/src/core/stream/representation/representation_stream.ts +3 -3
- package/src/manifest/__tests__/manifest.test.ts +94 -68
- package/src/manifest/manifest.ts +73 -33
- package/src/manifest/period.ts +10 -0
- package/src/manifest/representation_index/types.ts +7 -2
- package/src/parsers/manifest/dash/common/get_minimum_and_maximum_positions.ts +10 -5
- package/src/parsers/manifest/dash/common/indexes/base.ts +4 -3
- package/src/parsers/manifest/dash/common/indexes/list.ts +8 -1
- package/src/parsers/manifest/dash/common/indexes/template.ts +8 -3
- package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +7 -7
- package/src/parsers/manifest/dash/common/parse_mpd.ts +38 -20
- package/src/parsers/manifest/local/parse_local_manifest.ts +3 -2
- package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +9 -4
- package/src/parsers/manifest/smooth/create_parser.ts +36 -18
- package/src/parsers/manifest/types.ts +45 -17
- package/src/parsers/manifest/utils/{get_maximum_position.ts → get_maximum_positions.ts} +13 -7
- package/dist/_esm5.processed/core/init/duration_updater.d.ts +0 -27
- package/dist/_esm5.processed/core/init/duration_updater.js +0 -136
- package/src/core/init/duration_updater.ts +0 -206
|
@@ -17,13 +17,40 @@
|
|
|
17
17
|
import config from "../../config";
|
|
18
18
|
import log from "../../log";
|
|
19
19
|
import Manifest from "../../manifest";
|
|
20
|
+
import isNullOrUndefined from "../../utils/is_null_or_undefined";
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
/**
|
|
23
|
+
* All possible initial time options that can be set.
|
|
24
|
+
*
|
|
25
|
+
* Written this way (with many type possibilities) on purpose to avoid issues
|
|
26
|
+
* if the application using the RxPlayer gives undocumented values such as
|
|
27
|
+
* `null`.
|
|
28
|
+
*
|
|
29
|
+
* TODO we shouldn't be that robust, at least not outside the API code, because
|
|
30
|
+
* if we begin to be that liberal here, where should we end?
|
|
31
|
+
* We may instead either want to progressively remove tolerance on the wrong
|
|
32
|
+
* type being given or be better to enforce type-compliancy in the API code.
|
|
33
|
+
*/
|
|
34
|
+
export interface IInitialTimeOptions {
|
|
35
|
+
/** If set, we should begin at this position, in seconds. */
|
|
36
|
+
position? : number | null | undefined;
|
|
37
|
+
/** If set, we should begin at this unix timestamp, in seconds. */
|
|
38
|
+
wallClockTime? : number | null | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* If set, we should begin at this position relative to the content's start,
|
|
41
|
+
* in seconds.
|
|
42
|
+
*/
|
|
43
|
+
fromFirstPosition? : number | null | undefined;
|
|
44
|
+
/**
|
|
45
|
+
* If set, we should begin at this position relative to the content's end,
|
|
46
|
+
* in seconds.
|
|
47
|
+
*/
|
|
48
|
+
fromLastPosition? : number | null | undefined;
|
|
49
|
+
/** If set, we should begin at this position relative to the whole duration of
|
|
50
|
+
* the content, in percentage.
|
|
51
|
+
*/
|
|
52
|
+
percentage? : number | null | undefined;
|
|
53
|
+
}
|
|
27
54
|
|
|
28
55
|
/**
|
|
29
56
|
* Returns the calculated initial time for the content described by the given
|
|
@@ -43,33 +70,39 @@ export default function getInitialTime(
|
|
|
43
70
|
lowLatencyMode : boolean,
|
|
44
71
|
startAt? : IInitialTimeOptions
|
|
45
72
|
) : number {
|
|
46
|
-
if (startAt
|
|
47
|
-
const min = manifest.
|
|
48
|
-
|
|
49
|
-
if (
|
|
73
|
+
if (!isNullOrUndefined(startAt)) {
|
|
74
|
+
const min = manifest.getMinimumSafePosition();
|
|
75
|
+
let max;
|
|
76
|
+
if (manifest.isLive) {
|
|
77
|
+
max = manifest.getLivePosition();
|
|
78
|
+
}
|
|
79
|
+
if (max === undefined) {
|
|
80
|
+
max = manifest.getMaximumSafePosition();
|
|
81
|
+
}
|
|
82
|
+
if (!isNullOrUndefined(startAt.position)) {
|
|
50
83
|
log.debug("Init: using startAt.minimumPosition");
|
|
51
84
|
return Math.max(Math.min(startAt.position, max), min);
|
|
52
85
|
}
|
|
53
|
-
else if (startAt.wallClockTime
|
|
86
|
+
else if (!isNullOrUndefined(startAt.wallClockTime)) {
|
|
54
87
|
log.debug("Init: using startAt.wallClockTime");
|
|
55
|
-
const ast = manifest.availabilityStartTime
|
|
88
|
+
const ast = manifest.availabilityStartTime === undefined ?
|
|
56
89
|
0 :
|
|
57
90
|
manifest.availabilityStartTime;
|
|
58
91
|
const position = startAt.wallClockTime - ast;
|
|
59
92
|
return Math.max(Math.min(position, max), min);
|
|
60
93
|
}
|
|
61
|
-
else if (startAt.fromFirstPosition
|
|
94
|
+
else if (!isNullOrUndefined(startAt.fromFirstPosition)) {
|
|
62
95
|
log.debug("Init: using startAt.fromFirstPosition");
|
|
63
96
|
const { fromFirstPosition } = startAt;
|
|
64
97
|
return fromFirstPosition <= 0 ? min :
|
|
65
98
|
Math.min(max, min + fromFirstPosition);
|
|
66
99
|
}
|
|
67
|
-
else if (startAt.fromLastPosition
|
|
100
|
+
else if (!isNullOrUndefined(startAt.fromLastPosition)) {
|
|
68
101
|
log.debug("Init: using startAt.fromLastPosition");
|
|
69
102
|
const { fromLastPosition } = startAt;
|
|
70
103
|
return fromLastPosition >= 0 ? max :
|
|
71
104
|
Math.max(min, max + fromLastPosition);
|
|
72
|
-
} else if (startAt.percentage
|
|
105
|
+
} else if (!isNullOrUndefined(startAt.percentage)) {
|
|
73
106
|
log.debug("Init: using startAt.percentage");
|
|
74
107
|
const { percentage } = startAt;
|
|
75
108
|
if (percentage > 100) {
|
|
@@ -83,22 +116,22 @@ export default function getInitialTime(
|
|
|
83
116
|
}
|
|
84
117
|
}
|
|
85
118
|
|
|
86
|
-
const minimumPosition = manifest.
|
|
119
|
+
const minimumPosition = manifest.getMinimumSafePosition();
|
|
87
120
|
if (manifest.isLive) {
|
|
88
121
|
const { suggestedPresentationDelay,
|
|
89
122
|
clockOffset } = manifest;
|
|
90
|
-
const maximumPosition = manifest.
|
|
123
|
+
const maximumPosition = manifest.getMaximumSafePosition();
|
|
91
124
|
let liveTime : number;
|
|
92
125
|
const { DEFAULT_LIVE_GAP } = config.getCurrent();
|
|
93
126
|
|
|
94
|
-
if (clockOffset
|
|
127
|
+
if (clockOffset === undefined) {
|
|
95
128
|
log.info("Init: no clock offset found for a live content, " +
|
|
96
129
|
"starting close to maximum available position");
|
|
97
130
|
liveTime = maximumPosition;
|
|
98
131
|
} else {
|
|
99
132
|
log.info("Init: clock offset found for a live content, " +
|
|
100
133
|
"checking if we can start close to it");
|
|
101
|
-
const ast = manifest.availabilityStartTime
|
|
134
|
+
const ast = manifest.availabilityStartTime === undefined ?
|
|
102
135
|
0 :
|
|
103
136
|
manifest.availabilityStartTime;
|
|
104
137
|
const clockRelativeLiveTime = (performance.now() + clockOffset) / 1000 - ast;
|
|
@@ -39,11 +39,12 @@ import SegmentBuffersStore from "../segment_buffers";
|
|
|
39
39
|
import StreamOrchestrator, {
|
|
40
40
|
IStreamOrchestratorOptions,
|
|
41
41
|
} from "../stream";
|
|
42
|
+
import ContentTimeBoundariesObserver from "./content_time_boundaries_observer";
|
|
42
43
|
import createStreamPlaybackObserver from "./create_stream_playback_observer";
|
|
43
|
-
import DurationUpdater from "./duration_updater";
|
|
44
44
|
import emitLoadedEvent from "./emit_loaded_event";
|
|
45
45
|
import { maintainEndOfStream } from "./end_of_stream";
|
|
46
46
|
import initialSeekAndPlay from "./initial_seek_and_play";
|
|
47
|
+
import MediaDurationUpdater from "./media_duration_updater";
|
|
47
48
|
import StallAvoider, {
|
|
48
49
|
IDiscontinuityEvent,
|
|
49
50
|
ILockedStreamEvent,
|
|
@@ -104,7 +105,7 @@ export default function createMediaSourceLoader(
|
|
|
104
105
|
autoPlay : boolean
|
|
105
106
|
) : Observable<IMediaSourceLoaderEvent> {
|
|
106
107
|
/** Maintains the MediaSource's duration up-to-date with the Manifest */
|
|
107
|
-
const
|
|
108
|
+
const mediaDurationUpdater = new MediaDurationUpdater(manifest, mediaSource);
|
|
108
109
|
|
|
109
110
|
const initialPeriod = manifest.getPeriodForTime(initialTime) ??
|
|
110
111
|
manifest.getNextPeriod(initialTime);
|
|
@@ -154,7 +155,7 @@ export default function createMediaSourceLoader(
|
|
|
154
155
|
segmentFetcherCreator,
|
|
155
156
|
bufferOptions
|
|
156
157
|
).pipe(
|
|
157
|
-
mergeMap((evt)
|
|
158
|
+
mergeMap((evt) => {
|
|
158
159
|
switch (evt.type) {
|
|
159
160
|
case "end-of-stream":
|
|
160
161
|
log.debug("Init: end-of-stream order received.");
|
|
@@ -181,6 +182,21 @@ export default function createMediaSourceLoader(
|
|
|
181
182
|
})
|
|
182
183
|
);
|
|
183
184
|
|
|
185
|
+
const contentTimeObserver = ContentTimeBoundariesObserver(manifest,
|
|
186
|
+
streams$,
|
|
187
|
+
streamObserver)
|
|
188
|
+
.pipe(
|
|
189
|
+
mergeMap((evt) => {
|
|
190
|
+
switch (evt.type) {
|
|
191
|
+
case "contentDurationUpdate":
|
|
192
|
+
log.debug("Init: Duration has to be updated.", evt.value);
|
|
193
|
+
mediaDurationUpdater.updateKnownDuration(evt.value);
|
|
194
|
+
return EMPTY;
|
|
195
|
+
default:
|
|
196
|
+
return observableOf(evt);
|
|
197
|
+
}
|
|
198
|
+
}));
|
|
199
|
+
|
|
184
200
|
/**
|
|
185
201
|
* On subscription, keep the playback speed synchronized to the speed set by
|
|
186
202
|
* the user on the media element and force a speed of `0` when the buffer is
|
|
@@ -209,13 +225,14 @@ export default function createMediaSourceLoader(
|
|
|
209
225
|
observableOf(evt) :
|
|
210
226
|
emitLoadedEvent(observation$, mediaElement, segmentBuffersStore, false)));
|
|
211
227
|
|
|
212
|
-
return observableMerge(
|
|
213
|
-
loadingEvts$,
|
|
228
|
+
return observableMerge(loadingEvts$,
|
|
214
229
|
playbackRate$,
|
|
215
230
|
stallAvoider$,
|
|
216
231
|
streams$,
|
|
232
|
+
contentTimeObserver,
|
|
217
233
|
streamEvents$
|
|
218
234
|
).pipe(finalize(() => {
|
|
235
|
+
mediaDurationUpdater.stop();
|
|
219
236
|
// clean-up every created SegmentBuffers
|
|
220
237
|
segmentBuffersStore.disposeAll();
|
|
221
238
|
}));
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright 2015 CANAL+ Group
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
combineLatest as observableCombineLatest,
|
|
19
|
+
distinctUntilChanged,
|
|
20
|
+
EMPTY,
|
|
21
|
+
fromEvent as observableFromEvent,
|
|
22
|
+
interval as observableInterval,
|
|
23
|
+
map,
|
|
24
|
+
merge as observableMerge,
|
|
25
|
+
mergeMap,
|
|
26
|
+
Observable,
|
|
27
|
+
of as observableOf,
|
|
28
|
+
startWith,
|
|
29
|
+
Subscription,
|
|
30
|
+
switchMap,
|
|
31
|
+
timer,
|
|
32
|
+
} from "rxjs";
|
|
33
|
+
import {
|
|
34
|
+
onSourceOpen$,
|
|
35
|
+
onSourceClose$,
|
|
36
|
+
onSourceEnded$,
|
|
37
|
+
} from "../../compat/event_listeners";
|
|
38
|
+
import log from "../../log";
|
|
39
|
+
import Manifest from "../../manifest";
|
|
40
|
+
import { fromEvent } from "../../utils/event_emitter";
|
|
41
|
+
import createSharedReference, {
|
|
42
|
+
ISharedReference,
|
|
43
|
+
} from "../../utils/reference";
|
|
44
|
+
|
|
45
|
+
/** Number of seconds in a regular year. */
|
|
46
|
+
const YEAR_IN_SECONDS = 365 * 24 * 3600;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Keep the MediaSource's duration up-to-date with what is being played.
|
|
50
|
+
* @class MediaDurationUpdater
|
|
51
|
+
*/
|
|
52
|
+
export default class MediaDurationUpdater {
|
|
53
|
+
private _subscription : Subscription;
|
|
54
|
+
/**
|
|
55
|
+
* The last known audio Adaptation (i.e. track) chosen for the last Period.
|
|
56
|
+
* Useful to determinate the duration of the current content.
|
|
57
|
+
* `undefined` if the audio track for the last Period has never been known yet.
|
|
58
|
+
* `null` if there are no chosen audio Adaptation.
|
|
59
|
+
*/
|
|
60
|
+
private _lastKnownDuration : ISharedReference<number | undefined>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create a new `MediaDurationUpdater` that will keep the given MediaSource's
|
|
64
|
+
* duration as soon as possible.
|
|
65
|
+
* This duration will be updated until the `stop` method is called.
|
|
66
|
+
* @param {Object} manifest - The Manifest currently played.
|
|
67
|
+
* For another content, you will have to create another `MediaDurationUpdater`.
|
|
68
|
+
* @param {MediaSource} mediaSource - The MediaSource on which the content is
|
|
69
|
+
* pushed.
|
|
70
|
+
*/
|
|
71
|
+
constructor(manifest : Manifest, mediaSource : MediaSource) {
|
|
72
|
+
this._lastKnownDuration = createSharedReference(undefined);
|
|
73
|
+
this._subscription = isMediaSourceOpened$(mediaSource).pipe(
|
|
74
|
+
switchMap((canUpdate) =>
|
|
75
|
+
canUpdate ? observableCombineLatest([this._lastKnownDuration.asObservable(),
|
|
76
|
+
fromEvent(manifest, "manifestUpdate")
|
|
77
|
+
.pipe(startWith(null))]) :
|
|
78
|
+
EMPTY
|
|
79
|
+
),
|
|
80
|
+
switchMap(([lastKnownDuration]) =>
|
|
81
|
+
areSourceBuffersUpdating$(mediaSource.sourceBuffers).pipe(
|
|
82
|
+
switchMap((areSBUpdating) => {
|
|
83
|
+
return areSBUpdating ? EMPTY :
|
|
84
|
+
recursivelyTryUpdatingDuration();
|
|
85
|
+
function recursivelyTryUpdatingDuration() : Observable<never> {
|
|
86
|
+
const res = setMediaSourceDuration(mediaSource,
|
|
87
|
+
manifest,
|
|
88
|
+
lastKnownDuration);
|
|
89
|
+
if (res === MediaSourceDurationUpdateStatus.Success) {
|
|
90
|
+
return EMPTY;
|
|
91
|
+
}
|
|
92
|
+
return timer(2000)
|
|
93
|
+
.pipe(mergeMap(() => recursivelyTryUpdatingDuration()));
|
|
94
|
+
}
|
|
95
|
+
})))).subscribe();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* By default, the `MediaDurationUpdater` only set a safe estimate for the
|
|
100
|
+
* MediaSource's duration.
|
|
101
|
+
* A more precize duration can be set by communicating to it a more precize
|
|
102
|
+
* media duration through `updateKnownDuration`.
|
|
103
|
+
* If the duration becomes unknown, `undefined` can be given to it so the
|
|
104
|
+
* `MediaDurationUpdater` goes back to a safe estimate.
|
|
105
|
+
* @param {number | undefined} newDuration
|
|
106
|
+
*/
|
|
107
|
+
public updateKnownDuration(
|
|
108
|
+
newDuration : number | undefined
|
|
109
|
+
) : void {
|
|
110
|
+
this._lastKnownDuration.setValue(newDuration);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Stop the `MediaDurationUpdater` from updating and free its resources.
|
|
115
|
+
* Once stopped, it is not possible to start it again, beside creating another
|
|
116
|
+
* `MediaDurationUpdater`.
|
|
117
|
+
*/
|
|
118
|
+
public stop() {
|
|
119
|
+
this._subscription.unsubscribe();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Checks that duration can be updated on the MediaSource, and then
|
|
125
|
+
* sets it.
|
|
126
|
+
*
|
|
127
|
+
* Returns either:
|
|
128
|
+
* - the new duration it has been updated to if it has
|
|
129
|
+
* - `null` if it hasn'nt been updated
|
|
130
|
+
*
|
|
131
|
+
* @param {MediaSource} mediaSource
|
|
132
|
+
* @param {Object} manifest
|
|
133
|
+
* @returns {string}
|
|
134
|
+
*/
|
|
135
|
+
function setMediaSourceDuration(
|
|
136
|
+
mediaSource: MediaSource,
|
|
137
|
+
manifest: Manifest,
|
|
138
|
+
knownDuration : number | undefined
|
|
139
|
+
) : MediaSourceDurationUpdateStatus {
|
|
140
|
+
let newDuration = knownDuration;
|
|
141
|
+
|
|
142
|
+
if (newDuration === undefined) {
|
|
143
|
+
if (manifest.isDynamic) {
|
|
144
|
+
const maxPotentialPos = manifest.getLivePosition() ??
|
|
145
|
+
manifest.getMaximumSafePosition();
|
|
146
|
+
// Some targets poorly support setting a very high number for durations.
|
|
147
|
+
// Yet, in dynamic contents, we would prefer setting a value as high as possible
|
|
148
|
+
// to still be able to seek anywhere we want to (even ahead of the Manifest if
|
|
149
|
+
// we want to). As such, we put it at a safe default value of 2^32 excepted
|
|
150
|
+
// when the maximum position is already relatively close to that value, where
|
|
151
|
+
// we authorize exceptionally going over it.
|
|
152
|
+
newDuration = Math.max(Math.pow(2, 32), maxPotentialPos + YEAR_IN_SECONDS);
|
|
153
|
+
} else {
|
|
154
|
+
newDuration = manifest.getMaximumSafePosition();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
let maxBufferedEnd : number = 0;
|
|
159
|
+
for (let i = 0; i < mediaSource.sourceBuffers.length; i++) {
|
|
160
|
+
const sourceBuffer = mediaSource.sourceBuffers[i];
|
|
161
|
+
const sbBufferedLen = sourceBuffer.buffered.length;
|
|
162
|
+
if (sbBufferedLen > 0) {
|
|
163
|
+
maxBufferedEnd = Math.max(sourceBuffer.buffered.end(sbBufferedLen - 1));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (newDuration === mediaSource.duration) {
|
|
168
|
+
return MediaSourceDurationUpdateStatus.Success;
|
|
169
|
+
} else if (maxBufferedEnd > newDuration) {
|
|
170
|
+
// We already buffered further than the duration we want to set.
|
|
171
|
+
// Keep the duration that was set at that time as a security.
|
|
172
|
+
if (maxBufferedEnd < mediaSource.duration) {
|
|
173
|
+
try {
|
|
174
|
+
log.info("Init: Updating duration to what is currently buffered", maxBufferedEnd);
|
|
175
|
+
mediaSource.duration = newDuration;
|
|
176
|
+
} catch (err) {
|
|
177
|
+
log.warn("Duration Updater: Can't update duration on the MediaSource.", err);
|
|
178
|
+
return MediaSourceDurationUpdateStatus.Failed;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return MediaSourceDurationUpdateStatus.Partial;
|
|
182
|
+
} else {
|
|
183
|
+
const oldDuration = mediaSource.duration;
|
|
184
|
+
try {
|
|
185
|
+
log.info("Init: Updating duration", newDuration);
|
|
186
|
+
mediaSource.duration = newDuration;
|
|
187
|
+
} catch (err) {
|
|
188
|
+
log.warn("Duration Updater: Can't update duration on the MediaSource.", err);
|
|
189
|
+
return MediaSourceDurationUpdateStatus.Failed;
|
|
190
|
+
}
|
|
191
|
+
const deltaToExpected = Math.abs(mediaSource.duration - newDuration);
|
|
192
|
+
if (deltaToExpected >= 0.1) {
|
|
193
|
+
const deltaToBefore = Math.abs(mediaSource.duration - oldDuration);
|
|
194
|
+
return deltaToExpected < deltaToBefore ? MediaSourceDurationUpdateStatus.Partial :
|
|
195
|
+
MediaSourceDurationUpdateStatus.Failed;
|
|
196
|
+
}
|
|
197
|
+
return MediaSourceDurationUpdateStatus.Success;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* String describing the result of the process of updating a MediaSource's
|
|
203
|
+
* duration.
|
|
204
|
+
*/
|
|
205
|
+
const enum MediaSourceDurationUpdateStatus {
|
|
206
|
+
/** The MediaSource's duration has been updated to the asked duration. */
|
|
207
|
+
Success = "success",
|
|
208
|
+
/**
|
|
209
|
+
* The MediaSource's duration has been updated and is now closer to the asked
|
|
210
|
+
* duration but is not yet the actually asked duration.
|
|
211
|
+
*/
|
|
212
|
+
Partial = "partial",
|
|
213
|
+
/**
|
|
214
|
+
* The MediaSource's duration could not have been updated due to an issue or
|
|
215
|
+
* has been updated but to a value actually further from the asked duration
|
|
216
|
+
* from what it was before.
|
|
217
|
+
*/
|
|
218
|
+
Failed = "failed",
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Returns an Observable which will emit only when all the SourceBuffers ended
|
|
223
|
+
* all pending updates.
|
|
224
|
+
* @param {SourceBufferList} sourceBuffers
|
|
225
|
+
* @returns {Observable}
|
|
226
|
+
*/
|
|
227
|
+
function areSourceBuffersUpdating$(
|
|
228
|
+
sourceBuffers: SourceBufferList
|
|
229
|
+
) : Observable<boolean> {
|
|
230
|
+
if (sourceBuffers.length === 0) {
|
|
231
|
+
return observableOf(false);
|
|
232
|
+
}
|
|
233
|
+
const sourceBufferUpdatingStatuses : Array<Observable<boolean>> = [];
|
|
234
|
+
|
|
235
|
+
for (let i = 0; i < sourceBuffers.length; i++) {
|
|
236
|
+
const sourceBuffer = sourceBuffers[i];
|
|
237
|
+
sourceBufferUpdatingStatuses.push(
|
|
238
|
+
observableMerge(
|
|
239
|
+
observableFromEvent(sourceBuffer, "updatestart").pipe(map(() => true)),
|
|
240
|
+
observableFromEvent(sourceBuffer, "update").pipe(map(() => false)),
|
|
241
|
+
observableInterval(500).pipe(map(() => sourceBuffer.updating))
|
|
242
|
+
).pipe(
|
|
243
|
+
startWith(sourceBuffer.updating),
|
|
244
|
+
distinctUntilChanged()
|
|
245
|
+
)
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
return observableCombineLatest(sourceBufferUpdatingStatuses).pipe(
|
|
249
|
+
map((areUpdating) => {
|
|
250
|
+
return areUpdating.some((isUpdating) => isUpdating);
|
|
251
|
+
}),
|
|
252
|
+
distinctUntilChanged());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Emit a boolean that tells if the media source is opened or not.
|
|
257
|
+
* @param {MediaSource} mediaSource
|
|
258
|
+
* @returns {Object}
|
|
259
|
+
*/
|
|
260
|
+
function isMediaSourceOpened$(mediaSource: MediaSource): Observable<boolean> {
|
|
261
|
+
return observableMerge(onSourceOpen$(mediaSource).pipe(map(() => true)),
|
|
262
|
+
onSourceEnded$(mediaSource).pipe(map(() => false)),
|
|
263
|
+
onSourceClose$(mediaSource).pipe(map(() => false))
|
|
264
|
+
).pipe(
|
|
265
|
+
startWith(mediaSource.readyState === "open"),
|
|
266
|
+
distinctUntilChanged()
|
|
267
|
+
);
|
|
268
|
+
}
|
|
@@ -91,11 +91,8 @@ export interface IAdaptationStreamPlaybackObservation extends
|
|
|
91
91
|
isPaused: boolean;
|
|
92
92
|
/** Last "playback rate" asked by the user. */
|
|
93
93
|
speed : number;
|
|
94
|
-
/**
|
|
95
|
-
|
|
96
|
-
* Difference between the live edge and the current position, in seconds.
|
|
97
|
-
*/
|
|
98
|
-
liveGap : number | undefined;
|
|
94
|
+
/** Theoretical maximum position on the content that can currently be played. */
|
|
95
|
+
maximumPosition : number;
|
|
99
96
|
}
|
|
100
97
|
|
|
101
98
|
/** Arguments given when creating a new `AdaptationStream`. */
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
import { MediaError } from "../../../errors";
|
|
27
27
|
import Manifest, {
|
|
28
28
|
Adaptation,
|
|
29
|
+
Period,
|
|
29
30
|
Representation,
|
|
30
31
|
} from "../../../manifest";
|
|
31
32
|
import { fromEvent } from "../../../utils/event_emitter";
|
|
@@ -53,13 +54,15 @@ import ABRManager, {
|
|
|
53
54
|
* @returns {Object}
|
|
54
55
|
*/
|
|
55
56
|
export default function createRepresentationEstimator(
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
content : { manifest : Manifest;
|
|
58
|
+
period : Period;
|
|
59
|
+
adaptation : Adaptation; },
|
|
58
60
|
abrManager : ABRManager,
|
|
59
61
|
observation$ : Observable<IABRManagerPlaybackObservation>
|
|
60
62
|
) : { estimator$ : Observable<IABREstimate>;
|
|
61
63
|
abrFeedbacks$ : Subject<IABRStreamEvents>; }
|
|
62
64
|
{
|
|
65
|
+
const { manifest, adaptation } = content;
|
|
63
66
|
const abrFeedbacks$ = new Subject<IABRStreamEvents>();
|
|
64
67
|
const estimator$ = observableMerge(
|
|
65
68
|
// subscribe "first" (hack as it is a merge here) to event
|
|
@@ -90,7 +93,7 @@ export default function createRepresentationEstimator(
|
|
|
90
93
|
return true;
|
|
91
94
|
}),
|
|
92
95
|
switchMap((playableRepresentations) =>
|
|
93
|
-
abrManager.get$(
|
|
96
|
+
abrManager.get$(content,
|
|
94
97
|
playableRepresentations,
|
|
95
98
|
observation$,
|
|
96
99
|
abrFeedbacks$)));
|
|
@@ -64,7 +64,6 @@ import {
|
|
|
64
64
|
IMultiplePeriodStreamsEvent,
|
|
65
65
|
IPeriodStreamEvent,
|
|
66
66
|
IStreamOrchestratorEvent,
|
|
67
|
-
IStreamWarningEvent,
|
|
68
67
|
} from "../types";
|
|
69
68
|
import ActivePeriodEmitter from "./active_period_emitter";
|
|
70
69
|
import areStreamsComplete from "./are_streams_complete";
|
|
@@ -151,31 +150,8 @@ export default function StreamOrchestrator(
|
|
|
151
150
|
});
|
|
152
151
|
});
|
|
153
152
|
|
|
154
|
-
// trigger warnings when the wanted time is before or after the manifest's
|
|
155
|
-
// segments
|
|
156
|
-
const outOfManifest$ = playbackObserver.observe(true).pipe(
|
|
157
|
-
filterMap<IStreamOrchestratorPlaybackObservation, IStreamWarningEvent, null>((
|
|
158
|
-
{ position, wantedTimeOffset }
|
|
159
|
-
) => {
|
|
160
|
-
const offsetedPosition = wantedTimeOffset + position;
|
|
161
|
-
if (offsetedPosition < manifest.getMinimumPosition()) {
|
|
162
|
-
const warning = new MediaError("MEDIA_TIME_BEFORE_MANIFEST",
|
|
163
|
-
"The current position is behind the " +
|
|
164
|
-
"earliest time announced in the Manifest.");
|
|
165
|
-
return EVENTS.warning(warning);
|
|
166
|
-
} else if (offsetedPosition > manifest.getMaximumPosition()) {
|
|
167
|
-
const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST",
|
|
168
|
-
"The current position is after the latest " +
|
|
169
|
-
"time announced in the Manifest.");
|
|
170
|
-
return EVENTS.warning(warning);
|
|
171
|
-
}
|
|
172
|
-
return null;
|
|
173
|
-
}, null));
|
|
174
|
-
|
|
175
|
-
const bufferTypes = segmentBuffersStore.getBufferTypes();
|
|
176
|
-
|
|
177
153
|
// Every PeriodStreams for every possible types
|
|
178
|
-
const streamsArray =
|
|
154
|
+
const streamsArray = segmentBuffersStore.getBufferTypes().map((bufferType) => {
|
|
179
155
|
return manageEveryStreams(bufferType, initialPeriod)
|
|
180
156
|
.pipe(deferSubscriptions(), share());
|
|
181
157
|
});
|
|
@@ -203,10 +179,7 @@ export default function StreamOrchestrator(
|
|
|
203
179
|
map((emitEndOfStream) =>
|
|
204
180
|
emitEndOfStream ? EVENTS.endOfStream() : EVENTS.resumeStream()));
|
|
205
181
|
|
|
206
|
-
return observableMerge(...streamsArray,
|
|
207
|
-
activePeriodChanged$,
|
|
208
|
-
endOfStream$,
|
|
209
|
-
outOfManifest$);
|
|
182
|
+
return observableMerge(...streamsArray, activePeriodChanged$, endOfStream$);
|
|
210
183
|
|
|
211
184
|
/**
|
|
212
185
|
* Manage creation and removal of Streams for every Periods for a given type.
|
|
@@ -30,7 +30,10 @@ import {
|
|
|
30
30
|
switchMap,
|
|
31
31
|
} from "rxjs";
|
|
32
32
|
import config from "../../../config";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
formatError,
|
|
35
|
+
MediaError,
|
|
36
|
+
} from "../../../errors";
|
|
34
37
|
import log from "../../../log";
|
|
35
38
|
import Manifest, {
|
|
36
39
|
Adaptation,
|
|
@@ -81,11 +84,8 @@ export interface IPeriodStreamPlaybackObservation {
|
|
|
81
84
|
* which we actually want to download segments for.
|
|
82
85
|
*/
|
|
83
86
|
wantedTimeOffset : number;
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
* Difference between the live edge and the current position, in seconds.
|
|
87
|
-
*/
|
|
88
|
-
liveGap : number | undefined;
|
|
87
|
+
/** Theoretical maximum position on the content that can currently be played. */
|
|
88
|
+
maximumPosition : number;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
/** Arguments required by the `PeriodStream`. */
|
|
@@ -332,9 +332,12 @@ function createOrReuseSegmentBuffer(
|
|
|
332
332
|
* @returns {string}
|
|
333
333
|
*/
|
|
334
334
|
function getFirstDeclaredMimeType(adaptation : Adaptation) : string {
|
|
335
|
-
const
|
|
336
|
-
if (representations
|
|
337
|
-
|
|
335
|
+
const representations = adaptation.getPlayableRepresentations();
|
|
336
|
+
if (representations.length === 0) {
|
|
337
|
+
const noRepErr = new MediaError("NO_PLAYABLE_REPRESENTATION",
|
|
338
|
+
"No Representation in the chosen " +
|
|
339
|
+
adaptation.type + " Adaptation can be played");
|
|
340
|
+
throw noRepErr;
|
|
338
341
|
}
|
|
339
342
|
return representations[0].getMimeTypeString();
|
|
340
343
|
}
|