rx-player 3.29.0-dev.2022091600 → 3.29.0-dev.2022103100

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 (184) hide show
  1. package/CHANGELOG.md +22 -10
  2. package/MAINTAINERS.md +2 -1
  3. package/VERSION +1 -1
  4. package/dist/_esm5.processed/compat/browser_detection.d.ts +4 -1
  5. package/dist/_esm5.processed/compat/browser_detection.js +7 -1
  6. package/dist/_esm5.processed/compat/can_reuse_media_keys.d.ts +12 -0
  7. package/dist/_esm5.processed/compat/can_reuse_media_keys.js +15 -0
  8. package/dist/_esm5.processed/compat/enable_audio_track.d.ts +11 -0
  9. package/dist/_esm5.processed/compat/enable_audio_track.js +26 -0
  10. package/dist/_esm5.processed/compat/index.d.ts +4 -2
  11. package/dist/_esm5.processed/compat/index.js +4 -2
  12. package/dist/_esm5.processed/compat/{should_renew_media_keys.d.ts → should_renew_media_key_system_access.d.ts} +3 -3
  13. package/dist/_esm5.processed/compat/{should_renew_media_keys.js → should_renew_media_key_system_access.js} +3 -3
  14. package/dist/_esm5.processed/config.d.ts +1 -0
  15. package/dist/_esm5.processed/core/api/playback_observer.d.ts +14 -5
  16. package/dist/_esm5.processed/core/api/playback_observer.js +54 -22
  17. package/dist/_esm5.processed/core/api/public_api.d.ts +7 -4
  18. package/dist/_esm5.processed/core/api/public_api.js +79 -61
  19. package/dist/_esm5.processed/core/api/tracks_management/media_element_track_choice_manager.js +9 -10
  20. package/dist/_esm5.processed/core/decrypt/__tests__/__global__/utils.js +1 -1
  21. package/dist/_esm5.processed/core/decrypt/find_key_system.js +3 -3
  22. package/dist/_esm5.processed/core/decrypt/get_media_keys.js +4 -1
  23. package/dist/_esm5.processed/core/fetchers/cdn_prioritizer.d.ts +98 -0
  24. package/dist/_esm5.processed/core/fetchers/cdn_prioritizer.js +173 -0
  25. package/dist/_esm5.processed/core/fetchers/manifest/manifest_fetcher.js +4 -4
  26. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.d.ts +2 -1
  27. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher.js +15 -16
  28. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher_creator.d.ts +3 -1
  29. package/dist/_esm5.processed/core/fetchers/segment/segment_fetcher_creator.js +5 -2
  30. package/dist/_esm5.processed/core/fetchers/utils/schedule_request.d.ts +103 -0
  31. package/dist/_esm5.processed/core/fetchers/utils/schedule_request.js +414 -0
  32. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.d.ts +4 -3
  33. package/dist/_esm5.processed/core/init/content_time_boundaries_observer.js +21 -22
  34. package/dist/_esm5.processed/core/init/initialize_media_source.d.ts +18 -4
  35. package/dist/_esm5.processed/core/init/initialize_media_source.js +10 -2
  36. package/dist/_esm5.processed/core/init/load_on_media_source.js +7 -1
  37. package/dist/_esm5.processed/core/init/stall_avoider.js +3 -1
  38. package/dist/_esm5.processed/default_config.d.ts +11 -0
  39. package/dist/_esm5.processed/default_config.js +11 -0
  40. package/dist/_esm5.processed/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.js +1 -1
  41. package/dist/_esm5.processed/manifest/manifest.js +11 -2
  42. package/dist/_esm5.processed/manifest/representation.d.ts +12 -1
  43. package/dist/_esm5.processed/manifest/representation.js +1 -0
  44. package/dist/_esm5.processed/manifest/representation_index/static.d.ts +1 -1
  45. package/dist/_esm5.processed/manifest/representation_index/static.js +2 -2
  46. package/dist/_esm5.processed/manifest/representation_index/types.d.ts +32 -15
  47. package/dist/_esm5.processed/manifest/update_period_in_place.js +1 -0
  48. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.d.ts +10 -9
  49. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/base.js +11 -11
  50. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_init_segment.d.ts +1 -1
  51. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_init_segment.js +3 -3
  52. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_segments_from_timeline.d.ts +1 -1
  53. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/get_segments_from_timeline.js +4 -4
  54. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.d.ts +7 -7
  55. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/list.js +12 -8
  56. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.d.ts +5 -6
  57. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/template.js +16 -16
  58. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.d.ts +11 -10
  59. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.js +14 -13
  60. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/tokens.d.ts +3 -4
  61. package/dist/_esm5.processed/parsers/manifest/dash/common/indexes/tokens.js +4 -12
  62. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_mpd.js +2 -4
  63. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representation_index.js +1 -3
  64. package/dist/_esm5.processed/parsers/manifest/dash/common/parse_representations.js +6 -2
  65. package/dist/_esm5.processed/parsers/manifest/dash/common/resolve_base_urls.d.ts +1 -2
  66. package/dist/_esm5.processed/parsers/manifest/dash/common/resolve_base_urls.js +3 -8
  67. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/BaseURL.d.ts +1 -1
  68. package/dist/_esm5.processed/parsers/manifest/dash/js-parser/node_parsers/BaseURL.js +2 -20
  69. package/dist/_esm5.processed/parsers/manifest/dash/node_parser_types.d.ts +0 -6
  70. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/generators/BaseURL.js +0 -11
  71. package/dist/_esm5.processed/parsers/manifest/dash/wasm-parser/ts/types.d.ts +5 -1
  72. package/dist/_esm5.processed/parsers/manifest/local/parse_local_manifest.js +2 -1
  73. package/dist/_esm5.processed/parsers/manifest/local/representation_index.js +2 -2
  74. package/dist/_esm5.processed/parsers/manifest/metaplaylist/metaplaylist_parser.js +7 -1
  75. package/dist/_esm5.processed/parsers/manifest/smooth/create_parser.js +17 -11
  76. package/dist/_esm5.processed/parsers/manifest/smooth/representation_index.js +2 -2
  77. package/dist/_esm5.processed/parsers/manifest/types.d.ts +24 -0
  78. package/dist/_esm5.processed/parsers/texttracks/ttml/html/apply_extent.js +8 -1
  79. package/dist/_esm5.processed/parsers/texttracks/ttml/html/apply_origin.js +9 -1
  80. package/dist/_esm5.processed/parsers/texttracks/ttml/html/create_element.js +2 -1
  81. package/dist/_esm5.processed/transports/dash/construct_segment_url.d.ts +18 -0
  82. package/dist/_esm5.processed/transports/dash/construct_segment_url.js +21 -0
  83. package/dist/_esm5.processed/transports/dash/image_pipelines.d.ts +3 -2
  84. package/dist/_esm5.processed/transports/dash/image_pipelines.js +5 -3
  85. package/dist/_esm5.processed/transports/dash/segment_loader.js +4 -2
  86. package/dist/_esm5.processed/transports/dash/text_loader.js +4 -2
  87. package/dist/_esm5.processed/transports/dash/text_parser.js +3 -1
  88. package/dist/_esm5.processed/transports/local/segment_loader.d.ts +3 -2
  89. package/dist/_esm5.processed/transports/local/segment_loader.js +2 -2
  90. package/dist/_esm5.processed/transports/metaplaylist/pipelines.js +8 -8
  91. package/dist/_esm5.processed/transports/smooth/pipelines.js +11 -7
  92. package/dist/_esm5.processed/transports/smooth/utils.d.ts +4 -2
  93. package/dist/_esm5.processed/transports/smooth/utils.js +7 -1
  94. package/dist/_esm5.processed/transports/types.d.ts +5 -3
  95. package/dist/_esm5.processed/utils/event_emitter.d.ts +1 -2
  96. package/dist/_esm5.processed/utils/resolve_url.d.ts +7 -4
  97. package/dist/_esm5.processed/utils/resolve_url.js +11 -8
  98. package/dist/mpd-parser.wasm +0 -0
  99. package/dist/rx-player.js +5656 -13236
  100. package/dist/rx-player.min.js +1 -1
  101. package/jest.config.js +28 -23
  102. package/package.json +29 -29
  103. package/sonar-project.properties +1 -1
  104. package/src/compat/__tests__/can_reuse_media_keys.test.ts +54 -0
  105. package/src/compat/__tests__/enable_audio_track.test.ts +341 -0
  106. package/src/compat/__tests__/{should_renew_media_keys.test.ts → should_renew_media_key_system_access.test.ts} +7 -5
  107. package/src/compat/browser_detection.ts +10 -0
  108. package/src/compat/can_reuse_media_keys.ts +19 -0
  109. package/src/compat/enable_audio_track.ts +33 -0
  110. package/src/compat/index.ts +6 -2
  111. package/src/compat/{should_renew_media_keys.ts → should_renew_media_key_system_access.ts} +3 -3
  112. package/src/core/api/playback_observer.ts +97 -33
  113. package/src/core/api/public_api.ts +126 -74
  114. package/src/core/api/tracks_management/media_element_track_choice_manager.ts +3 -11
  115. package/src/core/decrypt/__tests__/__global__/media_keys.test.ts +182 -0
  116. package/src/core/decrypt/__tests__/__global__/utils.ts +2 -0
  117. package/src/core/decrypt/find_key_system.ts +3 -3
  118. package/src/core/decrypt/get_media_keys.ts +5 -1
  119. package/src/core/fetchers/cdn_prioritizer.ts +198 -0
  120. package/src/core/fetchers/manifest/manifest_fetcher.ts +7 -11
  121. package/src/core/fetchers/segment/segment_fetcher.ts +12 -10
  122. package/src/core/fetchers/segment/segment_fetcher_creator.ts +10 -1
  123. package/src/core/fetchers/utils/schedule_request.ts +482 -0
  124. package/src/core/init/content_time_boundaries_observer.ts +12 -12
  125. package/src/core/init/initialize_media_source.ts +31 -4
  126. package/src/core/init/load_on_media_source.ts +11 -2
  127. package/src/core/init/stall_avoider.ts +4 -1
  128. package/src/core/stream/orchestrator/stream_orchestrator.ts +0 -1
  129. package/src/default_config.ts +12 -0
  130. package/src/experimental/tools/VideoThumbnailLoader/video_thumbnail_loader.ts +1 -0
  131. package/src/manifest/manifest.ts +11 -2
  132. package/src/manifest/representation.ts +15 -0
  133. package/src/manifest/representation_index/__tests__/static.test.ts +1 -1
  134. package/src/manifest/representation_index/static.ts +3 -3
  135. package/src/manifest/representation_index/types.ts +33 -15
  136. package/src/manifest/update_period_in_place.ts +1 -0
  137. package/src/parsers/manifest/dash/common/indexes/base.ts +22 -23
  138. package/src/parsers/manifest/dash/common/indexes/get_init_segment.ts +6 -4
  139. package/src/parsers/manifest/dash/common/indexes/get_segments_from_timeline.ts +5 -5
  140. package/src/parsers/manifest/dash/common/indexes/list.ts +19 -20
  141. package/src/parsers/manifest/dash/common/indexes/template.ts +23 -27
  142. package/src/parsers/manifest/dash/common/indexes/timeline/timeline_representation_index.ts +25 -23
  143. package/src/parsers/manifest/dash/common/indexes/tokens.ts +7 -18
  144. package/src/parsers/manifest/dash/common/parse_mpd.ts +2 -4
  145. package/src/parsers/manifest/dash/common/parse_representation_index.ts +1 -6
  146. package/src/parsers/manifest/dash/common/parse_representations.ts +9 -0
  147. package/src/parsers/manifest/dash/common/resolve_base_urls.ts +5 -9
  148. package/src/parsers/manifest/dash/js-parser/node_parsers/BaseURL.ts +2 -29
  149. package/src/parsers/manifest/dash/js-parser/node_parsers/__tests__/AdaptationSet.test.ts +7 -11
  150. package/src/parsers/manifest/dash/node_parser_types.ts +0 -7
  151. package/src/parsers/manifest/dash/wasm-parser/rs/events.rs +3 -1
  152. package/src/parsers/manifest/dash/wasm-parser/rs/processor/attributes.rs +1 -8
  153. package/src/parsers/manifest/dash/wasm-parser/ts/generators/BaseURL.ts +0 -13
  154. package/src/parsers/manifest/dash/wasm-parser/ts/types.ts +8 -0
  155. package/src/parsers/manifest/local/parse_local_manifest.ts +1 -0
  156. package/src/parsers/manifest/local/representation_index.ts +2 -2
  157. package/src/parsers/manifest/metaplaylist/metaplaylist_parser.ts +7 -1
  158. package/src/parsers/manifest/smooth/create_parser.ts +17 -13
  159. package/src/parsers/manifest/smooth/representation_index.ts +2 -2
  160. package/src/parsers/manifest/types.ts +26 -1
  161. package/src/parsers/manifest/utils/__tests__/get_first_time_from_adaptations.test.ts +15 -0
  162. package/src/parsers/manifest/utils/__tests__/get_last_time_from_adaptation.test.ts +15 -0
  163. package/src/parsers/texttracks/ttml/html/apply_extent.ts +8 -1
  164. package/src/parsers/texttracks/ttml/html/apply_origin.ts +9 -1
  165. package/src/parsers/texttracks/ttml/html/create_element.ts +2 -2
  166. package/src/transports/dash/construct_segment_url.ts +28 -0
  167. package/src/transports/dash/image_pipelines.ts +5 -2
  168. package/src/transports/dash/segment_loader.ts +5 -2
  169. package/src/transports/dash/text_loader.ts +5 -2
  170. package/src/transports/dash/text_parser.ts +3 -1
  171. package/src/transports/local/segment_loader.ts +3 -2
  172. package/src/transports/metaplaylist/pipelines.ts +28 -9
  173. package/src/transports/smooth/pipelines.ts +12 -6
  174. package/src/transports/smooth/utils.ts +13 -1
  175. package/src/transports/types.ts +6 -3
  176. package/src/utils/__tests__/event_emitter.test.ts +72 -67
  177. package/src/utils/__tests__/initialization_segment_cache.test.ts +7 -7
  178. package/src/utils/__tests__/resolve_url.test.ts +27 -23
  179. package/src/utils/event_emitter.ts +1 -3
  180. package/src/utils/resolve_url.ts +11 -8
  181. package/dist/_esm5.processed/core/fetchers/utils/try_urls_with_backoff.d.ts +0 -85
  182. package/dist/_esm5.processed/core/fetchers/utils/try_urls_with_backoff.js +0 -264
  183. package/dummy +0 -1
  184. package/src/core/fetchers/utils/try_urls_with_backoff.ts +0 -287
@@ -24,6 +24,7 @@
24
24
 
25
25
  import {
26
26
  MediaKeysImpl,
27
+ MediaKeySystemAccessImpl,
27
28
  mockCompat,
28
29
  testContentDecryptorError,
29
30
  } from "./utils";
@@ -104,6 +105,8 @@ describe("core - decrypt - global tests - media key system access", () => {
104
105
  /* eslint-enable max-len */
105
106
  return new Promise<void>((res, rej) => {
106
107
  mockCompat({});
108
+ const mockCreateMediaKeys = jest.spyOn(MediaKeySystemAccessImpl.prototype,
109
+ "createMediaKeys");
107
110
  const { ContentDecryptorState } = jest.requireActual("../../content_decryptor");
108
111
  const ContentDecryptor = jest.requireActual("../../content_decryptor").default;
109
112
  const contentDecryptor = new ContentDecryptor(videoElt, ksConfig);
@@ -112,6 +115,7 @@ describe("core - decrypt - global tests - media key system access", () => {
112
115
  receivedStateChange++;
113
116
  try {
114
117
  expect(newState).toEqual(ContentDecryptorState.WaitingForAttachment);
118
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(1);
115
119
  } catch (err) { rej(err); }
116
120
  setTimeout(() => {
117
121
  try {
@@ -126,6 +130,184 @@ describe("core - decrypt - global tests - media key system access", () => {
126
130
  });
127
131
  });
128
132
 
133
+ /* eslint-disable max-len */
134
+ it("should not call createMediaKeys again if previous one is compatible", () => {
135
+ /* eslint-enable max-len */
136
+ return new Promise<void>((res, rej) => {
137
+ mockCompat({});
138
+ const mockCreateMediaKeys = jest.spyOn(MediaKeySystemAccessImpl.prototype,
139
+ "createMediaKeys");
140
+ const { ContentDecryptorState } = jest.requireActual("../../content_decryptor");
141
+ const ContentDecryptor = jest.requireActual("../../content_decryptor").default;
142
+
143
+ const contentDecryptor1 = new ContentDecryptor(videoElt, ksConfig);
144
+ let receivedStateChange1 = 0;
145
+ contentDecryptor1.addEventListener("error", rej);
146
+ contentDecryptor1.addEventListener("stateChange", (state1: any) => {
147
+ receivedStateChange1++;
148
+ try {
149
+ if (receivedStateChange1 === 2) {
150
+ expect(state1).toEqual(ContentDecryptorState.ReadyForContent);
151
+ return;
152
+ } else if (receivedStateChange1 !== 1) {
153
+ throw new Error("Unexpected stateChange event.");
154
+ }
155
+ expect(state1).toEqual(ContentDecryptorState.WaitingForAttachment);
156
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(1);
157
+ contentDecryptor1.attach();
158
+ } catch (err) { rej(err); }
159
+
160
+ setTimeout(() => {
161
+ contentDecryptor1.dispose();
162
+ const contentDecryptor2 = new ContentDecryptor(videoElt, ksConfig);
163
+ let receivedStateChange2 = 0;
164
+ contentDecryptor2.addEventListener("error", rej);
165
+ contentDecryptor2.addEventListener("stateChange", (state2: any) => {
166
+ receivedStateChange2++;
167
+ try {
168
+ if (receivedStateChange2 === 2) {
169
+ expect(state2).toEqual(ContentDecryptorState.ReadyForContent);
170
+ return;
171
+ } else if (receivedStateChange2 !== 1) {
172
+ throw new Error("Unexpected stateChange event.");
173
+ }
174
+ expect(state2).toEqual(ContentDecryptorState.WaitingForAttachment);
175
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(1);
176
+ contentDecryptor2.attach();
177
+ setTimeout(() => {
178
+ try {
179
+ contentDecryptor2.dispose();
180
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(1);
181
+ res();
182
+ } catch (err) { rej(err); }
183
+ });
184
+ } catch (err) { rej(err); }
185
+ });
186
+ }, 10);
187
+ });
188
+ });
189
+ });
190
+
191
+ /* eslint-disable max-len */
192
+ it("should call createMediaKeys again if the platform needs re-creation of the MediaKeys", () => {
193
+ /* eslint-enable max-len */
194
+ return new Promise<void>((res, rej) => {
195
+ mockCompat({
196
+ canReuseMediaKeys: jest.fn(() => false),
197
+ });
198
+ const mockCreateMediaKeys = jest.spyOn(MediaKeySystemAccessImpl.prototype,
199
+ "createMediaKeys");
200
+ const { ContentDecryptorState } = jest.requireActual("../../content_decryptor");
201
+ const ContentDecryptor = jest.requireActual("../../content_decryptor").default;
202
+
203
+ const contentDecryptor1 = new ContentDecryptor(videoElt, ksConfig);
204
+ let receivedStateChange1 = 0;
205
+ contentDecryptor1.addEventListener("error", rej);
206
+ contentDecryptor1.addEventListener("stateChange", (state1: any) => {
207
+ receivedStateChange1++;
208
+ try {
209
+ if (receivedStateChange1 === 2) {
210
+ expect(state1).toEqual(ContentDecryptorState.ReadyForContent);
211
+ return;
212
+ } else if (receivedStateChange1 !== 1) {
213
+ throw new Error("Unexpected stateChange event.");
214
+ }
215
+ expect(state1).toEqual(ContentDecryptorState.WaitingForAttachment);
216
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(1);
217
+ contentDecryptor1.attach();
218
+ } catch (err) { rej(err); }
219
+
220
+ setTimeout(() => {
221
+ contentDecryptor1.dispose();
222
+ const contentDecryptor2 = new ContentDecryptor(videoElt, ksConfig);
223
+ let receivedStateChange2 = 0;
224
+ contentDecryptor2.addEventListener("error", rej);
225
+ contentDecryptor2.addEventListener("stateChange", (state2: any) => {
226
+ receivedStateChange2++;
227
+ try {
228
+ if (receivedStateChange2 === 2) {
229
+ expect(state2).toEqual(ContentDecryptorState.ReadyForContent);
230
+ return;
231
+ } else if (receivedStateChange2 !== 1) {
232
+ throw new Error("Unexpected stateChange event.");
233
+ }
234
+ expect(state2).toEqual(ContentDecryptorState.WaitingForAttachment);
235
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(2);
236
+ contentDecryptor2.attach();
237
+ setTimeout(() => {
238
+ try {
239
+ contentDecryptor2.dispose();
240
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(2);
241
+ res();
242
+ } catch (err) { rej(err); }
243
+ });
244
+ } catch (err) { rej(err); }
245
+ });
246
+ }, 10);
247
+ });
248
+ });
249
+ });
250
+
251
+ /* eslint-disable max-len */
252
+ it("should not call createMediaKeys again if the platform needs MediaKeySystemAccess renewal", () => {
253
+ /* eslint-enable max-len */
254
+ return new Promise<void>((res, rej) => {
255
+ mockCompat({
256
+ shouldRenewMediaKeySystemAccess: jest.fn(() => true),
257
+ });
258
+ const mockCreateMediaKeys = jest.spyOn(MediaKeySystemAccessImpl.prototype,
259
+ "createMediaKeys");
260
+ const { ContentDecryptorState } = jest.requireActual("../../content_decryptor");
261
+ const ContentDecryptor = jest.requireActual("../../content_decryptor").default;
262
+
263
+ const contentDecryptor1 = new ContentDecryptor(videoElt, ksConfig);
264
+ let receivedStateChange1 = 0;
265
+ contentDecryptor1.addEventListener("error", rej);
266
+ contentDecryptor1.addEventListener("stateChange", (state1: any) => {
267
+ receivedStateChange1++;
268
+ try {
269
+ if (receivedStateChange1 === 2) {
270
+ expect(state1).toEqual(ContentDecryptorState.ReadyForContent);
271
+ return;
272
+ } else if (receivedStateChange1 !== 1) {
273
+ throw new Error("Unexpected stateChange event.");
274
+ }
275
+ expect(state1).toEqual(ContentDecryptorState.WaitingForAttachment);
276
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(1);
277
+ contentDecryptor1.attach();
278
+ } catch (err) { rej(err); }
279
+
280
+ setTimeout(() => {
281
+ contentDecryptor1.dispose();
282
+ const contentDecryptor2 = new ContentDecryptor(videoElt, ksConfig);
283
+ let receivedStateChange2 = 0;
284
+ contentDecryptor2.addEventListener("error", rej);
285
+ contentDecryptor2.addEventListener("stateChange", (state2: any) => {
286
+ receivedStateChange2++;
287
+ try {
288
+ if (receivedStateChange2 === 2) {
289
+ expect(state2).toEqual(ContentDecryptorState.ReadyForContent);
290
+ return;
291
+ } else if (receivedStateChange2 !== 1) {
292
+ throw new Error("Unexpected stateChange event.");
293
+ }
294
+ expect(state2).toEqual(ContentDecryptorState.WaitingForAttachment);
295
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(2);
296
+ contentDecryptor2.attach();
297
+ setTimeout(() => {
298
+ try {
299
+ contentDecryptor2.dispose();
300
+ expect(mockCreateMediaKeys).toHaveBeenCalledTimes(2);
301
+ res();
302
+ } catch (err) { rej(err); }
303
+ });
304
+ } catch (err) { rej(err); }
305
+ });
306
+ }, 10);
307
+ });
308
+ });
309
+ });
310
+
129
311
  /* eslint-disable max-len */
130
312
  it("should not create any session if no encrypted event was received", (done) => {
131
313
  /* eslint-enable max-len */
@@ -286,6 +286,8 @@ export function mockCompat(exportedFunctions = {}) {
286
286
  setMediaKeys: mockSetMediaKeys,
287
287
  getInitData: mockGetInitData,
288
288
  generateKeyRequest: mockGenerateKeyRequest,
289
+ shouldRenewMediaKeySystemAccess: jest.fn(() => false),
290
+ canReuseMediaKeys: jest.fn(() => true),
289
291
  ...exportedFunctions }));
290
292
 
291
293
  return { mockEvents,
@@ -17,7 +17,7 @@
17
17
  import {
18
18
  ICustomMediaKeySystemAccess,
19
19
  requestMediaKeySystemAccess,
20
- shouldRenewMediaKeys,
20
+ shouldRenewMediaKeySystemAccess,
21
21
  } from "../../compat";
22
22
  import config from "../../config";
23
23
  import { EncryptedMediaError } from "../../errors";
@@ -74,7 +74,7 @@ function checkCachedMediaKeySystemAccess(
74
74
  keySystemAccess: MediaKeySystemAccess|ICustomMediaKeySystemAccess;
75
75
  } {
76
76
  const mksConfiguration = currentKeySystemAccess.getConfiguration();
77
- if (shouldRenewMediaKeys() || mksConfiguration == null) {
77
+ if (shouldRenewMediaKeySystemAccess() || mksConfiguration == null) {
78
78
  return null;
79
79
  }
80
80
 
@@ -187,7 +187,7 @@ function buildKeySystemConfigurations(
187
187
  // https://www.w3.org/TR/encrypted-media/#get-supported-configuration-and-consent
188
188
 
189
189
  const videoCapabilities: IMediaCapability[] =
190
- flatMap(audioRobustnesses, (robustness) =>
190
+ flatMap(videoRobustnesses, (robustness) =>
191
191
  ["video/mp4;codecs=\"avc1.4d401e\"",
192
192
  "video/mp4;codecs=\"avc1.42e01e\"",
193
193
  "video/webm;codecs=\"vp8\""].map(contentType => {
@@ -15,6 +15,7 @@
15
15
  */
16
16
 
17
17
  import {
18
+ canReuseMediaKeys,
18
19
  ICustomMediaKeys,
19
20
  ICustomMediaKeySystemAccess,
20
21
  } from "../../compat";
@@ -95,7 +96,10 @@ export default async function getMediaKeysInfos(
95
96
  const currentState = MediaKeysInfosStore.getState(mediaElement);
96
97
  const persistentSessionsStore = createPersistentSessionsStorage(options);
97
98
 
98
- if (currentState !== null && evt.type === "reuse-media-key-system-access") {
99
+ if (canReuseMediaKeys() &&
100
+ currentState !== null &&
101
+ evt.type === "reuse-media-key-system-access")
102
+ {
99
103
  const { mediaKeys, loadedSessionsStore } = currentState;
100
104
 
101
105
  // We might just rely on the currently attached MediaKeys instance.
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Copyright 2015 CANAL+ Group
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import config from "../../config";
18
+ import { ICdnMetadata } from "../../parsers/manifest";
19
+ import { IPlayerError } from "../../public_types";
20
+ import arrayFindIndex from "../../utils/array_find_index";
21
+ import EventEmitter from "../../utils/event_emitter";
22
+ import { CancellationSignal } from "../../utils/task_canceller";
23
+
24
+ /**
25
+ * Class signaling the priority between multiple CDN available for any given
26
+ * resource.
27
+ *
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.
32
+ *
33
+ * @class CdnPrioritizer
34
+ */
35
+ export default class CdnPrioritizer extends EventEmitter<ICdnPrioritizerEvents> {
36
+ /**
37
+ * Structure keeping a list of CDN currently downgraded.
38
+ * Downgraded CDN immediately have a lower priority than any non-downgraded
39
+ * CDN for a specific amount of time.
40
+ */
41
+ private _downgradedCdnList : {
42
+ /**
43
+ * Metadata of downgraded CDN, sorted by the time at which they have
44
+ * been downgraded.
45
+ */
46
+ metadata : ICdnMetadata[];
47
+ /**
48
+ * Timeout ID (to give to `clearTimeout`) of elements in the `metadata`
49
+ * array, for the element at the same index in the `metadata` array.
50
+ *
51
+ * This structure has been writted as an object of two arrays of the same
52
+ * length, instead of an array of objects, to simplify the usage of the
53
+ * `metadata` array which is used considerably more than the `timeouts`
54
+ * array.
55
+ */
56
+ timeouts : number[];
57
+ };
58
+
59
+ /**
60
+ * @param {Object} destroySignal
61
+ */
62
+ constructor(destroySignal : CancellationSignal) {
63
+ super();
64
+ this._downgradedCdnList = { metadata: [], timeouts: [] };
65
+ destroySignal.register(() => {
66
+ for (const timeout of this._downgradedCdnList.timeouts) {
67
+ clearTimeout(timeout);
68
+ }
69
+ this._downgradedCdnList = { metadata: [], timeouts: [] };
70
+ });
71
+ }
72
+
73
+ /**
74
+ * From the list of __ALL__ CDNs available to a resource, return them in the
75
+ * order in which requests should be performed.
76
+ *
77
+ * Note: It is VERY important to include all CDN that are able to reach the
78
+ * wanted resource, even those which will in the end not be used anyway.
79
+ * If some CDN are not communicated, the `CdnPrioritizer` might wrongly
80
+ * consider that the current resource don't have any of the CDN prioritized
81
+ * internally and return other CDN which should have been forbidden if it knew
82
+ * about the other, non-used, ones.
83
+ *
84
+ * @param {Array.<string>} everyCdnForResource - Array of ALL available CDN
85
+ * able to reach the wanted resource - even those which might not be used in
86
+ * the end.
87
+ * @returns {Array.<Object>} - Array of CDN that can be tried to reach the
88
+ * resource, sorted by order of CDN preference, according to the
89
+ * `CdnPrioritizer`'s own list of priorities.
90
+ */
91
+ public getCdnPreferenceForResource(
92
+ everyCdnForResource : ICdnMetadata[]
93
+ ) : ICdnMetadata[] {
94
+ if (everyCdnForResource.length <= 1) {
95
+ // The huge majority of contents have only one CDN available.
96
+ // Here, prioritizing make no sense.
97
+ return everyCdnForResource;
98
+ }
99
+
100
+ return this._innerGetCdnPreferenceForResource(everyCdnForResource);
101
+ }
102
+
103
+ /**
104
+ * Limit usage of the CDN for a configured amount of time.
105
+ * Call this method if you encountered an issue with that CDN which leads you
106
+ * to want to prevent its usage currently.
107
+ *
108
+ * Note that the CDN can still be the preferred one if no other CDN exist for
109
+ * a wanted resource.
110
+ * @param {string} metadata
111
+ */
112
+ public downgradeCdn(metadata : ICdnMetadata) : void {
113
+ const indexOf = indexOfMetadata(this._downgradedCdnList.metadata, metadata);
114
+ if (indexOf >= 0) {
115
+ this._removeIndexFromDowngradeList(indexOf);
116
+ }
117
+
118
+ const { DEFAULT_CDN_DOWNGRADE_TIME } = config.getCurrent();
119
+ const downgradeTime = DEFAULT_CDN_DOWNGRADE_TIME;
120
+ this._downgradedCdnList.metadata.push(metadata);
121
+ const timeout = window.setTimeout(() => {
122
+ const newIndex = indexOfMetadata(this._downgradedCdnList.metadata, metadata);
123
+ if (newIndex >= 0) {
124
+ this._removeIndexFromDowngradeList(newIndex);
125
+ }
126
+ this.trigger("priorityChange", null);
127
+ }, downgradeTime);
128
+ this._downgradedCdnList.timeouts.push(timeout);
129
+ this.trigger("priorityChange", null);
130
+ }
131
+
132
+ /**
133
+ * From the list of __ALL__ CDNs available to a resource, return them in the
134
+ * order in which requests should be performed.
135
+ *
136
+ * Note: It is VERY important to include all CDN that are able to reach the
137
+ * wanted resource, even those which will in the end not be used anyway.
138
+ * If some CDN are not communicated, the `CdnPrioritizer` might wrongly
139
+ * consider that the current resource don't have any of the CDN prioritized
140
+ * internally and return other CDN which should have been forbidden if it knew
141
+ * about the other, non-used, ones.
142
+ *
143
+ * @param {Array.<string>} everyCdnForResource - Array of ALL available CDN
144
+ * able to reach the wanted resource - even those which might not be used in
145
+ * the end.
146
+ * @returns {Array.<string>} - Array of CDN that can be tried to reach the
147
+ * resource, sorted by order of CDN preference, according to the
148
+ * `CdnPrioritizer`'s own list of priorities.
149
+ */
150
+ private _innerGetCdnPreferenceForResource(
151
+ everyCdnForResource : ICdnMetadata[]
152
+ ) : ICdnMetadata[] {
153
+ const [allowedInOrder, downgradedInOrder] = everyCdnForResource
154
+ .reduce((acc : [ICdnMetadata[], ICdnMetadata[]], elt : ICdnMetadata) => {
155
+ if (this._downgradedCdnList.metadata.some(c => c.id === elt.id &&
156
+ c.baseUrl === elt.baseUrl))
157
+ {
158
+ acc[1].push(elt);
159
+ } else {
160
+ acc[0].push(elt);
161
+ }
162
+ return acc;
163
+ }, [[], []]);
164
+ return allowedInOrder.concat(downgradedInOrder);
165
+ }
166
+
167
+ /**
168
+ * @param {number} index
169
+ */
170
+ private _removeIndexFromDowngradeList(index : number) : void {
171
+ this._downgradedCdnList.metadata.splice(index, 1);
172
+ const oldTimeout = this._downgradedCdnList.timeouts.splice(index, 1);
173
+ clearTimeout(oldTimeout[0]);
174
+ }
175
+ }
176
+
177
+ export interface ICdnPrioritizerEvents {
178
+ warnings : IPlayerError[];
179
+ priorityChange : null;
180
+ }
181
+
182
+ /**
183
+ * Find the index of the given CDN metadata in a CDN metadata array.
184
+ * Returns `-1` if not found.
185
+ * @param {Array.<Object>} arr
186
+ * @param {Object} elt
187
+ * @returns {number}
188
+ */
189
+ function indexOfMetadata(
190
+ arr : ICdnMetadata[],
191
+ elt : ICdnMetadata
192
+ ) : number {
193
+ if (arr.length === 0) {
194
+ return -1;
195
+ }
196
+ return elt.id !== undefined ? arrayFindIndex(arr, m => m.id === elt.id) :
197
+ arrayFindIndex(arr, m => m.baseUrl === elt.baseUrl);
198
+ }
@@ -33,8 +33,8 @@ import TaskCanceller from "../../../utils/task_canceller";
33
33
  import errorSelector from "../utils/error_selector";
34
34
  import {
35
35
  IBackoffSettings,
36
- tryRequestPromiseWithBackoff,
37
- } from "../utils/try_urls_with_backoff";
36
+ scheduleRequestPromise,
37
+ } from "../utils/schedule_request";
38
38
 
39
39
 
40
40
  /** What will be sent once parsed. */
@@ -228,9 +228,7 @@ export default class ManifestFetcher {
228
228
  const { resolveManifestUrl } = pipelines;
229
229
  assert(resolveManifestUrl !== undefined);
230
230
  const callResolver = () => resolveManifestUrl(resolverUrl, canceller.signal);
231
- return tryRequestPromiseWithBackoff(callResolver,
232
- backoffSettings,
233
- canceller.signal);
231
+ return scheduleRequestPromise(callResolver, backoffSettings, canceller.signal);
234
232
  }
235
233
 
236
234
  /**
@@ -252,9 +250,7 @@ export default class ManifestFetcher {
252
250
  const callLoader = () => loadManifest(manifestUrl,
253
251
  { timeout: requestTimeout },
254
252
  canceller.signal);
255
- return tryRequestPromiseWithBackoff(callLoader,
256
- backoffSettings,
257
- canceller.signal);
253
+ return scheduleRequestPromise(callLoader, backoffSettings, canceller.signal);
258
254
  }
259
255
  });
260
256
  }
@@ -349,9 +345,9 @@ export default class ManifestFetcher {
349
345
  performRequest : () => Promise<T>
350
346
  ) : Promise<T> {
351
347
  try {
352
- const data = await tryRequestPromiseWithBackoff(performRequest,
353
- backoffSettings,
354
- canceller.signal);
348
+ const data = await scheduleRequestPromise(performRequest,
349
+ backoffSettings,
350
+ canceller.signal);
355
351
  return data;
356
352
  } catch (err) {
357
353
  throw errorSelector(err);
@@ -24,6 +24,7 @@ import Manifest, {
24
24
  Period,
25
25
  Representation,
26
26
  } from "../../../manifest";
27
+ import { ICdnMetadata } from "../../../parsers/manifest";
27
28
  import { IPlayerError } from "../../../public_types";
28
29
  import {
29
30
  IChunkCompleteInformation,
@@ -49,8 +50,9 @@ import {
49
50
  IRequestProgressCallbackPayload,
50
51
  } from "../../adaptive";
51
52
  import { IBufferType } from "../../segment_buffers";
53
+ import CdnPrioritizer from "../cdn_prioritizer";
52
54
  import errorSelector from "../utils/error_selector";
53
- import { tryURLsWithBackoff } from "../utils/try_urls_with_backoff";
55
+ import { scheduleRequestWithCdns } from "../utils/schedule_request";
54
56
 
55
57
 
56
58
  /** Allows to generate a unique identifies for each request. */
@@ -71,6 +73,7 @@ const generateRequestID = idGenerator();
71
73
  export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
72
74
  bufferType : IBufferType,
73
75
  pipeline : ISegmentPipeline<TLoadedFormat, TSegmentDataType>,
76
+ cdnPrioritizer : CdnPrioritizer | null,
74
77
  lifecycleCallbacks : ISegmentFetcherLifecycleCallbacks,
75
78
  options : ISegmentFetcherOptions
76
79
  ) : ISegmentFetcher<TSegmentDataType> {
@@ -102,8 +105,6 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
102
105
  fetcherCallbacks : ISegmentFetcherCallbacks<TSegmentDataType>,
103
106
  cancellationSignal : CancellationSignal
104
107
  ) : Promise<void> {
105
- const { segment } = content;
106
-
107
108
  // used by logs
108
109
  const segmentIdString = getLoggableSegmentId(content);
109
110
  const requestId = generateRequestID();
@@ -193,10 +194,11 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
193
194
  });
194
195
 
195
196
  try {
196
- const res = await tryURLsWithBackoff(segment.mediaURLs ?? [null],
197
- callLoaderWithUrl,
198
- objectAssign({ onRetry }, options),
199
- cancellationSignal);
197
+ const res = await scheduleRequestWithCdns(content.representation.cdnMetadata,
198
+ cdnPrioritizer,
199
+ callLoaderWithUrl,
200
+ objectAssign({ onRetry }, options),
201
+ cancellationSignal);
200
202
 
201
203
  if (res.resultType === "segment-loaded") {
202
204
  const loadedData = res.resultData.responseData;
@@ -236,13 +238,13 @@ export default function createSegmentFetcher<TLoadedFormat, TSegmentDataType>(
236
238
 
237
239
  /**
238
240
  * Call a segment loader for the given URL with the right arguments.
239
- * @param {string|null} url
241
+ * @param {Object|null} cdnMetadata
240
242
  * @returns {Promise}
241
243
  */
242
244
  function callLoaderWithUrl(
243
- url : string | null
245
+ cdnMetadata : ICdnMetadata | null
244
246
  ) : ReturnType<ISegmentLoader<TLoadedFormat>> {
245
- return loadSegment(url,
247
+ return loadSegment(cdnMetadata,
246
248
  content,
247
249
  requestOptions,
248
250
  cancellationSignal,
@@ -19,7 +19,9 @@ import {
19
19
  ISegmentPipeline,
20
20
  ITransportPipelines,
21
21
  } from "../../../transports";
22
+ import { CancellationSignal } from "../../../utils/task_canceller";
22
23
  import { IBufferType } from "../../segment_buffers";
24
+ import CdnPrioritizer from "../cdn_prioritizer";
23
25
  import applyPrioritizerToSegmentFetcher, {
24
26
  IPrioritizedSegmentFetcher,
25
27
  } from "./prioritized_segment_fetcher";
@@ -81,13 +83,18 @@ export default class SegmentFetcherCreator {
81
83
  */
82
84
  private readonly _backoffOptions : ISegmentFetcherCreatorBackoffOptions;
83
85
 
86
+ private readonly _cdnPrioritizer : CdnPrioritizer;
87
+
84
88
  /**
85
89
  * @param {Object} transport
86
90
  */
87
91
  constructor(
88
92
  transport : ITransportPipelines,
89
- options : ISegmentFetcherCreatorBackoffOptions
93
+ options : ISegmentFetcherCreatorBackoffOptions,
94
+ cancelSignal : CancellationSignal
90
95
  ) {
96
+ const cdnPrioritizer = new CdnPrioritizer(cancelSignal);
97
+
91
98
  const { MIN_CANCELABLE_PRIORITY,
92
99
  MAX_HIGH_PRIORITY_LEVEL } = config.getCurrent();
93
100
  this._transport = transport;
@@ -95,6 +102,7 @@ export default class SegmentFetcherCreator {
95
102
  prioritySteps: { high: MAX_HIGH_PRIORITY_LEVEL,
96
103
  low: MIN_CANCELABLE_PRIORITY },
97
104
  });
105
+ this._cdnPrioritizer = cdnPrioritizer;
98
106
  this._backoffOptions = options;
99
107
  }
100
108
 
@@ -116,6 +124,7 @@ export default class SegmentFetcherCreator {
116
124
  const segmentFetcher = createSegmentFetcher<unknown, unknown>(
117
125
  bufferType,
118
126
  pipelines as ISegmentPipeline<unknown, unknown>,
127
+ this._cdnPrioritizer,
119
128
  callbacks,
120
129
  backoffOptions
121
130
  );