rx-player 3.27.0-dev.2022032800 → 3.27.0
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 +3 -2
- package/VERSION +1 -1
- package/dist/_esm5.processed/compat/get_start_date.d.ts +30 -0
- package/dist/_esm5.processed/compat/get_start_date.js +44 -0
- package/dist/_esm5.processed/compat/index.d.ts +2 -1
- package/dist/_esm5.processed/compat/index.js +2 -1
- package/dist/_esm5.processed/config.d.ts +1 -2
- package/dist/_esm5.processed/core/api/public_api.js +25 -25
- package/dist/_esm5.processed/core/segment_buffers/garbage_collector.js +4 -1
- package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -1
- package/dist/_esm5.processed/core/stream/period/period_stream.js +9 -3
- package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.js +3 -2
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +2 -2
- package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +9 -3
- package/dist/_esm5.processed/core/stream/representation/get_needed_segments.d.ts +11 -1
- package/dist/_esm5.processed/core/stream/representation/get_needed_segments.js +27 -45
- package/dist/_esm5.processed/core/stream/representation/representation_stream.js +6 -4
- package/dist/_esm5.processed/default_config.d.ts +2 -8
- package/dist/_esm5.processed/default_config.js +2 -8
- package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +39 -38
- package/dist/_esm5.processed/utils/reference.js +0 -2
- package/dist/_esm5.processed/utils/task_canceller.d.ts +8 -1
- package/dist/_esm5.processed/utils/task_canceller.js +9 -1
- package/dist/rx-player.js +198 -145
- package/dist/rx-player.min.js +1 -1
- package/package.json +1 -1
- package/sonar-project.properties +1 -1
- package/src/compat/get_start_date.ts +48 -0
- package/src/compat/index.ts +2 -0
- package/src/core/api/public_api.ts +23 -27
- package/src/core/segment_buffers/garbage_collector.ts +4 -0
- package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -1
- package/src/core/stream/period/period_stream.ts +9 -4
- package/src/core/stream/representation/force_garbage_collection.ts +4 -1
- package/src/core/stream/representation/get_buffer_status.ts +21 -13
- package/src/core/stream/representation/get_needed_segments.ts +40 -55
- package/src/core/stream/representation/representation_stream.ts +6 -4
- package/src/default_config.ts +20 -27
- package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +41 -44
- package/src/utils/reference.ts +0 -2
- package/src/utils/task_canceller.ts +16 -1
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.0
|
|
4
|
+
sonar.projectVersion=3.27.0
|
|
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
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Calculating a live-offseted media position necessitate to obtain first an
|
|
19
|
+
* offset, and then adding that offset to the wanted position.
|
|
20
|
+
*
|
|
21
|
+
* That offset is in most case present inside the Manifest file, yet in cases
|
|
22
|
+
* without it or without a Manifest, such as the "directfile" mode, the RxPlayer
|
|
23
|
+
* won't know that offset.
|
|
24
|
+
*
|
|
25
|
+
* Thankfully Safari declares a `getStartDate` method allowing to obtain that
|
|
26
|
+
* offset when available. This logic is mainly useful when playing HLS contents
|
|
27
|
+
* in directfile mode on Safari.
|
|
28
|
+
* @param {HTMLMediaElement} mediaElement
|
|
29
|
+
* @returns {number|undefined}
|
|
30
|
+
*/
|
|
31
|
+
export default function getStartDate(
|
|
32
|
+
mediaElement : HTMLMediaElement
|
|
33
|
+
) : number | undefined {
|
|
34
|
+
const _mediaElement : HTMLMediaElement & {
|
|
35
|
+
getStartDate? : () => number | Date | null | undefined;
|
|
36
|
+
} = mediaElement;
|
|
37
|
+
if (typeof _mediaElement.getStartDate === "function") {
|
|
38
|
+
const startDate = _mediaElement.getStartDate();
|
|
39
|
+
if (typeof startDate === "object" && startDate !== null) {
|
|
40
|
+
const startDateNum = +startDate;
|
|
41
|
+
if (!isNaN(startDateNum)) {
|
|
42
|
+
return startDateNum / 1000;
|
|
43
|
+
}
|
|
44
|
+
} else if (typeof startDate === "number" && !isNaN(startDate)) {
|
|
45
|
+
return startDate;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
package/src/compat/index.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
isFullscreen,
|
|
45
45
|
requestFullscreen,
|
|
46
46
|
} from "./fullscreen";
|
|
47
|
+
import getStartDate from "./get_start_date";
|
|
47
48
|
import hasEMEAPIs from "./has_eme_apis";
|
|
48
49
|
import isCodecSupported from "./is_codec_supported";
|
|
49
50
|
import isNode from "./is_node";
|
|
@@ -79,6 +80,7 @@ export {
|
|
|
79
80
|
exitFullscreen,
|
|
80
81
|
generateKeyRequest,
|
|
81
82
|
getInitData,
|
|
83
|
+
getStartDate,
|
|
82
84
|
hasEMEAPIs,
|
|
83
85
|
ICompatTextTrack,
|
|
84
86
|
ICompatVTTCue,
|
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
import {
|
|
47
47
|
events,
|
|
48
48
|
exitFullscreen,
|
|
49
|
+
getStartDate,
|
|
49
50
|
isFullscreen,
|
|
50
51
|
requestFullscreen,
|
|
51
52
|
} from "../../compat";
|
|
@@ -427,7 +428,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
427
428
|
// See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
|
|
428
429
|
videoElement.preload = "auto";
|
|
429
430
|
|
|
430
|
-
this.version = /* PLAYER_VERSION */"3.27.0
|
|
431
|
+
this.version = /* PLAYER_VERSION */"3.27.0";
|
|
431
432
|
this.log = log;
|
|
432
433
|
this.state = "STOPPED";
|
|
433
434
|
this.videoElement = videoElement;
|
|
@@ -1276,24 +1277,8 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
1276
1277
|
|
|
1277
1278
|
const { isDirectFile, manifest } = this._priv_contentInfos;
|
|
1278
1279
|
if (isDirectFile) {
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
// That offset is in most case present inside the Manifest file, yet in
|
|
1282
|
-
// directfile mode, the RxPlayer won't parse that file, the browser does it.
|
|
1283
|
-
//
|
|
1284
|
-
// Thankfully Safari declares a `getStartDate` method allowing to obtain
|
|
1285
|
-
// that offset when available. This logic is thus mainly useful when
|
|
1286
|
-
// playing HLS contents in directfile mode on Safari.
|
|
1287
|
-
const mediaElement : HTMLMediaElement & {
|
|
1288
|
-
getStartDate? : () => number | null | undefined;
|
|
1289
|
-
} = this.videoElement;
|
|
1290
|
-
if (typeof mediaElement.getStartDate === "function") {
|
|
1291
|
-
const startDate = mediaElement.getStartDate();
|
|
1292
|
-
if (typeof startDate === "number" && !isNaN(startDate)) {
|
|
1293
|
-
return startDate + mediaElement.currentTime;
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
return mediaElement.currentTime;
|
|
1280
|
+
const startDate = getStartDate(this.videoElement);
|
|
1281
|
+
return (startDate ?? 0) + this.videoElement.currentTime;
|
|
1297
1282
|
}
|
|
1298
1283
|
if (manifest !== null) {
|
|
1299
1284
|
const currentTime = this.videoElement.currentTime;
|
|
@@ -1596,12 +1581,19 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
1596
1581
|
} else if (!isNullOrUndefined(timeObj.position)) {
|
|
1597
1582
|
positionWanted = timeObj.position;
|
|
1598
1583
|
} else if (!isNullOrUndefined(timeObj.wallClockTime)) {
|
|
1599
|
-
|
|
1600
|
-
timeObj.wallClockTime
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1584
|
+
if (manifest !== null) {
|
|
1585
|
+
positionWanted = timeObj.wallClockTime - (
|
|
1586
|
+
manifest.availabilityStartTime ?? 0
|
|
1587
|
+
);
|
|
1588
|
+
} else if (isDirectFile && this.videoElement !== null) {
|
|
1589
|
+
const startDate = getStartDate(this.videoElement);
|
|
1590
|
+
if (startDate !== undefined) {
|
|
1591
|
+
positionWanted = timeObj.wallClockTime - startDate;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
if (positionWanted === undefined) {
|
|
1595
|
+
positionWanted = timeObj.wallClockTime;
|
|
1596
|
+
}
|
|
1605
1597
|
} else {
|
|
1606
1598
|
throw new Error("invalid time object. You must set one of the " +
|
|
1607
1599
|
"following properties: \"relative\", \"position\" or " +
|
|
@@ -2893,8 +2885,12 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2893
2885
|
const ast = manifest.availabilityStartTime ?? 0;
|
|
2894
2886
|
positionData.wallClockTime = observation.position + ast;
|
|
2895
2887
|
positionData.liveGap = maximumPosition - observation.position;
|
|
2888
|
+
} else if (isDirectFile && this.videoElement !== null) {
|
|
2889
|
+
const startDate = getStartDate(this.videoElement);
|
|
2890
|
+
if (startDate !== undefined) {
|
|
2891
|
+
positionData.wallClockTime = startDate + observation.position;
|
|
2892
|
+
}
|
|
2896
2893
|
}
|
|
2897
|
-
|
|
2898
2894
|
this.trigger("positionUpdate", positionData);
|
|
2899
2895
|
}
|
|
2900
2896
|
|
|
@@ -2946,7 +2942,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
|
|
|
2946
2942
|
return activeRepresentations[currentPeriod.id];
|
|
2947
2943
|
}
|
|
2948
2944
|
}
|
|
2949
|
-
Player.version = /* PLAYER_VERSION */"3.27.0
|
|
2945
|
+
Player.version = /* PLAYER_VERSION */"3.27.0";
|
|
2950
2946
|
|
|
2951
2947
|
/** Payload emitted with a `positionUpdate` event. */
|
|
2952
2948
|
export interface IPositionUpdateItem {
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
ignoreElements,
|
|
23
23
|
mergeMap,
|
|
24
24
|
Observable,
|
|
25
|
+
of as observableOf,
|
|
25
26
|
} from "rxjs";
|
|
26
27
|
import log from "../../log";
|
|
27
28
|
import { getInnerAndOuterTimeRanges } from "../../utils/ranges";
|
|
@@ -152,6 +153,9 @@ function clearBuffer(
|
|
|
152
153
|
const clean$ = observableFrom(
|
|
153
154
|
cleanedupRanges.map((range) => {
|
|
154
155
|
log.debug("GC: cleaning range from SegmentBuffer", range);
|
|
156
|
+
if (range.start >= range.end) {
|
|
157
|
+
return observableOf(null);
|
|
158
|
+
}
|
|
155
159
|
return segmentBuffer.removeBuffer(range.start, range.end);
|
|
156
160
|
})
|
|
157
161
|
).pipe(concatAll(),
|
|
@@ -348,7 +348,8 @@ export default function StreamOrchestrator(
|
|
|
348
348
|
|
|
349
349
|
return observableConcat(
|
|
350
350
|
...rangesToClean.map(({ start, end }) =>
|
|
351
|
-
|
|
351
|
+
start >= end ? EMPTY :
|
|
352
|
+
segmentBuffer.removeBuffer(start, end).pipe(ignoreElements())),
|
|
352
353
|
playbackObserver.observe(true).pipe(
|
|
353
354
|
take(1),
|
|
354
355
|
mergeMap((observation) => {
|
|
@@ -170,10 +170,15 @@ export default function PeriodStream({
|
|
|
170
170
|
if (SegmentBuffersStore.isNative(bufferType)) {
|
|
171
171
|
return reloadAfterSwitch(period, bufferType, playbackObserver, 0);
|
|
172
172
|
}
|
|
173
|
-
|
|
174
|
-
.removeBuffer(period.start,
|
|
175
|
-
|
|
176
|
-
|
|
173
|
+
if (period.end === undefined) {
|
|
174
|
+
cleanBuffer$ = segmentBufferStatus.value.removeBuffer(period.start,
|
|
175
|
+
Infinity);
|
|
176
|
+
} else if (period.end <= period.start) {
|
|
177
|
+
cleanBuffer$ = observableOf(null);
|
|
178
|
+
} else {
|
|
179
|
+
cleanBuffer$ = segmentBufferStatus.value.removeBuffer(period.start,
|
|
180
|
+
period.end);
|
|
181
|
+
}
|
|
177
182
|
} else {
|
|
178
183
|
if (segmentBufferStatus.type === "uninitialized") {
|
|
179
184
|
segmentBuffersStore.disableSegmentBuffer(bufferType);
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
defer as observableDefer,
|
|
20
20
|
from as observableFrom,
|
|
21
21
|
Observable,
|
|
22
|
+
of as observableOf,
|
|
22
23
|
} from "rxjs";
|
|
23
24
|
import config from "../../../config";
|
|
24
25
|
import log from "../../../log";
|
|
@@ -54,7 +55,9 @@ export default function forceGarbageCollection(
|
|
|
54
55
|
|
|
55
56
|
log.debug("Stream: GC cleaning", cleanedupRanges);
|
|
56
57
|
return observableFrom(
|
|
57
|
-
cleanedupRanges.map(({ start, end }) =>
|
|
58
|
+
cleanedupRanges.map(({ start, end }) =>
|
|
59
|
+
start >= end ? observableOf(null) :
|
|
60
|
+
bufferingQueue.removeBuffer(start, end))
|
|
58
61
|
).pipe(concatAll());
|
|
59
62
|
});
|
|
60
63
|
}
|
|
@@ -62,8 +62,8 @@ export interface IBufferStatus {
|
|
|
62
62
|
*/
|
|
63
63
|
shouldRefreshManifest : boolean;
|
|
64
64
|
/**
|
|
65
|
-
* If 'true', the buffer memory is saturated
|
|
66
|
-
*
|
|
65
|
+
* If 'true', the buffer memory is saturated, thus we may have issues loading
|
|
66
|
+
* new segments.
|
|
67
67
|
*/
|
|
68
68
|
isBufferFull: boolean;
|
|
69
69
|
}
|
|
@@ -123,16 +123,18 @@ export default function getBufferStatus(
|
|
|
123
123
|
const getBufferedHistory = segmentBuffer.getSegmentHistory.bind(segmentBuffer);
|
|
124
124
|
|
|
125
125
|
/** List of segments we will need to download. */
|
|
126
|
-
const {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
126
|
+
const { segmentsToLoad,
|
|
127
|
+
segmentsOnHold,
|
|
128
|
+
isBufferFull } = getNeededSegments({ content,
|
|
129
|
+
bufferedSegments,
|
|
130
|
+
currentPlaybackTime,
|
|
131
|
+
fastSwitchThreshold,
|
|
132
|
+
getBufferedHistory,
|
|
133
|
+
neededRange,
|
|
134
|
+
segmentsBeingPushed,
|
|
135
|
+
maxBufferSize });
|
|
136
|
+
|
|
137
|
+
const prioritizedNeededSegments: IQueuedSegment[] = segmentsToLoad.map((segment) => (
|
|
136
138
|
{
|
|
137
139
|
priority: getSegmentPriority(segment.time, wantedStartPosition),
|
|
138
140
|
segment,
|
|
@@ -148,7 +150,8 @@ export default function getBufferStatus(
|
|
|
148
150
|
const lastPosition = representation.index.getLastPosition();
|
|
149
151
|
if (!representation.index.isInitialized() ||
|
|
150
152
|
period.end === undefined ||
|
|
151
|
-
prioritizedNeededSegments.length > 0
|
|
153
|
+
prioritizedNeededSegments.length > 0 ||
|
|
154
|
+
segmentsOnHold.length > 0)
|
|
152
155
|
{
|
|
153
156
|
hasFinishedLoading = false;
|
|
154
157
|
} else {
|
|
@@ -192,6 +195,11 @@ export default function getBufferStatus(
|
|
|
192
195
|
if (segmentsBeingPushed.length > 0) {
|
|
193
196
|
nextSegmentStart = Math.min(...segmentsBeingPushed.map(info => info.segment.time));
|
|
194
197
|
}
|
|
198
|
+
if (segmentsOnHold.length > 0) {
|
|
199
|
+
nextSegmentStart = nextSegmentStart !== null ?
|
|
200
|
+
Math.min(nextSegmentStart, segmentsOnHold[0].time) :
|
|
201
|
+
segmentsOnHold[0].time;
|
|
202
|
+
}
|
|
195
203
|
if (prioritizedNeededSegments.length > 0) {
|
|
196
204
|
nextSegmentStart = nextSegmentStart !== null ?
|
|
197
205
|
Math.min(nextSegmentStart, prioritizedNeededSegments[0].segment.time) :
|
|
@@ -84,7 +84,17 @@ export interface IGetNeededSegmentsArguments {
|
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
interface INeededSegments {
|
|
87
|
-
|
|
87
|
+
/** Segments that should be loaded right now, by chronological order. */
|
|
88
|
+
segmentsToLoad: ISegment[];
|
|
89
|
+
/**
|
|
90
|
+
* Segments that should be loaded, but not right now, due to some other
|
|
91
|
+
* constraints, such as memory limitations.
|
|
92
|
+
*/
|
|
93
|
+
segmentsOnHold : ISegment[];
|
|
94
|
+
/**
|
|
95
|
+
* If `true` the buffer is currently full according to the given limits.
|
|
96
|
+
* Memory should be freed if possible, for example by cleaning the buffers.
|
|
97
|
+
*/
|
|
88
98
|
isBufferFull: boolean;
|
|
89
99
|
}
|
|
90
100
|
/**
|
|
@@ -112,8 +122,6 @@ export default function getNeededSegments({
|
|
|
112
122
|
segmentsBeingPushed,
|
|
113
123
|
maxBufferSize);
|
|
114
124
|
|
|
115
|
-
// Current buffer length in seconds
|
|
116
|
-
let bufferLength = getBufferLength(bufferedSegments, segmentsBeingPushed);
|
|
117
125
|
const availableSegmentsForRange = representation.index
|
|
118
126
|
.getSegments(neededRange.start, neededRange.end - neededRange.start);
|
|
119
127
|
|
|
@@ -151,16 +159,16 @@ export default function getNeededSegments({
|
|
|
151
159
|
return true;
|
|
152
160
|
});
|
|
153
161
|
const { MINIMUM_SEGMENT_SIZE,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
let isMemorySaturated = false;
|
|
162
|
+
MIN_BUFFER_AHEAD } = config.getCurrent();
|
|
163
|
+
let shouldStopLoadingSegments = false;
|
|
157
164
|
/**
|
|
158
165
|
* Epsilon compensating for rounding errors when comparing the start and end
|
|
159
166
|
* time of multiple segments.
|
|
160
167
|
*/
|
|
161
168
|
const ROUNDING_ERROR = Math.min(1 / 60, MINIMUM_SEGMENT_SIZE);
|
|
162
169
|
let isBufferFull = false;
|
|
163
|
-
const
|
|
170
|
+
const segmentsOnHold : ISegment[] = [];
|
|
171
|
+
const segmentsToLoad = availableSegmentsForRange.filter(segment => {
|
|
164
172
|
const contentObject = objectAssign({ segment }, content);
|
|
165
173
|
|
|
166
174
|
// First, check that the segment is not already being pushed
|
|
@@ -176,21 +184,10 @@ export default function getNeededSegments({
|
|
|
176
184
|
if (segment.isInit) {
|
|
177
185
|
return true; // never skip initialization segments
|
|
178
186
|
}
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
// That we cannot download atleast till
|
|
182
|
-
// NeededRange.Start ( current position ) + a CONST
|
|
183
|
-
// Then the buffer is full
|
|
184
|
-
if (time < neededRange.start + MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP) {
|
|
185
|
-
isBufferFull = true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (isMemorySaturated &&
|
|
189
|
-
bufferLength > MIN_BUFFER_LENGTH) {
|
|
190
|
-
|
|
187
|
+
if (shouldStopLoadingSegments) {
|
|
188
|
+
segmentsOnHold.push(segment);
|
|
191
189
|
return false;
|
|
192
190
|
}
|
|
193
|
-
|
|
194
191
|
if (segment.complete && duration < MINIMUM_SEGMENT_SIZE) {
|
|
195
192
|
return false; // too small, don't download
|
|
196
193
|
}
|
|
@@ -239,29 +236,36 @@ export default function getNeededSegments({
|
|
|
239
236
|
}
|
|
240
237
|
}
|
|
241
238
|
|
|
239
|
+
const estimatedSegmentSize = (duration * content.representation.bitrate) / 8000;
|
|
240
|
+
if (availableBufferSize - estimatedSegmentSize < 0) {
|
|
241
|
+
isBufferFull = true;
|
|
242
|
+
if (time > neededRange.start + MIN_BUFFER_AHEAD) {
|
|
243
|
+
shouldStopLoadingSegments = true;
|
|
244
|
+
segmentsOnHold.push(segment);
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
242
249
|
// check if there is an hole in place of the segment currently
|
|
243
250
|
for (let i = 0; i < segmentsToKeep.length; i++) {
|
|
244
251
|
const completeSeg = segmentsToKeep[i];
|
|
245
|
-
if (completeSeg.end > time) {
|
|
246
|
-
// `true` if `completeSeg` starts too far after `time`
|
|
247
|
-
return completeSeg.start > time + ROUNDING_ERROR ||
|
|
248
|
-
// `true` if `completeSeg` ends too soon before `end`
|
|
249
|
-
getLastContiguousSegment(segmentsToKeep, i).end < end - ROUNDING_ERROR;
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
252
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
253
|
+
// For the first already-loaded segment, take the first one ending after
|
|
254
|
+
// this one' s start
|
|
255
|
+
if ((completeSeg.end + ROUNDING_ERROR) > time) {
|
|
256
|
+
const shouldLoad = completeSeg.start > time + ROUNDING_ERROR ||
|
|
257
|
+
getLastContiguousSegment(segmentsToKeep, i).end <
|
|
258
|
+
end - ROUNDING_ERROR;
|
|
259
|
+
if (shouldLoad) {
|
|
260
|
+
availableBufferSize -= estimatedSegmentSize;
|
|
261
|
+
}
|
|
262
|
+
return shouldLoad;
|
|
263
|
+
}
|
|
258
264
|
}
|
|
259
265
|
availableBufferSize -= estimatedSegmentSize;
|
|
260
|
-
bufferLength += duration;
|
|
261
|
-
|
|
262
266
|
return true;
|
|
263
267
|
});
|
|
264
|
-
return {
|
|
268
|
+
return { segmentsToLoad, segmentsOnHold, isBufferFull };
|
|
265
269
|
|
|
266
270
|
}
|
|
267
271
|
/**
|
|
@@ -286,7 +290,7 @@ function getAvailableBufferSize(
|
|
|
286
290
|
}, 0);
|
|
287
291
|
return bufferedSegments.reduce((size, chunk) => {
|
|
288
292
|
if (chunk.chunkSize !== undefined) {
|
|
289
|
-
return size - (chunk.chunkSize /
|
|
293
|
+
return size - (chunk.chunkSize / 1000);
|
|
290
294
|
} else {
|
|
291
295
|
return size;
|
|
292
296
|
}
|
|
@@ -294,25 +298,6 @@ function getAvailableBufferSize(
|
|
|
294
298
|
} , availableBufferSize);
|
|
295
299
|
}
|
|
296
300
|
|
|
297
|
-
/**
|
|
298
|
-
* Compute the length of the buffer in seconds
|
|
299
|
-
* @param bufferedSegments
|
|
300
|
-
* @param segmentsBeingPushed
|
|
301
|
-
* @returns bufferLength in seconds
|
|
302
|
-
*/
|
|
303
|
-
function getBufferLength(
|
|
304
|
-
bufferedSegments: IBufferedChunk[],
|
|
305
|
-
segmentsBeingPushed: IEndOfSegmentInfos[]
|
|
306
|
-
) : number {
|
|
307
|
-
const bufferLength = bufferedSegments.reduce((length, segment) => {
|
|
308
|
-
return length + (segment.end - segment.start);
|
|
309
|
-
}, 0);
|
|
310
|
-
const bufferBeingPushed = segmentsBeingPushed.reduce((length, segment) => {
|
|
311
|
-
return length + segment.segment.duration;
|
|
312
|
-
}, 0);
|
|
313
|
-
return bufferLength + bufferBeingPushed;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
301
|
/**
|
|
317
302
|
* From the given array of buffered chunks (`bufferedSegments`) returns the last
|
|
318
303
|
* buffered chunk contiguous with the one at the `startIndex` index given.
|
|
@@ -384,10 +384,12 @@ export default function RepresentationStream<TSegmentDataType>({
|
|
|
384
384
|
const gcedPosition = Math.max(
|
|
385
385
|
0,
|
|
386
386
|
wantedStartPosition - UPTO_CURRENT_POSITION_CLEANUP);
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
387
|
+
if (gcedPosition > 0) {
|
|
388
|
+
bufferRemoval = segmentBuffer
|
|
389
|
+
.removeBuffer(0, gcedPosition)
|
|
390
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
391
|
+
.pipe(ignoreElements());
|
|
392
|
+
}
|
|
391
393
|
}
|
|
392
394
|
return status.shouldRefreshManifest ?
|
|
393
395
|
observableConcat(observableOf(EVENTS.needsManifestRefresh()),
|
package/src/default_config.ts
CHANGED
|
@@ -1164,40 +1164,33 @@ const DEFAULT_CONFIG = {
|
|
|
1164
1164
|
*/
|
|
1165
1165
|
DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR: 1 / 1000,
|
|
1166
1166
|
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1167
|
+
/**
|
|
1168
|
+
* RxPlayer's media buffers have a linked history registering recent events
|
|
1169
|
+
* that happened on those.
|
|
1170
|
+
* The reason is to implement various heuristics in case of weird browser
|
|
1171
|
+
* behavior.
|
|
1172
|
+
*
|
|
1173
|
+
* The `BUFFERED_HISTORY_RETENTION_TIME` is the minimum age an entry of
|
|
1174
|
+
* that history can have before being removed from the history.
|
|
1175
|
+
*/
|
|
1176
1176
|
BUFFERED_HISTORY_RETENTION_TIME: 60000,
|
|
1177
1177
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1178
|
+
/**
|
|
1179
|
+
* RxPlayer's media buffers have a linked history registering recent events
|
|
1180
|
+
* that happened on those.
|
|
1181
|
+
* The reason is to implement various heuristics in case of weird browser
|
|
1182
|
+
* behavior.
|
|
1183
|
+
*
|
|
1184
|
+
* The `BUFFERED_HISTORY_RETENTION_TIME` is the maximum number of entries
|
|
1185
|
+
* there can be in that history.
|
|
1186
|
+
*/
|
|
1187
1187
|
BUFFERED_HISTORY_MAXIMUM_ENTRIES: 200,
|
|
1188
1188
|
|
|
1189
|
-
/**
|
|
1190
|
-
* Minimum buffer (in seconds) we should have, regardless of memory
|
|
1191
|
-
* constraints
|
|
1192
|
-
*/
|
|
1193
|
-
MIN_BUFFER_LENGTH : 5,
|
|
1194
|
-
|
|
1195
1189
|
/**
|
|
1196
1190
|
* Minimum buffer in seconds ahead relative to current time
|
|
1197
|
-
* we should be able to download
|
|
1198
|
-
* Before trying to agressively free up memory
|
|
1191
|
+
* we should be able to download, even in cases of saturated memory.
|
|
1199
1192
|
*/
|
|
1200
|
-
|
|
1193
|
+
MIN_BUFFER_AHEAD: 5,
|
|
1201
1194
|
|
|
1202
1195
|
/**
|
|
1203
1196
|
* Distance in seconds behind the current position
|
|
@@ -14,9 +14,7 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import TaskCanceller
|
|
18
|
-
CancellationError,
|
|
19
|
-
} from "../../utils/task_canceller";
|
|
17
|
+
import TaskCanceller from "../../utils/task_canceller";
|
|
20
18
|
import { ISegmentLoader } from "../types";
|
|
21
19
|
import checkISOBMFFIntegrity from "../utils/check_isobmff_integrity";
|
|
22
20
|
import inferSegmentContainer from "../utils/infer_segment_container";
|
|
@@ -32,61 +30,60 @@ export default function addSegmentIntegrityChecks<T>(
|
|
|
32
30
|
segmentLoader : ISegmentLoader<T>
|
|
33
31
|
) : ISegmentLoader<T> {
|
|
34
32
|
return (url, content, initialCancelSignal, callbacks) => {
|
|
35
|
-
return new Promise((
|
|
36
|
-
const
|
|
37
|
-
const unregisterCancelLstnr = initialCancelSignal
|
|
38
|
-
.register(function onCheckCancellation(err : CancellationError) {
|
|
39
|
-
canceller.cancel();
|
|
40
|
-
rej(err);
|
|
41
|
-
});
|
|
33
|
+
return new Promise((resolve, reject) => {
|
|
34
|
+
const requestCanceller = new TaskCanceller({ cancelOn: initialCancelSignal });
|
|
42
35
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
* @param {*} data
|
|
47
|
-
*/
|
|
48
|
-
function cancelAndRejectOnBadIntegrity(data : T) : void {
|
|
49
|
-
if (!(data instanceof Array) && !(data instanceof Uint8Array) ||
|
|
50
|
-
inferSegmentContainer(content.adaptation.type,
|
|
51
|
-
content.representation) !== "mp4")
|
|
52
|
-
{
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
try {
|
|
56
|
-
checkISOBMFFIntegrity(new Uint8Array(data), content.segment.isInit);
|
|
57
|
-
} catch (err) {
|
|
58
|
-
unregisterCancelLstnr();
|
|
59
|
-
canceller.cancel();
|
|
60
|
-
rej(err);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
36
|
+
// Reject the `CancellationError` when `requestCanceller`'s signal emits
|
|
37
|
+
// `stopRejectingOnCancel` here is a function allowing to stop this mechanism
|
|
38
|
+
const stopRejectingOnCancel = requestCanceller.signal.register(reject);
|
|
63
39
|
|
|
64
|
-
segmentLoader(url, content,
|
|
40
|
+
segmentLoader(url, content, requestCanceller.signal, {
|
|
65
41
|
...callbacks,
|
|
66
42
|
onNewChunk(data) {
|
|
67
|
-
|
|
68
|
-
|
|
43
|
+
try {
|
|
44
|
+
trowOnIntegrityError(data);
|
|
69
45
|
callbacks.onNewChunk(data);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
// Do not reject with a `CancellationError` after cancelling the request
|
|
48
|
+
stopRejectingOnCancel();
|
|
49
|
+
// Cancel the request
|
|
50
|
+
requestCanceller.cancel();
|
|
51
|
+
// Reject with thrown error
|
|
52
|
+
reject(err);
|
|
70
53
|
}
|
|
71
54
|
},
|
|
72
55
|
}).then((info) => {
|
|
73
|
-
if (
|
|
56
|
+
if (requestCanceller.isUsed) {
|
|
74
57
|
return;
|
|
75
58
|
}
|
|
76
|
-
|
|
77
|
-
|
|
59
|
+
stopRejectingOnCancel();
|
|
78
60
|
if (info.resultType === "segment-loaded") {
|
|
79
|
-
|
|
61
|
+
try {
|
|
62
|
+
trowOnIntegrityError(info.resultData.responseData);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
reject(err);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
80
67
|
}
|
|
81
|
-
|
|
82
|
-
|
|
68
|
+
resolve(info);
|
|
83
69
|
}, (error : unknown) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
unregisterCancelLstnr();
|
|
87
|
-
rej(error);
|
|
88
|
-
}
|
|
70
|
+
stopRejectingOnCancel();
|
|
71
|
+
reject(error);
|
|
89
72
|
});
|
|
90
73
|
});
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* If the data's seems to be corrupted, throws an `INTEGRITY_ERROR` error.
|
|
77
|
+
* @param {*} data
|
|
78
|
+
*/
|
|
79
|
+
function trowOnIntegrityError(data : T) : void {
|
|
80
|
+
if (!(data instanceof ArrayBuffer) && !(data instanceof Uint8Array) ||
|
|
81
|
+
inferSegmentContainer(content.adaptation.type,
|
|
82
|
+
content.representation) !== "mp4")
|
|
83
|
+
{
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
checkISOBMFFIntegrity(new Uint8Array(data), content.segment.isInit);
|
|
87
|
+
}
|
|
91
88
|
};
|
|
92
89
|
}
|