rx-player 4.4.0 → 4.4.1-dev.2025101500

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 (341) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +52 -113
  3. package/VERSION +1 -1
  4. package/dist/commonjs/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  5. package/dist/commonjs/__GENERATED_CODE/embedded_worker.js +1 -1
  6. package/dist/commonjs/core/{main/common → entry}/FreezeResolver.d.ts +3 -3
  7. package/dist/commonjs/core/entry/FreezeResolver.d.ts.map +1 -0
  8. package/dist/commonjs/core/{main/common → entry}/FreezeResolver.js +3 -3
  9. package/dist/{es2017/core/main/worker → commonjs/core/entry}/content_preparer.d.ts +22 -15
  10. package/dist/commonjs/core/entry/content_preparer.d.ts.map +1 -0
  11. package/dist/commonjs/core/{main/worker → entry}/content_preparer.js +62 -64
  12. package/dist/{es2017/core/main/common → commonjs/core/entry}/content_time_boundaries_observer.d.ts +5 -5
  13. package/dist/commonjs/core/entry/content_time_boundaries_observer.d.ts.map +1 -0
  14. package/dist/commonjs/core/{main/common → entry}/content_time_boundaries_observer.js +6 -6
  15. package/dist/commonjs/core/entry/core_entry.d.ts +36 -0
  16. package/dist/commonjs/core/entry/core_entry.d.ts.map +1 -0
  17. package/dist/commonjs/core/{main/worker/worker_main.js → entry/core_entry.js} +192 -153
  18. package/dist/commonjs/core/{main/worker/worker_text_displayer_interface.d.ts → entry/core_text_displayer_interface.d.ts} +11 -11
  19. package/dist/commonjs/core/entry/core_text_displayer_interface.d.ts.map +1 -0
  20. package/dist/commonjs/core/{main/worker/worker_text_displayer_interface.js → entry/core_text_displayer_interface.js} +22 -22
  21. package/dist/commonjs/core/{main/common → entry}/create_content_time_boundaries_observer.d.ts +6 -6
  22. package/dist/commonjs/core/entry/create_content_time_boundaries_observer.d.ts.map +1 -0
  23. package/dist/commonjs/core/{main/common → entry}/create_content_time_boundaries_observer.js +1 -1
  24. package/dist/commonjs/core/{main/common → entry}/get_buffered_data_per_media_buffer.d.ts +4 -4
  25. package/dist/commonjs/core/entry/get_buffered_data_per_media_buffer.d.ts.map +1 -0
  26. package/dist/commonjs/core/{main/common → entry}/get_buffered_data_per_media_buffer.js +1 -1
  27. package/dist/{es2017/core/main/common → commonjs/core/entry}/get_thumbnail_data.d.ts +3 -3
  28. package/dist/commonjs/core/entry/get_thumbnail_data.d.ts.map +1 -0
  29. package/dist/commonjs/core/{main/common → entry}/get_thumbnail_data.js +2 -2
  30. package/dist/commonjs/core/entry/index.d.ts +5 -0
  31. package/dist/commonjs/core/entry/index.d.ts.map +1 -0
  32. package/dist/commonjs/core/entry/index.js +4 -0
  33. package/dist/{es2017/core/main/common → commonjs/core/entry}/synchronize_sinks_on_observation.d.ts +2 -2
  34. package/dist/commonjs/core/entry/synchronize_sinks_on_observation.d.ts.map +1 -0
  35. package/dist/commonjs/core/{main/worker → entry}/track_choice_setter.d.ts +4 -4
  36. package/dist/commonjs/core/entry/track_choice_setter.d.ts.map +1 -0
  37. package/dist/commonjs/core/{main/worker → entry}/track_choice_setter.js +4 -4
  38. package/dist/commonjs/core/entry/utils.d.ts +3 -0
  39. package/dist/commonjs/core/entry/utils.d.ts.map +1 -0
  40. package/dist/commonjs/core/entry/utils.js +11 -0
  41. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
  42. package/dist/commonjs/core/stream/orchestrator/stream_orchestrator.js +17 -0
  43. package/dist/commonjs/core/types.d.ts +519 -1
  44. package/dist/commonjs/core/types.d.ts.map +1 -1
  45. package/dist/commonjs/core/types.js +1 -0
  46. package/dist/commonjs/experimental/features/local.d.ts.map +1 -1
  47. package/dist/commonjs/experimental/features/local.js +7 -1
  48. package/dist/commonjs/experimental/features/metaplaylist.d.ts.map +1 -1
  49. package/dist/commonjs/experimental/features/metaplaylist.js +7 -1
  50. package/dist/commonjs/experimental/features/multi_thread.d.ts.map +1 -1
  51. package/dist/commonjs/experimental/features/multi_thread.js +6 -2
  52. package/dist/commonjs/features/features_object.js +1 -1
  53. package/dist/commonjs/features/list/dash.d.ts.map +1 -1
  54. package/dist/commonjs/features/list/dash.js +7 -1
  55. package/dist/commonjs/features/list/dash_wasm.d.ts.map +1 -1
  56. package/dist/commonjs/features/list/dash_wasm.js +7 -1
  57. package/dist/commonjs/features/list/media_source_main.d.ts.map +1 -1
  58. package/dist/commonjs/features/list/media_source_main.js +7 -1
  59. package/dist/commonjs/features/list/smooth.d.ts.map +1 -1
  60. package/dist/commonjs/features/list/smooth.js +7 -1
  61. package/dist/commonjs/features/types.d.ts +20 -4
  62. package/dist/commonjs/features/types.d.ts.map +1 -1
  63. package/dist/commonjs/main_thread/api/public_api.d.ts.map +1 -1
  64. package/dist/commonjs/main_thread/api/public_api.js +39 -42
  65. package/dist/commonjs/main_thread/core_interface/base.d.ts +13 -0
  66. package/dist/commonjs/main_thread/core_interface/base.d.ts.map +1 -0
  67. package/dist/commonjs/main_thread/core_interface/base.js +32 -0
  68. package/dist/commonjs/main_thread/core_interface/monothread.d.ts +13 -0
  69. package/dist/commonjs/main_thread/core_interface/monothread.d.ts.map +1 -0
  70. package/dist/commonjs/main_thread/core_interface/monothread.js +56 -0
  71. package/dist/commonjs/main_thread/core_interface/multithread.d.ts +25 -0
  72. package/dist/commonjs/main_thread/core_interface/multithread.d.ts.map +1 -0
  73. package/dist/commonjs/main_thread/core_interface/multithread.js +67 -0
  74. package/dist/commonjs/main_thread/core_interface/types.d.ts +6 -0
  75. package/dist/commonjs/main_thread/core_interface/types.d.ts.map +1 -0
  76. package/dist/commonjs/main_thread/core_interface/types.js +2 -0
  77. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts +166 -108
  78. package/dist/commonjs/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  79. package/dist/commonjs/main_thread/init/media_source_content_initializer.js +1492 -919
  80. package/dist/commonjs/main_thread/init/utils/create_core_playback_observer.d.ts.map +1 -1
  81. package/dist/commonjs/main_thread/init/utils/create_core_playback_observer.js +2 -1
  82. package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts +1 -1
  83. package/dist/commonjs/main_thread/init/utils/update_manifest_codec_support.d.ts.map +1 -1
  84. package/dist/commonjs/main_thread/types.d.ts +537 -0
  85. package/dist/commonjs/main_thread/types.d.ts.map +1 -1
  86. package/dist/commonjs/manifest/utils.d.ts.map +1 -1
  87. package/dist/commonjs/manifest/utils.js +18 -4
  88. package/dist/commonjs/mse/worker_media_source_interface.d.ts +2 -2
  89. package/dist/commonjs/mse/worker_media_source_interface.d.ts.map +1 -1
  90. package/dist/commonjs/mse/worker_media_source_interface.js +12 -12
  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 +31 -27
  94. package/dist/commonjs/parsers/manifest/smooth/parse_C_nodes.d.ts +3 -2
  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 +16 -7
  97. package/dist/commonjs/parsers/manifest/smooth/parse_protection_node.d.ts +3 -2
  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 +37 -6
  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 +3 -2
  103. package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.d.ts.map +1 -1
  104. package/dist/commonjs/parsers/manifest/smooth/utils/reduceChildren.js +28 -5
  105. package/dist/{es2017/playback_observer/worker_playback_observer.d.ts → commonjs/playback_observer/core_playback_observer.d.ts} +8 -8
  106. package/dist/commonjs/playback_observer/core_playback_observer.d.ts.map +1 -0
  107. package/dist/commonjs/playback_observer/{worker_playback_observer.js → core_playback_observer.js} +13 -13
  108. package/dist/commonjs/transports/smooth/pipelines.d.ts.map +1 -1
  109. package/dist/commonjs/transports/smooth/pipelines.js +25 -3
  110. package/dist/commonjs/worker_entry_point.js +62 -2
  111. package/dist/es2017/__GENERATED_CODE/embedded_worker.d.ts.map +1 -1
  112. package/dist/es2017/__GENERATED_CODE/embedded_worker.js +1 -1
  113. package/dist/es2017/core/{main/common → entry}/FreezeResolver.d.ts +3 -3
  114. package/dist/es2017/core/entry/FreezeResolver.d.ts.map +1 -0
  115. package/dist/es2017/core/{main/common → entry}/FreezeResolver.js +3 -3
  116. package/dist/{commonjs/core/main/worker → es2017/core/entry}/content_preparer.d.ts +22 -15
  117. package/dist/es2017/core/entry/content_preparer.d.ts.map +1 -0
  118. package/dist/es2017/core/{main/worker → entry}/content_preparer.js +53 -55
  119. package/dist/{commonjs/core/main/common → es2017/core/entry}/content_time_boundaries_observer.d.ts +5 -5
  120. package/dist/es2017/core/entry/content_time_boundaries_observer.d.ts.map +1 -0
  121. package/dist/es2017/core/{main/common → entry}/content_time_boundaries_observer.js +6 -6
  122. package/dist/es2017/core/entry/core_entry.d.ts +36 -0
  123. package/dist/es2017/core/entry/core_entry.d.ts.map +1 -0
  124. package/dist/es2017/core/{main/worker/worker_main.js → entry/core_entry.js} +153 -114
  125. package/dist/es2017/core/{main/worker/worker_text_displayer_interface.d.ts → entry/core_text_displayer_interface.d.ts} +11 -11
  126. package/dist/es2017/core/entry/core_text_displayer_interface.d.ts.map +1 -0
  127. package/dist/es2017/core/{main/worker/worker_text_displayer_interface.js → entry/core_text_displayer_interface.js} +10 -10
  128. package/dist/es2017/core/{main/common → entry}/create_content_time_boundaries_observer.d.ts +6 -6
  129. package/dist/es2017/core/entry/create_content_time_boundaries_observer.d.ts.map +1 -0
  130. package/dist/es2017/core/{main/common → entry}/create_content_time_boundaries_observer.js +1 -1
  131. package/dist/es2017/core/{main/common → entry}/get_buffered_data_per_media_buffer.d.ts +4 -4
  132. package/dist/es2017/core/entry/get_buffered_data_per_media_buffer.d.ts.map +1 -0
  133. package/dist/es2017/core/{main/common → entry}/get_buffered_data_per_media_buffer.js +1 -1
  134. package/dist/{commonjs/core/main/common → es2017/core/entry}/get_thumbnail_data.d.ts +3 -3
  135. package/dist/es2017/core/entry/get_thumbnail_data.d.ts.map +1 -0
  136. package/dist/es2017/core/{main/common → entry}/get_thumbnail_data.js +2 -2
  137. package/dist/es2017/core/entry/index.d.ts +5 -0
  138. package/dist/es2017/core/entry/index.d.ts.map +1 -0
  139. package/dist/es2017/core/entry/index.js +2 -0
  140. package/dist/{commonjs/core/main/common → es2017/core/entry}/synchronize_sinks_on_observation.d.ts +2 -2
  141. package/dist/es2017/core/entry/synchronize_sinks_on_observation.d.ts.map +1 -0
  142. package/dist/es2017/core/{main/worker → entry}/track_choice_setter.d.ts +4 -4
  143. package/dist/es2017/core/entry/track_choice_setter.d.ts.map +1 -0
  144. package/dist/es2017/core/{main/worker → entry}/track_choice_setter.js +4 -4
  145. package/dist/es2017/core/entry/utils.d.ts +3 -0
  146. package/dist/es2017/core/entry/utils.d.ts.map +1 -0
  147. package/dist/es2017/core/entry/utils.js +8 -0
  148. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.d.ts.map +1 -1
  149. package/dist/es2017/core/stream/orchestrator/stream_orchestrator.js +18 -1
  150. package/dist/es2017/core/types.d.ts +519 -1
  151. package/dist/es2017/core/types.d.ts.map +1 -1
  152. package/dist/es2017/core/types.js +1 -0
  153. package/dist/es2017/experimental/features/local.d.ts.map +1 -1
  154. package/dist/es2017/experimental/features/local.js +7 -1
  155. package/dist/es2017/experimental/features/metaplaylist.d.ts.map +1 -1
  156. package/dist/es2017/experimental/features/metaplaylist.js +7 -1
  157. package/dist/es2017/experimental/features/multi_thread.d.ts.map +1 -1
  158. package/dist/es2017/experimental/features/multi_thread.js +6 -2
  159. package/dist/es2017/features/features_object.js +1 -1
  160. package/dist/es2017/features/list/dash.d.ts.map +1 -1
  161. package/dist/es2017/features/list/dash.js +7 -1
  162. package/dist/es2017/features/list/dash_wasm.d.ts.map +1 -1
  163. package/dist/es2017/features/list/dash_wasm.js +7 -1
  164. package/dist/es2017/features/list/media_source_main.d.ts.map +1 -1
  165. package/dist/es2017/features/list/media_source_main.js +7 -1
  166. package/dist/es2017/features/list/smooth.d.ts.map +1 -1
  167. package/dist/es2017/features/list/smooth.js +7 -1
  168. package/dist/es2017/features/types.d.ts +20 -4
  169. package/dist/es2017/features/types.d.ts.map +1 -1
  170. package/dist/es2017/main_thread/api/public_api.d.ts.map +1 -1
  171. package/dist/es2017/main_thread/api/public_api.js +40 -43
  172. package/dist/es2017/main_thread/core_interface/base.d.ts +13 -0
  173. package/dist/es2017/main_thread/core_interface/base.d.ts.map +1 -0
  174. package/dist/es2017/main_thread/core_interface/base.js +28 -0
  175. package/dist/es2017/main_thread/core_interface/monothread.d.ts +13 -0
  176. package/dist/es2017/main_thread/core_interface/monothread.d.ts.map +1 -0
  177. package/dist/es2017/main_thread/core_interface/monothread.js +32 -0
  178. package/dist/es2017/main_thread/core_interface/multithread.d.ts +25 -0
  179. package/dist/es2017/main_thread/core_interface/multithread.d.ts.map +1 -0
  180. package/dist/es2017/main_thread/core_interface/multithread.js +45 -0
  181. package/dist/es2017/main_thread/core_interface/types.d.ts +6 -0
  182. package/dist/es2017/main_thread/core_interface/types.d.ts.map +1 -0
  183. package/dist/es2017/main_thread/core_interface/types.js +1 -0
  184. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts +166 -108
  185. package/dist/es2017/main_thread/init/media_source_content_initializer.d.ts.map +1 -1
  186. package/dist/es2017/main_thread/init/media_source_content_initializer.js +1405 -760
  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 +2 -1
  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 +537 -0
  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 +16 -4
  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/parsers/manifest/smooth/create_parser.d.ts +1 -1
  199. package/dist/es2017/parsers/manifest/smooth/create_parser.d.ts.map +1 -1
  200. package/dist/es2017/parsers/manifest/smooth/create_parser.js +31 -27
  201. package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.d.ts +3 -2
  202. package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.d.ts.map +1 -1
  203. package/dist/es2017/parsers/manifest/smooth/parse_C_nodes.js +16 -7
  204. package/dist/es2017/parsers/manifest/smooth/parse_protection_node.d.ts +3 -2
  205. package/dist/es2017/parsers/manifest/smooth/parse_protection_node.d.ts.map +1 -1
  206. package/dist/es2017/parsers/manifest/smooth/parse_protection_node.js +15 -6
  207. package/dist/es2017/parsers/manifest/smooth/utils/parseBoolean.d.ts +1 -1
  208. package/dist/es2017/parsers/manifest/smooth/utils/parseBoolean.d.ts.map +1 -1
  209. package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.d.ts +3 -2
  210. package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.d.ts.map +1 -1
  211. package/dist/es2017/parsers/manifest/smooth/utils/reduceChildren.js +6 -5
  212. package/dist/{commonjs/playback_observer/worker_playback_observer.d.ts → es2017/playback_observer/core_playback_observer.d.ts} +8 -8
  213. package/dist/es2017/playback_observer/core_playback_observer.d.ts.map +1 -0
  214. package/dist/es2017/playback_observer/{worker_playback_observer.js → core_playback_observer.js} +2 -2
  215. package/dist/es2017/transports/smooth/pipelines.d.ts.map +1 -1
  216. package/dist/es2017/transports/smooth/pipelines.js +25 -3
  217. package/dist/es2017/worker_entry_point.js +62 -2
  218. package/dist/rx-player.js +21882 -19021
  219. package/dist/rx-player.min.js +20 -20
  220. package/dist/worker.js +8 -8
  221. package/package.json +1 -1
  222. package/src/README.md +88 -198
  223. package/src/__GENERATED_CODE/embedded_worker.ts +1 -1
  224. package/src/core/{main/common → entry}/FreezeResolver.ts +7 -7
  225. package/src/core/{main → entry}/README.md +1 -1
  226. package/src/core/{main/worker → entry}/content_preparer.ts +77 -76
  227. package/src/core/{main/common → entry}/content_time_boundaries_observer.ts +10 -10
  228. package/src/core/{main/worker/worker_main.ts → entry/core_entry.ts} +223 -149
  229. package/src/core/{main/worker/worker_text_displayer_interface.ts → entry/core_text_displayer_interface.ts} +26 -26
  230. package/src/core/{main/common → entry}/create_content_time_boundaries_observer.ts +7 -7
  231. package/src/core/{main/common → entry}/get_buffered_data_per_media_buffer.ts +6 -6
  232. package/src/core/{main/common → entry}/get_thumbnail_data.ts +5 -5
  233. package/src/core/entry/index.ts +4 -0
  234. package/src/core/{main/common → entry}/synchronize_sinks_on_observation.ts +2 -2
  235. package/src/core/{main/worker → entry}/track_choice_setter.ts +7 -7
  236. package/src/core/entry/utils.ts +11 -0
  237. package/src/core/stream/orchestrator/stream_orchestrator.ts +26 -1
  238. package/src/core/types.ts +631 -3
  239. package/src/experimental/features/__tests__/local.test.ts +11 -2
  240. package/src/experimental/features/__tests__/metaplaylist.test.ts +11 -2
  241. package/src/experimental/features/__tests__/multi_thread.test.ts +8 -3
  242. package/src/experimental/features/local.ts +7 -1
  243. package/src/experimental/features/metaplaylist.ts +7 -1
  244. package/src/experimental/features/multi_thread.ts +6 -2
  245. package/src/features/features_object.ts +1 -1
  246. package/src/features/list/__tests__/dash.test.ts +12 -3
  247. package/src/features/list/__tests__/smooth.test.ts +11 -2
  248. package/src/features/list/dash.ts +7 -1
  249. package/src/features/list/dash_wasm.ts +7 -1
  250. package/src/features/list/media_source_main.ts +7 -1
  251. package/src/features/list/smooth.ts +7 -1
  252. package/src/features/types.ts +23 -4
  253. package/src/main_thread/README.md +8 -0
  254. package/src/main_thread/api/public_api.ts +46 -48
  255. package/src/main_thread/core_interface/README.md +22 -0
  256. package/src/main_thread/core_interface/base.ts +36 -0
  257. package/src/main_thread/core_interface/monothread.ts +46 -0
  258. package/src/main_thread/core_interface/multithread.ts +49 -0
  259. package/src/main_thread/core_interface/types.ts +5 -0
  260. package/src/main_thread/init/media_source_content_initializer.ts +2034 -1152
  261. package/src/main_thread/init/utils/create_core_playback_observer.ts +2 -1
  262. package/src/main_thread/init/utils/update_manifest_codec_support.ts +1 -1
  263. package/src/main_thread/types.ts +610 -0
  264. package/src/manifest/utils.ts +20 -4
  265. package/src/mse/worker_media_source_interface.ts +35 -35
  266. package/src/parsers/manifest/smooth/create_parser.ts +40 -34
  267. package/src/parsers/manifest/smooth/parse_C_nodes.ts +19 -8
  268. package/src/parsers/manifest/smooth/parse_protection_node.ts +17 -9
  269. package/src/parsers/manifest/smooth/utils/parseBoolean.ts +1 -1
  270. package/src/parsers/manifest/smooth/utils/reduceChildren.ts +10 -7
  271. package/src/playback_observer/{worker_playback_observer.ts → core_playback_observer.ts} +13 -13
  272. package/src/transports/smooth/pipelines.ts +25 -5
  273. package/src/worker_entry_point.ts +71 -2
  274. package/dist/commonjs/core/main/common/FreezeResolver.d.ts.map +0 -1
  275. package/dist/commonjs/core/main/common/content_time_boundaries_observer.d.ts.map +0 -1
  276. package/dist/commonjs/core/main/common/create_content_time_boundaries_observer.d.ts.map +0 -1
  277. package/dist/commonjs/core/main/common/get_buffered_data_per_media_buffer.d.ts.map +0 -1
  278. package/dist/commonjs/core/main/common/get_thumbnail_data.d.ts.map +0 -1
  279. package/dist/commonjs/core/main/common/synchronize_sinks_on_observation.d.ts.map +0 -1
  280. package/dist/commonjs/core/main/worker/content_preparer.d.ts.map +0 -1
  281. package/dist/commonjs/core/main/worker/globals.d.ts +0 -14
  282. package/dist/commonjs/core/main/worker/globals.d.ts.map +0 -1
  283. package/dist/commonjs/core/main/worker/globals.js +0 -26
  284. package/dist/commonjs/core/main/worker/index.d.ts +0 -3
  285. package/dist/commonjs/core/main/worker/index.d.ts.map +0 -1
  286. package/dist/commonjs/core/main/worker/index.js +0 -4
  287. package/dist/commonjs/core/main/worker/send_message.d.ts +0 -4
  288. package/dist/commonjs/core/main/worker/send_message.d.ts.map +0 -1
  289. package/dist/commonjs/core/main/worker/send_message.js +0 -23
  290. package/dist/commonjs/core/main/worker/track_choice_setter.d.ts.map +0 -1
  291. package/dist/commonjs/core/main/worker/worker_main.d.ts +0 -2
  292. package/dist/commonjs/core/main/worker/worker_main.d.ts.map +0 -1
  293. package/dist/commonjs/core/main/worker/worker_text_displayer_interface.d.ts.map +0 -1
  294. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts +0 -308
  295. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.d.ts.map +0 -1
  296. package/dist/commonjs/main_thread/init/multi_thread_content_initializer.js +0 -1713
  297. package/dist/commonjs/main_thread/init/send_message.d.ts +0 -3
  298. package/dist/commonjs/main_thread/init/send_message.d.ts.map +0 -1
  299. package/dist/commonjs/main_thread/init/send_message.js +0 -13
  300. package/dist/commonjs/multithread_types.d.ts +0 -915
  301. package/dist/commonjs/multithread_types.d.ts.map +0 -1
  302. package/dist/commonjs/multithread_types.js +0 -7
  303. package/dist/commonjs/playback_observer/worker_playback_observer.d.ts.map +0 -1
  304. package/dist/es2017/core/main/common/FreezeResolver.d.ts.map +0 -1
  305. package/dist/es2017/core/main/common/content_time_boundaries_observer.d.ts.map +0 -1
  306. package/dist/es2017/core/main/common/create_content_time_boundaries_observer.d.ts.map +0 -1
  307. package/dist/es2017/core/main/common/get_buffered_data_per_media_buffer.d.ts.map +0 -1
  308. package/dist/es2017/core/main/common/get_thumbnail_data.d.ts.map +0 -1
  309. package/dist/es2017/core/main/common/synchronize_sinks_on_observation.d.ts.map +0 -1
  310. package/dist/es2017/core/main/worker/content_preparer.d.ts.map +0 -1
  311. package/dist/es2017/core/main/worker/globals.d.ts +0 -14
  312. package/dist/es2017/core/main/worker/globals.d.ts.map +0 -1
  313. package/dist/es2017/core/main/worker/globals.js +0 -18
  314. package/dist/es2017/core/main/worker/index.d.ts +0 -3
  315. package/dist/es2017/core/main/worker/index.d.ts.map +0 -1
  316. package/dist/es2017/core/main/worker/index.js +0 -2
  317. package/dist/es2017/core/main/worker/send_message.d.ts +0 -4
  318. package/dist/es2017/core/main/worker/send_message.d.ts.map +0 -1
  319. package/dist/es2017/core/main/worker/send_message.js +0 -19
  320. package/dist/es2017/core/main/worker/track_choice_setter.d.ts.map +0 -1
  321. package/dist/es2017/core/main/worker/worker_main.d.ts +0 -2
  322. package/dist/es2017/core/main/worker/worker_main.d.ts.map +0 -1
  323. package/dist/es2017/core/main/worker/worker_text_displayer_interface.d.ts.map +0 -1
  324. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts +0 -308
  325. package/dist/es2017/main_thread/init/multi_thread_content_initializer.d.ts.map +0 -1
  326. package/dist/es2017/main_thread/init/multi_thread_content_initializer.js +0 -1559
  327. package/dist/es2017/main_thread/init/send_message.d.ts +0 -3
  328. package/dist/es2017/main_thread/init/send_message.d.ts.map +0 -1
  329. package/dist/es2017/main_thread/init/send_message.js +0 -10
  330. package/dist/es2017/multithread_types.d.ts +0 -915
  331. package/dist/es2017/multithread_types.d.ts.map +0 -1
  332. package/dist/es2017/multithread_types.js +0 -6
  333. package/dist/es2017/playback_observer/worker_playback_observer.d.ts.map +0 -1
  334. package/src/core/main/worker/globals.ts +0 -38
  335. package/src/core/main/worker/index.ts +0 -2
  336. package/src/core/main/worker/send_message.ts +0 -28
  337. package/src/main_thread/init/multi_thread_content_initializer.ts +0 -2330
  338. package/src/main_thread/init/send_message.ts +0 -15
  339. package/src/multithread_types.ts +0 -1095
  340. /package/dist/commonjs/core/{main/common → entry}/synchronize_sinks_on_observation.js +0 -0
  341. /package/dist/es2017/core/{main/common → entry}/synchronize_sinks_on_observation.js +0 -0
@@ -1,150 +1,148 @@
1
- /**
2
- * Copyright 2015 CANAL+ Group
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
1
  import type { IMediaElement } from "../../compat/browser_compatibility_types";
18
- import isCodecSupported from "../../compat/is_codec_supported";
2
+ import getEmeApiImplementation from "../../compat/eme";
19
3
  import mayMediaElementFailOnUndecipherableData from "../../compat/may_media_element_fail_on_undecipherable_data";
20
4
  import shouldReloadMediaSourceOnDecipherabilityUpdate from "../../compat/should_reload_media_source_on_decipherability_update";
21
- import config from "../../config";
5
+ import type { ISegmentSinkMetrics } from "../../core/segment_sinks/segment_sinks_store";
22
6
  import type {
23
7
  IAdaptiveRepresentationSelectorArguments,
24
- IRepresentationEstimator,
25
- } from "../../core/adaptive";
26
- import AdaptiveRepresentationSelector from "../../core/adaptive";
27
- import CmcdDataBuilder from "../../core/cmcd";
8
+ IAdaptationChoice,
9
+ IResolutionInfo,
10
+ ICreateMediaSourceCoreMessage,
11
+ ISentError,
12
+ ICoreMessage,
13
+ ISentLogValue,
14
+ } from "../../core/types";
15
+ import { CoreMessageType } from "../../core/types";
28
16
  import {
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";
17
+ EncryptedMediaError,
18
+ MediaError,
19
+ NetworkError,
20
+ OtherError,
21
+ SourceBufferError,
22
+ } from "../../errors";
49
23
  import features from "../../features";
50
24
  import log from "../../log";
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";
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";
54
36
  import type {
55
37
  ICmcdOptions,
56
38
  IInitialManifest,
57
39
  IKeySystemOption,
58
40
  IPlayerError,
41
+ IRepresentationFilter,
59
42
  } from "../../public_types";
60
- import type { IThumbnailResponse, ITransportPipelines } from "../../transports";
61
- import areArraysOfNumbersEqual from "../../utils/are_arrays_of_numbers_equal";
43
+ import type { IThumbnailResponse, ITransportOptions } from "../../transports";
44
+ import arrayFind from "../../utils/array_find";
62
45
  import assert, { assertUnreachable } from "../../utils/assert";
63
- import createCancellablePromise from "../../utils/create_cancellable_promise";
46
+ import idGenerator from "../../utils/id_generator";
64
47
  import isNullOrUndefined from "../../utils/is_null_or_undefined";
65
- import noop from "../../utils/noop";
48
+ import type { IAcceptedLogValue } from "../../utils/logger";
66
49
  import objectAssign from "../../utils/object_assign";
67
50
  import type { IReadOnlySharedReference } from "../../utils/reference";
68
- import type { ISyncOrAsyncValue } from "../../utils/sync_or_async";
69
- import SyncOrAsync from "../../utils/sync_or_async";
51
+ import SharedReference from "../../utils/reference";
52
+ import { RequestError } from "../../utils/request";
70
53
  import type { CancellationSignal } from "../../utils/task_canceller";
71
- import TaskCanceller 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";
72
58
  import { ContentDecryptorState, getKeySystemConfiguration } from "../decrypt";
73
- import type { IProcessedProtectionData } from "../decrypt";
74
- import type ContentDecryptor from "../decrypt";
75
59
  import type { ITextDisplayer } from "../text_displayer";
60
+ import { MainThreadMessageType } from "../types";
76
61
  import type { ITextDisplayerOptions } from "./types";
77
62
  import { ContentInitializer } from "./types";
63
+ import type { ICorePlaybackObservation } from "./utils/create_core_playback_observer";
78
64
  import createCorePlaybackObserver from "./utils/create_core_playback_observer";
79
- import createMediaSource from "./utils/create_media_source";
65
+ import {
66
+ resetMediaElement,
67
+ disableRemotePlaybackOnManagedMediaSource,
68
+ } from "./utils/create_media_source";
80
69
  import type { IInitialTimeOptions } from "./utils/get_initial_time";
81
70
  import getInitialTime from "./utils/get_initial_time";
82
71
  import getLoadedReference from "./utils/get_loaded_reference";
83
72
  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";
86
73
  import RebufferingController from "./utils/rebuffering_controller";
87
- import StreamEventsEmitter from "./utils/stream_events_emitter";
74
+ import StreamEventsEmitter from "./utils/stream_events_emitter/stream_events_emitter";
88
75
  import listenToMediaError from "./utils/throw_on_media_error";
76
+ import { updateManifestCodecSupport } from "./utils/update_manifest_codec_support";
77
+
78
+ const generateContentId = idGenerator();
89
79
 
90
80
  /**
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
- *
99
81
  * @class MediaSourceContentInitializer
100
82
  */
101
83
  export default class MediaSourceContentInitializer extends ContentInitializer {
102
84
  /** Constructor settings associated to this `MediaSourceContentInitializer`. */
103
- private _initSettings: IInitializeArguments;
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;
104
107
  /**
105
108
  * `TaskCanceller` allowing to abort everything that the
106
109
  * `MediaSourceContentInitializer` is doing.
107
110
  */
108
111
  private _initCanceller: TaskCanceller;
109
- /** Interface allowing to fetch and refresh the Manifest. */
110
- private _manifestFetcher: ManifestFetcher;
111
- /**
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.
116
- */
117
- private _manifest: ISyncOrAsyncValue<IManifest> | null;
118
-
119
- private _cmcdDataBuilder: CmcdDataBuilder | null;
120
-
121
112
  /**
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.
113
+ * `TaskCanceller` allowing to abort and clean-up every task and resource
114
+ * linked to the current `MediaSource` instance.
131
115
  *
132
- * - If set to `"enabled"`, decryption capabilities are available, and
133
- * `value` points to the corresponding `ContentDecryptor`.
116
+ * It may be triggered either at content stop (and thus at the same time than
117
+ * the `_initCanceller`) or when reloading the content.
134
118
  */
135
- private _decryptionCapabilities:
136
- | {
137
- status: "uninitialized";
138
- value: null;
119
+ private _currentMediaSourceCanceller: TaskCanceller;
120
+
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;
139
132
  }
140
- | {
141
- status: "disabled";
142
- value: EncryptedMediaError;
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;
143
143
  }
144
- | {
145
- status: "enabled";
146
- value: ContentDecryptor;
147
- };
144
+ >;
145
+ };
148
146
 
149
147
  /**
150
148
  * Create a new `MediaSourceContentInitializer`, associated to the given
@@ -153,78 +151,173 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
153
151
  */
154
152
  constructor(settings: IInitializeArguments) {
155
153
  super();
156
- this._initSettings = settings;
154
+ this._settings = settings;
157
155
  this._initCanceller = new TaskCanceller();
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
- });
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;
168
165
  }
169
166
 
170
167
  /**
171
168
  * Perform non-destructive preparation steps, to prepare a future content.
172
- * For now, this mainly mean loading the Manifest document.
173
169
  */
174
170
  public prepare(): void {
175
- if (this._manifest !== null) {
171
+ if (this._currentContentInfo !== null || this._initCanceller.isUsed()) {
176
172
  return;
177
173
  }
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();
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
+ });
193
218
  this._initCanceller.signal.register(() => {
194
- this._manifestFetcher.dispose();
219
+ coreInterface.sendMessage({
220
+ type: MainThreadMessageType.StopContent,
221
+ contentId,
222
+ value: null,
223
+ });
224
+ });
225
+ if (this._initCanceller.isUsed()) {
226
+ return;
227
+ }
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);
285
+ 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);
195
289
  });
196
- }
197
290
 
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
291
+ // Also bind all `SharedReference` objects:
207
292
 
208
- /** Translate errors coming from the media element into RxPlayer errors. */
209
- listenToMediaError(
210
- mediaElement,
211
- (error: MediaError) => this._onFatalError(error),
293
+ const throttleVideoBitrate =
294
+ adaptiveOptions.throttlers.throttleBitrate.video ?? new SharedReference(Infinity);
295
+ bindNumberReferencesToCore(
296
+ coreInterface,
212
297
  this._initCanceller.signal,
298
+ [wantedBufferAhead, "wantedBufferAhead"],
299
+ [maxVideoBufferSize, "maxVideoBufferSize"],
300
+ [maxBufferAhead, "maxBufferAhead"],
301
+ [maxBufferBehind, "maxBufferBehind"],
302
+ [throttleVideoBitrate, "throttleVideoBitrate"],
213
303
  );
214
304
 
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);
305
+ const limitVideoResolution =
306
+ adaptiveOptions.throttlers.limitResolution.video ??
307
+ new SharedReference<IResolutionInfo>({
308
+ height: undefined,
309
+ width: undefined,
310
+ pixelRatio: 1,
227
311
  });
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
+ );
228
321
  }
229
322
 
230
323
  /**
@@ -235,589 +328,472 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
235
328
  * DASH's MPD) will be refreshed immediately.
236
329
  */
237
330
  public updateContentUrls(urls: string[] | undefined, refreshNow: boolean): void {
238
- this._manifestFetcher.updateContentUrls(urls, refreshNow);
239
- }
240
-
241
- /**
242
- * Stop content and free all resources linked to this
243
- * `MediaSourceContentInitializer`.
244
- */
245
- public dispose(): void {
246
- this._initCanceller.cancel();
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
+ });
247
339
  }
248
340
 
249
341
  /**
250
- * Callback called when an error interrupting playback arised.
251
- * @param {*} err
342
+ * @param {HTMLMediaElement} mediaElement
343
+ * @param {Object} playbackObserver
252
344
  */
253
- private _onFatalError(err: unknown) {
345
+ public start(
346
+ mediaElement: IMediaElement,
347
+ playbackObserver: IMediaElementPlaybackObserver,
348
+ ): void {
349
+ this.prepare(); // Load Manifest if not already done
254
350
  if (this._initCanceller.isUsed()) {
255
351
  return;
256
352
  }
257
- this._initCanceller.cancel();
258
- this.trigger("error", err);
259
- }
260
353
 
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(
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(
278
361
  mediaElement,
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 },
362
+ this._settings.textTrackOptions.textTrackElement,
387
363
  );
388
- });
389
- }
390
-
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
364
+ } else if (features.nativeTextDisplayer !== null) {
365
+ assert(this._hasTextBufferFeature());
366
+ textDisplayer = new features.nativeTextDisplayer(mediaElement);
367
+ } else {
368
+ assert(!this._hasTextBufferFeature());
416
369
  }
370
+ this._initCanceller.signal.register(() => {
371
+ textDisplayer?.stop();
372
+ });
417
373
 
418
- manifest.addEventListener(
419
- "manifestUpdate",
420
- (updates) => {
421
- this.trigger("manifestUpdate", updates);
422
- this._refreshManifestCodecSupport(manifest, mediaElement);
423
- },
424
- initCanceller.signal,
425
- );
426
-
427
- manifest.addEventListener(
428
- "decipherabilityUpdate",
429
- (elts) => {
430
- this.trigger("decipherabilityUpdate", elts);
431
- },
432
- initCanceller.signal,
433
- );
434
-
435
- manifest.addEventListener(
436
- "supportUpdate",
437
- () => {
438
- this.trigger("codecSupportUpdate", null);
439
- },
440
- initCanceller.signal,
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,
441
379
  );
442
380
 
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
- );
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);
453
386
 
454
- const cdnPrioritizer = new CdnPrioritizer(initCanceller.signal);
455
- const segmentQueueCreator = new SegmentQueueCreator(
456
- transport,
457
- cdnPrioritizer,
458
- this._cmcdDataBuilder,
459
- segmentRequestOptions,
387
+ const mediaSourceStatus = new SharedReference<MediaSourceInitializationStatus>(
388
+ MediaSourceInitializationStatus.Nothing,
460
389
  );
461
390
 
462
- this._refreshManifestCodecSupport(manifest, mediaElement);
463
- this.trigger("manifestReady", manifest);
464
- if (initCanceller.isUsed()) {
465
- return;
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;
466
402
  }
467
403
 
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,
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
+ }
482
417
  },
483
- initialMediaSourceCanceller,
418
+ { clearSignal: this._initCanceller.signal, emitCurrentValue: true },
484
419
  );
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,
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 },
503
428
  );
504
- }
505
429
 
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()) {
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.");
519
448
  return;
520
449
  }
521
- this.trigger("reloadingMediaSource", reloadOrder);
522
- if (initCanceller.isUsed()) {
450
+ if (
451
+ reloadingContentInfo === null ||
452
+ reloadingContentInfo.mediaSourceInfo === null
453
+ ) {
454
+ log.warn("Init", "Asked to reload when no MediaSource is active.");
523
455
  return;
524
456
  }
525
457
 
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,
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.");
490
+ return;
491
+ }
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
+
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
+ }
536
555
  },
537
- newCanceller,
556
+ { emitCurrentValue: true, clearSignal: this._initCanceller.signal },
538
557
  );
539
- })
540
- .catch((err) => {
541
- if (newCanceller.isUsed()) {
558
+ break;
559
+ }
560
+
561
+ case CoreMessageType.Warning:
562
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
542
563
  return;
543
564
  }
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
- }
565
+ this.trigger("warning", formatCoreError(msgData.value));
566
+ break;
627
567
 
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,
639
- );
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,
568
+ case CoreMessageType.Error:
569
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
570
+ return;
571
+ }
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,
653
581
  );
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);
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
+ );
696
598
  }
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
- },
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
+ });
780
640
  });
641
+ }
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;
781
652
  }
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
- });
792
-
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
- );
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
+ });
683
+ });
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
+ }
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;
805
783
 
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;
784
+ case CoreMessageType.NeedsBufferFlush: {
785
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
786
+ return;
787
+ }
815
788
  const lastObservation = playbackObserver.getReference().getValue();
816
789
  const currentTime = lastObservation.position.isAwaitingFuturePosition()
817
790
  ? lastObservation.position.getWanted()
818
791
  : mediaElement.currentTime;
819
- const relativeResumingPosition = payload?.relativeResumingPosition ?? 0;
820
- const canBeApproximateSeek = Boolean(payload?.relativePosHasBeenDefaulted);
792
+ const relativeResumingPosition = msgData.value?.relativeResumingPosition ?? 0;
793
+ const canBeApproximateSeek = Boolean(
794
+ msgData.value?.relativePosHasBeenDefaulted,
795
+ );
796
+ let wantedSeekingTime: number;
821
797
 
822
798
  if (relativeResumingPosition === 0 && canBeApproximateSeek) {
823
799
  // in case relativeResumingPosition is 0, we still perform
@@ -827,251 +803,886 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
827
803
  wantedSeekingTime = currentTime + relativeResumingPosition;
828
804
  }
829
805
  playbackObserver.setCurrentTime(wantedSeekingTime);
806
+ break;
807
+ }
830
808
 
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 },
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,
858
819
  );
859
- },
820
+ if (period !== undefined) {
821
+ this.trigger("activePeriodChanged", { period });
822
+ }
823
+ break;
824
+ }
860
825
 
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
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
+ });
872
859
  }
860
+ break;
861
+ }
873
862
 
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.
863
+ case CoreMessageType.RepresentationChanged: {
877
864
  if (
878
- manifest.isLastPeriodKnown &&
879
- value.period.id === manifest.periods[manifest.periods.length - 1].id
865
+ this._currentContentInfo?.contentId !== msgData.contentId ||
866
+ this._currentContentInfo.manifest === null
880
867
  ) {
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
- }
868
+ return;
890
869
  }
891
- },
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
+ });
903
+ }
904
+ break;
905
+ }
892
906
 
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
- },
907
+ case CoreMessageType.EncryptionDataEncountered:
908
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
909
+ return;
910
+ }
911
+ lastContentProtection.setValue(msgData.value);
912
+ break;
907
913
 
908
- lockedStream: (value) =>
909
- rebufferingController.onLockedStream(value.bufferType, value.period),
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
+ }
910
924
 
911
- adaptationChange: (value) => {
912
- self.trigger("adaptationChange", value);
913
- if (cancelSignal.isCancelled()) {
914
- return; // Previous call has stopped streams due to a side-effect
925
+ case CoreMessageType.ManifestUpdate: {
926
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
927
+ return;
928
+ }
929
+ const manifest = this._currentContentInfo?.manifest;
930
+ if (isNullOrUndefined(manifest)) {
931
+ log.error("Init", "Manifest update but no Manifest loaded");
932
+ return;
915
933
  }
916
- contentTimeBoundariesObserver.onAdaptationChange(
917
- value.type,
918
- value.period,
919
- value.adaptation,
934
+
935
+ replicateUpdatesOnManifestMetadata(
936
+ manifest,
937
+ msgData.value.manifest,
938
+ msgData.value.updates,
920
939
  );
921
- },
940
+ this._currentContentInfo?.streamEventsEmitter?.onManifestUpdate(manifest);
941
+
942
+ this._updateCodecSupport(manifest, mediaElement);
943
+ this.trigger("manifestUpdate", msgData.value.updates);
944
+ break;
945
+ }
922
946
 
923
- representationChange: (value) => {
924
- self.trigger("representationChange", value);
925
- if (cancelSignal.isCancelled()) {
926
- return; // Previous call has stopped streams due to a side-effect
947
+ case CoreMessageType.UpdatePlaybackRate:
948
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
949
+ return;
927
950
  }
928
- contentTimeBoundariesObserver.onRepresentationChange(value.type, value.period);
929
- },
951
+ playbackObserver.setPlaybackRate(msgData.value);
952
+ break;
930
953
 
931
- inbandEvent: (value) => self.trigger("inbandEvents", value),
954
+ case CoreMessageType.BitrateEstimateChange:
955
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
956
+ return;
957
+ }
958
+ this.trigger("bitrateEstimateChange", {
959
+ type: msgData.value.bufferType,
960
+ bitrate: msgData.value.bitrate,
961
+ });
962
+ break;
932
963
 
933
- warning: (value) => self.trigger("warning", value),
964
+ case CoreMessageType.InbandEvent:
965
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
966
+ return;
967
+ }
968
+ this.trigger("inbandEvents", msgData.value);
969
+ break;
934
970
 
935
- periodStreamReady: (value) => self.trigger("periodStreamReady", value),
971
+ case CoreMessageType.LockedStream: {
972
+ if (
973
+ this._currentContentInfo?.contentId !== msgData.contentId ||
974
+ this._currentContentInfo.manifest === null
975
+ ) {
976
+ return;
977
+ }
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
+ }
936
991
 
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
992
+ case CoreMessageType.PeriodStreamReady: {
993
+ if (
994
+ this._currentContentInfo?.contentId !== msgData.contentId ||
995
+ this._currentContentInfo.manifest === null
996
+ ) {
997
+ return;
941
998
  }
942
- self.trigger("periodStreamCleared", {
943
- type: value.type,
944
- periodId: value.period.id,
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,
945
1059
  });
946
- },
1060
+ break;
1061
+ }
947
1062
 
948
- bitrateEstimateChange: (value) => {
949
- self._cmcdDataBuilder?.updateThroughput(value.type, value.bitrate);
950
- self.trigger("bitrateEstimateChange", value);
951
- },
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
+ }
952
1076
 
953
- needsMediaSourceReload: (payload) => {
954
- reloadMediaSource(
955
- payload.timeOffset,
956
- payload.minimumPosition,
957
- payload.maximumPosition,
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,
958
1087
  );
959
- },
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
+ }
960
1102
 
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 });
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");
973
1109
  } 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);
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
+ });
984
1124
  }
985
1125
  }
986
- },
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
+ }
987
1175
 
988
- encryptionDataEncountered: (value) => {
989
- if (self._decryptionCapabilities.status === "disabled") {
990
- self._onFatalError(self._decryptionCapabilities.value);
1176
+ case CoreMessageType.StopTextDisplayer: {
1177
+ if (this._currentContentInfo?.contentId !== msgData.contentId) {
991
1178
  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",
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,
997
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) {
998
1242
  return;
999
1243
  }
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
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);
1004
1275
  }
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;
1005
1342
  }
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,
1006
1357
  },
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
+ };
1007
1364
 
1008
- error: (err) => self._onFatalError(err),
1009
- };
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.");
1010
1369
  }
1011
1370
 
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);
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
+ );
1037
1399
  }
1038
- if (maximumPosition !== undefined) {
1039
- position = Math.min(maximumPosition, position);
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");
1040
1480
  }
1041
- onReloadOrder({ position, autoPlay: !isPaused });
1481
+ });
1482
+
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);
1532
+ }
1533
+ }
1534
+
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;
1042
1675
  }
1043
- }
1044
1676
 
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 {
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
+ */
1069
1681
  const rebufferingController = new RebufferingController(
1070
1682
  playbackObserver,
1071
1683
  manifest,
1072
1684
  speed,
1073
1685
  );
1074
- // Bubble-up events
1075
1686
  rebufferingController.addEventListener("stalled", (evt) =>
1076
1687
  this.trigger("stalled", evt),
1077
1688
  );
@@ -1081,105 +1692,415 @@ export default class MediaSourceContentInitializer extends ContentInitializer {
1081
1692
  rebufferingController.addEventListener("warning", (err) =>
1082
1693
  this.trigger("warning", err),
1083
1694
  );
1084
- cancelSignal.register(() => rebufferingController.destroy());
1695
+ cancelSignal.register(() => {
1696
+ rebufferingController.destroy();
1697
+ });
1085
1698
  rebufferingController.start();
1086
- return rebufferingController;
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;
1087
1821
  }
1088
1822
 
1089
1823
  /**
1090
- * Evaluates a list of codecs to determine their support status.
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.
1091
1834
  *
1092
- * @param {Array} codecsToCheck - The list of codecs to check.
1093
- * @returns {Array} - The list of evaluated codecs with their support status updated.
1835
+ * @param {Object} parameters
1836
+ * @returns {boolean} - Returns `true` if all conditions where met for
1837
+ * playback start.
1094
1838
  */
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
- };
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(),
1139
1884
  });
1140
- return codecsSupportInfo;
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
+ },
1895
+ });
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;
1141
1912
  }
1142
1913
 
1143
1914
  /**
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
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.
1149
1920
  */
1150
- private _refreshManifestCodecSupport(
1151
- manifest: IManifest,
1921
+ private _onCreateMediaSourceMessage(
1922
+ msg: ICreateMediaSourceCoreMessage,
1152
1923
  mediaElement: IMediaElement,
1924
+ mediaSourceStatus: SharedReference<MediaSourceInitializationStatus>,
1925
+ coreInterface: CoreInterface,
1153
1926
  ): void {
1154
- const codecsToTest = manifest.getCodecsWithUnknownSupport();
1155
- const codecsSupportInfo = this.getCodecsSupportInfo(codecsToTest, mediaElement);
1156
- if (codecsSupportInfo.length > 0) {
1927
+ if (this._currentContentInfo?.contentId !== msg.contentId) {
1928
+ log.info("Init", "Ignoring MediaSource attachment due to wrong `contentId`");
1929
+ } else {
1930
+ const { mediaSourceId } = msg;
1157
1931
  try {
1158
- manifest.updateCodecSupport(codecsSupportInfo);
1159
- } catch (err) {
1160
- this._onFatalError(err);
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);
1161
2003
  }
1162
2004
  }
1163
2005
  }
1164
2006
  }
1165
2007
 
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;
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;
1179
2080
  }
1180
2081
 
1181
2082
  /** Arguments to give to the `InitializeOnMediaSource` function. */
1182
2083
  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;
1183
2104
  /** Options concerning the ABR logic. */
1184
2105
  adaptiveOptions: IAdaptiveRepresentationSelectorArguments;
1185
2106
  /** `true` if we should play when loaded. */
@@ -1216,6 +2137,14 @@ export interface IInitializeArguments {
1216
2137
  keySystems: IKeySystemOption[];
1217
2138
  /** `true` to play low-latency contents optimally. */
1218
2139
  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
+ };
1219
2148
  /** Settings linked to Manifest requests. */
1220
2149
  manifestRequestSettings: {
1221
2150
  /** Maximum number of time a request on error will be retried. */
@@ -1241,8 +2170,6 @@ export interface IInitializeArguments {
1241
2170
  */
1242
2171
  initialManifest: IInitialManifest | undefined;
1243
2172
  };
1244
- /** Logic linked Manifest and segment loading and parsing. */
1245
- transport: ITransportPipelines;
1246
2173
  /** Configuration for the segment requesting logic. */
1247
2174
  segmentRequestOptions: {
1248
2175
  lowLatencyMode: boolean;
@@ -1271,196 +2198,151 @@ export interface IInitializeArguments {
1271
2198
  url: string | undefined;
1272
2199
  }
1273
2200
 
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;
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 {
1286
2265
  /**
1287
- * Interface allowing to prioritize CDN between one another depending on past
1288
- * performances, content steering, etc.
2266
+ * The `MediaSource` is not attached to the `HTMLMediaElement` and shouldn't
2267
+ * be yet.
1289
2268
  */
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;
2269
+ Nothing,
2270
+ /**
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.
2280
+ */
2281
+ AttachNow,
2282
+ /** The `MediaSource` is attached to the `HTMLMediaElement`. */
2283
+ Attached,
1301
2284
  }
1302
2285
 
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
- });
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;
1354
2295
  }
1355
2296
 
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
- });
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
+ }
1392
2326
  }
1393
2327
 
1394
2328
  /**
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.
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 {*}
1404
2338
  */
1405
- type IReloadMediaSourceCallback = (reloadOrder: {
1406
- position: number;
1407
- autoPlay: boolean;
1408
- }) => void;
2339
+ function formatSentLogObject(arg: ISentLogValue): IAcceptedLogValue {
2340
+ if (typeof arg !== "object") {
2341
+ return arg;
2342
+ }
1409
2343
 
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);
2344
+ if (arg?.isSerializedError === true) {
2345
+ return formatCoreError(arg as ISentError);
1465
2346
  }
2347
+ return arg as Exclude<ISentLogValue, ISentError>;
1466
2348
  }