rx-player 3.27.0-dev.2022032100 → 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 (60) hide show
  1. package/CHANGELOG.md +5 -2
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/compat/eme/custom_media_keys/old_webkit_media_keys.js +15 -11
  4. package/dist/_esm5.processed/compat/eme/custom_media_keys/webkit_media_keys.js +22 -6
  5. package/dist/_esm5.processed/compat/eme/generate_key_request.d.ts +4 -6
  6. package/dist/_esm5.processed/compat/eme/generate_key_request.js +4 -6
  7. package/dist/_esm5.processed/compat/get_start_date.d.ts +30 -0
  8. package/dist/_esm5.processed/compat/get_start_date.js +44 -0
  9. package/dist/_esm5.processed/compat/index.d.ts +2 -1
  10. package/dist/_esm5.processed/compat/index.js +2 -1
  11. package/dist/_esm5.processed/config.d.ts +1 -5
  12. package/dist/_esm5.processed/core/api/public_api.js +25 -25
  13. package/dist/_esm5.processed/core/decrypt/content_decryptor.js +11 -3
  14. package/dist/_esm5.processed/core/decrypt/create_or_load_session.js +1 -1
  15. package/dist/_esm5.processed/core/decrypt/create_session.d.ts +3 -1
  16. package/dist/_esm5.processed/core/decrypt/create_session.js +15 -5
  17. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.d.ts +94 -1
  18. package/dist/_esm5.processed/core/decrypt/utils/loaded_sessions_store.js +237 -96
  19. package/dist/_esm5.processed/core/segment_buffers/garbage_collector.js +4 -1
  20. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +2 -1
  21. package/dist/_esm5.processed/core/stream/period/period_stream.js +9 -3
  22. package/dist/_esm5.processed/core/stream/representation/append_segment_to_buffer.js +4 -3
  23. package/dist/_esm5.processed/core/stream/representation/force_garbage_collection.js +3 -2
  24. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.d.ts +2 -2
  25. package/dist/_esm5.processed/core/stream/representation/get_buffer_status.js +9 -3
  26. package/dist/_esm5.processed/core/stream/representation/get_needed_segments.d.ts +11 -1
  27. package/dist/_esm5.processed/core/stream/representation/get_needed_segments.js +27 -45
  28. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +6 -4
  29. package/dist/_esm5.processed/default_config.d.ts +2 -35
  30. package/dist/_esm5.processed/default_config.js +2 -35
  31. package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +39 -38
  32. package/dist/_esm5.processed/utils/reference.js +0 -2
  33. package/dist/_esm5.processed/utils/task_canceller.d.ts +8 -1
  34. package/dist/_esm5.processed/utils/task_canceller.js +9 -1
  35. package/dist/rx-player.js +927 -587
  36. package/dist/rx-player.min.js +1 -1
  37. package/package.json +1 -1
  38. package/sonar-project.properties +1 -1
  39. package/src/compat/eme/custom_media_keys/old_webkit_media_keys.ts +16 -12
  40. package/src/compat/eme/custom_media_keys/webkit_media_keys.ts +21 -8
  41. package/src/compat/eme/generate_key_request.ts +4 -6
  42. package/src/compat/get_start_date.ts +48 -0
  43. package/src/compat/index.ts +2 -0
  44. package/src/core/api/public_api.ts +23 -27
  45. package/src/core/decrypt/content_decryptor.ts +15 -4
  46. package/src/core/decrypt/create_or_load_session.ts +4 -1
  47. package/src/core/decrypt/create_session.ts +23 -9
  48. package/src/core/decrypt/utils/loaded_sessions_store.ts +254 -102
  49. package/src/core/segment_buffers/garbage_collector.ts +4 -0
  50. package/src/core/stream/orchestrator/stream_orchestrator.ts +2 -1
  51. package/src/core/stream/period/period_stream.ts +9 -4
  52. package/src/core/stream/representation/append_segment_to_buffer.ts +17 -13
  53. package/src/core/stream/representation/force_garbage_collection.ts +4 -1
  54. package/src/core/stream/representation/get_buffer_status.ts +21 -13
  55. package/src/core/stream/representation/get_needed_segments.ts +40 -55
  56. package/src/core/stream/representation/representation_stream.ts +6 -4
  57. package/src/default_config.ts +20 -57
  58. package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +41 -44
  59. package/src/utils/reference.ts +0 -2
  60. package/src/utils/task_canceller.ts +16 -1
@@ -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()),
@@ -1057,36 +1057,6 @@ const DEFAULT_CONFIG = {
1057
1057
  */
1058
1058
  EME_MAX_STORED_PERSISTENT_SESSION_INFORMATION: 1000,
1059
1059
 
1060
- /**
1061
- * Attempts to closing a MediaKeySession can fail, most likely because the
1062
- * MediaKeySession was not initialized yet.
1063
- * When we consider that we're in one of these case, we will retry to close it.
1064
- *
1065
- * To avoid going into an infinite loop of retry, this number indicates a
1066
- * maximum number of attemps we're going to make (`0` meaning no retry at all,
1067
- * `1` only one retry and so on).
1068
- */
1069
- EME_SESSION_CLOSING_MAX_RETRY: 5,
1070
-
1071
- /**
1072
- * When closing a MediaKeySession failed due to the reasons explained for the
1073
- * `EME_SESSION_CLOSING_MAX_RETRY` config property, we may (among other
1074
- * triggers) choose to wait a delay raising exponentially at each retry before
1075
- * that new attempt.
1076
- * This value indicates the initial value for this delay, in milliseconds.
1077
- */
1078
- EME_SESSION_CLOSING_INITIAL_DELAY: 100,
1079
-
1080
- /**
1081
- * When closing a MediaKeySession failed due to the reasons explained for the
1082
- * `EME_SESSION_CLOSING_MAX_RETRY` config property, we may (among other
1083
- * triggers) choose to wait a delay raising exponentially at each retry before
1084
- * that new attempt.
1085
- * This value indicates the maximum possible value for this delay, in
1086
- * milliseconds.
1087
- */
1088
- EME_SESSION_CLOSING_MAX_DELAY: 1000,
1089
-
1090
1060
  /**
1091
1061
  * After loading a persistent MediaKeySession, the RxPlayer needs to ensure
1092
1062
  * that its keys still allow to decrypt a content.
@@ -1194,40 +1164,33 @@ const DEFAULT_CONFIG = {
1194
1164
  */
1195
1165
  DEFAULT_MAXIMUM_TIME_ROUNDING_ERROR: 1 / 1000,
1196
1166
 
1197
- /**
1198
- * RxPlayer's media buffers have a linked history registering recent events
1199
- * that happened on those.
1200
- * The reason is to implement various heuristics in case of weird browser
1201
- * behavior.
1202
- *
1203
- * The `BUFFERED_HISTORY_RETENTION_TIME` is the minimum age an entry of
1204
- * that history can have before being removed from the history.
1205
- */
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
+ */
1206
1176
  BUFFERED_HISTORY_RETENTION_TIME: 60000,
1207
1177
 
1208
- /**
1209
- * RxPlayer's media buffers have a linked history registering recent events
1210
- * that happened on those.
1211
- * The reason is to implement various heuristics in case of weird browser
1212
- * behavior.
1213
- *
1214
- * The `BUFFERED_HISTORY_RETENTION_TIME` is the maximum number of entries
1215
- * there can be in that history.
1216
- */
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
+ */
1217
1187
  BUFFERED_HISTORY_MAXIMUM_ENTRIES: 200,
1218
1188
 
1219
- /**
1220
- * Minimum buffer (in seconds) we should have, regardless of memory
1221
- * constraints
1222
- */
1223
- MIN_BUFFER_LENGTH : 5,
1224
-
1225
1189
  /**
1226
1190
  * Minimum buffer in seconds ahead relative to current time
1227
- * we should be able to download
1228
- * Before trying to agressively free up memory
1191
+ * we should be able to download, even in cases of saturated memory.
1229
1192
  */
1230
- MIN_BUFFER_DISTANCE_BEFORE_CLEAN_UP: 10,
1193
+ MIN_BUFFER_AHEAD: 5,
1231
1194
 
1232
1195
  /**
1233
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
  }
@@ -18,7 +18,6 @@ import {
18
18
  Observable,
19
19
  Subscriber,
20
20
  } from "rxjs";
21
- import log from "../log";
22
21
  import { CancellationSignal } from "./task_canceller";
23
22
 
24
23
  /**
@@ -194,7 +193,6 @@ export function createSharedReference<T>(initialValue : T) : ISharedReference<T>
194
193
  if (__ENVIRONMENT__.CURRENT_ENV === __ENVIRONMENT__.DEV as number) {
195
194
  throw new Error("Finished shared references cannot be updated");
196
195
  } else {
197
- log.error("Finished shared references cannot be updated");
198
196
  return;
199
197
  }
200
198
  }
@@ -136,14 +136,29 @@ export default class TaskCanceller {
136
136
  * Creates a new `TaskCanceller`, with its own `CancellationSignal` created
137
137
  * as its `signal` provide.
138
138
  * You can then pass this property to async task you wish to be cancellable.
139
+ * @param {Object|undefined} options
139
140
  */
140
- constructor() {
141
+ constructor(options? : {
142
+ /**
143
+ * If set the TaskCanceller created here will automatically be triggered
144
+ * when that signal emits.
145
+ */
146
+ cancelOn? : CancellationSignal | undefined;
147
+ } | undefined) {
141
148
  const [trigger, register] = createCancellationFunctions();
142
149
  this.isUsed = false;
143
150
  this._trigger = trigger;
144
151
  this.signal = new CancellationSignal(register);
152
+
153
+ if (options?.cancelOn !== undefined) {
154
+ const unregisterParent = options.cancelOn.register(() => {
155
+ this.cancel();
156
+ });
157
+ this.signal.register(unregisterParent);
158
+ }
145
159
  }
146
160
 
161
+
147
162
  /**
148
163
  * "Trigger" the `TaskCanceller`, notify through its associated
149
164
  * `CancellationSignal` (its `signal` property) that a task should be aborted.