rx-player 3.33.2 → 3.33.3

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 (90) hide show
  1. package/CHANGELOG.md +1079 -1128
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/compat/can_rely_on_request_media_key_system_access.d.ts +34 -0
  4. package/dist/_esm5.processed/compat/can_rely_on_request_media_key_system_access.js +40 -0
  5. package/dist/_esm5.processed/compat/generate_init_data.d.ts +14 -0
  6. package/dist/_esm5.processed/compat/generate_init_data.js +61 -0
  7. package/dist/_esm5.processed/compat/may_media_element_fail_on_undecipherable_data.d.ts +16 -0
  8. package/dist/_esm5.processed/compat/may_media_element_fail_on_undecipherable_data.js +17 -0
  9. package/dist/_esm5.processed/compat/should_wait_for_data_before_loaded.d.ts +1 -1
  10. package/dist/_esm5.processed/compat/should_wait_for_data_before_loaded.js +5 -3
  11. package/dist/_esm5.processed/config.d.ts +2 -0
  12. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +1 -1
  13. package/dist/_esm5.processed/core/api/debug/buffer_graph.js +3 -3
  14. package/dist/_esm5.processed/core/api/public_api.d.ts +17 -0
  15. package/dist/_esm5.processed/core/api/public_api.js +41 -2
  16. package/dist/_esm5.processed/core/api/tracks_management/media_element_track_choice_manager.js +21 -25
  17. package/dist/_esm5.processed/core/decrypt/attach_media_keys.js +6 -6
  18. package/dist/_esm5.processed/core/decrypt/find_key_system.d.ts +10 -0
  19. package/dist/_esm5.processed/core/decrypt/find_key_system.js +42 -1
  20. package/dist/_esm5.processed/core/decrypt/session_events_listener.js +2 -2
  21. package/dist/_esm5.processed/core/fetchers/manifest/manifest_fetcher.js +2 -2
  22. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +1 -1
  23. package/dist/_esm5.processed/core/init/media_source_content_initializer.js +34 -12
  24. package/dist/_esm5.processed/core/init/utils/get_loaded_reference.js +6 -1
  25. package/dist/_esm5.processed/core/init/utils/initial_seek_and_play.js +4 -1
  26. package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.js +24 -25
  27. package/dist/_esm5.processed/core/segment_buffers/implementations/image/image_segment_buffer.js +1 -1
  28. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.js +1 -1
  29. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/text_track_cues_store.js +2 -2
  30. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/utils.d.ts +15 -0
  31. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/utils.js +23 -0
  32. package/dist/_esm5.processed/core/segment_buffers/implementations/text/native/native_text_segment_buffer.js +1 -1
  33. package/dist/_esm5.processed/core/segment_buffers/inventory/segment_inventory.d.ts +18 -4
  34. package/dist/_esm5.processed/core/segment_buffers/inventory/segment_inventory.js +57 -13
  35. package/dist/_esm5.processed/core/stream/adaptation/adaptation_stream.js +3 -0
  36. package/dist/_esm5.processed/core/stream/representation/utils/append_segment_to_buffer.js +15 -8
  37. package/dist/_esm5.processed/core/stream/representation/utils/get_buffer_status.js +1 -37
  38. package/dist/_esm5.processed/core/stream/representation/utils/get_needed_segments.js +19 -11
  39. package/dist/_esm5.processed/core/stream/representation/utils/push_init_segment.js +6 -6
  40. package/dist/_esm5.processed/core/stream/representation/utils/push_media_segment.js +9 -9
  41. package/dist/_esm5.processed/default_config.d.ts +31 -0
  42. package/dist/_esm5.processed/default_config.js +48 -17
  43. package/dist/_esm5.processed/errors/assertion_error.d.ts +0 -1
  44. package/dist/_esm5.processed/errors/assertion_error.js +1 -2
  45. package/dist/_esm5.processed/errors/custom_loader_error.d.ts +0 -1
  46. package/dist/_esm5.processed/errors/custom_loader_error.js +1 -2
  47. package/dist/_esm5.processed/errors/encrypted_media_error.d.ts +0 -1
  48. package/dist/_esm5.processed/errors/encrypted_media_error.js +1 -2
  49. package/dist/_esm5.processed/errors/media_error.d.ts +0 -1
  50. package/dist/_esm5.processed/errors/media_error.js +1 -2
  51. package/dist/_esm5.processed/errors/network_error.d.ts +0 -1
  52. package/dist/_esm5.processed/errors/network_error.js +1 -2
  53. package/dist/_esm5.processed/errors/other_error.d.ts +0 -1
  54. package/dist/_esm5.processed/errors/other_error.js +1 -2
  55. package/dist/_esm5.processed/errors/request_error.d.ts +0 -1
  56. package/dist/_esm5.processed/errors/request_error.js +17 -15
  57. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader_error.d.ts +0 -1
  58. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader_error.js +2 -2
  59. package/dist/_esm5.processed/experimental/tools/createMetaplaylist/get_duration_from_manifest.js +1 -1
  60. package/dist/_esm5.processed/experimental/tools/mediaCapabilitiesProber/probers/decodingInfo.js +4 -4
  61. package/dist/_esm5.processed/parsers/containers/isobmff/utils.js +3 -1
  62. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +1 -3
  63. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representation_index.js +9 -5
  64. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/utils.d.ts +0 -1
  65. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/utils.js +1 -2
  66. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/ContentComponent.d.ts +1 -1
  67. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/ContentComponent.js +1 -1
  68. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/ContentProtection.d.ts +1 -1
  69. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/ContentProtection.js +1 -1
  70. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/Scheme.d.ts +1 -1
  71. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/Scheme.js +1 -1
  72. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/parsers_stack.d.ts +2 -1
  73. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/types.d.ts +55 -55
  74. package/dist/_esm5.processed/parsers/manifest/smooth/parse_protection_node.js +1 -1
  75. package/dist/_esm5.processed/transports/dash/extract_complete_chunks.d.ts +1 -1
  76. package/dist/_esm5.processed/transports/dash/extract_complete_chunks.js +6 -2
  77. package/dist/_esm5.processed/transports/dash/low_latency_segment_loader.js +4 -2
  78. package/dist/_esm5.processed/utils/languages/ISO_639-1_to_ISO_639-3.js +182 -182
  79. package/dist/_esm5.processed/utils/languages/ISO_639-2_to_ISO_639-3.js +19 -19
  80. package/dist/_esm5.processed/utils/languages/normalize.js +4 -1
  81. package/dist/_esm5.processed/utils/resolve_url.d.ts +13 -10
  82. package/dist/_esm5.processed/utils/resolve_url.js +220 -69
  83. package/dist/_esm5.processed/utils/string_parsing.d.ts +1 -1
  84. package/dist/_esm5.processed/utils/string_parsing.js +1 -1
  85. package/dist/_esm5.processed/utils/task_canceller.d.ts +0 -1
  86. package/dist/_esm5.processed/utils/task_canceller.js +3 -2
  87. package/dist/mpd-parser.wasm +0 -0
  88. package/dist/rx-player.js +3951 -3398
  89. package/dist/rx-player.min.js +1 -1
  90. package/package.json +40 -40
@@ -65,6 +65,8 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
65
65
  }
66
66
  };
67
67
  import { shouldReloadMediaSourceOnDecipherabilityUpdate } from "../../compat";
68
+ /* eslint-disable-next-line max-len */
69
+ import mayMediaElementFailOnUndecipherableData from "../../compat/may_media_element_fail_on_undecipherable_data";
68
70
  import config from "../../config";
69
71
  import { MediaError } from "../../errors";
70
72
  import log from "../../log";
@@ -353,6 +355,14 @@ var MediaSourceContentInitializer = /** @class */ (function (_super) {
353
355
  }, { clearSignal: cancelSignal, emitCurrentValue: true });
354
356
  var streamObserver = createStreamPlaybackObserver(playbackObserver, { autoPlay: autoPlay, manifest: manifest, initialPlayPerformed: initialPlayPerformed, initialSeekPerformed: initialSeekPerformed, speed: speed, startTime: initialTime }, cancelSignal);
355
357
  var rebufferingController = this._createRebufferingController(playbackObserver, manifest, speed, cancelSignal);
358
+ if (mayMediaElementFailOnUndecipherableData) {
359
+ // On some devices, just reload immediately when data become undecipherable
360
+ manifest.addEventListener("decipherabilityUpdate", function (elts) {
361
+ if (elts.some(function (e) { return e.representation.decipherable !== true; })) {
362
+ reloadMediaSource(0, undefined, undefined);
363
+ }
364
+ }, cancelSignal);
365
+ }
356
366
  var contentTimeBoundariesObserver = this
357
367
  ._createContentTimeBoundariesObserver(manifest, mediaSource, streamObserver, segmentBuffersStore, cancelSignal);
358
368
  /**
@@ -487,18 +497,7 @@ var MediaSourceContentInitializer = /** @class */ (function (_super) {
487
497
  },
488
498
  addedSegment: function (value) { return self.trigger("addedSegment", value); },
489
499
  needsMediaSourceReload: function (payload) {
490
- var _a, _b;
491
- var lastObservation = streamObserver.getReference().getValue();
492
- var currentPosition = (_a = lastObservation.position.pending) !== null && _a !== void 0 ? _a : streamObserver.getCurrentTime();
493
- var isPaused = (_b = lastObservation.paused.pending) !== null && _b !== void 0 ? _b : streamObserver.getIsPaused();
494
- var position = currentPosition + payload.timeOffset;
495
- if (payload.minimumPosition !== undefined) {
496
- position = Math.max(payload.minimumPosition, position);
497
- }
498
- if (payload.maximumPosition !== undefined) {
499
- position = Math.min(payload.maximumPosition, position);
500
- }
501
- onReloadOrder({ position: position, autoPlay: !isPaused });
500
+ reloadMediaSource(payload.timeOffset, payload.minimumPosition, payload.maximumPosition);
502
501
  },
503
502
  needsDecipherabilityFlush: function () {
504
503
  var _a, _b, _c;
@@ -534,6 +533,29 @@ var MediaSourceContentInitializer = /** @class */ (function (_super) {
534
533
  error: function (err) { return self._onFatalError(err); },
535
534
  };
536
535
  }
536
+ /**
537
+ * Callback allowing to reload the current content.
538
+ * @param {number} deltaPosition - Position you want to seek to after
539
+ * reloading, as a delta in seconds from the last polled playing position.
540
+ * @param {number|undefined} minimumPosition - If set, minimum time bound
541
+ * in seconds after `deltaPosition` has been applied.
542
+ * @param {number|undefined} maximumPosition - If set, minimum time bound
543
+ * in seconds after `deltaPosition` has been applied.
544
+ */
545
+ function reloadMediaSource(deltaPosition, minimumPosition, maximumPosition) {
546
+ var _a, _b;
547
+ var lastObservation = streamObserver.getReference().getValue();
548
+ var currentPosition = (_a = lastObservation.position.pending) !== null && _a !== void 0 ? _a : streamObserver.getCurrentTime();
549
+ var isPaused = (_b = lastObservation.paused.pending) !== null && _b !== void 0 ? _b : streamObserver.getIsPaused();
550
+ var position = currentPosition + deltaPosition;
551
+ if (minimumPosition !== undefined) {
552
+ position = Math.max(minimumPosition, position);
553
+ }
554
+ if (maximumPosition !== undefined) {
555
+ position = Math.min(maximumPosition, position);
556
+ }
557
+ onReloadOrder({ position: position, autoPlay: !isPaused });
558
+ }
537
559
  };
538
560
  /**
539
561
  * Creates a `ContentTimeBoundariesObserver`, a class indicating various
@@ -35,7 +35,12 @@ export default function getLoadedReference(playbackObserver, mediaElement, isDir
35
35
  observation.readyState === 0) {
36
36
  return;
37
37
  }
38
- if (!shouldWaitForDataBeforeLoaded(isDirectfile, mediaElement.hasAttribute("playsinline"))) {
38
+ if (!shouldWaitForDataBeforeLoaded(isDirectfile)) {
39
+ // The duration is NaN if no media data is available,
40
+ // which means media is not loaded yet.
41
+ if (isNaN(mediaElement.duration)) {
42
+ return;
43
+ }
39
44
  if (mediaElement.duration > 0) {
40
45
  isLoaded.setValue(true);
41
46
  listenCanceller.cancel();
@@ -101,7 +101,10 @@ export default function performInitialSeekAndPlay(mediaElement, playbackObserver
101
101
  */
102
102
  var isAwaitingSeek = hasAskedForInitialSeek;
103
103
  playbackObserver.listen(function (observation, stopListening) {
104
- if (hasAskedForInitialSeek && observation.seeking) {
104
+ if (hasAskedForInitialSeek &&
105
+ (observation.seeking ||
106
+ observation.event === "seeking" ||
107
+ observation.event === "internal-seeking")) {
105
108
  isAwaitingSeek = false;
106
109
  return;
107
110
  }
@@ -125,8 +125,10 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
125
125
  AudioVideoSegmentBuffer.prototype.pushChunk = function (infos, cancellationSignal) {
126
126
  assertDataIsBufferSource(infos.data.chunk);
127
127
  log.debug("AVSB: receiving order to push data to the SourceBuffer", this.bufferType, getLoggableSegmentId(infos.inventoryInfos));
128
- return this._addToQueue({ type: SegmentBufferOperation.Push,
129
- value: infos }, cancellationSignal);
128
+ return this._addToQueue({
129
+ type: SegmentBufferOperation.Push,
130
+ value: infos,
131
+ }, cancellationSignal);
130
132
  };
131
133
  /**
132
134
  * Remove buffered data (added to the same FIFO queue than `pushChunk`).
@@ -137,8 +139,7 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
137
139
  */
138
140
  AudioVideoSegmentBuffer.prototype.removeBuffer = function (start, end, cancellationSignal) {
139
141
  log.debug("AVSB: receiving order to remove data from the SourceBuffer", this.bufferType, start, end);
140
- return this._addToQueue({ type: SegmentBufferOperation.Remove,
141
- value: { start: start, end: end } }, cancellationSignal);
142
+ return this._addToQueue({ type: SegmentBufferOperation.Remove, value: { start: start, end: end } }, cancellationSignal);
142
143
  };
143
144
  /**
144
145
  * Indicate that every chunks from a Segment has been given to pushChunk so
@@ -151,8 +152,7 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
151
152
  */
152
153
  AudioVideoSegmentBuffer.prototype.endOfSegment = function (infos, cancellationSignal) {
153
154
  log.debug("AVSB: receiving order for validating end of segment", this.bufferType, getLoggableSegmentId(infos));
154
- return this._addToQueue({ type: SegmentBufferOperation.EndOfSegment,
155
- value: infos }, cancellationSignal);
155
+ return this._addToQueue({ type: SegmentBufferOperation.EndOfSegment, value: infos }, cancellationSignal);
156
156
  };
157
157
  /**
158
158
  * Returns the currently buffered data, in a TimeRanges object.
@@ -180,9 +180,9 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
180
180
  }
181
181
  };
182
182
  var queued = this._queue.map(parseQueuedOperation);
183
- return this._pendingTask === null ?
184
- queued :
185
- [parseQueuedOperation(this._pendingTask)].concat(queued);
183
+ return this._pendingTask === null
184
+ ? queued
185
+ : [parseQueuedOperation(this._pendingTask)].concat(queued);
186
186
  };
187
187
  /**
188
188
  * Dispose of the resources used by this AudioVideoSegmentBuffer.
@@ -220,15 +220,15 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
220
220
  AudioVideoSegmentBuffer.prototype._onPendingTaskError = function (err) {
221
221
  this._lastInitSegmentUniqueId = null; // initialize init segment as a security
222
222
  if (this._pendingTask !== null) {
223
- var error = err instanceof Error ?
224
- err :
225
- new Error("An unknown error occured when doing operations " +
223
+ var error = err instanceof Error
224
+ ? err
225
+ : new Error("An unknown error occured when doing operations " +
226
226
  "on the SourceBuffer");
227
227
  var task = this._pendingTask;
228
228
  if (task.type === SegmentBufferOperation.Push &&
229
229
  task.data.length === 0 &&
230
230
  task.inventoryData !== null) {
231
- this._segmentInventory.insertChunk(task.inventoryData, false);
231
+ this._segmentInventory.insertChunk(task.inventoryData, false, performance.now());
232
232
  }
233
233
  this._pendingTask = null;
234
234
  task.reject(error);
@@ -243,8 +243,7 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
243
243
  AudioVideoSegmentBuffer.prototype._addToQueue = function (operation, cancellationSignal) {
244
244
  var _this = this;
245
245
  return createCancellablePromise(cancellationSignal, function (resolve, reject) {
246
- var shouldRestartQueue = _this._queue.length === 0 &&
247
- _this._pendingTask === null;
246
+ var shouldRestartQueue = _this._queue.length === 0 && _this._pendingTask === null;
248
247
  var queueItem = objectAssign({ resolve: resolve, reject: reject }, operation);
249
248
  _this._queue.push(queueItem);
250
249
  if (shouldRestartQueue) {
@@ -279,7 +278,7 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
279
278
  switch (task.type) {
280
279
  case SegmentBufferOperation.Push:
281
280
  if (task.inventoryData !== null) {
282
- this._segmentInventory.insertChunk(task.inventoryData, true);
281
+ this._segmentInventory.insertChunk(task.inventoryData, true, performance.now());
283
282
  }
284
283
  break;
285
284
  case SegmentBufferOperation.EndOfSegment:
@@ -298,7 +297,8 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
298
297
  return;
299
298
  }
300
299
  }
301
- else { // if this._pendingTask is null, go to next item in queue
300
+ else {
301
+ // if this._pendingTask is null, go to next item in queue
302
302
  var nextItem = this._queue.shift();
303
303
  if (nextItem === undefined) {
304
304
  return; // we have nothing left to do
@@ -313,17 +313,15 @@ var AudioVideoSegmentBuffer = /** @class */ (function (_super) {
313
313
  dataToPush = this._preparePushOperation(itemValue.data);
314
314
  }
315
315
  catch (e) {
316
- this._pendingTask = objectAssign({ data: [],
317
- inventoryData: itemValue.inventoryInfos }, nextItem);
318
- var error = e instanceof Error ?
319
- e :
320
- new Error("An unknown error occured when preparing a push operation");
316
+ this._pendingTask = objectAssign({ data: [], inventoryData: itemValue.inventoryInfos }, nextItem);
317
+ var error = e instanceof Error
318
+ ? e
319
+ : new Error("An unknown error occured when preparing a push operation");
321
320
  this._lastInitSegmentUniqueId = null; // initialize init segment as a security
322
321
  nextItem.reject(error);
323
322
  return;
324
323
  }
325
- this._pendingTask = objectAssign({ data: dataToPush,
326
- inventoryData: itemValue.inventoryInfos }, nextItem);
324
+ this._pendingTask = objectAssign({ data: dataToPush, inventoryData: itemValue.inventoryInfos }, nextItem);
327
325
  }
328
326
  }
329
327
  try {
@@ -450,7 +448,8 @@ export default AudioVideoSegmentBuffer;
450
448
  * @param {Object} data
451
449
  */
452
450
  function assertDataIsBufferSource(data) {
453
- if (0 /* __ENVIRONMENT__.CURRENT_ENV */ === 0 /* __ENVIRONMENT__.PRODUCTION */) {
451
+ if (0 /* __ENVIRONMENT__.CURRENT_ENV */ ===
452
+ 0 /* __ENVIRONMENT__.PRODUCTION */) {
454
453
  return;
455
454
  }
456
455
  if (typeof data !== "object" ||
@@ -80,7 +80,7 @@ var ImageSegmentBuffer = /** @class */ (function (_super) {
80
80
  try {
81
81
  this._buffered.insert(startTime, endTime);
82
82
  if (infos.inventoryInfos !== null) {
83
- this._segmentInventory.insertChunk(infos.inventoryInfos, true);
83
+ this._segmentInventory.insertChunk(infos.inventoryInfos, true, performance.now());
84
84
  }
85
85
  }
86
86
  catch (err) {
@@ -231,7 +231,7 @@ var HTMLTextSegmentBuffer = /** @class */ (function (_super) {
231
231
  return;
232
232
  }
233
233
  if (infos.inventoryInfos !== null) {
234
- this._segmentInventory.insertChunk(infos.inventoryInfos, true);
234
+ this._segmentInventory.insertChunk(infos.inventoryInfos, true, performance.now());
235
235
  }
236
236
  this._buffer.insert(cues, start, end);
237
237
  this._buffered.insert(start, end);
@@ -14,7 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
  import assert from "../../../../../utils/assert";
17
- import { areNearlyEqual, getCuesAfter, getCuesBefore, removeCuesInfosBetween, } from "./utils";
17
+ import { areNearlyEqual, getCuesAfter, getCuesBefore, removeCuesInfosBetween, areCuesStartNearlyEqual, } from "./utils";
18
18
  /**
19
19
  * first or last IHTMLCue in a group can have a slighlty different start
20
20
  * or end time than the start or end time of the ICuesGroup due to parsing
@@ -223,7 +223,7 @@ var TextTrackCuesStore = /** @class */ (function () {
223
223
  for (var cueIdx = 0; cueIdx < cuesBuffer.length; cueIdx++) {
224
224
  var cuesInfos = cuesBuffer[cueIdx];
225
225
  if (start < cuesInfos.end) {
226
- if (areNearlyEqual(start, cuesInfos.start, relativeDelta)) {
226
+ if (areCuesStartNearlyEqual(cuesInfosToInsert, cuesInfos)) {
227
227
  if (areNearlyEqual(end, cuesInfos.end, relativeDelta)) {
228
228
  // exact same segment
229
229
  // ours: |AAAAA|
@@ -22,6 +22,21 @@ import { ICuesGroup, IHTMLCue } from "./types";
22
22
  * @returns {Boolean}
23
23
  */
24
24
  export declare function areNearlyEqual(a: number, b: number, delta?: number): boolean;
25
+ /**
26
+ * Check if two cues start are almost the same.
27
+ * It should depend on there relative length:
28
+ *
29
+ * [0, 2] and [2, 4] start are NOT equals
30
+ * [0, 2] and [0, 4] start are equals
31
+ * [0, 0.1] and [0.101, 2] start are NOT equals
32
+ * [0, 2] and [0.01, 4] start are equals
33
+ * [0, 100] and [1, 200] start are NOT equals
34
+ * @see MAX_DELTA_BUFFER_TIME
35
+ * @param {Number} firstCue the existing cue
36
+ * @param {Number} secondCue the cue that we test if it follow firstCue
37
+ * @returns {Boolean}
38
+ */
39
+ export declare function areCuesStartNearlyEqual(firstCue: ICuesGroup, secondCue: ICuesGroup): boolean;
25
40
  /**
26
41
  * Get all cues which have data before the given time.
27
42
  * @param {Object} cues
@@ -73,6 +73,29 @@ export function areNearlyEqual(a, b, delta) {
73
73
  if (delta === void 0) { delta = MAX_DELTA_BUFFER_TIME; }
74
74
  return Math.abs(a - b) <= Math.min(delta, MAX_DELTA_BUFFER_TIME);
75
75
  }
76
+ var EPSILON = 5e-2; // 5%
77
+ /**
78
+ * Check if two cues start are almost the same.
79
+ * It should depend on there relative length:
80
+ *
81
+ * [0, 2] and [2, 4] start are NOT equals
82
+ * [0, 2] and [0, 4] start are equals
83
+ * [0, 0.1] and [0.101, 2] start are NOT equals
84
+ * [0, 2] and [0.01, 4] start are equals
85
+ * [0, 100] and [1, 200] start are NOT equals
86
+ * @see MAX_DELTA_BUFFER_TIME
87
+ * @param {Number} firstCue the existing cue
88
+ * @param {Number} secondCue the cue that we test if it follow firstCue
89
+ * @returns {Boolean}
90
+ */
91
+ export function areCuesStartNearlyEqual(firstCue, secondCue) {
92
+ var firstCueDuration = firstCue.end - firstCue.start;
93
+ var secondCueDuration = secondCue.end - secondCue.start;
94
+ var diffBetweenStart = Math.abs(firstCue.start - secondCue.start);
95
+ var minDuration = Math.min(firstCueDuration, secondCueDuration, MAX_DELTA_BUFFER_TIME);
96
+ // ratio diff/ minduration is bellow 5%
97
+ return diffBetweenStart / minDuration <= EPSILON;
98
+ }
76
99
  /**
77
100
  * Get all cues which have data before the given time.
78
101
  * @param {Object} cues
@@ -157,7 +157,7 @@ var NativeTextSegmentBuffer = /** @class */ (function (_super) {
157
157
  }
158
158
  this._buffered.insert(start, end);
159
159
  if (infos.inventoryInfos !== null) {
160
- this._segmentInventory.insertChunk(infos.inventoryInfos, true);
160
+ this._segmentInventory.insertChunk(infos.inventoryInfos, true, performance.now());
161
161
  }
162
162
  }
163
163
  catch (err) {
@@ -24,11 +24,11 @@ export declare const enum ChunkStatus {
24
24
  * fully-loaded and pushed.
25
25
  *
26
26
  * Once and if the corresponding segment is fully-pushed, its `ChunkStatus`
27
- * switches to `Complete`.
27
+ * switches to `FullyLoaded`.
28
28
  */
29
29
  PartiallyPushed = 0,
30
30
  /** This chunk corresponds to a fully-loaded segment. */
31
- Complete = 1,
31
+ FullyLoaded = 1,
32
32
  /**
33
33
  * This chunk's push operation failed, in this scenario there is no certitude
34
34
  * about the presence of that chunk in the buffer: it may not be present,
@@ -39,6 +39,15 @@ export declare const enum ChunkStatus {
39
39
  }
40
40
  /** Information stored on a single chunk by the SegmentInventory. */
41
41
  export interface IBufferedChunk {
42
+ /**
43
+ * Value of the monotonically-increasing timestamp used by the RxPlayer at
44
+ * the time the segment was succesfully pushed to the buffer.
45
+ *
46
+ * We add this value here as we may want to wait for some time before
47
+ * synchronizing the buffer to ensure the browser has properly considered the
48
+ * full segment in its `buffered` value.
49
+ */
50
+ insertionTs: number;
42
51
  /**
43
52
  * Complete size of the pushed chunk, in bytes.
44
53
  * Note that this does not always reflect the memory imprint of the segment in
@@ -190,10 +199,15 @@ export default class SegmentInventory {
190
199
  * Chunks are decodable sub-parts of a whole segment. Once all chunks in a
191
200
  * segment have been inserted, you should call the `completeSegment` method.
192
201
  * @param {Object} chunkInformation
202
+ * @param {boolean} succeed - If `true` the insertion operation finished with
203
+ * success, if `false` an error arised while doing it.
204
+ * @param {number} insertionTs - The monotonically-increasing timestamp at the
205
+ * time the segment has been confirmed to be inserted by the buffer.
193
206
  */
194
- insertChunk({ period, adaptation, representation, segment, chunkSize, start, end }: IInsertedChunkInfos, succeed: boolean): void;
207
+ insertChunk({ period, adaptation, representation, segment, chunkSize, start, end }: IInsertedChunkInfos, succeed: boolean, insertionTs: number): void;
195
208
  /**
196
- * Indicate that inserted chunks can now be considered as a complete segment.
209
+ * Indicate that inserted chunks can now be considered as a fully-loaded
210
+ * segment.
197
211
  * Take in argument the same content than what was given to `insertChunk` for
198
212
  * the corresponding chunks.
199
213
  * @param {Object} content
@@ -139,8 +139,19 @@ var SegmentInventory = /** @class */ (function () {
139
139
  // those segments are contiguous, we have no way to infer their real
140
140
  // end
141
141
  if (prevSegment.bufferedEnd === undefined) {
142
- prevSegment.bufferedEnd = thisSegment.precizeStart ? thisSegment.start :
143
- prevSegment.end;
142
+ if (thisSegment.precizeStart) {
143
+ prevSegment.bufferedEnd = thisSegment.start;
144
+ }
145
+ else if (prevSegment.infos.segment.complete) {
146
+ prevSegment.bufferedEnd = prevSegment.end;
147
+ }
148
+ else {
149
+ // We cannot truly trust the anounced end here as the segment was
150
+ // potentially not complete at its time of announce.
151
+ // Just assume the next's segment announced start is right - as
152
+ // `start` is in that scenario more "trustable" than `end`.
153
+ prevSegment.bufferedEnd = thisSegment.start;
154
+ }
144
155
  log.debug("SI: calculating buffered end of contiguous segment", bufferType, prevSegment.bufferedEnd, prevSegment.end);
145
156
  }
146
157
  thisSegment.bufferedStart = prevSegment.bufferedEnd;
@@ -182,8 +193,12 @@ var SegmentInventory = /** @class */ (function () {
182
193
  * Chunks are decodable sub-parts of a whole segment. Once all chunks in a
183
194
  * segment have been inserted, you should call the `completeSegment` method.
184
195
  * @param {Object} chunkInformation
196
+ * @param {boolean} succeed - If `true` the insertion operation finished with
197
+ * success, if `false` an error arised while doing it.
198
+ * @param {number} insertionTs - The monotonically-increasing timestamp at the
199
+ * time the segment has been confirmed to be inserted by the buffer.
185
200
  */
186
- SegmentInventory.prototype.insertChunk = function (_a, succeed) {
201
+ SegmentInventory.prototype.insertChunk = function (_a, succeed, insertionTs) {
187
202
  var period = _a.period, adaptation = _a.adaptation, representation = _a.representation, segment = _a.segment, chunkSize = _a.chunkSize, start = _a.start, end = _a.end;
188
203
  if (segment.isInit) {
189
204
  return;
@@ -195,7 +210,7 @@ var SegmentInventory = /** @class */ (function () {
195
210
  }
196
211
  var inventory = this._inventory;
197
212
  var newSegment = { status: succeed ? 0 /* ChunkStatus.PartiallyPushed */ :
198
- 2 /* ChunkStatus.Failed */, chunkSize: chunkSize, splitted: false, start: start, end: end, precizeStart: false,
213
+ 2 /* ChunkStatus.Failed */, insertionTs: insertionTs, chunkSize: chunkSize, splitted: false, start: start, end: end, precizeStart: false,
199
214
  precizeEnd: false,
200
215
  bufferedStart: undefined,
201
216
  bufferedEnd: undefined,
@@ -389,6 +404,7 @@ var SegmentInventory = /** @class */ (function () {
389
404
  // ===> : |--|====|-|
390
405
  log.warn("SI: Segment pushed is contained in a previous one", bufferType, start, end, segmentI.start, segmentI.end);
391
406
  var nextSegment = { status: segmentI.status,
407
+ insertionTs: segmentI.insertionTs,
392
408
  /**
393
409
  * Note: this sadly means we're doing as if
394
410
  * that chunk is present two times.
@@ -506,7 +522,8 @@ var SegmentInventory = /** @class */ (function () {
506
522
  }
507
523
  };
508
524
  /**
509
- * Indicate that inserted chunks can now be considered as a complete segment.
525
+ * Indicate that inserted chunks can now be considered as a fully-loaded
526
+ * segment.
510
527
  * Take in argument the same content than what was given to `insertChunk` for
511
528
  * the corresponding chunks.
512
529
  * @param {Object} content
@@ -547,7 +564,7 @@ var SegmentInventory = /** @class */ (function () {
547
564
  i -= length_1;
548
565
  }
549
566
  if (this._inventory[firstI].status === 0 /* ChunkStatus.PartiallyPushed */) {
550
- this._inventory[firstI].status = 1 /* ChunkStatus.Complete */;
567
+ this._inventory[firstI].status = 1 /* ChunkStatus.FullyLoaded */;
551
568
  }
552
569
  this._inventory[firstI].chunkSize = segmentSize;
553
570
  this._inventory[firstI].end = lastEnd;
@@ -570,6 +587,8 @@ var SegmentInventory = /** @class */ (function () {
570
587
  }
571
588
  }
572
589
  else {
590
+ // TODO FIXME There might be a false positive here when the
591
+ // `SEGMENT_SYNCHRONIZATION_DELAY` config value is at play
573
592
  log.debug("SI: buffered range not known after sync. Skipping history.", seg.start, seg.end);
574
593
  }
575
594
  }
@@ -612,7 +631,7 @@ export default SegmentInventory;
612
631
  */
613
632
  function bufferedStartLooksCoherent(thisSegment) {
614
633
  if (thisSegment.bufferedStart === undefined ||
615
- thisSegment.status !== 1 /* ChunkStatus.Complete */) {
634
+ thisSegment.status !== 1 /* ChunkStatus.FullyLoaded */) {
616
635
  return false;
617
636
  }
618
637
  var start = thisSegment.start, end = thisSegment.end;
@@ -633,7 +652,8 @@ function bufferedStartLooksCoherent(thisSegment) {
633
652
  */
634
653
  function bufferedEndLooksCoherent(thisSegment) {
635
654
  if (thisSegment.bufferedEnd === undefined ||
636
- thisSegment.status !== 1 /* ChunkStatus.Complete */) {
655
+ !thisSegment.infos.segment.complete ||
656
+ thisSegment.status !== 1 /* ChunkStatus.FullyLoaded */) {
637
657
  return false;
638
658
  }
639
659
  var start = thisSegment.start, end = thisSegment.end;
@@ -654,7 +674,7 @@ function bufferedEndLooksCoherent(thisSegment) {
654
674
  * @param {Object} lastDeletedSegmentInfos
655
675
  */
656
676
  function guessBufferedStartFromRangeStart(firstSegmentInRange, rangeStart, lastDeletedSegmentInfos, bufferType) {
657
- var MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE = config.getCurrent().MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE;
677
+ var _a = config.getCurrent(), MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE = _a.MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE, MISSING_DATA_TRIGGER_SYNC_DELAY = _a.MISSING_DATA_TRIGGER_SYNC_DELAY, SEGMENT_SYNCHRONIZATION_DELAY = _a.SEGMENT_SYNCHRONIZATION_DELAY;
658
678
  if (firstSegmentInRange.bufferedStart !== undefined) {
659
679
  if (firstSegmentInRange.bufferedStart < rangeStart) {
660
680
  log.debug("SI: Segment partially GCed at the start", bufferType, firstSegmentInRange.bufferedStart, rangeStart);
@@ -684,6 +704,12 @@ function guessBufferedStartFromRangeStart(firstSegmentInRange, rangeStart, lastD
684
704
  }
685
705
  else if (firstSegmentInRange.start - rangeStart <=
686
706
  MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE) {
707
+ var now = performance.now();
708
+ if (firstSegmentInRange.start - rangeStart >= MISSING_DATA_TRIGGER_SYNC_DELAY &&
709
+ now - firstSegmentInRange.insertionTs < SEGMENT_SYNCHRONIZATION_DELAY) {
710
+ log.debug("SI: Ignored bufferedStart synchronization", bufferType, rangeStart, firstSegmentInRange.start, now - firstSegmentInRange.insertionTs);
711
+ return;
712
+ }
687
713
  log.debug("SI: found true buffered start", bufferType, rangeStart, firstSegmentInRange.start);
688
714
  firstSegmentInRange.bufferedStart = rangeStart;
689
715
  if (bufferedStartLooksCoherent(firstSegmentInRange)) {
@@ -696,7 +722,13 @@ function guessBufferedStartFromRangeStart(firstSegmentInRange, rangeStart, lastD
696
722
  firstSegmentInRange.bufferedStart = firstSegmentInRange.start;
697
723
  }
698
724
  else {
699
- log.debug("SI: Segment appears immediately garbage collected at the start", bufferType, firstSegmentInRange.bufferedStart, rangeStart);
725
+ var now = performance.now();
726
+ if (firstSegmentInRange.start - rangeStart >= MISSING_DATA_TRIGGER_SYNC_DELAY &&
727
+ now - firstSegmentInRange.insertionTs < SEGMENT_SYNCHRONIZATION_DELAY) {
728
+ log.debug("SI: Ignored bufferedStart synchronization", bufferType, rangeStart, firstSegmentInRange.start, now - firstSegmentInRange.insertionTs);
729
+ return;
730
+ }
731
+ log.debug("SI: Segment appears immediately garbage collected at the start", bufferType, rangeStart, firstSegmentInRange.start);
700
732
  firstSegmentInRange.bufferedStart = rangeStart;
701
733
  }
702
734
  }
@@ -708,7 +740,7 @@ function guessBufferedStartFromRangeStart(firstSegmentInRange, rangeStart, lastD
708
740
  * @param {string} bufferType
709
741
  */
710
742
  function guessBufferedEndFromRangeEnd(lastSegmentInRange, rangeEnd, bufferType) {
711
- var MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE = config.getCurrent().MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE;
743
+ var _a = config.getCurrent(), MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE = _a.MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE, MISSING_DATA_TRIGGER_SYNC_DELAY = _a.MISSING_DATA_TRIGGER_SYNC_DELAY, SEGMENT_SYNCHRONIZATION_DELAY = _a.SEGMENT_SYNCHRONIZATION_DELAY;
712
744
  if (lastSegmentInRange.bufferedEnd !== undefined) {
713
745
  if (lastSegmentInRange.bufferedEnd > rangeEnd) {
714
746
  log.debug("SI: Segment partially GCed at the end", bufferType, lastSegmentInRange.bufferedEnd, rangeEnd);
@@ -725,8 +757,14 @@ function guessBufferedEndFromRangeEnd(lastSegmentInRange, rangeEnd, bufferType)
725
757
  log.debug("SI: buffered end is precize end", bufferType, lastSegmentInRange.end);
726
758
  lastSegmentInRange.bufferedEnd = lastSegmentInRange.end;
727
759
  }
728
- else if (rangeEnd - lastSegmentInRange.end <=
729
- MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE) {
760
+ else if (rangeEnd - lastSegmentInRange.end <= MAX_MANIFEST_BUFFERED_START_END_DIFFERENCE ||
761
+ !lastSegmentInRange.infos.segment.complete) {
762
+ var now = performance.now();
763
+ if (rangeEnd - lastSegmentInRange.end >= MISSING_DATA_TRIGGER_SYNC_DELAY &&
764
+ now - lastSegmentInRange.insertionTs < SEGMENT_SYNCHRONIZATION_DELAY) {
765
+ log.debug("SI: Ignored bufferedEnd synchronization", bufferType, rangeEnd, lastSegmentInRange.end, now - lastSegmentInRange.insertionTs);
766
+ return;
767
+ }
730
768
  log.debug("SI: found true buffered end", bufferType, rangeEnd, lastSegmentInRange.end);
731
769
  lastSegmentInRange.bufferedEnd = rangeEnd;
732
770
  if (bufferedEndLooksCoherent(lastSegmentInRange)) {
@@ -739,6 +777,12 @@ function guessBufferedEndFromRangeEnd(lastSegmentInRange, rangeEnd, bufferType)
739
777
  lastSegmentInRange.bufferedEnd = lastSegmentInRange.end;
740
778
  }
741
779
  else {
780
+ var now = performance.now();
781
+ if (rangeEnd - lastSegmentInRange.end >= MISSING_DATA_TRIGGER_SYNC_DELAY &&
782
+ now - lastSegmentInRange.insertionTs < SEGMENT_SYNCHRONIZATION_DELAY) {
783
+ log.debug("SI: Ignored bufferedEnd synchronization", bufferType, rangeEnd, lastSegmentInRange.end, now - lastSegmentInRange.insertionTs);
784
+ return;
785
+ }
742
786
  log.debug("SI: Segment appears immediately garbage collected at the end", bufferType, lastSegmentInRange.bufferedEnd, rangeEnd);
743
787
  lastSegmentInRange.bufferedEnd = rangeEnd;
744
788
  }
@@ -200,6 +200,9 @@ export default function AdaptationStream(_a, callbacks, parentCancelSignal) {
200
200
  inbandEvent: callbacks.inbandEvent,
201
201
  warning: callbacks.warning,
202
202
  error: function (err) {
203
+ if (TaskCanceller.isCancellationError(err) && adapStreamCanceller.isUsed()) {
204
+ return;
205
+ }
203
206
  adapStreamCanceller.cancel();
204
207
  callbacks.error(err);
205
208
  },
@@ -53,6 +53,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
53
53
  * This file allows any Stream to push data to a SegmentBuffer.
54
54
  */
55
55
  import { MediaError } from "../../../../errors";
56
+ import sleep from "../../../../utils/sleep";
56
57
  import { CancellationError } from "../../../../utils/task_canceller";
57
58
  import forceGarbageCollection from "./force_garbage_collection";
58
59
  /**
@@ -66,17 +67,17 @@ import forceGarbageCollection from "./force_garbage_collection";
66
67
  * @returns {Promise}
67
68
  */
68
69
  export default function appendSegmentToBuffer(playbackObserver, segmentBuffer, dataInfos, cancellationSignal) {
69
- var _a;
70
70
  return __awaiter(this, void 0, void 0, function () {
71
71
  var appendError_1, reason, position, currentPos, err2_1, reason;
72
+ var _a;
72
73
  return __generator(this, function (_b) {
73
74
  switch (_b.label) {
74
75
  case 0:
75
- _b.trys.push([0, 2, , 8]);
76
+ _b.trys.push([0, 2, , 9]);
76
77
  return [4 /*yield*/, segmentBuffer.pushChunk(dataInfos, cancellationSignal)];
77
78
  case 1:
78
79
  _b.sent();
79
- return [3 /*break*/, 8];
80
+ return [3 /*break*/, 9];
80
81
  case 2:
81
82
  appendError_1 = _b.sent();
82
83
  if (cancellationSignal.isCancelled() && appendError_1 instanceof CancellationError) {
@@ -93,21 +94,27 @@ export default function appendSegmentToBuffer(playbackObserver, segmentBuffer, d
93
94
  currentPos = (_a = position.pending) !== null && _a !== void 0 ? _a : position.last;
94
95
  _b.label = 3;
95
96
  case 3:
96
- _b.trys.push([3, 6, , 7]);
97
+ _b.trys.push([3, 7, , 8]);
97
98
  return [4 /*yield*/, forceGarbageCollection(currentPos, segmentBuffer, cancellationSignal)];
98
99
  case 4:
99
100
  _b.sent();
100
- return [4 /*yield*/, segmentBuffer.pushChunk(dataInfos, cancellationSignal)];
101
+ return [4 /*yield*/, sleep(200)];
101
102
  case 5:
102
103
  _b.sent();
103
- return [3 /*break*/, 7];
104
+ if (cancellationSignal.cancellationError !== null) {
105
+ throw cancellationSignal.cancellationError;
106
+ }
107
+ return [4 /*yield*/, segmentBuffer.pushChunk(dataInfos, cancellationSignal)];
104
108
  case 6:
109
+ _b.sent();
110
+ return [3 /*break*/, 8];
111
+ case 7:
105
112
  err2_1 = _b.sent();
106
113
  reason = err2_1 instanceof Error ? err2_1.toString() :
107
114
  "Could not clean the buffer";
108
115
  throw new MediaError("BUFFER_FULL_ERROR", reason, { adaptation: dataInfos.inventoryInfos.adaptation });
109
- case 7: return [3 /*break*/, 8];
110
- case 8: return [2 /*return*/];
116
+ case 8: return [3 /*break*/, 9];
117
+ case 9: return [2 /*return*/];
111
118
  }
112
119
  });
113
120
  });