shaka-player 4.3.10 → 4.4.0

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 (316) hide show
  1. package/.github/workflows/release-please.yaml +7 -0
  2. package/AUTHORS +9 -0
  3. package/CHANGELOG.md +295 -200
  4. package/CONTRIBUTORS +8 -0
  5. package/LICENSE +32 -0
  6. package/README.md +63 -10
  7. package/demo/autoTemplate.txt +40 -5
  8. package/demo/close_button.js +11 -0
  9. package/demo/common/asset.js +13 -1
  10. package/demo/common/assets.js +53 -3
  11. package/demo/common/message_ids.js +25 -1
  12. package/demo/config.js +69 -10
  13. package/demo/customWarning.txt +2 -2
  14. package/demo/index.html +3 -1
  15. package/demo/locales/en.json +26 -2
  16. package/demo/locales/source.json +105 -9
  17. package/demo/main.js +52 -9
  18. package/demo/search.js +3 -1
  19. package/demo/service_worker.js +3 -0
  20. package/dist/controls.css +1 -1
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +124 -112
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +124 -112
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +46 -28
  27. package/dist/locales.js +28 -28
  28. package/dist/receiver.compiled.debug.js +52 -46
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +52 -46
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +676 -391
  33. package/dist/shaka-player.compiled.debug.d.ts +676 -391
  34. package/dist/shaka-player.compiled.debug.externs.js +1188 -616
  35. package/dist/shaka-player.compiled.debug.js +1045 -700
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1188 -616
  38. package/dist/shaka-player.compiled.js +1094 -933
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +677 -392
  41. package/dist/shaka-player.ui.debug.d.ts +677 -392
  42. package/dist/shaka-player.ui.debug.externs.js +1023 -451
  43. package/dist/shaka-player.ui.debug.js +1100 -739
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +1023 -451
  46. package/dist/shaka-player.ui.js +1244 -1071
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/tutorials/ad_monetization.md +15 -1
  49. package/docs/tutorials/application-level-redirects.md +1 -1
  50. package/docs/tutorials/config.md +9 -5
  51. package/docs/tutorials/drm-config.md +61 -0
  52. package/docs/tutorials/fairplay.md +19 -4
  53. package/docs/tutorials/license-server-auth.md +4 -4
  54. package/docs/tutorials/license-wrapping.md +2 -2
  55. package/docs/tutorials/ui-customization.md +7 -3
  56. package/docs/tutorials/ui.md +7 -7
  57. package/docs/tutorials/upgrade.md +3 -0
  58. package/externs/ima.js +123 -2
  59. package/externs/isoboxer.js +217 -0
  60. package/externs/mediakeys.js +18 -0
  61. package/externs/mux.js +1 -105
  62. package/externs/pictureinpicture.js +48 -0
  63. package/externs/shaka/ads.js +61 -1
  64. package/externs/shaka/cea.js +110 -0
  65. package/externs/shaka/codecs.js +32 -0
  66. package/externs/shaka/manifest.js +101 -1
  67. package/externs/shaka/manifest_parser.js +0 -10
  68. package/externs/shaka/net.js +36 -7
  69. package/externs/shaka/offline.js +8 -1
  70. package/externs/shaka/player.js +220 -21
  71. package/externs/shaka/text.js +1 -382
  72. package/externs/shaka/transmuxer.js +59 -0
  73. package/karma.conf.js +13 -0
  74. package/lib/abr/simple_abr_manager.js +6 -3
  75. package/lib/ads/ad_manager.js +60 -2
  76. package/lib/ads/client_side_ad.js +86 -0
  77. package/lib/ads/client_side_ad_manager.js +79 -3
  78. package/lib/ads/server_side_ad.js +86 -0
  79. package/lib/ads/server_side_ad_manager.js +13 -0
  80. package/lib/cast/cast_utils.js +1 -0
  81. package/lib/cea/cea608_data_channel.js +24 -14
  82. package/lib/cea/cea608_memory.js +5 -2
  83. package/lib/cea/cea708_service.js +8 -9
  84. package/lib/cea/cea708_window.js +1 -2
  85. package/lib/cea/cea_decoder.js +23 -6
  86. package/lib/cea/cea_utils.js +25 -2
  87. package/lib/cea/dummy_caption_decoder.js +21 -0
  88. package/lib/cea/dummy_cea_parser.js +1 -3
  89. package/lib/cea/mp4_cea_parser.js +15 -10
  90. package/lib/cea/ts_cea_parser.js +69 -0
  91. package/lib/dash/content_protection.js +32 -2
  92. package/lib/dash/dash_parser.js +93 -31
  93. package/lib/dash/mpd_utils.js +6 -25
  94. package/lib/dash/segment_base.js +12 -14
  95. package/lib/dash/segment_list.js +10 -6
  96. package/lib/dash/segment_template.js +349 -32
  97. package/lib/dependencies/all.js +8 -0
  98. package/lib/hls/hls_classes.js +33 -8
  99. package/lib/hls/hls_parser.js +805 -191
  100. package/lib/hls/manifest_text_parser.js +4 -14
  101. package/lib/media/adaptation_set_criteria.js +43 -3
  102. package/lib/media/closed_caption_parser.js +74 -12
  103. package/lib/media/drm_engine.js +189 -49
  104. package/lib/media/gap_jumping_controller.js +1 -1
  105. package/lib/media/manifest_parser.js +34 -0
  106. package/lib/media/media_source_capabilities.js +10 -0
  107. package/lib/media/media_source_engine.js +475 -231
  108. package/lib/media/playhead.js +1 -1
  109. package/lib/media/presentation_timeline.js +62 -0
  110. package/lib/media/segment_index.js +50 -14
  111. package/lib/media/segment_prefetch.js +277 -0
  112. package/lib/media/segment_reference.js +70 -4
  113. package/lib/media/streaming_engine.js +258 -81
  114. package/lib/media/video_wrapper.js +15 -7
  115. package/lib/mss/content_protection.js +353 -0
  116. package/lib/mss/mss_parser.js +1081 -0
  117. package/lib/net/networking_engine.js +60 -10
  118. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  119. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  120. package/lib/offline/manifest_converter.js +6 -0
  121. package/lib/offline/storage.js +2 -4
  122. package/lib/player.js +309 -105
  123. package/lib/polyfill/media_capabilities.js +43 -3
  124. package/lib/polyfill/patchedmediakeys_apple.js +5 -0
  125. package/lib/polyfill/patchedmediakeys_nop.js +5 -0
  126. package/lib/polyfill/patchedmediakeys_webkit.js +5 -0
  127. package/lib/text/cue.js +208 -130
  128. package/lib/text/lrc_text_parser.js +2 -2
  129. package/lib/text/mp4_vtt_parser.js +2 -2
  130. package/lib/text/sbv_text_parser.js +1 -1
  131. package/lib/text/simple_text_displayer.js +2 -1
  132. package/lib/text/srt_text_parser.js +9 -1
  133. package/lib/text/ssa_text_parser.js +2 -2
  134. package/lib/text/text_engine.js +3 -21
  135. package/lib/text/text_utils.js +71 -20
  136. package/lib/text/ui_text_displayer.js +14 -14
  137. package/lib/text/vtt_text_parser.js +22 -14
  138. package/lib/transmuxer/aac_transmuxer.js +224 -0
  139. package/lib/transmuxer/ac3.js +142 -0
  140. package/lib/transmuxer/ac3_transmuxer.js +212 -0
  141. package/lib/transmuxer/adts.js +232 -0
  142. package/lib/transmuxer/ec3.js +103 -0
  143. package/lib/transmuxer/ec3_transmuxer.js +212 -0
  144. package/lib/transmuxer/h264.js +324 -0
  145. package/lib/transmuxer/mp3_transmuxer.js +204 -0
  146. package/lib/transmuxer/mpeg_audio.js +196 -0
  147. package/lib/transmuxer/mpeg_ts_transmuxer.js +182 -0
  148. package/lib/transmuxer/mss_transmuxer.js +389 -0
  149. package/lib/{media/transmuxer.js → transmuxer/muxjs_transmuxer.js} +34 -80
  150. package/lib/transmuxer/transmuxer_engine.js +161 -0
  151. package/lib/transmuxer/ts_transmuxer.js +738 -0
  152. package/lib/util/cmcd_manager.js +131 -71
  153. package/lib/util/error.js +62 -26
  154. package/lib/util/exp_golomb.js +220 -0
  155. package/lib/util/fairplay_utils.js +64 -13
  156. package/lib/util/id3_utils.js +56 -3
  157. package/lib/util/language_utils.js +11 -8
  158. package/lib/util/manifest_parser_utils.js +3 -0
  159. package/lib/util/mime_utils.js +44 -7
  160. package/lib/util/mp4_box_parsers.js +233 -5
  161. package/lib/util/mp4_generator.js +1381 -0
  162. package/lib/util/periods.js +40 -34
  163. package/lib/util/platform.js +17 -3
  164. package/lib/util/player_configuration.js +38 -1
  165. package/lib/util/stream_utils.js +138 -27
  166. package/lib/util/text_parser.js +1 -1
  167. package/lib/util/ts_parser.js +470 -47
  168. package/lib/util/xml_utils.js +16 -3
  169. package/maintained-branches.md +2 -2
  170. package/package.json +3 -1
  171. package/roadmap.md +21 -6
  172. package/shaka-player.uncompiled.js +18 -0
  173. package/test/abr/simple_abr_manager_unit.js +32 -4
  174. package/test/ads/ad_manager_unit.js +18 -2
  175. package/test/cast/cast_utils_unit.js +7 -2
  176. package/test/cea/cea608_memory_unit.js +24 -0
  177. package/test/cea/cea_decoder_unit.js +33 -0
  178. package/test/dash/dash_parser_content_protection_unit.js +93 -3
  179. package/test/dash/dash_parser_live_unit.js +56 -13
  180. package/test/dash/dash_parser_manifest_unit.js +70 -2
  181. package/test/dash/dash_parser_segment_base_unit.js +14 -9
  182. package/test/dash/dash_parser_segment_template_unit.js +286 -6
  183. package/test/demo/demo_unit.js +5 -1
  184. package/test/hls/hls_live_unit.js +188 -28
  185. package/test/hls/hls_parser_unit.js +250 -109
  186. package/test/hls/manifest_text_parser_unit.js +18 -13
  187. package/test/media/adaptation_set_criteria_unit.js +46 -14
  188. package/test/media/adaptation_set_unit.js +2 -0
  189. package/test/media/drm_engine_integration.js +11 -8
  190. package/test/media/drm_engine_unit.js +220 -7
  191. package/test/media/media_source_engine_integration.js +122 -38
  192. package/test/media/media_source_engine_unit.js +133 -153
  193. package/test/media/playhead_unit.js +3 -0
  194. package/test/media/presentation_timeline_unit.js +51 -0
  195. package/test/media/segment_index_unit.js +52 -0
  196. package/test/media/segment_prefetch_unit.js +243 -0
  197. package/test/media/streaming_engine_integration.js +9 -3
  198. package/test/media/streaming_engine_unit.js +440 -59
  199. package/test/mss/mss_parser_content_protection_unit.js +49 -0
  200. package/test/mss/mss_parser_unit.js +472 -0
  201. package/test/mss/mss_player_integration.js +123 -0
  202. package/test/offline/manifest_convert_unit.js +9 -0
  203. package/test/offline/storage_integration.js +6 -1
  204. package/test/player_unit.js +386 -133
  205. package/test/polyfill/media_capabilities_unit.js +18 -0
  206. package/test/test/assets/hls-raw-aac/fileSequence0.aac +0 -0
  207. package/test/test/assets/hls-raw-aac/fileSequence1.aac +0 -0
  208. package/test/test/assets/hls-raw-aac/fileSequence2.aac +0 -0
  209. package/test/test/assets/hls-raw-aac/fileSequence3.aac +0 -0
  210. package/test/test/assets/hls-raw-aac/manifest.m3u8 +4 -0
  211. package/test/test/assets/hls-raw-aac/stream.m4a.m3u8 +14 -0
  212. package/test/test/assets/hls-raw-ac3/fileSequence0.ac3 +0 -0
  213. package/test/test/assets/hls-raw-ac3/fileSequence1.ac3 +0 -0
  214. package/test/test/assets/hls-raw-ac3/fileSequence2.ac3 +0 -0
  215. package/test/test/assets/hls-raw-ac3/fileSequence3.ac3 +0 -0
  216. package/test/test/assets/hls-raw-ac3/fileSequence4.ac3 +0 -0
  217. package/test/test/assets/hls-raw-ac3/fileSequence5.ac3 +0 -0
  218. package/test/test/assets/hls-raw-ac3/fileSequence6.ac3 +0 -0
  219. package/test/test/assets/hls-raw-ac3/fileSequence7.ac3 +0 -0
  220. package/test/test/assets/hls-raw-ac3/prog_index.m3u8 +31 -0
  221. package/test/test/assets/hls-raw-ec3/fileSequence0.ec3 +0 -0
  222. package/test/test/assets/hls-raw-ec3/fileSequence1.ec3 +0 -0
  223. package/test/test/assets/hls-raw-ec3/fileSequence2.ec3 +0 -0
  224. package/test/test/assets/hls-raw-ec3/fileSequence3.ec3 +0 -0
  225. package/test/test/assets/hls-raw-ec3/fileSequence4.ec3 +0 -0
  226. package/test/test/assets/hls-raw-ec3/fileSequence5.ec3 +0 -0
  227. package/test/test/assets/hls-raw-ec3/fileSequence6.ec3 +0 -0
  228. package/test/test/assets/hls-raw-ec3/fileSequence7.ec3 +0 -0
  229. package/test/test/assets/hls-raw-ec3/prog_index.m3u8 +31 -0
  230. package/test/test/assets/hls-raw-mp3/chunklist_w905519204.m3u8 +13 -0
  231. package/test/test/assets/hls-raw-mp3/media_w905519204_0.mp3 +0 -0
  232. package/test/test/assets/hls-raw-mp3/media_w905519204_1.mp3 +0 -0
  233. package/test/test/assets/hls-raw-mp3/media_w905519204_2.mp3 +0 -0
  234. package/test/test/assets/hls-raw-mp3/media_w905519204_3.mp3 +0 -0
  235. package/test/test/assets/hls-raw-mp3/playlist.m3u8 +4 -0
  236. package/test/test/assets/hls-ts-aac/playlist.m3u8 +23 -0
  237. package/test/test/assets/hls-ts-aac/segment_0.ts +0 -0
  238. package/test/test/assets/hls-ts-aac/segment_1.ts +0 -0
  239. package/test/test/assets/hls-ts-aac/segment_2.ts +0 -0
  240. package/test/test/assets/hls-ts-aac/segment_3.ts +0 -0
  241. package/test/test/assets/hls-ts-aac/segment_4.ts +0 -0
  242. package/test/test/assets/hls-ts-aac/segment_5.ts +0 -0
  243. package/test/test/assets/hls-ts-aac/segment_6.ts +0 -0
  244. package/test/test/assets/hls-ts-aac/segment_7.ts +0 -0
  245. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0000.ts +0 -0
  246. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0001.ts +0 -0
  247. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0002.ts +0 -0
  248. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0003.ts +0 -0
  249. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0004.ts +0 -0
  250. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0005.ts +0 -0
  251. package/test/test/assets/hls-ts-ac3/prog_index.m3u8 +19 -0
  252. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0000.ts +0 -0
  253. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0001.ts +0 -0
  254. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0002.ts +0 -0
  255. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0003.ts +0 -0
  256. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0004.ts +0 -0
  257. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0005.ts +0 -0
  258. package/test/test/assets/hls-ts-ec3/prog_index.m3u8 +19 -0
  259. package/test/test/assets/hls-ts-h264/fileSequence0.ts +0 -0
  260. package/test/test/assets/hls-ts-h264/fileSequence1.ts +0 -0
  261. package/test/test/assets/hls-ts-h264/fileSequence2.ts +0 -0
  262. package/test/test/assets/hls-ts-h264/fileSequence3.ts +0 -0
  263. package/test/test/assets/hls-ts-h264/fileSequence4.ts +0 -0
  264. package/test/test/assets/hls-ts-h264/fileSequence5.ts +0 -0
  265. package/test/test/assets/hls-ts-h264/prog_index.m3u8 +25 -0
  266. package/test/test/assets/hls-ts-mp3/manifest.m3u8 +13 -0
  267. package/test/test/assets/hls-ts-mp3/media_w239891843_0.ts +0 -0
  268. package/test/test/assets/hls-ts-mp3/media_w239891843_1.ts +0 -0
  269. package/test/test/assets/hls-ts-mp3/media_w239891843_2.ts +0 -0
  270. package/test/test/assets/hls-ts-mp3/media_w239891843_3.ts +0 -0
  271. package/test/test/assets/hls-ts-muxed-aac-h264/chunk.m3u8 +17 -0
  272. package/test/test/assets/hls-ts-muxed-aac-h264/n_0_0_0.ts +0 -0
  273. package/test/test/assets/hls-ts-muxed-aac-h264/n_1_0_0.ts +0 -0
  274. package/test/test/assets/hls-ts-muxed-aac-h264/n_2_0_0.ts +0 -0
  275. package/test/test/assets/hls-ts-muxed-aac-h264/n_3_0_0.ts +0 -0
  276. package/test/test/assets/hls-ts-muxed-aac-h264/n_4_0_0.ts +0 -0
  277. package/test/test/assets/hls-ts-muxed-aac-h264/n_5_0_0.ts +0 -0
  278. package/test/test/assets/hls-ts-muxed-aac-h264/playlist.m3u8 +4 -0
  279. package/test/test/boot.js +7 -2
  280. package/test/test/util/fake_ad.js +99 -0
  281. package/test/test/util/fake_ad_manager.js +7 -1
  282. package/test/test/util/fake_networking_engine.js +41 -14
  283. package/test/test/util/fake_segment_prefetch.js +81 -0
  284. package/test/test/util/manifest_generator.js +19 -5
  285. package/test/test/util/mss_parser_util.js +115 -0
  286. package/test/test/util/offline_utils.js +1 -0
  287. package/test/test/util/simple_fakes.js +12 -3
  288. package/test/test/util/streaming_engine_util.js +14 -4
  289. package/test/test/util/ttml_utils.js +2 -2
  290. package/test/text/cue_integration.js +1 -1
  291. package/test/text/srt_text_parser_unit.js +78 -0
  292. package/test/text/text_engine_unit.js +17 -8
  293. package/test/text/ui_text_displayer_unit.js +4 -4
  294. package/test/text/vtt_text_parser_unit.js +59 -40
  295. package/test/text/web_vtt_generator_unit.js +9 -3
  296. package/test/{media/transmuxer_integration.js → transmuxer/muxjs_transmuxer_integration.js} +30 -33
  297. package/test/transmuxer/transmuxer_engine_integration.js +60 -0
  298. package/test/transmuxer/transmuxer_integration.js +296 -0
  299. package/test/util/cmcd_manager_unit.js +162 -13
  300. package/test/util/language_utils_unit.js +9 -2
  301. package/test/util/mp4_box_parsers_unit.js +39 -4
  302. package/test/util/periods_unit.js +34 -26
  303. package/test/util/stream_utils_unit.js +60 -107
  304. package/ui/controls.js +19 -1
  305. package/ui/controls.less +1 -0
  306. package/ui/externs/ui.js +11 -1
  307. package/ui/language_utils.js +15 -0
  308. package/ui/less/thumbnails.less +26 -0
  309. package/ui/locales/oc.json +42 -0
  310. package/ui/pip_button.js +83 -2
  311. package/ui/range_element.js +15 -0
  312. package/ui/resolution_selection.js +9 -2
  313. package/ui/seek_bar.js +223 -0
  314. package/ui/ui.js +5 -0
  315. package/lib/cea/i_caption_decoder.js +0 -51
  316. package/lib/cea/i_cea_parser.js +0 -68
@@ -12,10 +12,11 @@ goog.require('shaka.media.Capabilities');
12
12
  goog.require('shaka.media.ContentWorkarounds');
13
13
  goog.require('shaka.media.ClosedCaptionParser');
14
14
  goog.require('shaka.media.IClosedCaptionParser');
15
+ goog.require('shaka.media.ManifestParser');
15
16
  goog.require('shaka.media.SegmentReference');
16
17
  goog.require('shaka.media.TimeRangesUtils');
17
- goog.require('shaka.media.Transmuxer');
18
18
  goog.require('shaka.text.TextEngine');
19
+ goog.require('shaka.transmuxer.TransmuxerEngine');
19
20
  goog.require('shaka.util.BufferUtils');
20
21
  goog.require('shaka.util.Destroyer');
21
22
  goog.require('shaka.util.Error');
@@ -25,6 +26,8 @@ goog.require('shaka.util.IDestroyable');
25
26
  goog.require('shaka.util.Id3Utils');
26
27
  goog.require('shaka.util.ManifestParserUtils');
27
28
  goog.require('shaka.util.MimeUtils');
29
+ goog.require('shaka.util.Mp4BoxParsers');
30
+ goog.require('shaka.util.Mp4Parser');
28
31
  goog.require('shaka.util.Platform');
29
32
  goog.require('shaka.util.PublicPromise');
30
33
  goog.require('shaka.util.TsParser');
@@ -100,7 +103,7 @@ shaka.media.MediaSourceEngine = class {
100
103
  /** @private {shaka.util.EventManager} */
101
104
  this.eventManager_ = new shaka.util.EventManager();
102
105
 
103
- /** @private {!Object.<string, !shaka.media.Transmuxer>} */
106
+ /** @private {!Object.<string, !shaka.extern.Transmuxer>} */
104
107
  this.transmuxers_ = {};
105
108
 
106
109
  /** @private {?shaka.media.IClosedCaptionParser} */
@@ -115,12 +118,24 @@ shaka.media.MediaSourceEngine = class {
115
118
  /** @private {MediaSource} */
116
119
  this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
117
120
 
121
+ /** @private {boolean} */
122
+ this.reloadingMediaSource_ = false;
123
+
118
124
  /** @type {!shaka.util.Destroyer} */
119
125
  this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_());
120
126
 
121
127
  /** @private {boolean} */
122
128
  this.sequenceMode_ = false;
123
129
 
130
+ /** @private {string} */
131
+ this.manifestType_ = shaka.media.ManifestParser.UNKNOWN;
132
+
133
+ /** @private {boolean} */
134
+ this.ignoreManifestTimestampsInSegmentsMode_ = false;
135
+
136
+ /** @private {boolean} */
137
+ this.attemptTimestampOffsetCalculation_ = false;
138
+
124
139
  /** @private {!shaka.util.PublicPromise.<number>} */
125
140
  this.textSequenceModeOffset_ = new shaka.util.PublicPromise();
126
141
  }
@@ -174,12 +189,15 @@ shaka.media.MediaSourceEngine = class {
174
189
  * @return {boolean}
175
190
  */
176
191
  static isStreamSupported(stream) {
177
- const fullMimeType = shaka.util.MimeUtils.getFullType(
192
+ const MimeUtils = shaka.util.MimeUtils;
193
+ const fullMimeType = MimeUtils.getFullType(stream.mimeType, stream.codecs);
194
+ const extendedMimeType = MimeUtils.getExtendedType(stream);
195
+ const fullMimeTypeWithAllCodecs = MimeUtils.getFullTypeWithAllCodecs(
178
196
  stream.mimeType, stream.codecs);
179
- const extendedMimeType = shaka.util.MimeUtils.getExtendedType(stream);
197
+ const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
180
198
  return shaka.text.TextEngine.isTypeSupported(fullMimeType) ||
181
199
  shaka.media.Capabilities.isTypeSupported(extendedMimeType) ||
182
- shaka.media.Transmuxer.isSupported(fullMimeType, stream.type);
200
+ TransmuxerEngine.isSupported(fullMimeTypeWithAllCodecs, stream.type);
183
201
  }
184
202
 
185
203
  /**
@@ -227,7 +245,7 @@ shaka.media.MediaSourceEngine = class {
227
245
  'application/ttml+xml',
228
246
  'application/mp4; codecs="stpp"',
229
247
  // Containerless types
230
- ...shaka.media.MediaSourceEngine.RAW_FORMATS,
248
+ ...shaka.util.MimeUtils.RAW_FORMATS,
231
249
  ];
232
250
 
233
251
  const support = {};
@@ -238,7 +256,7 @@ shaka.media.MediaSourceEngine = class {
238
256
  support[type] = true;
239
257
  } else {
240
258
  support[type] = shaka.media.Capabilities.isTypeSupported(type) ||
241
- shaka.media.Transmuxer.isSupported(type);
259
+ shaka.transmuxer.TransmuxerEngine.isSupported(type);
242
260
  }
243
261
  } else {
244
262
  support[type] = shaka.util.Platform.supportsMediaType(type);
@@ -344,70 +362,112 @@ shaka.media.MediaSourceEngine = class {
344
362
  * shaka.extern.Stream>} streamsByType
345
363
  * A map of content types to streams. All streams must be supported
346
364
  * according to MediaSourceEngine.isStreamSupported.
347
- * @param {boolean} forceTransmux
348
- * If true, this will transmux AAC and TS content even if it is natively
349
- * supported.
350
365
  * @param {boolean=} sequenceMode
351
366
  * If true, the media segments are appended to the SourceBuffer in strict
352
367
  * sequence.
368
+ * @param {string=} manifestType
369
+ * Indicates the type of the manifest.
370
+ * @param {boolean=} ignoreManifestTimestampsInSegmentsMode
371
+ * If true, don't adjust the timestamp offset to account for manifest
372
+ * segment durations being out of sync with segment durations. In other
373
+ * words, assume that there are no gaps in the segments when appending
374
+ * to the SourceBuffer, even if the manifest and segment times disagree.
375
+ * Indicates if the manifest has text streams.
353
376
  *
354
377
  * @return {!Promise}
355
378
  */
356
- async init(streamsByType, forceTransmux, sequenceMode=false) {
357
- const ContentType = shaka.util.ManifestParserUtils.ContentType;
358
-
379
+ async init(streamsByType, sequenceMode=false,
380
+ manifestType=shaka.media.ManifestParser.UNKNOWN,
381
+ ignoreManifestTimestampsInSegmentsMode=false) {
359
382
  await this.mediaSourceOpen_;
360
383
 
361
384
  this.sequenceMode_ = sequenceMode;
385
+ this.manifestType_ = manifestType;
386
+ this.ignoreManifestTimestampsInSegmentsMode_ =
387
+ ignoreManifestTimestampsInSegmentsMode;
388
+
389
+ this.attemptTimestampOffsetCalculation_ = !this.sequenceMode_ &&
390
+ this.manifestType_ == shaka.media.ManifestParser.HLS &&
391
+ !this.ignoreManifestTimestampsInSegmentsMode_;
362
392
 
363
393
  for (const contentType of streamsByType.keys()) {
364
394
  const stream = streamsByType.get(contentType);
365
- goog.asserts.assert(
366
- shaka.media.MediaSourceEngine.isStreamSupported(stream),
367
- 'Type negotiation should happen before MediaSourceEngine.init!');
395
+ this.initSourceBuffer_(contentType, stream);
396
+ this.queues_[contentType] = [];
397
+ }
398
+ }
368
399
 
369
- let mimeType = shaka.util.MimeUtils.getFullType(
370
- stream.mimeType, stream.codecs);
371
- if (contentType == ContentType.TEXT) {
372
- this.reinitText(mimeType, sequenceMode, stream.external);
373
- } else {
374
- if ((forceTransmux ||
375
- !shaka.media.Capabilities.isTypeSupported(mimeType)) &&
376
- shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
377
- this.transmuxers_[contentType] =
378
- new shaka.media.Transmuxer(mimeType);
400
+ /**
401
+ * Initialize a specific SourceBuffer.
402
+ *
403
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
404
+ * @param {shaka.extern.Stream} stream
405
+ * @private
406
+ */
407
+ initSourceBuffer_(contentType, stream) {
408
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
409
+
410
+ goog.asserts.assert(
411
+ shaka.media.MediaSourceEngine.isStreamSupported(stream),
412
+ 'Type negotiation should happen before MediaSourceEngine.init!');
413
+
414
+ let mimeType = shaka.util.MimeUtils.getFullType(
415
+ stream.mimeType, stream.codecs);
416
+ if (contentType == ContentType.TEXT) {
417
+ this.reinitText(mimeType, this.sequenceMode_, stream.external);
418
+ } else {
419
+ let needTransmux = this.config_.forceTransmux;
420
+ if (!shaka.media.Capabilities.isTypeSupported(mimeType) ||
421
+ (!this.sequenceMode_ &&
422
+ shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType))) {
423
+ needTransmux = true;
424
+ }
425
+ const mimeTypeWithAllCodecs =
426
+ shaka.util.MimeUtils.getFullTypeWithAllCodecs(
427
+ stream.mimeType, stream.codecs);
428
+ const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
429
+ if (needTransmux) {
430
+ const transmuxerPlugin =
431
+ TransmuxerEngine.findTransmuxer(mimeTypeWithAllCodecs);
432
+ if (transmuxerPlugin) {
433
+ const transmuxer = transmuxerPlugin();
434
+ this.transmuxers_[contentType] = transmuxer;
379
435
  mimeType =
380
- shaka.media.Transmuxer.convertCodecs(contentType, mimeType);
436
+ transmuxer.convertCodecs(contentType, mimeTypeWithAllCodecs);
381
437
  }
382
- const type = mimeType + this.config_.sourceBufferExtraFeatures;
438
+ }
439
+ const type = mimeType + this.config_.sourceBufferExtraFeatures;
383
440
 
384
- this.destroyer_.ensureNotDestroyed();
441
+ this.destroyer_.ensureNotDestroyed();
385
442
 
386
- let sourceBuffer;
443
+ let sourceBuffer;
387
444
 
388
- try {
389
- sourceBuffer = this.mediaSource_.addSourceBuffer(type);
390
- } catch (exception) {
391
- throw new shaka.util.Error(
392
- shaka.util.Error.Severity.CRITICAL,
393
- shaka.util.Error.Category.MEDIA,
394
- shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
395
- exception,
396
- 'The mediaSource_ status was' + this.mediaSource_.readyState +
397
- 'expected \'open\'');
398
- }
445
+ try {
446
+ sourceBuffer = this.mediaSource_.addSourceBuffer(type);
447
+ } catch (exception) {
448
+ throw new shaka.util.Error(
449
+ shaka.util.Error.Severity.CRITICAL,
450
+ shaka.util.Error.Category.MEDIA,
451
+ shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
452
+ exception,
453
+ 'The mediaSource_ status was' + this.mediaSource_.readyState +
454
+ 'expected \'open\'');
455
+ }
399
456
 
400
- this.eventManager_.listen(
401
- sourceBuffer, 'error',
402
- () => this.onError_(contentType));
403
- this.eventManager_.listen(
404
- sourceBuffer, 'updateend',
405
- () => this.onUpdateEnd_(contentType));
406
- this.sourceBuffers_[contentType] = sourceBuffer;
407
- this.sourceBufferTypes_[contentType] = mimeType;
408
- this.queues_[contentType] = [];
409
- this.expectedEncryption_[contentType] = !!stream.drmInfos.length;
457
+ if (this.sequenceMode_) {
458
+ sourceBuffer.mode =
459
+ shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
410
460
  }
461
+
462
+ this.eventManager_.listen(
463
+ sourceBuffer, 'error',
464
+ () => this.onError_(contentType));
465
+ this.eventManager_.listen(
466
+ sourceBuffer, 'updateend',
467
+ () => this.onUpdateEnd_(contentType));
468
+ this.sourceBuffers_[contentType] = sourceBuffer;
469
+ this.sourceBufferTypes_[contentType] = mimeType;
470
+ this.expectedEncryption_[contentType] = !!stream.drmInfos.length;
411
471
  }
412
472
  }
413
473
 
@@ -440,6 +500,9 @@ shaka.media.MediaSourceEngine = class {
440
500
  * object has been destroyed.
441
501
  */
442
502
  ended() {
503
+ if (this.reloadingMediaSource_) {
504
+ return false;
505
+ }
443
506
  return this.mediaSource_ ? this.mediaSource_.readyState == 'ended' : true;
444
507
  }
445
508
 
@@ -450,6 +513,9 @@ shaka.media.MediaSourceEngine = class {
450
513
  * @return {?number} The timestamp in seconds, or null if nothing is buffered.
451
514
  */
452
515
  bufferStart(contentType) {
516
+ if (this.reloadingMediaSource_) {
517
+ return null;
518
+ }
453
519
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
454
520
  if (contentType == ContentType.TEXT) {
455
521
  return this.textEngine_.bufferStart();
@@ -465,6 +531,9 @@ shaka.media.MediaSourceEngine = class {
465
531
  * @return {?number} The timestamp in seconds, or null if nothing is buffered.
466
532
  */
467
533
  bufferEnd(contentType) {
534
+ if (this.reloadingMediaSource_) {
535
+ return null;
536
+ }
468
537
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
469
538
  if (contentType == ContentType.TEXT) {
470
539
  return this.textEngine_.bufferEnd();
@@ -482,6 +551,9 @@ shaka.media.MediaSourceEngine = class {
482
551
  * @return {boolean}
483
552
  */
484
553
  isBuffered(contentType, time) {
554
+ if (this.reloadingMediaSource_) {
555
+ return false;
556
+ }
485
557
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
486
558
  if (contentType == ContentType.TEXT) {
487
559
  return this.textEngine_.isBuffered(time);
@@ -500,6 +572,9 @@ shaka.media.MediaSourceEngine = class {
500
572
  * @return {number} The amount of time buffered ahead in seconds.
501
573
  */
502
574
  bufferedAheadOf(contentType, time) {
575
+ if (this.reloadingMediaSource_) {
576
+ return 0;
577
+ }
503
578
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
504
579
  if (contentType == ContentType.TEXT) {
505
580
  return this.textEngine_.bufferedAheadOf(time);
@@ -518,11 +593,12 @@ shaka.media.MediaSourceEngine = class {
518
593
  const TimeRangesUtils = shaka.media.TimeRangesUtils;
519
594
 
520
595
  const info = {
521
- total: TimeRangesUtils.getBufferedInfo(this.video_.buffered),
522
- audio: TimeRangesUtils.getBufferedInfo(
523
- this.getBuffered_(ContentType.AUDIO)),
524
- video: TimeRangesUtils.getBufferedInfo(
525
- this.getBuffered_(ContentType.VIDEO)),
596
+ total: this.reloadingMediaSource_ ? [] :
597
+ TimeRangesUtils.getBufferedInfo(this.video_.buffered),
598
+ audio: this.reloadingMediaSource_ ? [] :
599
+ TimeRangesUtils.getBufferedInfo(this.getBuffered_(ContentType.AUDIO)),
600
+ video: this.reloadingMediaSource_ ? [] :
601
+ TimeRangesUtils.getBufferedInfo(this.getBuffered_(ContentType.VIDEO)),
526
602
  text: [],
527
603
  };
528
604
 
@@ -568,6 +644,83 @@ shaka.media.MediaSourceEngine = class {
568
644
  return new shaka.media.ClosedCaptionParser(mimeType);
569
645
  }
570
646
 
647
+ /**
648
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
649
+ * @param {!BufferSource} data
650
+ * @param {?shaka.media.SegmentReference} reference The segment reference
651
+ * we are appending, or null for init segments
652
+ * @param {!string} mimeType
653
+ * @param {!number} timestampOffset
654
+ * @return {?number}
655
+ * @private
656
+ */
657
+ getTimestampAndDispatchMetadata_(contentType, data, reference, mimeType,
658
+ timestampOffset) {
659
+ let timestamp = null;
660
+
661
+ const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
662
+ if (shaka.util.MimeUtils.RAW_FORMATS.includes(mimeType)) {
663
+ const frames = shaka.util.Id3Utils.getID3Frames(uint8ArrayData);
664
+ if (frames.length && reference) {
665
+ const metadataTimestamp = frames.find((frame) => {
666
+ return frame.description ===
667
+ 'com.apple.streaming.transportStreamTimestamp';
668
+ });
669
+ if (metadataTimestamp && metadataTimestamp.data) {
670
+ timestamp = Math.round(metadataTimestamp.data) / 1000;
671
+ }
672
+ /** @private {shaka.extern.ID3Metadata} */
673
+ const metadata = {
674
+ cueTime: reference.startTime,
675
+ data: uint8ArrayData,
676
+ frames: frames,
677
+ dts: reference.startTime,
678
+ pts: reference.startTime,
679
+ };
680
+ this.onMetadata_([metadata], /* offset= */ 0, reference.endTime);
681
+ }
682
+ } else if (mimeType.includes('/mp4') &&
683
+ reference && reference.timestampOffset == 0 &&
684
+ reference.initSegmentReference &&
685
+ reference.initSegmentReference.timescale) {
686
+ const timescale = reference.initSegmentReference.timescale;
687
+ if (!isNaN(timescale)) {
688
+ const Mp4Parser = shaka.util.Mp4Parser;
689
+ let startTime = 0;
690
+ let parsedMedia = false;
691
+ new Mp4Parser()
692
+ .box('moof', Mp4Parser.children)
693
+ .box('traf', Mp4Parser.children)
694
+ .fullBox('tfdt', (box) => {
695
+ goog.asserts.assert(
696
+ box.version == 0 || box.version == 1,
697
+ 'TFDT version can only be 0 or 1');
698
+ const parsed = shaka.util.Mp4BoxParsers.parseTFDTInaccurate(
699
+ box.reader, box.version);
700
+ startTime = parsed.baseMediaDecodeTime / timescale;
701
+ parsedMedia = true;
702
+ box.parser.stop();
703
+ }).parse(data, /* partialOkay= */ true);
704
+ if (parsedMedia) {
705
+ timestamp = startTime;
706
+ }
707
+ }
708
+ } else if (!mimeType.includes('/mp4') && !mimeType.includes('/webm') &&
709
+ shaka.util.TsParser.probe(uint8ArrayData)) {
710
+ const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
711
+ const startTime = tsParser.getStartTime()[contentType];
712
+ if (startTime != null) {
713
+ timestamp = startTime;
714
+ }
715
+ const metadata = tsParser.getMetadata();
716
+ if (metadata.length) {
717
+ this.onMetadata_(metadata, timestampOffset,
718
+ reference ? reference.endTime : null);
719
+ }
720
+ }
721
+ return timestamp;
722
+ }
723
+
571
724
  /**
572
725
  * Enqueue an operation to append data to the SourceBuffer.
573
726
  * Start and end times are needed for TextEngine, but not for MediaSource.
@@ -578,6 +731,7 @@ shaka.media.MediaSourceEngine = class {
578
731
  * @param {!BufferSource} data
579
732
  * @param {?shaka.media.SegmentReference} reference The segment reference
580
733
  * we are appending, or null for init segments
734
+ * @param {shaka.extern.Stream} stream
581
735
  * @param {?boolean} hasClosedCaptions True if the buffer contains CEA closed
582
736
  * captions
583
737
  * @param {boolean=} seeked True if we just seeked
@@ -588,7 +742,7 @@ shaka.media.MediaSourceEngine = class {
588
742
  * @return {!Promise}
589
743
  */
590
744
  async appendBuffer(
591
- contentType, data, reference, hasClosedCaptions, seeked = false,
745
+ contentType, data, reference, stream, hasClosedCaptions, seeked = false,
592
746
  adaptation = false, isChunkedData = false) {
593
747
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
594
748
 
@@ -605,77 +759,49 @@ shaka.media.MediaSourceEngine = class {
605
759
  return;
606
760
  }
607
761
 
608
- const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
762
+ let timestampOffset = this.sourceBuffers_[contentType].timestampOffset;
763
+
609
764
  let mimeType = this.sourceBufferTypes_[contentType];
610
765
  if (this.transmuxers_[contentType]) {
611
766
  mimeType = this.transmuxers_[contentType].getOrginalMimeType();
612
767
  }
613
- if (shaka.util.TsParser.probe(uint8ArrayData)) {
614
- const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
615
- // The SourceBuffer timestampOffset may or may not be set yet, so this is
616
- // the timestamp offset that would eventually compute for this segment
617
- // either way.
618
- const timestampOffset =
619
- reference.startTime - (tsParser.getStartTime()[contentType] || 0);
620
- const metadata = tsParser.getMetadata();
621
- if (metadata.length) {
622
- this.onMetadata_(metadata, timestampOffset,
623
- reference ? reference.endTime : null);
624
- }
625
- } else {
626
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(mimeType)) {
627
- const frames = shaka.util.Id3Utils.getID3Frames(uint8ArrayData);
628
- if (frames.length && reference) {
629
- /** @private {shaka.extern.ID3Metadata} */
630
- const metadata = {
631
- cueTime: reference.startTime,
632
- data: uint8ArrayData,
633
- frames: frames,
634
- dts: reference.startTime,
635
- pts: reference.startTime,
636
- };
637
- this.onMetadata_([metadata], /* offset= */ 0, reference.endTime);
768
+ if (reference) {
769
+ const timestamp = this.getTimestampAndDispatchMetadata_(
770
+ contentType, data, reference, mimeType, timestampOffset);
771
+ if (timestamp != null) {
772
+ const calculatedTimestampOffset = reference.startTime - timestamp;
773
+ const timestampOffsetDifference =
774
+ Math.abs(timestampOffset - calculatedTimestampOffset);
775
+ if (timestampOffsetDifference >= 0.1 || seeked || adaptation) {
776
+ timestampOffset = calculatedTimestampOffset;
777
+ if (this.attemptTimestampOffsetCalculation_) {
778
+ this.enqueueOperation_(
779
+ contentType,
780
+ () => this.abort_(contentType));
781
+ this.enqueueOperation_(
782
+ contentType,
783
+ () => this.setTimestampOffset_(contentType, timestampOffset));
784
+ }
785
+ }
786
+ // Timestamps can only be reliably extracted from video, not audio.
787
+ // Packed audio formats do not have internal timestamps at all.
788
+ // Prefer video for this when available.
789
+ const isBestSourceBufferForTimestamps =
790
+ contentType == ContentType.VIDEO ||
791
+ !(ContentType.VIDEO in this.sourceBuffers_);
792
+ if (this.sequenceMode_ && isBestSourceBufferForTimestamps) {
793
+ this.textSequenceModeOffset_.resolve(timestampOffset);
638
794
  }
639
795
  }
640
796
  }
641
-
642
- if (this.transmuxers_[contentType]) {
643
- // When seeked we should reset the transmuxer captionstreams
644
- // so it does not ignores the captions from previous segments
645
- if (seeked) {
646
- this.transmuxers_[contentType].resetCaptions();
647
- }
648
-
649
- const transmuxedData =
650
- await this.transmuxers_[contentType].transmux(data);
651
- // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
652
- // the video stream, so textEngine may not have been initialized.
653
- if (!this.textEngine_) {
654
- this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
655
- this.sequenceMode_, /* external= */ false);
656
- }
657
- // This doesn't work for native TS support (ex. Edge/Chromecast),
658
- // since no transmuxing is needed for native TS.
659
- if (transmuxedData.captions && transmuxedData.captions.length) {
660
- const videoOffset =
661
- this.sourceBuffers_[ContentType.VIDEO].timestampOffset;
662
- const closedCaptions = this.textEngine_
663
- .convertMuxjsCaptionsToShakaCaptions(transmuxedData.captions);
664
- this.textEngine_.storeAndAppendClosedCaptions(
665
- closedCaptions,
666
- reference ? reference.startTime : null,
667
- reference ? reference.endTime : null,
668
- videoOffset);
669
- }
670
-
671
- data = transmuxedData.data;
672
- } else if (hasClosedCaptions && contentType == ContentType.VIDEO) {
797
+ if (hasClosedCaptions && contentType == ContentType.VIDEO) {
673
798
  if (!this.textEngine_) {
674
799
  this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE,
675
800
  this.sequenceMode_, /* external= */ false);
676
801
  }
677
802
  if (!this.captionParser_) {
678
- this.captionParser_ = this.getCaptionParser(mimeType);
803
+ const basicType = mimeType.split(';', 1)[0];
804
+ this.captionParser_ = this.getCaptionParser(basicType);
679
805
  }
680
806
  // If it is the init segment for closed captions, initialize the closed
681
807
  // caption parser.
@@ -684,92 +810,23 @@ shaka.media.MediaSourceEngine = class {
684
810
  } else {
685
811
  const closedCaptions = this.captionParser_.parseFrom(data);
686
812
  if (closedCaptions.length) {
687
- const videoOffset =
688
- this.sourceBuffers_[ContentType.VIDEO].timestampOffset;
689
813
  this.textEngine_.storeAndAppendClosedCaptions(
690
814
  closedCaptions,
691
815
  reference.startTime,
692
816
  reference.endTime,
693
- videoOffset);
817
+ timestampOffset);
694
818
  }
695
819
  }
696
820
  }
697
821
 
822
+ if (this.transmuxers_[contentType]) {
823
+ data = await this.transmuxers_[contentType].transmux(
824
+ data, stream, reference, this.mediaSource_.duration);
825
+ }
826
+
698
827
  data = this.workAroundBrokenPlatforms_(
699
828
  data, reference ? reference.startTime : null, contentType);
700
829
 
701
- const sourceBuffer = this.sourceBuffers_[contentType];
702
- const SEQUENCE = shaka.media.MediaSourceEngine.SourceBufferMode_.SEQUENCE;
703
-
704
- if (this.sequenceMode_ && sourceBuffer.mode != SEQUENCE && reference) {
705
- // This is the first media segment to be appended to a SourceBuffer in
706
- // sequence mode. We set the mode late so that we can trick MediaSource
707
- // into extracting a timestamp for us to align text segments in sequence
708
- // mode.
709
-
710
- const duration = this.mediaSource_.duration;
711
-
712
- // Timestamps can only be reliably extracted from video, not audio.
713
- // Packed audio formats do not have internal timestamps at all.
714
- // Prefer video for this when available.
715
- const isBestSourceBufferForTimestamps =
716
- contentType == ContentType.VIDEO ||
717
- !(ContentType.VIDEO in this.sourceBuffers_);
718
- if (isBestSourceBufferForTimestamps) {
719
- // Append the segment in segments mode first, with offset of 0 and an
720
- // open append window.
721
- const originalRange =
722
- [sourceBuffer.appendWindowStart, sourceBuffer.appendWindowEnd];
723
- sourceBuffer.appendWindowStart = 0;
724
- sourceBuffer.appendWindowEnd = Infinity;
725
-
726
- const originalOffset = sourceBuffer.timestampOffset;
727
- sourceBuffer.timestampOffset = 0;
728
-
729
- await this.enqueueOperation_(
730
- contentType, () => this.append_(contentType, data));
731
- // If the input buffer passed to SourceBuffer#appendBuffer() does not
732
- // contain a complete media segment, the call will exit while the
733
- // SourceBuffer's append state is
734
- // still PARSING_MEDIA_SEGMENT. Reset the parser state by calling
735
- // abort() to safely reset timestampOffset to 'originalOffset'.
736
- // https://www.w3.org/TR/media-source-2/#sourcebuffer-segment-parser-loop
737
- await this.enqueueOperation_(
738
- contentType, () => this.abort_(contentType));
739
-
740
- // Reset the offset and append window.
741
- sourceBuffer.timestampOffset = originalOffset;
742
- sourceBuffer.appendWindowStart = originalRange[0];
743
- sourceBuffer.appendWindowEnd = originalRange[1];
744
-
745
- // Now get the timestamp of the segment and compute the offset for text
746
- // segments.
747
- const mediaStartTime = shaka.media.TimeRangesUtils.bufferStart(
748
- this.getBuffered_(contentType));
749
- const textOffset = (reference.startTime || 0) - (mediaStartTime || 0);
750
- this.textSequenceModeOffset_.resolve(textOffset);
751
-
752
- // Clear the buffer.
753
- await this.enqueueOperation_(
754
- contentType,
755
- () => this.remove_(contentType, 0, duration));
756
-
757
- // Finally, flush the buffer in case of choppy video start on HLS fMP4.
758
- if (contentType == ContentType.VIDEO) {
759
- await this.enqueueOperation_(
760
- contentType,
761
- () => this.flush_(contentType));
762
- }
763
- }
764
-
765
- // Now switch to sequence mode and fall through to our normal operations.
766
- sourceBuffer.mode = SEQUENCE;
767
-
768
- // When we change the buffer mode the duration is lost, so we need to set
769
- // it explicitly.
770
- await this.setDuration(duration);
771
- }
772
-
773
830
  if (reference && this.sequenceMode_ && contentType != ContentType.TEXT) {
774
831
  // In sequence mode, for non-text streams, if we just cleared the buffer
775
832
  // and are either performing an unbuffered seek or handling an automatic
@@ -919,23 +976,33 @@ shaka.media.MediaSourceEngine = class {
919
976
  * @param {number} appendWindowEnd The timestamp to set the append window end
920
977
  * to. For future appends, frames/samples with timestamps greater than this
921
978
  * value will be dropped.
922
- * @param {boolean} sequenceMode If true, the timestampOffset will not be
923
- * applied in this step.
979
+ * @param {boolean} ignoreTimestampOffset If true, the timestampOffset will
980
+ * not be applied in this step.
981
+ * @param {shaka.extern.Stream} stream The current stream.
982
+ * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
983
+ * shaka.extern.Stream>} streamsByType
984
+ * A map of content types to streams. All streams must be supported
985
+ * according to MediaSourceEngine.isStreamSupported.
986
+ *
924
987
  * @return {!Promise}
925
988
  */
926
989
  async setStreamProperties(
927
990
  contentType, timestampOffset, appendWindowStart, appendWindowEnd,
928
- sequenceMode) {
991
+ ignoreTimestampOffset, stream, streamsByType) {
929
992
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
930
993
  if (contentType == ContentType.TEXT) {
931
- if (!sequenceMode) {
994
+ if (!ignoreTimestampOffset) {
932
995
  this.textEngine_.setTimestampOffset(timestampOffset);
933
996
  }
934
997
  this.textEngine_.setAppendWindow(appendWindowStart, appendWindowEnd);
935
998
  return;
936
999
  }
1000
+ const operations = [];
937
1001
 
938
- await Promise.all([
1002
+ const hasChangedCodecs =
1003
+ await this.codecSwitchIfNecessary_(contentType, stream, streamsByType);
1004
+
1005
+ if (!hasChangedCodecs) {
939
1006
  // Queue an abort() to help MSE splice together overlapping segments.
940
1007
  // We set appendWindowEnd when we change periods in DASH content, and the
941
1008
  // period transition may result in overlap.
@@ -944,20 +1011,21 @@ shaka.media.MediaSourceEngine = class {
944
1011
  // always enter a PARSING_MEDIA_SEGMENT state and we can't change the
945
1012
  // timestamp offset. By calling abort(), we reset the state so we can
946
1013
  // set it.
947
- this.enqueueOperation_(
948
- contentType,
949
- () => this.abort_(contentType)),
950
- // Don't set the timestampOffset here when in sequenceMode, since we
951
- // use timestampOffset for a different purpose in that mode (e.g. to
952
- // indicate where the current segment is).
953
- sequenceMode ? Promise.resolve() : this.enqueueOperation_(
1014
+ operations.push(this.enqueueOperation_(
954
1015
  contentType,
955
- () => this.setTimestampOffset_(contentType, timestampOffset)),
956
- this.enqueueOperation_(
1016
+ () => this.abort_(contentType)));
1017
+ }
1018
+ if (!ignoreTimestampOffset) {
1019
+ operations.push(this.enqueueOperation_(
957
1020
  contentType,
958
- () => this.setAppendWindow_(
959
- contentType, appendWindowStart, appendWindowEnd)),
960
- ]);
1021
+ () => this.setTimestampOffset_(contentType, timestampOffset)));
1022
+ }
1023
+ operations.push(this.enqueueOperation_(
1024
+ contentType,
1025
+ () => this.setAppendWindow_(
1026
+ contentType, appendWindowStart, appendWindowEnd)));
1027
+
1028
+ await Promise.all(operations);
961
1029
  }
962
1030
 
963
1031
  /**
@@ -1200,6 +1268,9 @@ shaka.media.MediaSourceEngine = class {
1200
1268
  * @private
1201
1269
  */
1202
1270
  onUpdateEnd_(contentType) {
1271
+ if (this.reloadingMediaSource_) {
1272
+ return;
1273
+ }
1203
1274
  const operation = this.queues_[contentType][0];
1204
1275
  goog.asserts.assert(operation, 'Spurious updateend event!');
1205
1276
  if (!operation) {
@@ -1238,7 +1309,7 @@ shaka.media.MediaSourceEngine = class {
1238
1309
  * Enqueue an operation which must block all other operations on all
1239
1310
  * SourceBuffers.
1240
1311
  *
1241
- * @param {function()} run
1312
+ * @param {function():(Promise|undefined)} run
1242
1313
  * @return {!Promise}
1243
1314
  * @private
1244
1315
  */
@@ -1304,15 +1375,16 @@ shaka.media.MediaSourceEngine = class {
1304
1375
  }
1305
1376
  }
1306
1377
 
1307
- // Run the real operation, which is synchronous.
1378
+ // Run the real operation, which can be asynchronous.
1308
1379
  try {
1309
- run();
1380
+ await run();
1310
1381
  } catch (exception) {
1311
1382
  throw new shaka.util.Error(
1312
1383
  shaka.util.Error.Severity.CRITICAL,
1313
1384
  shaka.util.Error.Category.MEDIA,
1314
1385
  shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
1315
- exception);
1386
+ exception,
1387
+ this.video_.error || 'No error in the media element');
1316
1388
  } finally {
1317
1389
  // Unblock the queues.
1318
1390
  for (const contentType in this.sourceBuffers_) {
@@ -1355,7 +1427,8 @@ shaka.media.MediaSourceEngine = class {
1355
1427
  shaka.util.Error.Severity.CRITICAL,
1356
1428
  shaka.util.Error.Category.MEDIA,
1357
1429
  shaka.util.Error.Code.MEDIA_SOURCE_OPERATION_THREW,
1358
- exception));
1430
+ exception,
1431
+ this.video_.error || 'No error in the media element'));
1359
1432
  }
1360
1433
  this.popFromQueue_(contentType);
1361
1434
  }
@@ -1410,15 +1483,17 @@ shaka.media.MediaSourceEngine = class {
1410
1483
  const encryptionExpected = this.expectedEncryption_[contentType];
1411
1484
 
1412
1485
  // If:
1413
- // 1. this is an init segment,
1414
- // 2. and encryption is expected,
1415
- // 3. and the platform requires encryption in all init segments,
1416
- // 4. and the content is MP4 (mimeType == "video/mp4" or "audio/mp4"),
1486
+ // 1. the configuration tells to insert fake encryption,
1487
+ // 2. and this is an init segment,
1488
+ // 3. and encryption is expected,
1489
+ // 4. and the platform requires encryption in all init segments,
1490
+ // 5. and the content is MP4 (mimeType == "video/mp4" or "audio/mp4"),
1417
1491
  // then insert fake encryption metadata for init segments that lack it.
1418
1492
  // The MP4 requirement is because we can currently only do this
1419
1493
  // transformation on MP4 containers.
1420
1494
  // See: https://github.com/shaka-project/shaka-player/issues/2759
1421
- if (isInitSegment &&
1495
+ if (this.config_.insertFakeEncryptionInInit &&
1496
+ isInitSegment &&
1422
1497
  encryptionExpected &&
1423
1498
  shaka.util.Platform.requiresEncryptionInfoInAllInitSegments() &&
1424
1499
  shaka.util.MimeUtils.getContainerType(
@@ -1430,6 +1505,188 @@ shaka.media.MediaSourceEngine = class {
1430
1505
  return segment;
1431
1506
  }
1432
1507
 
1508
+ /**
1509
+ * Prepare the SourceBuffer to parse a potentially new type or codec.
1510
+ *
1511
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1512
+ * @param {string} mimeType
1513
+ * @param {?shaka.extern.Transmuxer} transmuxer
1514
+ * @private
1515
+ */
1516
+ change_(contentType, mimeType, transmuxer) {
1517
+ const ContentType = shaka.util.ManifestParserUtils.ContentType;
1518
+ if (contentType === ContentType.TEXT) {
1519
+ shaka.log.debug(`Change not supported for ${contentType}`);
1520
+ return;
1521
+ }
1522
+ shaka.log.debug(
1523
+ `Change Type: ${this.sourceBufferTypes_[contentType]} -> ${mimeType}`);
1524
+ if (shaka.media.Capabilities.isChangeTypeSupported()) {
1525
+ if (this.transmuxers_[contentType]) {
1526
+ this.transmuxers_[contentType].destroy();
1527
+ }
1528
+ if (transmuxer) {
1529
+ this.transmuxers_[contentType] = transmuxer;
1530
+ }
1531
+ const type = mimeType + this.config_.sourceBufferExtraFeatures;
1532
+ this.sourceBuffers_[contentType].changeType(type);
1533
+ this.sourceBufferTypes_[contentType] = mimeType;
1534
+ } else {
1535
+ shaka.log.debug('Change Type not supported');
1536
+ }
1537
+
1538
+ // Fake an 'updateend' event to resolve the operation.
1539
+ this.onUpdateEnd_(contentType);
1540
+ }
1541
+
1542
+ /**
1543
+ * Enqueue an operation to prepare the SourceBuffer to parse a potentially new
1544
+ * type or codec.
1545
+ *
1546
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1547
+ * @param {string} mimeType
1548
+ * @param {?shaka.extern.Transmuxer} transmuxer
1549
+ * @return {!Promise}
1550
+ */
1551
+ changeType(contentType, mimeType, transmuxer) {
1552
+ return this.enqueueOperation_(
1553
+ contentType,
1554
+ () => this.change_(contentType, mimeType, transmuxer));
1555
+ }
1556
+
1557
+ /**
1558
+ * Resets the MediaSource and re-adds source buffers due to codec mismatch
1559
+ *
1560
+ * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
1561
+ * shaka.extern.Stream>} streamsByType
1562
+ * @private
1563
+ */
1564
+ async reset_(streamsByType) {
1565
+ this.reloadingMediaSource_ = true;
1566
+ const currentTime = this.video_.currentTime;
1567
+ try {
1568
+ this.eventManager_.removeAll();
1569
+ this.sourceBuffers_ = {};
1570
+
1571
+ const cleanup = [];
1572
+ for (const contentType in this.transmuxers_) {
1573
+ cleanup.push(this.transmuxers_[contentType].destroy());
1574
+ }
1575
+ for (const contentType in this.sourceBuffers_) {
1576
+ const sourceBuffer = this.sourceBuffers_[contentType];
1577
+ try {
1578
+ this.mediaSource_.removeSourceBuffer(sourceBuffer);
1579
+ } catch (e) {}
1580
+ }
1581
+ await Promise.all(cleanup);
1582
+ this.transmuxers_ = {};
1583
+
1584
+ const previousDuration = this.mediaSource_.duration;
1585
+ this.mediaSourceOpen_ = new shaka.util.PublicPromise();
1586
+ this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
1587
+ await this.mediaSourceOpen_;
1588
+ this.mediaSource_.duration = previousDuration;
1589
+
1590
+ const sourceBufferAdded = new shaka.util.PublicPromise();
1591
+ const sourceBuffers =
1592
+ /** @type {EventTarget} */(this.mediaSource_.sourceBuffers);
1593
+
1594
+ const totalOfBuffers = streamsByType.size;
1595
+ let numberOfSourceBufferAdded = 0;
1596
+ this.eventManager_.listen(sourceBuffers, 'addsourcebuffer', (event) => {
1597
+ numberOfSourceBufferAdded++;
1598
+ if (numberOfSourceBufferAdded === totalOfBuffers) {
1599
+ sourceBufferAdded.resolve();
1600
+ }
1601
+ });
1602
+
1603
+ for (const contentType of streamsByType.keys()) {
1604
+ const stream = streamsByType.get(contentType);
1605
+ this.initSourceBuffer_(contentType, stream);
1606
+ }
1607
+
1608
+ // Fake a seek to catchup the playhead.
1609
+ this.video_.currentTime = currentTime;
1610
+
1611
+ await sourceBufferAdded;
1612
+ } finally {
1613
+ this.reloadingMediaSource_ = false;
1614
+ }
1615
+ }
1616
+
1617
+ /**
1618
+ * Resets the Media Source
1619
+ * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
1620
+ * shaka.extern.Stream>} streamsByType
1621
+ * @return {!Promise}
1622
+ */
1623
+ reset(streamsByType) {
1624
+ return this.enqueueBlockingOperation_(
1625
+ () => this.reset_(streamsByType));
1626
+ }
1627
+
1628
+ /**
1629
+ * Codec switch if necessary, this will not resolve until the codec
1630
+ * switch is over.
1631
+ * @param {shaka.util.ManifestParserUtils.ContentType} contentType
1632
+ * @param {shaka.extern.Stream} stream
1633
+ * @param {!Map.<shaka.util.ManifestParserUtils.ContentType,
1634
+ * shaka.extern.Stream>} streamsByType
1635
+ * @return {!Promise.<boolean>} true if there was a codec switch,
1636
+ * false otherwise.
1637
+ * @private
1638
+ */
1639
+ async codecSwitchIfNecessary_(contentType, stream, streamsByType) {
1640
+ const MimeUtils = shaka.util.MimeUtils;
1641
+ const currentCodec = MimeUtils.getCodecBase(
1642
+ MimeUtils.getCodecs(this.sourceBufferTypes_[contentType]));
1643
+ const currentBasicType = MimeUtils.getBasicType(
1644
+ this.sourceBufferTypes_[contentType]);
1645
+
1646
+ /** @type {?shaka.extern.Transmuxer} */
1647
+ let transmuxer;
1648
+ let newMimeType = shaka.util.MimeUtils.getFullType(
1649
+ stream.mimeType, stream.codecs);
1650
+ let needTransmux = this.config_.forceTransmux;
1651
+ if (!shaka.media.Capabilities.isTypeSupported(newMimeType) ||
1652
+ (!this.sequenceMode_ &&
1653
+ shaka.util.MimeUtils.RAW_FORMATS.includes(newMimeType))) {
1654
+ needTransmux = true;
1655
+ }
1656
+ const newMimeTypeWithAllCodecs =
1657
+ shaka.util.MimeUtils.getFullTypeWithAllCodecs(
1658
+ stream.mimeType, stream.codecs);
1659
+ const TransmuxerEngine = shaka.transmuxer.TransmuxerEngine;
1660
+ if (needTransmux) {
1661
+ const transmuxerPlugin =
1662
+ TransmuxerEngine.findTransmuxer(newMimeTypeWithAllCodecs);
1663
+ if (transmuxerPlugin) {
1664
+ transmuxer = transmuxerPlugin();
1665
+ newMimeType =
1666
+ transmuxer.convertCodecs(contentType, newMimeTypeWithAllCodecs);
1667
+ }
1668
+ }
1669
+
1670
+ const newCodec = MimeUtils.getCodecBase(
1671
+ MimeUtils.getCodecs(newMimeType));
1672
+ const newBasicType = MimeUtils.getBasicType(newMimeType);
1673
+
1674
+ // Current/new codecs base and basic type match then no need to switch
1675
+ if (currentCodec === newCodec && currentBasicType === newBasicType) {
1676
+ return false;
1677
+ }
1678
+
1679
+ if (shaka.media.Capabilities.isChangeTypeSupported()) {
1680
+ await this.changeType(contentType, newMimeType, transmuxer);
1681
+ } else {
1682
+ if (transmuxer) {
1683
+ transmuxer.destroy();
1684
+ }
1685
+ await this.reset(streamsByType);
1686
+ }
1687
+ return true;
1688
+ }
1689
+
1433
1690
  /**
1434
1691
  * Update LCEVC DIL object when ready for LCEVC Decodes
1435
1692
  * @param {?shaka.lcevc.Dil} lcevcDil
@@ -1473,16 +1730,3 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
1473
1730
  SEQUENCE: 'sequence',
1474
1731
  SEGMENTS: 'segments',
1475
1732
  };
1476
-
1477
-
1478
- /**
1479
- * MIME types of raw formats.
1480
- *
1481
- * @const {!Array.<string>}
1482
- */
1483
- shaka.media.MediaSourceEngine.RAW_FORMATS = [
1484
- 'audio/aac',
1485
- 'audio/ac3',
1486
- 'audio/ec3',
1487
- 'audio/mpeg',
1488
- ];