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.
Files changed (41) hide show
  1. package/CHANGELOG.md +3 -2
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/compat/get_start_date.d.ts +30 -0
  4. package/dist/_esm5.processed/compat/get_start_date.js +44 -0
  5. package/dist/_esm5.processed/compat/index.d.ts +2 -1
  6. package/dist/_esm5.processed/compat/index.js +2 -1
  7. package/dist/_esm5.processed/config.d.ts +1 -2
  8. package/dist/_esm5.processed/core/api/public_api.js +25 -25
  9. package/dist/_esm5.processed/core/segment_buffers/garbage_collector.js +4 -1
  10. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -1
  11. package/dist/_esm5.processed/core/stream/period/period_stream.js +9 -3
  12. package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.js +3 -2
  13. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +2 -2
  14. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +9 -3
  15. package/dist/_esm5.processed/core/stream/representation/get_needed_segments.d.ts +11 -1
  16. package/dist/_esm5.processed/core/stream/representation/get_needed_segments.js +27 -45
  17. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +6 -4
  18. package/dist/_esm5.processed/default_config.d.ts +2 -8
  19. package/dist/_esm5.processed/default_config.js +2 -8
  20. package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +39 -38
  21. package/dist/_esm5.processed/utils/reference.js +0 -2
  22. package/dist/_esm5.processed/utils/task_canceller.d.ts +8 -1
  23. package/dist/_esm5.processed/utils/task_canceller.js +9 -1
  24. package/dist/rx-player.js +198 -145
  25. package/dist/rx-player.min.js +1 -1
  26. package/package.json +1 -1
  27. package/sonar-project.properties +1 -1
  28. package/src/compat/get_start_date.ts +48 -0
  29. package/src/compat/index.ts +2 -0
  30. package/src/core/api/public_api.ts +23 -27
  31. package/src/core/segment_buffers/garbage_collector.ts +4 -0
  32. package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -1
  33. package/src/core/stream/period/period_stream.ts +9 -4
  34. package/src/core/stream/representation/force_garbage_collection.ts +4 -1
  35. package/src/core/stream/representation/get_buffer_status.ts +21 -13
  36. package/src/core/stream/representation/get_needed_segments.ts +40 -55
  37. package/src/core/stream/representation/representation_stream.ts +6 -4
  38. package/src/default_config.ts +20 -27
  39. package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +41 -44
  40. package/src/utils/reference.ts +0 -2
  41. package/src/utils/task_canceller.ts +16 -1
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "rx-player",
3
3
  "author": "Canal+",
4
- "version": "3.27.0-dev.2022032800",
4
+ "version": "3.27.0",
5
5
  "description": "Canal+ HTML5 Video Player",
6
6
  "main": "./dist/rx-player.js",
7
7
  "keywords": [
@@ -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-dev.2022032800
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
+ }
@@ -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-dev.2022032800";
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
- // Calculating the "wall-clock time" necessitate to obtain first an offset,
1280
- // and then adding that offset to the HTMLMediaElement's current position.
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
- positionWanted = (isDirectFile || manifest === null) ?
1600
- timeObj.wallClockTime :
1601
- timeObj.wallClockTime - (
1602
- manifest.availabilityStartTime !== undefined ?
1603
- manifest.availabilityStartTime :
1604
- 0);
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-dev.2022032800";
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
- segmentBuffer.removeBuffer(start, end).pipe(ignoreElements())),
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
- cleanBuffer$ = segmentBufferStatus.value
174
- .removeBuffer(period.start,
175
- period.end == null ? Infinity :
176
- period.end);
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 }) => bufferingQueue.removeBuffer(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 before being able to download
66
- * at least MIN_REQUIRED_BUFFER_AHEAD ( default : 10sec )
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 { neededSegments, isBufferFull } = getNeededSegments({ content,
127
- bufferedSegments,
128
- currentPlaybackTime,
129
- fastSwitchThreshold,
130
- getBufferedHistory,
131
- neededRange,
132
- segmentsBeingPushed,
133
- maxBufferSize });
134
-
135
- const prioritizedNeededSegments: IQueuedSegment[] = neededSegments.map((segment) => (
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
- neededSegments: ISegment[];
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
- MIN_BUFFER_LENGTH,
155
- MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP } = config.getCurrent();
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 neededSegments = availableSegmentsForRange.filter(segment => {
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 (isMemorySaturated) {
180
- // If we are so saturated in memory
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
- const estimatedSegmentSize = (duration * content.representation.bitrate) / 8000;
254
- if (availableBufferSize - estimatedSegmentSize < 0 &&
255
- bufferLength > MIN_BUFFER_LENGTH) {
256
- isMemorySaturated = true;
257
- return false;
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 { neededSegments, isBufferFull };
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 / 8000);
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
- bufferRemoval = segmentBuffer
388
- .removeBuffer(0, gcedPosition)
389
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
390
- .pipe(ignoreElements());
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()),
@@ -1164,40 +1164,33 @@ const DEFAULT_CONFIG = {
1164
1164
  */
1165
1165
  DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR: 1 / 1000,
1166
1166
 
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
- */
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
- * 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
- */
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
- MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP: 10,
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((res, rej) => {
36
- const canceller = new TaskCanceller();
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
- * If the data's seems to be corrupted, cancel the loading task and reject
45
- * with an `INTEGRITY_ERROR` error.
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, canceller.signal, {
40
+ segmentLoader(url, content, requestCanceller.signal, {
65
41
  ...callbacks,
66
42
  onNewChunk(data) {
67
- cancelAndRejectOnBadIntegrity(data);
68
- if (!canceller.isUsed) {
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 (canceller.isUsed) {
56
+ if (requestCanceller.isUsed) {
74
57
  return;
75
58
  }
76
- unregisterCancelLstnr();
77
-
59
+ stopRejectingOnCancel();
78
60
  if (info.resultType === "segment-loaded") {
79
- cancelAndRejectOnBadIntegrity(info.resultData.responseData);
61
+ try {
62
+ trowOnIntegrityError(info.resultData.responseData);
63
+ } catch (err) {
64
+ reject(err);
65
+ return;
66
+ }
80
67
  }
81
- res(info);
82
-
68
+ resolve(info);
83
69
  }, (error : unknown) => {
84
- // The segmentLoader's cancellations cases are all handled here
85
- if (!TaskCanceller.isCancellationError(error)) {
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
  }