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
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.27.
|
|
4
|
+
sonar.projectVersion=3.27.1-dev.2022041500
|
|
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
|
|
@@ -21,7 +21,11 @@ import {
|
|
|
21
21
|
of as observableOf,
|
|
22
22
|
} from "rxjs";
|
|
23
23
|
import log from "../../log";
|
|
24
|
-
import {
|
|
24
|
+
import Manifest, {
|
|
25
|
+
Adaptation,
|
|
26
|
+
Period,
|
|
27
|
+
Representation,
|
|
28
|
+
} from "../../manifest";
|
|
25
29
|
import objectAssign from "../../utils/object_assign";
|
|
26
30
|
import { ISharedReference } from "../../utils/reference";
|
|
27
31
|
import takeFirstSet from "../../utils/take_first_set";
|
|
@@ -98,18 +102,21 @@ export default class ABRManager {
|
|
|
98
102
|
* Take type and an array of the available representations, spit out an
|
|
99
103
|
* observable emitting the best representation (given the network/buffer
|
|
100
104
|
* state).
|
|
101
|
-
* @param {
|
|
105
|
+
* @param {Object} context
|
|
102
106
|
* @param {Array.<Representation>} representations
|
|
103
107
|
* @param {Observable<Object>} observation$
|
|
104
108
|
* @param {Observable<Object>} streamEvents$
|
|
105
109
|
* @returns {Observable}
|
|
106
110
|
*/
|
|
107
111
|
public get$(
|
|
108
|
-
|
|
112
|
+
context : { manifest : Manifest;
|
|
113
|
+
period : Period;
|
|
114
|
+
adaptation : Adaptation; },
|
|
109
115
|
representations : Representation[],
|
|
110
116
|
observation$ : Observable<IABRManagerPlaybackObservation>,
|
|
111
117
|
streamEvents$ : Observable<IABRStreamEvents>
|
|
112
118
|
) : Observable<IABREstimate> {
|
|
119
|
+
const { type } = context.adaptation;
|
|
113
120
|
const bandwidthEstimator = this._getBandwidthEstimator(type);
|
|
114
121
|
const manualBitrate$ =
|
|
115
122
|
takeFirstSet<Observable<number>>(this._manualBitrates[type]?.asObservable(),
|
|
@@ -125,6 +132,7 @@ export default class ABRManager {
|
|
|
125
132
|
this._throttlers.throttleBitrate[type],
|
|
126
133
|
this._throttlers.throttle[type]);
|
|
127
134
|
return RepresentationEstimator({ bandwidthEstimator,
|
|
135
|
+
context,
|
|
128
136
|
streamEvents$,
|
|
129
137
|
observation$,
|
|
130
138
|
filters$,
|
|
@@ -29,9 +29,10 @@ import {
|
|
|
29
29
|
withLatestFrom,
|
|
30
30
|
} from "rxjs";
|
|
31
31
|
import log from "../../log";
|
|
32
|
-
import {
|
|
32
|
+
import Manifest, {
|
|
33
33
|
Adaptation,
|
|
34
34
|
ISegment,
|
|
35
|
+
Period,
|
|
35
36
|
Representation,
|
|
36
37
|
} from "../../manifest";
|
|
37
38
|
import { getLeftSizeOfRange } from "../../utils/ranges";
|
|
@@ -141,12 +142,8 @@ export interface IRepresentationEstimatorPlaybackObservation {
|
|
|
141
142
|
speed : number;
|
|
142
143
|
/** `duration` property of the HTMLMediaElement on which the content plays. */
|
|
143
144
|
duration : number;
|
|
144
|
-
/**
|
|
145
|
-
|
|
146
|
-
* and the current position.
|
|
147
|
-
* `undefined` for non-live contents.
|
|
148
|
-
*/
|
|
149
|
-
liveGap : number | undefined;
|
|
145
|
+
/** Theoretical maximum position on the content that can currently be played. */
|
|
146
|
+
maximumPosition : number;
|
|
150
147
|
}
|
|
151
148
|
|
|
152
149
|
/** Content of the `IABRMetricsEvent` event's payload. */
|
|
@@ -357,6 +354,15 @@ export interface IRepresentationEstimatorArguments {
|
|
|
357
354
|
maxAutoBitrate$ : Observable<number>;
|
|
358
355
|
/** The list of Representations the `RepresentationEstimator` can choose from. */
|
|
359
356
|
representations : Representation[];
|
|
357
|
+
/** Context for the list of Representations to choose. */
|
|
358
|
+
context : {
|
|
359
|
+
/** In which Manifest the Representations are. */
|
|
360
|
+
manifest : Manifest;
|
|
361
|
+
/** In which Period the Representations are. */
|
|
362
|
+
period : Period;
|
|
363
|
+
/** In which Adaptation the Representations are. */
|
|
364
|
+
adaptation : Adaptation;
|
|
365
|
+
};
|
|
360
366
|
}
|
|
361
367
|
|
|
362
368
|
/**
|
|
@@ -396,6 +402,7 @@ function getFilteredRepresentations(
|
|
|
396
402
|
*/
|
|
397
403
|
export default function RepresentationEstimator({
|
|
398
404
|
bandwidthEstimator,
|
|
405
|
+
context,
|
|
399
406
|
observation$,
|
|
400
407
|
filters$,
|
|
401
408
|
initialBitrate,
|
|
@@ -534,7 +541,7 @@ export default function RepresentationEstimator({
|
|
|
534
541
|
bufferBasedBitrate ],
|
|
535
542
|
currentRepresentation ]
|
|
536
543
|
) : IABREstimate => {
|
|
537
|
-
const { bufferGap,
|
|
544
|
+
const { bufferGap, position, maximumPosition } = observation;
|
|
538
545
|
|
|
539
546
|
const filteredReps = getFilteredRepresentations(representations,
|
|
540
547
|
filters);
|
|
@@ -615,8 +622,8 @@ export default function RepresentationEstimator({
|
|
|
615
622
|
let chosenRepFromGuessMode : Representation | null = null;
|
|
616
623
|
if (lowLatencyMode &&
|
|
617
624
|
currentRepresentation !== null &&
|
|
618
|
-
|
|
619
|
-
|
|
625
|
+
context.manifest.isDynamic &&
|
|
626
|
+
maximumPosition - position < 40)
|
|
620
627
|
{
|
|
621
628
|
chosenRepFromGuessMode = guessBasedChooser.getGuess(representations,
|
|
622
629
|
observation,
|
|
@@ -428,7 +428,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
428
428
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
429
429
|
videoElement.preload = "auto";
|
|
430
430
|
|
|
431
|
-
this.version = /* PLAYER_VERSION */"3.27.
|
|
431
|
+
this.version = /* PLAYER_VERSION */"3.27.1-dev.2022041500";
|
|
432
432
|
this.log = log;
|
|
433
433
|
this.state = "STOPPED";
|
|
434
434
|
this.videoElement = videoElement;
|
|
@@ -1604,14 +1604,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
1604
1604
|
if (positionWanted === undefined) {
|
|
1605
1605
|
throw new Error("invalid time given");
|
|
1606
1606
|
}
|
|
1607
|
-
|
|
1608
|
-
if (manifest !== null && !manifest.isLive) {
|
|
1609
|
-
const maximumTime = manifest.getMaximumPosition();
|
|
1610
|
-
seekAt = maximumTime !== undefined ? Math.min(positionWanted,
|
|
1611
|
-
maximumTime - 0.001) :
|
|
1612
|
-
positionWanted;
|
|
1613
|
-
}
|
|
1614
|
-
this.videoElement.currentTime = seekAt;
|
|
1607
|
+
this.videoElement.currentTime = positionWanted;
|
|
1615
1608
|
return positionWanted;
|
|
1616
1609
|
}
|
|
1617
1610
|
|
|
@@ -2235,7 +2228,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2235
2228
|
|
|
2236
2229
|
const { manifest } = this._priv_contentInfos;
|
|
2237
2230
|
if (manifest !== null) {
|
|
2238
|
-
return manifest.
|
|
2231
|
+
return manifest.getMinimumSafePosition();
|
|
2239
2232
|
}
|
|
2240
2233
|
return null;
|
|
2241
2234
|
}
|
|
@@ -2259,7 +2252,10 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2259
2252
|
}
|
|
2260
2253
|
|
|
2261
2254
|
if (manifest !== null) {
|
|
2262
|
-
|
|
2255
|
+
if (!manifest.isDynamic && this.videoElement !== null) {
|
|
2256
|
+
return this.videoElement.duration;
|
|
2257
|
+
}
|
|
2258
|
+
return manifest.getMaximumSafePosition();
|
|
2263
2259
|
}
|
|
2264
2260
|
return null;
|
|
2265
2261
|
}
|
|
@@ -2864,7 +2860,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2864
2860
|
|
|
2865
2861
|
this._priv_lastContentPlaybackInfos.lastPlaybackPosition = observation.position;
|
|
2866
2862
|
|
|
2867
|
-
const maximumPosition = manifest !== null ? manifest.
|
|
2863
|
+
const maximumPosition = manifest !== null ? manifest.getMaximumSafePosition() :
|
|
2868
2864
|
undefined;
|
|
2869
2865
|
const positionData : IPositionUpdateItem = {
|
|
2870
2866
|
position: observation.position,
|
|
@@ -2878,13 +2874,15 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2878
2874
|
};
|
|
2879
2875
|
|
|
2880
2876
|
if (manifest !== null &&
|
|
2881
|
-
maximumPosition !== undefined &&
|
|
2882
2877
|
manifest.isLive &&
|
|
2883
2878
|
observation.position > 0
|
|
2884
2879
|
) {
|
|
2885
2880
|
const ast = manifest.availabilityStartTime ?? 0;
|
|
2886
2881
|
positionData.wallClockTime = observation.position + ast;
|
|
2887
|
-
|
|
2882
|
+
const livePosition = manifest.getLivePosition();
|
|
2883
|
+
if (livePosition !== undefined) {
|
|
2884
|
+
positionData.liveGap = livePosition - observation.position;
|
|
2885
|
+
}
|
|
2888
2886
|
} else if (isDirectFile && this.videoElement !== null) {
|
|
2889
2887
|
const startDate = getStartDate(this.videoElement);
|
|
2890
2888
|
if (startDate !== undefined) {
|
|
@@ -2942,7 +2940,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2942
2940
|
return activeRepresentations[currentPeriod.id];
|
|
2943
2941
|
}
|
|
2944
2942
|
}
|
|
2945
|
-
Player.version = /* PLAYER_VERSION */"3.27.
|
|
2943
|
+
Player.version = /* PLAYER_VERSION */"3.27.1-dev.2022041500";
|
|
2946
2944
|
|
|
2947
2945
|
/** Payload emitted with a `positionUpdate` event. */
|
|
2948
2946
|
export interface IPositionUpdateItem {
|
|
@@ -0,0 +1,312 @@
|
|
|
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
|
+
distinctUntilChanged,
|
|
19
|
+
ignoreElements,
|
|
20
|
+
map,
|
|
21
|
+
merge as observableMerge,
|
|
22
|
+
Observable,
|
|
23
|
+
skipWhile,
|
|
24
|
+
startWith,
|
|
25
|
+
tap,
|
|
26
|
+
} from "rxjs";
|
|
27
|
+
import { MediaError } from "../../errors";
|
|
28
|
+
import Manifest, {
|
|
29
|
+
Adaptation,
|
|
30
|
+
IRepresentationIndex,
|
|
31
|
+
} from "../../manifest";
|
|
32
|
+
import { fromEvent } from "../../utils/event_emitter";
|
|
33
|
+
import filterMap from "../../utils/filter_map";
|
|
34
|
+
import createSharedReference from "../../utils/reference";
|
|
35
|
+
import { IReadOnlyPlaybackObserver } from "../api";
|
|
36
|
+
import { IStreamOrchestratorEvent } from "../stream";
|
|
37
|
+
import EVENTS from "./events_generators";
|
|
38
|
+
import { IWarningEvent } from "./types";
|
|
39
|
+
|
|
40
|
+
// NOTE As of now (RxJS 7.4.0), RxJS defines `ignoreElements` default
|
|
41
|
+
// first type parameter as `any` instead of the perfectly fine `unknown`,
|
|
42
|
+
// leading to linter issues, as it forbids the usage of `any`.
|
|
43
|
+
// This is why we're disabling the eslint rule.
|
|
44
|
+
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Observes the position and Adaptations being played and deduce various events
|
|
48
|
+
* related to the available time boundaries:
|
|
49
|
+
* - Emit when the theoretical duration of the content becomes known or when it
|
|
50
|
+
* changes.
|
|
51
|
+
* - Emit warnings when the duration goes out of what is currently
|
|
52
|
+
* theoretically playable.
|
|
53
|
+
*
|
|
54
|
+
* @param {Object} manifest
|
|
55
|
+
* @param {Observable} streams
|
|
56
|
+
* @param {Object} playbackObserver
|
|
57
|
+
* @returns {Observable}
|
|
58
|
+
*/
|
|
59
|
+
export default function ContentTimeBoundariesObserver(
|
|
60
|
+
manifest : Manifest,
|
|
61
|
+
streams : Observable<IStreamOrchestratorEvent>,
|
|
62
|
+
playbackObserver : IReadOnlyPlaybackObserver<IContentTimeObserverPlaybackObservation>
|
|
63
|
+
|
|
64
|
+
) : Observable<IContentDurationUpdateEvent | IWarningEvent> {
|
|
65
|
+
/**
|
|
66
|
+
* Allows to calculate the minimum and maximum playable position on the
|
|
67
|
+
* whole content.
|
|
68
|
+
*/
|
|
69
|
+
const maximumPositionCalculator = new MaximumPositionCalculator(manifest);
|
|
70
|
+
|
|
71
|
+
// trigger warnings when the wanted time is before or after the manifest's
|
|
72
|
+
// segments
|
|
73
|
+
const outOfManifest$ = playbackObserver.observe(true).pipe(
|
|
74
|
+
filterMap<IContentTimeObserverPlaybackObservation, IWarningEvent, null>((
|
|
75
|
+
{ position, wantedTimeOffset }
|
|
76
|
+
) => {
|
|
77
|
+
const offsetedPosition = wantedTimeOffset + position;
|
|
78
|
+
if (
|
|
79
|
+
offsetedPosition < manifest.getMinimumSafePosition()
|
|
80
|
+
) {
|
|
81
|
+
const warning = new MediaError("MEDIA_TIME_BEFORE_MANIFEST",
|
|
82
|
+
"The current position is behind the " +
|
|
83
|
+
"earliest time announced in the Manifest.");
|
|
84
|
+
return EVENTS.warning(warning);
|
|
85
|
+
} else if (
|
|
86
|
+
offsetedPosition > maximumPositionCalculator.getCurrentMaximumPosition()
|
|
87
|
+
) {
|
|
88
|
+
const warning = new MediaError("MEDIA_TIME_AFTER_MANIFEST",
|
|
89
|
+
"The current position is after the latest " +
|
|
90
|
+
"time announced in the Manifest.");
|
|
91
|
+
return EVENTS.warning(warning);
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}, null));
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Contains the content duration according to the last audio and video
|
|
98
|
+
* Adaptation chosen for the last Period.
|
|
99
|
+
* `undefined` if unknown yet.
|
|
100
|
+
*/
|
|
101
|
+
const contentDuration = createSharedReference<number | undefined>(undefined);
|
|
102
|
+
|
|
103
|
+
const updateDurationOnManifestUpdate$ = fromEvent(manifest, "manifestUpdate").pipe(
|
|
104
|
+
startWith(null),
|
|
105
|
+
tap(() => {
|
|
106
|
+
if (!manifest.isDynamic) {
|
|
107
|
+
const maxPos = maximumPositionCalculator.getCurrentMaximumPosition();
|
|
108
|
+
contentDuration.setValue(maxPos);
|
|
109
|
+
} else {
|
|
110
|
+
// TODO handle finished dynamic contents?
|
|
111
|
+
contentDuration.setValue(undefined);
|
|
112
|
+
}
|
|
113
|
+
}),
|
|
114
|
+
ignoreElements()
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
const updateDurationAndTimeBoundsOnTrackChange$ = streams.pipe(
|
|
118
|
+
tap((message) => { // Update Manifest's bounds and duration if necessary
|
|
119
|
+
if (message.type === "adaptationChange") {
|
|
120
|
+
const lastPeriod = manifest.periods[manifest.periods.length - 1];
|
|
121
|
+
if (message.value.period.id === lastPeriod?.id) {
|
|
122
|
+
if (message.value.type === "audio") {
|
|
123
|
+
maximumPositionCalculator
|
|
124
|
+
.updateLastAudioAdaptation(message.value.adaptation);
|
|
125
|
+
if (!manifest.isDynamic) {
|
|
126
|
+
contentDuration.setValue(
|
|
127
|
+
maximumPositionCalculator.getCurrentMaximumPosition()
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
} else if (message.value.type === "video") {
|
|
131
|
+
maximumPositionCalculator
|
|
132
|
+
.updateLastVideoAdaptation(message.value.adaptation);
|
|
133
|
+
if (!manifest.isDynamic) {
|
|
134
|
+
contentDuration.setValue(
|
|
135
|
+
maximumPositionCalculator.getCurrentMaximumPosition()
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}),
|
|
142
|
+
ignoreElements()
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
return observableMerge(
|
|
146
|
+
updateDurationOnManifestUpdate$,
|
|
147
|
+
updateDurationAndTimeBoundsOnTrackChange$,
|
|
148
|
+
outOfManifest$,
|
|
149
|
+
contentDuration.asObservable().pipe(
|
|
150
|
+
skipWhile((val) => val === undefined),
|
|
151
|
+
distinctUntilChanged(),
|
|
152
|
+
map(value => ({ type: "contentDurationUpdate" as const, value }))
|
|
153
|
+
));
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Calculate the last position from the last chosen audio and video Adaptations
|
|
158
|
+
* for the last Period (or a default one, if no Adaptations has been chosen).
|
|
159
|
+
* @class MaximumPositionCalculator
|
|
160
|
+
*/
|
|
161
|
+
class MaximumPositionCalculator {
|
|
162
|
+
private _manifest : Manifest;
|
|
163
|
+
|
|
164
|
+
// TODO replicate for the minimum position ?
|
|
165
|
+
private _lastAudioAdaptation : Adaptation | undefined | null;
|
|
166
|
+
private _lastVideoAdaptation : Adaptation | undefined | null;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @param {Object} manifest
|
|
170
|
+
*/
|
|
171
|
+
constructor(manifest : Manifest) {
|
|
172
|
+
this._manifest = manifest;
|
|
173
|
+
this._lastAudioAdaptation = undefined;
|
|
174
|
+
this._lastVideoAdaptation = undefined;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Update the last known audio Adaptation for the last Period.
|
|
179
|
+
* If no Adaptation has been set, it should be set to `null`.
|
|
180
|
+
*
|
|
181
|
+
* Allows to calculate the maximum position more precizely in
|
|
182
|
+
* `getCurrentMaximumPosition`.
|
|
183
|
+
* @param {Object|null} adaptation
|
|
184
|
+
*/
|
|
185
|
+
public updateLastAudioAdaptation(adaptation : Adaptation | null) : void {
|
|
186
|
+
this._lastAudioAdaptation = adaptation;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Update the last known video Adaptation for the last Period.
|
|
191
|
+
* If no Adaptation has been set, it should be set to `null`.
|
|
192
|
+
*
|
|
193
|
+
* Allows to calculate the maximum position more precizely in
|
|
194
|
+
* `getCurrentMaximumPosition`.
|
|
195
|
+
* @param {Object|null} adaptation
|
|
196
|
+
*/
|
|
197
|
+
public updateLastVideoAdaptation(adaptation : Adaptation | null) : void {
|
|
198
|
+
this._lastVideoAdaptation = adaptation;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Returns an estimate of the maximum position reachable under the current
|
|
203
|
+
* circumstances.
|
|
204
|
+
* @returns {number}
|
|
205
|
+
*/
|
|
206
|
+
public getCurrentMaximumPosition() : number {
|
|
207
|
+
if (this._manifest.isDynamic) {
|
|
208
|
+
return this._manifest.getLivePosition() ??
|
|
209
|
+
this._manifest.getMaximumSafePosition();
|
|
210
|
+
}
|
|
211
|
+
if (this._lastVideoAdaptation === undefined ||
|
|
212
|
+
this._lastAudioAdaptation === undefined)
|
|
213
|
+
{
|
|
214
|
+
return this._manifest.getMaximumSafePosition();
|
|
215
|
+
} else if (this._lastAudioAdaptation === null) {
|
|
216
|
+
if (this._lastVideoAdaptation === null) {
|
|
217
|
+
return this._manifest.getMaximumSafePosition();
|
|
218
|
+
} else {
|
|
219
|
+
const lastVideoPosition =
|
|
220
|
+
getLastPositionFromAdaptation(this._lastVideoAdaptation);
|
|
221
|
+
if (typeof lastVideoPosition !== "number") {
|
|
222
|
+
return this._manifest.getMaximumSafePosition();
|
|
223
|
+
}
|
|
224
|
+
return lastVideoPosition;
|
|
225
|
+
}
|
|
226
|
+
} else if (this._lastVideoAdaptation === null) {
|
|
227
|
+
const lastAudioPosition =
|
|
228
|
+
getLastPositionFromAdaptation(this._lastAudioAdaptation);
|
|
229
|
+
if (typeof lastAudioPosition !== "number") {
|
|
230
|
+
return this._manifest.getMaximumSafePosition();
|
|
231
|
+
}
|
|
232
|
+
return lastAudioPosition;
|
|
233
|
+
} else {
|
|
234
|
+
const lastAudioPosition = getLastPositionFromAdaptation(
|
|
235
|
+
this._lastAudioAdaptation
|
|
236
|
+
);
|
|
237
|
+
const lastVideoPosition = getLastPositionFromAdaptation(
|
|
238
|
+
this._lastVideoAdaptation
|
|
239
|
+
);
|
|
240
|
+
if (typeof lastAudioPosition !== "number" ||
|
|
241
|
+
typeof lastVideoPosition !== "number")
|
|
242
|
+
{
|
|
243
|
+
return this._manifest.getMaximumSafePosition();
|
|
244
|
+
} else {
|
|
245
|
+
return Math.min(lastAudioPosition, lastVideoPosition);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Returns "last time of reference" from the adaptation given.
|
|
253
|
+
* `undefined` if a time could not be found.
|
|
254
|
+
* Null if the Adaptation has no segments (it could be that it didn't started or
|
|
255
|
+
* that it already finished for example).
|
|
256
|
+
*
|
|
257
|
+
* We consider the earliest last time from every representations in the given
|
|
258
|
+
* adaptation.
|
|
259
|
+
* @param {Object} adaptation
|
|
260
|
+
* @returns {Number|undefined|null}
|
|
261
|
+
*/
|
|
262
|
+
function getLastPositionFromAdaptation(
|
|
263
|
+
adaptation: Adaptation
|
|
264
|
+
) : number | undefined | null {
|
|
265
|
+
const { representations } = adaptation;
|
|
266
|
+
let min : null | number = null;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Some Manifest parsers use the exact same `IRepresentationIndex` reference
|
|
270
|
+
* for each Representation of a given Adaptation, because in the actual source
|
|
271
|
+
* Manifest file, indexing data is often defined at Adaptation-level.
|
|
272
|
+
* This variable allows to optimize the logic here when this is the case.
|
|
273
|
+
*/
|
|
274
|
+
let lastIndex : IRepresentationIndex | undefined;
|
|
275
|
+
for (let i = 0; i < representations.length; i++) {
|
|
276
|
+
if (representations[i].index !== lastIndex) {
|
|
277
|
+
lastIndex = representations[i].index;
|
|
278
|
+
const lastPosition = representations[i].index.getLastPosition();
|
|
279
|
+
if (lastPosition === undefined) { // we cannot tell
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
if (lastPosition !== null) {
|
|
283
|
+
min = min == null ? lastPosition :
|
|
284
|
+
Math.min(min, lastPosition);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (min === null) { // It means that all positions were null === no segments (yet?)
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
return min;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Emitted when the duration of the full content (== the last playable position)
|
|
296
|
+
* has changed.
|
|
297
|
+
*/
|
|
298
|
+
export interface IContentDurationUpdateEvent {
|
|
299
|
+
type: "contentDurationUpdate";
|
|
300
|
+
/** The new theoretical duration, `undefined` if unknown, */
|
|
301
|
+
value : number | undefined;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export interface IContentTimeObserverPlaybackObservation {
|
|
305
|
+
/** The position we are in the video in seconds at the time of the observation. */
|
|
306
|
+
position : number;
|
|
307
|
+
/**
|
|
308
|
+
* Offset, in seconds to add to `position` to obtain the starting position at
|
|
309
|
+
* which we actually want to download segments for.
|
|
310
|
+
*/
|
|
311
|
+
wantedTimeOffset : number;
|
|
312
|
+
}
|
|
@@ -62,26 +62,37 @@ export default function createStreamPlaybackObserver(
|
|
|
62
62
|
) : Observable<IStreamOrchestratorPlaybackObservation> {
|
|
63
63
|
return observableCombineLatest([observation$, speed.asObservable()]).pipe(
|
|
64
64
|
map(([observation, lastSpeed]) => {
|
|
65
|
+
let wantedTimeOffset = 0;
|
|
66
|
+
if (!initialSeekPerformed.getValue()) {
|
|
67
|
+
wantedTimeOffset = startTime - observation.position;
|
|
68
|
+
} else if (!manifest.isDynamic || manifest.isLastPeriodKnown) {
|
|
69
|
+
const lastPeriod = manifest.periods[manifest.periods.length - 1];
|
|
70
|
+
if (lastPeriod !== undefined &&
|
|
71
|
+
lastPeriod.end !== undefined &&
|
|
72
|
+
observation.position > lastPeriod.end)
|
|
73
|
+
{
|
|
74
|
+
wantedTimeOffset = lastPeriod.end -
|
|
75
|
+
observation.position -
|
|
76
|
+
1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
65
80
|
return {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
undefined,
|
|
81
|
+
// TODO more exact according to the current Adaptation chosen?
|
|
82
|
+
maximumPosition: manifest.getMaximumSafePosition(),
|
|
69
83
|
position: observation.position,
|
|
70
84
|
duration: observation.duration,
|
|
71
85
|
isPaused: initialPlayPerformed.getValue() ? observation.paused :
|
|
72
86
|
!autoPlay,
|
|
73
87
|
readyState: observation.readyState,
|
|
74
88
|
speed: lastSpeed,
|
|
75
|
-
|
|
76
89
|
// wantedTimeOffset is an offset to add to the timing's current time to have
|
|
77
90
|
// the "real" wanted position.
|
|
78
91
|
// For now, this is seen when the media element has not yet seeked to its
|
|
79
92
|
// initial position, the currentTime will most probably be 0 where the
|
|
80
93
|
// effective starting position will be _startTime_.
|
|
81
94
|
// Thus we initially set a wantedTimeOffset equal to startTime.
|
|
82
|
-
wantedTimeOffset
|
|
83
|
-
0 :
|
|
84
|
-
startTime - observation.position,
|
|
95
|
+
wantedTimeOffset,
|
|
85
96
|
};
|
|
86
97
|
}));
|
|
87
98
|
});
|