rx-player 3.30.0-dev.2023020100 → 3.30.0-dev.2023022200

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 (167) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/VERSION +1 -1
  3. package/dist/_esm5.processed/compat/eme/close_session.js +2 -2
  4. package/dist/_esm5.processed/compat/event_listeners.js +1 -1
  5. package/dist/_esm5.processed/core/adaptive/adaptive_representation_selector.js +4 -2
  6. package/dist/_esm5.processed/core/api/debug/buffer_graph.d.ts +28 -0
  7. package/dist/_esm5.processed/core/api/debug/buffer_graph.js +175 -0
  8. package/dist/_esm5.processed/core/api/debug/buffer_size_graph.d.ts +10 -0
  9. package/dist/_esm5.processed/core/api/debug/buffer_size_graph.js +105 -0
  10. package/dist/_esm5.processed/core/api/debug/constants.d.ts +2 -0
  11. package/dist/_esm5.processed/core/api/debug/constants.js +2 -0
  12. package/dist/_esm5.processed/core/api/debug/index.d.ts +2 -0
  13. package/dist/_esm5.processed/core/api/debug/index.js +2 -0
  14. package/dist/_esm5.processed/core/api/debug/modules/general_info.d.ts +3 -0
  15. package/dist/_esm5.processed/core/api/debug/modules/general_info.js +199 -0
  16. package/dist/_esm5.processed/core/api/debug/modules/segment_buffer_content.d.ts +4 -0
  17. package/dist/_esm5.processed/core/api/debug/modules/segment_buffer_content.js +121 -0
  18. package/dist/_esm5.processed/core/api/debug/modules/segment_buffer_size.d.ts +3 -0
  19. package/dist/_esm5.processed/core/api/debug/modules/segment_buffer_size.js +35 -0
  20. package/dist/_esm5.processed/core/api/debug/render.d.ts +3 -0
  21. package/dist/_esm5.processed/core/api/debug/render.js +32 -0
  22. package/dist/_esm5.processed/core/api/debug/utils.d.ts +39 -0
  23. package/dist/_esm5.processed/core/api/debug/utils.js +57 -0
  24. package/dist/_esm5.processed/core/api/playback_observer.js +3 -2
  25. package/dist/_esm5.processed/core/api/public_api.d.ts +3 -0
  26. package/dist/_esm5.processed/core/api/public_api.js +24 -13
  27. package/dist/_esm5.processed/core/api/utils.js +3 -3
  28. package/dist/_esm5.processed/core/decrypt/__tests__/__global__/utils.d.ts +5 -5
  29. package/dist/_esm5.processed/core/decrypt/attach_media_keys.js +1 -1
  30. package/dist/_esm5.processed/core/decrypt/content_decryptor.js +1 -1
  31. package/dist/_esm5.processed/core/decrypt/session_events_listener.js +37 -47
  32. package/dist/_esm5.processed/core/decrypt/utils/check_key_statuses.js +4 -0
  33. package/dist/_esm5.processed/core/fetchers/cdn_prioritizer.d.ts +17 -8
  34. package/dist/_esm5.processed/core/fetchers/cdn_prioritizer.js +10 -6
  35. package/dist/_esm5.processed/core/fetchers/manifest/manifest_fetcher.js +5 -4
  36. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +22 -5
  37. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +37 -21
  38. package/dist/_esm5.processed/core/fetchers/segment/task_prioritizer.js +21 -23
  39. package/dist/_esm5.processed/core/fetchers/utils/schedule_request.js +6 -5
  40. package/dist/_esm5.processed/core/init/directfile_content_initializer.js +1 -1
  41. package/dist/_esm5.processed/core/init/media_source_content_initializer.js +27 -31
  42. package/dist/_esm5.processed/core/init/utils/content_time_boundaries_observer.js +2 -2
  43. package/dist/_esm5.processed/core/init/utils/create_media_source.js +3 -12
  44. package/dist/_esm5.processed/core/init/utils/end_of_stream.js +6 -3
  45. package/dist/_esm5.processed/core/init/utils/get_loaded_reference.js +2 -1
  46. package/dist/_esm5.processed/core/init/utils/initial_seek_and_play.js +9 -5
  47. package/dist/_esm5.processed/core/init/utils/initialize_content_decryption.js +2 -1
  48. package/dist/_esm5.processed/core/init/utils/media_duration_updater.js +23 -19
  49. package/dist/_esm5.processed/core/init/utils/stream_events_emitter/stream_events_emitter.js +6 -4
  50. package/dist/_esm5.processed/core/init/utils/throw_on_media_error.js +1 -1
  51. package/dist/_esm5.processed/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.js +7 -10
  52. package/dist/_esm5.processed/core/segment_buffers/implementations/text/html/html_text_segment_buffer.js +4 -2
  53. package/dist/_esm5.processed/core/segment_buffers/segment_buffers_store.js +13 -9
  54. package/dist/_esm5.processed/core/stream/adaptation/adaptation_stream.js +10 -11
  55. package/dist/_esm5.processed/core/stream/orchestrator/stream_orchestrator.js +26 -12
  56. package/dist/_esm5.processed/core/stream/period/period_stream.js +11 -10
  57. package/dist/_esm5.processed/core/stream/representation/representation_stream.js +15 -15
  58. package/dist/_esm5.processed/core/stream/representation/utils/append_segment_to_buffer.js +1 -1
  59. package/dist/_esm5.processed/core/stream/representation/utils/downloading_queue.js +16 -6
  60. package/dist/_esm5.processed/experimental/features/debug_element.d.ts +8 -0
  61. package/dist/_esm5.processed/experimental/features/debug_element.js +10 -0
  62. package/dist/_esm5.processed/experimental/features/index.d.ts +1 -0
  63. package/dist/_esm5.processed/experimental/features/index.js +1 -0
  64. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.js +7 -4
  65. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +5 -4
  66. package/dist/_esm5.processed/features/features_object.js +1 -0
  67. package/dist/_esm5.processed/features/initialize_features.js +13 -10
  68. package/dist/_esm5.processed/features/types.d.ts +3 -0
  69. package/dist/_esm5.processed/manifest/adaptation.js +4 -0
  70. package/dist/_esm5.processed/manifest/manifest.js +2 -0
  71. package/dist/_esm5.processed/manifest/representation.js +11 -4
  72. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_adaptation_sets.js +105 -137
  73. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representations.js +25 -5
  74. package/dist/_esm5.processed/public_types.d.ts +1 -0
  75. package/dist/_esm5.processed/transports/dash/add_segment_integrity_checks_to_loader.js +13 -12
  76. package/dist/_esm5.processed/transports/dash/low_latency_segment_loader.js +2 -2
  77. package/dist/_esm5.processed/transports/dash/manifest_parser.js +1 -1
  78. package/dist/_esm5.processed/transports/dash/segment_loader.js +4 -4
  79. package/dist/_esm5.processed/transports/local/segment_loader.js +13 -26
  80. package/dist/_esm5.processed/transports/smooth/segment_loader.js +4 -4
  81. package/dist/_esm5.processed/transports/utils/call_custom_manifest_loader.js +3 -3
  82. package/dist/_esm5.processed/utils/cancellable_sleep.js +4 -10
  83. package/dist/_esm5.processed/utils/create_cancellable_promise.d.ts +26 -0
  84. package/dist/_esm5.processed/utils/create_cancellable_promise.js +52 -0
  85. package/dist/_esm5.processed/utils/reference.js +6 -0
  86. package/dist/_esm5.processed/utils/request/xhr.js +1 -1
  87. package/dist/_esm5.processed/utils/task_canceller.d.ts +34 -15
  88. package/dist/_esm5.processed/utils/task_canceller.js +55 -22
  89. package/dist/rx-player.js +739 -626
  90. package/dist/rx-player.min.js +1 -1
  91. package/jest.config.js +1 -0
  92. package/package.json +18 -18
  93. package/scripts/build/constants.d.ts +1 -0
  94. package/scripts/report_build_sizes +4 -0
  95. package/sonar-project.properties +1 -1
  96. package/src/compat/eme/close_session.ts +2 -2
  97. package/src/compat/event_listeners.ts +1 -1
  98. package/src/core/adaptive/adaptive_representation_selector.ts +4 -2
  99. package/src/core/api/debug/buffer_graph.ts +247 -0
  100. package/src/core/api/debug/buffer_size_graph.ts +131 -0
  101. package/src/core/api/debug/constants.ts +2 -0
  102. package/src/core/api/debug/index.ts +3 -0
  103. package/src/core/api/debug/modules/general_info.ts +204 -0
  104. package/src/core/api/debug/modules/segment_buffer_content.ts +155 -0
  105. package/src/core/api/debug/modules/segment_buffer_size.ts +48 -0
  106. package/src/core/api/debug/render.ts +40 -0
  107. package/src/core/api/debug/utils.ts +103 -0
  108. package/src/core/api/playback_observer.ts +4 -2
  109. package/src/core/api/public_api.ts +27 -13
  110. package/src/core/api/utils.ts +3 -3
  111. package/src/core/decrypt/attach_media_keys.ts +1 -1
  112. package/src/core/decrypt/content_decryptor.ts +1 -1
  113. package/src/core/decrypt/session_events_listener.ts +37 -49
  114. package/src/core/decrypt/utils/check_key_statuses.ts +6 -0
  115. package/src/core/fetchers/cdn_prioritizer.ts +18 -9
  116. package/src/core/fetchers/manifest/manifest_fetcher.ts +5 -4
  117. package/src/core/fetchers/segment/segment_fetcher.ts +36 -14
  118. package/src/core/fetchers/segment/task_prioritizer.ts +25 -30
  119. package/src/core/fetchers/utils/schedule_request.ts +6 -5
  120. package/src/core/init/directfile_content_initializer.ts +1 -1
  121. package/src/core/init/media_source_content_initializer.ts +37 -41
  122. package/src/core/init/utils/content_time_boundaries_observer.ts +2 -2
  123. package/src/core/init/utils/create_media_source.ts +4 -16
  124. package/src/core/init/utils/end_of_stream.ts +6 -3
  125. package/src/core/init/utils/get_loaded_reference.ts +2 -1
  126. package/src/core/init/utils/initial_seek_and_play.ts +9 -5
  127. package/src/core/init/utils/initialize_content_decryption.ts +2 -1
  128. package/src/core/init/utils/media_duration_updater.ts +25 -20
  129. package/src/core/init/utils/stream_events_emitter/stream_events_emitter.ts +6 -4
  130. package/src/core/init/utils/throw_on_media_error.ts +1 -1
  131. package/src/core/segment_buffers/implementations/audio_video/audio_video_segment_buffer.ts +7 -11
  132. package/src/core/segment_buffers/implementations/text/html/html_text_segment_buffer.ts +4 -2
  133. package/src/core/segment_buffers/segment_buffers_store.ts +16 -13
  134. package/src/core/stream/adaptation/adaptation_stream.ts +10 -11
  135. package/src/core/stream/orchestrator/stream_orchestrator.ts +26 -12
  136. package/src/core/stream/period/period_stream.ts +11 -10
  137. package/src/core/stream/representation/representation_stream.ts +15 -15
  138. package/src/core/stream/representation/utils/append_segment_to_buffer.ts +1 -1
  139. package/src/core/stream/representation/utils/downloading_queue.ts +16 -4
  140. package/src/experimental/features/__tests__/debug_element.test.ts +26 -0
  141. package/src/experimental/features/debug_element.ts +13 -0
  142. package/src/experimental/features/index.ts +1 -0
  143. package/src/experimental/tools/VideoThumbnailLoader/prepare_source_buffer.ts +7 -4
  144. package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +5 -4
  145. package/src/features/__tests__/initialize_features.test.ts +11 -0
  146. package/src/features/features_object.ts +1 -0
  147. package/src/features/initialize_features.ts +15 -10
  148. package/src/features/types.ts +9 -0
  149. package/src/manifest/adaptation.ts +7 -0
  150. package/src/manifest/manifest.ts +4 -0
  151. package/src/manifest/representation.ts +10 -4
  152. package/src/parsers/manifest/dash/common/parse_adaptation_sets.ts +116 -151
  153. package/src/parsers/manifest/dash/common/parse_representations.ts +21 -4
  154. package/src/public_types.ts +1 -0
  155. package/src/transports/dash/add_segment_integrity_checks_to_loader.ts +30 -24
  156. package/src/transports/dash/low_latency_segment_loader.ts +2 -2
  157. package/src/transports/dash/manifest_parser.ts +1 -1
  158. package/src/transports/dash/segment_loader.ts +4 -4
  159. package/src/transports/local/segment_loader.ts +14 -30
  160. package/src/transports/smooth/segment_loader.ts +4 -4
  161. package/src/transports/utils/call_custom_manifest_loader.ts +3 -3
  162. package/src/typings/globals.d.ts +2 -0
  163. package/src/utils/cancellable_sleep.ts +5 -14
  164. package/src/utils/create_cancellable_promise.ts +69 -0
  165. package/src/utils/reference.ts +6 -0
  166. package/src/utils/request/xhr.ts +1 -1
  167. package/src/utils/task_canceller.ts +63 -34
@@ -48,14 +48,14 @@ export function emitSeekEvents(
48
48
  onSeeked: () => void,
49
49
  cancelSignal : CancellationSignal
50
50
  ) : void {
51
- if (cancelSignal.isCancelled || mediaElement === null) {
51
+ if (cancelSignal.isCancelled() || mediaElement === null) {
52
52
  return ;
53
53
  }
54
54
 
55
55
  let wasSeeking = playbackObserver.getReference().getValue().seeking;
56
56
  if (wasSeeking) {
57
57
  onSeeking();
58
- if (cancelSignal.isCancelled) {
58
+ if (cancelSignal.isCancelled()) {
59
59
  return;
60
60
  }
61
61
  }
@@ -94,7 +94,7 @@ export function constructPlayerStateReference(
94
94
  initializer.addEventListener("loaded", () => {
95
95
  if (playerStateRef.getValue() === PLAYER_STATES.LOADING) {
96
96
  playerStateRef.setValue(PLAYER_STATES.LOADED);
97
- if (!cancelSignal.isCancelled) {
97
+ if (!cancelSignal.isCancelled()) {
98
98
  const newState = getLoadedContentState(mediaElement, null);
99
99
  if (newState !== PLAYER_STATES.PAUSED) {
100
100
  playerStateRef.setValue(newState);
@@ -62,7 +62,7 @@ export default async function attachMediaKeys(
62
62
 
63
63
  // If this task has been cancelled while we were closing previous sessions,
64
64
  // stop now (and thus avoid setting the new media keys);
65
- if (cancelSignal.isCancelled) {
65
+ if (cancelSignal.isCancelled()) {
66
66
  throw cancelSignal.cancellationError;
67
67
  }
68
68
 
@@ -727,7 +727,7 @@ export default class ContentDecryptor extends EventEmitter<IContentDecryptorEven
727
727
  * formatted and sent in an "error" event.
728
728
  */
729
729
  private _onFatalError(err : unknown) : void {
730
- if (this._canceller.isUsed) {
730
+ if (this._canceller.isUsed()) {
731
731
  return;
732
732
  }
733
733
  const formattedErr = err instanceof Error ?
@@ -58,13 +58,14 @@ export default function SessionEventsListener(
58
58
  const { getLicenseConfig = {} } = keySystemOptions;
59
59
 
60
60
  /** Allows to manually cancel everything the `SessionEventsListener` is doing. */
61
- const manualCanceller = new TaskCanceller({ cancelOn: cancelSignal });
61
+ const manualCanceller = new TaskCanceller();
62
+ manualCanceller.linkToSignal(cancelSignal);
62
63
 
63
64
  if (!isNullOrUndefined(session.closed)) {
64
65
  session.closed
65
66
  .then(() => manualCanceller.cancel())
66
67
  .catch((err) => { // Should never happen
67
- if (cancelSignal.isCancelled) {
68
+ if (cancelSignal.isCancelled()) {
68
69
  return;
69
70
  }
70
71
  manualCanceller.cancel();
@@ -79,8 +80,8 @@ export default function SessionEventsListener(
79
80
 
80
81
  onKeyStatusesChange(session, (keyStatusesEvent) => {
81
82
  handleKeyStatusesChangeEvent(keyStatusesEvent as Event).catch(error => {
82
- if (cancelSignal.isCancelled ||
83
- (manualCanceller.isUsed && error instanceof CancellationSignal))
83
+ if (cancelSignal.isCancelled() ||
84
+ (manualCanceller.isUsed() && error instanceof CancellationSignal))
84
85
  {
85
86
  return;
86
87
  }
@@ -104,7 +105,7 @@ export default function SessionEventsListener(
104
105
  backoffOptions,
105
106
  manualCanceller.signal)
106
107
  .then((licenseObject) => {
107
- if (manualCanceller.isUsed) {
108
+ if (manualCanceller.isUsed()) {
108
109
  return Promise.resolve();
109
110
  }
110
111
  if (isNullOrUndefined(licenseObject)) {
@@ -114,7 +115,7 @@ export default function SessionEventsListener(
114
115
  }
115
116
  })
116
117
  .catch((err : unknown) => {
117
- if (manualCanceller.isUsed) {
118
+ if (manualCanceller.isUsed()) {
118
119
  return;
119
120
  }
120
121
  manualCanceller.cancel();
@@ -133,6 +134,8 @@ export default function SessionEventsListener(
133
134
  });
134
135
  }, manualCanceller.signal);
135
136
 
137
+ checkAndHandleCurrentKeyStatuses();
138
+ return;
136
139
  /**
137
140
  * @param {Event} keyStatusesEvent
138
141
  * @returns {Promise}
@@ -144,22 +147,22 @@ export default function SessionEventsListener(
144
147
 
145
148
  await Promise.all([
146
149
  runOnKeyStatusesChangeCallback(),
147
- Promise.resolve(handleKeyStatusEvent()),
150
+ Promise.resolve(checkAndHandleCurrentKeyStatuses()),
148
151
  ]);
149
152
 
150
153
  async function runOnKeyStatusesChangeCallback() {
151
- if (manualCanceller.isUsed) {
154
+ if (manualCanceller.isUsed()) {
152
155
  return;
153
156
  }
154
157
  if (typeof keySystemOptions.onKeyStatusesChange === "function") {
155
158
  let ret;
156
159
  try {
157
160
  ret = await keySystemOptions.onKeyStatusesChange(keyStatusesEvent, session);
158
- if (manualCanceller.isUsed) {
161
+ if (manualCanceller.isUsed()) {
159
162
  return;
160
163
  }
161
164
  } catch (error) {
162
- if (cancelSignal.isCancelled) {
165
+ if (cancelSignal.isCancelled()) {
163
166
  return;
164
167
  }
165
168
  const err = new EncryptedMediaError("KEY_STATUS_CHANGE_ERROR",
@@ -178,41 +181,41 @@ export default function SessionEventsListener(
178
181
  }
179
182
  }
180
183
  }
184
+ }
181
185
 
182
- /**
183
- * Check current MediaKeyStatus for each key in the given MediaKeySession and:
184
- * - throw if at least one status is a non-recoverable error
185
- * - call warning callback for recoverable errors
186
- * - call onKeyUpdate callback when the MediaKeyStatus of any key is updated
187
- */
188
- function handleKeyStatusEvent() : void {
189
- if (manualCanceller.isUsed || session.keyStatuses.size === 0) {
190
- return ;
191
- }
192
- const { warning, blacklistedKeyIds, whitelistedKeyIds } =
193
- checkKeyStatuses(session, keySystemOptions, keySystem);
194
- if (warning !== undefined) {
195
- callbacks.onWarning(warning);
196
- if (manualCanceller.isUsed) {
197
- return;
198
- }
186
+ /**
187
+ * Check current MediaKeyStatus for each key in the given MediaKeySession and:
188
+ * - throw if at least one status is a non-recoverable error
189
+ * - call warning callback for recoverable errors
190
+ * - call onKeyUpdate callback when the MediaKeyStatus of any key is updated
191
+ */
192
+ function checkAndHandleCurrentKeyStatuses() : void {
193
+ if (manualCanceller.isUsed() || session.keyStatuses.size === 0) {
194
+ return ;
195
+ }
196
+ const { warning, blacklistedKeyIds, whitelistedKeyIds } =
197
+ checkKeyStatuses(session, keySystemOptions, keySystem);
198
+ if (warning !== undefined) {
199
+ callbacks.onWarning(warning);
200
+ if (manualCanceller.isUsed()) {
201
+ return;
199
202
  }
200
- callbacks.onKeyUpdate({ whitelistedKeyIds, blacklistedKeyIds });
201
203
  }
204
+ callbacks.onKeyUpdate({ whitelistedKeyIds, blacklistedKeyIds });
202
205
  }
203
206
 
204
207
  function runGetLicense(
205
208
  message : Uint8Array,
206
209
  messageType : MediaKeyMessageType
207
210
  ) : Promise<BufferSource | null> {
208
- return new Promise((res, rej) => {
211
+ let timeoutId : number | undefined;
212
+ return new Promise<BufferSource | null>((res, rej) => {
209
213
  log.debug("DRM: Calling `getLicense`", messageType);
210
214
  const getLicense = keySystemOptions.getLicense(message, messageType);
211
215
  const getLicenseTimeout = isNullOrUndefined(getLicenseConfig.timeout) ?
212
216
  10 * 1000 :
213
217
  getLicenseConfig.timeout;
214
218
 
215
- let timeoutId : number | undefined;
216
219
  if (getLicenseTimeout >= 0) {
217
220
  timeoutId = setTimeout(() => {
218
221
  rej(new GetLicenseTimeoutError(
@@ -220,25 +223,10 @@ export default function SessionEventsListener(
220
223
  ));
221
224
  }, getLicenseTimeout) as unknown as number;
222
225
  }
223
-
224
- try {
225
- Promise.resolve(getLicense).then(
226
- (val) => {
227
- clearTimeoutIfOne();
228
- res(val);
229
- },
230
- (err) => {
231
- clearTimeoutIfOne();
232
- rej(err);
233
- });
234
- } catch (err) {
235
- clearTimeoutIfOne();
236
- rej(err);
237
- }
238
- function clearTimeoutIfOne() {
239
- if (timeoutId !== undefined) {
240
- clearTimeout(timeoutId);
241
- }
226
+ Promise.resolve(getLicense).then(res, rej);
227
+ }).finally(() => {
228
+ if (timeoutId !== undefined) {
229
+ clearTimeout(timeoutId);
242
230
  }
243
231
  });
244
232
  }
@@ -18,6 +18,7 @@ import { ICustomMediaKeySession } from "../../../compat";
18
18
  /* eslint-disable-next-line max-len */
19
19
  import getUUIDKidFromKeyStatusKID from "../../../compat/eme/get_uuid_kid_from_keystatus_kid";
20
20
  import { EncryptedMediaError } from "../../../errors";
21
+ import log from "../../../log";
21
22
  import {
22
23
  IEncryptedMediaErrorKeyStatusObject,
23
24
  IKeySystemOption,
@@ -109,6 +110,11 @@ export default function checkKeyStatuses(
109
110
  new Uint8Array(keyStatusKeyId));
110
111
 
111
112
  const keyStatusObj = { keyId: keyId.buffer, keyStatus };
113
+
114
+ if (log.hasLevel("DEBUG")) {
115
+ log.debug(`DRM: key status update (${bytesToHex(keyId)}): ${keyStatus}`);
116
+ }
117
+
112
118
  switch (keyStatus) {
113
119
  case KEY_STATUSES.EXPIRED: {
114
120
  const error = new EncryptedMediaError(
@@ -16,19 +16,22 @@
16
16
 
17
17
  import config from "../../config";
18
18
  import { ICdnMetadata } from "../../parsers/manifest";
19
- import { IPlayerError } from "../../public_types";
20
19
  import arrayFindIndex from "../../utils/array_find_index";
21
20
  import EventEmitter from "../../utils/event_emitter";
22
21
  import { CancellationSignal } from "../../utils/task_canceller";
23
22
 
24
23
  /**
25
- * Class signaling the priority between multiple CDN available for any given
26
- * resource.
24
+ * Class storing and signaling the priority between multiple CDN available for
25
+ * any given resource.
27
26
  *
28
- * This class might perform requests and schedule timeouts by itself to keep its
29
- * internal list of CDN priority up-to-date.
30
- * When it is not needed anymore, you should call the `dispose` method to clear
31
- * all resources.
27
+ * This class was first created to implement the complexities behind
28
+ * Content Steering features, though its handling hasn't been added yet as we
29
+ * wait for its specification to be both standardized and relied on in the wild.
30
+ * In the meantime, it acts as an abstraction for the simple concept of
31
+ * avoiding to request a CDN for any segment when an issue is encountered with
32
+ * one (e.g. HTTP 500 statuses) and several CDN exist for a given resource. It
33
+ * should be noted that this is also one of the planified features of the
34
+ * Content Steering specification.
32
35
  *
33
36
  * @class CdnPrioritizer
34
37
  */
@@ -41,7 +44,7 @@ export default class CdnPrioritizer extends EventEmitter<ICdnPrioritizerEvents>
41
44
  private _downgradedCdnList : {
42
45
  /**
43
46
  * Metadata of downgraded CDN, sorted by the time at which they have
44
- * been downgraded.
47
+ * been downgraded ascending.
45
48
  */
46
49
  metadata : ICdnMetadata[];
47
50
  /**
@@ -174,8 +177,14 @@ export default class CdnPrioritizer extends EventEmitter<ICdnPrioritizerEvents>
174
177
  }
175
178
  }
176
179
 
180
+ /** Events sent by a `CdnPrioritizer` */
177
181
  export interface ICdnPrioritizerEvents {
178
- warnings : IPlayerError[];
182
+ /**
183
+ * The priority of one or several CDN changed.
184
+ *
185
+ * You might want to re-check if a CDN should still be used when this event
186
+ * is triggered.
187
+ */
179
188
  priorityChange : null;
180
189
  }
181
190
 
@@ -155,7 +155,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
155
155
  manifestProm
156
156
  .then((val : IManifestFetcherParsedResult) => {
157
157
  this.trigger("manifestReady", val.manifest);
158
- if (!this._canceller.isUsed) {
158
+ if (!this._canceller.isUsed()) {
159
159
  this._recursivelyRefreshManifest(val.manifest, val);
160
160
  }
161
161
  })
@@ -362,7 +362,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
362
362
  */
363
363
  function onWarnings(warnings : Error[]) : void {
364
364
  for (const warning of warnings) {
365
- if (cancelSignal.isCancelled) {
365
+ if (cancelSignal.isCancelled()) {
366
366
  return;
367
367
  }
368
368
  const formattedError = formatError(warning, {
@@ -470,7 +470,8 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
470
470
  * be effectively considered.
471
471
  * `nextRefreshCanceller` will allow to cancel every other when one is triggered.
472
472
  */
473
- const nextRefreshCanceller = new TaskCanceller({ cancelOn: this._canceller.signal });
473
+ const nextRefreshCanceller = new TaskCanceller();
474
+ nextRefreshCanceller.linkToSignal(this._canceller.signal);
474
475
 
475
476
  /* Function to manually schedule a Manifest refresh */
476
477
  this.scheduleManualRefresh = (settings : IManifestRefreshSettings) => {
@@ -669,7 +670,7 @@ export default class ManifestFetcher extends EventEmitter<IManifestFetcherEvent>
669
670
  }
670
671
 
671
672
  private _onFatalError(err : unknown) : void {
672
- if (this._canceller.isUsed) {
673
+ if (this._canceller.isUsed()) {
673
674
  return;
674
675
  }
675
676
  this.trigger("error", err);
@@ -64,10 +64,22 @@ const generateRequestID = idGenerator();
64
64
  * An `ISegmentFetcher` also implements a retry mechanism, based on the given
65
65
  * `options` argument, which may retry a segment request when it fails.
66
66
  *
67
- * @param {string} bufferType
68
- * @param {Object} pipeline
69
- * @param {Object} lifecycleCallbacks
70
- * @param {Object} options
67
+ * @param {string} bufferType - Type of buffer concerned (e.g. `"audio"`,
68
+ * `"video"`, `"text" etc.)
69
+ * @param {Object} pipeline - The transport-specific logic allowing to load
70
+ * segments of the given buffer type and transport protocol (e.g. DASH).
71
+ * @param {Object|null} cdnPrioritizer - Abstraction allowing to synchronize,
72
+ * update and keep track of the priorization of the CDN to use to load any given
73
+ * segment, in cases where multiple ones are available.
74
+ *
75
+ * Can be set to `null` in which case a minimal priorization logic will be used
76
+ * instead.
77
+ * @param {Object} lifecycleCallbacks - Callbacks that can be registered to be
78
+ * informed when new requests are made, ended, new metrics are available etc.
79
+ * This should be mainly useful to implement an adaptive logic relying on those
80
+ * metrics and events.
81
+ * @param {Object} options - Various tweaking options allowing to configure the
82
+ * behavior of the returned `ISegmentFetcher`.
71
83
  * @returns {Function}
72
84
  */
73
85
  export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
@@ -184,14 +196,7 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
184
196
  id: requestId,
185
197
  content });
186
198
 
187
- cancellationSignal.register(() => {
188
- if (requestInfo !== undefined) {
189
- return; // Request already terminated
190
- }
191
- log.debug("SF: Segment request cancelled", segmentIdString);
192
- requestInfo = null;
193
- lifecycleCallbacks.onRequestEnd?.({ id: requestId });
194
- });
199
+ cancellationSignal.register(onCancellation);
195
200
 
196
201
  try {
197
202
  const res = await scheduleRequestWithCdns(content.representation.cdnMetadata,
@@ -220,22 +225,34 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
220
225
  requestInfo = null;
221
226
  }
222
227
 
223
- if (!cancellationSignal.isCancelled) {
228
+ if (!cancellationSignal.isCancelled()) {
224
229
  // The current task could have been canceled as a result of one
225
230
  // of the previous callbacks call. In that case, we don't want to send
226
231
  // a "requestEnd" again as it has already been sent on cancellation.
227
232
  lifecycleCallbacks.onRequestEnd?.({ id: requestId });
228
233
  }
234
+ cancellationSignal.deregister(onCancellation);
229
235
  } catch (err) {
236
+ cancellationSignal.deregister(onCancellation);
230
237
  requestInfo = null;
231
238
  if (err instanceof CancellationError) {
232
239
  log.debug("SF: Segment request aborted", segmentIdString);
233
240
  throw err;
234
241
  }
235
242
  log.debug("SF: Segment request failed", segmentIdString);
243
+ lifecycleCallbacks.onRequestEnd?.({ id: requestId });
236
244
  throw errorSelector(err);
237
245
  }
238
246
 
247
+ function onCancellation() {
248
+ if (requestInfo !== undefined) {
249
+ return; // Request already terminated
250
+ }
251
+ log.debug("SF: Segment request cancelled", segmentIdString);
252
+ requestInfo = null;
253
+ lifecycleCallbacks.onRequestEnd?.({ id: requestId });
254
+ }
255
+
239
256
  /**
240
257
  * Call a segment loader for the given URL with the right arguments.
241
258
  * @param {Object|null} cdnMetadata
@@ -360,11 +377,16 @@ export interface ISegmentFetcherCallbacks<TSegmentDataType> {
360
377
  /**
361
378
  * Callback called when all decodable chunks of the loaded segment have been
362
379
  * communicated through the `onChunk` callback.
380
+ *
381
+ * This callback is called before the corresponding `ISegmentFetcher`'s
382
+ * returned Promise is resolved.
363
383
  */
364
384
  onAllChunksReceived() : void;
365
385
 
366
386
  /**
367
- * Callback called when the segment request has to restart from scratch. */
387
+ * Callback called when the segment request has to restart from scratch, e.g.
388
+ * due to a request error.
389
+ */
368
390
  onRetry(error : IPlayerError) : void;
369
391
  }
370
392
 
@@ -1,5 +1,6 @@
1
1
  import log from "../../../log";
2
2
  import arrayFindIndex from "../../../utils/array_find_index";
3
+ import createCancellablePromise from "../../../utils/create_cancellable_promise";
3
4
  import TaskCanceller, {
4
5
  CancellationError,
5
6
  CancellationSignal,
@@ -64,19 +65,34 @@ export default class TaskPrioritizer<T> {
64
65
  cancelSignal: CancellationSignal
65
66
  ): Promise<T> {
66
67
  let newTask: IPrioritizerTask<T>;
67
-
68
- return new Promise<T>((resolve, reject) => {
68
+ return createCancellablePromise<T>(cancelSignal, (resolve, reject) => {
69
69
  /** Function allowing to start the underlying Promise. */
70
70
  const trigger = () => {
71
71
  if (newTask.hasEnded) {
72
- unregisterCancelSignal();
73
72
  return;
74
73
  }
75
- const interrupter = new TaskCanceller({ cancelOn: cancelSignal });
74
+ const finishTask = () => {
75
+ unlinkInterrupter();
76
+ this._endTask(newTask);
77
+ };
78
+
79
+ const onResolve = (value: T) => {
80
+ callbacks.beforeEnded();
81
+ finishTask();
82
+ resolve(value);
83
+ };
84
+
85
+ const onReject = (err: unknown) => {
86
+ finishTask();
87
+ reject(err);
88
+ };
89
+
90
+ const interrupter = new TaskCanceller();
91
+ const unlinkInterrupter = interrupter.linkToSignal(cancelSignal);
76
92
  newTask.interrupter = interrupter;
77
93
  interrupter.signal.register(() => {
78
94
  newTask.interrupter = null;
79
- if (!cancelSignal.isCancelled) {
95
+ if (!cancelSignal.isCancelled()) {
80
96
  callbacks.beforeInterrupted();
81
97
  }
82
98
  });
@@ -89,8 +105,8 @@ export default class TaskPrioritizer<T> {
89
105
  newTask.taskFn(interrupter.signal)
90
106
  .then(onResolve)
91
107
  .catch((err) => {
92
- if (!cancelSignal.isCancelled &&
93
- interrupter.isUsed &&
108
+ if (!cancelSignal.isCancelled() &&
109
+ interrupter.isUsed() &&
94
110
  err instanceof CancellationError)
95
111
  {
96
112
  return;
@@ -99,29 +115,6 @@ export default class TaskPrioritizer<T> {
99
115
  });
100
116
  };
101
117
 
102
- const unregisterCancelSignal = cancelSignal.register(
103
- (cancellationError: CancellationError) => {
104
- this._endTask(newTask);
105
- reject(cancellationError);
106
- }
107
- );
108
-
109
- const finishTask = () => {
110
- unregisterCancelSignal();
111
- this._endTask(newTask);
112
- };
113
-
114
- const onResolve = (value: T) => {
115
- callbacks.beforeEnded();
116
- finishTask();
117
- resolve(value);
118
- };
119
-
120
- const onReject = (err: unknown) => {
121
- finishTask();
122
- reject(err);
123
- };
124
-
125
118
  newTask = {
126
119
  hasEnded: false,
127
120
  priority,
@@ -146,6 +139,8 @@ export default class TaskPrioritizer<T> {
146
139
  this._interruptCancellableTasks();
147
140
  }
148
141
  }
142
+
143
+ return () => this._endTask(newTask);
149
144
  });
150
145
  }
151
146
 
@@ -315,7 +315,7 @@ export async function scheduleRequestWithCdns<T>(
315
315
  async function retryWithNextCdn(prevRequestError : unknown) : Promise<T> {
316
316
  const nextCdn = getCdnToRequest();
317
317
 
318
- if (cancellationSignal.isCancelled) {
318
+ if (cancellationSignal.isCancelled()) {
319
319
  throw cancellationSignal.cancellationError;
320
320
  }
321
321
 
@@ -324,7 +324,7 @@ export async function scheduleRequestWithCdns<T>(
324
324
  }
325
325
 
326
326
  onRetry(prevRequestError);
327
- if (cancellationSignal.isCancelled) {
327
+ if (cancellationSignal.isCancelled()) {
328
328
  throw cancellationSignal.cancellationError;
329
329
  }
330
330
 
@@ -358,12 +358,13 @@ export async function scheduleRequestWithCdns<T>(
358
358
  return requestCdn(nextWantedCdn);
359
359
  }
360
360
 
361
- const canceller = new TaskCanceller({ cancelOn: cancellationSignal });
361
+ const canceller = new TaskCanceller();
362
+ const unlinkCanceller = canceller.linkToSignal(cancellationSignal);
362
363
  return new Promise<T>((res, rej) => {
363
364
  /* eslint-disable-next-line @typescript-eslint/no-misused-promises */
364
365
  cdnPrioritizer?.addEventListener("priorityChange", () => {
365
366
  const updatedPrioritaryCdn = getCdnToRequest();
366
- if (cancellationSignal.isCancelled) {
367
+ if (cancellationSignal.isCancelled()) {
367
368
  throw cancellationSignal.cancellationError;
368
369
  }
369
370
  if (updatedPrioritaryCdn === undefined) {
@@ -378,7 +379,7 @@ export async function scheduleRequestWithCdns<T>(
378
379
 
379
380
  cancellableSleep(blockedFor, canceller.signal)
380
381
  .then(() => requestCdn(nextWantedCdn).then(res, rej), noop);
381
- });
382
+ }).finally(unlinkCanceller);
382
383
  }
383
384
 
384
385
  /**
@@ -167,7 +167,7 @@ export default class DirectFileContentInitializer extends ContentInitializer {
167
167
  }
168
168
  }, { emitCurrentValue: true, clearSignal: cancelSignal }))
169
169
  .catch((err) => {
170
- if (!cancelSignal.isCancelled) {
170
+ if (!cancelSignal.isCancelled()) {
171
171
  this._onFatalError(err);
172
172
  }
173
173
  });