rx-player 4.4.1-dev.2025101500 → 4.4.1

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 (350) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/README.md +111 -50
  3. package/VERSION +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  5. package/dist/commonjs/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  6. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  7. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  8. package/dist/commonjs/core/{entry → main/common}/FreezeResolver.d.ts +3 -3
  9. package/dist/commonjs/core/main/common/FreezeResolver.d.ts.map +1 -0
  10. package/dist/commonjs/core/{entry → main/common}/FreezeResolver.js +3 -3
  11. package/dist/commonjs/core/{entry → main/common}/content_time_boundaries_observer.d.ts +5 -5
  12. package/dist/commonjs/core/main/common/content_time_boundaries_observer.d.ts.map +1 -0
  13. package/dist/commonjs/core/{entry → main/common}/content_time_boundaries_observer.js +6 -6
  14. package/dist/{es2017/core/entry → commonjs/core/main/common}/create_content_time_boundaries_observer.d.ts +6 -6
  15. package/dist/commonjs/core/main/common/create_content_time_boundaries_observer.d.ts.map +1 -0
  16. package/dist/commonjs/core/{entry → main/common}/create_content_time_boundaries_observer.js +1 -1
  17. package/dist/{es2017/core/entry → commonjs/core/main/common}/get_buffered_data_per_media_buffer.d.ts +4 -4
  18. package/dist/commonjs/core/main/common/get_buffered_data_per_media_buffer.d.ts.map +1 -0
  19. package/dist/commonjs/core/{entry → main/common}/get_buffered_data_per_media_buffer.js +1 -1
  20. package/dist/{es2017/core/entry → commonjs/core/main/common}/get_thumbnail_data.d.ts +3 -3
  21. package/dist/commonjs/core/main/common/get_thumbnail_data.d.ts.map +1 -0
  22. package/dist/commonjs/core/{entry → main/common}/get_thumbnail_data.js +2 -2
  23. package/dist/{es2017/core/entry → commonjs/core/main/common}/synchronize_sinks_on_observation.d.ts +2 -2
  24. package/dist/commonjs/core/main/common/synchronize_sinks_on_observation.d.ts.map +1 -0
  25. package/dist/{es2017/core/entry → commonjs/core/main/worker}/content_preparer.d.ts +15 -22
  26. package/dist/commonjs/core/main/worker/content_preparer.d.ts.map +1 -0
  27. package/dist/commonjs/core/{entry → main/worker}/content_preparer.js +64 -62
  28. package/dist/commonjs/core/main/worker/globals.d.ts +14 -0
  29. package/dist/commonjs/core/main/worker/globals.d.ts.map +1 -0
  30. package/dist/commonjs/core/main/worker/globals.js +26 -0
  31. package/dist/commonjs/core/main/worker/index.d.ts +3 -0
  32. package/dist/commonjs/core/main/worker/index.d.ts.map +1 -0
  33. package/dist/commonjs/core/main/worker/index.js +4 -0
  34. package/dist/commonjs/core/main/worker/send_message.d.ts +4 -0
  35. package/dist/commonjs/core/main/worker/send_message.d.ts.map +1 -0
  36. package/dist/commonjs/core/main/worker/send_message.js +23 -0
  37. package/dist/{es2017/core/entry → commonjs/core/main/worker}/track_choice_setter.d.ts +4 -4
  38. package/dist/commonjs/core/main/worker/track_choice_setter.d.ts.map +1 -0
  39. package/dist/commonjs/core/{entry → main/worker}/track_choice_setter.js +4 -4
  40. package/dist/commonjs/core/main/worker/worker_main.d.ts +2 -0
  41. package/dist/commonjs/core/main/worker/worker_main.d.ts.map +1 -0
  42. package/dist/commonjs/core/{entry/core_entry.js → main/worker/worker_main.js} +153 -192
  43. package/dist/commonjs/core/{entry/core_text_displayer_interface.d.ts → main/worker/worker_text_displayer_interface.d.ts} +11 -11
  44. package/dist/commonjs/core/main/worker/worker_text_displayer_interface.d.ts.map +1 -0
  45. package/dist/commonjs/core/{entry/core_text_displayer_interface.js → main/worker/worker_text_displayer_interface.js} +22 -22
  46. package/dist/commonjs/core/types.d.ts +1 -519
  47. package/dist/commonjs/core/types.d.ts.map +1 -1
  48. package/dist/commonjs/core/types.js +0 -1
  49. package/dist/commonjs/experimental/features/local.d.ts.map +1 -1
  50. package/dist/commonjs/experimental/features/local.js +1 -7
  51. package/dist/commonjs/experimental/features/metaplaylist.d.ts.map +1 -1
  52. package/dist/commonjs/experimental/features/metaplaylist.js +1 -7
  53. package/dist/commonjs/experimental/features/multi_thread.d.ts.map +1 -1
  54. package/dist/commonjs/experimental/features/multi_thread.js +2 -6
  55. package/dist/commonjs/features/features_object.js +1 -1
  56. package/dist/commonjs/features/list/dash.d.ts.map +1 -1
  57. package/dist/commonjs/features/list/dash.js +1 -7
  58. package/dist/commonjs/features/list/dash_wasm.d.ts.map +1 -1
  59. package/dist/commonjs/features/list/dash_wasm.js +1 -7
  60. package/dist/commonjs/features/list/media_source_main.d.ts.map +1 -1
  61. package/dist/commonjs/features/list/media_source_main.js +1 -7
  62. package/dist/commonjs/features/list/smooth.d.ts.map +1 -1
  63. package/dist/commonjs/features/list/smooth.js +1 -7
  64. package/dist/commonjs/features/types.d.ts +4 -20
  65. package/dist/commonjs/features/types.d.ts.map +1 -1
  66. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  67. package/dist/commonjs/main_thread/api/public_api.js +44 -40
  68. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +108 -166
  69. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  70. package/dist/commonjs/main_thread/init/media_source_content_initializer.js +918 -1491
  71. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +308 -0
  72. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -0
  73. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.js +1713 -0
  74. package/dist/commonjs/main_thread/init/send_message.d.ts +3 -0
  75. package/dist/commonjs/main_thread/init/send_message.d.ts.map +1 -0
  76. package/dist/commonjs/main_thread/init/send_message.js +13 -0
  77. package/dist/commonjs/main_thread/init/utils/create_core_playback_observer.d.ts.map +1 -1
  78. package/dist/commonjs/main_thread/init/utils/create_core_playback_observer.js +1 -2
  79. package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts +1 -1
  80. package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
  81. package/dist/commonjs/main_thread/types.d.ts +0 -537
  82. package/dist/commonjs/main_thread/types.d.ts.map +1 -1
  83. package/dist/commonjs/manifest/utils.d.ts.map +1 -1
  84. package/dist/commonjs/manifest/utils.js +4 -18
  85. package/dist/commonjs/mse/worker_media_source_interface.d.ts +2 -2
  86. package/dist/commonjs/mse/worker_media_source_interface.d.ts.map +1 -1
  87. package/dist/commonjs/mse/worker_media_source_interface.js +12 -12
  88. package/dist/commonjs/multithread_types.d.ts +915 -0
  89. package/dist/commonjs/multithread_types.d.ts.map +1 -0
  90. package/dist/commonjs/multithread_types.js +7 -0
  91. package/dist/commonjs/parsers/manifest/smooth/create_parser.d.ts +1 -1
  92. package/dist/commonjs/parsers/manifest/smooth/create_parser.d.ts.map +1 -1
  93. package/dist/commonjs/parsers/manifest/smooth/create_parser.js +27 -31
  94. package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.d.ts +2 -3
  95. package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.d.ts.map +1 -1
  96. package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.js +7 -16
  97. package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.d.ts +2 -3
  98. package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.d.ts.map +1 -1
  99. package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.js +6 -37
  100. package/dist/commonjs/parsers/manifest/smooth/utils/parseBoolean.d.ts +1 -1
  101. package/dist/commonjs/parsers/manifest/smooth/utils/parseBoolean.d.ts.map +1 -1
  102. package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.d.ts +2 -3
  103. package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.d.ts.map +1 -1
  104. package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.js +5 -28
  105. package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts +28 -8
  106. package/dist/commonjs/playback_observer/media_element_playback_observer.d.ts.map +1 -1
  107. package/dist/commonjs/playback_observer/media_element_playback_observer.js +146 -64
  108. package/dist/{es2017/playback_observer/core_playback_observer.d.ts → commonjs/playback_observer/worker_playback_observer.d.ts} +8 -8
  109. package/dist/commonjs/playback_observer/worker_playback_observer.d.ts.map +1 -0
  110. package/dist/commonjs/playback_observer/{core_playback_observer.js → worker_playback_observer.js} +13 -13
  111. package/dist/commonjs/transports/smooth/pipelines.d.ts.map +1 -1
  112. package/dist/commonjs/transports/smooth/pipelines.js +3 -25
  113. package/dist/commonjs/worker_entry_point.js +2 -62
  114. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.d.ts.map +1 -1
  115. package/dist/es2017/__GENERATED_CODE/embedded_dash_wasm.js +1 -1
  116. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  117. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  118. package/dist/es2017/core/{entry → main/common}/FreezeResolver.d.ts +3 -3
  119. package/dist/es2017/core/main/common/FreezeResolver.d.ts.map +1 -0
  120. package/dist/es2017/core/{entry → main/common}/FreezeResolver.js +3 -3
  121. package/dist/es2017/core/{entry → main/common}/content_time_boundaries_observer.d.ts +5 -5
  122. package/dist/es2017/core/main/common/content_time_boundaries_observer.d.ts.map +1 -0
  123. package/dist/es2017/core/{entry → main/common}/content_time_boundaries_observer.js +6 -6
  124. package/dist/{commonjs/core/entry → es2017/core/main/common}/create_content_time_boundaries_observer.d.ts +6 -6
  125. package/dist/es2017/core/main/common/create_content_time_boundaries_observer.d.ts.map +1 -0
  126. package/dist/es2017/core/{entry → main/common}/create_content_time_boundaries_observer.js +1 -1
  127. package/dist/{commonjs/core/entry → es2017/core/main/common}/get_buffered_data_per_media_buffer.d.ts +4 -4
  128. package/dist/es2017/core/main/common/get_buffered_data_per_media_buffer.d.ts.map +1 -0
  129. package/dist/es2017/core/{entry → main/common}/get_buffered_data_per_media_buffer.js +1 -1
  130. package/dist/{commonjs/core/entry → es2017/core/main/common}/get_thumbnail_data.d.ts +3 -3
  131. package/dist/es2017/core/main/common/get_thumbnail_data.d.ts.map +1 -0
  132. package/dist/es2017/core/{entry → main/common}/get_thumbnail_data.js +2 -2
  133. package/dist/{commonjs/core/entry → es2017/core/main/common}/synchronize_sinks_on_observation.d.ts +2 -2
  134. package/dist/es2017/core/main/common/synchronize_sinks_on_observation.d.ts.map +1 -0
  135. package/dist/{commonjs/core/entry → es2017/core/main/worker}/content_preparer.d.ts +15 -22
  136. package/dist/es2017/core/main/worker/content_preparer.d.ts.map +1 -0
  137. package/dist/es2017/core/{entry → main/worker}/content_preparer.js +55 -53
  138. package/dist/es2017/core/main/worker/globals.d.ts +14 -0
  139. package/dist/es2017/core/main/worker/globals.d.ts.map +1 -0
  140. package/dist/es2017/core/main/worker/globals.js +18 -0
  141. package/dist/es2017/core/main/worker/index.d.ts +3 -0
  142. package/dist/es2017/core/main/worker/index.d.ts.map +1 -0
  143. package/dist/es2017/core/main/worker/index.js +2 -0
  144. package/dist/es2017/core/main/worker/send_message.d.ts +4 -0
  145. package/dist/es2017/core/main/worker/send_message.d.ts.map +1 -0
  146. package/dist/es2017/core/main/worker/send_message.js +19 -0
  147. package/dist/{commonjs/core/entry → es2017/core/main/worker}/track_choice_setter.d.ts +4 -4
  148. package/dist/es2017/core/main/worker/track_choice_setter.d.ts.map +1 -0
  149. package/dist/es2017/core/{entry → main/worker}/track_choice_setter.js +4 -4
  150. package/dist/es2017/core/main/worker/worker_main.d.ts +2 -0
  151. package/dist/es2017/core/main/worker/worker_main.d.ts.map +1 -0
  152. package/dist/es2017/core/{entry/core_entry.js → main/worker/worker_main.js} +114 -153
  153. package/dist/es2017/core/{entry/core_text_displayer_interface.d.ts → main/worker/worker_text_displayer_interface.d.ts} +11 -11
  154. package/dist/es2017/core/main/worker/worker_text_displayer_interface.d.ts.map +1 -0
  155. package/dist/es2017/core/{entry/core_text_displayer_interface.js → main/worker/worker_text_displayer_interface.js} +10 -10
  156. package/dist/es2017/core/types.d.ts +1 -519
  157. package/dist/es2017/core/types.d.ts.map +1 -1
  158. package/dist/es2017/core/types.js +0 -1
  159. package/dist/es2017/experimental/features/local.d.ts.map +1 -1
  160. package/dist/es2017/experimental/features/local.js +1 -7
  161. package/dist/es2017/experimental/features/metaplaylist.d.ts.map +1 -1
  162. package/dist/es2017/experimental/features/metaplaylist.js +1 -7
  163. package/dist/es2017/experimental/features/multi_thread.d.ts.map +1 -1
  164. package/dist/es2017/experimental/features/multi_thread.js +2 -6
  165. package/dist/es2017/features/features_object.js +1 -1
  166. package/dist/es2017/features/list/dash.d.ts.map +1 -1
  167. package/dist/es2017/features/list/dash.js +1 -7
  168. package/dist/es2017/features/list/dash_wasm.d.ts.map +1 -1
  169. package/dist/es2017/features/list/dash_wasm.js +1 -7
  170. package/dist/es2017/features/list/media_source_main.d.ts.map +1 -1
  171. package/dist/es2017/features/list/media_source_main.js +1 -7
  172. package/dist/es2017/features/list/smooth.d.ts.map +1 -1
  173. package/dist/es2017/features/list/smooth.js +1 -7
  174. package/dist/es2017/features/types.d.ts +4 -20
  175. package/dist/es2017/features/types.d.ts.map +1 -1
  176. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  177. package/dist/es2017/main_thread/api/public_api.js +45 -41
  178. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +108 -166
  179. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  180. package/dist/es2017/main_thread/init/media_source_content_initializer.js +760 -1405
  181. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +308 -0
  182. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts.map +1 -0
  183. package/dist/es2017/main_thread/init/multi_thread_content_initializer.js +1559 -0
  184. package/dist/es2017/main_thread/init/send_message.d.ts +3 -0
  185. package/dist/es2017/main_thread/init/send_message.d.ts.map +1 -0
  186. package/dist/es2017/main_thread/init/send_message.js +10 -0
  187. package/dist/es2017/main_thread/init/utils/create_core_playback_observer.d.ts.map +1 -1
  188. package/dist/es2017/main_thread/init/utils/create_core_playback_observer.js +1 -2
  189. package/dist/es2017/main_thread/init/utils/update_manifest_codec_support.d.ts +1 -1
  190. package/dist/es2017/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
  191. package/dist/es2017/main_thread/types.d.ts +0 -537
  192. package/dist/es2017/main_thread/types.d.ts.map +1 -1
  193. package/dist/es2017/manifest/utils.d.ts.map +1 -1
  194. package/dist/es2017/manifest/utils.js +4 -16
  195. package/dist/es2017/mse/worker_media_source_interface.d.ts +2 -2
  196. package/dist/es2017/mse/worker_media_source_interface.d.ts.map +1 -1
  197. package/dist/es2017/mse/worker_media_source_interface.js +12 -12
  198. package/dist/es2017/multithread_types.d.ts +915 -0
  199. package/dist/es2017/multithread_types.d.ts.map +1 -0
  200. package/dist/es2017/multithread_types.js +6 -0
  201. package/dist/es2017/parsers/manifest/smooth/create_parser.d.ts +1 -1
  202. package/dist/es2017/parsers/manifest/smooth/create_parser.d.ts.map +1 -1
  203. package/dist/es2017/parsers/manifest/smooth/create_parser.js +27 -31
  204. package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.d.ts +2 -3
  205. package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.d.ts.map +1 -1
  206. package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.js +7 -16
  207. package/dist/es2017/parsers/manifest/smooth/parse_protection_node.d.ts +2 -3
  208. package/dist/es2017/parsers/manifest/smooth/parse_protection_node.d.ts.map +1 -1
  209. package/dist/es2017/parsers/manifest/smooth/parse_protection_node.js +6 -15
  210. package/dist/es2017/parsers/manifest/smooth/utils/parseBoolean.d.ts +1 -1
  211. package/dist/es2017/parsers/manifest/smooth/utils/parseBoolean.d.ts.map +1 -1
  212. package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.d.ts +2 -3
  213. package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.d.ts.map +1 -1
  214. package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.js +5 -6
  215. package/dist/es2017/playback_observer/media_element_playback_observer.d.ts +28 -8
  216. package/dist/es2017/playback_observer/media_element_playback_observer.d.ts.map +1 -1
  217. package/dist/es2017/playback_observer/media_element_playback_observer.js +144 -64
  218. package/dist/{commonjs/playback_observer/core_playback_observer.d.ts → es2017/playback_observer/worker_playback_observer.d.ts} +8 -8
  219. package/dist/es2017/playback_observer/worker_playback_observer.d.ts.map +1 -0
  220. package/dist/es2017/playback_observer/{core_playback_observer.js → worker_playback_observer.js} +2 -2
  221. package/dist/es2017/transports/smooth/pipelines.d.ts.map +1 -1
  222. package/dist/es2017/transports/smooth/pipelines.js +3 -25
  223. package/dist/es2017/worker_entry_point.js +2 -62
  224. package/dist/mpd-parser.wasm +0 -0
  225. package/dist/rx-player.js +19165 -21886
  226. package/dist/rx-player.min.js +20 -20
  227. package/dist/worker.js +8 -8
  228. package/eslint.config.mjs +647 -348
  229. package/package.json +6 -6
  230. package/src/README.md +198 -88
  231. package/src/__GENERATED_CODE/embedded_dash_wasm.ts +1 -1
  232. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  233. package/src/core/{entry → main}/README.md +1 -1
  234. package/src/core/{entry → main/common}/FreezeResolver.ts +7 -7
  235. package/src/core/{entry → main/common}/content_time_boundaries_observer.ts +10 -10
  236. package/src/core/{entry → main/common}/create_content_time_boundaries_observer.ts +7 -7
  237. package/src/core/{entry → main/common}/get_buffered_data_per_media_buffer.ts +6 -6
  238. package/src/core/{entry → main/common}/get_thumbnail_data.ts +5 -5
  239. package/src/core/{entry → main/common}/synchronize_sinks_on_observation.ts +2 -2
  240. package/src/core/{entry → main/worker}/content_preparer.ts +76 -77
  241. package/src/core/main/worker/globals.ts +38 -0
  242. package/src/core/main/worker/index.ts +2 -0
  243. package/src/core/main/worker/send_message.ts +28 -0
  244. package/src/core/{entry → main/worker}/track_choice_setter.ts +7 -7
  245. package/src/core/{entry/core_entry.ts → main/worker/worker_main.ts} +149 -223
  246. package/src/core/{entry/core_text_displayer_interface.ts → main/worker/worker_text_displayer_interface.ts} +26 -26
  247. package/src/core/types.ts +3 -631
  248. package/src/experimental/features/__tests__/local.test.ts +2 -11
  249. package/src/experimental/features/__tests__/metaplaylist.test.ts +2 -11
  250. package/src/experimental/features/__tests__/multi_thread.test.ts +3 -8
  251. package/src/experimental/features/local.ts +1 -7
  252. package/src/experimental/features/metaplaylist.ts +1 -7
  253. package/src/experimental/features/multi_thread.ts +2 -6
  254. package/src/features/features_object.ts +1 -1
  255. package/src/features/list/__tests__/dash.test.ts +3 -12
  256. package/src/features/list/__tests__/smooth.test.ts +2 -11
  257. package/src/features/list/dash.ts +1 -7
  258. package/src/features/list/dash_wasm.ts +1 -7
  259. package/src/features/list/media_source_main.ts +1 -7
  260. package/src/features/list/smooth.ts +1 -7
  261. package/src/features/types.ts +4 -23
  262. package/src/main_thread/README.md +0 -8
  263. package/src/main_thread/api/public_api.ts +51 -47
  264. package/src/main_thread/init/media_source_content_initializer.ts +1164 -2046
  265. package/src/main_thread/init/multi_thread_content_initializer.ts +2330 -0
  266. package/src/main_thread/init/send_message.ts +15 -0
  267. package/src/main_thread/init/utils/create_core_playback_observer.ts +1 -2
  268. package/src/main_thread/init/utils/update_manifest_codec_support.ts +1 -1
  269. package/src/main_thread/types.ts +0 -610
  270. package/src/manifest/utils.ts +4 -20
  271. package/src/mse/worker_media_source_interface.ts +35 -35
  272. package/src/multithread_types.ts +1095 -0
  273. package/src/parsers/manifest/smooth/create_parser.ts +34 -40
  274. package/src/parsers/manifest/smooth/parse_C_nodes.ts +8 -19
  275. package/src/parsers/manifest/smooth/parse_protection_node.ts +9 -17
  276. package/src/parsers/manifest/smooth/utils/parseBoolean.ts +1 -1
  277. package/src/parsers/manifest/smooth/utils/reduceChildren.ts +7 -10
  278. package/src/playback_observer/media_element_playback_observer.ts +177 -73
  279. package/src/playback_observer/{core_playback_observer.ts → worker_playback_observer.ts} +13 -13
  280. package/src/transports/smooth/pipelines.ts +5 -25
  281. package/src/worker_entry_point.ts +2 -71
  282. package/dist/commonjs/core/entry/FreezeResolver.d.ts.map +0 -1
  283. package/dist/commonjs/core/entry/content_preparer.d.ts.map +0 -1
  284. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +0 -1
  285. package/dist/commonjs/core/entry/core_entry.d.ts +0 -36
  286. package/dist/commonjs/core/entry/core_entry.d.ts.map +0 -1
  287. package/dist/commonjs/core/entry/core_text_displayer_interface.d.ts.map +0 -1
  288. package/dist/commonjs/core/entry/create_content_time_boundaries_observer.d.ts.map +0 -1
  289. package/dist/commonjs/core/entry/get_buffered_data_per_media_buffer.d.ts.map +0 -1
  290. package/dist/commonjs/core/entry/get_thumbnail_data.d.ts.map +0 -1
  291. package/dist/commonjs/core/entry/index.d.ts +0 -5
  292. package/dist/commonjs/core/entry/index.d.ts.map +0 -1
  293. package/dist/commonjs/core/entry/index.js +0 -4
  294. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
  295. package/dist/commonjs/core/entry/track_choice_setter.d.ts.map +0 -1
  296. package/dist/commonjs/core/entry/utils.d.ts +0 -3
  297. package/dist/commonjs/core/entry/utils.d.ts.map +0 -1
  298. package/dist/commonjs/core/entry/utils.js +0 -11
  299. package/dist/commonjs/main_thread/core_interface/base.d.ts +0 -13
  300. package/dist/commonjs/main_thread/core_interface/base.d.ts.map +0 -1
  301. package/dist/commonjs/main_thread/core_interface/base.js +0 -32
  302. package/dist/commonjs/main_thread/core_interface/monothread.d.ts +0 -13
  303. package/dist/commonjs/main_thread/core_interface/monothread.d.ts.map +0 -1
  304. package/dist/commonjs/main_thread/core_interface/monothread.js +0 -56
  305. package/dist/commonjs/main_thread/core_interface/multithread.d.ts +0 -25
  306. package/dist/commonjs/main_thread/core_interface/multithread.d.ts.map +0 -1
  307. package/dist/commonjs/main_thread/core_interface/multithread.js +0 -67
  308. package/dist/commonjs/main_thread/core_interface/types.d.ts +0 -6
  309. package/dist/commonjs/main_thread/core_interface/types.d.ts.map +0 -1
  310. package/dist/commonjs/main_thread/core_interface/types.js +0 -2
  311. package/dist/commonjs/playback_observer/core_playback_observer.d.ts.map +0 -1
  312. package/dist/es2017/core/entry/FreezeResolver.d.ts.map +0 -1
  313. package/dist/es2017/core/entry/content_preparer.d.ts.map +0 -1
  314. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +0 -1
  315. package/dist/es2017/core/entry/core_entry.d.ts +0 -36
  316. package/dist/es2017/core/entry/core_entry.d.ts.map +0 -1
  317. package/dist/es2017/core/entry/core_text_displayer_interface.d.ts.map +0 -1
  318. package/dist/es2017/core/entry/create_content_time_boundaries_observer.d.ts.map +0 -1
  319. package/dist/es2017/core/entry/get_buffered_data_per_media_buffer.d.ts.map +0 -1
  320. package/dist/es2017/core/entry/get_thumbnail_data.d.ts.map +0 -1
  321. package/dist/es2017/core/entry/index.d.ts +0 -5
  322. package/dist/es2017/core/entry/index.d.ts.map +0 -1
  323. package/dist/es2017/core/entry/index.js +0 -2
  324. package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts.map +0 -1
  325. package/dist/es2017/core/entry/track_choice_setter.d.ts.map +0 -1
  326. package/dist/es2017/core/entry/utils.d.ts +0 -3
  327. package/dist/es2017/core/entry/utils.d.ts.map +0 -1
  328. package/dist/es2017/core/entry/utils.js +0 -8
  329. package/dist/es2017/main_thread/core_interface/base.d.ts +0 -13
  330. package/dist/es2017/main_thread/core_interface/base.d.ts.map +0 -1
  331. package/dist/es2017/main_thread/core_interface/base.js +0 -28
  332. package/dist/es2017/main_thread/core_interface/monothread.d.ts +0 -13
  333. package/dist/es2017/main_thread/core_interface/monothread.d.ts.map +0 -1
  334. package/dist/es2017/main_thread/core_interface/monothread.js +0 -32
  335. package/dist/es2017/main_thread/core_interface/multithread.d.ts +0 -25
  336. package/dist/es2017/main_thread/core_interface/multithread.d.ts.map +0 -1
  337. package/dist/es2017/main_thread/core_interface/multithread.js +0 -45
  338. package/dist/es2017/main_thread/core_interface/types.d.ts +0 -6
  339. package/dist/es2017/main_thread/core_interface/types.d.ts.map +0 -1
  340. package/dist/es2017/main_thread/core_interface/types.js +0 -1
  341. package/dist/es2017/playback_observer/core_playback_observer.d.ts.map +0 -1
  342. package/src/core/entry/index.ts +0 -4
  343. package/src/core/entry/utils.ts +0 -11
  344. package/src/main_thread/core_interface/README.md +0 -22
  345. package/src/main_thread/core_interface/base.ts +0 -36
  346. package/src/main_thread/core_interface/monothread.ts +0 -46
  347. package/src/main_thread/core_interface/multithread.ts +0 -49
  348. package/src/main_thread/core_interface/types.ts +0 -5
  349. /package/dist/commonjs/core/{entry → main/common}/synchronize_sinks_on_observation.js +0 -0
  350. /package/dist/es2017/core/{entry → main/common}/synchronize_sinks_on_observation.js +0 -0
@@ -1,148 +1,150 @@
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
+
1
17
  import type { IMediaElement } from "../../compat/browser_compatibility_types";
2
- import getEmeApiImplementation from "../../compat/eme";
18
+ import isCodecSupported from "../../compat/is_codec_supported";
3
19
  import mayMediaElementFailOnUndecipherableData from "../../compat/may_media_element_fail_on_undecipherable_data";
4
20
  import shouldReloadMediaSourceOnDecipherabilityUpdate from "../../compat/should_reload_media_source_on_decipherability_update";
5
- import type { ISegmentSinkMetrics } from "../../core/segment_sinks/segment_sinks_store";
21
+ import config from "../../config";
6
22
  import type {
7
23
  IAdaptiveRepresentationSelectorArguments,
8
- IAdaptationChoice,
9
- IResolutionInfo,
10
- ICreateMediaSourceCoreMessage,
11
- ISentError,
12
- ICoreMessage,
13
- ISentLogValue,
14
- } from "../../core/types";
15
- import { CoreMessageType } from "../../core/types";
24
+ IRepresentationEstimator,
25
+ } from "../../core/adaptive";
26
+ import AdaptiveRepresentationSelector from "../../core/adaptive";
27
+ import CmcdDataBuilder from "../../core/cmcd";
16
28
  import {
17
- EncryptedMediaError,
18
- MediaError,
19
- NetworkError,
20
- OtherError,
21
- SourceBufferError,
22
- } from "../../errors";
29
+ CdnPrioritizer,
30
+ createThumbnailFetcher,
31
+ ManifestFetcher,
32
+ SegmentQueueCreator,
33
+ } from "../../core/fetchers";
34
+ import createContentTimeBoundariesObserver from "../../core/main/common/create_content_time_boundaries_observer";
35
+ import type { IFreezeResolution } from "../../core/main/common/FreezeResolver";
36
+ import FreezeResolver from "../../core/main/common/FreezeResolver";
37
+ import getThumbnailData from "../../core/main/common/get_thumbnail_data";
38
+ import synchronizeSegmentSinksOnObservation from "../../core/main/common/synchronize_sinks_on_observation";
39
+ import SegmentSinksStore from "../../core/segment_sinks";
40
+ import type {
41
+ IStreamOrchestratorOptions,
42
+ IStreamOrchestratorCallbacks,
43
+ INeedsBufferFlushPayload,
44
+ } from "../../core/stream";
45
+ import StreamOrchestrator from "../../core/stream";
46
+ import type { ITextDisplayerInterface } from "../../core/types";
47
+ import type { EncryptedMediaError } from "../../errors";
48
+ import { MediaError } from "../../errors";
23
49
  import features from "../../features";
24
50
  import log from "../../log";
25
- import type { IManifestMetadata } from "../../manifest";
26
- import {
27
- replicateUpdatesOnManifestMetadata,
28
- updateDecipherabilityFromKeyIds,
29
- updateDecipherabilityFromProtectionData,
30
- } from "../../manifest";
31
- import MainMediaSourceInterface from "../../mse/main_media_source_interface";
32
- import type {
33
- IReadOnlyPlaybackObserver,
34
- IMediaElementPlaybackObserver,
35
- } from "../../playback_observer";
51
+ import type { IManifest, IPeriodMetadata, ICodecSupportInfo } from "../../manifest";
52
+ import type MainMediaSourceInterface from "../../mse/main_media_source_interface";
53
+ import type { IMediaElementPlaybackObserver } from "../../playback_observer";
36
54
  import type {
37
55
  ICmcdOptions,
38
56
  IInitialManifest,
39
57
  IKeySystemOption,
40
58
  IPlayerError,
41
- IRepresentationFilter,
42
59
  } from "../../public_types";
43
- import type { IThumbnailResponse, ITransportOptions } from "../../transports";
44
- import arrayFind from "../../utils/array_find";
60
+ import type { IThumbnailResponse, ITransportPipelines } from "../../transports";
61
+ import areArraysOfNumbersEqual from "../../utils/are_arrays_of_numbers_equal";
45
62
  import assert, { assertUnreachable } from "../../utils/assert";
46
- import idGenerator from "../../utils/id_generator";
63
+ import createCancellablePromise from "../../utils/create_cancellable_promise";
47
64
  import isNullOrUndefined from "../../utils/is_null_or_undefined";
48
- import type { IAcceptedLogValue } from "../../utils/logger";
65
+ import noop from "../../utils/noop";
49
66
  import objectAssign from "../../utils/object_assign";
50
67
  import type { IReadOnlySharedReference } from "../../utils/reference";
51
- import SharedReference from "../../utils/reference";
52
- import { RequestError } from "../../utils/request";
68
+ import type { ISyncOrAsyncValue } from "../../utils/sync_or_async";
69
+ import SyncOrAsync from "../../utils/sync_or_async";
53
70
  import type { CancellationSignal } from "../../utils/task_canceller";
54
- import TaskCanceller, { CancellationError } from "../../utils/task_canceller";
55
- import type CoreInterface from "../core_interface/types";
56
- import type { IContentProtection } from "../decrypt";
57
- import type IContentDecryptor from "../decrypt";
71
+ import TaskCanceller from "../../utils/task_canceller";
58
72
  import { ContentDecryptorState, getKeySystemConfiguration } from "../decrypt";
73
+ import type { IProcessedProtectionData } from "../decrypt";
74
+ import type ContentDecryptor from "../decrypt";
59
75
  import type { ITextDisplayer } from "../text_displayer";
60
- import { MainThreadMessageType } from "../types";
61
76
  import type { ITextDisplayerOptions } from "./types";
62
77
  import { ContentInitializer } from "./types";
63
- import type { ICorePlaybackObservation } from "./utils/create_core_playback_observer";
64
78
  import createCorePlaybackObserver from "./utils/create_core_playback_observer";
65
- import {
66
- resetMediaElement,
67
- disableRemotePlaybackOnManagedMediaSource,
68
- } from "./utils/create_media_source";
79
+ import createMediaSource from "./utils/create_media_source";
69
80
  import type { IInitialTimeOptions } from "./utils/get_initial_time";
70
81
  import getInitialTime from "./utils/get_initial_time";
71
82
  import getLoadedReference from "./utils/get_loaded_reference";
72
83
  import performInitialSeekAndPlay from "./utils/initial_seek_and_play";
84
+ import initializeContentDecryption from "./utils/initialize_content_decryption";
85
+ import MainThreadTextDisplayerInterface from "./utils/main_thread_text_displayer_interface";
73
86
  import RebufferingController from "./utils/rebuffering_controller";
74
- import StreamEventsEmitter from "./utils/stream_events_emitter/stream_events_emitter";
87
+ import StreamEventsEmitter from "./utils/stream_events_emitter";
75
88
  import listenToMediaError from "./utils/throw_on_media_error";
76
- import { updateManifestCodecSupport } from "./utils/update_manifest_codec_support";
77
-
78
- const generateContentId = idGenerator();
79
89
 
80
90
  /**
91
+ * Allows to load a new content thanks to the MediaSource Extensions (a.k.a. MSE)
92
+ * Web APIs.
93
+ *
94
+ * Through this `ContentInitializer`, a Manifest will be fetched (and depending
95
+ * on the situation, refreshed), a `MediaSource` instance will be linked to the
96
+ * wanted `HTMLMediaElement` and chunks of media data, called segments, will be
97
+ * pushed on buffers associated to this `MediaSource` instance.
98
+ *
81
99
  * @class MediaSourceContentInitializer
82
100
  */
83
101
  export default class MediaSourceContentInitializer extends ContentInitializer {
84
102
  /** Constructor settings associated to this `MediaSourceContentInitializer`. */
85
- private _settings: IInitializeArguments;
86
-
87
- /**
88
- * The Core may be sending messages as soon as we're preparing the content but
89
- * the `MediaSourceContentInitializer` is only able to handle all of them only
90
- * once `start`ed.
91
- *
92
- * As such `_queuedCoreMessages` is set to an Array when `prepare` has been
93
- * called but not `start` yet, and contains all core messages that have to
94
- * be processed when `start` is called.
95
- *
96
- * It is set to `null` when there's no need to rely on that queue (either not
97
- * yet `prepare`d or already `start`ed).
98
- */
99
- private _queuedCoreMessages: ICoreMessage[] | null;
100
-
101
- /**
102
- * Information relative to the current loaded content.
103
- *
104
- * `null` when no content is prepared yet.
105
- */
106
- private _currentContentInfo: IMediaSourceContentInitializerContentInfos | null;
103
+ private _initSettings: IInitializeArguments;
107
104
  /**
108
105
  * `TaskCanceller` allowing to abort everything that the
109
106
  * `MediaSourceContentInitializer` is doing.
110
107
  */
111
108
  private _initCanceller: TaskCanceller;
109
+ /** Interface allowing to fetch and refresh the Manifest. */
110
+ private _manifestFetcher: ManifestFetcher;
112
111
  /**
113
- * `TaskCanceller` allowing to abort and clean-up every task and resource
114
- * linked to the current `MediaSource` instance.
115
- *
116
- * It may be triggered either at content stop (and thus at the same time than
117
- * the `_initCanceller`) or when reloading the content.
112
+ * Reference to the `Manifest` Object:
113
+ * - as an asynchronous value if it is still in the process of being loaded.
114
+ * - as an synchronous value if it has been loaded
115
+ * - `null` if the load task has not started yet.
118
116
  */
119
- private _currentMediaSourceCanceller: TaskCanceller;
117
+ private _manifest: ISyncOrAsyncValue<IManifest> | null;
120
118
 
121
- private _awaitingRequests: {
122
- nextRequestId: number;
123
- /**
124
- * Stores the resolvers and the current messageId that is sent to the core to
125
- * receive segment sink metrics.
126
- * The purpose of collecting metrics is for monitoring and debugging.
127
- */
128
- pendingSinkMetrics: Map<
129
- number /* request id */,
130
- {
131
- resolve: (value: ISegmentSinkMetrics | undefined) => void;
119
+ private _cmcdDataBuilder: CmcdDataBuilder | null;
120
+
121
+ /**
122
+ * Describes the decryption capabilities on the current content, discriminated
123
+ * by a `status` property:
124
+ *
125
+ * - If set to `"uninitialized"`, decryption capabilities have not been
126
+ * set up yet.
127
+ *
128
+ * - If set to `"disabled"`, decryption capabilities are explicitely
129
+ * disabled. If encrypted content needs to be decrypted, the accompanying
130
+ * error `value` describes the reason why decryption is not enabled.
131
+ *
132
+ * - If set to `"enabled"`, decryption capabilities are available, and
133
+ * `value` points to the corresponding `ContentDecryptor`.
134
+ */
135
+ private _decryptionCapabilities:
136
+ | {
137
+ status: "uninitialized";
138
+ value: null;
132
139
  }
133
- >;
134
- /**
135
- * Stores the resolvers and the current messageId that is sent to the web worker to
136
- * receive image thumbnails.
137
- */
138
- pendingThumbnailFetching: Map<
139
- number /* request id */,
140
- {
141
- resolve: (value: IThumbnailResponse) => void;
142
- reject: (error: Error) => void;
140
+ | {
141
+ status: "disabled";
142
+ value: EncryptedMediaError;
143
143
  }
144
- >;
145
- };
144
+ | {
145
+ status: "enabled";
146
+ value: ContentDecryptor;
147
+ };
146
148
 
147
149
  /**
148
150
  * Create a new `MediaSourceContentInitializer`, associated to the given
@@ -151,173 +153,78 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
151
153
  */
152
154
  constructor(settings: IInitializeArguments) {
153
155
  super();
154
- this._settings = settings;
156
+ this._initSettings = settings;
155
157
  this._initCanceller = new TaskCanceller();
156
- this._currentMediaSourceCanceller = new TaskCanceller();
157
- this._currentMediaSourceCanceller.linkToSignal(this._initCanceller.signal);
158
- this._currentContentInfo = null;
159
- this._awaitingRequests = {
160
- nextRequestId: 0,
161
- pendingSinkMetrics: new Map(),
162
- pendingThumbnailFetching: new Map(),
163
- };
164
- this._queuedCoreMessages = null;
158
+ this._manifest = null;
159
+ this._decryptionCapabilities = { status: "uninitialized", value: null };
160
+ const urls = settings.url === undefined ? undefined : [settings.url];
161
+ this._cmcdDataBuilder =
162
+ settings.cmcd === undefined ? null : new CmcdDataBuilder(settings.cmcd);
163
+ this._manifestFetcher = new ManifestFetcher(urls, settings.transport, {
164
+ ...settings.manifestRequestSettings,
165
+ lowLatencyMode: settings.lowLatencyMode,
166
+ cmcdDataBuilder: this._cmcdDataBuilder,
167
+ });
165
168
  }
166
169
 
167
170
  /**
168
171
  * Perform non-destructive preparation steps, to prepare a future content.
172
+ * For now, this mainly mean loading the Manifest document.
169
173
  */
170
174
  public prepare(): void {
171
- if (this._currentContentInfo !== null || this._initCanceller.isUsed()) {
172
- return;
173
- }
174
- const contentId = generateContentId();
175
- const {
176
- adaptiveOptions,
177
- transport,
178
- transportOptions,
179
- useMseInWorker,
180
- coreInterface,
181
- } = this._settings;
182
- const { wantedBufferAhead, maxVideoBufferSize, maxBufferAhead, maxBufferBehind } =
183
- this._settings.bufferOptions;
184
- const initialVideoBitrate = adaptiveOptions.initialBitrates.video;
185
- const initialAudioBitrate = adaptiveOptions.initialBitrates.audio;
186
- this._currentContentInfo = {
187
- contentId,
188
- contentDecryptor: null,
189
- manifest: null,
190
- mediaSourceInfo: null,
191
- rebufferingController: null,
192
- streamEventsEmitter: null,
193
- initialTime: undefined,
194
- autoPlay: undefined,
195
- initialPlayPerformed: null,
196
- useMseInWorker,
197
- };
198
- coreInterface.sendMessage({
199
- type: MainThreadMessageType.PrepareContent,
200
- value: {
201
- contentId,
202
- cmcd: this._settings.cmcd,
203
- enableRepresentationAvoidance: this._settings.enableRepresentationAvoidance,
204
- url: this._settings.url,
205
- hasText: this._hasTextBufferFeature(),
206
- transport,
207
- transportOptions,
208
- initialVideoBitrate,
209
- initialAudioBitrate,
210
- manifestRetryOptions: {
211
- ...this._settings.manifestRequestSettings,
212
- lowLatencyMode: this._settings.lowLatencyMode,
213
- },
214
- segmentRetryOptions: this._settings.segmentRequestOptions,
215
- useMseInWorker,
216
- },
217
- });
218
- this._initCanceller.signal.register(() => {
219
- coreInterface.sendMessage({
220
- type: MainThreadMessageType.StopContent,
221
- contentId,
222
- value: null,
223
- });
224
- });
225
- if (this._initCanceller.isUsed()) {
175
+ if (this._manifest !== null) {
226
176
  return;
227
177
  }
228
- this._queuedCoreMessages = [];
229
- log.debug("Init", "addEventListener prepare buffering core messages");
230
- const onmessage = (msgData: ICoreMessage): void => {
231
- if (msgData.type !== CoreMessageType.LogMessage) {
232
- log.debug("Init", "Core message received", msgData.type);
233
- }
234
- const type = msgData.type;
235
- switch (type) {
236
- case CoreMessageType.LogMessage: {
237
- const formatted: IAcceptedLogValue[] = msgData.value.logs.map((l) => {
238
- switch (typeof l) {
239
- case "string":
240
- case "number":
241
- case "boolean":
242
- case "undefined":
243
- return l;
244
- case "object":
245
- if (l === null) {
246
- return null;
247
- }
248
- return formatSentLogObject(l);
249
- default:
250
- assertUnreachable(l);
251
- }
252
- });
253
- switch (msgData.value.logLevel) {
254
- case "NONE":
255
- break;
256
- case "ERROR":
257
- log.error(msgData.value.namespace, ...formatted);
258
- break;
259
- case "WARNING":
260
- log.warn(msgData.value.namespace, ...formatted);
261
- break;
262
- case "INFO":
263
- log.info(msgData.value.namespace, ...formatted);
264
- break;
265
- case "DEBUG":
266
- log.debug(msgData.value.namespace, ...formatted);
267
- break;
268
- default:
269
- assertUnreachable(msgData.value.logLevel);
270
- }
271
- break;
272
- }
273
- default:
274
- if (this._queuedCoreMessages !== null) {
275
- this._queuedCoreMessages.push(msgData);
276
- }
277
- break;
278
- }
279
- };
280
- this._settings.coreInterface.addMessageListener(onmessage);
281
- const onmessageerror = () => {
282
- log.error("Init", "Error when receiving message from core.");
283
- };
284
- this._settings.coreInterface.addErrorListener(onmessageerror);
178
+ this._manifest = SyncOrAsync.createAsync(
179
+ createCancellablePromise(this._initCanceller.signal, (res, rej) => {
180
+ this._manifestFetcher.addEventListener("warning", (err: IPlayerError) =>
181
+ this.trigger("warning", err),
182
+ );
183
+ this._manifestFetcher.addEventListener("error", (err: unknown) => {
184
+ this.trigger("error", err);
185
+ rej(err);
186
+ });
187
+ this._manifestFetcher.addEventListener("manifestReady", (manifest) => {
188
+ res(manifest);
189
+ });
190
+ }),
191
+ );
192
+ this._manifestFetcher.start();
285
193
  this._initCanceller.signal.register(() => {
286
- log.debug("Init", "removeEventListener prepare for core message");
287
- this._settings.coreInterface.removeMessageListener(onmessage);
288
- this._settings.coreInterface.removeErrorListener(onmessageerror);
194
+ this._manifestFetcher.dispose();
289
195
  });
196
+ }
290
197
 
291
- // Also bind all `SharedReference` objects:
198
+ /**
199
+ * @param {HTMLMediaElement} mediaElement
200
+ * @param {Object} playbackObserver
201
+ */
202
+ public start(
203
+ mediaElement: IMediaElement,
204
+ playbackObserver: IMediaElementPlaybackObserver,
205
+ ): void {
206
+ this.prepare(); // Load Manifest if not already done
292
207
 
293
- const throttleVideoBitrate =
294
- adaptiveOptions.throttlers.throttleBitrate.video ?? new SharedReference(Infinity);
295
- bindNumberReferencesToCore(
296
- coreInterface,
208
+ /** Translate errors coming from the media element into RxPlayer errors. */
209
+ listenToMediaError(
210
+ mediaElement,
211
+ (error: MediaError) => this._onFatalError(error),
297
212
  this._initCanceller.signal,
298
- [wantedBufferAhead, "wantedBufferAhead"],
299
- [maxVideoBufferSize, "maxVideoBufferSize"],
300
- [maxBufferAhead, "maxBufferAhead"],
301
- [maxBufferBehind, "maxBufferBehind"],
302
- [throttleVideoBitrate, "throttleVideoBitrate"],
303
213
  );
304
214
 
305
- const limitVideoResolution =
306
- adaptiveOptions.throttlers.limitResolution.video ??
307
- new SharedReference<IResolutionInfo>({
308
- height: undefined,
309
- width: undefined,
310
- pixelRatio: 1,
215
+ this._setupInitialMediaSourceAndDecryption(mediaElement)
216
+ .then((initResult) =>
217
+ this._onInitialMediaSourceReady(
218
+ mediaElement,
219
+ initResult.mediaSource,
220
+ playbackObserver,
221
+ initResult.drmSystemId,
222
+ initResult.unlinkMediaSource,
223
+ ),
224
+ )
225
+ .catch((err) => {
226
+ this._onFatalError(err);
311
227
  });
312
- limitVideoResolution.onUpdate(
313
- (newVal) => {
314
- coreInterface.sendMessage({
315
- type: MainThreadMessageType.ReferenceUpdate,
316
- value: { name: "limitVideoResolution", newVal },
317
- });
318
- },
319
- { clearSignal: this._initCanceller.signal, emitCurrentValue: true },
320
- );
321
228
  }
322
229
 
323
230
  /**
@@ -328,472 +235,589 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
328
235
  * DASH's MPD) will be refreshed immediately.
329
236
  */
330
237
  public updateContentUrls(urls: string[] | undefined, refreshNow: boolean): void {
331
- if (this._currentContentInfo === null) {
332
- return;
333
- }
334
- this._settings.coreInterface.sendMessage({
335
- type: MainThreadMessageType.ContentUrlsUpdate,
336
- contentId: this._currentContentInfo.contentId,
337
- value: { urls, refreshNow },
338
- });
238
+ this._manifestFetcher.updateContentUrls(urls, refreshNow);
339
239
  }
340
240
 
341
241
  /**
342
- * @param {HTMLMediaElement} mediaElement
343
- * @param {Object} playbackObserver
242
+ * Stop content and free all resources linked to this
243
+ * `MediaSourceContentInitializer`.
344
244
  */
345
- public start(
346
- mediaElement: IMediaElement,
347
- playbackObserver: IMediaElementPlaybackObserver,
348
- ): void {
349
- this.prepare(); // Load Manifest if not already done
245
+ public dispose(): void {
246
+ this._initCanceller.cancel();
247
+ }
248
+
249
+ /**
250
+ * Callback called when an error interrupting playback arised.
251
+ * @param {*} err
252
+ */
253
+ private _onFatalError(err: unknown) {
350
254
  if (this._initCanceller.isUsed()) {
351
255
  return;
352
256
  }
257
+ this._initCanceller.cancel();
258
+ this.trigger("error", err);
259
+ }
353
260
 
354
- let textDisplayer: ITextDisplayer | null = null;
355
- if (
356
- this._settings.textTrackOptions.textTrackMode === "html" &&
357
- features.htmlTextDisplayer !== null
358
- ) {
359
- assert(this._hasTextBufferFeature());
360
- textDisplayer = new features.htmlTextDisplayer(
261
+ /**
262
+ * Initialize decryption mechanisms if needed and begin creating and relying
263
+ * on the initial `MediaSourceInterface` for this content.
264
+ * @param {HTMLMediaElement|null} mediaElement
265
+ * @returns {Promise.<Object>}
266
+ */
267
+ private _setupInitialMediaSourceAndDecryption(mediaElement: IMediaElement): Promise<{
268
+ mediaSource: MainMediaSourceInterface;
269
+ drmSystemId: string | undefined;
270
+ unlinkMediaSource: TaskCanceller;
271
+ }> {
272
+ const initCanceller = this._initCanceller;
273
+ return createCancellablePromise(initCanceller.signal, (resolve) => {
274
+ const { keySystems } = this._initSettings;
275
+
276
+ /** Initialize decryption capabilities. */
277
+ const { statusRef: drmInitRef, contentDecryptor } = initializeContentDecryption(
361
278
  mediaElement,
362
- this._settings.textTrackOptions.textTrackElement,
279
+ keySystems,
280
+ {
281
+ onWarning: (err: IPlayerError) => this.trigger("warning", err),
282
+ onError: (err: Error) => this._onFatalError(err),
283
+ onBlackListProtectionData: (val) => {
284
+ // Ugly IIFE workaround to allow async event listener
285
+ (async () => {
286
+ if (this._manifest === null) {
287
+ return;
288
+ }
289
+ const manifest =
290
+ this._manifest.syncValue ?? (await this._manifest.getValueAsAsync());
291
+ blackListProtectionDataOnManifest(manifest, val);
292
+ })().catch(noop);
293
+ },
294
+ onKeyIdsCompatibilityUpdate: (updates) => {
295
+ // Ugly IIFE workaround to allow async event listener
296
+ (async () => {
297
+ if (this._manifest === null) {
298
+ return;
299
+ }
300
+ const manifest =
301
+ this._manifest.syncValue ?? (await this._manifest.getValueAsAsync());
302
+ updateKeyIdsDecipherabilityOnManifest(
303
+ manifest,
304
+ updates.whitelistedKeyIds,
305
+ updates.blacklistedKeyIds,
306
+ updates.delistedKeyIds,
307
+ );
308
+ })().catch(noop);
309
+ },
310
+
311
+ onCodecSupportUpdate: () => {
312
+ const syncManifest = this._manifest?.syncValue;
313
+ if (isNullOrUndefined(syncManifest)) {
314
+ // The Manifest is not yet fetched, but we will be able to check
315
+ // the codecs once it is the case
316
+ this._manifest?.getValueAsAsync().then((loadedManifest) => {
317
+ if (this._initCanceller.isUsed()) {
318
+ return;
319
+ }
320
+ this._refreshManifestCodecSupport(loadedManifest, mediaElement);
321
+ }, noop);
322
+ } else {
323
+ this._refreshManifestCodecSupport(syncManifest, mediaElement);
324
+ }
325
+ },
326
+ },
327
+ initCanceller.signal,
328
+ );
329
+
330
+ if (contentDecryptor.enabled) {
331
+ this._decryptionCapabilities = {
332
+ status: "enabled",
333
+ value: contentDecryptor.value,
334
+ };
335
+ } else {
336
+ this._decryptionCapabilities = {
337
+ status: "disabled",
338
+ value: contentDecryptor.value,
339
+ };
340
+ }
341
+
342
+ drmInitRef.onUpdate(
343
+ (drmStatus, stopListeningToDrmUpdates) => {
344
+ if (drmStatus.initializationState.type === "uninitialized") {
345
+ return;
346
+ }
347
+ stopListeningToDrmUpdates();
348
+
349
+ const mediaSourceCanceller = new TaskCanceller();
350
+ mediaSourceCanceller.linkToSignal(initCanceller.signal);
351
+ createMediaSource(mediaElement, mediaSourceCanceller.signal)
352
+ .then((mediaSource) => {
353
+ const lastDrmStatus = drmInitRef.getValue();
354
+ if (lastDrmStatus.initializationState.type === "awaiting-media-link") {
355
+ lastDrmStatus.initializationState.value.isMediaLinked.setValue(true);
356
+ drmInitRef.onUpdate(
357
+ (newDrmStatus, stopListeningToDrmUpdatesAgain) => {
358
+ if (newDrmStatus.initializationState.type === "initialized") {
359
+ stopListeningToDrmUpdatesAgain();
360
+ resolve({
361
+ mediaSource,
362
+ drmSystemId: newDrmStatus.drmSystemId,
363
+ unlinkMediaSource: mediaSourceCanceller,
364
+ });
365
+ return;
366
+ }
367
+ },
368
+ { emitCurrentValue: true, clearSignal: initCanceller.signal },
369
+ );
370
+ } else if (drmStatus.initializationState.type === "initialized") {
371
+ resolve({
372
+ mediaSource,
373
+ drmSystemId: drmStatus.drmSystemId,
374
+ unlinkMediaSource: mediaSourceCanceller,
375
+ });
376
+ return;
377
+ }
378
+ })
379
+ .catch((err) => {
380
+ if (mediaSourceCanceller.isUsed()) {
381
+ return;
382
+ }
383
+ this._onFatalError(err);
384
+ });
385
+ },
386
+ { emitCurrentValue: true, clearSignal: initCanceller.signal },
363
387
  );
364
- } else if (features.nativeTextDisplayer !== null) {
365
- assert(this._hasTextBufferFeature());
366
- textDisplayer = new features.nativeTextDisplayer(mediaElement);
367
- } else {
368
- assert(!this._hasTextBufferFeature());
369
- }
370
- this._initCanceller.signal.register(() => {
371
- textDisplayer?.stop();
372
388
  });
389
+ }
373
390
 
374
- /** Translate errors coming from the media element into RxPlayer errors. */
375
- listenToMediaError(
376
- mediaElement,
377
- (error: MediaError) => this._onFatalError(error),
378
- this._initCanceller.signal,
391
+ private async _onInitialMediaSourceReady(
392
+ mediaElement: IMediaElement,
393
+ initialMediaSource: MainMediaSourceInterface,
394
+ playbackObserver: IMediaElementPlaybackObserver,
395
+ drmSystemId: string | undefined,
396
+ initialMediaSourceCanceller: TaskCanceller,
397
+ ): Promise<void> {
398
+ const {
399
+ adaptiveOptions,
400
+ autoPlay,
401
+ bufferOptions,
402
+ lowLatencyMode,
403
+ segmentRequestOptions,
404
+ speed,
405
+ startAt,
406
+ textTrackOptions,
407
+ transport,
408
+ } = this._initSettings;
409
+ const initCanceller = this._initCanceller;
410
+ assert(this._manifest !== null);
411
+ let manifest: IManifest;
412
+ try {
413
+ manifest = this._manifest.syncValue ?? (await this._manifest.getValueAsAsync());
414
+ } catch (_e) {
415
+ return; // The error should already have been processed through an event listener
416
+ }
417
+
418
+ manifest.addEventListener(
419
+ "manifestUpdate",
420
+ (updates) => {
421
+ this.trigger("manifestUpdate", updates);
422
+ this._refreshManifestCodecSupport(manifest, mediaElement);
423
+ },
424
+ initCanceller.signal,
379
425
  );
380
426
 
381
- /**
382
- * Send content protection initialization data.
383
- * TODO remove and use ContentDecryptor directly when possible.
384
- */
385
- const lastContentProtection = new SharedReference<IContentProtection | null>(null);
427
+ manifest.addEventListener(
428
+ "decipherabilityUpdate",
429
+ (elts) => {
430
+ this.trigger("decipherabilityUpdate", elts);
431
+ },
432
+ initCanceller.signal,
433
+ );
386
434
 
387
- const mediaSourceStatus = new SharedReference<MediaSourceInitializationStatus>(
388
- MediaSourceInitializationStatus.Nothing,
435
+ manifest.addEventListener(
436
+ "supportUpdate",
437
+ () => {
438
+ this.trigger("codecSupportUpdate", null);
439
+ },
440
+ initCanceller.signal,
389
441
  );
390
442
 
391
- const { statusRef: drmInitializationStatus, contentDecryptor } =
392
- this._initializeContentDecryption(
393
- mediaElement,
394
- lastContentProtection,
395
- mediaSourceStatus,
396
- () => notifyAndStartMediaSourceReload(0, undefined, undefined),
397
- this._initCanceller.signal,
398
- );
399
- const contentInfo = this._currentContentInfo;
400
- if (contentInfo !== null) {
401
- contentInfo.contentDecryptor = contentDecryptor;
443
+ log.debug("Init", "Calculating initial time");
444
+ const initialTime = getInitialTime(manifest, lowLatencyMode, startAt);
445
+ log.debug("Init", "Initial time calculated", { initialTime });
446
+
447
+ /** Choose the right "Representation" for a given "Adaptation". */
448
+ const representationEstimator = AdaptiveRepresentationSelector(adaptiveOptions);
449
+ const subBufferOptions = objectAssign(
450
+ { textTrackOptions, drmSystemId },
451
+ bufferOptions,
452
+ );
453
+
454
+ const cdnPrioritizer = new CdnPrioritizer(initCanceller.signal);
455
+ const segmentQueueCreator = new SegmentQueueCreator(
456
+ transport,
457
+ cdnPrioritizer,
458
+ this._cmcdDataBuilder,
459
+ segmentRequestOptions,
460
+ );
461
+
462
+ this._refreshManifestCodecSupport(manifest, mediaElement);
463
+ this.trigger("manifestReady", manifest);
464
+ if (initCanceller.isUsed()) {
465
+ return;
402
466
  }
403
467
 
404
- const playbackStartParams = {
405
- mediaElement,
406
- textDisplayer,
407
- playbackObserver,
408
- drmInitializationStatus,
409
- mediaSourceStatus,
410
- };
411
- mediaSourceStatus.onUpdate(
412
- (msInitStatus, stopListeningMSStatus) => {
413
- if (msInitStatus === MediaSourceInitializationStatus.Attached) {
414
- stopListeningMSStatus();
415
- this._startPlaybackIfReady(playbackStartParams);
416
- }
468
+ // handle initial load and reloads
469
+ this._setupContentWithNewMediaSource(
470
+ {
471
+ mediaElement,
472
+ playbackObserver,
473
+ mediaSource: initialMediaSource,
474
+ initialTime,
475
+ autoPlay,
476
+ manifest,
477
+ representationEstimator,
478
+ cdnPrioritizer,
479
+ segmentQueueCreator,
480
+ speed,
481
+ bufferOptions: subBufferOptions,
417
482
  },
418
- { clearSignal: this._initCanceller.signal, emitCurrentValue: true },
483
+ initialMediaSourceCanceller,
419
484
  );
420
- drmInitializationStatus.onUpdate(
421
- (initializationStatus, stopListeningDrm) => {
422
- if (initializationStatus.initializationState.type === "initialized") {
423
- stopListeningDrm();
424
- this._startPlaybackIfReady(playbackStartParams);
425
- }
426
- },
427
- { emitCurrentValue: true, clearSignal: this._initCanceller.signal },
485
+ }
486
+
487
+ /**
488
+ * Load the content defined by the Manifest in the mediaSource given at the
489
+ * given position and playing status.
490
+ * This function recursively re-call itself when a MediaSource reload is
491
+ * wanted.
492
+ * @param {Object} args
493
+ * @param {Object} currentCanceller
494
+ */
495
+ private _setupContentWithNewMediaSource(
496
+ args: IBufferingMediaSettings,
497
+ currentCanceller: TaskCanceller,
498
+ ): void {
499
+ this._startLoadingContentOnMediaSource(
500
+ args,
501
+ this._createReloadMediaSourceCallback(args, currentCanceller),
502
+ currentCanceller.signal,
428
503
  );
504
+ }
429
505
 
430
- /**
431
- * Reset directly (synchronously) the current `MediaSource` and signal to
432
- * the core that we did so.
433
- * @param {number} deltaPosition - Position you want to seek to after
434
- * reloading, as a delta in seconds from the last polled playing position.
435
- * @param {number|undefined} minimumPosition - If set, minimum time bound
436
- * in seconds after `deltaPosition` has been applied.
437
- * @param {number|undefined} maximumPosition - If set, minimum time bound
438
- * in seconds after `deltaPosition` has been applied.
439
- */
440
- const notifyAndStartMediaSourceReload = (
441
- deltaPosition: number,
442
- minimumPosition: number | undefined,
443
- maximumPosition: number | undefined,
444
- ): void => {
445
- const reloadingContentInfo = this._currentContentInfo;
446
- if (reloadingContentInfo === null) {
447
- log.warn("Init", "Asked to reload when no content is loaded.");
448
- return;
449
- }
450
- if (
451
- reloadingContentInfo === null ||
452
- reloadingContentInfo.mediaSourceInfo === null
453
- ) {
454
- log.warn("Init", "Asked to reload when no MediaSource is active.");
506
+ /**
507
+ * Create `IReloadMediaSourceCallback` allowing to handle reload orders.
508
+ * @param {Object} args
509
+ * @param {Object} currentCanceller
510
+ */
511
+ private _createReloadMediaSourceCallback(
512
+ args: IBufferingMediaSettings,
513
+ currentCanceller: TaskCanceller,
514
+ ): IReloadMediaSourceCallback {
515
+ const initCanceller = this._initCanceller;
516
+ return (reloadOrder: { position: number; autoPlay: boolean }): void => {
517
+ currentCanceller.cancel();
518
+ if (initCanceller.isUsed()) {
455
519
  return;
456
520
  }
457
-
458
- const mediaSourceId =
459
- reloadingContentInfo.mediaSourceInfo.type === "main"
460
- ? reloadingContentInfo.mediaSourceInfo.mediaSource.id
461
- : reloadingContentInfo.mediaSourceInfo.mediaSourceId;
462
- this._settings.coreInterface.sendMessage({
463
- type: MainThreadMessageType.MediaSourceReload,
464
- mediaSourceId,
465
- value: null,
466
- });
467
- reloadMediaSource(deltaPosition, minimumPosition, maximumPosition);
468
- };
469
-
470
- /**
471
- * Reset directly (synchronously) the current `MediaSource`.
472
- *
473
- * It is assumed that `core` already knows about this action. If not, call
474
- * `notifyAndStartMediaSourceReload` instead.
475
- * @param {number} deltaPosition - Position you want to seek to after
476
- * reloading, as a delta in seconds from the last polled playing position.
477
- * @param {number|undefined} minimumPosition - If set, minimum time bound
478
- * in seconds after `deltaPosition` has been applied.
479
- * @param {number|undefined} maximumPosition - If set, minimum time bound
480
- * in seconds after `deltaPosition` has been applied.
481
- */
482
- const reloadMediaSource = (
483
- deltaPosition: number,
484
- minimumPosition: number | undefined,
485
- maximumPosition: number | undefined,
486
- ): void => {
487
- const reloadingContentInfo = this._currentContentInfo;
488
- if (reloadingContentInfo === null) {
489
- log.warn("Init", "Asked to reload when no content is loaded.");
521
+ this.trigger("reloadingMediaSource", reloadOrder);
522
+ if (initCanceller.isUsed()) {
490
523
  return;
491
524
  }
492
- const lastObservation = playbackObserver.getReference().getValue();
493
- const currentPosition = lastObservation.position.getWanted();
494
- const isPaused =
495
- reloadingContentInfo.initialPlayPerformed?.getValue() === true ||
496
- reloadingContentInfo.autoPlay === undefined
497
- ? lastObservation.paused
498
- : !reloadingContentInfo.autoPlay;
499
- let position = currentPosition + deltaPosition;
500
- if (minimumPosition !== undefined) {
501
- position = Math.max(minimumPosition, position);
502
- }
503
- if (maximumPosition !== undefined) {
504
- position = Math.min(maximumPosition, position);
505
- }
506
-
507
- this._reload(
508
- mediaElement,
509
- textDisplayer,
510
- playbackObserver,
511
- mediaSourceStatus,
512
- position,
513
- !isPaused,
514
- );
515
- };
516
525
 
517
- const onmessage = (msgData: ICoreMessage) => {
518
- switch (msgData.type) {
519
- case CoreMessageType.AttachMediaSource: {
520
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
521
- return;
522
- }
523
- if (this._currentContentInfo !== null) {
524
- if (this._currentContentInfo.mediaSourceInfo?.type === "main") {
525
- this._currentContentInfo.mediaSourceInfo.mediaSource.dispose();
526
- }
527
- this._currentContentInfo.mediaSourceInfo = {
528
- type: "core",
529
- mediaSourceId: msgData.mediaSourceId,
530
- };
531
- }
532
- const mediaSourceLink = msgData.value;
533
- mediaSourceStatus.onUpdate(
534
- (currStatus, stopListening) => {
535
- if (currStatus === MediaSourceInitializationStatus.AttachNow) {
536
- stopListening();
537
- log.info("media", "Attaching MediaSource URL to the media element");
538
- if (mediaSourceLink.type === "handle") {
539
- mediaElement.srcObject = mediaSourceLink.value;
540
- this._currentMediaSourceCanceller.signal.register(() => {
541
- mediaElement.srcObject = null;
542
- });
543
- } else {
544
- mediaElement.src = mediaSourceLink.value;
545
- this._currentMediaSourceCanceller.signal.register(() => {
546
- resetMediaElement(mediaElement, mediaSourceLink.value);
547
- });
548
- }
549
- disableRemotePlaybackOnManagedMediaSource(
550
- mediaElement,
551
- this._currentMediaSourceCanceller.signal,
552
- );
553
- mediaSourceStatus.setValue(MediaSourceInitializationStatus.Attached);
554
- }
526
+ const newCanceller = new TaskCanceller();
527
+ newCanceller.linkToSignal(initCanceller.signal);
528
+ createMediaSource(args.mediaElement, newCanceller.signal)
529
+ .then((newMediaSource) => {
530
+ this._setupContentWithNewMediaSource(
531
+ {
532
+ ...args,
533
+ mediaSource: newMediaSource,
534
+ initialTime: reloadOrder.position,
535
+ autoPlay: reloadOrder.autoPlay,
555
536
  },
556
- { emitCurrentValue: true, clearSignal: this._initCanceller.signal },
537
+ newCanceller,
557
538
  );
558
- break;
559
- }
560
-
561
- case CoreMessageType.Warning:
562
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
563
- return;
564
- }
565
- this.trigger("warning", formatCoreError(msgData.value));
566
- break;
567
-
568
- case CoreMessageType.Error:
569
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
539
+ })
540
+ .catch((err) => {
541
+ if (newCanceller.isUsed()) {
570
542
  return;
571
543
  }
572
- this._onFatalError(formatCoreError(msgData.value));
573
- break;
574
-
575
- case CoreMessageType.CreateMediaSource:
576
- this._onCreateMediaSourceMessage(
577
- msgData,
578
- mediaElement,
579
- mediaSourceStatus,
580
- this._settings.coreInterface,
544
+ this._onFatalError(err);
545
+ });
546
+ };
547
+ }
548
+
549
+ /**
550
+ * Buffer the content on the given MediaSource.
551
+ * @param {Object} args
552
+ * @param {function} onReloadOrder
553
+ * @param {Object} cancelSignal
554
+ */
555
+ private _startLoadingContentOnMediaSource(
556
+ args: IBufferingMediaSettings,
557
+ onReloadOrder: IReloadMediaSourceCallback,
558
+ cancelSignal: CancellationSignal,
559
+ ): void {
560
+ const {
561
+ autoPlay,
562
+ bufferOptions,
563
+ initialTime,
564
+ manifest,
565
+ mediaElement,
566
+ mediaSource,
567
+ playbackObserver,
568
+ representationEstimator,
569
+ cdnPrioritizer,
570
+ segmentQueueCreator,
571
+ speed,
572
+ } = args;
573
+ const { transport } = this._initSettings;
574
+
575
+ const initialPeriod =
576
+ manifest.getPeriodForTime(initialTime) ?? manifest.getNextPeriod(initialTime);
577
+ if (initialPeriod === undefined) {
578
+ const error = new MediaError(
579
+ "MEDIA_STARTING_TIME_NOT_FOUND",
580
+ "Wanted starting time not found in the Manifest.",
581
+ );
582
+ return this._onFatalError(error);
583
+ }
584
+
585
+ let textDisplayerInterface: ITextDisplayerInterface | null = null;
586
+ const textDisplayer = createTextDisplayer(
587
+ mediaElement,
588
+ this._initSettings.textTrackOptions,
589
+ );
590
+ if (textDisplayer !== null) {
591
+ const sender = new MainThreadTextDisplayerInterface(textDisplayer);
592
+ textDisplayerInterface = sender;
593
+ cancelSignal.register(() => {
594
+ sender.stop();
595
+ textDisplayer?.stop();
596
+ });
597
+ }
598
+
599
+ /** Interface to create media buffers. */
600
+ const segmentSinksStore = new SegmentSinksStore(
601
+ mediaSource,
602
+ mediaElement.nodeName === "VIDEO",
603
+ textDisplayerInterface,
604
+ );
605
+
606
+ cancelSignal.register(() => {
607
+ segmentSinksStore.disposeAll();
608
+ });
609
+
610
+ const { autoPlayResult, initialPlayPerformed } = performInitialSeekAndPlay(
611
+ {
612
+ mediaElement,
613
+ playbackObserver,
614
+ startTime: initialTime,
615
+ mustAutoPlay: autoPlay,
616
+ onWarning: (err) => {
617
+ this.trigger("warning", err);
618
+ },
619
+ isDirectfile: false,
620
+ },
621
+ cancelSignal,
622
+ );
623
+
624
+ if (cancelSignal.isCancelled()) {
625
+ return;
626
+ }
627
+
628
+ initialPlayPerformed.onUpdate(
629
+ (isPerformed, stopListening) => {
630
+ if (isPerformed) {
631
+ stopListening();
632
+ const streamEventsEmitter = new StreamEventsEmitter(manifest, playbackObserver);
633
+ manifest.addEventListener(
634
+ "manifestUpdate",
635
+ () => {
636
+ streamEventsEmitter.onManifestUpdate(manifest);
637
+ },
638
+ cancelSignal,
581
639
  );
582
- break;
583
-
584
- case CoreMessageType.AddSourceBuffer:
585
- {
586
- if (
587
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
588
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
589
- msgData.mediaSourceId
590
- ) {
591
- return;
592
- }
593
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
594
- mediaSource.addSourceBuffer(
595
- msgData.value.sourceBufferType,
596
- msgData.value.codec,
597
- );
598
- }
599
- break;
600
-
601
- case CoreMessageType.SourceBufferAppend:
602
- {
603
- if (
604
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
605
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
606
- msgData.mediaSourceId
607
- ) {
608
- return;
609
- }
610
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
611
- const sourceBuffer = arrayFind(
612
- mediaSource.sourceBuffers,
613
- (s) => s.type === msgData.sourceBufferType,
614
- );
615
- if (sourceBuffer === undefined) {
616
- return;
617
- }
618
- sourceBuffer
619
- .appendBuffer(msgData.value.data, msgData.value.params)
620
- .then((buffered) => {
621
- this._settings.coreInterface.sendMessage({
622
- type: MainThreadMessageType.SourceBufferSuccess,
623
- mediaSourceId: mediaSource.id,
624
- sourceBufferType: sourceBuffer.type,
625
- operationId: msgData.operationId,
626
- value: { buffered },
627
- });
628
- })
629
- .catch((error) => {
630
- this._settings.coreInterface.sendMessage({
631
- type: MainThreadMessageType.SourceBufferError,
632
- mediaSourceId: mediaSource.id,
633
- sourceBufferType: sourceBuffer.type,
634
- operationId: msgData.operationId,
635
- value:
636
- error instanceof CancellationError
637
- ? { errorName: "CancellationError" }
638
- : formatSourceBufferError(error).serialize(),
639
- });
640
- });
640
+ streamEventsEmitter.addEventListener(
641
+ "event",
642
+ (payload) => {
643
+ this.trigger("streamEvent", payload);
644
+ },
645
+ cancelSignal,
646
+ );
647
+ streamEventsEmitter.addEventListener(
648
+ "eventSkip",
649
+ (payload) => {
650
+ this.trigger("streamEventSkip", payload);
651
+ },
652
+ cancelSignal,
653
+ );
654
+ streamEventsEmitter.start();
655
+ cancelSignal.register(() => {
656
+ streamEventsEmitter.stop();
657
+ });
658
+ }
659
+ },
660
+ { clearSignal: cancelSignal, emitCurrentValue: true },
661
+ );
662
+
663
+ const coreObserver = createCorePlaybackObserver(
664
+ playbackObserver,
665
+ {
666
+ autoPlay,
667
+ manifest,
668
+ mediaSource,
669
+ textDisplayer,
670
+ initialPlayPerformed,
671
+ speed,
672
+ },
673
+ cancelSignal,
674
+ );
675
+
676
+ this._cmcdDataBuilder?.startMonitoringPlayback(coreObserver);
677
+ cancelSignal.register(() => {
678
+ this._cmcdDataBuilder?.stopMonitoringPlayback();
679
+ });
680
+
681
+ const rebufferingController = this._createRebufferingController(
682
+ playbackObserver,
683
+ manifest,
684
+ speed,
685
+ cancelSignal,
686
+ );
687
+ const freezeResolver = new FreezeResolver(segmentSinksStore);
688
+
689
+ if (mayMediaElementFailOnUndecipherableData()) {
690
+ // On some devices, just reload immediately when data become undecipherable
691
+ manifest.addEventListener(
692
+ "decipherabilityUpdate",
693
+ (elts) => {
694
+ if (elts.some((e) => e.representation.decipherable !== true)) {
695
+ reloadMediaSource(0, undefined, undefined);
641
696
  }
642
- break;
643
-
644
- case CoreMessageType.SourceBufferRemove:
645
- {
646
- if (
647
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
648
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
649
- msgData.mediaSourceId
650
- ) {
651
- return;
652
- }
653
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
654
- const sourceBuffer = arrayFind(
655
- mediaSource.sourceBuffers,
656
- (s) => s.type === msgData.sourceBufferType,
657
- );
658
- if (sourceBuffer === undefined) {
659
- return;
660
- }
661
- sourceBuffer
662
- .remove(msgData.value.start, msgData.value.end)
663
- .then((buffered) => {
664
- this._settings.coreInterface.sendMessage({
665
- type: MainThreadMessageType.SourceBufferSuccess,
666
- mediaSourceId: mediaSource.id,
667
- sourceBufferType: sourceBuffer.type,
668
- operationId: msgData.operationId,
669
- value: { buffered },
670
- });
671
- })
672
- .catch((error) => {
673
- this._settings.coreInterface.sendMessage({
674
- type: MainThreadMessageType.SourceBufferError,
675
- mediaSourceId: mediaSource.id,
676
- sourceBufferType: sourceBuffer.type,
677
- operationId: msgData.operationId,
678
- value:
679
- error instanceof CancellationError
680
- ? { errorName: "CancellationError" }
681
- : formatSourceBufferError(error).serialize(),
682
- });
697
+ },
698
+ cancelSignal,
699
+ );
700
+ }
701
+
702
+ coreObserver.listen(
703
+ (observation) => {
704
+ synchronizeSegmentSinksOnObservation(observation, segmentSinksStore);
705
+ const freezeResolution = freezeResolver.onNewObservation(observation);
706
+ if (freezeResolution === null) {
707
+ return;
708
+ }
709
+
710
+ // TODO: The following method looks generic, we may be able to factorize
711
+ // it with other reload handlers after some work.
712
+ const triggerReload = () => {
713
+ const lastObservation = playbackObserver.getReference().getValue();
714
+ const position = lastObservation.position.isAwaitingFuturePosition()
715
+ ? lastObservation.position.getWanted()
716
+ : (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
717
+ const autoplay = initialPlayPerformed.getValue()
718
+ ? !playbackObserver.getIsPaused()
719
+ : autoPlay;
720
+ onReloadOrder({ position, autoPlay: autoplay });
721
+ };
722
+
723
+ handleFreezeResolution(freezeResolution, {
724
+ enableRepresentationAvoidance: this._initSettings.enableRepresentationAvoidance,
725
+ manifest,
726
+ triggerReload,
727
+ playbackObserver,
728
+ });
729
+ },
730
+ { clearSignal: cancelSignal },
731
+ );
732
+
733
+ const contentTimeBoundariesObserver = createContentTimeBoundariesObserver(
734
+ manifest,
735
+ mediaSource,
736
+ coreObserver,
737
+ segmentSinksStore,
738
+ {
739
+ onWarning: (err: IPlayerError) => this.trigger("warning", err),
740
+ onPeriodChanged: (period: IPeriodMetadata) =>
741
+ this.trigger("activePeriodChanged", { period }),
742
+ },
743
+ cancelSignal,
744
+ );
745
+
746
+ /**
747
+ * Emit a "loaded" events once the initial play has been performed and the
748
+ * media can begin playback.
749
+ * Also emits warning events if issues arise when doing so.
750
+ */
751
+ autoPlayResult
752
+ .then(() => {
753
+ getLoadedReference(playbackObserver, false, cancelSignal).onUpdate(
754
+ (isLoaded, stopListening) => {
755
+ if (isLoaded) {
756
+ const fetchThumbnails = createThumbnailFetcher(
757
+ transport.thumbnails,
758
+ cdnPrioritizer,
759
+ );
760
+ stopListening();
761
+ this.trigger("loaded", {
762
+ getSegmentSinkMetrics: async () => {
763
+ return new Promise((resolve) =>
764
+ resolve(segmentSinksStore.getSegmentSinksMetrics()),
765
+ );
766
+ },
767
+ getThumbnailData: async (
768
+ periodId: string,
769
+ thumbnailTrackId: string,
770
+ time: number,
771
+ ): Promise<IThumbnailResponse> => {
772
+ return getThumbnailData(
773
+ fetchThumbnails,
774
+ manifest,
775
+ periodId,
776
+ thumbnailTrackId,
777
+ time,
778
+ );
779
+ },
683
780
  });
684
- }
685
- break;
686
-
687
- case CoreMessageType.AbortSourceBuffer:
688
- {
689
- if (
690
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
691
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
692
- msgData.mediaSourceId
693
- ) {
694
- return;
695
- }
696
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
697
- const sourceBuffer = arrayFind(
698
- mediaSource.sourceBuffers,
699
- (s) => s.type === msgData.sourceBufferType,
700
- );
701
- if (sourceBuffer === undefined) {
702
- return;
703
- }
704
- sourceBuffer.abort();
705
- }
706
- break;
707
-
708
- case CoreMessageType.UpdateMediaSourceDuration:
709
- {
710
- if (
711
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
712
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
713
- msgData.mediaSourceId
714
- ) {
715
- return;
716
- }
717
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
718
- if (mediaSource?.id !== msgData.mediaSourceId) {
719
- return;
720
- }
721
- mediaSource.setDuration(msgData.value.duration, msgData.value.isRealEndKnown);
722
- }
723
- break;
724
-
725
- case CoreMessageType.InterruptMediaSourceDurationUpdate:
726
- {
727
- if (
728
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
729
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
730
- msgData.mediaSourceId
731
- ) {
732
- return;
733
781
  }
734
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
735
- if (mediaSource?.id !== msgData.mediaSourceId) {
736
- return;
737
- }
738
- mediaSource.interruptDurationSetting();
739
- }
740
- break;
741
-
742
- case CoreMessageType.EndOfStream:
743
- {
744
- if (
745
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
746
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
747
- msgData.mediaSourceId
748
- ) {
749
- return;
750
- }
751
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
752
- mediaSource.maintainEndOfStream();
753
- }
754
- break;
755
-
756
- case CoreMessageType.InterruptEndOfStream:
757
- {
758
- if (
759
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
760
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
761
- msgData.mediaSourceId
762
- ) {
763
- return;
764
- }
765
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
766
- mediaSource.stopEndOfStream();
767
- }
768
- break;
769
-
770
- case CoreMessageType.DisposeMediaSource:
771
- {
772
- if (
773
- this._currentContentInfo?.mediaSourceInfo?.type !== "main" ||
774
- this._currentContentInfo.mediaSourceInfo.mediaSource.id !==
775
- msgData.mediaSourceId
776
- ) {
777
- return;
778
- }
779
- const { mediaSource } = this._currentContentInfo.mediaSourceInfo;
780
- mediaSource.dispose();
781
- }
782
- break;
782
+ },
783
+ { emitCurrentValue: true, clearSignal: cancelSignal },
784
+ );
785
+ })
786
+ .catch((err) => {
787
+ if (cancelSignal.isCancelled()) {
788
+ return; // Current loading cancelled, no need to trigger the error
789
+ }
790
+ this._onFatalError(err);
791
+ });
783
792
 
784
- case CoreMessageType.NeedsBufferFlush: {
785
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
786
- return;
787
- }
793
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
794
+ const self = this;
795
+ StreamOrchestrator(
796
+ { manifest, initialPeriod },
797
+ coreObserver,
798
+ representationEstimator,
799
+ segmentSinksStore,
800
+ segmentQueueCreator,
801
+ bufferOptions,
802
+ handleStreamOrchestratorCallbacks(),
803
+ cancelSignal,
804
+ );
805
+
806
+ /**
807
+ * Returns Object handling the callbacks from a `StreamOrchestrator`, which
808
+ * are basically how it communicates about events.
809
+ * @returns {Object}
810
+ */
811
+ function handleStreamOrchestratorCallbacks(): IStreamOrchestratorCallbacks {
812
+ return {
813
+ needsBufferFlush: (payload?: INeedsBufferFlushPayload) => {
814
+ let wantedSeekingTime: number;
788
815
  const lastObservation = playbackObserver.getReference().getValue();
789
816
  const currentTime = lastObservation.position.isAwaitingFuturePosition()
790
817
  ? lastObservation.position.getWanted()
791
818
  : mediaElement.currentTime;
792
- const relativeResumingPosition = msgData.value?.relativeResumingPosition ?? 0;
793
- const canBeApproximateSeek = Boolean(
794
- msgData.value?.relativePosHasBeenDefaulted,
795
- );
796
- let wantedSeekingTime: number;
819
+ const relativeResumingPosition = payload?.relativeResumingPosition ?? 0;
820
+ const canBeApproximateSeek = Boolean(payload?.relativePosHasBeenDefaulted);
797
821
 
798
822
  if (relativeResumingPosition === 0 && canBeApproximateSeek) {
799
823
  // in case relativeResumingPosition is 0, we still perform
@@ -803,886 +827,251 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
803
827
  wantedSeekingTime = currentTime + relativeResumingPosition;
804
828
  }
805
829
  playbackObserver.setCurrentTime(wantedSeekingTime);
806
- break;
807
- }
808
830
 
809
- case CoreMessageType.ActivePeriodChanged: {
810
- if (
811
- this._currentContentInfo?.contentId !== msgData.contentId ||
812
- this._currentContentInfo.manifest === null
813
- ) {
814
- return;
815
- }
816
- const period = arrayFind(
817
- this._currentContentInfo.manifest.periods,
818
- (p) => p.id === msgData.value.periodId,
831
+ // Seek again once data begins to be buffered.
832
+ // This is sadly necessary on some browsers to avoid decoding
833
+ // issues after a flush.
834
+ //
835
+ // NOTE: there's in theory a potential race condition in the following
836
+ // logic as the callback could be called when media data is still
837
+ // being removed by the browser - which is an asynchronous process.
838
+ // The following condition checking for buffered data could thus lead
839
+ // to a false positive where we're actually checking previous data.
840
+ // For now, such scenario is avoided by setting the
841
+ // `includeLastObservation` option to `false` and calling
842
+ // `needsBufferFlush` once MSE media removal operations have been
843
+ // explicitely validated by the browser, but that's a complex and easy
844
+ // to break system.
845
+ playbackObserver.listen(
846
+ (obs, stopListening) => {
847
+ if (
848
+ // Data is buffered around the current position
849
+ obs.currentRange !== null ||
850
+ // Or, for whatever reason, we have no buffer but we're already advancing
851
+ obs.position.getPolled() > wantedSeekingTime + 0.1
852
+ ) {
853
+ stopListening();
854
+ playbackObserver.setCurrentTime(obs.position.getWanted() + 0.001);
855
+ }
856
+ },
857
+ { includeLastObservation: false, clearSignal: cancelSignal },
819
858
  );
820
- if (period !== undefined) {
821
- this.trigger("activePeriodChanged", { period });
822
- }
823
- break;
824
- }
859
+ },
825
860
 
826
- case CoreMessageType.AdaptationChanged: {
827
- if (
828
- this._currentContentInfo?.contentId !== msgData.contentId ||
829
- this._currentContentInfo.manifest === null
830
- ) {
831
- return;
832
- }
833
- const period = arrayFind(
834
- this._currentContentInfo.manifest.periods,
835
- (p) => p.id === msgData.value.periodId,
836
- );
837
- if (period === undefined) {
838
- return;
839
- }
840
- if (msgData.value.adaptationId === null) {
841
- this.trigger("adaptationChange", {
842
- period,
843
- adaptation: null,
844
- type: msgData.value.type,
845
- });
846
- return;
847
- }
848
- const adaptations = period.adaptations[msgData.value.type] ?? [];
849
- const adaptation = arrayFind(
850
- adaptations,
851
- (a) => a.id === msgData.value.adaptationId,
852
- );
853
- if (adaptation !== undefined) {
854
- this.trigger("adaptationChange", {
855
- period,
856
- adaptation,
857
- type: msgData.value.type,
858
- });
861
+ streamStatusUpdate(value) {
862
+ // Announce discontinuities if found
863
+ const { period, bufferType, imminentDiscontinuity, position } = value;
864
+ rebufferingController.updateDiscontinuityInfo({
865
+ period,
866
+ bufferType,
867
+ discontinuity: imminentDiscontinuity,
868
+ position,
869
+ });
870
+ if (cancelSignal.isCancelled()) {
871
+ return; // Previous call has stopped streams due to a side-effect
859
872
  }
860
- break;
861
- }
862
873
 
863
- case CoreMessageType.RepresentationChanged: {
874
+ // If the status for the last Period indicates that segments are all loaded
875
+ // or on the contrary that the loading resumed, announce it to the
876
+ // ContentTimeBoundariesObserver.
864
877
  if (
865
- this._currentContentInfo?.contentId !== msgData.contentId ||
866
- this._currentContentInfo.manifest === null
878
+ manifest.isLastPeriodKnown &&
879
+ value.period.id === manifest.periods[manifest.periods.length - 1].id
867
880
  ) {
868
- return;
869
- }
870
- const period = arrayFind(
871
- this._currentContentInfo.manifest.periods,
872
- (p) => p.id === msgData.value.periodId,
873
- );
874
- if (period === undefined) {
875
- return;
876
- }
877
- if (msgData.value.representationId === null) {
878
- this.trigger("representationChange", {
879
- period,
880
- type: msgData.value.type,
881
- representation: null,
882
- });
883
- return;
884
- }
885
- const adaptations = period.adaptations[msgData.value.type] ?? [];
886
- const adaptation = arrayFind(
887
- adaptations,
888
- (a) => a.id === msgData.value.adaptationId,
889
- );
890
- if (adaptation === undefined) {
891
- return;
892
- }
893
- const representation = arrayFind(
894
- adaptation.representations,
895
- (r) => r.id === msgData.value.representationId,
896
- );
897
- if (representation !== undefined) {
898
- this.trigger("representationChange", {
899
- period,
900
- type: msgData.value.type,
901
- representation,
902
- });
881
+ const hasFinishedLoadingLastPeriod =
882
+ value.hasFinishedLoading || value.isEmptyStream;
883
+ if (hasFinishedLoadingLastPeriod) {
884
+ contentTimeBoundariesObserver.onLastSegmentFinishedLoading(
885
+ value.bufferType,
886
+ );
887
+ } else {
888
+ contentTimeBoundariesObserver.onLastSegmentLoadingResume(value.bufferType);
889
+ }
903
890
  }
904
- break;
905
- }
891
+ },
906
892
 
907
- case CoreMessageType.EncryptionDataEncountered:
908
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
909
- return;
910
- }
911
- lastContentProtection.setValue(msgData.value);
912
- break;
893
+ needsManifestRefresh: () =>
894
+ self._manifestFetcher.scheduleManualRefresh({
895
+ enablePartialRefresh: true,
896
+ canUseUnsafeMode: true,
897
+ }),
898
+
899
+ manifestMightBeOufOfSync: () => {
900
+ const { OUT_OF_SYNC_MANIFEST_REFRESH_DELAY } = config.getCurrent();
901
+ self._manifestFetcher.scheduleManualRefresh({
902
+ enablePartialRefresh: false,
903
+ canUseUnsafeMode: false,
904
+ delay: OUT_OF_SYNC_MANIFEST_REFRESH_DELAY,
905
+ });
906
+ },
913
907
 
914
- case CoreMessageType.ManifestReady: {
915
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
916
- return;
917
- }
918
- const manifest = msgData.value.manifest;
919
- this._currentContentInfo.manifest = manifest;
920
- this._updateCodecSupport(manifest, mediaElement);
921
- this._startPlaybackIfReady(playbackStartParams);
922
- break;
923
- }
908
+ lockedStream: (value) =>
909
+ rebufferingController.onLockedStream(value.bufferType, value.period),
924
910
 
925
- case CoreMessageType.ManifestUpdate: {
926
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
927
- return;
911
+ adaptationChange: (value) => {
912
+ self.trigger("adaptationChange", value);
913
+ if (cancelSignal.isCancelled()) {
914
+ return; // Previous call has stopped streams due to a side-effect
928
915
  }
929
- const manifest = this._currentContentInfo?.manifest;
930
- if (isNullOrUndefined(manifest)) {
931
- log.error("Init", "Manifest update but no Manifest loaded");
932
- return;
916
+ contentTimeBoundariesObserver.onAdaptationChange(
917
+ value.type,
918
+ value.period,
919
+ value.adaptation,
920
+ );
921
+ },
922
+
923
+ representationChange: (value) => {
924
+ self.trigger("representationChange", value);
925
+ if (cancelSignal.isCancelled()) {
926
+ return; // Previous call has stopped streams due to a side-effect
933
927
  }
928
+ contentTimeBoundariesObserver.onRepresentationChange(value.type, value.period);
929
+ },
934
930
 
935
- replicateUpdatesOnManifestMetadata(
936
- manifest,
937
- msgData.value.manifest,
938
- msgData.value.updates,
939
- );
940
- this._currentContentInfo?.streamEventsEmitter?.onManifestUpdate(manifest);
931
+ inbandEvent: (value) => self.trigger("inbandEvents", value),
941
932
 
942
- this._updateCodecSupport(manifest, mediaElement);
943
- this.trigger("manifestUpdate", msgData.value.updates);
944
- break;
945
- }
933
+ warning: (value) => self.trigger("warning", value),
946
934
 
947
- case CoreMessageType.UpdatePlaybackRate:
948
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
949
- return;
950
- }
951
- playbackObserver.setPlaybackRate(msgData.value);
952
- break;
935
+ periodStreamReady: (value) => self.trigger("periodStreamReady", value),
953
936
 
954
- case CoreMessageType.BitrateEstimateChange:
955
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
956
- return;
937
+ periodStreamCleared: (value) => {
938
+ contentTimeBoundariesObserver.onPeriodCleared(value.type, value.period);
939
+ if (cancelSignal.isCancelled()) {
940
+ return; // Previous call has stopped streams due to a side-effect
957
941
  }
958
- this.trigger("bitrateEstimateChange", {
959
- type: msgData.value.bufferType,
960
- bitrate: msgData.value.bitrate,
942
+ self.trigger("periodStreamCleared", {
943
+ type: value.type,
944
+ periodId: value.period.id,
961
945
  });
962
- break;
946
+ },
963
947
 
964
- case CoreMessageType.InbandEvent:
965
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
966
- return;
948
+ bitrateEstimateChange: (value) => {
949
+ self._cmcdDataBuilder?.updateThroughput(value.type, value.bitrate);
950
+ self.trigger("bitrateEstimateChange", value);
951
+ },
952
+
953
+ needsMediaSourceReload: (payload) => {
954
+ reloadMediaSource(
955
+ payload.timeOffset,
956
+ payload.minimumPosition,
957
+ payload.maximumPosition,
958
+ );
959
+ },
960
+
961
+ needsDecipherabilityFlush() {
962
+ const keySystem = getKeySystemConfiguration(mediaElement);
963
+ if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem?.[0])) {
964
+ const lastObservation = coreObserver.getReference().getValue();
965
+ const position = lastObservation.position.isAwaitingFuturePosition()
966
+ ? lastObservation.position.getWanted()
967
+ : (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
968
+ const isPaused =
969
+ lastObservation.paused.pending ??
970
+ coreObserver.getIsPaused() ??
971
+ lastObservation.paused.last;
972
+ onReloadOrder({ position, autoPlay: !isPaused });
973
+ } else {
974
+ const lastObservation = coreObserver.getReference().getValue();
975
+ const position = lastObservation.position.isAwaitingFuturePosition()
976
+ ? lastObservation.position.getWanted()
977
+ : (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
978
+ // simple seek close to the current position
979
+ // to flush the buffers
980
+ if (position + 0.001 < lastObservation.duration) {
981
+ playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001);
982
+ } else {
983
+ playbackObserver.setCurrentTime(position);
984
+ }
967
985
  }
968
- this.trigger("inbandEvents", msgData.value);
969
- break;
986
+ },
970
987
 
971
- case CoreMessageType.LockedStream: {
972
- if (
973
- this._currentContentInfo?.contentId !== msgData.contentId ||
974
- this._currentContentInfo.manifest === null
975
- ) {
988
+ encryptionDataEncountered: (value) => {
989
+ if (self._decryptionCapabilities.status === "disabled") {
990
+ self._onFatalError(self._decryptionCapabilities.value);
991
+ return;
992
+ } else if (self._decryptionCapabilities.status === "uninitialized") {
993
+ // Should never happen
994
+ log.error(
995
+ "Init",
996
+ "received encryption data without known decryption capabilities",
997
+ );
976
998
  return;
977
999
  }
978
- const period = arrayFind(
979
- this._currentContentInfo.manifest.periods,
980
- (p) => p.id === msgData.value.periodId,
981
- );
982
- if (period === undefined) {
983
- return;
984
- }
985
- this._currentContentInfo.rebufferingController?.onLockedStream(
986
- msgData.value.bufferType,
987
- period,
988
- );
989
- break;
990
- }
991
-
992
- case CoreMessageType.PeriodStreamReady: {
993
- if (
994
- this._currentContentInfo?.contentId !== msgData.contentId ||
995
- this._currentContentInfo.manifest === null
996
- ) {
997
- return;
998
- }
999
- const period = arrayFind(
1000
- this._currentContentInfo.manifest.periods,
1001
- (p) => p.id === msgData.value.periodId,
1002
- );
1003
- if (period === undefined) {
1004
- return;
1005
- }
1006
- const ref = new SharedReference<IAdaptationChoice | null | undefined>(
1007
- undefined,
1008
- );
1009
- ref.onUpdate(
1010
- (adapChoice) => {
1011
- if (this._currentContentInfo === null) {
1012
- ref.finish();
1013
- return;
1014
- }
1015
- if (!isNullOrUndefined(adapChoice)) {
1016
- adapChoice.representations.onUpdate(
1017
- (repChoice, stopListening) => {
1018
- if (this._currentContentInfo === null) {
1019
- stopListening();
1020
- return;
1021
- }
1022
- this._settings.coreInterface.sendMessage({
1023
- type: MainThreadMessageType.RepresentationUpdate,
1024
- contentId: this._currentContentInfo.contentId,
1025
- value: {
1026
- periodId: msgData.value.periodId,
1027
- adaptationId: adapChoice.adaptationId,
1028
- bufferType: msgData.value.bufferType,
1029
- choice: repChoice,
1030
- },
1031
- });
1032
- },
1033
- { clearSignal: this._initCanceller.signal },
1034
- );
1035
- }
1036
- this._settings.coreInterface.sendMessage({
1037
- type: MainThreadMessageType.TrackUpdate,
1038
- contentId: this._currentContentInfo.contentId,
1039
- value: {
1040
- periodId: msgData.value.periodId,
1041
- bufferType: msgData.value.bufferType,
1042
- choice: isNullOrUndefined(adapChoice)
1043
- ? adapChoice
1044
- : {
1045
- adaptationId: adapChoice.adaptationId,
1046
- switchingMode: adapChoice.switchingMode,
1047
- initialRepresentations: adapChoice.representations.getValue(),
1048
- relativeResumingPosition: adapChoice.relativeResumingPosition,
1049
- },
1050
- },
1051
- });
1052
- },
1053
- { clearSignal: this._initCanceller.signal },
1054
- );
1055
- this.trigger("periodStreamReady", {
1056
- period,
1057
- type: msgData.value.bufferType,
1058
- adaptationRef: ref,
1059
- });
1060
- break;
1061
- }
1062
-
1063
- case CoreMessageType.PeriodStreamCleared: {
1064
- if (
1065
- this._currentContentInfo?.contentId !== msgData.contentId ||
1066
- this._currentContentInfo.manifest === null
1067
- ) {
1068
- return;
1069
- }
1070
- this.trigger("periodStreamCleared", {
1071
- periodId: msgData.value.periodId,
1072
- type: msgData.value.bufferType,
1073
- });
1074
- break;
1075
- }
1076
-
1077
- case CoreMessageType.DiscontinuityUpdate: {
1078
- if (
1079
- this._currentContentInfo?.contentId !== msgData.contentId ||
1080
- this._currentContentInfo.manifest === null
1081
- ) {
1082
- return;
1083
- }
1084
- const period = arrayFind(
1085
- this._currentContentInfo.manifest.periods,
1086
- (p) => p.id === msgData.value.periodId,
1087
- );
1088
- if (period === undefined) {
1089
- log.warn("Init", "Discontinuity's Period not found", {
1090
- periodId: msgData.value.periodId,
1091
- });
1092
- return;
1093
- }
1094
- this._currentContentInfo.rebufferingController?.updateDiscontinuityInfo({
1095
- period,
1096
- bufferType: msgData.value.bufferType,
1097
- discontinuity: msgData.value.discontinuity,
1098
- position: msgData.value.position,
1099
- });
1100
- break;
1101
- }
1102
-
1103
- case CoreMessageType.PushTextData: {
1104
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1105
- return;
1106
- }
1107
- if (textDisplayer === null) {
1108
- log.warn("text", "Received AddTextData message but no text displayer exists");
1109
- } else {
1110
- try {
1111
- const ranges = textDisplayer.pushTextData(msgData.value);
1112
- this._settings.coreInterface.sendMessage({
1113
- type: MainThreadMessageType.PushTextDataSuccess,
1114
- contentId: msgData.contentId,
1115
- value: { ranges },
1116
- });
1117
- } catch (err) {
1118
- const message = err instanceof Error ? err.message : "Unknown error";
1119
- this._settings.coreInterface.sendMessage({
1120
- type: MainThreadMessageType.PushTextDataError,
1121
- contentId: msgData.contentId,
1122
- value: { message },
1123
- });
1124
- }
1125
- }
1126
- break;
1127
- }
1128
-
1129
- case CoreMessageType.RemoveTextData: {
1130
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1131
- return;
1132
- }
1133
- if (textDisplayer === null) {
1134
- log.warn(
1135
- "text",
1136
- "Received RemoveTextData message but no text displayer exists",
1137
- );
1138
- } else {
1139
- try {
1140
- const ranges = textDisplayer.removeBuffer(
1141
- msgData.value.start,
1142
- msgData.value.end,
1143
- );
1144
- this._settings.coreInterface.sendMessage({
1145
- type: MainThreadMessageType.RemoveTextDataSuccess,
1146
- contentId: msgData.contentId,
1147
- value: { ranges },
1148
- });
1149
- } catch (err) {
1150
- const message = err instanceof Error ? err.message : "Unknown error";
1151
- this._settings.coreInterface.sendMessage({
1152
- type: MainThreadMessageType.RemoveTextDataError,
1153
- contentId: msgData.contentId,
1154
- value: { message },
1155
- });
1156
- }
1157
- }
1158
- break;
1159
- }
1160
-
1161
- case CoreMessageType.ResetTextDisplayer: {
1162
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1163
- return;
1164
- }
1165
- if (textDisplayer === null) {
1166
- log.warn(
1167
- "text",
1168
- "Received ResetTextDisplayer message but no text displayer exists",
1169
- );
1170
- } else {
1171
- textDisplayer.reset();
1172
- }
1173
- break;
1174
- }
1175
-
1176
- case CoreMessageType.StopTextDisplayer: {
1177
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1178
- return;
1179
- }
1180
- if (textDisplayer === null) {
1181
- log.warn(
1182
- "text",
1183
- "Received StopTextDisplayer message but no text displayer exists",
1184
- );
1185
- } else {
1186
- textDisplayer.stop();
1187
- }
1188
- break;
1189
- }
1190
-
1191
- case CoreMessageType.ReloadingMediaSource:
1192
- {
1193
- if (
1194
- this._currentContentInfo === null ||
1195
- this._currentContentInfo.mediaSourceInfo === null
1196
- ) {
1197
- return;
1198
- }
1199
- const mediaSourceId =
1200
- this._currentContentInfo.mediaSourceInfo.type === "main"
1201
- ? this._currentContentInfo.mediaSourceInfo.mediaSource.id
1202
- : this._currentContentInfo.mediaSourceInfo.mediaSourceId;
1203
-
1204
- if (mediaSourceId !== msgData.mediaSourceId) {
1205
- return;
1206
- }
1207
- reloadMediaSource(
1208
- msgData.value.timeOffset,
1209
- msgData.value.minimumPosition,
1210
- msgData.value.maximumPosition,
1211
- );
1212
- }
1213
- break;
1214
-
1215
- case CoreMessageType.NeedsDecipherabilityFlush:
1216
- {
1217
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1218
- return;
1219
- }
1220
-
1221
- const keySystem = getKeySystemConfiguration(mediaElement);
1222
- if (shouldReloadMediaSourceOnDecipherabilityUpdate(keySystem?.[0])) {
1223
- notifyAndStartMediaSourceReload(0, undefined, undefined);
1224
- } else {
1225
- const lastObservation = playbackObserver.getReference().getValue();
1226
-
1227
- const currentPosition = lastObservation.position.getWanted();
1228
-
1229
- // simple seek close to the current position
1230
- // to flush the buffers
1231
- if (currentPosition + 0.001 < lastObservation.duration) {
1232
- playbackObserver.setCurrentTime(mediaElement.currentTime + 0.001);
1233
- } else {
1234
- playbackObserver.setCurrentTime(currentPosition);
1235
- }
1236
- }
1237
- }
1238
- break;
1239
-
1240
- case CoreMessageType.SegmentSinkStoreUpdate: {
1241
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1242
- return;
1243
- }
1244
- const sinkObj = this._awaitingRequests.pendingSinkMetrics.get(
1245
- msgData.value.requestId,
1246
- );
1247
- if (sinkObj !== undefined) {
1248
- sinkObj.resolve(msgData.value.segmentSinkMetrics);
1249
- } else {
1250
- log.error("Init", "Failed to send segment sink store update");
1251
- }
1252
- break;
1253
- }
1254
-
1255
- case CoreMessageType.InitSuccess:
1256
- case CoreMessageType.InitError:
1257
- // Should already be handled by the API
1258
- break;
1259
-
1260
- case CoreMessageType.LogMessage:
1261
- // Already handled by prepare's handler
1262
- break;
1263
- case CoreMessageType.ThumbnailDataResponse: {
1264
- if (this._currentContentInfo?.contentId !== msgData.contentId) {
1265
- return;
1266
- }
1267
- const tObj = this._awaitingRequests.pendingThumbnailFetching.get(
1268
- msgData.value.requestId,
1269
- );
1270
- if (tObj !== undefined) {
1271
- if (msgData.value.status === "error") {
1272
- tObj.reject(formatCoreError(msgData.value.error));
1273
- } else {
1274
- tObj.resolve(msgData.value.data);
1000
+ for (const protectionData of value) {
1001
+ self._decryptionCapabilities.value.onInitializationData(protectionData);
1002
+ if (cancelSignal.isCancelled()) {
1003
+ return; // Previous call has stopped streams due to a side-effect
1275
1004
  }
1276
- } else {
1277
- log.error("Init", "Failed to send segment sink store update");
1278
- }
1279
- break;
1280
- }
1281
- default:
1282
- assertUnreachable(msgData);
1283
- }
1284
- };
1285
-
1286
- log.debug("Init", "addEventListener for core message");
1287
- if (this._queuedCoreMessages !== null) {
1288
- const bufferedMessages = this._queuedCoreMessages.slice();
1289
- log.debug("Init", "Processing buffered messages", {
1290
- ammount: bufferedMessages.length,
1291
- });
1292
- for (const message of bufferedMessages) {
1293
- onmessage(message);
1294
- }
1295
- this._queuedCoreMessages = null;
1296
- }
1297
- this._settings.coreInterface.addMessageListener(onmessage);
1298
- this._initCanceller.signal.register(() => {
1299
- log.debug("Init", "removeEventListener for core message");
1300
- this._settings.coreInterface.removeMessageListener(onmessage);
1301
- });
1302
- }
1303
-
1304
- public dispose(): void {
1305
- this._initCanceller.cancel();
1306
- if (this._currentContentInfo !== null) {
1307
- if (this._currentContentInfo.mediaSourceInfo?.type === "main") {
1308
- this._currentContentInfo.mediaSourceInfo.mediaSource.dispose();
1309
- }
1310
- this._currentContentInfo = null;
1311
- }
1312
- }
1313
-
1314
- private _onFatalError(err: unknown) {
1315
- if (this._initCanceller.isUsed()) {
1316
- return;
1317
- }
1318
- this._initCanceller.cancel();
1319
- this.trigger("error", err);
1320
- }
1321
-
1322
- private _initializeContentDecryption(
1323
- mediaElement: IMediaElement,
1324
- lastContentProtection: IReadOnlySharedReference<null | IContentProtection>,
1325
- mediaSourceStatus: SharedReference<MediaSourceInitializationStatus>,
1326
- reloadMediaSource: () => void,
1327
- cancelSignal: CancellationSignal,
1328
- ): {
1329
- statusRef: IReadOnlySharedReference<IDrmInitializationStatus>;
1330
- contentDecryptor: IContentDecryptor | null;
1331
- } {
1332
- const { keySystems } = this._settings;
1333
-
1334
- // TODO private?
1335
- const createEmeDisabledReference = (errMsg: string) => {
1336
- mediaSourceStatus.setValue(MediaSourceInitializationStatus.AttachNow);
1337
- lastContentProtection.onUpdate(
1338
- (data, stopListening) => {
1339
- if (data === null) {
1340
- // initial value
1341
- return;
1342
1005
  }
1343
- stopListening();
1344
- const err = new EncryptedMediaError("MEDIA_IS_ENCRYPTED_ERROR", errMsg, {
1345
- keyStatuses: undefined,
1346
- keySystemConfiguration: undefined,
1347
- keySystem: undefined,
1348
- });
1349
- this._onFatalError(err);
1350
- },
1351
- { clearSignal: cancelSignal },
1352
- );
1353
- const ref = new SharedReference({
1354
- initializationState: {
1355
- type: "initialized" as const,
1356
- value: null,
1357
1006
  },
1358
- contentDecryptor: null,
1359
- drmSystemId: undefined,
1360
- });
1361
- ref.finish(); // We know that no new value will be triggered
1362
- return { statusRef: ref, contentDecryptor: null };
1363
- };
1364
-
1365
- if (keySystems.length === 0) {
1366
- return createEmeDisabledReference("No `keySystems` option given.");
1367
- } else if (features.decrypt === null) {
1368
- return createEmeDisabledReference("EME feature not activated.");
1369
- }
1370
-
1371
- const emeApi = mediaElement.FORCED_EME_API ?? getEmeApiImplementation("auto");
1372
- if (emeApi === null) {
1373
- return createEmeDisabledReference("EME API not available on the current page.");
1374
- }
1375
-
1376
- log.debug("Init", "Creating ContentDecryptor");
1377
-
1378
- const ContentDecryptor = features.decrypt;
1379
- const contentDecryptor = new ContentDecryptor(emeApi, mediaElement, keySystems);
1380
- const drmStatusRef = new SharedReference<IDrmInitializationStatus>(
1381
- {
1382
- initializationState: { type: "uninitialized", value: null },
1383
- drmSystemId: undefined,
1384
- },
1385
- cancelSignal,
1386
- );
1387
-
1388
- const updateCodecSupportOnStateChange = (state: ContentDecryptorState) => {
1389
- if (state > ContentDecryptorState.Initializing) {
1390
- const manifest = this._currentContentInfo?.manifest;
1391
- if (isNullOrUndefined(manifest)) {
1392
- return;
1393
- }
1394
- this._updateCodecSupport(manifest, mediaElement);
1395
- contentDecryptor.removeEventListener(
1396
- "stateChange",
1397
- updateCodecSupportOnStateChange,
1398
- );
1399
- }
1400
- };
1401
- contentDecryptor.addEventListener("stateChange", updateCodecSupportOnStateChange);
1402
-
1403
- contentDecryptor.addEventListener("keyIdsCompatibilityUpdate", (updates) => {
1404
- if (
1405
- this._currentContentInfo === null ||
1406
- this._currentContentInfo.manifest === null
1407
- ) {
1408
- return;
1409
- }
1410
- const manUpdates = updateDecipherabilityFromKeyIds(
1411
- this._currentContentInfo.manifest,
1412
- updates,
1413
- );
1414
- if (
1415
- mayMediaElementFailOnUndecipherableData() &&
1416
- manUpdates.some((e) => e.representation.decipherable !== true)
1417
- ) {
1418
- reloadMediaSource();
1419
- } else {
1420
- this._settings.coreInterface.sendMessage({
1421
- type: MainThreadMessageType.DecipherabilityStatusUpdate,
1422
- contentId: this._currentContentInfo.contentId,
1423
- value: manUpdates.map((s) => ({
1424
- representationUniqueId: s.representation.uniqueId,
1425
- decipherable: s.representation.decipherable,
1426
- })),
1427
- });
1428
- }
1429
- this.trigger("decipherabilityUpdate", manUpdates);
1430
- });
1431
- contentDecryptor.addEventListener("blackListProtectionData", (protData) => {
1432
- if (
1433
- this._currentContentInfo === null ||
1434
- this._currentContentInfo.manifest === null
1435
- ) {
1436
- return;
1437
- }
1438
- const manUpdates = updateDecipherabilityFromProtectionData(
1439
- this._currentContentInfo.manifest,
1440
- protData,
1441
- );
1442
- if (
1443
- mayMediaElementFailOnUndecipherableData() &&
1444
- manUpdates.some((e) => e.representation.decipherable !== true)
1445
- ) {
1446
- reloadMediaSource();
1447
- } else {
1448
- this._settings.coreInterface.sendMessage({
1449
- type: MainThreadMessageType.DecipherabilityStatusUpdate,
1450
- contentId: this._currentContentInfo.contentId,
1451
- value: manUpdates.map((s) => ({
1452
- representationUniqueId: s.representation.uniqueId,
1453
- decipherable: s.representation.decipherable,
1454
- })),
1455
- });
1456
- }
1457
- this.trigger("decipherabilityUpdate", manUpdates);
1458
- });
1459
- contentDecryptor.addEventListener("stateChange", (state) => {
1460
- if (state === ContentDecryptorState.WaitingForAttachment) {
1461
- mediaSourceStatus.onUpdate(
1462
- (currStatus, stopListening) => {
1463
- if (currStatus === MediaSourceInitializationStatus.Nothing) {
1464
- mediaSourceStatus.setValue(MediaSourceInitializationStatus.AttachNow);
1465
- } else if (currStatus === MediaSourceInitializationStatus.Attached) {
1466
- stopListening();
1467
- if (state === ContentDecryptorState.WaitingForAttachment) {
1468
- contentDecryptor.attach();
1469
- }
1470
- }
1471
- },
1472
- { clearSignal: cancelSignal, emitCurrentValue: true },
1473
- );
1474
- } else if (state === ContentDecryptorState.ReadyForContent) {
1475
- drmStatusRef.setValue({
1476
- initializationState: { type: "initialized", value: null },
1477
- drmSystemId: contentDecryptor.systemId,
1478
- });
1479
- contentDecryptor.removeEventListener("stateChange");
1480
- }
1481
- });
1482
1007
 
1483
- contentDecryptor.addEventListener("error", (error) => {
1484
- this._onFatalError(error);
1485
- });
1486
-
1487
- contentDecryptor.addEventListener("warning", (error) => {
1488
- this.trigger("warning", error);
1489
- });
1490
-
1491
- lastContentProtection.onUpdate(
1492
- (data) => {
1493
- if (data === null) {
1494
- return;
1495
- }
1496
- contentDecryptor.onInitializationData(data);
1497
- },
1498
- { clearSignal: cancelSignal },
1499
- );
1500
-
1501
- cancelSignal.register(() => {
1502
- contentDecryptor.dispose();
1503
- });
1504
-
1505
- return { statusRef: drmStatusRef, contentDecryptor };
1506
- }
1507
- /**
1508
- * Retrieves all unknown codecs from the current manifest, checks these unknown codecs
1509
- * to determine if they are supported, updates the manifest with the support
1510
- * status of these codecs, and forwards the list of supported codecs to core.
1511
- * @param manifest
1512
- */
1513
- private _updateCodecSupport(manifest: IManifestMetadata, mediaElement: IMediaElement) {
1514
- try {
1515
- const updatedCodecs = updateManifestCodecSupport(
1516
- manifest,
1517
- this._currentContentInfo?.contentDecryptor ?? null,
1518
- mediaElement,
1519
- this._currentContentInfo?.useMseInWorker ?? false,
1520
- );
1521
- if (updatedCodecs.length > 0) {
1522
- this._settings.coreInterface.sendMessage({
1523
- type: MainThreadMessageType.CodecSupportUpdate,
1524
- value: updatedCodecs,
1525
- });
1526
- // TODO what if one day the core updates codec support by itself?
1527
- // We wouldn't know...
1528
- this.trigger("codecSupportUpdate", null);
1529
- }
1530
- } catch (err) {
1531
- this._onFatalError(err);
1008
+ error: (err) => self._onFatalError(err),
1009
+ };
1532
1010
  }
1533
- }
1534
1011
 
1535
- private _hasTextBufferFeature(): boolean {
1536
- return (
1537
- (this._settings.textTrackOptions.textTrackMode === "html" &&
1538
- features.htmlTextDisplayer !== null) ||
1539
- features.nativeTextDisplayer !== null
1540
- );
1541
- }
1542
-
1543
- private _reload(
1544
- mediaElement: IMediaElement,
1545
- textDisplayer: ITextDisplayer | null,
1546
- playbackObserver: IMediaElementPlaybackObserver,
1547
- mediaSourceStatus: SharedReference<MediaSourceInitializationStatus>,
1548
- position: number,
1549
- autoPlay: boolean,
1550
- ) {
1551
- this._currentMediaSourceCanceller.cancel();
1552
- this._currentMediaSourceCanceller = new TaskCanceller();
1553
- this._currentMediaSourceCanceller.linkToSignal(this._initCanceller.signal);
1554
- mediaSourceStatus.setValue(MediaSourceInitializationStatus.AttachNow);
1555
- this.trigger("reloadingMediaSource", { position, autoPlay });
1556
-
1557
- mediaSourceStatus.onUpdate(
1558
- (status, stopListeningMSStatusUpdates) => {
1559
- if (status !== MediaSourceInitializationStatus.Attached) {
1560
- return;
1561
- }
1562
- stopListeningMSStatusUpdates();
1563
- const corePlaybackObserver = this._setUpModulesOnNewMediaSource(
1564
- {
1565
- initialTime: position,
1566
- autoPlay,
1567
- mediaElement,
1568
- textDisplayer,
1569
- playbackObserver,
1570
- },
1571
- this._currentMediaSourceCanceller.signal,
1572
- );
1573
-
1574
- if (
1575
- !this._currentMediaSourceCanceller.isUsed() &&
1576
- corePlaybackObserver !== null &&
1577
- this._currentContentInfo !== null
1578
- ) {
1579
- const contentId = this._currentContentInfo.contentId;
1580
- corePlaybackObserver.listen(
1581
- (obs) => {
1582
- this._settings.coreInterface.sendMessage({
1583
- type: MainThreadMessageType.PlaybackObservation,
1584
- contentId,
1585
- value: objectAssign(obs, {
1586
- position: obs.position.serialize(),
1587
- }),
1588
- });
1589
- },
1590
- {
1591
- includeLastObservation: true,
1592
- clearSignal: this._currentMediaSourceCanceller.signal,
1593
- },
1594
- );
1595
- }
1596
- },
1597
- {
1598
- clearSignal: this._currentMediaSourceCanceller.signal,
1599
- emitCurrentValue: true,
1600
- },
1601
- );
1602
- }
1603
-
1604
- /**
1605
- * Start-up modules and mechanisms (initial seek, auto-play etc.) needed each
1606
- * time a content is loaded AND re-loaded on a `HTMLMediaElement`, when the
1607
- * manifest is known.
1608
- *
1609
- * Note that this does not include reacting to incoming core messages nor
1610
- * sending them, those actions have to be handled separately.
1611
- *
1612
- * @param {Object} parameters
1613
- * @param {Object} cancelSignal
1614
- * @returns {Object|null} - Playback Observer created for this content. `null`
1615
- * only if playback initialization failed (most likely because it has been
1616
- * cancelled).
1617
- */
1618
- private _setUpModulesOnNewMediaSource(
1619
- parameters: {
1620
- initialTime: number;
1621
- autoPlay: boolean;
1622
- mediaElement: IMediaElement;
1623
- textDisplayer: ITextDisplayer | null;
1624
- playbackObserver: IMediaElementPlaybackObserver;
1625
- },
1626
- cancelSignal: CancellationSignal,
1627
- ): IReadOnlyPlaybackObserver<ICorePlaybackObservation> | null {
1628
- if (cancelSignal.isCancelled()) {
1629
- return null;
1630
- }
1631
- if (this._currentContentInfo === null) {
1632
- log.error("Init", "Setting up modules without a contentId");
1633
- return null;
1634
- }
1635
- if (this._currentContentInfo.manifest === null) {
1636
- log.error("Init", "Setting up modules without a loaded Manifest");
1637
- return null;
1638
- }
1639
-
1640
- const { manifest, mediaSourceInfo } = this._currentContentInfo;
1641
- const { speed } = this._settings;
1642
- const { initialTime, autoPlay, mediaElement, textDisplayer, playbackObserver } =
1643
- parameters;
1644
- this._currentContentInfo.initialTime = initialTime;
1645
- this._currentContentInfo.autoPlay = autoPlay;
1646
-
1647
- const { autoPlayResult, initialPlayPerformed } = performInitialSeekAndPlay(
1648
- {
1649
- mediaElement,
1650
- playbackObserver,
1651
- startTime: initialTime,
1652
- mustAutoPlay: autoPlay,
1653
- onWarning: (err) => this.trigger("warning", err),
1654
- isDirectfile: false,
1655
- },
1656
- cancelSignal,
1657
- );
1658
- this._currentContentInfo.initialPlayPerformed = initialPlayPerformed;
1659
- const corePlaybackObserver = createCorePlaybackObserver(
1660
- playbackObserver,
1661
- {
1662
- autoPlay,
1663
- initialPlayPerformed,
1664
- manifest,
1665
- mediaSource:
1666
- mediaSourceInfo?.type === "main" ? mediaSourceInfo.mediaSource : null,
1667
- speed,
1668
- textDisplayer,
1669
- },
1670
- cancelSignal,
1671
- );
1672
-
1673
- if (cancelSignal.isCancelled()) {
1674
- return null;
1012
+ /**
1013
+ * Callback allowing to reload the current content.
1014
+ * @param {number} deltaPosition - Position you want to seek to after
1015
+ * reloading, as a delta in seconds from the last polled playing position.
1016
+ * @param {number|undefined} minimumPosition - If set, minimum time bound
1017
+ * in seconds after `deltaPosition` has been applied.
1018
+ * @param {number|undefined} maximumPosition - If set, minimum time bound
1019
+ * in seconds after `deltaPosition` has been applied.
1020
+ */
1021
+ function reloadMediaSource(
1022
+ deltaPosition: number,
1023
+ minimumPosition: number | undefined,
1024
+ maximumPosition: number | undefined,
1025
+ ): void {
1026
+ const lastObservation = coreObserver.getReference().getValue();
1027
+ const currentPosition = lastObservation.position.isAwaitingFuturePosition()
1028
+ ? lastObservation.position.getWanted()
1029
+ : (coreObserver.getCurrentTime() ?? lastObservation.position.getPolled());
1030
+ const isPaused =
1031
+ lastObservation.paused.pending ??
1032
+ coreObserver.getIsPaused() ??
1033
+ lastObservation.paused.last;
1034
+ let position = currentPosition + deltaPosition;
1035
+ if (minimumPosition !== undefined) {
1036
+ position = Math.max(minimumPosition, position);
1037
+ }
1038
+ if (maximumPosition !== undefined) {
1039
+ position = Math.min(maximumPosition, position);
1040
+ }
1041
+ onReloadOrder({ position, autoPlay: !isPaused });
1675
1042
  }
1043
+ }
1676
1044
 
1677
- /**
1678
- * Class trying to avoid various stalling situations, emitting "stalled"
1679
- * events when it cannot, as well as "unstalled" events when it get out of one.
1680
- */
1045
+ /**
1046
+ * Creates a `RebufferingController`, a class trying to avoid various stalling
1047
+ * situations (such as rebuffering periods), and returns it.
1048
+ *
1049
+ * Various methods from that class need then to be called at various events
1050
+ * (see `RebufferingController` definition).
1051
+ *
1052
+ * This function also handles the `RebufferingController`'s events:
1053
+ * - emit "stalled" events when stalling situations cannot be prevented,
1054
+ * - emit "unstalled" events when we could get out of one,
1055
+ * - emit "warning" on various rebuffering-related minor issues
1056
+ * like discontinuity skipping.
1057
+ * @param {Object} playbackObserver
1058
+ * @param {Object} manifest
1059
+ * @param {Object} speed
1060
+ * @param {Object} cancelSignal
1061
+ * @returns {Object}
1062
+ */
1063
+ private _createRebufferingController(
1064
+ playbackObserver: IMediaElementPlaybackObserver,
1065
+ manifest: IManifest,
1066
+ speed: IReadOnlySharedReference<number>,
1067
+ cancelSignal: CancellationSignal,
1068
+ ): RebufferingController {
1681
1069
  const rebufferingController = new RebufferingController(
1682
1070
  playbackObserver,
1683
1071
  manifest,
1684
1072
  speed,
1685
1073
  );
1074
+ // Bubble-up events
1686
1075
  rebufferingController.addEventListener("stalled", (evt) =>
1687
1076
  this.trigger("stalled", evt),
1688
1077
  );
@@ -1692,415 +1081,105 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
1692
1081
  rebufferingController.addEventListener("warning", (err) =>
1693
1082
  this.trigger("warning", err),
1694
1083
  );
1695
- cancelSignal.register(() => {
1696
- rebufferingController.destroy();
1697
- });
1084
+ cancelSignal.register(() => rebufferingController.destroy());
1698
1085
  rebufferingController.start();
1699
- this._currentContentInfo.rebufferingController = rebufferingController;
1700
-
1701
- const currentContentInfo = this._currentContentInfo;
1702
- initialPlayPerformed.onUpdate(
1703
- (isPerformed, stopListening) => {
1704
- if (isPerformed) {
1705
- stopListening();
1706
- const streamEventsEmitter = new StreamEventsEmitter(manifest, playbackObserver);
1707
- currentContentInfo.streamEventsEmitter = streamEventsEmitter;
1708
- streamEventsEmitter.addEventListener(
1709
- "event",
1710
- (payload) => {
1711
- this.trigger("streamEvent", payload);
1712
- },
1713
- cancelSignal,
1714
- );
1715
- streamEventsEmitter.addEventListener(
1716
- "eventSkip",
1717
- (payload) => {
1718
- this.trigger("streamEventSkip", payload);
1719
- },
1720
- cancelSignal,
1721
- );
1722
- streamEventsEmitter.start();
1723
- cancelSignal.register(() => {
1724
- streamEventsEmitter.stop();
1725
- });
1726
- }
1727
- },
1728
- { clearSignal: cancelSignal, emitCurrentValue: true },
1729
- );
1730
-
1731
- const _getSegmentSinkMetrics = async (): Promise<ISegmentSinkMetrics | undefined> => {
1732
- this._awaitingRequests.nextRequestId++;
1733
- const requestId = this._awaitingRequests.nextRequestId;
1734
- this._settings.coreInterface.sendMessage({
1735
- type: MainThreadMessageType.PullSegmentSinkStoreInfos,
1736
- value: { requestId },
1737
- });
1738
- return new Promise((resolve, reject) => {
1739
- const rejectFn = (err: CancellationError) => {
1740
- cancelSignal.deregister(rejectFn);
1741
- this._awaitingRequests.pendingSinkMetrics.delete(requestId);
1742
- return reject(err);
1743
- };
1744
- this._awaitingRequests.pendingSinkMetrics.set(requestId, {
1745
- resolve: (value: ISegmentSinkMetrics | undefined) => {
1746
- cancelSignal.deregister(rejectFn);
1747
- this._awaitingRequests.pendingSinkMetrics.delete(requestId);
1748
- resolve(value);
1749
- },
1750
- });
1751
- cancelSignal.register(rejectFn);
1752
- });
1753
- };
1754
- const _getThumbnailsData = async (
1755
- periodId: string,
1756
- thumbnailTrackId: string,
1757
- time: number,
1758
- ): Promise<IThumbnailResponse> => {
1759
- if (this._currentContentInfo === null) {
1760
- return Promise.reject(new Error("Cannot fetch thumbnails: No content loaded."));
1761
- }
1762
- this._awaitingRequests.nextRequestId++;
1763
- const requestId = this._awaitingRequests.nextRequestId;
1764
- this._settings.coreInterface.sendMessage({
1765
- type: MainThreadMessageType.ThumbnailDataRequest,
1766
- contentId: this._currentContentInfo.contentId,
1767
- value: { requestId, periodId, thumbnailTrackId, time },
1768
- });
1769
-
1770
- return new Promise((resolve, reject) => {
1771
- const rejectFn = (err: CancellationError) => {
1772
- cleanUp();
1773
- reject(err);
1774
- };
1775
- const cleanUp = () => {
1776
- cancelSignal.deregister(rejectFn);
1777
- this._awaitingRequests.pendingThumbnailFetching.delete(requestId);
1778
- };
1779
-
1780
- this._awaitingRequests.pendingThumbnailFetching.set(requestId, {
1781
- resolve: (value: IThumbnailResponse) => {
1782
- cleanUp();
1783
- resolve(value);
1784
- },
1785
- reject: (value: unknown) => {
1786
- cleanUp();
1787
- reject(value);
1788
- },
1789
- });
1790
- cancelSignal.register(rejectFn);
1791
- });
1792
- };
1793
- /**
1794
- * Emit a "loaded" events once the initial play has been performed and the
1795
- * media can begin playback.
1796
- * Also emits warning events if issues arise when doing so.
1797
- */
1798
- autoPlayResult
1799
- .then(() => {
1800
- getLoadedReference(playbackObserver, false, cancelSignal).onUpdate(
1801
- (isLoaded, stopListening) => {
1802
- if (isLoaded) {
1803
- stopListening();
1804
- this.trigger("loaded", {
1805
- getSegmentSinkMetrics: _getSegmentSinkMetrics,
1806
- getThumbnailData: _getThumbnailsData,
1807
- });
1808
- }
1809
- },
1810
- { emitCurrentValue: true, clearSignal: cancelSignal },
1811
- );
1812
- })
1813
- .catch((err) => {
1814
- if (cancelSignal.isCancelled()) {
1815
- return;
1816
- }
1817
- this._onFatalError(err);
1818
- });
1819
-
1820
- return corePlaybackObserver;
1086
+ return rebufferingController;
1821
1087
  }
1822
1088
 
1823
1089
  /**
1824
- * Initialize content playback if and only if those conditions are filled:
1825
- * - The Manifest is fetched and stored in `this._currentContentInfo`.
1826
- * - `drmInitializationStatus` indicates that DRM matters are initialized.
1827
- * - `mediaSourceStatus` indicates that the MediaSource is attached to the
1828
- * `mediaElement`.
1829
- *
1830
- * In other cases, this method will do nothing.
1831
- *
1832
- * To call when any of those conditions might become `true`, to start-up
1833
- * playback.
1090
+ * Evaluates a list of codecs to determine their support status.
1834
1091
  *
1835
- * @param {Object} parameters
1836
- * @returns {boolean} - Returns `true` if all conditions where met for
1837
- * playback start.
1092
+ * @param {Array} codecsToCheck - The list of codecs to check.
1093
+ * @returns {Array} - The list of evaluated codecs with their support status updated.
1838
1094
  */
1839
- private _startPlaybackIfReady(parameters: {
1840
- mediaElement: IMediaElement;
1841
- textDisplayer: ITextDisplayer | null;
1842
- playbackObserver: IMediaElementPlaybackObserver;
1843
- drmInitializationStatus: IReadOnlySharedReference<IDrmInitializationStatus>;
1844
- mediaSourceStatus: IReadOnlySharedReference<MediaSourceInitializationStatus>;
1845
- }): boolean {
1846
- if (this._currentContentInfo === null || this._currentContentInfo.manifest === null) {
1847
- return false;
1848
- }
1849
- const drmInitStatus = parameters.drmInitializationStatus.getValue();
1850
- if (drmInitStatus.initializationState.type !== "initialized") {
1851
- return false;
1852
- }
1853
- const msInitStatus = parameters.mediaSourceStatus.getValue();
1854
- if (msInitStatus !== MediaSourceInitializationStatus.Attached) {
1855
- return false;
1856
- }
1857
-
1858
- const { contentId, manifest } = this._currentContentInfo;
1859
- log.debug("Init", "Calculating initial time");
1860
- const initialTime = getInitialTime(
1861
- manifest,
1862
- this._settings.lowLatencyMode,
1863
- this._settings.startAt,
1864
- );
1865
- log.debug("Init", "Initial time calculated", { initialTime });
1866
- const { enableFastSwitching, onCodecSwitch } = this._settings.bufferOptions;
1867
- const corePlaybackObserver = this._setUpModulesOnNewMediaSource(
1868
- {
1869
- initialTime,
1870
- autoPlay: this._settings.autoPlay,
1871
- mediaElement: parameters.mediaElement,
1872
- textDisplayer: parameters.textDisplayer,
1873
- playbackObserver: parameters.playbackObserver,
1874
- },
1875
- this._currentMediaSourceCanceller.signal,
1876
- );
1877
-
1878
- if (this._currentMediaSourceCanceller.isUsed() || corePlaybackObserver === null) {
1879
- return true;
1880
- }
1881
- const initialObservation = corePlaybackObserver.getReference().getValue();
1882
- const sentInitialObservation = objectAssign(initialObservation, {
1883
- position: initialObservation.position.serialize(),
1884
- });
1885
- this._settings.coreInterface.sendMessage({
1886
- type: MainThreadMessageType.StartPreparedContent,
1887
- contentId,
1888
- value: {
1889
- initialTime,
1890
- initialObservation: sentInitialObservation,
1891
- drmSystemId: drmInitStatus.drmSystemId,
1892
- enableFastSwitching,
1893
- onCodecSwitch,
1894
- },
1095
+ private getCodecsSupportInfo(
1096
+ codecsToCheck: Array<{ mimeType: string; codec: string }>,
1097
+ mediaElement: IMediaElement,
1098
+ ): ICodecSupportInfo[] {
1099
+ const codecsSupportInfo: ICodecSupportInfo[] = codecsToCheck.map((codecToCheck) => {
1100
+ const inputCodec = `${codecToCheck.mimeType};codecs="${codecToCheck.codec}"`;
1101
+ const isSupported = isCodecSupported(mediaElement, inputCodec);
1102
+ if (!isSupported) {
1103
+ return {
1104
+ mimeType: codecToCheck.mimeType,
1105
+ codec: codecToCheck.codec,
1106
+ supported: false,
1107
+ supportedIfEncrypted: false,
1108
+ };
1109
+ }
1110
+ /**
1111
+ * `true` if the codec is supported when encrypted, `false` if it is not
1112
+ * supported, or `undefined` if we cannot obtain that information.
1113
+ */
1114
+ let supportedIfEncrypted: boolean | undefined;
1115
+ if (this._decryptionCapabilities.status === "uninitialized") {
1116
+ supportedIfEncrypted = undefined;
1117
+ } else if (this._decryptionCapabilities.status === "disabled") {
1118
+ // It's ambiguous here, but let's say that no ContentDecryptor means that
1119
+ // the codec is supported by it.
1120
+ supportedIfEncrypted = true;
1121
+ } else {
1122
+ const contentDecryptor = this._decryptionCapabilities.value;
1123
+ if (contentDecryptor.getState() !== ContentDecryptorState.Initializing) {
1124
+ // No information is available regarding the support status.
1125
+ // Defaulting to assume the codec is supported.
1126
+ supportedIfEncrypted =
1127
+ contentDecryptor.isCodecSupported(
1128
+ codecToCheck.mimeType,
1129
+ codecToCheck.codec,
1130
+ ) ?? true;
1131
+ }
1132
+ }
1133
+ return {
1134
+ mimeType: codecToCheck.mimeType,
1135
+ codec: codecToCheck.codec,
1136
+ supported: isSupported,
1137
+ supportedIfEncrypted,
1138
+ };
1895
1139
  });
1896
-
1897
- corePlaybackObserver.listen(
1898
- (obs) => {
1899
- this._settings.coreInterface.sendMessage({
1900
- type: MainThreadMessageType.PlaybackObservation,
1901
- contentId,
1902
- value: objectAssign(obs, { position: obs.position.serialize() }),
1903
- });
1904
- },
1905
- {
1906
- includeLastObservation: false,
1907
- clearSignal: this._currentMediaSourceCanceller.signal,
1908
- },
1909
- );
1910
- this.trigger("manifestReady", manifest);
1911
- return true;
1140
+ return codecsSupportInfo;
1912
1141
  }
1913
1142
 
1914
1143
  /**
1915
- * Handles core messages asking to create a MediaSource.
1916
- * @param {Object} msg - The core's message received.
1917
- * @param {HTMLMediaElement} mediaElement - HTMLMediaElement on which the
1918
- * content plays.
1919
- * @param {Object} coreInterface - The interface to the core.
1144
+ * Update the support status of all Representations in the Manifest.
1145
+ *
1146
+ * To call anytime either the Manifest is linked to new codecs or new means
1147
+ * to test for codec support are available.
1148
+ * @param {Object} manifest
1920
1149
  */
1921
- private _onCreateMediaSourceMessage(
1922
- msg: ICreateMediaSourceCoreMessage,
1150
+ private _refreshManifestCodecSupport(
1151
+ manifest: IManifest,
1923
1152
  mediaElement: IMediaElement,
1924
- mediaSourceStatus: SharedReference<MediaSourceInitializationStatus>,
1925
- coreInterface: CoreInterface,
1926
1153
  ): void {
1927
- if (this._currentContentInfo?.contentId !== msg.contentId) {
1928
- log.info("Init", "Ignoring MediaSource attachment due to wrong `contentId`");
1929
- } else {
1930
- const { mediaSourceId } = msg;
1154
+ const codecsToTest = manifest.getCodecsWithUnknownSupport();
1155
+ const codecsSupportInfo = this.getCodecsSupportInfo(codecsToTest, mediaElement);
1156
+ if (codecsSupportInfo.length > 0) {
1931
1157
  try {
1932
- mediaSourceStatus.onUpdate(
1933
- (currStatus, stopListening) => {
1934
- if (this._currentContentInfo === null) {
1935
- stopListening();
1936
- return;
1937
- }
1938
- if (currStatus === MediaSourceInitializationStatus.AttachNow) {
1939
- stopListening();
1940
- const mediaSource = new MainMediaSourceInterface(
1941
- mediaSourceId,
1942
- "FORCED_MEDIA_SOURCE" in mediaElement
1943
- ? mediaElement.FORCED_MEDIA_SOURCE
1944
- : undefined,
1945
- );
1946
- if (this._currentContentInfo.mediaSourceInfo?.type === "main") {
1947
- this._currentContentInfo.mediaSourceInfo.mediaSource.dispose();
1948
- }
1949
- this._currentContentInfo.mediaSourceInfo = {
1950
- type: "main",
1951
- mediaSource,
1952
- };
1953
- mediaSource.addEventListener("mediaSourceOpen", () => {
1954
- coreInterface.sendMessage({
1955
- type: MainThreadMessageType.MediaSourceReadyStateChange,
1956
- mediaSourceId,
1957
- value: "open",
1958
- });
1959
- });
1960
- mediaSource.addEventListener("mediaSourceEnded", () => {
1961
- coreInterface.sendMessage({
1962
- type: MainThreadMessageType.MediaSourceReadyStateChange,
1963
- mediaSourceId,
1964
- value: "ended",
1965
- });
1966
- });
1967
- mediaSource.addEventListener("mediaSourceClose", () => {
1968
- coreInterface.sendMessage({
1969
- type: MainThreadMessageType.MediaSourceReadyStateChange,
1970
- mediaSourceId,
1971
- value: "closed",
1972
- });
1973
- });
1974
- let url: string | null = null;
1975
- if (mediaSource.handle.type === "handle") {
1976
- mediaElement.srcObject = mediaSource.handle.value;
1977
- } else {
1978
- url = URL.createObjectURL(mediaSource.handle.value);
1979
- mediaElement.src = url;
1980
- }
1981
- this._currentMediaSourceCanceller.signal.register(() => {
1982
- mediaSource.dispose();
1983
- resetMediaElement(mediaElement, url);
1984
- });
1985
- mediaSourceStatus.setValue(MediaSourceInitializationStatus.Attached);
1986
- disableRemotePlaybackOnManagedMediaSource(
1987
- mediaElement,
1988
- this._currentMediaSourceCanceller.signal,
1989
- );
1990
- }
1991
- },
1992
- {
1993
- emitCurrentValue: true,
1994
- clearSignal: this._currentMediaSourceCanceller.signal,
1995
- },
1996
- );
1997
- } catch (_err) {
1998
- const error = new OtherError(
1999
- "NONE",
2000
- "Unknown error when creating the MediaSource",
2001
- );
2002
- this._onFatalError(error);
1158
+ manifest.updateCodecSupport(codecsSupportInfo);
1159
+ } catch (err) {
1160
+ this._onFatalError(err);
2003
1161
  }
2004
1162
  }
2005
1163
  }
2006
1164
  }
2007
1165
 
2008
- export interface IMediaSourceContentInitializerContentInfos {
2009
- /**
2010
- * "contentId", which is the identifier for the currently loaded content.
2011
- * Allows to ensure that the Core is referencing the current content, not
2012
- * a previously stopped one.
2013
- */
2014
- contentId: string;
2015
- /**
2016
- * Current parsed Manifest.
2017
- * `null` if not fetched / parsed yet.
2018
- */
2019
- manifest: IManifestMetadata | null;
2020
-
2021
- /**
2022
- * Current MediaSource linked to the content.
2023
- *
2024
- * `null` if no MediaSource is currently created for the content.
2025
- */
2026
- mediaSourceInfo:
2027
- | {
2028
- type: "main";
2029
- mediaSource: MainMediaSourceInterface;
2030
- }
2031
- | {
2032
- type: "core";
2033
- mediaSourceId: string;
2034
- }
2035
- | null;
2036
- /**
2037
- * Current `RebufferingController` linked to the content, allowing to
2038
- * detect and handle rebuffering situations.
2039
- *
2040
- * `null` if none is currently created for the content.
2041
- */
2042
- rebufferingController: RebufferingController | null;
2043
- /**
2044
- * Current `StreamEventsEmitter` linked to the content, allowing to
2045
- * send events found in the Manifest.
2046
- *
2047
- * `null` if none is currently created for the content.
2048
- */
2049
- streamEventsEmitter: StreamEventsEmitter | null;
2050
- /**
2051
- * The initial position to seek to in seconds once the content is loadeed.
2052
- * `undefined` if unknown yet.
2053
- */
2054
- initialTime: number | undefined;
2055
- /**
2056
- * Whether to automatically play once the content is loaded.
2057
- * `undefined` if unknown yet.
2058
- */
2059
- autoPlay: boolean | undefined;
2060
- /**
2061
- * Set to `true` once the initial play (or skipping the initial play when
2062
- * autoplay is not enabled) has been done.
2063
- * Set to `false` when it hasn't been done yet.
2064
- *
2065
- * Set to `null` when those considerations are not taken yet.
2066
- */
2067
- initialPlayPerformed: IReadOnlySharedReference<boolean> | null;
2068
- /**
2069
- * Set to the initialized `ContentDecryptor` instance linked to that content.
2070
- *
2071
- * Set to `null` when those considerations are not taken.
2072
- */
2073
- contentDecryptor: IContentDecryptor | null;
2074
- /**
2075
- * If `true`, MSE API should be used in the core part of the RxPlayer (in the
2076
- * WebWorker).
2077
- * If `false`, they should be relied on on main thread.
2078
- */
2079
- useMseInWorker: boolean;
1166
+ function createTextDisplayer(
1167
+ mediaElement: IMediaElement,
1168
+ textTrackOptions: ITextDisplayerOptions,
1169
+ ): ITextDisplayer | null {
1170
+ if (textTrackOptions.textTrackMode === "html" && features.htmlTextDisplayer !== null) {
1171
+ return new features.htmlTextDisplayer(
1172
+ mediaElement,
1173
+ textTrackOptions.textTrackElement,
1174
+ );
1175
+ } else if (features.nativeTextDisplayer !== null) {
1176
+ return new features.nativeTextDisplayer(mediaElement);
1177
+ }
1178
+ return null;
2080
1179
  }
2081
1180
 
2082
1181
  /** Arguments to give to the `InitializeOnMediaSource` function. */
2083
1182
  export interface IInitializeArguments {
2084
- /**
2085
- * The `MediaSourceContentInitializer` will interact with the RxPlayer's core
2086
- * logic (the one loading media data) by exchanging messages through an
2087
- * interface called the `CoreInterface`.
2088
- *
2089
- * This `CoreInterface` allows to abstract its actual current implementation.
2090
- * E.g., the core logic could be running in a WebWorker or in main thread, in
2091
- * which cases message exchanging mechanisms would be different.
2092
- */
2093
- coreInterface: CoreInterface;
2094
- /**
2095
- * If `true`, MSE API should be used in the core part of the RxPlayer (in the
2096
- * WebWorker).
2097
- * If `false`, they should be relied on on main thread.
2098
- *
2099
- * This might depend on both browser capabilities and preferences. It is
2100
- * assumed that the caller perform all those checks, the `ContentInitializer`
2101
- * won't check again the validity of this value.
2102
- */
2103
- useMseInWorker: boolean;
2104
1183
  /** Options concerning the ABR logic. */
2105
1184
  adaptiveOptions: IAdaptiveRepresentationSelectorArguments;
2106
1185
  /** `true` if we should play when loaded. */
@@ -2137,14 +1216,6 @@ export interface IInitializeArguments {
2137
1216
  keySystems: IKeySystemOption[];
2138
1217
  /** `true` to play low-latency contents optimally. */
2139
1218
  lowLatencyMode: boolean;
2140
- /**
2141
- * The type of "transport" wanted, e.g. "dash" or "smooth".
2142
- */
2143
- transport: string;
2144
- /** Options relative to the streaming protocol. */
2145
- transportOptions: Omit<ITransportOptions, "representationFilter"> & {
2146
- representationFilter?: IRepresentationFilter | string | undefined;
2147
- };
2148
1219
  /** Settings linked to Manifest requests. */
2149
1220
  manifestRequestSettings: {
2150
1221
  /** Maximum number of time a request on error will be retried. */
@@ -2170,6 +1241,8 @@ export interface IInitializeArguments {
2170
1241
  */
2171
1242
  initialManifest: IInitialManifest | undefined;
2172
1243
  };
1244
+ /** Logic linked Manifest and segment loading and parsing. */
1245
+ transport: ITransportPipelines;
2173
1246
  /** Configuration for the segment requesting logic. */
2174
1247
  segmentRequestOptions: {
2175
1248
  lowLatencyMode: boolean;
@@ -2198,151 +1271,196 @@ export interface IInitializeArguments {
2198
1271
  url: string | undefined;
2199
1272
  }
2200
1273
 
2201
- function bindNumberReferencesToCore(
2202
- coreInterface: CoreInterface,
2203
- cancellationSignal: CancellationSignal,
2204
- ...refs: Array<
2205
- [
2206
- IReadOnlySharedReference<number>,
2207
- (
2208
- | "wantedBufferAhead"
2209
- | "maxVideoBufferSize"
2210
- | "maxBufferBehind"
2211
- | "maxBufferAhead"
2212
- | "throttleVideoBitrate"
2213
- ),
2214
- ]
2215
- >
2216
- ): void {
2217
- for (const ref of refs) {
2218
- ref[0].onUpdate(
2219
- (newVal) => {
2220
- // NOTE: The TypeScript checks have already been made by this function's
2221
- // overload, but the body here is not aware of that.
2222
- coreInterface.sendMessage({
2223
- type: MainThreadMessageType.ReferenceUpdate,
2224
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any
2225
- value: { name: ref[1] as any, newVal: newVal as any },
2226
- });
2227
- },
2228
- { clearSignal: cancellationSignal, emitCurrentValue: true },
2229
- );
2230
- }
2231
- }
2232
-
2233
- function formatCoreError(sentError: ISentError): IPlayerError {
2234
- switch (sentError.name) {
2235
- case "NetworkError":
2236
- return new NetworkError(
2237
- sentError.code,
2238
- new RequestError(
2239
- sentError.baseError.url,
2240
- sentError.baseError.status,
2241
- sentError.baseError.type,
2242
- ),
2243
- );
2244
- case "MediaError":
2245
- // eslint-disable-next-line
2246
- return new MediaError(sentError.code as any, sentError.reason, {
2247
- tracks: sentError.tracks,
2248
- });
2249
- case "EncryptedMediaError":
2250
- // We assume that everything have already been checked Worker-side here
2251
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
2252
- return new EncryptedMediaError(sentError.code, sentError.reason, {
2253
- keyStatuses: sentError.keyStatuses,
2254
- keySystemConfiguration: sentError.keySystemConfiguration,
2255
- keySystem: sentError.keySystem,
2256
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2257
- } as any);
2258
- case "OtherError":
2259
- return new OtherError(sentError.code, sentError.reason);
2260
- }
2261
- }
2262
-
2263
- /** Enume allowing to state what is the current status of MediaSource initialization. */
2264
- const enum MediaSourceInitializationStatus {
2265
- /**
2266
- * The `MediaSource` is not attached to the `HTMLMediaElement` and shouldn't
2267
- * be yet.
2268
- */
2269
- Nothing,
1274
+ /** Arguments needed when starting to buffer media on a specific MediaSource. */
1275
+ interface IBufferingMediaSettings {
1276
+ /** Various stream-related options. */
1277
+ bufferOptions: IStreamOrchestratorOptions;
1278
+ /* Manifest of the content we want to play. */
1279
+ manifest: IManifest;
1280
+ /** Media Element on which the content will be played. */
1281
+ mediaElement: IMediaElement;
1282
+ /** Emit playback conditions regularly. */
1283
+ playbackObserver: IMediaElementPlaybackObserver;
1284
+ /** Estimate the right Representation. */
1285
+ representationEstimator: IRepresentationEstimator;
2270
1286
  /**
2271
- * The `MediaSource` is not yet attached to the `HTMLMediaElement` but it
2272
- * now can and should be.
2273
- *
2274
- * The purpose of this enum variant is to be set when wanting to indicate
2275
- * that `MediaSource` attachment has to be done, in code that do not have
2276
- * the capability to do so.
2277
- *
2278
- * The code that can do so would then read that value and then set this enum
2279
- * to `Attached` once the `MediaSource` is attached.
1287
+ * Interface allowing to prioritize CDN between one another depending on past
1288
+ * performances, content steering, etc.
2280
1289
  */
2281
- AttachNow,
2282
- /** The `MediaSource` is attached to the `HTMLMediaElement`. */
2283
- Attached,
1290
+ cdnPrioritizer: CdnPrioritizer;
1291
+ /** Module to facilitate segment fetching. */
1292
+ segmentQueueCreator: SegmentQueueCreator;
1293
+ /** Last wanted playback rate. */
1294
+ speed: IReadOnlySharedReference<number>;
1295
+ /** `MediaSource` element on which the media will be buffered. */
1296
+ mediaSource: MainMediaSourceInterface;
1297
+ /** The initial position to seek to in media time, in seconds. */
1298
+ initialTime: number;
1299
+ /** If `true` it should automatically play once enough data is loaded. */
1300
+ autoPlay: boolean;
2284
1301
  }
2285
1302
 
2286
- interface IDrmInitializationStatus {
2287
- /** Current initialization state the decryption logic is in. */
2288
- initializationState: IDecryptionInitializationState;
2289
- /**
2290
- * If set, corresponds to the hex string describing the current key system
2291
- * used.
2292
- * `undefined` if unknown or if it does not apply.
2293
- */
2294
- drmSystemId: string | undefined;
1303
+ /**
1304
+ * Change the decipherability of Representations which have their key id in one
1305
+ * of the given Arrays:
1306
+ *
1307
+ * - Those who have a key id listed in `whitelistedKeyIds` will have their
1308
+ * decipherability updated to `true`
1309
+ *
1310
+ * - Those who have a key id listed in `blacklistedKeyIds` will have their
1311
+ * decipherability updated to `false`
1312
+ *
1313
+ * - Those who have a key id listed in `delistedKeyIds` will have their
1314
+ * decipherability updated to `undefined`.
1315
+ *
1316
+ * @param {Object} manifest
1317
+ * @param {Array.<Uint8Array>} whitelistedKeyIds
1318
+ * @param {Array.<Uint8Array>} blacklistedKeyIds
1319
+ * @param {Array.<Uint8Array>} delistedKeyIds
1320
+ */
1321
+ function updateKeyIdsDecipherabilityOnManifest(
1322
+ manifest: IManifest,
1323
+ whitelistedKeyIds: Uint8Array[],
1324
+ blacklistedKeyIds: Uint8Array[],
1325
+ delistedKeyIds: Uint8Array[],
1326
+ ): void {
1327
+ manifest.updateRepresentationsDeciperability((ctx) => {
1328
+ const { representation } = ctx;
1329
+ if (representation.contentProtections === undefined) {
1330
+ return representation.decipherable;
1331
+ }
1332
+ const contentKIDs = representation.contentProtections.keyIds;
1333
+ if (contentKIDs !== undefined) {
1334
+ for (const elt of contentKIDs) {
1335
+ for (const blacklistedKeyId of blacklistedKeyIds) {
1336
+ if (areArraysOfNumbersEqual(blacklistedKeyId, elt)) {
1337
+ return false;
1338
+ }
1339
+ }
1340
+ for (const whitelistedKeyId of whitelistedKeyIds) {
1341
+ if (areArraysOfNumbersEqual(whitelistedKeyId, elt)) {
1342
+ return true;
1343
+ }
1344
+ }
1345
+ for (const delistedKeyId of delistedKeyIds) {
1346
+ if (areArraysOfNumbersEqual(delistedKeyId, elt)) {
1347
+ return undefined;
1348
+ }
1349
+ }
1350
+ }
1351
+ }
1352
+ return representation.decipherable;
1353
+ });
2295
1354
  }
2296
1355
 
2297
- /** Initialization steps to add decryption capabilities to an `HTMLMediaElement`. */
2298
- type IDecryptionInitializationState =
2299
- /**
2300
- * Decryption capabilities have not been initialized yet.
2301
- * You should wait before performing any action on the concerned
2302
- * `HTMLMediaElement` (such as linking a content / `MediaSource` to it).
2303
- */
2304
- | { type: "uninitialized"; value: null }
2305
- /**
2306
- * The `MediaSource` or media url can be linked AND segments can be pushed to
2307
- * the `HTMLMediaElement` on which decryption capabilities were wanted.
2308
- */
2309
- | {
2310
- type: "initialized";
2311
- value: null;
2312
- };
2313
-
2314
- function formatSourceBufferError(error: unknown): SourceBufferError {
2315
- if (error instanceof SourceBufferError) {
2316
- return error;
2317
- } else if (error instanceof Error) {
2318
- return new SourceBufferError(
2319
- error.name,
2320
- error.message,
2321
- error.name === "QuotaExceededError",
2322
- );
2323
- } else {
2324
- return new SourceBufferError("Error", "Unknown SourceBufferError Error", false);
2325
- }
1356
+ /**
1357
+ * Update decipherability to `false` to any Representation which is linked to
1358
+ * the given initialization data.
1359
+ * @param {Object} manifest
1360
+ * @param {Object} initData
1361
+ */
1362
+ function blackListProtectionDataOnManifest(
1363
+ manifest: IManifest,
1364
+ initData: IProcessedProtectionData,
1365
+ ) {
1366
+ manifest.updateRepresentationsDeciperability((ctx) => {
1367
+ const rep = ctx.representation;
1368
+ if (rep.decipherable === false) {
1369
+ return false;
1370
+ }
1371
+ const segmentProtections = rep.contentProtections?.initData ?? [];
1372
+ for (const protection of segmentProtections) {
1373
+ if (initData.type === undefined || protection.type === initData.type) {
1374
+ const containedInitData = initData.values
1375
+ .getFormattedValues()
1376
+ .every((undecipherableVal) => {
1377
+ return protection.values.some((currVal) => {
1378
+ return (
1379
+ (undecipherableVal.systemId === undefined ||
1380
+ currVal.systemId === undecipherableVal.systemId) &&
1381
+ areArraysOfNumbersEqual(currVal.data, undecipherableVal.data)
1382
+ );
1383
+ });
1384
+ });
1385
+ if (containedInitData) {
1386
+ return false;
1387
+ }
1388
+ }
1389
+ }
1390
+ return rep.decipherable;
1391
+ });
2326
1392
  }
2327
1393
 
2328
1394
  /**
2329
- * The Core might send back logs. In that situation, the message might be
2330
- * formatted slightly differently to be able to cross threads (so a
2331
- * serializable format has to be sent).
2332
- *
2333
- * This function translates that Core format to what's expected by the
2334
- * logger.
2335
- *
2336
- * @param {*} arg
2337
- * @returns {*}
1395
+ * Function to call when you want to "reload" the MediaSource: basically
1396
+ * restarting playback on a new MediaSource for the same content (it may
1397
+ * be for varied reasons, such as ensuring data buffers are empty, or
1398
+ * restarting after some kind of fatal error).
1399
+ * @param {Object} reloadOrder
1400
+ * @param {number} reloadOrder.position - Position in seconds at which we
1401
+ * should restart from when playback restarts.
1402
+ * @param {boolean} reloadOrder.autoPlay - If `true` we will directly play
1403
+ * once enough data is re-loaded.
2338
1404
  */
2339
- function formatSentLogObject(arg: ISentLogValue): IAcceptedLogValue {
2340
- if (typeof arg !== "object") {
2341
- return arg;
2342
- }
1405
+ type IReloadMediaSourceCallback = (reloadOrder: {
1406
+ position: number;
1407
+ autoPlay: boolean;
1408
+ }) => void;
2343
1409
 
2344
- if (arg?.isSerializedError === true) {
2345
- return formatCoreError(arg as ISentError);
1410
+ /**
1411
+ * Handle accordingly an `IFreezeResolution` object.
1412
+ * @param {Object|null} freezeResolution - The `IFreezeResolution` suggested.
1413
+ * @param {Object} param - Parameters that might be needed to implement the
1414
+ * resolution.
1415
+ * @param {Object} param.manifest - The current content's Manifest object.
1416
+ * @param {Object} param.playbackObserver - Object regularly emitting playback
1417
+ * conditions.
1418
+ * @param {Function} param.triggerReload - Function to call if we need to ask
1419
+ * for a "MediaSource reload".
1420
+ * @param {Boolean} param.enableRepresentationAvoidance - If `true`, this
1421
+ * function is authorized to mark `Representation` as "to avoid" if the
1422
+ * `IFreezeResolution` object suggest it.
1423
+ */
1424
+ function handleFreezeResolution(
1425
+ freezeResolution: IFreezeResolution,
1426
+ {
1427
+ playbackObserver,
1428
+ enableRepresentationAvoidance,
1429
+ manifest,
1430
+ triggerReload,
1431
+ }: {
1432
+ playbackObserver: IMediaElementPlaybackObserver;
1433
+ enableRepresentationAvoidance: boolean;
1434
+ manifest: IManifest;
1435
+ triggerReload: () => void;
1436
+ },
1437
+ ): void {
1438
+ switch (freezeResolution.type) {
1439
+ case "reload": {
1440
+ log.info("Init", "Planning reload due to freeze");
1441
+ triggerReload();
1442
+ break;
1443
+ }
1444
+ case "flush": {
1445
+ log.info("Init", "Flushing buffer due to freeze");
1446
+ const observation = playbackObserver.getReference().getValue();
1447
+ const currentTime = observation.position.isAwaitingFuturePosition()
1448
+ ? observation.position.getWanted()
1449
+ : playbackObserver.getCurrentTime();
1450
+ const relativeResumingPosition = freezeResolution.value.relativeSeek;
1451
+ const wantedSeekingTime = currentTime + relativeResumingPosition;
1452
+ playbackObserver.setCurrentTime(wantedSeekingTime);
1453
+ break;
1454
+ }
1455
+ case "avoid-representations": {
1456
+ const contents = freezeResolution.value;
1457
+ if (enableRepresentationAvoidance) {
1458
+ manifest.addRepresentationsToAvoid(contents);
1459
+ }
1460
+ triggerReload();
1461
+ break;
1462
+ }
1463
+ default:
1464
+ assertUnreachable(freezeResolution);
2346
1465
  }
2347
- return arg as Exclude<ISentLogValue, ISentError>;
2348
1466
  }