rx-player 3.28.0-dev.2022061700 → 3.28.0-dev.2022062700
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 +5 -3
- package/VERSION +1 -1
- package/appveyor.yml +1 -1
- package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.d.ts +32 -11
- package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +19 -26
- package/dist/_esm5.processed/core/api/playback_observer.d.ts +47 -35
- package/dist/_esm5.processed/core/api/playback_observer.js +120 -117
- package/dist/_esm5.processed/core/api/public_api.d.ts +4 -6
- package/dist/_esm5.processed/core/api/public_api.js +18 -18
- package/dist/_esm5.processed/core/decrypt/content_decryptor.js +60 -31
- package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +1 -1
- package/dist/_esm5.processed/core/init/create_stream_playback_observer.d.ts +1 -1
- package/dist/_esm5.processed/core/init/create_stream_playback_observer.js +23 -6
- package/dist/_esm5.processed/core/init/initial_seek_and_play.js +3 -3
- package/dist/_esm5.processed/core/init/initialize_directfile.js +1 -1
- package/dist/_esm5.processed/core/init/load_on_media_source.js +1 -1
- package/dist/_esm5.processed/core/init/stall_avoider.js +12 -8
- package/dist/_esm5.processed/core/stream/adaptation/create_representation_estimator.js +1 -1
- package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +5 -4
- package/dist/_esm5.processed/core/stream/period/create_empty_adaptation_stream.js +1 -1
- package/dist/_esm5.processed/core/stream/period/period_stream.js +21 -5
- package/dist/_esm5.processed/core/stream/reload_after_switch.js +1 -1
- package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.js +1 -1
- package/dist/_esm5.processed/core/stream/representation/representation_stream.js +1 -1
- package/dist/_esm5.processed/utils/reference.d.ts +29 -19
- package/dist/_esm5.processed/utils/reference.js +21 -5
- package/dist/rx-player.js +366 -257
- package/dist/rx-player.min.js +1 -1
- package/package.json +2 -2
- package/sonar-project.properties +1 -1
- package/src/README.md +84 -68
- package/src/core/adaptive/adaptive_representation_selector.ts +47 -40
- package/src/core/api/playback_observer.ts +185 -173
- package/src/core/api/public_api.ts +23 -21
- package/src/core/decrypt/content_decryptor.ts +57 -23
- package/src/core/init/content_time_boundaries_observer.ts +1 -1
- package/src/core/init/create_stream_playback_observer.ts +69 -47
- package/src/core/init/initial_seek_and_play.ts +3 -3
- package/src/core/init/initialize_directfile.ts +1 -1
- package/src/core/init/load_on_media_source.ts +1 -1
- package/src/core/init/stall_avoider.ts +12 -9
- package/src/core/stream/adaptation/create_representation_estimator.ts +6 -6
- package/src/core/stream/orchestrator/stream_orchestrator.ts +5 -4
- package/src/core/stream/period/create_empty_adaptation_stream.ts +1 -1
- package/src/core/stream/period/period_stream.ts +33 -14
- package/src/core/stream/reload_after_switch.ts +1 -1
- package/src/core/stream/representation/append_segment_to_buffer.ts +1 -1
- package/src/core/stream/representation/representation_stream.ts +1 -1
- package/src/utils/reference.ts +40 -28
|
@@ -491,20 +491,13 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
|
|
|
491
491
|
return;
|
|
492
492
|
}
|
|
493
493
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
evt.value.blacklistedKeyIDs);
|
|
502
|
-
} else {
|
|
503
|
-
// When the MediaKeySession is just a cached/persisted one, we don't
|
|
504
|
-
// have any concept of "implicit key id".
|
|
505
|
-
linkedKeys = { whitelisted: evt.value.whitelistedKeyIds,
|
|
506
|
-
blacklisted: evt.value.blacklistedKeyIDs };
|
|
507
|
-
}
|
|
494
|
+
const linkedKeys = getKeyIdsLinkedToSession(
|
|
495
|
+
initializationData,
|
|
496
|
+
sessionInfo.record,
|
|
497
|
+
options.singleLicensePer,
|
|
498
|
+
sessionInfo.source === MediaKeySessionLoadingType.Created,
|
|
499
|
+
evt.value.whitelistedKeyIds,
|
|
500
|
+
evt.value.blacklistedKeyIDs);
|
|
508
501
|
|
|
509
502
|
sessionInfo.record.associateKeyIds(linkedKeys.whitelisted);
|
|
510
503
|
sessionInfo.record.associateKeyIds(linkedKeys.blacklisted);
|
|
@@ -1053,22 +1046,46 @@ interface IAttachedMediaKeysData {
|
|
|
1053
1046
|
}
|
|
1054
1047
|
|
|
1055
1048
|
/**
|
|
1056
|
-
* Returns
|
|
1057
|
-
* a
|
|
1049
|
+
* Returns set of all usable and unusable keys - explicit or implicit - that are
|
|
1050
|
+
* linked to a `MediaKeySession`.
|
|
1058
1051
|
*
|
|
1059
1052
|
* In the RxPlayer, there is a concept of "explicit" key ids, which are key ids
|
|
1060
1053
|
* found in a license whose status can be known through the `keyStatuses`
|
|
1061
1054
|
* property from a `MediaKeySession`, and of "implicit" key ids, which are key
|
|
1062
1055
|
* ids which were expected to be in a fetched license, but apparently weren't.
|
|
1063
|
-
*
|
|
1064
|
-
* @param {
|
|
1065
|
-
*
|
|
1066
|
-
* @param {
|
|
1067
|
-
*
|
|
1056
|
+
*
|
|
1057
|
+
* @param {Object} initializationData - Initialization data object used to make
|
|
1058
|
+
* the request for the current license.
|
|
1059
|
+
* @param {Object} keySessionRecord - The `KeySessionRecord` associated with the
|
|
1060
|
+
* session that has been loaded. It might give supplementary information on
|
|
1061
|
+
* keys implicitly linked to the license.
|
|
1062
|
+
* @param {string|undefined} singleLicensePer - Setting allowing to indicate the
|
|
1063
|
+
* scope a given license should have.
|
|
1064
|
+
* @param {boolean} isCurrentLicense - If `true` the license has been fetched
|
|
1065
|
+
* especially for the current content.
|
|
1066
|
+
*
|
|
1067
|
+
* Knowing this allows to determine that if decryption keys that should have
|
|
1068
|
+
* been referenced in the fetched license (according to the `singleLicensePer`
|
|
1069
|
+
* setting) are missing, then the keys surely must have been voluntarly
|
|
1070
|
+
* removed from the license.
|
|
1071
|
+
*
|
|
1072
|
+
* If it is however set to `false`, it means that the license is an older
|
|
1073
|
+
* license that might have been linked to another content, thus we cannot make
|
|
1074
|
+
* that assumption.
|
|
1075
|
+
* @param {Array.<Uint8Array>} usableKeyIds - Key ids that are present in the
|
|
1076
|
+
* license and can be used.
|
|
1077
|
+
* @param {Array.<Uint8Array>} unusableKeyIds - Key ids that are present in the
|
|
1078
|
+
* license yet cannot be used.
|
|
1079
|
+
* @returns {Object} - Returns an object with the following properties:
|
|
1080
|
+
* - `whitelisted`: Array of key ids for keys that are known to be usable
|
|
1081
|
+
* - `blacklisted`: Array of key ids for keys that are considered unusable.
|
|
1082
|
+
* The qualities linked to those keys should not be played.
|
|
1068
1083
|
*/
|
|
1069
|
-
function
|
|
1084
|
+
function getKeyIdsLinkedToSession(
|
|
1070
1085
|
initializationData : IProcessedProtectionData,
|
|
1086
|
+
keySessionRecord : KeySessionRecord,
|
|
1071
1087
|
singleLicensePer : undefined | "init-data" | "content" | "periods",
|
|
1088
|
+
isCurrentLicense : boolean,
|
|
1072
1089
|
usableKeyIds : Uint8Array[],
|
|
1073
1090
|
unusableKeyIds : Uint8Array[]
|
|
1074
1091
|
) : { whitelisted : Uint8Array[];
|
|
@@ -1081,6 +1098,19 @@ function getFetchedLicenseKeysInfo(
|
|
|
1081
1098
|
const associatedKeyIds = [...usableKeyIds,
|
|
1082
1099
|
...unusableKeyIds];
|
|
1083
1100
|
|
|
1101
|
+
// Add all key ids associated to the `KeySessionRecord` yet not in
|
|
1102
|
+
// `usableKeyIds` nor in `unusableKeyIds`
|
|
1103
|
+
const allKnownKeyIds = keySessionRecord.getAssociatedKeyIds();
|
|
1104
|
+
for (const kid of allKnownKeyIds) {
|
|
1105
|
+
if (!associatedKeyIds.some(ak => areKeyIdsEqual(ak, kid))) {
|
|
1106
|
+
if (log.hasLevel("DEBUG")) {
|
|
1107
|
+
log.debug("DRM: KeySessionRecord's key missing in the license, blacklisting it",
|
|
1108
|
+
bytesToHex(kid));
|
|
1109
|
+
}
|
|
1110
|
+
associatedKeyIds.push(kid);
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1084
1114
|
if (singleLicensePer !== undefined && singleLicensePer !== "init-data") {
|
|
1085
1115
|
// We want to add the current key ids in the blacklist if it is
|
|
1086
1116
|
// not already there.
|
|
@@ -1102,11 +1132,15 @@ function getFetchedLicenseKeysInfo(
|
|
|
1102
1132
|
return !associatedKeyIds.some(k => areKeyIdsEqual(k, expected));
|
|
1103
1133
|
});
|
|
1104
1134
|
if (missingKeyIds.length > 0) {
|
|
1135
|
+
if (log.hasLevel("DEBUG")) {
|
|
1136
|
+
log.debug("DRM: init data keys missing in the license, blacklisting them",
|
|
1137
|
+
missingKeyIds.map(m => bytesToHex(m)).join(", "));
|
|
1138
|
+
}
|
|
1105
1139
|
associatedKeyIds.push(...missingKeyIds) ;
|
|
1106
1140
|
}
|
|
1107
1141
|
}
|
|
1108
1142
|
|
|
1109
|
-
if (content !== undefined) {
|
|
1143
|
+
if (isCurrentLicense && content !== undefined) {
|
|
1110
1144
|
if (singleLicensePer === "content") {
|
|
1111
1145
|
// Put it in a Set to automatically filter out duplicates (by ref)
|
|
1112
1146
|
const contentKeys = new Set<Uint8Array>();
|
|
@@ -73,7 +73,7 @@ export default function ContentTimeBoundariesObserver(
|
|
|
73
73
|
|
|
74
74
|
// trigger warnings when the wanted time is before or after the manifest's
|
|
75
75
|
// segments
|
|
76
|
-
const outOfManifest$ = playbackObserver.
|
|
76
|
+
const outOfManifest$ = playbackObserver.getReference().asObservable().pipe(
|
|
77
77
|
filterMap<IContentTimeObserverPlaybackObservation, IWarningEvent, null>((
|
|
78
78
|
{ position }
|
|
79
79
|
) => {
|
|
@@ -14,13 +14,11 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
combineLatest as observableCombineLatest,
|
|
19
|
-
map,
|
|
20
|
-
Observable,
|
|
21
|
-
} from "rxjs";
|
|
22
17
|
import Manifest from "../../manifest";
|
|
23
|
-
import {
|
|
18
|
+
import createSharedReference, {
|
|
19
|
+
IReadOnlySharedReference,
|
|
20
|
+
} from "../../utils/reference";
|
|
21
|
+
import { CancellationSignal } from "../../utils/task_canceller";
|
|
24
22
|
import {
|
|
25
23
|
IPlaybackObservation,
|
|
26
24
|
IReadOnlyPlaybackObserver,
|
|
@@ -47,7 +45,7 @@ export interface IStreamPlaybackObserverArguments {
|
|
|
47
45
|
* @param {Object} manifest
|
|
48
46
|
* @param {Object} playbackObserver
|
|
49
47
|
* @param {Object} args
|
|
50
|
-
* @returns {
|
|
48
|
+
* @returns {Object}
|
|
51
49
|
*/
|
|
52
50
|
export default function createStreamPlaybackObserver(
|
|
53
51
|
manifest : Manifest,
|
|
@@ -58,47 +56,71 @@ export default function createStreamPlaybackObserver(
|
|
|
58
56
|
speed,
|
|
59
57
|
startTime } : IStreamPlaybackObserverArguments
|
|
60
58
|
) : IReadOnlyPlaybackObserver<IStreamOrchestratorPlaybackObservation> {
|
|
61
|
-
return playbackObserver.deriveReadOnlyObserver(function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
59
|
+
return playbackObserver.deriveReadOnlyObserver(function transform(
|
|
60
|
+
observationRef : IReadOnlySharedReference<IPlaybackObservation>,
|
|
61
|
+
cancellationSignal : CancellationSignal
|
|
62
|
+
) : IReadOnlySharedReference<IStreamOrchestratorPlaybackObservation> {
|
|
63
|
+
const newRef = createSharedReference(constructStreamPlaybackObservation());
|
|
64
|
+
|
|
65
|
+
speed.onUpdate(emitStreamPlaybackObservation, {
|
|
66
|
+
clearSignal: cancellationSignal,
|
|
67
|
+
emitCurrentValue: false,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
observationRef.onUpdate(emitStreamPlaybackObservation, {
|
|
71
|
+
clearSignal: cancellationSignal,
|
|
72
|
+
emitCurrentValue: false,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
cancellationSignal.register(() => {
|
|
76
|
+
newRef.finish();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return newRef;
|
|
80
|
+
|
|
81
|
+
function constructStreamPlaybackObservation() {
|
|
82
|
+
const observation = observationRef.getValue();
|
|
83
|
+
const lastSpeed = speed.getValue();
|
|
84
|
+
let pendingPosition : number | undefined;
|
|
85
|
+
if (!initialSeekPerformed.getValue()) {
|
|
86
|
+
pendingPosition = startTime;
|
|
87
|
+
} else if (!manifest.isDynamic || manifest.isLastPeriodKnown) {
|
|
88
|
+
// HACK: When the position is actually further than the maximum
|
|
89
|
+
// position for a finished content, we actually want to be loading
|
|
90
|
+
// the last segment before ending.
|
|
91
|
+
// For now, this behavior is implicitely forced by making as if we
|
|
92
|
+
// want to seek one second before the period's end (despite never
|
|
93
|
+
// doing it).
|
|
94
|
+
const lastPeriod = manifest.periods[manifest.periods.length - 1];
|
|
95
|
+
if (lastPeriod !== undefined &&
|
|
96
|
+
lastPeriod.end !== undefined &&
|
|
97
|
+
observation.position > lastPeriod.end)
|
|
98
|
+
{
|
|
99
|
+
pendingPosition = lastPeriod.end - 1;
|
|
83
100
|
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
// TODO more exact according to the current Adaptation chosen?
|
|
105
|
+
maximumPosition: manifest.getMaximumSafePosition(),
|
|
106
|
+
position: {
|
|
107
|
+
last: observation.position,
|
|
108
|
+
pending: pendingPosition,
|
|
109
|
+
},
|
|
110
|
+
duration: observation.duration,
|
|
111
|
+
paused: {
|
|
112
|
+
last: observation.paused,
|
|
113
|
+
pending: initialPlayPerformed.getValue() ? undefined :
|
|
114
|
+
!autoPlay === observation.paused ? undefined :
|
|
115
|
+
!autoPlay,
|
|
116
|
+
},
|
|
117
|
+
readyState: observation.readyState,
|
|
118
|
+
speed: lastSpeed,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
84
121
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
position: {
|
|
89
|
-
last: observation.position,
|
|
90
|
-
pending: pendingPosition,
|
|
91
|
-
},
|
|
92
|
-
duration: observation.duration,
|
|
93
|
-
paused: {
|
|
94
|
-
last: observation.paused,
|
|
95
|
-
pending: initialPlayPerformed.getValue() ? undefined :
|
|
96
|
-
!autoPlay === observation.paused ? undefined :
|
|
97
|
-
!autoPlay,
|
|
98
|
-
},
|
|
99
|
-
readyState: observation.readyState,
|
|
100
|
-
speed: lastSpeed,
|
|
101
|
-
};
|
|
102
|
-
}));
|
|
122
|
+
function emitStreamPlaybackObservation() {
|
|
123
|
+
newRef.setValue(constructStreamPlaybackObservation());
|
|
124
|
+
}
|
|
103
125
|
});
|
|
104
126
|
}
|
|
@@ -83,7 +83,7 @@ export function waitUntilPlayable(
|
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* Try to play content then handle autoplay errors.
|
|
86
|
-
* @param {HTMLMediaElement}
|
|
86
|
+
* @param {HTMLMediaElement} mediaElement
|
|
87
87
|
* @returns {Observable}
|
|
88
88
|
*/
|
|
89
89
|
function autoPlay(
|
|
@@ -161,12 +161,12 @@ export default function initialSeekAndPlay(
|
|
|
161
161
|
const seekAndPlay$ = seek$.pipe(
|
|
162
162
|
mergeMap(() : Observable<IWarningEvent | undefined> => {
|
|
163
163
|
if (!shouldValidateMetadata() || mediaElement.duration > 0) {
|
|
164
|
-
return waitUntilPlayable(playbackObserver.
|
|
164
|
+
return waitUntilPlayable(playbackObserver.getReference().asObservable());
|
|
165
165
|
} else {
|
|
166
166
|
const error = new MediaError("MEDIA_ERR_NOT_LOADED_METADATA",
|
|
167
167
|
"Cannot load automatically: your browser " +
|
|
168
168
|
"falsely announced having loaded the content.");
|
|
169
|
-
return waitUntilPlayable(playbackObserver.
|
|
169
|
+
return waitUntilPlayable(playbackObserver.getReference().asObservable())
|
|
170
170
|
.pipe(startWith(EVENTS.warning(error)));
|
|
171
171
|
}
|
|
172
172
|
}),
|
|
@@ -158,7 +158,7 @@ export default function initializeDirectfileContent({
|
|
|
158
158
|
// through a throwing Observable.
|
|
159
159
|
const mediaError$ = throwOnMediaError(mediaElement);
|
|
160
160
|
|
|
161
|
-
const observation$ = playbackObserver.
|
|
161
|
+
const observation$ = playbackObserver.getReference().asObservable();
|
|
162
162
|
|
|
163
163
|
// Set the speed set by the user on the media element while pausing a
|
|
164
164
|
// little longer while the buffer is empty.
|
|
@@ -125,7 +125,7 @@ export default function createMediaSourceLoader(
|
|
|
125
125
|
startTime: initialTime,
|
|
126
126
|
mustAutoPlay: autoPlay });
|
|
127
127
|
|
|
128
|
-
const observation$ = playbackObserver.
|
|
128
|
+
const observation$ = playbackObserver.getReference().asObservable();
|
|
129
129
|
const streamEvents$ = initialPlayPerformed.asObservable().pipe(
|
|
130
130
|
filter((hasPlayed) => hasPlayed),
|
|
131
131
|
mergeMap(() => streamEventsEmitter(manifest, mediaElement, observation$)));
|
|
@@ -139,7 +139,7 @@ export default function StallAvoider(
|
|
|
139
139
|
* order (first ordered by Period's start, then by bufferType in any order.
|
|
140
140
|
*/
|
|
141
141
|
const discontinuitiesStore$ = discontinuityUpdate$.pipe(
|
|
142
|
-
withLatestFrom(playbackObserver.
|
|
142
|
+
withLatestFrom(playbackObserver.getReference().asObservable()),
|
|
143
143
|
scan(
|
|
144
144
|
(discontinuitiesStore, [evt, observation]) =>
|
|
145
145
|
updateDiscontinuitiesStore(discontinuitiesStore, evt, observation),
|
|
@@ -179,7 +179,7 @@ export default function StallAvoider(
|
|
|
179
179
|
* Period handled by that stream to unlock the situation.
|
|
180
180
|
*/
|
|
181
181
|
const unlock$ = lockedStream$.pipe(
|
|
182
|
-
withLatestFrom(playbackObserver.
|
|
182
|
+
withLatestFrom(playbackObserver.getReference().asObservable()),
|
|
183
183
|
tap(([lockedStreamEvt, observation]) => {
|
|
184
184
|
// TODO(PaulB) also skip when the user's wanted speed is set to `0`, as we
|
|
185
185
|
// might not want to seek in that case?
|
|
@@ -211,7 +211,7 @@ export default function StallAvoider(
|
|
|
211
211
|
ignoreElements()
|
|
212
212
|
);
|
|
213
213
|
|
|
214
|
-
const stall$ = playbackObserver.
|
|
214
|
+
const stall$ = playbackObserver.getReference().asObservable().pipe(
|
|
215
215
|
withLatestFrom(discontinuitiesStore$),
|
|
216
216
|
map(([observation, discontinuitiesStore]) => {
|
|
217
217
|
const { buffered,
|
|
@@ -279,12 +279,15 @@ export default function StallAvoider(
|
|
|
279
279
|
if (ignoredStallTimeStamp === null) {
|
|
280
280
|
ignoredStallTimeStamp = now;
|
|
281
281
|
}
|
|
282
|
-
if (isSeekingApproximate &&
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
282
|
+
if (isSeekingApproximate && observation.position < lastSeekingPosition) {
|
|
283
|
+
log.debug("Init: the device appeared to have seeked back by itself.");
|
|
284
|
+
if (now - ignoredStallTimeStamp < FORCE_DISCONTINUITY_SEEK_DELAY) {
|
|
285
|
+
return { type: "stalled" as const,
|
|
286
|
+
value: stalledReason };
|
|
287
|
+
} else {
|
|
288
|
+
log.warn("Init: ignored stall for too long, checking discontinuity",
|
|
289
|
+
now - ignoredStallTimeStamp);
|
|
290
|
+
}
|
|
288
291
|
}
|
|
289
292
|
lastSeekingPosition = null;
|
|
290
293
|
}
|
|
@@ -70,12 +70,12 @@ export default function getRepresentationEstimate(
|
|
|
70
70
|
updateRepresentationsReference();
|
|
71
71
|
manifest.addEventListener("decipherabilityUpdate", updateRepresentationsReference);
|
|
72
72
|
const unregisterCleanUp = cancellationSignal.register(cleanUp);
|
|
73
|
-
const
|
|
74
|
-
abrCallbacks
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
const { estimates: estimateRef,
|
|
74
|
+
callbacks: abrCallbacks } = representationEstimator(content,
|
|
75
|
+
currentRepresentation,
|
|
76
|
+
representations,
|
|
77
|
+
playbackObserver,
|
|
78
|
+
cancellationSignal);
|
|
79
79
|
return { abrCallbacks, estimateRef };
|
|
80
80
|
|
|
81
81
|
function updateRepresentationsReference() : void {
|
|
@@ -142,7 +142,7 @@ export default function StreamOrchestrator(
|
|
|
142
142
|
Infinity;
|
|
143
143
|
return BufferGarbageCollector({
|
|
144
144
|
segmentBuffer,
|
|
145
|
-
currentTime$: playbackObserver.
|
|
145
|
+
currentTime$: playbackObserver.getReference().asObservable()
|
|
146
146
|
.pipe(map(o => o.position.pending ?? o.position.last)),
|
|
147
147
|
maxBufferBehind$: maxBufferBehind.asObservable().pipe(
|
|
148
148
|
map(val => Math.min(val, defaultMaxBehind))),
|
|
@@ -264,7 +264,8 @@ export default function StreamOrchestrator(
|
|
|
264
264
|
|
|
265
265
|
// Restart the current Stream when the wanted time is in another period
|
|
266
266
|
// than the ones already considered
|
|
267
|
-
const
|
|
267
|
+
const observation$ = playbackObserver.getReference().asObservable();
|
|
268
|
+
const restartStreamsWhenOutOfBounds$ = observation$.pipe(
|
|
268
269
|
filterMap<
|
|
269
270
|
IStreamOrchestratorPlaybackObservation,
|
|
270
271
|
Period,
|
|
@@ -329,7 +330,7 @@ export default function StreamOrchestrator(
|
|
|
329
330
|
// to reduce the risk of race conditions where the next observation
|
|
330
331
|
// was going to be emitted synchronously.
|
|
331
332
|
nextTickObs().pipe(ignoreElements()),
|
|
332
|
-
playbackObserver.
|
|
333
|
+
playbackObserver.getReference().asObservable().pipe(
|
|
333
334
|
take(1),
|
|
334
335
|
mergeMap((observation) => {
|
|
335
336
|
const shouldAutoPlay = !(observation.paused.pending ??
|
|
@@ -399,7 +400,7 @@ export default function StreamOrchestrator(
|
|
|
399
400
|
const destroyNextStreams$ = new Subject<void>();
|
|
400
401
|
|
|
401
402
|
// Emits when the current position goes over the end of the current Stream.
|
|
402
|
-
const endOfCurrentStream$ = playbackObserver.
|
|
403
|
+
const endOfCurrentStream$ = playbackObserver.getReference().asObservable()
|
|
403
404
|
.pipe(filter(({ position }) =>
|
|
404
405
|
basePeriod.end != null &&
|
|
405
406
|
(position.pending ?? position.last) >= basePeriod.end));
|
|
@@ -48,7 +48,7 @@ export default function createEmptyAdaptationStream(
|
|
|
48
48
|
const { period } = content;
|
|
49
49
|
let hasFinishedLoading = false;
|
|
50
50
|
const wantedBufferAhead$ = wantedBufferAhead.asObservable();
|
|
51
|
-
const observation$ = playbackObserver.
|
|
51
|
+
const observation$ = playbackObserver.getReference().asObservable();
|
|
52
52
|
return observableCombineLatest([observation$,
|
|
53
53
|
wantedBufferAhead$]).pipe(
|
|
54
54
|
mergeMap(([observation, wba]) => {
|
|
@@ -42,7 +42,10 @@ import Manifest, {
|
|
|
42
42
|
import { IAudioTrackSwitchingMode } from "../../../public_types";
|
|
43
43
|
import objectAssign from "../../../utils/object_assign";
|
|
44
44
|
import { getLeftSizeOfRange } from "../../../utils/ranges";
|
|
45
|
-
import {
|
|
45
|
+
import createSharedReference, {
|
|
46
|
+
IReadOnlySharedReference,
|
|
47
|
+
} from "../../../utils/reference";
|
|
48
|
+
import { CancellationSignal } from "../../../utils/task_canceller";
|
|
46
49
|
import WeakMapMemory from "../../../utils/weak_map_memory";
|
|
47
50
|
import { IRepresentationEstimator } from "../../adaptive";
|
|
48
51
|
import { IReadOnlyPlaybackObserver } from "../../api";
|
|
@@ -357,18 +360,34 @@ function createAdaptationStreamPlaybackObserver(
|
|
|
357
360
|
initialPlaybackObserver : IReadOnlyPlaybackObserver<IPeriodStreamPlaybackObservation>,
|
|
358
361
|
segmentBuffer : SegmentBuffer
|
|
359
362
|
) : IReadOnlyPlaybackObserver<IAdaptationStreamPlaybackObservation> {
|
|
360
|
-
return initialPlaybackObserver.deriveReadOnlyObserver(
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
)
|
|
363
|
+
return initialPlaybackObserver.deriveReadOnlyObserver(function transform(
|
|
364
|
+
observationRef : IReadOnlySharedReference<IPeriodStreamPlaybackObservation>,
|
|
365
|
+
cancellationSignal : CancellationSignal
|
|
366
|
+
) : IReadOnlySharedReference<IAdaptationStreamPlaybackObservation> {
|
|
367
|
+
const newRef = createSharedReference(constructAdaptationStreamPlaybackObservation());
|
|
368
|
+
|
|
369
|
+
observationRef.onUpdate(emitAdaptationStreamPlaybackObservation, {
|
|
370
|
+
clearSignal: cancellationSignal,
|
|
371
|
+
emitCurrentValue: false,
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
cancellationSignal.register(() => {
|
|
375
|
+
newRef.finish();
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return newRef;
|
|
379
|
+
|
|
380
|
+
function constructAdaptationStreamPlaybackObservation(
|
|
381
|
+
) : IAdaptationStreamPlaybackObservation {
|
|
382
|
+
const baseObservation = observationRef.getValue();
|
|
383
|
+
const buffered = segmentBuffer.getBufferedRanges();
|
|
384
|
+
const bufferGap = getLeftSizeOfRange(buffered, baseObservation.position.last);
|
|
385
|
+
return objectAssign({}, baseObservation, { bufferGap });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function emitAdaptationStreamPlaybackObservation() {
|
|
389
|
+
newRef.setValue(constructAdaptationStreamPlaybackObservation());
|
|
390
|
+
}
|
|
391
|
+
});
|
|
364
392
|
|
|
365
|
-
function mapObservation(
|
|
366
|
-
baseObservation : IPeriodStreamPlaybackObservation
|
|
367
|
-
) : IAdaptationStreamPlaybackObservation {
|
|
368
|
-
const buffered = segmentBuffer.getBufferedRanges();
|
|
369
|
-
return objectAssign({},
|
|
370
|
-
baseObservation,
|
|
371
|
-
{ bufferGap: getLeftSizeOfRange(buffered,
|
|
372
|
-
baseObservation.position.last) });
|
|
373
|
-
}
|
|
374
393
|
}
|
|
@@ -62,7 +62,7 @@ export default function reloadAfterSwitch(
|
|
|
62
62
|
// It can happen when `reloadAfterSwitch` is called as a side-effect of the
|
|
63
63
|
// same event that triggers the playback observation to be emitted.
|
|
64
64
|
return nextTickObs().pipe(
|
|
65
|
-
mergeMap(() => playbackObserver.
|
|
65
|
+
mergeMap(() => playbackObserver.getReference().asObservable()),
|
|
66
66
|
map((observation) => {
|
|
67
67
|
const currentTime = playbackObserver.getCurrentTime();
|
|
68
68
|
const pos = currentTime + deltaPos;
|
|
@@ -61,7 +61,7 @@ export default function appendSegmentToBuffer<T>(
|
|
|
61
61
|
throw new MediaError("BUFFER_APPEND_ERROR", reason);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
return playbackObserver.
|
|
64
|
+
return playbackObserver.getReference().asObservable().pipe(
|
|
65
65
|
take(1),
|
|
66
66
|
mergeMap((observation) => {
|
|
67
67
|
const currentPos = observation.position.pending ??
|
|
@@ -166,7 +166,7 @@ export default function RepresentationStream<TSegmentDataType>({
|
|
|
166
166
|
|
|
167
167
|
/** Observable emitting the stream "status" and filling `lastSegmentQueue`. */
|
|
168
168
|
const status$ = observableCombineLatest([
|
|
169
|
-
playbackObserver.
|
|
169
|
+
playbackObserver.getReference().asObservable(),
|
|
170
170
|
bufferGoal$,
|
|
171
171
|
maxBufferSize$,
|
|
172
172
|
terminate$.pipe(take(1),
|
package/src/utils/reference.ts
CHANGED
|
@@ -57,11 +57,26 @@ import { CancellationSignal } from "./task_canceller";
|
|
|
57
57
|
* those use cases makes the intent of the corresponding code clearer.
|
|
58
58
|
*/
|
|
59
59
|
export interface ISharedReference<T> {
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Get the last set value for this shared reference.
|
|
62
|
+
* @returns {*}
|
|
63
|
+
*/
|
|
61
64
|
getValue() : T;
|
|
62
65
|
|
|
63
|
-
/**
|
|
66
|
+
/**
|
|
67
|
+
* Update the value of this shared reference.
|
|
68
|
+
* @param {*} newVal
|
|
69
|
+
*/
|
|
64
70
|
setValue(newVal : T) : void;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Update the value of this shared reference only if the value changed.
|
|
74
|
+
*
|
|
75
|
+
* Note that this function only performs a strict equality reference through
|
|
76
|
+
* the "===" operator. Different objects that are structurally the same will
|
|
77
|
+
* thus be considered different.
|
|
78
|
+
* @param {*} newVal
|
|
79
|
+
*/
|
|
65
80
|
setValueIfChanged(newVal : T) : void;
|
|
66
81
|
|
|
67
82
|
/**
|
|
@@ -72,25 +87,6 @@ export interface ISharedReference<T> {
|
|
|
72
87
|
* @returns {Observable}
|
|
73
88
|
*/
|
|
74
89
|
asObservable(skipCurrentValue? : boolean) : Observable<T>;
|
|
75
|
-
/**
|
|
76
|
-
* Triggers a callback each time this reference's value is updated.
|
|
77
|
-
*
|
|
78
|
-
* Can be given several options as argument:
|
|
79
|
-
* - clearSignal: When the attach `CancellationSignal` emits, the given
|
|
80
|
-
* callback will not be called anymore on reference updates.
|
|
81
|
-
* - emitCurrentValue: If `true`, the callback will be called directly and
|
|
82
|
-
* synchronously on this call with its current value.
|
|
83
|
-
* @param {Function} cb
|
|
84
|
-
* @param {Object} [options]
|
|
85
|
-
*/
|
|
86
|
-
onUpdate(
|
|
87
|
-
cb : (val : T) => void,
|
|
88
|
-
options? : {
|
|
89
|
-
clearSignal?: CancellationSignal;
|
|
90
|
-
emitCurrentValue?: boolean;
|
|
91
|
-
},
|
|
92
|
-
) : void;
|
|
93
|
-
|
|
94
90
|
/**
|
|
95
91
|
* Allows to register a callback to be called each time the value inside the
|
|
96
92
|
* reference is updated.
|
|
@@ -105,11 +101,10 @@ export interface ISharedReference<T> {
|
|
|
105
101
|
onUpdate(
|
|
106
102
|
cb : (val : T) => void,
|
|
107
103
|
options? : {
|
|
108
|
-
clearSignal?: CancellationSignal;
|
|
109
|
-
emitCurrentValue?: boolean;
|
|
104
|
+
clearSignal?: CancellationSignal | undefined;
|
|
105
|
+
emitCurrentValue?: boolean | undefined;
|
|
110
106
|
},
|
|
111
107
|
) : void;
|
|
112
|
-
|
|
113
108
|
/**
|
|
114
109
|
* Indicate that no new values will be emitted.
|
|
115
110
|
* Allows to automatically close all Observables generated from this shared
|
|
@@ -217,15 +212,15 @@ export function createSharedReference<T>(initialValue : T) : ISharedReference<T>
|
|
|
217
212
|
|
|
218
213
|
/**
|
|
219
214
|
* Update the value of this shared reference.
|
|
220
|
-
* @param {*}
|
|
215
|
+
* @param {*} newVal
|
|
221
216
|
*/
|
|
222
217
|
setValue(newVal : T) : void {
|
|
223
218
|
if (isFinished) {
|
|
224
219
|
if (__ENVIRONMENT__.CURRENT_ENV === __ENVIRONMENT__.DEV as number) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return;
|
|
220
|
+
/* eslint-disable-next-line no-console */
|
|
221
|
+
console.error("Finished shared references cannot be updated");
|
|
228
222
|
}
|
|
223
|
+
return;
|
|
229
224
|
}
|
|
230
225
|
value = newVal;
|
|
231
226
|
|
|
@@ -244,6 +239,10 @@ export function createSharedReference<T>(initialValue : T) : ISharedReference<T>
|
|
|
244
239
|
}
|
|
245
240
|
},
|
|
246
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Update the value of this shared reference only if it changed.
|
|
244
|
+
* @param {*} newVal
|
|
245
|
+
*/
|
|
247
246
|
setValueIfChanged(newVal : T) : void {
|
|
248
247
|
if (newVal !== value) {
|
|
249
248
|
this.setValue(newVal);
|
|
@@ -345,14 +344,27 @@ export function createSharedReference<T>(initialValue : T) : ISharedReference<T>
|
|
|
345
344
|
if (!cbObj.hasBeenCleared) {
|
|
346
345
|
cbObj.complete();
|
|
347
346
|
}
|
|
347
|
+
cbObj.hasBeenCleared = true;
|
|
348
348
|
} catch (_) {
|
|
349
349
|
/* nothing */
|
|
350
350
|
}
|
|
351
351
|
}
|
|
352
|
+
cbs.length = 0;
|
|
352
353
|
},
|
|
353
354
|
};
|
|
354
355
|
}
|
|
355
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Create a new `ISharedReference` based on another one by mapping over its
|
|
359
|
+
* referenced value each time it is updated.
|
|
360
|
+
* @param {Object} originalRef - The Original `ISharedReference` you wish to map
|
|
361
|
+
* over.
|
|
362
|
+
* @param {Function} mappingFn - The mapping function which will receives
|
|
363
|
+
* `originalRef`'s value and outputs this new reference's value.
|
|
364
|
+
* @param {Object | undefined} [cancellationSignal] - Optionally, a
|
|
365
|
+
* `CancellationSignal` which will finish that reference when it emits.
|
|
366
|
+
* @returns {Object} - The new, mapped, reference.
|
|
367
|
+
*/
|
|
356
368
|
export function createMappedReference<T, U>(
|
|
357
369
|
originalRef : IReadOnlySharedReference<T>,
|
|
358
370
|
mappingFn : (x : T) => U,
|