rx-player 4.3.0-dev.2024112900 → 4.3.0-dev.2025012900

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 (174) hide show
  1. package/CHANGELOG.md +30 -8
  2. package/VERSION +1 -1
  3. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  5. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  6. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  7. package/dist/commonjs/compat/browser_compatibility_types.d.ts +7 -2
  8. package/dist/commonjs/compat/browser_compatibility_types.d.ts.map +1 -1
  9. package/dist/commonjs/compat/browser_compatibility_types.js +0 -1
  10. package/dist/commonjs/compat/browser_detection.js +1 -1
  11. package/dist/commonjs/compat/event_listeners.d.ts.map +1 -1
  12. package/dist/commonjs/compat/event_listeners.js +2 -4
  13. package/dist/commonjs/compat/has_mse_in_worker.d.ts.map +1 -1
  14. package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  15. package/dist/commonjs/core/cmcd/cmcd_data_builder.js +1 -2
  16. package/dist/commonjs/core/main/worker/send_message.d.ts.map +1 -1
  17. package/dist/commonjs/core/main/worker/send_message.js +0 -1
  18. package/dist/commonjs/core/main/worker/worker_main.js +1 -1
  19. package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  20. package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +4 -8
  21. package/dist/commonjs/core/stream/period/period_stream.js +1 -3
  22. package/dist/commonjs/core/stream/period/utils/get_adaptation_switch_strategy.js +1 -2
  23. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/api/index.d.ts.map +1 -1
  24. package/dist/commonjs/main_thread/api/public_api.js +4 -4
  25. package/dist/commonjs/main_thread/decrypt/clear_on_stop.d.ts.map +1 -1
  26. package/dist/commonjs/main_thread/decrypt/clear_on_stop.js +60 -14
  27. package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  28. package/dist/commonjs/main_thread/decrypt/content_decryptor.js +21 -9
  29. package/dist/commonjs/main_thread/decrypt/dispose_decryption_resources.d.ts.map +1 -1
  30. package/dist/commonjs/main_thread/decrypt/dispose_decryption_resources.js +2 -18
  31. package/dist/commonjs/main_thread/decrypt/find_key_system.d.ts.map +1 -1
  32. package/dist/commonjs/main_thread/decrypt/find_key_system.js +134 -68
  33. package/dist/commonjs/main_thread/decrypt/get_key_system_configuration.js +2 -2
  34. package/dist/commonjs/main_thread/decrypt/get_media_keys.js +5 -3
  35. package/dist/commonjs/main_thread/decrypt/init_media_keys.d.ts.map +1 -1
  36. package/dist/commonjs/main_thread/decrypt/init_media_keys.js +2 -2
  37. package/dist/{es2017/main_thread/decrypt/utils/media_keys_infos_store.d.ts → commonjs/main_thread/decrypt/utils/media_keys_attacher.d.ts} +31 -9
  38. package/dist/commonjs/main_thread/decrypt/utils/media_keys_attacher.d.ts.map +1 -0
  39. package/dist/commonjs/main_thread/decrypt/utils/media_keys_attacher.js +282 -0
  40. package/dist/commonjs/main_thread/init/utils/rebuffering_controller.d.ts.map +1 -1
  41. package/dist/commonjs/main_thread/init/utils/rebuffering_controller.js +20 -9
  42. package/dist/commonjs/main_thread/text_displayer/html/text_track_cues_store.js +1 -1
  43. package/dist/commonjs/main_thread/tracks_store/track_dispatcher.d.ts.map +1 -1
  44. package/dist/commonjs/main_thread/tracks_store/track_dispatcher.js +3 -4
  45. package/dist/commonjs/main_thread/tracks_store/tracks_store.d.ts.map +1 -1
  46. package/dist/commonjs/main_thread/tracks_store/tracks_store.js +1 -1
  47. package/dist/commonjs/manifest/classes/representation.d.ts +15 -1
  48. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  49. package/dist/commonjs/manifest/classes/representation.js +17 -0
  50. package/dist/commonjs/manifest/utils.d.ts +15 -0
  51. package/dist/commonjs/manifest/utils.d.ts.map +1 -1
  52. package/dist/commonjs/manifest/utils.js +28 -5
  53. package/dist/commonjs/parsers/manifest/dash/common/parse_representations.js +2 -2
  54. package/dist/commonjs/playback_observer/media_element_playback_observer.js +1 -1
  55. package/dist/commonjs/public_types.d.ts +44 -0
  56. package/dist/commonjs/public_types.d.ts.map +1 -1
  57. package/dist/commonjs/utils/flat_map.d.ts +2 -2
  58. package/dist/commonjs/utils/flat_map.js +2 -2
  59. package/dist/commonjs/utils/url-utils.d.ts.map +1 -1
  60. package/dist/commonjs/utils/url-utils.js +11 -10
  61. package/dist/commonjs/utils/xml-parser.d.ts.map +1 -1
  62. package/dist/commonjs/utils/xml-parser.js +10 -8
  63. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts +1 -1
  64. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  65. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  66. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  67. package/dist/es2017/compat/browser_compatibility_types.d.ts +7 -2
  68. package/dist/es2017/compat/browser_compatibility_types.d.ts.map +1 -1
  69. package/dist/es2017/compat/browser_compatibility_types.js +0 -1
  70. package/dist/es2017/compat/browser_detection.js +1 -1
  71. package/dist/es2017/compat/event_listeners.d.ts.map +1 -1
  72. package/dist/es2017/compat/event_listeners.js +2 -4
  73. package/dist/es2017/compat/has_mse_in_worker.d.ts.map +1 -1
  74. package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  75. package/dist/es2017/core/cmcd/cmcd_data_builder.js +1 -2
  76. package/dist/es2017/core/main/worker/send_message.d.ts.map +1 -1
  77. package/dist/es2017/core/main/worker/send_message.js +0 -1
  78. package/dist/es2017/core/main/worker/worker_main.js +1 -1
  79. package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  80. package/dist/es2017/core/stream/adaptation/adaptation_stream.js +4 -6
  81. package/dist/es2017/core/stream/period/period_stream.js +1 -3
  82. package/dist/es2017/core/stream/period/utils/get_adaptation_switch_strategy.js +1 -2
  83. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/api/index.d.ts.map +1 -1
  84. package/dist/es2017/main_thread/api/public_api.js +4 -4
  85. package/dist/es2017/main_thread/decrypt/clear_on_stop.d.ts.map +1 -1
  86. package/dist/es2017/main_thread/decrypt/clear_on_stop.js +3 -3
  87. package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  88. package/dist/es2017/main_thread/decrypt/content_decryptor.js +21 -9
  89. package/dist/es2017/main_thread/decrypt/dispose_decryption_resources.d.ts.map +1 -1
  90. package/dist/es2017/main_thread/decrypt/dispose_decryption_resources.js +2 -12
  91. package/dist/es2017/main_thread/decrypt/find_key_system.d.ts.map +1 -1
  92. package/dist/es2017/main_thread/decrypt/find_key_system.js +102 -56
  93. package/dist/es2017/main_thread/decrypt/get_key_system_configuration.js +2 -2
  94. package/dist/es2017/main_thread/decrypt/get_media_keys.js +2 -2
  95. package/dist/es2017/main_thread/decrypt/init_media_keys.d.ts.map +1 -1
  96. package/dist/es2017/main_thread/decrypt/init_media_keys.js +2 -2
  97. package/dist/{commonjs/main_thread/decrypt/utils/media_keys_infos_store.d.ts → es2017/main_thread/decrypt/utils/media_keys_attacher.d.ts} +31 -9
  98. package/dist/es2017/main_thread/decrypt/utils/media_keys_attacher.d.ts.map +1 -0
  99. package/dist/es2017/main_thread/decrypt/utils/media_keys_attacher.js +180 -0
  100. package/dist/es2017/main_thread/init/utils/rebuffering_controller.d.ts.map +1 -1
  101. package/dist/es2017/main_thread/init/utils/rebuffering_controller.js +20 -9
  102. package/dist/es2017/main_thread/text_displayer/html/text_track_cues_store.js +1 -1
  103. package/dist/es2017/main_thread/tracks_store/track_dispatcher.d.ts.map +1 -1
  104. package/dist/es2017/main_thread/tracks_store/track_dispatcher.js +3 -4
  105. package/dist/es2017/main_thread/tracks_store/tracks_store.d.ts.map +1 -1
  106. package/dist/es2017/main_thread/tracks_store/tracks_store.js +2 -2
  107. package/dist/es2017/manifest/classes/representation.d.ts +15 -1
  108. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  109. package/dist/es2017/manifest/classes/representation.js +17 -0
  110. package/dist/es2017/manifest/utils.d.ts +15 -0
  111. package/dist/es2017/manifest/utils.d.ts.map +1 -1
  112. package/dist/es2017/manifest/utils.js +27 -5
  113. package/dist/es2017/parsers/manifest/dash/common/parse_representations.js +2 -2
  114. package/dist/es2017/playback_observer/media_element_playback_observer.js +1 -1
  115. package/dist/es2017/public_types.d.ts +44 -0
  116. package/dist/es2017/public_types.d.ts.map +1 -1
  117. package/dist/es2017/utils/flat_map.d.ts +2 -2
  118. package/dist/es2017/utils/flat_map.js +2 -2
  119. package/dist/es2017/utils/url-utils.d.ts.map +1 -1
  120. package/dist/es2017/utils/url-utils.js +11 -10
  121. package/dist/es2017/utils/xml-parser.d.ts.map +1 -1
  122. package/dist/es2017/utils/xml-parser.js +10 -8
  123. package/dist/rx-player.js +505 -372
  124. package/dist/rx-player.min.js +18 -18
  125. package/dist/worker.js +6 -6
  126. package/package.json +21 -19
  127. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  128. package/src/compat/browser_compatibility_types.ts +22 -4
  129. package/src/compat/browser_detection.ts +1 -1
  130. package/src/compat/event_listeners.ts +2 -4
  131. package/src/compat/has_mse_in_worker.ts +1 -1
  132. package/src/core/cmcd/cmcd_data_builder.ts +1 -4
  133. package/src/core/main/worker/send_message.ts +4 -2
  134. package/src/core/main/worker/worker_main.ts +1 -1
  135. package/src/core/stream/adaptation/adaptation_stream.ts +4 -7
  136. package/src/core/stream/period/period_stream.ts +3 -3
  137. package/src/core/stream/period/utils/get_adaptation_switch_strategy.ts +1 -2
  138. package/src/main_thread/api/public_api.ts +4 -4
  139. package/src/main_thread/decrypt/__tests__/__global__/media_key_system_access.test.ts +239 -6
  140. package/src/main_thread/decrypt/__tests__/find_key_system.test.ts +325 -0
  141. package/src/main_thread/decrypt/clear_on_stop.ts +3 -3
  142. package/src/main_thread/decrypt/content_decryptor.ts +26 -8
  143. package/src/main_thread/decrypt/dispose_decryption_resources.ts +2 -13
  144. package/src/main_thread/decrypt/find_key_system.ts +120 -81
  145. package/src/main_thread/decrypt/get_key_system_configuration.ts +2 -2
  146. package/src/main_thread/decrypt/get_media_keys.ts +2 -2
  147. package/src/main_thread/decrypt/init_media_keys.ts +2 -2
  148. package/src/main_thread/decrypt/utils/media_keys_attacher.ts +284 -0
  149. package/src/main_thread/init/utils/rebuffering_controller.ts +20 -10
  150. package/src/main_thread/text_displayer/html/text_track_cues_store.ts +1 -1
  151. package/src/main_thread/tracks_store/track_dispatcher.ts +3 -6
  152. package/src/main_thread/tracks_store/tracks_store.ts +2 -1
  153. package/src/manifest/classes/representation.ts +18 -1
  154. package/src/manifest/utils.ts +30 -9
  155. package/src/parsers/manifest/dash/common/parse_representations.ts +2 -2
  156. package/src/playback_observer/media_element_playback_observer.ts +1 -1
  157. package/src/public_types.ts +45 -0
  158. package/src/utils/__tests__/flat_map.test.ts +7 -20
  159. package/src/utils/flat_map.ts +2 -2
  160. package/src/utils/url-utils.ts +11 -10
  161. package/src/utils/xml-parser.ts +13 -8
  162. package/vitest.config.mjs +9 -23
  163. package/dist/commonjs/main_thread/decrypt/attach_media_keys.d.ts +0 -60
  164. package/dist/commonjs/main_thread/decrypt/attach_media_keys.d.ts.map +0 -1
  165. package/dist/commonjs/main_thread/decrypt/attach_media_keys.js +0 -124
  166. package/dist/commonjs/main_thread/decrypt/utils/media_keys_infos_store.d.ts.map +0 -1
  167. package/dist/commonjs/main_thread/decrypt/utils/media_keys_infos_store.js +0 -45
  168. package/dist/es2017/main_thread/decrypt/attach_media_keys.d.ts +0 -60
  169. package/dist/es2017/main_thread/decrypt/attach_media_keys.d.ts.map +0 -1
  170. package/dist/es2017/main_thread/decrypt/attach_media_keys.js +0 -73
  171. package/dist/es2017/main_thread/decrypt/utils/media_keys_infos_store.d.ts.map +0 -1
  172. package/dist/es2017/main_thread/decrypt/utils/media_keys_infos_store.js +0 -43
  173. package/src/main_thread/decrypt/attach_media_keys.ts +0 -125
  174. package/src/main_thread/decrypt/utils/media_keys_infos_store.ts +0 -88
@@ -32,7 +32,7 @@ import arrayIncludes from "../../utils/array_includes";
32
32
  import flatMap from "../../utils/flat_map";
33
33
  import isNullOrUndefined from "../../utils/is_null_or_undefined";
34
34
  import type { CancellationSignal } from "../../utils/task_canceller";
35
- import MediaKeysInfosStore from "./utils/media_keys_infos_store";
35
+ import MediaKeysAttacher from "./utils/media_keys_attacher";
36
36
 
37
37
  type MediaKeysRequirement = "optional" | "required" | "not-allowed";
38
38
 
@@ -81,59 +81,81 @@ interface IKeySystemType {
81
81
  * system.
82
82
  */
83
83
  keyName: string | undefined;
84
- /** keyType: keySystem type (e.g. "com.widevine.alpha") */
84
+ /** KeySystem type (e.g. "com.widevine.alpha") */
85
85
  keyType: string;
86
- /** keySystem {Object}: the original keySystem object */
86
+ /** The original keySystem object */
87
87
  keySystemOptions: IKeySystemOption;
88
88
  }
89
89
 
90
90
  /**
91
- * @param {Object} keySystem
92
- * @param {Object} askedConfiguration
93
- * @param {MediaKeySystemAccess} currentKeySystemAccess
94
- * @param {Object} currentKeySystemOptions
95
- * @returns {null|Object}
91
+ * Takes a `newConfiguration` `MediaKeySystemConfiguration`, that is intended
92
+ * for the creation of a `MediaKeySystemAccess`, and a `prevConfiguration`
93
+ * `MediaKeySystemConfiguration`, that was the one used at creation of the
94
+ * current `MediaKeySystemAccess`.
95
+ *
96
+ * This function will then return `true` if it determined that the new
97
+ * configuration is conceptually compatible with the one used before, and
98
+ * `false` otherwise.
99
+ * @param {Object} newConfiguration - New wanted `MediaKeySystemConfiguration`
100
+ * @param {Object} prevConfiguration - The `MediaKeySystemConfiguration` that is
101
+ * relied on util now.
102
+ * @returns {boolean} - `true` if `newConfiguration` is compatible with
103
+ * `prevConfiguration`.
96
104
  */
97
- function checkCachedMediaKeySystemAccess(
98
- keySystem: IKeySystemOption,
99
- askedConfiguration: MediaKeySystemConfiguration,
100
- currentKeySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess,
101
- currentKeySystemOptions: IKeySystemOption,
102
- ): null | {
103
- keySystemOptions: IKeySystemOption;
104
- askedConfiguration: MediaKeySystemConfiguration;
105
- keySystemAccess: MediaKeySystemAccess | ICustomMediaKeySystemAccess;
106
- } {
107
- const mksConfiguration = currentKeySystemAccess.getConfiguration();
108
- if (shouldRenewMediaKeySystemAccess() || isNullOrUndefined(mksConfiguration)) {
109
- return null;
105
+ function isNewMediaKeySystemConfigurationCompatibleWithPreviousOne(
106
+ newConfiguration: MediaKeySystemConfiguration,
107
+ prevConfiguration: MediaKeySystemConfiguration,
108
+ ): boolean {
109
+ if (newConfiguration.label !== prevConfiguration.label) {
110
+ return false;
110
111
  }
111
112
 
112
- // TODO Do it with MediaKeySystemAccess.prototype.keySystem instead
113
- if (keySystem.type !== currentKeySystemOptions.type) {
114
- return null;
113
+ const prevDistinctiveIdentifier = prevConfiguration.distinctiveIdentifier ?? "optional";
114
+ const newDistinctiveIdentifier = newConfiguration.distinctiveIdentifier ?? "optional";
115
+ if (prevDistinctiveIdentifier !== newDistinctiveIdentifier) {
116
+ return false;
115
117
  }
116
118
 
117
- if (
118
- (!isNullOrUndefined(keySystem.persistentLicenseConfig) ||
119
- keySystem.persistentState === "required") &&
120
- mksConfiguration.persistentState !== "required"
121
- ) {
122
- return null;
119
+ const prevPersistentState = prevConfiguration.persistentState ?? "optional";
120
+ const newPersistentState = newConfiguration.persistentState ?? "optional";
121
+ if (prevPersistentState !== newPersistentState) {
122
+ return false;
123
123
  }
124
124
 
125
- if (
126
- keySystem.distinctiveIdentifier === "required" &&
127
- mksConfiguration.distinctiveIdentifier !== "required"
128
- ) {
129
- return null;
125
+ const prevInitDataTypes = prevConfiguration.initDataTypes ?? [];
126
+ const newInitDataTypes = newConfiguration.initDataTypes ?? [];
127
+ if (!isArraySubsetOf(newInitDataTypes, prevInitDataTypes)) {
128
+ return false;
130
129
  }
131
130
 
132
- return {
133
- keySystemOptions: keySystem,
134
- keySystemAccess: currentKeySystemAccess,
135
- askedConfiguration,
136
- };
131
+ const prevSessionTypes = prevConfiguration.sessionTypes ?? [];
132
+ const newSessionTypes = newConfiguration.sessionTypes ?? [];
133
+ if (!isArraySubsetOf(newSessionTypes, prevSessionTypes)) {
134
+ return false;
135
+ }
136
+
137
+ for (const prop of ["audioCapabilities", "videoCapabilities"] as const) {
138
+ const newCapabilities = newConfiguration[prop] ?? [];
139
+ const prevCapabilities = prevConfiguration[prop] ?? [];
140
+ const wasFound = newCapabilities.every((n) => {
141
+ for (let i = 0; i < prevCapabilities.length; i++) {
142
+ const prevCap = prevCapabilities[i];
143
+ if (
144
+ (prevCap.robustness ?? "") === (n.robustness ?? "") ||
145
+ (prevCap.encryptionScheme ?? null) === (n.encryptionScheme ?? null) ||
146
+ (prevCap.robustness ?? "") === (n.robustness ?? "")
147
+ ) {
148
+ return true;
149
+ }
150
+ }
151
+ return false;
152
+ });
153
+ if (!wasFound) {
154
+ return false;
155
+ }
156
+ }
157
+
158
+ return true;
137
159
  }
138
160
 
139
161
  /**
@@ -162,13 +184,23 @@ function buildKeySystemConfigurations(
162
184
  keySystemTypeInfo: IKeySystemType,
163
185
  ): MediaKeySystemConfiguration[] {
164
186
  const { keyName, keyType, keySystemOptions: keySystem } = keySystemTypeInfo;
165
- const sessionTypes = ["temporary"];
187
+ let sessionTypes: string[];
166
188
  let persistentState: MediaKeysRequirement = "optional";
167
189
  let distinctiveIdentifier: MediaKeysRequirement = "optional";
168
190
 
169
- if (!isNullOrUndefined(keySystem.persistentLicenseConfig)) {
191
+ if (Array.isArray(keySystem.wantedSessionTypes)) {
192
+ sessionTypes = keySystem.wantedSessionTypes;
193
+ if (
194
+ arrayIncludes(keySystem.wantedSessionTypes, "persistent-license") &&
195
+ !isNullOrUndefined(keySystem.persistentLicenseConfig)
196
+ ) {
197
+ persistentState = "required";
198
+ }
199
+ } else if (!isNullOrUndefined(keySystem.persistentLicenseConfig)) {
170
200
  persistentState = "required";
171
- sessionTypes.push("persistent-license");
201
+ sessionTypes = ["persistent-license"];
202
+ } else {
203
+ sessionTypes = ["temporary"];
172
204
  }
173
205
 
174
206
  if (!isNullOrUndefined(keySystem.persistentState)) {
@@ -384,14 +416,7 @@ export default function getMediaKeySystemAccess(
384
416
  cancelSignal: CancellationSignal,
385
417
  ): Promise<IFoundMediaKeySystemAccessEvent> {
386
418
  log.info("DRM: Searching for compatible MediaKeySystemAccess");
387
- /**
388
- * Array of set keySystems for this content.
389
- * Each item of this array is an object containing the following keys:
390
- * - keyName {string}: keySystem canonical name (e.g. "widevine")
391
- * - keyType {string}: keySystem type (e.g. "com.widevine.alpha")
392
- * - keySystem {Object}: the original keySystem object
393
- * @type {Array.<Object>}
394
- */
419
+ /** Array of set keySystems for this content. */
395
420
  const keySystemsType: IKeySystemType[] = keySystemsConfigs.reduce(
396
421
  (arr: IKeySystemType[], keySystemOptions) => {
397
422
  const { EME_KEY_SYSTEMS } = config.getCurrent();
@@ -444,36 +469,6 @@ export default function getMediaKeySystemAccess(
444
469
  }
445
470
 
446
471
  const chosenType = keySystemsType[index];
447
-
448
- const currentState = MediaKeysInfosStore.getState(mediaElement);
449
- if (currentState !== null) {
450
- if (eme.implementation === currentState.emeImplementation.implementation) {
451
- // Fast way to find a compatible keySystem if the currently loaded
452
- // one as exactly the same compatibility options.
453
- const cachedKeySystemAccess = checkCachedMediaKeySystemAccess(
454
- chosenType.keySystemOptions,
455
- currentState.askedConfiguration,
456
- currentState.mediaKeySystemAccess,
457
- currentState.keySystemOptions,
458
- );
459
- if (cachedKeySystemAccess !== null) {
460
- log.info("DRM: Found cached compatible keySystem");
461
- return Promise.resolve({
462
- type: "reuse-media-key-system-access" as const,
463
- value: {
464
- mediaKeySystemAccess: cachedKeySystemAccess.keySystemAccess,
465
- askedConfiguration: cachedKeySystemAccess.askedConfiguration,
466
- options: cachedKeySystemAccess.keySystemOptions,
467
- codecSupport: extractCodecSupportListFromConfiguration(
468
- cachedKeySystemAccess.askedConfiguration,
469
- cachedKeySystemAccess.keySystemAccess.getConfiguration(),
470
- ),
471
- },
472
- });
473
- }
474
- }
475
- }
476
-
477
472
  const { keyType, keySystemOptions } = chosenType;
478
473
 
479
474
  const keySystemConfigurations = buildKeySystemConfigurations(chosenType);
@@ -484,8 +479,37 @@ export default function getMediaKeySystemAccess(
484
479
  );
485
480
 
486
481
  let keySystemAccess;
482
+ const currentState = await MediaKeysAttacher.getAttachedMediaKeysState(mediaElement);
487
483
  for (let configIdx = 0; configIdx < keySystemConfigurations.length; configIdx++) {
488
484
  const keySystemConfiguration = keySystemConfigurations[configIdx];
485
+
486
+ // Check if the current `MediaKeySystemAccess` created cannot be reused here
487
+ if (
488
+ currentState !== null &&
489
+ !shouldRenewMediaKeySystemAccess() &&
490
+ // TODO: Do it with MediaKeySystemAccess.prototype.keySystem instead?
491
+ keyType === currentState.mediaKeySystemAccess.keySystem &&
492
+ eme.implementation === currentState.emeImplementation.implementation &&
493
+ isNewMediaKeySystemConfigurationCompatibleWithPreviousOne(
494
+ keySystemConfiguration,
495
+ currentState.askedConfiguration,
496
+ )
497
+ ) {
498
+ log.info("DRM: Found cached compatible keySystem");
499
+ return Promise.resolve({
500
+ type: "reuse-media-key-system-access" as const,
501
+ value: {
502
+ mediaKeySystemAccess: currentState.mediaKeySystemAccess,
503
+ askedConfiguration: currentState.askedConfiguration,
504
+ options: keySystemOptions,
505
+ codecSupport: extractCodecSupportListFromConfiguration(
506
+ currentState.askedConfiguration,
507
+ currentState.mediaKeySystemAccess.getConfiguration(),
508
+ ),
509
+ },
510
+ });
511
+ }
512
+
489
513
  try {
490
514
  keySystemAccess = await testKeySystem(keyType, [keySystemConfiguration]);
491
515
  log.info("DRM: Found compatible keysystem", keyType, index + 1);
@@ -544,3 +568,18 @@ export async function testKeySystem(
544
568
  }
545
569
  return keySystemAccess;
546
570
  }
571
+
572
+ /**
573
+ * Returns `true` if `arr1`'s values are entirely contained in `arr2`.
574
+ * @param {string} arr1
575
+ * @param {string} arr2
576
+ * @return {boolean}
577
+ */
578
+ function isArraySubsetOf(arr1: string[], arr2: string[]): boolean {
579
+ for (let i = 0; i < arr1.length; i++) {
580
+ if (!arrayIncludes(arr2, arr1[i])) {
581
+ return false;
582
+ }
583
+ }
584
+ return true;
585
+ }
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import type { IMediaElement } from "../../compat/browser_compatibility_types";
18
- import MediaKeysInfosStore from "./utils/media_keys_infos_store";
18
+ import MediaKeysAttacher from "./utils/media_keys_attacher";
19
19
 
20
20
  /**
21
21
  * Returns the name of the current key system used as well as its configuration,
@@ -26,7 +26,7 @@ import MediaKeysInfosStore from "./utils/media_keys_infos_store";
26
26
  export default function getKeySystemConfiguration(
27
27
  mediaElement: IMediaElement,
28
28
  ): [string, MediaKeySystemConfiguration] | null {
29
- const currentState = MediaKeysInfosStore.getState(mediaElement);
29
+ const currentState = MediaKeysAttacher.getAwaitedState(mediaElement);
30
30
  if (currentState === null) {
31
31
  return null;
32
32
  }
@@ -26,7 +26,7 @@ import getMediaKeySystemAccess from "./find_key_system";
26
26
  import type { ICodecSupportList } from "./find_key_system";
27
27
  import type { IMediaKeySessionStores } from "./types";
28
28
  import LoadedSessionsStore from "./utils/loaded_sessions_store";
29
- import MediaKeysInfosStore from "./utils/media_keys_infos_store";
29
+ import MediaKeysAttacher from "./utils/media_keys_attacher";
30
30
  import PersistentSessionsStore from "./utils/persistent_sessions_store";
31
31
  import ServerCertificateStore from "./utils/server_certificate_store";
32
32
 
@@ -94,7 +94,7 @@ export default async function getMediaKeysInfos(
94
94
  }
95
95
 
96
96
  const { options, mediaKeySystemAccess, askedConfiguration, codecSupport } = evt.value;
97
- const currentState = MediaKeysInfosStore.getState(mediaElement);
97
+ const currentState = await MediaKeysAttacher.getAttachedMediaKeysState(mediaElement);
98
98
  const persistentSessionsStore = createPersistentSessionsStorage(options);
99
99
 
100
100
  if (
@@ -18,9 +18,9 @@ import type { IMediaElement } from "../../compat/browser_compatibility_types";
18
18
  import log from "../../log";
19
19
  import type { IKeySystemOption } from "../../public_types";
20
20
  import type { CancellationSignal } from "../../utils/task_canceller";
21
- import { disableMediaKeys } from "./attach_media_keys";
22
21
  import type { IMediaKeysInfos } from "./get_media_keys";
23
22
  import getMediaKeysInfos from "./get_media_keys";
23
+ import MediaKeysAttacher from "./utils/media_keys_attacher";
24
24
 
25
25
  /**
26
26
  * Get media keys infos from key system configs then attach media keys to media element.
@@ -48,7 +48,7 @@ export default async function initMediaKeys(
48
48
 
49
49
  if (shouldDisableOldMediaKeys) {
50
50
  log.debug("DRM: Disabling old MediaKeys");
51
- await disableMediaKeys(mediaElement);
51
+ await MediaKeysAttacher.clearMediaKeys(mediaElement);
52
52
  }
53
53
  return mediaKeysInfo;
54
54
  }
@@ -0,0 +1,284 @@
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 type { IMediaElement } from "../../../compat/browser_compatibility_types";
18
+ import type {
19
+ ICustomMediaKeys,
20
+ ICustomMediaKeySystemAccess,
21
+ IEmeApiImplementation,
22
+ } from "../../../compat/eme";
23
+ import { setMediaKeys } from "../../../compat/eme/set_media_keys";
24
+ import { EncryptedMediaError } from "../../../errors";
25
+ import log from "../../../log";
26
+ import type { IKeySystemOption } from "../../../public_types";
27
+ import isNullOrUndefined from "../../../utils/is_null_or_undefined";
28
+ import type LoadedSessionsStore from "./loaded_sessions_store";
29
+
30
+ /** DRM-related state that can be associated to a single HTMLMediaElement. */
31
+ export interface IMediaElementMediaKeysInfos {
32
+ emeImplementation: IEmeApiImplementation;
33
+
34
+ /** Last keySystemOptions used with that HTMLMediaElement. */
35
+ keySystemOptions: IKeySystemOption;
36
+
37
+ /**
38
+ * The actual MediaKeySystemConfiguration asked to the
39
+ * `requestMediaKeySystemAccess` API.
40
+ */
41
+ askedConfiguration: MediaKeySystemConfiguration;
42
+
43
+ /**
44
+ * Last MediaKeySystemAccess used to create a MediaKeys bound to that
45
+ * HTMLMediaElement.
46
+ */
47
+ mediaKeySystemAccess: ICustomMediaKeySystemAccess | MediaKeySystemAccess;
48
+
49
+ /** Last MediaKeys instance bound to that HTMLMediaElement. */
50
+ mediaKeys: MediaKeys | ICustomMediaKeys;
51
+
52
+ /**
53
+ * Store containing information about every MediaKeySession active on the
54
+ * MediaKeys instance bound to that HTMLMediaElement.
55
+ */
56
+ loadedSessionsStore: LoadedSessionsStore;
57
+ }
58
+
59
+ // Store the MediaKeys infos attached to a media element.
60
+ const currentMediaState = new WeakMap<IMediaElement, IMediaKeysAttacherItem>();
61
+
62
+ interface IMediaKeysAttacherItem {
63
+ /**
64
+ * Information on `MediaKeys` which has been attached or is being attached
65
+ * to the `HTMLMediaElement`.
66
+ *
67
+ * `null` either if no `MediaKeys` is attached or if the last attachment
68
+ * task failed.
69
+ */
70
+ mediaKeysState: IMediaElementMediaKeysInfos | null;
71
+
72
+ /**
73
+ * Property set to a Promise **only** when the `MediaKeys` attachment is
74
+ * pending.
75
+ *
76
+ * This promise resolves once `mediaKeys` is attached to that
77
+ * `HTMLMediaElement`, and rejects if it failed to do so.
78
+ *
79
+ * `null` if no `MediaKeys` attachment is pending (it is already attached
80
+ * or it already failed to do so).
81
+ */
82
+ pendingTask: Promise<void> | null;
83
+ }
84
+
85
+ export default {
86
+ /**
87
+ * Attach new MediaKeys infos set on a HMTLMediaElement.
88
+ * @param {HTMLMediaElement} mediaElement
89
+ * @param {Object} mediaKeysInfo
90
+ * @returns {Promise}
91
+ */
92
+ async attach(
93
+ mediaElement: IMediaElement,
94
+ mediaKeysInfo: IMediaElementMediaKeysInfos,
95
+ ): Promise<void> {
96
+ const previousState = currentMediaState.get(mediaElement);
97
+ const pendingTask = attachMediaKeys(mediaElement, previousState, mediaKeysInfo).then(
98
+ () => {
99
+ currentMediaState.set(mediaElement, {
100
+ pendingTask: null,
101
+ mediaKeysState: mediaKeysInfo,
102
+ });
103
+ },
104
+ () => {
105
+ currentMediaState.set(mediaElement, {
106
+ pendingTask: null,
107
+ mediaKeysState: null,
108
+ });
109
+ },
110
+ );
111
+ currentMediaState.set(mediaElement, {
112
+ pendingTask,
113
+ mediaKeysState: mediaKeysInfo,
114
+ });
115
+ return pendingTask;
116
+ },
117
+
118
+ /**
119
+ * Get MediaKeys information expected to be linked to the given
120
+ * `HTMLMediaElement`.
121
+ *
122
+ * Unlike `getAttachedMediaKeysState`, this method is synchronous and will
123
+ * also return the expected state when `MediaKeys` attachment is still
124
+ * pending and thus when that state is not truly applied (and where it
125
+ * might fail before being applied).
126
+ *
127
+ * As such, only call this method if you want the currently expected state,
128
+ * not the actual one.
129
+ * @param {HTMLMediaElement} mediaElement
130
+ * @returns {Array}
131
+ */
132
+ getAwaitedState(mediaElement: IMediaElement): IMediaElementMediaKeysInfos | null {
133
+ const currentState = currentMediaState.get(mediaElement);
134
+ return currentState?.mediaKeysState ?? null;
135
+ },
136
+
137
+ /**
138
+ * Get MediaKeys information set on a HMTLMediaElement.
139
+ *
140
+ * This method is asynchronous because that state may still be in a process
141
+ * of being attached to the `HTMLMediaElement` (and the state we're
142
+ * currently setting may not work out).
143
+ * @param {HTMLMediaElement} mediaElement
144
+ * @returns {Object|null}
145
+ */
146
+ async getAttachedMediaKeysState(
147
+ mediaElement: IMediaElement,
148
+ ): Promise<IMediaElementMediaKeysInfos | null> {
149
+ const currentState = currentMediaState.get(mediaElement);
150
+ if (currentState === undefined) {
151
+ return null;
152
+ }
153
+ if (currentState.pendingTask !== null) {
154
+ await currentState.pendingTask;
155
+ return this.getAttachedMediaKeysState(mediaElement);
156
+ }
157
+ return currentState.mediaKeysState;
158
+ },
159
+
160
+ /**
161
+ * Remove MediaKeys currently set on a HMTLMediaElement and update state
162
+ * accordingly.
163
+ * @param {HTMLMediaElement} mediaElement
164
+ * @returns {Promise}
165
+ */
166
+ clearMediaKeys(mediaElement: IMediaElement): Promise<void> {
167
+ const previousState = currentMediaState.get(mediaElement);
168
+ const pendingTask = clearMediaKeys(mediaElement, previousState).then(
169
+ () => {
170
+ currentMediaState.set(mediaElement, {
171
+ pendingTask: null,
172
+ mediaKeysState: null,
173
+ });
174
+ },
175
+ () => {
176
+ currentMediaState.set(mediaElement, {
177
+ pendingTask: null,
178
+ mediaKeysState: null,
179
+ });
180
+ },
181
+ );
182
+ currentMediaState.set(mediaElement, {
183
+ pendingTask,
184
+ mediaKeysState: null,
185
+ });
186
+ return pendingTask;
187
+ },
188
+ };
189
+
190
+ /**
191
+ * Ensure that the last `MediaKeys` set on the given HTMLMediaElement is
192
+ * attached.
193
+ *
194
+ * The returned Promise never rejects, it will just log an error if the
195
+ * previous attachment failed.
196
+ *
197
+ * @param {Object} previousState
198
+ * @returns {Promise.<undefined>}
199
+ */
200
+ async function awaitMediaKeysAttachment(
201
+ previousState: IMediaKeysAttacherItem,
202
+ ): Promise<void> {
203
+ const promise = previousState.pendingTask;
204
+ if (isNullOrUndefined(promise)) {
205
+ return;
206
+ }
207
+ log.info("DRM: Awaiting previous MediaKeys attachment operation");
208
+ try {
209
+ await previousState.pendingTask;
210
+ } catch (err) {
211
+ log.info(
212
+ "DRM: previous MediaKeys attachment operation failed",
213
+ err instanceof Error ? err : "Unknown error",
214
+ );
215
+ }
216
+ }
217
+
218
+ async function attachMediaKeys(
219
+ mediaElement: IMediaElement,
220
+ previousState: IMediaKeysAttacherItem | undefined,
221
+ mediaKeysInfo: IMediaElementMediaKeysInfos,
222
+ ): Promise<void> {
223
+ if (previousState !== undefined) {
224
+ if (previousState.pendingTask !== null) {
225
+ // Ensure the `MediaKeys` has been fully attached to the HTMLMediaElement before
226
+ // resetting things, to avoid browser errors due to an invalid state.
227
+ await awaitMediaKeysAttachment(previousState);
228
+ }
229
+
230
+ const closeAllSessions =
231
+ !isNullOrUndefined(previousState.mediaKeysState) &&
232
+ previousState.mediaKeysState.loadedSessionsStore !==
233
+ mediaKeysInfo.loadedSessionsStore
234
+ ? previousState.mediaKeysState.loadedSessionsStore.closeAllSessions()
235
+ : Promise.resolve();
236
+
237
+ await closeAllSessions;
238
+
239
+ if (mediaElement.mediaKeys === mediaKeysInfo.mediaKeys) {
240
+ log.debug("DRM: Right MediaKeys already set");
241
+ return;
242
+ }
243
+ }
244
+
245
+ log.info("DRM: Attaching MediaKeys to the media element");
246
+ try {
247
+ await setMediaKeys(
248
+ mediaKeysInfo.emeImplementation,
249
+ mediaElement,
250
+ mediaKeysInfo.mediaKeys,
251
+ );
252
+ log.info("DRM: MediaKeys attached with success");
253
+ } catch (err) {
254
+ const errMessage = err instanceof Error ? err.toString() : "Unknown Error";
255
+ throw new EncryptedMediaError(
256
+ "MEDIA_KEYS_ATTACHMENT_ERROR",
257
+ "Could not attach the MediaKeys to the media element: " + errMessage,
258
+ );
259
+ }
260
+ }
261
+
262
+ async function clearMediaKeys(
263
+ mediaElement: IMediaElement,
264
+ previousState: IMediaKeysAttacherItem | undefined,
265
+ ): Promise<unknown> {
266
+ if (previousState === undefined) {
267
+ return;
268
+ }
269
+
270
+ if (previousState.pendingTask !== null) {
271
+ // Ensure the `MediaKeys` has been fully attached to the HTMLMediaElement before
272
+ // resetting things, to avoid browser errors due to an invalid state.
273
+ await awaitMediaKeysAttachment(previousState);
274
+ }
275
+
276
+ if (previousState.mediaKeysState === null) {
277
+ return;
278
+ }
279
+
280
+ log.info("DRM: Disposing of the current MediaKeys");
281
+ const { loadedSessionsStore } = previousState.mediaKeysState;
282
+ await loadedSessionsStore.closeAllSessions();
283
+ return setMediaKeys(previousState.mediaKeysState.emeImplementation, mediaElement, null);
284
+ }
@@ -171,15 +171,16 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
171
171
  if (position.isAwaitingFuturePosition()) {
172
172
  playbackRateUpdater.stopRebuffering();
173
173
  log.debug("Init: let rebuffering happen as we're awaiting a future position");
174
- this.trigger("stalled", stalledReason);
175
- return;
174
+ } else {
175
+ playbackRateUpdater.startRebuffering();
176
176
  }
177
177
 
178
- playbackRateUpdater.startRebuffering();
179
-
180
178
  if (
181
179
  this._manifest === null ||
182
180
  (isSeekingApproximate &&
181
+ // Don't handle discontinuities on devices with broken seeks before
182
+ // enough time have passed because seeking brings more risks to
183
+ // lead to a lengthy rebuffering-exiting process
183
184
  getMonotonicTimeStamp() - rebuffering.timestamp <= 1000)
184
185
  ) {
185
186
  this.trigger("stalled", stalledReason);
@@ -189,6 +190,16 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
189
190
  /** Position at which data is awaited. */
190
191
  const { position: stalledPosition } = rebuffering;
191
192
 
193
+ /**
194
+ * We may still be in the process of waiting for a position to be seeked
195
+ * to. When calculating a potential position to e.g. skip over
196
+ * discontinuities, we should compare it to that "target" position if
197
+ * one, not the one we're currently playing.
198
+ */
199
+ const targetTime = observation.position.isAwaitingFuturePosition()
200
+ ? observation.position.getWanted()
201
+ : this._playbackObserver.getCurrentTime();
202
+
192
203
  if (
193
204
  stalledPosition !== null &&
194
205
  stalledPosition !== undefined &&
@@ -201,10 +212,10 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
201
212
  );
202
213
  if (skippableDiscontinuity !== null) {
203
214
  const realSeekTime = skippableDiscontinuity + 0.001;
204
- if (realSeekTime <= this._playbackObserver.getCurrentTime()) {
215
+ if (realSeekTime <= targetTime) {
205
216
  log.info(
206
217
  "Init: position to seek already reached, no seeking",
207
- this._playbackObserver.getCurrentTime(),
218
+ targetTime,
208
219
  realSeekTime,
209
220
  );
210
221
  } else {
@@ -243,7 +254,7 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
243
254
  nextBufferRangeGap < BUFFER_DISCONTINUITY_THRESHOLD
244
255
  ) {
245
256
  const seekTo = positionBlockedAt + nextBufferRangeGap + EPSILON;
246
- if (this._playbackObserver.getCurrentTime() < seekTo) {
257
+ if (targetTime < seekTo) {
247
258
  log.warn(
248
259
  "Init: discontinuity encountered inferior to the threshold",
249
260
  positionBlockedAt,
@@ -266,8 +277,7 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
266
277
  if (period.end !== undefined && period.end <= positionBlockedAt) {
267
278
  if (
268
279
  this._manifest.periods[i + 1].start > positionBlockedAt &&
269
- this._manifest.periods[i + 1].start >
270
- this._playbackObserver.getCurrentTime()
280
+ this._manifest.periods[i + 1].start > targetTime
271
281
  ) {
272
282
  const nextPeriod = this._manifest.periods[i + 1];
273
283
  this._playbackObserver.setCurrentTime(nextPeriod.start);
@@ -315,7 +325,7 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
315
325
  }
316
326
  const observation = this._playbackObserver.getReference().getValue();
317
327
  if (
318
- !observation.rebuffering ||
328
+ observation.rebuffering === null ||
319
329
  observation.paused ||
320
330
  this._speed.getValue() <= 0 ||
321
331
  (bufferType !== "audio" && bufferType !== "video")