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

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 (169) hide show
  1. package/CHANGELOG.md +24 -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/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  19. package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +4 -8
  20. package/dist/commonjs/core/stream/period/period_stream.js +1 -3
  21. package/dist/commonjs/core/stream/period/utils/get_adaptation_switch_strategy.js +1 -2
  22. package/dist/commonjs/experimental/tools/mediaCapabilitiesProber/api/index.d.ts.map +1 -1
  23. package/dist/commonjs/main_thread/api/public_api.js +4 -4
  24. package/dist/commonjs/main_thread/decrypt/clear_on_stop.d.ts.map +1 -1
  25. package/dist/commonjs/main_thread/decrypt/clear_on_stop.js +60 -14
  26. package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  27. package/dist/commonjs/main_thread/decrypt/content_decryptor.js +21 -9
  28. package/dist/commonjs/main_thread/decrypt/dispose_decryption_resources.d.ts.map +1 -1
  29. package/dist/commonjs/main_thread/decrypt/dispose_decryption_resources.js +2 -18
  30. package/dist/commonjs/main_thread/decrypt/find_key_system.d.ts.map +1 -1
  31. package/dist/commonjs/main_thread/decrypt/find_key_system.js +134 -68
  32. package/dist/commonjs/main_thread/decrypt/get_key_system_configuration.js +2 -2
  33. package/dist/commonjs/main_thread/decrypt/get_media_keys.js +5 -3
  34. package/dist/commonjs/main_thread/decrypt/init_media_keys.d.ts.map +1 -1
  35. package/dist/commonjs/main_thread/decrypt/init_media_keys.js +2 -2
  36. 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
  37. package/dist/commonjs/main_thread/decrypt/utils/media_keys_attacher.d.ts.map +1 -0
  38. package/dist/commonjs/main_thread/decrypt/utils/media_keys_attacher.js +282 -0
  39. package/dist/commonjs/main_thread/init/utils/rebuffering_controller.js +1 -1
  40. package/dist/commonjs/main_thread/text_displayer/html/text_track_cues_store.js +1 -1
  41. package/dist/commonjs/main_thread/tracks_store/track_dispatcher.d.ts.map +1 -1
  42. package/dist/commonjs/main_thread/tracks_store/track_dispatcher.js +3 -4
  43. package/dist/commonjs/main_thread/tracks_store/tracks_store.d.ts.map +1 -1
  44. package/dist/commonjs/main_thread/tracks_store/tracks_store.js +1 -1
  45. package/dist/commonjs/manifest/classes/representation.d.ts +15 -1
  46. package/dist/commonjs/manifest/classes/representation.d.ts.map +1 -1
  47. package/dist/commonjs/manifest/classes/representation.js +17 -0
  48. package/dist/commonjs/manifest/utils.d.ts +15 -0
  49. package/dist/commonjs/manifest/utils.d.ts.map +1 -1
  50. package/dist/commonjs/manifest/utils.js +28 -5
  51. package/dist/commonjs/parsers/manifest/dash/common/parse_representations.js +2 -2
  52. package/dist/commonjs/playback_observer/media_element_playback_observer.js +1 -1
  53. package/dist/commonjs/public_types.d.ts +44 -0
  54. package/dist/commonjs/public_types.d.ts.map +1 -1
  55. package/dist/commonjs/utils/flat_map.d.ts +2 -2
  56. package/dist/commonjs/utils/flat_map.js +2 -2
  57. package/dist/commonjs/utils/url-utils.d.ts.map +1 -1
  58. package/dist/commonjs/utils/url-utils.js +11 -10
  59. package/dist/commonjs/utils/xml-parser.d.ts.map +1 -1
  60. package/dist/commonjs/utils/xml-parser.js +10 -8
  61. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts +1 -1
  62. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  63. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  64. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  65. package/dist/es2017/compat/browser_compatibility_types.d.ts +7 -2
  66. package/dist/es2017/compat/browser_compatibility_types.d.ts.map +1 -1
  67. package/dist/es2017/compat/browser_compatibility_types.js +0 -1
  68. package/dist/es2017/compat/browser_detection.js +1 -1
  69. package/dist/es2017/compat/event_listeners.d.ts.map +1 -1
  70. package/dist/es2017/compat/event_listeners.js +2 -4
  71. package/dist/es2017/compat/has_mse_in_worker.d.ts.map +1 -1
  72. package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  73. package/dist/es2017/core/cmcd/cmcd_data_builder.js +1 -2
  74. package/dist/es2017/core/main/worker/send_message.d.ts.map +1 -1
  75. package/dist/es2017/core/main/worker/send_message.js +0 -1
  76. package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  77. package/dist/es2017/core/stream/adaptation/adaptation_stream.js +4 -6
  78. package/dist/es2017/core/stream/period/period_stream.js +1 -3
  79. package/dist/es2017/core/stream/period/utils/get_adaptation_switch_strategy.js +1 -2
  80. package/dist/es2017/experimental/tools/mediaCapabilitiesProber/api/index.d.ts.map +1 -1
  81. package/dist/es2017/main_thread/api/public_api.js +4 -4
  82. package/dist/es2017/main_thread/decrypt/clear_on_stop.d.ts.map +1 -1
  83. package/dist/es2017/main_thread/decrypt/clear_on_stop.js +3 -3
  84. package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  85. package/dist/es2017/main_thread/decrypt/content_decryptor.js +21 -9
  86. package/dist/es2017/main_thread/decrypt/dispose_decryption_resources.d.ts.map +1 -1
  87. package/dist/es2017/main_thread/decrypt/dispose_decryption_resources.js +2 -12
  88. package/dist/es2017/main_thread/decrypt/find_key_system.d.ts.map +1 -1
  89. package/dist/es2017/main_thread/decrypt/find_key_system.js +102 -56
  90. package/dist/es2017/main_thread/decrypt/get_key_system_configuration.js +2 -2
  91. package/dist/es2017/main_thread/decrypt/get_media_keys.js +2 -2
  92. package/dist/es2017/main_thread/decrypt/init_media_keys.d.ts.map +1 -1
  93. package/dist/es2017/main_thread/decrypt/init_media_keys.js +2 -2
  94. 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
  95. package/dist/es2017/main_thread/decrypt/utils/media_keys_attacher.d.ts.map +1 -0
  96. package/dist/es2017/main_thread/decrypt/utils/media_keys_attacher.js +180 -0
  97. package/dist/es2017/main_thread/init/utils/rebuffering_controller.js +1 -1
  98. package/dist/es2017/main_thread/text_displayer/html/text_track_cues_store.js +1 -1
  99. package/dist/es2017/main_thread/tracks_store/track_dispatcher.d.ts.map +1 -1
  100. package/dist/es2017/main_thread/tracks_store/track_dispatcher.js +3 -4
  101. package/dist/es2017/main_thread/tracks_store/tracks_store.d.ts.map +1 -1
  102. package/dist/es2017/main_thread/tracks_store/tracks_store.js +2 -2
  103. package/dist/es2017/manifest/classes/representation.d.ts +15 -1
  104. package/dist/es2017/manifest/classes/representation.d.ts.map +1 -1
  105. package/dist/es2017/manifest/classes/representation.js +17 -0
  106. package/dist/es2017/manifest/utils.d.ts +15 -0
  107. package/dist/es2017/manifest/utils.d.ts.map +1 -1
  108. package/dist/es2017/manifest/utils.js +27 -5
  109. package/dist/es2017/parsers/manifest/dash/common/parse_representations.js +2 -2
  110. package/dist/es2017/playback_observer/media_element_playback_observer.js +1 -1
  111. package/dist/es2017/public_types.d.ts +44 -0
  112. package/dist/es2017/public_types.d.ts.map +1 -1
  113. package/dist/es2017/utils/flat_map.d.ts +2 -2
  114. package/dist/es2017/utils/flat_map.js +2 -2
  115. package/dist/es2017/utils/url-utils.d.ts.map +1 -1
  116. package/dist/es2017/utils/url-utils.js +11 -10
  117. package/dist/es2017/utils/xml-parser.d.ts.map +1 -1
  118. package/dist/es2017/utils/xml-parser.js +10 -8
  119. package/dist/rx-player.js +494 -364
  120. package/dist/rx-player.min.js +18 -18
  121. package/dist/worker.js +6 -6
  122. package/package.json +21 -19
  123. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  124. package/src/compat/browser_compatibility_types.ts +22 -4
  125. package/src/compat/browser_detection.ts +1 -1
  126. package/src/compat/event_listeners.ts +2 -4
  127. package/src/compat/has_mse_in_worker.ts +1 -1
  128. package/src/core/cmcd/cmcd_data_builder.ts +1 -4
  129. package/src/core/main/worker/send_message.ts +4 -2
  130. package/src/core/stream/adaptation/adaptation_stream.ts +4 -7
  131. package/src/core/stream/period/period_stream.ts +3 -3
  132. package/src/core/stream/period/utils/get_adaptation_switch_strategy.ts +1 -2
  133. package/src/main_thread/api/public_api.ts +4 -4
  134. package/src/main_thread/decrypt/__tests__/__global__/media_key_system_access.test.ts +239 -6
  135. package/src/main_thread/decrypt/__tests__/find_key_system.test.ts +325 -0
  136. package/src/main_thread/decrypt/clear_on_stop.ts +3 -3
  137. package/src/main_thread/decrypt/content_decryptor.ts +26 -8
  138. package/src/main_thread/decrypt/dispose_decryption_resources.ts +2 -13
  139. package/src/main_thread/decrypt/find_key_system.ts +120 -81
  140. package/src/main_thread/decrypt/get_key_system_configuration.ts +2 -2
  141. package/src/main_thread/decrypt/get_media_keys.ts +2 -2
  142. package/src/main_thread/decrypt/init_media_keys.ts +2 -2
  143. package/src/main_thread/decrypt/utils/media_keys_attacher.ts +284 -0
  144. package/src/main_thread/init/utils/rebuffering_controller.ts +1 -1
  145. package/src/main_thread/text_displayer/html/text_track_cues_store.ts +1 -1
  146. package/src/main_thread/tracks_store/track_dispatcher.ts +3 -6
  147. package/src/main_thread/tracks_store/tracks_store.ts +2 -1
  148. package/src/manifest/classes/representation.ts +18 -1
  149. package/src/manifest/utils.ts +30 -9
  150. package/src/parsers/manifest/dash/common/parse_representations.ts +2 -2
  151. package/src/playback_observer/media_element_playback_observer.ts +1 -1
  152. package/src/public_types.ts +45 -0
  153. package/src/utils/__tests__/flat_map.test.ts +7 -20
  154. package/src/utils/flat_map.ts +2 -2
  155. package/src/utils/url-utils.ts +11 -10
  156. package/src/utils/xml-parser.ts +13 -8
  157. package/vitest.config.mjs +9 -4
  158. package/dist/commonjs/main_thread/decrypt/attach_media_keys.d.ts +0 -60
  159. package/dist/commonjs/main_thread/decrypt/attach_media_keys.d.ts.map +0 -1
  160. package/dist/commonjs/main_thread/decrypt/attach_media_keys.js +0 -124
  161. package/dist/commonjs/main_thread/decrypt/utils/media_keys_infos_store.d.ts.map +0 -1
  162. package/dist/commonjs/main_thread/decrypt/utils/media_keys_infos_store.js +0 -45
  163. package/dist/es2017/main_thread/decrypt/attach_media_keys.d.ts +0 -60
  164. package/dist/es2017/main_thread/decrypt/attach_media_keys.d.ts.map +0 -1
  165. package/dist/es2017/main_thread/decrypt/attach_media_keys.js +0 -73
  166. package/dist/es2017/main_thread/decrypt/utils/media_keys_infos_store.d.ts.map +0 -1
  167. package/dist/es2017/main_thread/decrypt/utils/media_keys_infos_store.js +0 -43
  168. package/src/main_thread/decrypt/attach_media_keys.ts +0 -125
  169. 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
+ }
@@ -315,7 +315,7 @@ export default class RebufferingController extends EventEmitter<IRebufferingCont
315
315
  }
316
316
  const observation = this._playbackObserver.getReference().getValue();
317
317
  if (
318
- !observation.rebuffering ||
318
+ observation.rebuffering === null ||
319
319
  observation.paused ||
320
320
  this._speed.getValue() <= 0 ||
321
321
  (bufferType !== "audio" && bufferType !== "video")
@@ -358,7 +358,7 @@ export default class TextTrackCuesStore {
358
358
  }
359
359
  }
360
360
 
361
- if (cuesBuffer.length) {
361
+ if (cuesBuffer.length > 0) {
362
362
  const lastCue = cuesBuffer[cuesBuffer.length - 1];
363
363
  if (areNearlyEqual(lastCue.end, start, relativeDelta)) {
364
364
  // Match the end of the previous cue to the start of the following one
@@ -4,6 +4,7 @@ import type {
4
4
  ITrackSwitchingMode,
5
5
  } from "../../core/types";
6
6
  import type { IAdaptationMetadata, IRepresentationMetadata } from "../../manifest";
7
+ import { isRepresentationPlayable } from "../../manifest";
7
8
  import type {
8
9
  IAudioRepresentationsSwitchingMode,
9
10
  IVideoRepresentationsSwitchingMode,
@@ -184,11 +185,7 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
184
185
  if (repSettings === null) {
185
186
  // unlocking
186
187
  playableRepresentations = trackInfo.adaptation.representations.filter(
187
- (representation) => {
188
- return (
189
- representation.isSupported === true && representation.decipherable !== false
190
- );
191
- },
188
+ (representation) => isRepresentationPlayable(representation) === true,
192
189
  );
193
190
 
194
191
  // No need to remove the previous content when unlocking
@@ -200,7 +197,7 @@ export default class TrackDispatcher extends EventEmitter<ITrackDispatcherEvent>
200
197
  arrayIncludes(representationIds, r.id),
201
198
  );
202
199
  playableRepresentations = representations.filter(
203
- (r) => r.isSupported === true && r.decipherable !== false,
200
+ (representation) => isRepresentationPlayable(representation) === true,
204
201
  );
205
202
  if (playableRepresentations.length === 0) {
206
203
  self.trigger("noPlayableLockedRepresentation", null);
@@ -30,6 +30,7 @@ import type {
30
30
  } from "../../manifest";
31
31
  import {
32
32
  getSupportedAdaptations,
33
+ isRepresentationPlayable,
33
34
  toAudioTrack,
34
35
  toTextTrack,
35
36
  toVideoTrack,
@@ -399,7 +400,7 @@ export default class TracksStore extends EventEmitter<ITracksStoreEvents> {
399
400
  return false;
400
401
  }
401
402
  const playableRepresentations = adaptation.representations.filter(
402
- (r) => r.isSupported === true && r.decipherable !== false,
403
+ (r) => isRepresentationPlayable(r) === true,
403
404
  );
404
405
  return playableRepresentations.length > 0;
405
406
  },