rx-player 4.5.0-dev.2026033100 → 4.5.0-dev.2026043000

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 (272) hide show
  1. package/CHANGELOG.md +25 -5
  2. package/VERSION +1 -1
  3. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +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/can_patch_out_pssh.d.ts +42 -0
  8. package/dist/commonjs/compat/can_patch_out_pssh.d.ts.map +1 -0
  9. package/dist/commonjs/compat/can_patch_out_pssh.js +53 -0
  10. package/dist/commonjs/compat/env_detector.d.ts +2 -0
  11. package/dist/commonjs/compat/env_detector.d.ts.map +1 -1
  12. package/dist/commonjs/compat/env_detector.js +5 -0
  13. package/dist/commonjs/core/adaptive/network_analyzer.d.ts +1 -2
  14. package/dist/commonjs/core/adaptive/network_analyzer.d.ts.map +1 -1
  15. package/dist/commonjs/core/adaptive/network_analyzer.js +3 -3
  16. package/dist/commonjs/core/adaptive/utils/representation_score_calculator.js +2 -2
  17. package/dist/commonjs/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  18. package/dist/commonjs/core/cmcd/cmcd_data_builder.js +9 -3
  19. package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -1
  20. package/dist/commonjs/core/entry/content_preparer.js +5 -7
  21. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  22. package/dist/commonjs/core/entry/content_time_boundaries_observer.js +18 -4
  23. package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -1
  24. package/dist/commonjs/core/entry/core_entry.js +6 -2
  25. package/dist/commonjs/core/entry/core_text_displayer_interface.js +3 -3
  26. package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.d.ts.map +1 -1
  27. package/dist/commonjs/core/fetchers/manifest/manifest_fetcher.js +12 -0
  28. package/dist/commonjs/core/fetchers/thumbnails/thumbnail_fetcher.js +1 -1
  29. package/dist/commonjs/core/fetchers/utils/schedule_request.d.ts.map +1 -1
  30. package/dist/commonjs/core/fetchers/utils/schedule_request.js +4 -3
  31. package/dist/commonjs/core/segment_sinks/garbage_collector.d.ts +0 -2
  32. package/dist/commonjs/core/segment_sinks/garbage_collector.d.ts.map +1 -1
  33. package/dist/commonjs/core/segment_sinks/garbage_collector.js +0 -3
  34. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.d.ts +1 -1
  35. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.d.ts.map +1 -1
  36. package/dist/commonjs/core/segment_sinks/implementations/text/text_segment_sink.js +2 -2
  37. package/dist/commonjs/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  38. package/dist/commonjs/core/stream/adaptation/adaptation_stream.js +6 -6
  39. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
  40. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +37 -40
  41. package/dist/commonjs/default_config.d.ts +5 -0
  42. package/dist/commonjs/default_config.d.ts.map +1 -1
  43. package/dist/commonjs/default_config.js +5 -0
  44. package/dist/commonjs/errors/index.d.ts +2 -2
  45. package/dist/commonjs/errors/index.d.ts.map +1 -1
  46. package/dist/commonjs/errors/index.js +2 -1
  47. package/dist/commonjs/errors/media_error.d.ts +19 -4
  48. package/dist/commonjs/errors/media_error.d.ts.map +1 -1
  49. package/dist/commonjs/errors/media_error.js +29 -0
  50. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  51. package/dist/commonjs/main_thread/api/public_api.js +23 -16
  52. package/dist/commonjs/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  53. package/dist/commonjs/main_thread/decrypt/content_decryptor.js +4 -1
  54. package/dist/commonjs/main_thread/decrypt/session_events_listener.js +1 -1
  55. package/dist/commonjs/main_thread/decrypt/set_server_certificate.d.ts +2 -0
  56. package/dist/commonjs/main_thread/decrypt/set_server_certificate.d.ts.map +1 -1
  57. package/dist/commonjs/main_thread/decrypt/set_server_certificate.js +4 -0
  58. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +0 -8
  59. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  60. package/dist/commonjs/main_thread/init/media_source_content_initializer.js +59 -54
  61. package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts +35 -5
  62. package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts.map +1 -1
  63. package/dist/commonjs/main_thread/init/utils/stream_events_emitter/stream_events_emitter.js +60 -19
  64. package/dist/commonjs/main_thread/render_thumbnail.d.ts.map +1 -1
  65. package/dist/commonjs/main_thread/render_thumbnail.js +4 -0
  66. package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.d.ts.map +1 -1
  67. package/dist/commonjs/main_thread/tracks_store/media_element_tracks_store.js +1 -0
  68. package/dist/commonjs/main_thread/tracks_store/tracks_store.d.ts +1 -1
  69. package/dist/commonjs/main_thread/tracks_store/tracks_store.js +1 -1
  70. package/dist/{es2017/parsers/containers/isobmff/take_pssh_out.d.ts → commonjs/parsers/containers/isobmff/extract_pssh.d.ts} +6 -4
  71. package/dist/commonjs/parsers/containers/isobmff/extract_pssh.d.ts.map +1 -0
  72. package/dist/commonjs/parsers/containers/isobmff/{take_pssh_out.js → extract_pssh.js} +22 -17
  73. package/dist/commonjs/parsers/containers/isobmff/index.d.ts +2 -2
  74. package/dist/commonjs/parsers/containers/isobmff/index.d.ts.map +1 -1
  75. package/dist/commonjs/parsers/containers/isobmff/index.js +4 -4
  76. package/dist/commonjs/playback_observer/core_playback_observer.d.ts +4 -4
  77. package/dist/commonjs/playback_observer/core_playback_observer.d.ts.map +1 -1
  78. package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts +1 -2
  79. package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts.map +1 -1
  80. package/dist/commonjs/transports/dash/segment_parser.js +1 -1
  81. package/dist/commonjs/transports/local/segment_parser.js +1 -1
  82. package/dist/commonjs/utils/test-utils.d.ts +30 -0
  83. package/dist/commonjs/utils/test-utils.d.ts.map +1 -0
  84. package/dist/commonjs/utils/test-utils.js +79 -0
  85. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  86. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  87. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  88. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  89. package/dist/es2017/compat/can_patch_out_pssh.d.ts +42 -0
  90. package/dist/es2017/compat/can_patch_out_pssh.d.ts.map +1 -0
  91. package/dist/es2017/compat/can_patch_out_pssh.js +50 -0
  92. package/dist/es2017/compat/env_detector.d.ts +2 -0
  93. package/dist/es2017/compat/env_detector.d.ts.map +1 -1
  94. package/dist/es2017/compat/env_detector.js +5 -0
  95. package/dist/es2017/core/adaptive/network_analyzer.d.ts +1 -2
  96. package/dist/es2017/core/adaptive/network_analyzer.d.ts.map +1 -1
  97. package/dist/es2017/core/adaptive/network_analyzer.js +3 -3
  98. package/dist/es2017/core/adaptive/utils/representation_score_calculator.js +2 -2
  99. package/dist/es2017/core/cmcd/cmcd_data_builder.d.ts.map +1 -1
  100. package/dist/es2017/core/cmcd/cmcd_data_builder.js +9 -3
  101. package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -1
  102. package/dist/es2017/core/entry/content_preparer.js +3 -5
  103. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -1
  104. package/dist/es2017/core/entry/content_time_boundaries_observer.js +18 -4
  105. package/dist/es2017/core/entry/core_entry.d.ts.map +1 -1
  106. package/dist/es2017/core/entry/core_entry.js +6 -2
  107. package/dist/es2017/core/entry/core_text_displayer_interface.js +3 -3
  108. package/dist/es2017/core/fetchers/manifest/manifest_fetcher.d.ts.map +1 -1
  109. package/dist/es2017/core/fetchers/manifest/manifest_fetcher.js +12 -0
  110. package/dist/es2017/core/fetchers/thumbnails/thumbnail_fetcher.js +1 -1
  111. package/dist/es2017/core/fetchers/utils/schedule_request.d.ts.map +1 -1
  112. package/dist/es2017/core/fetchers/utils/schedule_request.js +2 -3
  113. package/dist/es2017/core/segment_sinks/garbage_collector.d.ts +0 -2
  114. package/dist/es2017/core/segment_sinks/garbage_collector.d.ts.map +1 -1
  115. package/dist/es2017/core/segment_sinks/garbage_collector.js +0 -3
  116. package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.d.ts +1 -1
  117. package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.d.ts.map +1 -1
  118. package/dist/es2017/core/segment_sinks/implementations/text/text_segment_sink.js +2 -2
  119. package/dist/es2017/core/stream/adaptation/adaptation_stream.d.ts.map +1 -1
  120. package/dist/es2017/core/stream/adaptation/adaptation_stream.js +6 -6
  121. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
  122. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.js +40 -39
  123. package/dist/es2017/default_config.d.ts +5 -0
  124. package/dist/es2017/default_config.d.ts.map +1 -1
  125. package/dist/es2017/default_config.js +5 -0
  126. package/dist/es2017/errors/index.d.ts +2 -2
  127. package/dist/es2017/errors/index.d.ts.map +1 -1
  128. package/dist/es2017/errors/index.js +2 -2
  129. package/dist/es2017/errors/media_error.d.ts +19 -4
  130. package/dist/es2017/errors/media_error.d.ts.map +1 -1
  131. package/dist/es2017/errors/media_error.js +28 -0
  132. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  133. package/dist/es2017/main_thread/api/public_api.js +20 -13
  134. package/dist/es2017/main_thread/decrypt/content_decryptor.d.ts.map +1 -1
  135. package/dist/es2017/main_thread/decrypt/content_decryptor.js +4 -1
  136. package/dist/es2017/main_thread/decrypt/session_events_listener.js +1 -1
  137. package/dist/es2017/main_thread/decrypt/set_server_certificate.d.ts +2 -0
  138. package/dist/es2017/main_thread/decrypt/set_server_certificate.d.ts.map +1 -1
  139. package/dist/es2017/main_thread/decrypt/set_server_certificate.js +4 -0
  140. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +0 -8
  141. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  142. package/dist/es2017/main_thread/init/media_source_content_initializer.js +60 -55
  143. package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts +35 -5
  144. package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.d.ts.map +1 -1
  145. package/dist/es2017/main_thread/init/utils/stream_events_emitter/stream_events_emitter.js +60 -19
  146. package/dist/es2017/main_thread/render_thumbnail.d.ts.map +1 -1
  147. package/dist/es2017/main_thread/render_thumbnail.js +4 -0
  148. package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.d.ts.map +1 -1
  149. package/dist/es2017/main_thread/tracks_store/media_element_tracks_store.js +1 -0
  150. package/dist/es2017/main_thread/tracks_store/tracks_store.d.ts +1 -1
  151. package/dist/es2017/main_thread/tracks_store/tracks_store.js +1 -1
  152. package/dist/{commonjs/parsers/containers/isobmff/take_pssh_out.d.ts → es2017/parsers/containers/isobmff/extract_pssh.d.ts} +6 -4
  153. package/dist/es2017/parsers/containers/isobmff/extract_pssh.d.ts.map +1 -0
  154. package/dist/es2017/parsers/containers/isobmff/{take_pssh_out.js → extract_pssh.js} +21 -16
  155. package/dist/es2017/parsers/containers/isobmff/index.d.ts +2 -2
  156. package/dist/es2017/parsers/containers/isobmff/index.d.ts.map +1 -1
  157. package/dist/es2017/parsers/containers/isobmff/index.js +2 -2
  158. package/dist/es2017/playback_observer/core_playback_observer.d.ts +4 -4
  159. package/dist/es2017/playback_observer/core_playback_observer.d.ts.map +1 -1
  160. package/dist/es2017/playback_observer/media_element_playback_observer.d.ts +1 -2
  161. package/dist/es2017/playback_observer/media_element_playback_observer.d.ts.map +1 -1
  162. package/dist/es2017/transports/dash/segment_parser.js +2 -2
  163. package/dist/es2017/transports/local/segment_parser.js +2 -2
  164. package/dist/es2017/utils/test-utils.d.ts +30 -0
  165. package/dist/es2017/utils/test-utils.d.ts.map +1 -0
  166. package/dist/es2017/utils/test-utils.js +36 -0
  167. package/dist/mpd-parser.wasm +0 -0
  168. package/dist/worker.js +6 -6
  169. package/package.json +5 -3
  170. package/src/README.md +7 -7
  171. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  172. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  173. package/src/compat/__tests__/can_patch_out_pssh.test.ts +40 -0
  174. package/src/compat/can_patch_out_pssh.ts +53 -0
  175. package/src/compat/env_detector.ts +4 -0
  176. package/src/core/README.md +1 -1
  177. package/src/core/adaptive/README.md +3 -3
  178. package/src/core/adaptive/__tests__/adaptive_representation_selector.test.ts +181 -110
  179. package/src/core/adaptive/__tests__/guess_based_chooser.test.ts +229 -123
  180. package/src/core/adaptive/__tests__/mocks.ts +100 -0
  181. package/src/core/adaptive/__tests__/network_analyzer.test.ts +152 -59
  182. package/src/core/adaptive/network_analyzer.ts +4 -4
  183. package/src/core/adaptive/utils/__tests__/filter_by_bitrate.test.ts +11 -19
  184. package/src/core/adaptive/utils/__tests__/filter_by_resolution.test.ts +12 -12
  185. package/src/core/adaptive/utils/__tests__/last_estimate_storage.test.ts +25 -12
  186. package/src/core/adaptive/utils/__tests__/pending_requests_store.test.ts +13 -9
  187. package/src/core/adaptive/utils/__tests__/representation_score_calculator.test.ts +11 -11
  188. package/src/core/adaptive/utils/__tests__/select_optimal_representation.test.ts +13 -23
  189. package/src/core/adaptive/utils/representation_score_calculator.ts +2 -2
  190. package/src/core/cmcd/__tests__/cmcd_data_builder.test.ts +60 -15
  191. package/src/core/cmcd/cmcd_data_builder.ts +9 -3
  192. package/src/core/entry/README.md +2 -2
  193. package/src/core/entry/__tests__/content_time_boundaries_observer.test.ts +5 -0
  194. package/src/core/entry/__tests__/core_text_displayer_interface.test.ts +20 -0
  195. package/src/core/entry/content_preparer.ts +2 -5
  196. package/src/core/entry/content_time_boundaries_observer.ts +18 -4
  197. package/src/core/entry/core_entry.ts +6 -2
  198. package/src/core/entry/core_text_displayer_interface.ts +3 -3
  199. package/src/core/fetchers/manifest/__tests__/manifest_fetcher.test.ts +52 -3
  200. package/src/core/fetchers/manifest/manifest_fetcher.ts +12 -0
  201. package/src/core/fetchers/thumbnails/__tests__/thumbnail_fetcher.test.ts +70 -0
  202. package/src/core/fetchers/thumbnails/thumbnail_fetcher.ts +1 -1
  203. package/src/core/fetchers/utils/schedule_request.ts +5 -3
  204. package/src/core/segment_sinks/__tests__/garbage_collector.test.ts +434 -0
  205. package/src/core/segment_sinks/__tests__/mocks.ts +49 -0
  206. package/src/core/segment_sinks/garbage_collector.ts +0 -3
  207. package/src/core/segment_sinks/implementations/text/__tests__/text_segment_sink.test.ts +177 -0
  208. package/src/core/segment_sinks/implementations/text/text_segment_sink.ts +2 -2
  209. package/src/core/segment_sinks/inventory/__tests__/buffered_history.test.ts +215 -0
  210. package/src/core/segment_sinks/inventory/__tests__/segment_inventory.test.ts +448 -0
  211. package/src/core/stream/adaptation/__tests__/adaptation_stream.test.ts +973 -0
  212. package/src/core/stream/adaptation/__tests__/get_representations_switch_strategy.test.ts +283 -374
  213. package/src/core/stream/adaptation/adaptation_stream.ts +6 -8
  214. package/src/core/stream/orchestrator/README.md +4 -4
  215. package/src/core/stream/orchestrator/__tests__/stream_orchestrator.test.ts +707 -0
  216. package/src/core/stream/orchestrator/stream_orchestrator.ts +41 -46
  217. package/src/core/stream/period/utils/__tests__/get_adaptation_switch_strategy.test.ts +290 -220
  218. package/src/core/stream/representation/__tests__/encryption_data_notifier.test.ts +93 -63
  219. package/src/core/stream/representation/utils/__tests__/append_segment_to_buffer.test.ts +106 -63
  220. package/src/core/stream/representation/utils/__tests__/check_for_discontinuity.test.ts +179 -204
  221. package/src/core/stream/representation/utils/__tests__/get_segment_priority.test.ts +7 -7
  222. package/src/core/stream/representation/utils/__tests__/push_init_segment.test.ts +103 -60
  223. package/src/core/stream/representation/utils/__tests__/push_media_segment.test.ts +231 -165
  224. package/src/default_config.ts +6 -0
  225. package/src/errors/__tests__/media_error.test.ts +106 -3
  226. package/src/errors/index.ts +2 -1
  227. package/src/errors/media_error.ts +63 -7
  228. package/src/experimental/README.md +1 -1
  229. package/src/features/README.md +3 -3
  230. package/src/main_thread/api/README.md +6 -7
  231. package/src/main_thread/api/public_api.ts +16 -10
  232. package/src/main_thread/decrypt/README.md +4 -4
  233. package/src/main_thread/decrypt/__tests__/__global__/content_decryptor.test.ts +135 -0
  234. package/src/main_thread/decrypt/__tests__/__global__/get_license.test.ts +70 -0
  235. package/src/main_thread/decrypt/__tests__/__global__/server_certificate.test.ts +44 -0
  236. package/src/main_thread/decrypt/__tests__/__global__/utils.ts +2 -2
  237. package/src/main_thread/decrypt/content_decryptor.ts +6 -1
  238. package/src/main_thread/decrypt/session_events_listener.ts +1 -1
  239. package/src/main_thread/decrypt/set_server_certificate.ts +5 -0
  240. package/src/main_thread/init/media_source_content_initializer.ts +72 -60
  241. package/src/main_thread/init/utils/__tests__/stream_events_emitter.test.ts +175 -0
  242. package/src/main_thread/init/utils/stream_events_emitter/stream_events_emitter.ts +90 -26
  243. package/src/main_thread/render_thumbnail.ts +4 -0
  244. package/src/main_thread/tracks_store/README.md +12 -0
  245. package/src/main_thread/tracks_store/__tests__/media_element_tracks_store.test.ts +25 -18
  246. package/src/main_thread/tracks_store/media_element_tracks_store.ts +1 -0
  247. package/src/main_thread/tracks_store/tracks_store.ts +1 -1
  248. package/src/manifest/classes/__tests__/mocks.ts +202 -0
  249. package/src/parsers/containers/isobmff/__tests__/extract_pssh.test.ts +199 -0
  250. package/src/parsers/containers/isobmff/{take_pssh_out.ts → extract_pssh.ts} +21 -17
  251. package/src/parsers/containers/isobmff/index.ts +2 -2
  252. package/src/parsers/manifest/dash/wasm-parser/README.md +9 -9
  253. package/src/playback_observer/__tests__/mocks.ts +152 -0
  254. package/src/playback_observer/core_playback_observer.ts +4 -4
  255. package/src/playback_observer/media_element_playback_observer.ts +1 -1
  256. package/src/tools/README.md +2 -2
  257. package/src/transports/README.md +5 -5
  258. package/src/transports/dash/segment_parser.ts +2 -2
  259. package/src/transports/local/segment_parser.ts +2 -2
  260. package/src/transports/metaplaylist/README.md +4 -4
  261. package/src/utils/README.md +3 -3
  262. package/src/utils/test-utils.ts +50 -0
  263. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts +0 -11
  264. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
  265. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.js +0 -20
  266. package/dist/commonjs/parsers/containers/isobmff/take_pssh_out.d.ts.map +0 -1
  267. package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts +0 -11
  268. package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
  269. package/dist/es2017/core/entry/synchronize_sinks_on_observation.js +0 -17
  270. package/dist/es2017/parsers/containers/isobmff/take_pssh_out.d.ts.map +0 -1
  271. package/src/core/adaptive/utils/__tests__/bandwith_estimator.test.ts +0 -117
  272. package/src/core/entry/synchronize_sinks_on_observation.ts +0 -22
@@ -1,10 +1,13 @@
1
1
  import { describe, it, expect } from "vitest";
2
- import MediaError from "../media_error";
2
+ import type { ISerializedMediaError } from "../media_error";
3
+ import MediaError, { deserializeMediaError } from "../media_error";
3
4
 
4
5
  describe("errors - MediaError", () => {
5
6
  it("should format a MediaError", () => {
6
7
  const reason = "test";
7
- const mediaError = new MediaError("MEDIA_TIME_BEFORE_MANIFEST", reason);
8
+ const mediaError = new MediaError("MEDIA_TIME_BEFORE_MANIFEST", reason, {
9
+ timeInfo: { position: 5, minPosition: 10, maxPosition: 100 },
10
+ });
8
11
  expect(mediaError).toBeInstanceOf(Error);
9
12
  expect(mediaError.name).toBe("MediaError");
10
13
  expect(mediaError.type).toBe("MEDIA_ERROR");
@@ -15,7 +18,9 @@ describe("errors - MediaError", () => {
15
18
 
16
19
  it("should be able to set it as fatal", () => {
17
20
  const reason = "test";
18
- const mediaError = new MediaError("MEDIA_TIME_AFTER_MANIFEST", reason);
21
+ const mediaError = new MediaError("MEDIA_TIME_AFTER_MANIFEST", reason, {
22
+ timeInfo: { position: 5, minPosition: 10, maxPosition: 100 },
23
+ });
19
24
  mediaError.fatal = true;
20
25
  expect(mediaError).toBeInstanceOf(Error);
21
26
  expect(mediaError.name).toBe("MediaError");
@@ -36,4 +41,102 @@ describe("errors - MediaError", () => {
36
41
  expect(mediaError.fatal).toBe(true);
37
42
  expect(mediaError.message).toBe("MEDIA_ERR_NETWORK: test");
38
43
  });
44
+
45
+ it("should expose and serialize custom position metadata", () => {
46
+ const mediaError = new MediaError("MEDIA_TIME_BEFORE_MANIFEST", "test", {
47
+ timeInfo: {
48
+ position: 3,
49
+ minPosition: 10,
50
+ maxPosition: 20,
51
+ },
52
+ });
53
+
54
+ expect(mediaError.timeInfo).toEqual({
55
+ position: 3,
56
+ minPosition: 10,
57
+ maxPosition: 20,
58
+ });
59
+ expect(mediaError.serialize()).toEqual({
60
+ isSerializedError: true,
61
+ name: "MediaError",
62
+ code: "MEDIA_TIME_BEFORE_MANIFEST",
63
+ reason: "test",
64
+ tracks: undefined,
65
+ timeInfo: {
66
+ position: 3,
67
+ minPosition: 10,
68
+ maxPosition: 20,
69
+ },
70
+ });
71
+ });
72
+
73
+ it("should deserialize a serialized MediaError with timeInfo", () => {
74
+ const serializedError: ISerializedMediaError = {
75
+ isSerializedError: true,
76
+ name: "MediaError",
77
+ code: "MEDIA_TIME_BEFORE_MANIFEST",
78
+ reason: "test",
79
+ tracks: undefined,
80
+ timeInfo: {
81
+ position: 3,
82
+ minPosition: 10,
83
+ maxPosition: 20,
84
+ },
85
+ };
86
+
87
+ const mediaError = deserializeMediaError(serializedError);
88
+
89
+ expect(mediaError).toBeInstanceOf(MediaError);
90
+ expect(mediaError.code).toBe("MEDIA_TIME_BEFORE_MANIFEST");
91
+ expect(mediaError.message).toBe("MEDIA_TIME_BEFORE_MANIFEST: test");
92
+ expect(mediaError.timeInfo).toEqual(serializedError.timeInfo);
93
+ });
94
+
95
+ it("should deserialize a serialized MediaError with tracks", () => {
96
+ const serializedError: ISerializedMediaError = {
97
+ isSerializedError: true,
98
+ name: "MediaError",
99
+ code: "BUFFER_APPEND_ERROR",
100
+ reason: "test",
101
+ tracks: [
102
+ {
103
+ type: "audio",
104
+ track: {
105
+ id: "fra1",
106
+ audioDescription: false,
107
+ language: "fra",
108
+ normalized: "fra",
109
+ representations: [],
110
+ },
111
+ },
112
+ ],
113
+ timeInfo: undefined,
114
+ };
115
+
116
+ const mediaError = deserializeMediaError(serializedError);
117
+
118
+ expect(mediaError).toBeInstanceOf(MediaError);
119
+ expect(mediaError.code).toBe("BUFFER_APPEND_ERROR");
120
+ expect(mediaError.message).toBe("BUFFER_APPEND_ERROR: test");
121
+ expect(mediaError.tracksInfo).toEqual(serializedError.tracks);
122
+ });
123
+
124
+ it("should deserialize a serialized MediaError without metadata", () => {
125
+ const serializedError = {
126
+ isSerializedError: true as const,
127
+ name: "MediaError" as const,
128
+ code: "MEDIA_ERR_NETWORK" as const,
129
+ reason: "test",
130
+ tracks: undefined,
131
+ timeInfo: undefined,
132
+ };
133
+
134
+ const mediaError = deserializeMediaError(serializedError);
135
+
136
+ expect(mediaError).toBeInstanceOf(MediaError);
137
+ expect(mediaError.code).toBe("MEDIA_ERR_NETWORK");
138
+ expect(mediaError.message).toBe("MEDIA_ERR_NETWORK: test");
139
+ expect(mediaError.timeInfo).toBeUndefined();
140
+ expect(mediaError.tracksInfo).toBeUndefined();
141
+ });
39
142
  });
@@ -22,7 +22,7 @@ import { ErrorCodes, ErrorTypes, NetworkErrorTypes } from "./error_codes";
22
22
  import formatError from "./format_error";
23
23
  import isKnownError from "./is_known_error";
24
24
  import type { ISerializedMediaError } from "./media_error";
25
- import MediaError from "./media_error";
25
+ import MediaError, { deserializeMediaError } from "./media_error";
26
26
  import type { ISerializedNetworkError } from "./network_error";
27
27
  import NetworkError from "./network_error";
28
28
  import type { ISerializedOtherError } from "./other_error";
@@ -46,6 +46,7 @@ export {
46
46
  ErrorTypes,
47
47
  formatError,
48
48
  MediaError,
49
+ deserializeMediaError,
49
50
  NetworkError,
50
51
  OtherError,
51
52
  NetworkErrorTypes,
@@ -15,16 +15,27 @@
15
15
  */
16
16
 
17
17
  import type { ITaggedTrack } from "../manifest";
18
+ import assert from "../utils/assert";
18
19
  import type { IMediaErrorCode } from "./error_codes";
19
20
  import { ErrorTypes } from "./error_codes";
20
21
  import errorMessage from "./error_message";
21
22
 
22
- type ICodeWithAdaptationType =
23
+ export type ICodeWithAdaptationType =
23
24
  | "BUFFER_APPEND_ERROR"
24
25
  | "BUFFER_FULL_ERROR"
25
26
  | "NO_PLAYABLE_REPRESENTATION"
26
27
  | "MANIFEST_INCOMPATIBLE_CODECS_ERROR";
27
28
 
29
+ export type ICodeWithManifestPositionError =
30
+ | "MEDIA_TIME_BEFORE_MANIFEST"
31
+ | "MEDIA_TIME_AFTER_MANIFEST";
32
+
33
+ interface ITimeInfo {
34
+ position: number;
35
+ minPosition: number;
36
+ maxPosition: number;
37
+ }
38
+
28
39
  /**
29
40
  * Error linked to the media Playback.
30
41
  *
@@ -36,6 +47,7 @@ export default class MediaError extends Error {
36
47
  public readonly type: "MEDIA_ERROR";
37
48
  public readonly code: IMediaErrorCode;
38
49
  public readonly tracksInfo: ITaggedTrack[] | undefined;
50
+ public readonly timeInfo: ITimeInfo | undefined;
39
51
  public fatal: boolean;
40
52
  private _originalMessage: string;
41
53
 
@@ -52,13 +64,18 @@ export default class MediaError extends Error {
52
64
  },
53
65
  );
54
66
  constructor(
55
- code: Exclude<IMediaErrorCode, ICodeWithAdaptationType>,
67
+ code: ICodeWithManifestPositionError,
68
+ reason: string,
69
+ context: {
70
+ timeInfo: ITimeInfo;
71
+ },
72
+ );
73
+ constructor(
74
+ code: Exclude<
75
+ IMediaErrorCode,
76
+ ICodeWithAdaptationType | ICodeWithManifestPositionError
77
+ >,
56
78
  reason: string,
57
- context?:
58
- | {
59
- tracks?: undefined;
60
- }
61
- | undefined,
62
79
  );
63
80
  constructor(
64
81
  code: IMediaErrorCode,
@@ -66,6 +83,7 @@ export default class MediaError extends Error {
66
83
  context?:
67
84
  | {
68
85
  tracks?: ITaggedTrack[] | undefined;
86
+ timeInfo?: ITimeInfo;
69
87
  }
70
88
  | undefined,
71
89
  ) {
@@ -82,6 +100,9 @@ export default class MediaError extends Error {
82
100
  if (context?.tracks !== undefined && context?.tracks.length > 0) {
83
101
  this.tracksInfo = context.tracks;
84
102
  }
103
+ if (context?.timeInfo !== undefined) {
104
+ this.timeInfo = context.timeInfo;
105
+ }
85
106
  }
86
107
 
87
108
  /**
@@ -97,10 +118,44 @@ export default class MediaError extends Error {
97
118
  code: this.code,
98
119
  reason: this._originalMessage,
99
120
  tracks: this.tracksInfo,
121
+ timeInfo: this.timeInfo,
100
122
  };
101
123
  }
102
124
  }
103
125
 
126
+ /**
127
+ * Re-create a `MediaError` from a serialized representation.
128
+ * @param {Object} serializedMediaError
129
+ * @returns {MediaError}
130
+ */
131
+ export function deserializeMediaError(
132
+ serializedMediaError: ISerializedMediaError,
133
+ ): MediaError {
134
+ if (
135
+ serializedMediaError.code === "MEDIA_TIME_BEFORE_MANIFEST" ||
136
+ serializedMediaError.code === "MEDIA_TIME_AFTER_MANIFEST"
137
+ ) {
138
+ assert(
139
+ serializedMediaError.timeInfo !== undefined,
140
+ `The MediaError with code ${serializedMediaError.code} does not provide "timeinfo"`,
141
+ );
142
+ return new MediaError(serializedMediaError.code, serializedMediaError.reason, {
143
+ timeInfo: serializedMediaError.timeInfo,
144
+ });
145
+ }
146
+ if (
147
+ serializedMediaError.code === "BUFFER_APPEND_ERROR" ||
148
+ serializedMediaError.code === "BUFFER_FULL_ERROR" ||
149
+ serializedMediaError.code === "NO_PLAYABLE_REPRESENTATION" ||
150
+ serializedMediaError.code === "MANIFEST_INCOMPATIBLE_CODECS_ERROR"
151
+ ) {
152
+ return new MediaError(serializedMediaError.code, serializedMediaError.reason, {
153
+ tracks: serializedMediaError.tracks,
154
+ });
155
+ }
156
+ return new MediaError(serializedMediaError.code, serializedMediaError.reason);
157
+ }
158
+
104
159
  /** Serializable object which allows to create a `MediaError` later. */
105
160
  export interface ISerializedMediaError {
106
161
  isSerializedError: true;
@@ -108,4 +163,5 @@ export interface ISerializedMediaError {
108
163
  code: IMediaErrorCode;
109
164
  reason: string;
110
165
  tracks: ITaggedTrack[] | undefined;
166
+ timeInfo?: ITimeInfo | undefined;
111
167
  }
@@ -15,5 +15,5 @@ is mainly to confine the "library path" of that feature to the
15
15
  `rx-player/experimental/features` subdirectory.
16
16
 
17
17
  Note that tools NEED to be declared in their own directory inside a `./tools/<TOOL_NAME>`
18
- directory, with their inner `index.js` file exporting them, as that's a requirement from
18
+ directory, with their inner `index.ts` file exporting them, as that's a requirement from
19
19
  our exporting strategy.
@@ -3,8 +3,8 @@
3
3
  This directory regroups the code importing all the "features" of the RxPlayer piece by
4
4
  piece.
5
5
 
6
- It is mostly used for allowing a library user to only imports some features of the
7
- RxPlayer without importing others.
6
+ It is mostly used for allowing a library user to import only some features of the RxPlayer
7
+ without importing others.
8
8
 
9
9
  How this is technically done is through a global `features` Object, which will have added
10
10
  properties (which corresponds to those features' code) each time a new feature is added.
@@ -12,4 +12,4 @@ properties (which corresponds to those features' code) each time a new feature i
12
12
  ## For workers
13
13
 
14
14
  The `worker` directory contains features intended for the worker-side that will then be
15
- exported through the RxPlayer's `worker/features` path.
15
+ exported through the RxPlayer's `experimental/worker/features` path.
@@ -20,13 +20,12 @@ As such, its main roles are to:
20
20
 
21
21
  To facilitate those actions, the API relies on multiple building blocks:
22
22
 
23
- - **the `PlaybackObserver` (./playback_observer.ts)**
24
-
25
- Provide multiple methods allowing to monitor the current playback conditions (e.g. the
26
- current position and/or the current playback rate). Many RxPlayer modules rely on this
27
- PlaybackObserver.
28
-
29
23
  - **the `option utils` (./option_utils.ts)**
30
24
 
31
25
  Parse options given to some RxPlayer API calls, to add default parameters and provide
32
- inteligible warnings/errors
26
+ intelligible warnings/errors
27
+
28
+ - **the `debug` directory (./debug/)**
29
+
30
+ Contains the logic for the debug overlay API, (e.g. that can be rendered through the
31
+ `createDebugElement` RxPlayer API)
@@ -437,7 +437,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
437
437
  // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1194624
438
438
  videoElement.preload = "auto";
439
439
 
440
- this.version = /* PLAYER_VERSION */ "4.5.0-dev.2026033100";
440
+ this.version = /* PLAYER_VERSION */ "4.5.0-dev.2026043000";
441
441
  this.log = log;
442
442
  this.state = "STOPPED";
443
443
  this.videoElement = videoElement;
@@ -1244,31 +1244,31 @@ class Player extends EventEmitter<IPublicAPIEvent> {
1244
1244
  ) {
1245
1245
  log.warn(
1246
1246
  "API",
1247
- "You only set a representationFilter function in a mulithreaded mode, ignoring it...",
1247
+ "You set a `representationFilter` function without a `workerId` while loading in multithread mode. That function cannot run in the worker, so it will be ignored.",
1248
1248
  );
1249
1249
  }
1250
1250
  }
1251
1251
  if (
1252
1252
  transportOptions.manifestLoader !== undefined &&
1253
- !isNullOrUndefined(transportOptions.manifestLoader.workerId)
1253
+ !isNullOrUndefined(transportOptions.manifestLoader.fn)
1254
1254
  ) {
1255
1255
  transportOptions.manifestLoader.fn = undefined;
1256
- if (isNullOrUndefined(transportOptions.manifestLoader.fn)) {
1256
+ if (isNullOrUndefined(transportOptions.manifestLoader.workerId)) {
1257
1257
  log.warn(
1258
1258
  "API",
1259
- "You only set a manifestLoader function in a mulithreaded mode, ignoring it...",
1259
+ "You set a `manifestLoader` function without a `workerId` while loading in multithread mode. That function cannot run in the worker, so it will be ignored.",
1260
1260
  );
1261
1261
  }
1262
1262
  }
1263
1263
  if (
1264
1264
  transportOptions.segmentLoader !== undefined &&
1265
- !isNullOrUndefined(transportOptions.segmentLoader.workerId)
1265
+ !isNullOrUndefined(transportOptions.segmentLoader.fn)
1266
1266
  ) {
1267
1267
  transportOptions.segmentLoader.fn = undefined;
1268
- if (isNullOrUndefined(transportOptions.segmentLoader.fn)) {
1268
+ if (isNullOrUndefined(transportOptions.segmentLoader.workerId)) {
1269
1269
  log.warn(
1270
1270
  "API",
1271
- "You only set a segmentLoader function in a mulithreaded mode, ignoring it...",
1271
+ "You set a `segmentLoader` function without a `workerId` while loading in multithread mode. That function cannot run in the worker, so it will be ignored.",
1272
1272
  );
1273
1273
  }
1274
1274
  }
@@ -3029,6 +3029,9 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3029
3029
  contentInfos: IPublicApiContentInfos,
3030
3030
  updates: IPeriodsUpdateResult,
3031
3031
  ): void {
3032
+ if (contentInfos.contentId !== this._priv_contentInfos?.contentId) {
3033
+ return; // Event for another content
3034
+ }
3032
3035
  if (this._priv_contentInfos === null || this._priv_contentInfos.manifest === null) {
3033
3036
  return;
3034
3037
  }
@@ -3210,7 +3213,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3210
3213
  "audioRepresentationChange",
3211
3214
  isNullOrUndefined(audioRepresentation)
3212
3215
  ? audioRepresentation
3213
- : toVideoRepresentation(audioRepresentation),
3216
+ : toAudioRepresentation(audioRepresentation),
3214
3217
  cancelSignal,
3215
3218
  );
3216
3219
  const videoRepresentation = this.__priv_getCurrentRepresentations()?.video ?? null;
@@ -3747,6 +3750,9 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3747
3750
  * to the content for which the error was received.
3748
3751
  */
3749
3752
  private _priv_onFatalError(err: unknown, contentInfos: IPublicApiContentInfos): void {
3753
+ if (contentInfos.contentId !== this._priv_contentInfos?.contentId) {
3754
+ return; // Event for another content
3755
+ }
3750
3756
  const formattedError = formatError(err, {
3751
3757
  defaultCode: "NONE",
3752
3758
  defaultReason: "An unknown error stopped content playback.",
@@ -3831,7 +3837,7 @@ class Player extends EventEmitter<IPublicAPIEvent> {
3831
3837
  return true;
3832
3838
  }
3833
3839
  }
3834
- Player.version = /* PLAYER_VERSION */ "4.5.0-dev.2026033100";
3840
+ Player.version = /* PLAYER_VERSION */ "4.5.0-dev.2026043000";
3835
3841
 
3836
3842
  /** Every events sent by the RxPlayer's public API. */
3837
3843
  interface IPublicAPIEvent {
@@ -12,11 +12,11 @@ imported by outside code).
12
12
  ## Overview
13
13
 
14
14
  This directory exports the `ContentDecryptor`, which allows to easily interface with the
15
- browser APIs for decrypting an encrypted content, it follows the
16
- [Encrypted Media Extensions recommandations](https://www.w3.org/TR/encrypted-media/).
15
+ browser APIs for decrypting encrypted content. It follows the
16
+ [Encrypted Media Extensions recommendations](https://www.w3.org/TR/encrypted-media/).
17
17
 
18
- The `ContentDecryptor` is a module isolated from the rest of the player taking a form of a
19
- class. It starts initializing decryption capabilities as soon as it is instanciated and
18
+ The `ContentDecryptor` is a module isolated from the rest of the player taking the form of
19
+ a class. It starts initializing decryption capabilities as soon as it is instantiated and
20
20
  emits various events to communicate with external code.
21
21
 
22
22
  A central concept of the `ContentDecryptor` is its states. As decryption initialization
@@ -1,10 +1,52 @@
1
1
  import { describe, beforeEach, it, expect, vi } from "vitest";
2
+ import getEmeApiImplementation from "../../../../compat/eme";
3
+ import { EncryptedMediaError } from "../../../../errors";
4
+ import type { IKeySystemOption } from "../../../../public_types";
5
+ import assert from "../../../../utils/assert";
2
6
  import { getMissingKeyIds } from "../../content_decryptor";
7
+ import { ContentDecryptorState } from "../../types";
8
+ import { defaultKSConfig, mockCompat } from "./utils";
9
+
10
+ const mocks = vi.hoisted(() => {
11
+ return {
12
+ shouldRenewMediaKeySystemAccess: vi.fn(() => false),
13
+ canReuseMediaKeys: vi.fn(() => true),
14
+ onEncrypted: vi.fn(),
15
+ requestMediaKeySystemAccess: vi.fn(),
16
+ setMediaKeys: vi.fn(),
17
+ getInitData: vi.fn(),
18
+ generateKeyRequest: vi.fn(),
19
+ };
20
+ });
21
+ vi.mock("../../../../compat/should_renew_media_key_system_access", () => ({
22
+ default: mocks.shouldRenewMediaKeySystemAccess,
23
+ }));
24
+ vi.mock("../../../../compat/can_reuse_media_keys", () => ({
25
+ default: mocks.canReuseMediaKeys,
26
+ }));
27
+ vi.mock("../../../../compat/eme", () => ({
28
+ default: () => ({
29
+ onEncrypted: mocks.onEncrypted,
30
+ requestMediaKeySystemAccess: mocks.requestMediaKeySystemAccess,
31
+ setMediaKeys: mocks.setMediaKeys,
32
+ }),
33
+ getInitData: mocks.getInitData,
34
+ generateKeyRequest: mocks.generateKeyRequest,
35
+ closeSession: vi.fn(),
36
+ loadSession: vi.fn(),
37
+ }));
3
38
 
4
39
  describe("content_decryptor - blacklist missing key Ids", () => {
5
40
  beforeEach(() => {
6
41
  vi.resetModules();
7
42
  vi.restoreAllMocks();
43
+ mocks.shouldRenewMediaKeySystemAccess.mockReset();
44
+ mocks.canReuseMediaKeys.mockReset();
45
+ mocks.onEncrypted.mockReset();
46
+ mocks.requestMediaKeySystemAccess.mockReset();
47
+ mocks.setMediaKeys.mockReset();
48
+ mocks.getInitData.mockReset();
49
+ mocks.generateKeyRequest.mockReset();
8
50
  });
9
51
 
10
52
  it("should return an empty array if actualKeyIds contains all expectedKeyIds", () => {
@@ -43,3 +85,96 @@ describe("content_decryptor - blacklist missing key Ids", () => {
43
85
  expect(result).toEqual([new Uint8Array([2]), new Uint8Array([4])]);
44
86
  });
45
87
  });
88
+
89
+ describe("content_decryptor - session decommissioning", () => {
90
+ beforeEach(() => {
91
+ vi.resetModules();
92
+ vi.restoreAllMocks();
93
+ mocks.shouldRenewMediaKeySystemAccess.mockReset();
94
+ mocks.canReuseMediaKeys.mockReset();
95
+ mocks.onEncrypted.mockReset();
96
+ mocks.requestMediaKeySystemAccess.mockReset();
97
+ mocks.setMediaKeys.mockReset();
98
+ mocks.getInitData.mockReset();
99
+ mocks.generateKeyRequest.mockReset();
100
+ });
101
+
102
+ it("should only remove the decommissioned session from tracked sessions", async () => {
103
+ vi.resetModules();
104
+ const videoElt = document.createElement("video");
105
+ mockCompat(mocks);
106
+ const sessionCallbacks: Array<{ onError: (err: unknown) => void }> = [];
107
+ vi.doMock("../../session_events_listener", () => ({
108
+ __esModule: true,
109
+ default: vi.fn(
110
+ (_session: unknown, _options: unknown, _access: unknown, callbacks: unknown) => {
111
+ sessionCallbacks.push(callbacks as { onError: (err: unknown) => void });
112
+ },
113
+ ),
114
+ BlacklistedSessionError: class BlacklistedSessionError extends Error {},
115
+ }));
116
+ const { DecommissionedSessionError } = await import("../../utils/check_key_statuses");
117
+ const { default: ContentDecryptor } = await import("../../content_decryptor");
118
+
119
+ const mockGetLicense = vi.fn(() => Promise.resolve(new Uint8Array([1, 2, 3, 4])));
120
+ const ksConfig: IKeySystemOption[] = [
121
+ {
122
+ type: "com.widevine.alpha",
123
+ getLicense: mockGetLicense,
124
+ onKeyInternalError: "close-session",
125
+ },
126
+ ];
127
+
128
+ const eme = getEmeApiImplementation("auto");
129
+ assert(eme !== null, "Expected to have an EME implementation");
130
+ const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfig);
131
+
132
+ await new Promise<void>((res, rej) => {
133
+ contentDecryptor.addEventListener("stateChange", (state) => {
134
+ if (state !== ContentDecryptorState.WaitingForAttachment) {
135
+ rej(new Error(`Unexpected state: ${state}`));
136
+ return;
137
+ }
138
+ contentDecryptor.removeEventListener("stateChange");
139
+ contentDecryptor.attach();
140
+ res();
141
+ });
142
+ });
143
+
144
+ contentDecryptor.onInitializationData({
145
+ type: "cenc",
146
+ values: [{ systemId: "15", data: new Uint8Array([1, 1, 1]) }],
147
+ });
148
+ contentDecryptor.onInitializationData({
149
+ type: "cenc",
150
+ values: [{ systemId: "15", data: new Uint8Array([2, 2, 2]) }],
151
+ });
152
+ contentDecryptor.onInitializationData({
153
+ type: "cenc",
154
+ values: [{ systemId: "15", data: new Uint8Array([3, 3, 3]) }],
155
+ });
156
+
157
+ await new Promise((res) => setTimeout(res, 120));
158
+
159
+ expect(
160
+ (contentDecryptor as unknown as { _currentSessions: unknown[] })._currentSessions,
161
+ ).toHaveLength(3);
162
+
163
+ sessionCallbacks[1].onError(
164
+ new DecommissionedSessionError(
165
+ new EncryptedMediaError("KEY_LOAD_ERROR", "forced decommissioning", {
166
+ keyStatuses: undefined,
167
+ keySystemConfiguration: defaultKSConfig[0],
168
+ keySystem: "com.widevine.alpha",
169
+ }),
170
+ ),
171
+ );
172
+ await new Promise((res) => setTimeout(res, 30));
173
+
174
+ expect(
175
+ (contentDecryptor as unknown as { _currentSessions: unknown[] })._currentSessions,
176
+ ).toHaveLength(2);
177
+
178
+ contentDecryptor.dispose(undefined);
179
+ });
180
+ });
@@ -284,6 +284,76 @@ describe("decrypt - global tests - getLicense", () => {
284
284
  ignoreLicenseRequests: false,
285
285
  });
286
286
  }, 15000);
287
+
288
+ it("should emit KEY_LOAD_TIMEOUT when getLicense times out", async () => {
289
+ const initData = new Uint8Array([54, 55, 75]);
290
+ const initDataEvent = {
291
+ type: "cenc",
292
+ values: [{ systemId: "15", data: initData }],
293
+ };
294
+ const challenge = formatFakeChallengeFromInitData(initData, "cenc");
295
+ const mediaKeySession = new MediaKeySessionImpl();
296
+ vi.spyOn(MediaKeysImpl.prototype, "createSession").mockReturnValue(mediaKeySession);
297
+ const mockGetLicense = vi.fn(() => {
298
+ return new Promise<BufferSource>((resolve) => {
299
+ setTimeout(() => {
300
+ resolve(challenge);
301
+ }, 30);
302
+ });
303
+ });
304
+
305
+ const videoElt = document.createElement("video");
306
+ mockCompat(mocks);
307
+
308
+ await new Promise<void>((res, rej) => {
309
+ const ksConfig: IKeySystemOption[] = [
310
+ {
311
+ type: "com.widevine.alpha",
312
+ getLicense: mockGetLicense,
313
+ getLicenseConfig: {
314
+ retry: 0,
315
+ timeout: 5,
316
+ },
317
+ },
318
+ ];
319
+ const eme = getEmeApiImplementation("auto");
320
+ assert(eme !== null);
321
+ const contentDecryptor = new ContentDecryptor(eme, videoElt, ksConfig);
322
+
323
+ contentDecryptor.addEventListener("stateChange", (newState: number) => {
324
+ if (newState !== ContentDecryptorState.WaitingForAttachment) {
325
+ rej(new Error(`Unexpected state: ${newState}`));
326
+ return;
327
+ }
328
+ contentDecryptor.removeEventListener("stateChange");
329
+ contentDecryptor.attach();
330
+ });
331
+
332
+ contentDecryptor.addEventListener("warning", (warning: Error) => {
333
+ rej(new Error(`Unexpected warning: ${warning.toString()}`));
334
+ });
335
+
336
+ contentDecryptor.addEventListener("error", (error: Error) => {
337
+ try {
338
+ expect(error).toBeInstanceOf(Error);
339
+ expect((error as IPlayerError).name).toEqual("EncryptedMediaError");
340
+ expect((error as IPlayerError).type).toEqual("ENCRYPTED_MEDIA_ERROR");
341
+ expect((error as IPlayerError).code).toEqual("KEY_LOAD_TIMEOUT");
342
+ expect((error as IPlayerError).message).toEqual(
343
+ "KEY_LOAD_TIMEOUT: The license server took too much time to respond.",
344
+ );
345
+ expect(mockGetLicense).toHaveBeenCalledTimes(1);
346
+ expect(mockGetLicense).toHaveBeenNthCalledWith(1, challenge, "license-request");
347
+ contentDecryptor.dispose(undefined);
348
+ res();
349
+ } catch (e) {
350
+ rej(e);
351
+ }
352
+ });
353
+
354
+ contentDecryptor.onInitializationData(initDataEvent);
355
+ });
356
+ });
287
357
  });
288
358
 
289
359
  /**