shaka-player 3.3.2 → 4.0.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 (389) hide show
  1. package/.csslintrc +11 -1
  2. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  3. package/.github/pull_request_template.md +6 -34
  4. package/.github/workflows/build-and-test.yaml +129 -0
  5. package/.github/workflows/custom-actions/prep-for-appspot/action.yaml +56 -0
  6. package/.github/workflows/custom-actions/set-commit-status/action.yaml +41 -0
  7. package/.github/workflows/demo-version-index.yaml +37 -0
  8. package/.github/workflows/nightly-demo.yaml +42 -0
  9. package/.github/workflows/release-please.yaml +168 -0
  10. package/.github/workflows/safari-homedir-launcher.sh +18 -0
  11. package/.github/workflows/selenium-lab-tests.yaml +92 -0
  12. package/.github/workflows/sync-labels.yaml +33 -0
  13. package/.github/workflows/update-issues.yaml +29 -0
  14. package/.github/workflows/validate-pr-title.yaml +35 -0
  15. package/AUTHORS +3 -0
  16. package/CHANGELOG.md +1866 -1675
  17. package/CONTRIBUTING.md +2 -2
  18. package/CONTRIBUTORS +3 -0
  19. package/README.md +16 -13
  20. package/app-engine/README.md +9 -0
  21. package/app-engine/demo-version-index/README.md +14 -0
  22. package/app-engine/demo-version-index/app.yaml +28 -0
  23. package/app-engine/demo-version-index/generate.py +164 -0
  24. package/app-engine/demo-version-index/main.py +11 -0
  25. package/app-engine/demo-version-index/requirements.txt +5 -0
  26. package/app-engine/demo-version-index/templates/index.html +114 -0
  27. package/app-engine/shaka-player-demo/README.md +18 -0
  28. package/app-engine/shaka-player-demo/app.yaml +61 -0
  29. package/app-engine/shaka-player-demo/main.py +95 -0
  30. package/app-engine/shaka-player-demo/posters/10yo.png +0 -0
  31. package/app-engine/shaka-player-demo/posters/10yo.xcf +0 -0
  32. package/app-engine/shaka-player-demo/posters/1yo.jpg +0 -0
  33. package/app-engine/shaka-player-demo/posters/2yo.png +0 -0
  34. package/app-engine/shaka-player-demo/posters/2yo.xcf +0 -0
  35. package/app-engine/shaka-player-demo/posters/3yo.png +0 -0
  36. package/app-engine/shaka-player-demo/posters/3yo.xcf +0 -0
  37. package/app-engine/shaka-player-demo/posters/4yo.png +0 -0
  38. package/app-engine/shaka-player-demo/posters/4yo.xcf +0 -0
  39. package/app-engine/shaka-player-demo/posters/5yo.png +0 -0
  40. package/app-engine/shaka-player-demo/posters/5yo.xcf +0 -0
  41. package/app-engine/shaka-player-demo/posters/6yo.png +0 -0
  42. package/app-engine/shaka-player-demo/posters/6yo.xcf +0 -0
  43. package/app-engine/shaka-player-demo/posters/7yo.png +0 -0
  44. package/app-engine/shaka-player-demo/posters/7yo.xcf +0 -0
  45. package/app-engine/shaka-player-demo/posters/8yo.png +0 -0
  46. package/app-engine/shaka-player-demo/posters/8yo.xcf +0 -0
  47. package/app-engine/shaka-player-demo/posters/9yo.png +0 -0
  48. package/app-engine/shaka-player-demo/posters/9yo.xcf +0 -0
  49. package/app-engine/shaka-player-demo/posters/audioOnly.gif +0 -0
  50. package/app-engine/shaka-player-demo/posters/audioRickRoll.gif +0 -0
  51. package/app-engine/shaka-player-demo/posters/colorbars.gif +0 -0
  52. package/app-engine/shaka-player-demo/posters/fireworks.jpg +0 -0
  53. package/app-engine/shaka-player-demo/posters/party-hat.png +0 -0
  54. package/app-engine/shaka-player-demo/posters/sources.txt +46 -0
  55. package/app-engine/shaka-player-demo/posters/standard.png +0 -0
  56. package/app-engine/shaka-player-demo/posters/trek.jpg +0 -0
  57. package/app-engine/shaka-player-demo/posters/valentines.png +0 -0
  58. package/app-engine/shaka-player-demo/posters/zombie.jpg +0 -0
  59. package/app-engine/shaka-player-demo/posters.py +110 -0
  60. package/app-engine/shaka-player-demo/requirements.txt +5 -0
  61. package/app-engine/shaka-player-demo/time.txt +1 -0
  62. package/build/README.md +3 -3
  63. package/build/checkversion.py +1 -1
  64. package/build/compiler.py +1 -1
  65. package/build/conformance.textproto +13 -0
  66. package/build/docker/Dockerfile +20 -0
  67. package/build/docker/docker-compose.yml +10 -0
  68. package/build/gendeps.py +18 -15
  69. package/build/shaka-lab.yaml +161 -0
  70. package/build/shakaBuildHelpers.py +4 -29
  71. package/build/test.py +94 -29
  72. package/build/types/core +0 -1
  73. package/build/types/polyfill +1 -3
  74. package/build/wrapper.template.js +2 -2
  75. package/demo/TengwarTelcontar.woff2 +0 -0
  76. package/demo/asset_card.js +1 -2
  77. package/demo/cast_receiver/index.html +1 -1
  78. package/demo/cast_receiver/receiver_app.css +14 -14
  79. package/demo/common/asset.js +17 -1
  80. package/demo/common/assets.js +31 -0
  81. package/demo/common/message_ids.js +6 -3
  82. package/demo/config.js +10 -12
  83. package/demo/custom.js +39 -1
  84. package/demo/customWarning.txt +2 -2
  85. package/demo/demo.less +29 -18
  86. package/demo/demo_uncompiled.js +1 -13
  87. package/demo/demo_utils.js +0 -1
  88. package/demo/index.html +1 -1
  89. package/demo/locales/en.json +6 -3
  90. package/demo/locales/source.json +22 -10
  91. package/demo/main.js +12 -9
  92. package/demo/search.js +2 -1
  93. package/dist/controls.css +3 -3
  94. package/dist/controls.css.map +1 -1
  95. package/dist/demo.compiled.debug.js +236 -232
  96. package/dist/demo.compiled.debug.map +4 -4
  97. package/dist/demo.compiled.js +236 -232
  98. package/dist/demo.compiled.map +4 -4
  99. package/dist/demo.css +1 -1
  100. package/dist/demo.css.map +1 -1
  101. package/dist/deps.js +229 -226
  102. package/dist/locales.js +34 -32
  103. package/dist/receiver.compiled.debug.js +75 -73
  104. package/dist/receiver.compiled.debug.map +4 -4
  105. package/dist/receiver.compiled.js +75 -73
  106. package/dist/receiver.compiled.map +4 -4
  107. package/dist/shaka-player.compiled.d.ts +321 -314
  108. package/dist/shaka-player.compiled.debug.d.ts +321 -314
  109. package/dist/shaka-player.compiled.debug.externs.js +140 -129
  110. package/dist/shaka-player.compiled.debug.js +527 -592
  111. package/dist/shaka-player.compiled.debug.map +4 -4
  112. package/dist/shaka-player.compiled.externs.js +140 -129
  113. package/dist/shaka-player.compiled.js +836 -860
  114. package/dist/shaka-player.compiled.map +4 -4
  115. package/dist/shaka-player.ui.d.ts +367 -358
  116. package/dist/shaka-player.ui.debug.d.ts +367 -358
  117. package/dist/shaka-player.ui.debug.externs.js +148 -129
  118. package/dist/shaka-player.ui.debug.js +586 -649
  119. package/dist/shaka-player.ui.debug.map +4 -4
  120. package/dist/shaka-player.ui.externs.js +148 -129
  121. package/dist/shaka-player.ui.js +1008 -1028
  122. package/dist/shaka-player.ui.map +4 -4
  123. package/docs/design/REDESIGN.md +33 -33
  124. package/docs/design/bg-fetch.md +2 -2
  125. package/docs/design/codec_preferences.md +1 -1
  126. package/docs/design/dash-audio-channels.md +3 -3
  127. package/docs/design/dash-manifests.md +1 -1
  128. package/docs/design/gap-jumping.md +3 -3
  129. package/docs/design/preload.md +2 -2
  130. package/docs/tutorials/config.md +0 -2
  131. package/docs/tutorials/drm-config.md +5 -0
  132. package/docs/tutorials/fairplay.md +7 -22
  133. package/docs/tutorials/faq.md +8 -19
  134. package/docs/tutorials/index.json +3 -8
  135. package/docs/tutorials/plugins.md +4 -6
  136. package/docs/tutorials/selenium-grid-config.md +122 -0
  137. package/docs/tutorials/upgrade.md +83 -11
  138. package/docs/tutorials/welcome.md +10 -2
  139. package/externs/cmcd.js +1 -1
  140. package/externs/polyfill.js +15 -0
  141. package/externs/shaka/manifest.js +5 -1
  142. package/externs/shaka/net.js +12 -0
  143. package/externs/shaka/offline.js +5 -1
  144. package/externs/shaka/player.js +39 -32
  145. package/externs/shaka/text.js +12 -12
  146. package/externs/sourcebuffer.js +15 -0
  147. package/karma.conf.js +137 -30
  148. package/lib/abr/simple_abr_manager.js +12 -6
  149. package/lib/ads/client_side_ad.js +13 -2
  150. package/lib/ads/client_side_ad_manager.js +22 -4
  151. package/lib/cast/cast_proxy.js +3 -4
  152. package/lib/cast/cast_utils.js +0 -1
  153. package/lib/cea/cea608_data_channel.js +2 -2
  154. package/lib/cea/cea708_service.js +5 -4
  155. package/lib/cea/mp4_cea_parser.js +11 -5
  156. package/lib/cea/sei_processor.js +6 -3
  157. package/lib/dash/content_protection.js +85 -5
  158. package/lib/dash/dash_parser.js +10 -12
  159. package/lib/dash/mpd_utils.js +4 -5
  160. package/lib/dash/segment_list.js +1 -2
  161. package/lib/dash/segment_template.js +3 -3
  162. package/lib/hls/hls_parser.js +386 -749
  163. package/lib/hls/manifest_text_parser.js +5 -6
  164. package/lib/media/adaptation_set.js +1 -2
  165. package/lib/media/content_workarounds.js +2 -2
  166. package/lib/media/drm_engine.js +105 -55
  167. package/lib/media/gap_jumping_controller.js +10 -45
  168. package/lib/media/media_source_engine.js +90 -17
  169. package/lib/media/mp4_segment_index_parser.js +1 -4
  170. package/lib/media/playhead.js +4 -7
  171. package/lib/media/presentation_timeline.js +19 -10
  172. package/lib/media/segment_index.js +13 -46
  173. package/lib/media/segment_reference.js +47 -1
  174. package/lib/media/streaming_engine.js +35 -17
  175. package/lib/media/time_ranges_utils.js +5 -12
  176. package/lib/media/video_wrapper.js +1 -1
  177. package/lib/net/networking_engine.js +4 -1
  178. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  179. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  180. package/lib/offline/manifest_converter.js +1 -0
  181. package/lib/offline/storage.js +104 -136
  182. package/lib/player.js +50 -216
  183. package/lib/polyfill/all.js +2 -3
  184. package/lib/polyfill/encryption_scheme.js +1 -1
  185. package/lib/polyfill/media_capabilities.js +15 -9
  186. package/lib/polyfill/mediasource.js +0 -28
  187. package/lib/polyfill/patchedmediakeys_nop.js +2 -0
  188. package/lib/polyfill/patchedmediakeys_webkit.js +2 -0
  189. package/lib/polyfill/symbol.js +46 -0
  190. package/lib/text/cue.js +1 -7
  191. package/lib/text/lrc_text_parser.js +8 -0
  192. package/lib/text/mp4_ttml_parser.js +8 -0
  193. package/lib/text/mp4_vtt_parser.js +8 -0
  194. package/lib/text/sbv_text_parser.js +8 -0
  195. package/lib/text/simple_text_displayer.js +3 -8
  196. package/lib/text/srt_text_parser.js +8 -0
  197. package/lib/text/ssa_text_parser.js +8 -0
  198. package/lib/text/text_engine.js +19 -3
  199. package/lib/text/ttml_text_parser.js +16 -5
  200. package/lib/text/ui_text_displayer.js +2 -8
  201. package/lib/text/vtt_text_parser.js +24 -2
  202. package/lib/text/web_vtt_generator.js +1 -7
  203. package/lib/util/buffer_utils.js +1 -3
  204. package/lib/util/ebml_parser.js +3 -3
  205. package/lib/util/error.js +10 -14
  206. package/lib/util/fairplay_utils.js +109 -0
  207. package/lib/util/functional.js +0 -42
  208. package/lib/util/iterables.js +0 -46
  209. package/lib/util/mime_utils.js +2 -0
  210. package/lib/util/mp4_box_parsers.js +1 -4
  211. package/lib/util/mp4_parser.js +5 -7
  212. package/lib/util/periods.js +10 -11
  213. package/lib/util/platform.js +48 -3
  214. package/lib/util/player_configuration.js +12 -6
  215. package/lib/util/pssh.js +24 -8
  216. package/lib/util/stream_utils.js +96 -25
  217. package/lib/util/string_utils.js +3 -5
  218. package/lib/util/uint8array_utils.js +3 -21
  219. package/lib/util/xml_utils.js +16 -8
  220. package/package.json +61 -36
  221. package/roadmap.md +27 -27
  222. package/shaka-player.uncompiled.js +1 -3
  223. package/test/abr/simple_abr_manager_unit.js +3 -8
  224. package/test/ads/ad_manager_unit.js +0 -6
  225. package/test/cast/cast_proxy_unit.js +0 -8
  226. package/test/cast/cast_receiver_integration.js +1 -18
  227. package/test/cast/cast_receiver_unit.js +1 -9
  228. package/test/cast/cast_sender_unit.js +0 -6
  229. package/test/cast/cast_utils_unit.js +1 -13
  230. package/test/cea/cea608_memory_unit.js +0 -5
  231. package/test/cea/cea708_service_unit.js +0 -7
  232. package/test/cea/cea708_window_unit.js +0 -5
  233. package/test/cea/cea_decoder_unit.js +0 -6
  234. package/test/cea/dtvcc_packet_builder_unit.js +0 -3
  235. package/test/cea/dtvcc_packet_unit.js +0 -5
  236. package/test/cea/mp4_cea_parser_unit.js +0 -4
  237. package/test/dash/dash_parser_content_protection_unit.js +45 -10
  238. package/test/dash/dash_parser_live_unit.js +2 -15
  239. package/test/dash/dash_parser_manifest_unit.js +2 -40
  240. package/test/dash/dash_parser_segment_base_unit.js +1 -8
  241. package/test/dash/dash_parser_segment_list_unit.js +1 -7
  242. package/test/dash/dash_parser_segment_template_unit.js +3 -13
  243. package/test/dash/mpd_utils_unit.js +3 -11
  244. package/test/demo/demo_unit.js +2 -6
  245. package/test/deprecate/enforcer_unit.js +0 -4
  246. package/test/deprecate/version_unit.js +0 -2
  247. package/test/hls/hls_live_unit.js +101 -234
  248. package/test/hls/hls_parser_unit.js +517 -447
  249. package/test/hls/manifest_text_parser_unit.js +0 -10
  250. package/test/media/adaptation_set_criteria_unit.js +3 -7
  251. package/test/media/adaptation_set_unit.js +0 -2
  252. package/test/media/buffering_observer_unit.js +1 -4
  253. package/test/media/closed_caption_parser_unit.js +0 -3
  254. package/test/media/drm_engine_integration.js +2 -15
  255. package/test/media/drm_engine_unit.js +90 -144
  256. package/test/media/manifest_parser_unit.js +0 -4
  257. package/test/media/media_source_engine_integration.js +26 -10
  258. package/test/media/media_source_engine_unit.js +8 -20
  259. package/test/media/mp4_segment_index_parser_unit.js +0 -4
  260. package/test/media/play_rate_controller_unit.js +0 -4
  261. package/test/media/playhead_unit.js +35 -207
  262. package/test/media/presentation_timeline_unit.js +4 -7
  263. package/test/media/quality_observer_unit.js +0 -4
  264. package/test/media/region_observer_unit.js +1 -6
  265. package/test/media/region_timeline_unit.js +0 -3
  266. package/test/media/segment_index_unit.js +15 -31
  267. package/test/media/segment_reference_unit.js +0 -3
  268. package/test/media/stall_detector_unit.js +0 -5
  269. package/test/media/streaming_engine_integration.js +13 -75
  270. package/test/media/streaming_engine_unit.js +14 -37
  271. package/test/media/time_ranges_utils_unit.js +0 -7
  272. package/test/media/transmuxer_integration.js +0 -6
  273. package/test/media/webm_segment_index_parser_unit.js +0 -4
  274. package/test/net/data_uri_plugin_unit.js +0 -7
  275. package/test/net/http_plugin_unit.js +0 -9
  276. package/test/net/networking_engine_unit.js +0 -11
  277. package/test/offline/download_progress_estimator_unit.js +0 -2
  278. package/test/offline/indexeddb_storage_unit.js +0 -6
  279. package/test/offline/manifest_convert_unit.js +34 -12
  280. package/test/offline/offline_integration.js +1 -10
  281. package/test/offline/offline_manifest_parser_unit.js +1 -6
  282. package/test/offline/offline_scheme_unit.js +0 -8
  283. package/test/offline/offline_uri_unit.js +0 -2
  284. package/test/offline/storage_compatibility_unit.js +1 -14
  285. package/test/offline/storage_integration.js +6 -31
  286. package/test/player_external.js +1 -15
  287. package/test/player_integration.js +38 -28
  288. package/test/player_load_graph_integration.js +1 -6
  289. package/test/player_src_equals_external.js +3 -9
  290. package/test/player_src_equals_integration.js +0 -7
  291. package/test/player_unit.js +78 -37
  292. package/test/routing/walker_unit.js +0 -8
  293. package/test/test/assets/large_file.mpd +1 -1
  294. package/test/test/boot.js +75 -22
  295. package/test/test/externs/jasmine.js +2 -2
  296. package/test/test/externs/karma.js +4 -0
  297. package/test/test/namespace.js +10 -0
  298. package/test/test/util/canned_idb.js +0 -8
  299. package/test/test/util/cea_utils.js +0 -6
  300. package/test/test/util/dash_parser_util.js +0 -13
  301. package/test/test/util/fake_ad.js +0 -3
  302. package/test/test/util/fake_ad_manager.js +0 -9
  303. package/test/test/util/fake_demo_main.js +0 -5
  304. package/test/test/util/fake_drm_engine.js +0 -6
  305. package/test/test/util/fake_media_source_engine.js +11 -11
  306. package/test/test/util/fake_networking_engine.js +0 -5
  307. package/test/test/util/fake_text_displayer.js +0 -5
  308. package/test/test/util/indexeddb_utils.js +1 -10
  309. package/test/test/util/jasmine_fetch.js +0 -1
  310. package/test/test/util/loader.js +0 -18
  311. package/test/test/util/manifest_generator.js +12 -15
  312. package/test/test/util/manifest_parser_util.js +4 -7
  313. package/test/test/util/offline_utils.js +1 -5
  314. package/test/test/util/simple_fakes.js +9 -26
  315. package/test/test/util/stream_generator.js +0 -14
  316. package/test/test/util/streaming_engine_util.js +1 -15
  317. package/test/test/util/test_scheme.js +25 -20
  318. package/test/test/util/ttml_utils.js +0 -2
  319. package/test/test/util/ui_utils.js +0 -9
  320. package/test/test/util/util.js +9 -23
  321. package/test/test/util/waiter.js +84 -43
  322. package/test/text/cue_integration.js +2 -6
  323. package/test/text/lrc_text_parser_unit.js +6 -11
  324. package/test/text/mp4_ttml_parser_unit.js +6 -10
  325. package/test/text/mp4_vtt_parser_unit.js +7 -12
  326. package/test/text/sbv_text_parser_unit.js +5 -10
  327. package/test/text/simple_text_displayer_unit.js +1 -8
  328. package/test/text/srt_text_parser_unit.js +5 -10
  329. package/test/text/ssa_text_parser_unit.js +17 -34
  330. package/test/text/text_engine_unit.js +47 -12
  331. package/test/text/text_track_integration.js +0 -2
  332. package/test/text/ttml_text_parser_unit.js +79 -88
  333. package/test/text/ui_text_displayer_unit.js +44 -41
  334. package/test/text/vtt_text_parser_unit.js +55 -66
  335. package/test/text/web_vtt_generator_unit.js +0 -3
  336. package/test/ui/ad_ui_unit.js +0 -11
  337. package/test/ui/localization_unit.js +0 -3
  338. package/test/ui/text_displayer_layout_unit.js +1 -12
  339. package/test/ui/ui_customization_unit.js +0 -6
  340. package/test/ui/ui_integration.js +0 -32
  341. package/test/ui/ui_unit.js +28 -17
  342. package/test/util/abortable_operation_unit.js +4 -9
  343. package/test/util/array_utils_unit.js +0 -2
  344. package/test/util/buffer_utils_unit.js +0 -2
  345. package/test/util/cmcd_manager_unit.js +3 -3
  346. package/test/util/data_view_reader_unit.js +0 -4
  347. package/test/util/ebml_parser_unit.js +0 -4
  348. package/test/util/event_manager_unit.js +0 -3
  349. package/test/util/fake_event_target_unit.js +0 -5
  350. package/test/util/iterables_unit.js +0 -64
  351. package/test/util/language_utils_unit.js +0 -3
  352. package/test/util/manifest_parser_utils_unit.js +0 -2
  353. package/test/util/mp4_box_parsers_unit.js +0 -5
  354. package/test/util/mp4_parser_unit.js +0 -4
  355. package/test/util/multi_map_unit.js +0 -3
  356. package/test/util/object_utils_unit.js +0 -2
  357. package/test/util/periods_unit.js +14 -7
  358. package/test/util/pssh_unit.js +0 -5
  359. package/test/util/state_history_unit.js +0 -2
  360. package/test/util/stream_utils_unit.js +26 -15
  361. package/test/util/string_utils_unit.js +0 -6
  362. package/test/util/text_parser_unit.js +0 -2
  363. package/test/util/xml_utils_unit.js +0 -4
  364. package/ui/controls.js +65 -40
  365. package/ui/fullscreen_button.js +26 -6
  366. package/ui/less/ad_controls.less +6 -6
  367. package/ui/less/buttons.less +6 -6
  368. package/ui/less/containers.less +10 -12
  369. package/ui/less/overflow_menu.less +4 -4
  370. package/ui/less/range_elements.less +7 -2
  371. package/ui/less/tooltip.less +4 -4
  372. package/ui/locales/en.json +1 -0
  373. package/ui/locales/sjn-translations.yaml +257 -0
  374. package/ui/locales/sjn.json +42 -0
  375. package/ui/locales/source.json +5 -0
  376. package/ui/resolution_selection.js +31 -33
  377. package/ui/seek_bar.js +2 -1
  378. package/ui/ui.js +0 -17
  379. package/.github/workflows/build_and_test.yaml +0 -73
  380. package/.github/workflows/release.yaml +0 -53
  381. package/docs/tutorials/upgrade-v2-5.md +0 -441
  382. package/docs/tutorials/upgrade-v3.md +0 -38
  383. package/externs/svgelement.js +0 -18
  384. package/externs/webcrypto.js +0 -18
  385. package/lib/polyfill/mathround.js +0 -49
  386. package/lib/polyfill/patchedmediakeys_apple.js +0 -744
  387. package/lib/polyfill/patchedmediakeys_ms.js +0 -698
  388. package/lib/util/mutex.js +0 -52
  389. package/test/util/functional_unit.js +0 -82
@@ -18,6 +18,7 @@ goog.require('shaka.log');
18
18
  goog.require('shaka.media.DrmEngine');
19
19
  goog.require('shaka.media.InitSegmentReference');
20
20
  goog.require('shaka.media.ManifestParser');
21
+ goog.require('shaka.media.MediaSourceEngine');
21
22
  goog.require('shaka.media.PresentationTimeline');
22
23
  goog.require('shaka.media.SegmentIndex');
23
24
  goog.require('shaka.media.SegmentReference');
@@ -26,20 +27,17 @@ goog.require('shaka.net.NetworkingEngine');
26
27
  goog.require('shaka.util.ArrayUtils');
27
28
  goog.require('shaka.util.BufferUtils');
28
29
  goog.require('shaka.util.CmcdManager');
29
- goog.require('shaka.util.DataViewReader');
30
30
  goog.require('shaka.util.Error');
31
31
  goog.require('shaka.util.FakeEvent');
32
32
  goog.require('shaka.util.Functional');
33
- goog.require('shaka.util.Iterables');
34
33
  goog.require('shaka.util.LanguageUtils');
35
34
  goog.require('shaka.util.ManifestParserUtils');
36
35
  goog.require('shaka.util.MimeUtils');
37
- goog.require('shaka.util.Mp4Parser');
38
- goog.require('shaka.util.Mp4BoxParsers');
39
- goog.require('shaka.util.Networking');
40
36
  goog.require('shaka.util.OperationManager');
41
37
  goog.require('shaka.util.Pssh');
42
38
  goog.require('shaka.util.Timer');
39
+ goog.require('shaka.util.Platform');
40
+ goog.require('shaka.util.XmlUtils');
43
41
  goog.requireType('shaka.hls.Segment');
44
42
 
45
43
 
@@ -96,9 +94,6 @@ shaka.hls.HlsParser = class {
96
94
  * createStreamInfoFromMediaTag_, createStreamInfoFromImageTag_ and
97
95
  * createStreamInfoFromVariantTag_.
98
96
  *
99
- * During parsing of updates, used by getStartTime_ to determine the start
100
- * time of the first segment from existing segment references.
101
- *
102
97
  * @private {!Map.<string, shaka.hls.HlsParser.StreamInfo>}
103
98
  */
104
99
  this.uriToStreamInfosMap_ = new Map();
@@ -125,6 +120,15 @@ shaka.hls.HlsParser = class {
125
120
  */
126
121
  this.updatePlaylistDelay_ = 0;
127
122
 
123
+ /**
124
+ * A time offset to apply to EXT-X-PROGRAM-DATE-TIME values to normalize
125
+ * them so that they start at 0. This is necessary because these times will
126
+ * be used to set presentation times for segments.
127
+ * null means we don't have enough data yet.
128
+ * @private {?number}
129
+ */
130
+ this.syncTimeOffset_ = null;
131
+
128
132
  /**
129
133
  * This timer is used to trigger the start of a manifest update. A manifest
130
134
  * update is async. Once the update is finished, the timer will be restarted
@@ -176,9 +180,6 @@ shaka.hls.HlsParser = class {
176
180
  /** @private {Map.<string, string>} */
177
181
  this.groupIdToCodecsMap_ = new Map();
178
182
 
179
- /** @private {?number} */
180
- this.playlistStartTime_ = null;
181
-
182
183
  /** A cache mapping EXT-X-MAP tag info to the InitSegmentReference created
183
184
  * from the tag.
184
185
  * The key is a string combining the EXT-X-MAP tag's absolute uri, and
@@ -186,15 +187,6 @@ shaka.hls.HlsParser = class {
186
187
  * {!Map.<string, !shaka.media.InitSegmentReference>} */
187
188
  this.mapTagToInitSegmentRefMap_ = new Map();
188
189
 
189
- /**
190
- * A cache mapping a discontinuity sequence number of a segment with
191
- * EXT-X-DISCONTINUITY tag into its timestamp offset.
192
- * Key: the discontinuity sequence number of a segment
193
- * Value: the segment reference's timestamp offset.
194
- * {!Map.<number, number>}
195
- */
196
- this.discontinuityToTso_ = new Map();
197
-
198
190
  /** @private {boolean} */
199
191
  this.lowLatencyMode_ = false;
200
192
  }
@@ -223,7 +215,7 @@ shaka.hls.HlsParser = class {
223
215
  this.masterPlaylistUri_ = response.uri;
224
216
 
225
217
  goog.asserts.assert(response.data, 'Response data should be non-null!');
226
- await this.parseManifest_(response.data);
218
+ await this.parseManifest_(response.data, uri);
227
219
 
228
220
  // Start the update timer if we want updates.
229
221
  const delay = this.updatePlaylistDelay_;
@@ -278,8 +270,6 @@ shaka.hls.HlsParser = class {
278
270
 
279
271
  /** @type {!Array.<!Promise>} */
280
272
  const updates = [];
281
- // Reset the start time for the new media playlist.
282
- this.playlistStartTime_ = null;
283
273
  const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
284
274
  // Wait for the first stream info created, so that the start time is fetched
285
275
  // and can be reused.
@@ -330,11 +320,10 @@ shaka.hls.HlsParser = class {
330
320
 
331
321
  const stream = streamInfo.stream;
332
322
 
333
- const segments = await this.createSegments_(
323
+ const segments = this.createSegments_(
334
324
  streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
335
325
  stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
336
- streamInfo.discontinuityToMediaSequence, stream.codecs,
337
- stream.bandwidth);
326
+ stream.codecs);
338
327
 
339
328
  stream.segmentIndex.mergeAndEvict(
340
329
  segments, this.presentationTimeline_.getSegmentAvailabilityStart());
@@ -371,14 +360,51 @@ shaka.hls.HlsParser = class {
371
360
  // No-op
372
361
  }
373
362
 
363
+ /**
364
+ * If necessary, makes sure that sync times will be normalized to 0, so that
365
+ * a stream does not start buffering at 50 years in because sync times are
366
+ * measured in time since 1970.
367
+ * @private
368
+ */
369
+ calculateSyncTimeOffset_() {
370
+ if (this.syncTimeOffset_ != null) {
371
+ // The offset was already calculated.
372
+ return;
373
+ }
374
+
375
+ const segments = new Set();
376
+ let lowestSyncTime = Infinity;
377
+ for (const streamInfo of this.uriToStreamInfosMap_.values()) {
378
+ const segmentIndex = streamInfo.stream.segmentIndex;
379
+ if (segmentIndex) {
380
+ segmentIndex.forEachTopLevelReference((segment) => {
381
+ if (segment.syncTime != null) {
382
+ lowestSyncTime = Math.min(lowestSyncTime, segment.syncTime);
383
+ segments.add(segment);
384
+ }
385
+ });
386
+ }
387
+ }
388
+ if (segments.size > 0) {
389
+ this.syncTimeOffset_ = -lowestSyncTime;
390
+ for (const segment of segments) {
391
+ segment.syncTime += this.syncTimeOffset_;
392
+ for (const partial of segment.partialReferences) {
393
+ partial.syncTime += this.syncTimeOffset_;
394
+ }
395
+ }
396
+ }
397
+ }
398
+
374
399
  /**
375
400
  * Parses the manifest.
376
401
  *
377
402
  * @param {BufferSource} data
403
+ * @param {string} uri
378
404
  * @return {!Promise}
379
405
  * @private
380
406
  */
381
- async parseManifest_(data) {
407
+ async parseManifest_(data, uri) {
382
408
  const Utils = shaka.hls.Utils;
383
409
 
384
410
  goog.asserts.assert(this.masterPlaylistUri_,
@@ -387,63 +413,106 @@ shaka.hls.HlsParser = class {
387
413
  const playlist = this.manifestTextParser_.parsePlaylist(
388
414
  data, this.masterPlaylistUri_);
389
415
 
390
- // We don't support directly providing a Media Playlist.
391
- // See the error code for details.
392
- if (playlist.type != shaka.hls.PlaylistType.MASTER) {
393
- throw new shaka.util.Error(
394
- shaka.util.Error.Severity.CRITICAL,
395
- shaka.util.Error.Category.MANIFEST,
396
- shaka.util.Error.Code.HLS_MASTER_PLAYLIST_NOT_PROVIDED);
397
- }
398
-
399
416
  /** @type {!Array.<!shaka.hls.Tag>} */
400
417
  const variablesTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-DEFINE');
401
418
 
402
419
  this.parseMasterVariables_(variablesTags);
403
420
 
404
- /** @type {!Array.<!shaka.hls.Tag>} */
405
- const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
406
- /** @type {!Array.<!shaka.hls.Tag>} */
407
- const variantTags = Utils.filterTagsByName(
408
- playlist.tags, 'EXT-X-STREAM-INF');
409
- /** @type {!Array.<!shaka.hls.Tag>} */
410
- const imageTags = Utils.filterTagsByName(
411
- playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
412
-
413
- this.parseCodecs_(variantTags);
414
-
415
- /** @type {!Array.<!shaka.hls.Tag>} */
416
- const sesionDataTags =
417
- Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
418
- for (const tag of sesionDataTags) {
419
- const id = tag.getAttributeValue('DATA-ID');
420
- const uri = tag.getAttributeValue('URI');
421
- const language = tag.getAttributeValue('LANGUAGE');
422
- const value = tag.getAttributeValue('VALUE');
423
- const data = (new Map()).set('id', id);
424
- if (uri) {
425
- data.set('uri',
426
- shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
427
- }
428
- if (language) {
429
- data.set('language', language);
430
- }
431
- if (value) {
432
- data.set('value', value);
433
- }
434
- const event = new shaka.util.FakeEvent('sessiondata', data);
435
- if (this.playerInterface_) {
436
- this.playerInterface_.onEvent(event);
421
+ /** @type {!Array.<!shaka.extern.Variant>} */
422
+ let variants = [];
423
+ /** @type {!Array.<!shaka.extern.Stream>} */
424
+ let textStreams = [];
425
+ /** @type {!Array.<!shaka.extern.Stream>} */
426
+ let imageStreams = [];
427
+
428
+ // Parsing a media playlist results in a single-variant stream.
429
+ if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
430
+ // Get necessary info for this stream, from the config. These are things
431
+ // we would normally find from the master playlist (e.g. from values on
432
+ // EXT-X-MEDIA tags).
433
+ const fullMimeType = this.config_.hls.mediaPlaylistFullMimeType;
434
+ const mimeType = shaka.util.MimeUtils.getBasicType(fullMimeType);
435
+ const type = mimeType.split('/')[0];
436
+ const codecs = shaka.util.MimeUtils.getCodecs(fullMimeType);
437
+
438
+ // Some values we cannot figure out, and aren't important enough to ask
439
+ // the user to provide through config values. A lot of these are only
440
+ // relevant to ABR, which isn't necessary if there's only one variant.
441
+ // So these unknowns should be set to false or null, largely.
442
+ const language = '';
443
+ const channelsCount = null;
444
+ const spatialAudio = false;
445
+ const characteristics = null;
446
+ const closedCaptions = new Map();
447
+ const forced = false; // Only relevant for text.
448
+ const primary = true; // This is the only stream!
449
+ const name = 'Media Playlist';
450
+
451
+ // Make the stream info, with those values.
452
+ const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
453
+ playlist, uri, uri, codecs, type, language, primary, name,
454
+ channelsCount, closedCaptions, characteristics, forced, spatialAudio,
455
+ mimeType);
456
+ goog.asserts.assert(streamInfo != null, 'StreamInfo is null!');
457
+ this.uriToStreamInfosMap_.set(uri, streamInfo);
458
+
459
+ // Wrap the stream from that stream info with a variant.
460
+ variants.push({
461
+ id: 0,
462
+ language: 'und',
463
+ primary: true,
464
+ audio: type == 'audio' ? streamInfo.stream : null,
465
+ video: type == 'video' ? streamInfo.stream : null,
466
+ bandwidth: 0,
467
+ allowedByApplication: true,
468
+ allowedByKeySystem: true,
469
+ decodingInfos: [],
470
+ });
471
+ } else {
472
+ /** @type {!Array.<!shaka.hls.Tag>} */
473
+ const mediaTags = Utils.filterTagsByName(playlist.tags, 'EXT-X-MEDIA');
474
+ /** @type {!Array.<!shaka.hls.Tag>} */
475
+ const variantTags = Utils.filterTagsByName(
476
+ playlist.tags, 'EXT-X-STREAM-INF');
477
+ /** @type {!Array.<!shaka.hls.Tag>} */
478
+ const imageTags = Utils.filterTagsByName(
479
+ playlist.tags, 'EXT-X-IMAGE-STREAM-INF');
480
+
481
+ this.parseCodecs_(variantTags);
482
+
483
+ /** @type {!Array.<!shaka.hls.Tag>} */
484
+ const sesionDataTags =
485
+ Utils.filterTagsByName(playlist.tags, 'EXT-X-SESSION-DATA');
486
+ for (const tag of sesionDataTags) {
487
+ const id = tag.getAttributeValue('DATA-ID');
488
+ const uri = tag.getAttributeValue('URI');
489
+ const language = tag.getAttributeValue('LANGUAGE');
490
+ const value = tag.getAttributeValue('VALUE');
491
+ const data = (new Map()).set('id', id);
492
+ if (uri) {
493
+ data.set('uri', shaka.hls.Utils.constructAbsoluteUri(
494
+ this.masterPlaylistUri_, uri));
495
+ }
496
+ if (language) {
497
+ data.set('language', language);
498
+ }
499
+ if (value) {
500
+ data.set('value', value);
501
+ }
502
+ const event = new shaka.util.FakeEvent('sessiondata', data);
503
+ if (this.playerInterface_) {
504
+ this.playerInterface_.onEvent(event);
505
+ }
437
506
  }
438
- }
439
507
 
440
- // Parse audio and video media tags first, so that we can extract segment
441
- // start time from audio/video streams and reuse for text streams.
442
- await this.createStreamInfosFromMediaTags_(mediaTags);
443
- this.parseClosedCaptions_(mediaTags);
444
- const variants = await this.createVariantsForTags_(variantTags);
445
- const textStreams = await this.parseTexts_(mediaTags);
446
- const imageStreams = await this.parseImages_(imageTags);
508
+ // Parse audio and video media tags first, so that we can extract segment
509
+ // start time from audio/video streams and reuse for text streams.
510
+ await this.createStreamInfosFromMediaTags_(mediaTags);
511
+ this.parseClosedCaptions_(mediaTags);
512
+ variants = await this.createVariantsForTags_(variantTags);
513
+ textStreams = await this.parseTexts_(mediaTags);
514
+ imageStreams = await this.parseImages_(imageTags);
515
+ }
447
516
 
448
517
  // Make sure that the parser has not been destroyed.
449
518
  if (!this.playerInterface_) {
@@ -453,6 +522,10 @@ shaka.hls.HlsParser = class {
453
522
  shaka.util.Error.Code.OPERATION_ABORTED);
454
523
  }
455
524
 
525
+ // Now that we have generated all streams, we can determine the offset to
526
+ // apply to sync times.
527
+ this.calculateSyncTimeOffset_();
528
+
456
529
  if (this.aesEncrypted_ && variants.length == 0) {
457
530
  // We do not support AES-128 encryption with HLS yet. Variants is null
458
531
  // when the playlist is encrypted with AES-128.
@@ -565,6 +638,7 @@ shaka.hls.HlsParser = class {
565
638
  imageStreams,
566
639
  offlineSessionIds: [],
567
640
  minBufferTime: 0,
641
+ sequenceMode: true,
568
642
  };
569
643
  this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
570
644
  }
@@ -765,7 +839,7 @@ shaka.hls.HlsParser = class {
765
839
  const videoRange = tag.getAttributeValue('VIDEO-RANGE');
766
840
 
767
841
  const streamInfos = await this.createStreamInfosForVariantTag_(tag,
768
- resolution, frameRate, bandwidth);
842
+ resolution, frameRate);
769
843
 
770
844
  if (streamInfos) {
771
845
  goog.asserts.assert(streamInfos.audio.length ||
@@ -799,11 +873,10 @@ shaka.hls.HlsParser = class {
799
873
  * @param {!shaka.hls.Tag} tag
800
874
  * @param {?string} resolution
801
875
  * @param {?string} frameRate
802
- * @param {number} bandwidth
803
876
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfos>}
804
877
  * @private
805
878
  */
806
- async createStreamInfosForVariantTag_(tag, resolution, frameRate, bandwidth) {
879
+ async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
807
880
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
808
881
  /** @type {!Array.<string>} */
809
882
  let allCodecs = this.getCodecsForVariantTag_(tag);
@@ -873,8 +946,7 @@ shaka.hls.HlsParser = class {
873
946
  let streamInfo;
874
947
  if (!ignoreStream) {
875
948
  streamInfo =
876
- await this.createStreamInfoFromVariantTag_(tag, allCodecs, type,
877
- bandwidth);
949
+ await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
878
950
  }
879
951
  if (streamInfo) {
880
952
  res[streamInfo.stream.type] = [streamInfo];
@@ -919,7 +991,7 @@ shaka.hls.HlsParser = class {
919
991
  // the content. This means that if the content changes profiles, it should
920
992
  // include both. Since all known browsers support changing profiles
921
993
  // without any other work, just ignore them. See also:
922
- // https://github.com/google/shaka-player/issues/1817
994
+ // https://github.com/shaka-project/shaka-player/issues/1817
923
995
  const shortCodec = shaka.util.MimeUtils.getCodecBase(codec);
924
996
  if (!seen.has(shortCodec)) {
925
997
  ret.push(codec);
@@ -1315,11 +1387,10 @@ shaka.hls.HlsParser = class {
1315
1387
  * @param {!shaka.hls.Tag} tag
1316
1388
  * @param {!Array.<string>} allCodecs
1317
1389
  * @param {string} type
1318
- * @param {number} bandwidth
1319
1390
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1320
1391
  * @private
1321
1392
  */
1322
- async createStreamInfoFromVariantTag_(tag, allCodecs, type, bandwidth) {
1393
+ async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
1323
1394
  goog.asserts.assert(tag.name == 'EXT-X-STREAM-INF',
1324
1395
  'Should only be called on variant tags!');
1325
1396
  const verbatimMediaPlaylistUri = this.variableSubstitution_(
@@ -1335,7 +1406,7 @@ shaka.hls.HlsParser = class {
1335
1406
  codecs, type, /* language= */ 'und', /* primary= */ false,
1336
1407
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1337
1408
  /* characteristics= */ null, /* forced= */ false,
1338
- /* spatialAudio= */ false, bandwidth);
1409
+ /* spatialAudio= */ false);
1339
1410
  if (streamInfo == null) {
1340
1411
  return null;
1341
1412
  }
@@ -1362,13 +1433,12 @@ shaka.hls.HlsParser = class {
1362
1433
  * @param {?string} characteristics
1363
1434
  * @param {boolean} forced
1364
1435
  * @param {boolean} spatialAudio
1365
- * @param {(number|undefined)} bandwidth
1366
1436
  * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1367
1437
  * @private
1368
1438
  */
1369
1439
  async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1370
1440
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1371
- spatialAudio, bandwidth = undefined) {
1441
+ spatialAudio) {
1372
1442
  // TODO: Refactor, too many parameters
1373
1443
  let absoluteMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1374
1444
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1382,6 +1452,34 @@ shaka.hls.HlsParser = class {
1382
1452
  const playlist = this.manifestTextParser_.parsePlaylist(
1383
1453
  response.data, absoluteMediaPlaylistUri);
1384
1454
 
1455
+ return this.convertParsedPlaylistIntoStreamInfo_(playlist,
1456
+ verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs, type,
1457
+ language, primary, name, channelsCount, closedCaptions, characteristics,
1458
+ forced, spatialAudio);
1459
+ }
1460
+
1461
+ /**
1462
+ * @param {!shaka.hls.Playlist} playlist
1463
+ * @param {string} verbatimMediaPlaylistUri
1464
+ * @param {string} absoluteMediaPlaylistUri
1465
+ * @param {string} codecs
1466
+ * @param {string} type
1467
+ * @param {string} language
1468
+ * @param {boolean} primary
1469
+ * @param {?string} name
1470
+ * @param {?number} channelsCount
1471
+ * @param {Map.<string, string>} closedCaptions
1472
+ * @param {?string} characteristics
1473
+ * @param {boolean} forced
1474
+ * @param {boolean} spatialAudio
1475
+ * @param {(string|undefined)} mimeType
1476
+ * @return {!Promise.<?shaka.hls.HlsParser.StreamInfo>}
1477
+ * @private
1478
+ */
1479
+ async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
1480
+ absoluteMediaPlaylistUri, codecs, type, language, primary, name,
1481
+ channelsCount, closedCaptions, characteristics, forced, spatialAudio,
1482
+ mimeType = undefined) {
1385
1483
  if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
1386
1484
  // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
1387
1485
  // playlists.
@@ -1391,6 +1489,22 @@ shaka.hls.HlsParser = class {
1391
1489
  shaka.util.Error.Code.HLS_INVALID_PLAYLIST_HIERARCHY);
1392
1490
  }
1393
1491
 
1492
+ /** @type {!Array.<!shaka.hls.Tag>} */
1493
+ const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
1494
+ 'EXT-X-DEFINE');
1495
+
1496
+ const mediaVariables = this.parseMediaVariables_(variablesTags);
1497
+
1498
+ goog.asserts.assert(playlist.segments != null,
1499
+ 'Media playlist should have segments!');
1500
+
1501
+ this.determinePresentationType_(playlist);
1502
+
1503
+ if (!mimeType) {
1504
+ mimeType = await this.guessMimeType_(type, codecs, playlist,
1505
+ mediaVariables);
1506
+ }
1507
+
1394
1508
  /** @type {!Array.<!shaka.hls.Tag>} */
1395
1509
  const drmTags = [];
1396
1510
  if (playlist.segments) {
@@ -1425,7 +1539,7 @@ shaka.hls.HlsParser = class {
1425
1539
  const drmParser =
1426
1540
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_[keyFormat];
1427
1541
 
1428
- const drmInfo = drmParser ? drmParser(drmTag) : null;
1542
+ const drmInfo = drmParser ? drmParser(drmTag, mimeType) : null;
1429
1543
  if (drmInfo) {
1430
1544
  if (drmInfo.keyIds) {
1431
1545
  for (const keyId of drmInfo.keyIds) {
@@ -1446,47 +1560,19 @@ shaka.hls.HlsParser = class {
1446
1560
  shaka.util.Error.Code.HLS_KEYFORMATS_NOT_SUPPORTED);
1447
1561
  }
1448
1562
 
1449
- /** @type {!Array.<!shaka.hls.Tag>} */
1450
- const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
1451
- 'EXT-X-DEFINE');
1452
-
1453
- const mediaVariables = this.parseMediaVariables_(variablesTags);
1454
-
1455
- goog.asserts.assert(playlist.segments != null,
1456
- 'Media playlist should have segments!');
1457
-
1458
- this.determinePresentationType_(playlist);
1459
-
1460
- /** @type {string} */
1461
- const mimeType = await this.guessMimeType_(type, codecs, playlist,
1462
- mediaVariables);
1463
-
1464
1563
  // MediaSource expects no codec strings combined with raw formats.
1465
- // TODO(#2337): Instead, create a Stream flag indicating a raw format.
1466
- if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
1564
+ if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(mimeType)) {
1467
1565
  codecs = '';
1468
1566
  }
1469
1567
 
1470
1568
  /** @type {!Map.<number, number>} */
1471
1569
  const mediaSequenceToStartTime = new Map();
1472
1570
 
1473
- /**
1474
- * A map of a discontinuity sequence number, to the first segment's media
1475
- * sequence number with the discontinuity sequence number.
1476
- * Key: the discontinuity sequence number of a few segments
1477
- * Value: the first segment's media sequence number of the segments with
1478
- * this discontinuity sequence number.
1479
- * Used to get the discontinuity sequence number with playlist delta
1480
- * updates with lowLatencyMode enabled.
1481
- * {!Map.<number, number>}
1482
- */
1483
- const discontinuityToMediaSequence = new Map();
1484
-
1485
1571
  let segments;
1486
1572
  try {
1487
- segments = await this.createSegments_(verbatimMediaPlaylistUri,
1573
+ segments = this.createSegments_(verbatimMediaPlaylistUri,
1488
1574
  playlist, type, mimeType, mediaSequenceToStartTime, mediaVariables,
1489
- discontinuityToMediaSequence, codecs, bandwidth);
1575
+ codecs);
1490
1576
  } catch (error) {
1491
1577
  if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
1492
1578
  shaka.log.alwaysWarn('Skipping unsupported HLS stream',
@@ -1558,7 +1644,6 @@ shaka.hls.HlsParser = class {
1558
1644
  minTimestamp,
1559
1645
  maxTimestamp: lastEndTime,
1560
1646
  mediaSequenceToStartTime,
1561
- discontinuityToMediaSequence,
1562
1647
  canSkipSegments,
1563
1648
  };
1564
1649
  }
@@ -1737,16 +1822,15 @@ shaka.hls.HlsParser = class {
1737
1822
  * @param {shaka.media.SegmentReference} previousReference
1738
1823
  * @param {!shaka.hls.Segment} hlsSegment
1739
1824
  * @param {number} startTime
1740
- * @param {number} timestampOffset
1741
1825
  * @param {!Map.<string, string>} variables
1742
1826
  * @param {string} absoluteMediaPlaylistUri
1743
- * @return {!shaka.media.SegmentReference}
1744
1827
  * @param {string} type
1828
+ * @return {!shaka.media.SegmentReference}
1745
1829
  * @private
1746
1830
  */
1747
1831
  createSegmentReference_(
1748
1832
  initSegmentReference, previousReference, hlsSegment, startTime,
1749
- timestampOffset, variables, absoluteMediaPlaylistUri, type) {
1833
+ variables, absoluteMediaPlaylistUri, type) {
1750
1834
  const tags = hlsSegment.tags;
1751
1835
  const absoluteSegmentUri = this.variableSubstitution_(
1752
1836
  hlsSegment.absoluteUri, variables);
@@ -1757,11 +1841,40 @@ shaka.hls.HlsParser = class {
1757
1841
  let startByte = 0;
1758
1842
  let endByte = null;
1759
1843
 
1844
+ if (hlsSegment.partialSegments.length && !this.lowLatencyMode_) {
1845
+ shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
1846
+ 'low-latency streaming mode is not enabled in Shaka ' +
1847
+ 'Player. Set streaming.lowLatencyMode configuration to ' +
1848
+ 'true, and see https://bit.ly/3clctcj for details.');
1849
+ }
1850
+
1851
+ let syncTime = null;
1852
+ if (!this.config_.hls.ignoreManifestProgramDateTime) {
1853
+ const dateTimeTag =
1854
+ shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-PROGRAM-DATE-TIME');
1855
+ if (dateTimeTag && dateTimeTag.value) {
1856
+ const time = shaka.util.XmlUtils.parseDate(dateTimeTag.value);
1857
+ goog.asserts.assert(time != null,
1858
+ 'EXT-X-PROGRAM-DATE-TIME format not valid');
1859
+ // Sync time offset is null on the first go-through. This indicates that
1860
+ // we have not yet seen every stream, and thus do not yet have enough
1861
+ // information to determine how to normalize the sync times.
1862
+ // For that first go-through, the sync time will be applied after the
1863
+ // references are all created. Until then, just offset by 0.
1864
+ syncTime = time + (this.syncTimeOffset_ || 0);
1865
+ }
1866
+ }
1867
+
1868
+ let status = shaka.media.SegmentReference.Status.AVAILABLE;
1869
+ if (shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-GAP')) {
1870
+ status = shaka.media.SegmentReference.Status.MISSING;
1871
+ }
1872
+
1760
1873
  // Create SegmentReferences for the partial segments.
1761
1874
  const partialSegmentRefs = [];
1762
1875
  if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
1763
- const enumerate = (it) => shaka.util.Iterables.enumerate(it);
1764
- for (const {i, item} of enumerate(hlsSegment.partialSegments)) {
1876
+ for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
1877
+ const item = hlsSegment.partialSegments[i];
1765
1878
  const pPreviousReference = i == 0 ?
1766
1879
  previousReference : partialSegmentRefs[partialSegmentRefs.length - 1];
1767
1880
  const pStartTime = (i == 0) ? startTime : pPreviousReference.endTime;
@@ -1782,6 +1895,9 @@ shaka.hls.HlsParser = class {
1782
1895
  this.parseByteRange_(pPreviousReference, pByterange);
1783
1896
  }
1784
1897
  const pUri = item.getAttributeValue('URI');
1898
+ if (!pUri) {
1899
+ continue;
1900
+ }
1785
1901
  const pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
1786
1902
  absoluteMediaPlaylistUri, pUri);
1787
1903
 
@@ -1792,7 +1908,7 @@ shaka.hls.HlsParser = class {
1792
1908
  pStartByte,
1793
1909
  pEndByte,
1794
1910
  initSegmentReference,
1795
- timestampOffset,
1911
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1796
1912
  /* appendWindowStart= */ 0,
1797
1913
  /* appendWindowEnd= */ Infinity);
1798
1914
  partialSegmentRefs.push(partial);
@@ -1860,12 +1976,14 @@ shaka.hls.HlsParser = class {
1860
1976
  startByte,
1861
1977
  endByte,
1862
1978
  initSegmentReference,
1863
- timestampOffset,
1979
+ /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1864
1980
  /* appendWindowStart= */ 0,
1865
1981
  /* appendWindowEnd= */ Infinity,
1866
1982
  partialSegmentRefs,
1867
1983
  tilesLayout,
1868
1984
  tileDuration,
1985
+ syncTime,
1986
+ status,
1869
1987
  );
1870
1988
  }
1871
1989
 
@@ -1919,15 +2037,12 @@ shaka.hls.HlsParser = class {
1919
2037
  * @param {string} mimeType
1920
2038
  * @param {!Map.<number, number>} mediaSequenceToStartTime
1921
2039
  * @param {!Map.<string, string>} variables
1922
- * @param {!Map.<number, number>} discontinuityToMediaSequence
1923
2040
  * @param {string} codecs
1924
- * @param {(number|undefined)} bandwidth
1925
- * @return {!Promise<!Array.<!shaka.media.SegmentReference>>}
2041
+ * @return {!Array.<!shaka.media.SegmentReference>}
1926
2042
  * @private
1927
2043
  */
1928
- async createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
1929
- mediaSequenceToStartTime, variables, discontinuityToMediaSequence,
1930
- codecs, bandwidth) {
2044
+ createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
2045
+ mediaSequenceToStartTime, variables, codecs) {
1931
2046
  /** @type {Array.<!shaka.hls.Segment>} */
1932
2047
  const hlsSegments = playlist.segments;
1933
2048
  goog.asserts.assert(hlsSegments.length, 'Playlist should have segments!');
@@ -1944,69 +2059,26 @@ shaka.hls.HlsParser = class {
1944
2059
  const skippedSegments =
1945
2060
  skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
1946
2061
  let position = mediaSequenceNumber + skippedSegments;
1947
- let firstStartTime;
2062
+ let firstStartTime = 0;
2063
+
1948
2064
  // For live stream, use the cached value in the mediaSequenceToStartTime
1949
2065
  // map if available.
1950
- // Since createSegments_() is asynchronous and we are updating the streams
1951
- // in parallel, the global playlistStartTime_ may get updated by other
1952
- // playlist updates rather than the current one.
1953
-
1954
2066
  if (this.isLive_() && mediaSequenceToStartTime.has(position)) {
1955
2067
  firstStartTime = mediaSequenceToStartTime.get(position);
1956
- } else {
1957
- if (this.playlistStartTime_ == null) {
1958
- // For VOD and EVENT playlists, all variants must start at the same
1959
- // time, so we can fetch the start time once and reuse for the others.
1960
- // This is not guaranteed when updating a LIVE stream. We assume the
1961
- // first segment in each live playlist is no more than one segment out
1962
- // of sync with the other playlists, so we can fetch the start time for
1963
- // once.
1964
- initSegmentRef = this.getInitSegmentReference_(
1965
- playlist.absoluteUri, hlsSegments[0].tags, variables);
1966
- goog.asserts.assert(
1967
- type != shaka.util.ManifestParserUtils.ContentType.TEXT &&
1968
- type != shaka.util.ManifestParserUtils.ContentType.IMAGE,
1969
- 'Should only get start time from audio or video streams');
1970
- this.playlistStartTime_ = await this.getStartTime_(
1971
- verbatimMediaPlaylistUri, initSegmentRef, mimeType,
1972
- position, /* isDiscontinuity= */ false,
1973
- hlsSegments[0], variables, type, codecs, bandwidth);
1974
- }
1975
- firstStartTime = this.playlistStartTime_;
1976
2068
  }
1977
2069
 
1978
2070
  const firstSegmentUri = hlsSegments[0].absoluteUri;
1979
2071
  shaka.log.debug('First segment', firstSegmentUri.split('/').pop(),
1980
2072
  'starts at', firstStartTime);
1981
2073
 
1982
- let discontintuitySequenceNum = shaka.hls.Utils.getFirstTagWithNameAsNumber(
1983
- playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE');
1984
- if (this.lowLatencyMode_) {
1985
- if (!discontinuityToMediaSequence.has(discontintuitySequenceNum)) {
1986
- discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
1987
- }
1988
- if (skippedSegments) {
1989
- // With delta updates, the DISCONTINUITY may be skipped. Check if
1990
- // the discontintuity Sequence Number based on the media sequence
1991
- // number.
1992
- while (discontinuityToMediaSequence.has(discontintuitySequenceNum + 1)
1993
- && discontinuityToMediaSequence.get(discontintuitySequenceNum + 1) <
1994
- position) {
1995
- discontintuitySequenceNum++;
1996
- }
1997
- }
1998
- }
1999
- let timestampOffset =
2000
- this.discontinuityToTso_.get(discontintuitySequenceNum) || 0;
2001
-
2002
2074
  /** @type {!Array.<!shaka.media.SegmentReference>} */
2003
2075
  const references = [];
2004
2076
 
2005
- const enumerate = (it) => shaka.util.Iterables.enumerate(it);
2006
- for (const {i, item} of enumerate(hlsSegments)) {
2077
+ for (let i = 0; i < hlsSegments.length; i++) {
2078
+ const item = hlsSegments[i];
2007
2079
  const previousReference = references[references.length - 1];
2008
- const startTime = (i == 0) ? firstStartTime :
2009
- previousReference.endTime;
2080
+ const startTime =
2081
+ (i == 0) ? firstStartTime : previousReference.endTime;
2010
2082
  position = mediaSequenceNumber + skippedSegments + i;
2011
2083
 
2012
2084
  mediaSequenceToStartTime.set(position, startTime);
@@ -2014,19 +2086,6 @@ shaka.hls.HlsParser = class {
2014
2086
  initSegmentRef = this.getInitSegmentReference_(playlist.absoluteUri,
2015
2087
  item.tags, variables);
2016
2088
 
2017
- const discontintuityTag = shaka.hls.Utils.getFirstTagWithName(item.tags,
2018
- 'EXT-X-DISCONTINUITY');
2019
- if (discontintuityTag) {
2020
- discontintuitySequenceNum++;
2021
- discontinuityToMediaSequence.set(discontintuitySequenceNum, position);
2022
-
2023
- // eslint-disable-next-line no-await-in-loop
2024
- timestampOffset = await this.getTimestampOffset_(
2025
- discontintuitySequenceNum, verbatimMediaPlaylistUri, initSegmentRef,
2026
- mimeType, position, item, variables, startTime, type, codecs,
2027
- bandwidth);
2028
- }
2029
-
2030
2089
  // If the stream is low latency and the user has not configured the
2031
2090
  // lowLatencyMode, but if it has been configured to activate the
2032
2091
  // lowLatencyMode if a stream of this type is detected, we automatically
@@ -2039,514 +2098,94 @@ shaka.hls.HlsParser = class {
2039
2098
  }
2040
2099
  }
2041
2100
 
2042
- const extinfTag =
2043
- shaka.hls.Utils.getFirstTagWithName(item.tags, 'EXTINF');
2044
- if (this.lowLatencyMode_ || extinfTag) {
2045
- const reference = this.createSegmentReference_(
2046
- initSegmentRef,
2047
- previousReference,
2048
- item,
2049
- startTime,
2050
- timestampOffset,
2051
- variables,
2052
- playlist.absoluteUri,
2053
- type);
2054
-
2055
- references.push(reference);
2056
- } else if (!this.lowLatencyMode_) {
2057
- // If a segment has no extinfTag, it must contain partial segments.
2058
- shaka.log.alwaysWarn('Low-latency HLS live stream detected, but ' +
2059
- 'low-latency streaming mode is not enabled in Shaka Player. ' +
2060
- 'Set streaming.lowLatencyMode configuration to true, and see ' +
2061
- 'https://bit.ly/3clctcj for details.');
2062
- }
2063
- }
2064
-
2065
- return references;
2066
- }
2067
-
2068
- /**
2069
- * Gets the start time of the first segment of the playlist from existing
2070
- * value (if possible) or by downloading it and parsing it otherwise.
2071
- *
2072
- * @param {number} discontintuitySequenceNum
2073
- * @param {string} verbatimMediaPlaylistUri
2074
- * @param {shaka.media.InitSegmentReference} initSegmentRef
2075
- * @param {string} mimeType
2076
- * @param {number} mediaSequenceNumber
2077
- * @param {!shaka.hls.Segment} segment
2078
- * @param {!Map.<string, string>} variables
2079
- * @param {number} startTime
2080
- * @param {string} type
2081
- * @param {string} codecs
2082
- * @param {number|undefined} bandwidth
2083
- * @return {!Promise.<number>}
2084
- * @throws {shaka.util.Error}
2085
- * @private
2086
- */
2087
- async getTimestampOffset_(discontintuitySequenceNum,
2088
- verbatimMediaPlaylistUri, initSegmentRef,
2089
- mimeType, mediaSequenceNumber, segment, variables, startTime, type,
2090
- codecs, bandwidth) {
2091
- let timestampOffset = 0;
2092
- if (this.discontinuityToTso_.has(discontintuitySequenceNum)) {
2093
- timestampOffset =
2094
- this.discontinuityToTso_.get(discontintuitySequenceNum);
2095
- } else {
2096
- const mediaStartTime = await this.getStartTime_(
2097
- verbatimMediaPlaylistUri, initSegmentRef, mimeType,
2098
- mediaSequenceNumber, /* isDiscontinuity= */ true, segment,
2099
- variables, type, codecs, bandwidth);
2100
- timestampOffset = startTime - mediaStartTime;
2101
- shaka.log.v1('Segment timestampOffset =', timestampOffset);
2102
- this.discontinuityToTso_.set(
2103
- discontintuitySequenceNum, timestampOffset);
2104
- }
2105
- return timestampOffset;
2106
- }
2107
-
2108
- /**
2109
- * Try to fetch the starting part of a segment, and fall back to a full
2110
- * segment if we have to.
2111
- *
2112
- * @param {!shaka.media.AnySegmentReference} reference
2113
- * @param {string} type
2114
- * @param {string} mimeType
2115
- * @param {string} codecs
2116
- * @param {number|undefined} bandwidth
2117
- * @return {!Promise.<shaka.extern.Response>}
2118
- * @private
2119
- */
2120
- async fetchStartOfSegment_(reference, type, mimeType, codecs, bandwidth) {
2121
- const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
2122
-
2123
- // Create two requests:
2124
- // 1. A partial request meant to fetch the smallest part of the segment
2125
- // required to get the time stamp.
2126
- // 2. A full request meant as a fallback for when the server does not
2127
- // support partial requests.
2128
- const fullRequest = shaka.util.Networking.createSegmentRequest(
2129
- reference.getUris(),
2130
- reference.startByte,
2131
- reference.endByte,
2132
- this.config_.retryParameters);
2133
-
2134
- // We can only add partial CMCD data here because the stream
2135
- // and manifest objects are still being created
2136
- this.playerInterface_.modifySegmentRequest(
2137
- fullRequest,
2138
- {
2139
- type: type,
2140
- init: reference instanceof shaka.media.InitSegmentReference,
2141
- duration: reference.endTime - reference.startTime,
2142
- mimeType: mimeType,
2143
- codecs: codecs,
2144
- bandwidth: bandwidth,
2145
- },
2146
- );
2147
-
2148
- if (this.config_.hls.useFullSegmentsForStartTime) {
2149
- return this.makeNetworkRequest_(fullRequest, requestType);
2150
- }
2151
-
2152
- const partialRequest = shaka.util.Networking.createSegmentRequest(
2153
- reference.getUris(),
2154
- reference.startByte,
2155
- reference.startByte + shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ - 1,
2156
- this.config_.retryParameters);
2157
-
2158
- this.playerInterface_.modifySegmentRequest(
2159
- partialRequest,
2160
- {
2161
- type: type,
2162
- init: reference instanceof shaka.media.InitSegmentReference,
2163
- duration: reference.endTime - reference.startTime,
2164
- mimeType: mimeType,
2165
- codecs: codecs,
2166
- bandwidth: bandwidth,
2167
- },
2168
- );
2169
-
2170
- // TODO(vaage): The need to do fall back requests is not likely to be unique
2171
- // to here. It would be nice if the fallback(s) could be included into
2172
- // the same abortable operation as the original request.
2173
- //
2174
- // What would need to change with networking engine to support requests
2175
- // with fallback(s)?
2176
- try {
2177
- const response = await this.makeNetworkRequest_(
2178
- partialRequest, requestType);
2179
-
2180
- return response;
2181
- } catch (e) {
2182
- // If the networking operation was aborted, we don't want to treat it as
2183
- // a request failure. We surface the error so that the OPERATION_ABORTED
2184
- // error will be handled correctly.
2185
- if (e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
2186
- throw e;
2187
- }
2188
-
2189
- // The partial request may fail for a number of reasons.
2190
- // Some servers do not support Range requests, and others do not support
2191
- // the OPTIONS request which must be made before any cross-origin Range
2192
- // request. Since this fallback is expensive, warn the app developer.
2193
- shaka.log.alwaysWarn('Unable to fetch the starting part of HLS ' +
2194
- 'segment! Falling back to a full segment request, ' +
2195
- 'which is expensive! Your server should ' +
2196
- 'support Range requests and CORS preflights.',
2197
- partialRequest.uris[0]);
2198
-
2199
- const response = await this.makeNetworkRequest_(fullRequest, requestType);
2200
-
2201
- return response;
2202
- }
2203
- }
2204
-
2205
- /**
2206
- * Gets the start time of a segment from the existing manifest (if possible)
2207
- * or by downloading it and parsing it otherwise.
2208
- *
2209
- * @param {string} verbatimMediaPlaylistUri
2210
- * @param {shaka.media.InitSegmentReference} initSegmentRef
2211
- * @param {string} mimeType
2212
- * @param {number} mediaSequenceNumber
2213
- * @param {boolean} isDiscontinuity
2214
- * @param {!shaka.hls.Segment} segment
2215
- * @param {!Map.<string, string>} variables
2216
- * @param {string} type
2217
- * @param {string} codecs
2218
- * @param {number|undefined} bandwidth
2219
- * @return {!Promise.<number>}
2220
- * @private
2221
- */
2222
- async getStartTime_(
2223
- verbatimMediaPlaylistUri, initSegmentRef, mimeType, mediaSequenceNumber,
2224
- isDiscontinuity, segment, variables, type, codecs, bandwidth) {
2225
- const segmentRef = this.createSegmentReference_(
2226
- initSegmentRef,
2227
- /* previousReference= */ null,
2228
- segment,
2229
- /* startTime= */ 0,
2230
- /* timestampOffset= */ 0,
2231
- variables,
2232
- /* absoluteMediaPlaylistUri= */ '',
2233
- type);
2234
- // If we are updating the manifest, we can usually skip fetching the segment
2235
- // by examining the references we already have. This won't be possible if
2236
- // there was some kind of lag or delay updating the manifest on the server,
2237
- // in which extreme case we would fall back to fetching a segment. This
2238
- // allows us to both avoid fetching segments when possible, and recover from
2239
- // certain server-side issues gracefully.
2240
- // Do not use cached start time for the segments with discontinuity tags.
2241
- if (this.manifest_ && !isDiscontinuity) {
2242
- const streamInfo =
2243
- this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
2244
- const startTime = streamInfo.mediaSequenceToStartTime.get(
2245
- mediaSequenceNumber);
2246
- if (startTime != undefined) {
2247
- // We found it! Avoid fetching and parsing the segment.
2248
- shaka.log.v1('Found segment start time in previous manifest',
2249
- startTime);
2250
- return startTime;
2251
- }
2252
-
2253
- shaka.log.debug(
2254
- 'Unable to find segment start time in previous manifest!');
2255
- }
2256
-
2257
- // TODO: Introduce a new tag to extend HLS and provide the first segment's
2258
- // start time. This will avoid the need for these fetches in content
2259
- // packaged with Shaka Packager. This web-friendly extension to HLS can
2260
- // then be proposed to Apple for inclusion in a future version of HLS.
2261
- // See https://github.com/google/shaka-packager/issues/294
2262
-
2263
- shaka.log.v1('Fetching segment to find start time');
2264
- mimeType = mimeType.toLowerCase();
2265
-
2266
- if (shaka.hls.HlsParser.RAW_FORMATS_.includes(mimeType)) {
2267
- // Raw formats contain no timestamps. Even if there is an ID3 tag with a
2268
- // timestamp, that's not going to be honored by MediaSource, which will
2269
- // use sequence mode for these segments. We don't yet support sequence
2270
- // mode, so we must reject these streams.
2271
- // TODO(#2337): Support sequence mode and align raw format timestamps to
2272
- // other streams.
2273
- shaka.log.alwaysWarn(
2274
- 'Raw formats are not yet supported. Skipping ' + mimeType);
2275
- throw new shaka.util.Error(
2276
- shaka.util.Error.Severity.RECOVERABLE,
2277
- shaka.util.Error.Category.MANIFEST,
2278
- shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
2279
- }
2280
-
2281
- if (mimeType == 'video/webm') {
2282
- shaka.log.alwaysWarn('WebM in HLS is not yet supported. Skipping.');
2283
- throw new shaka.util.Error(
2284
- shaka.util.Error.Severity.RECOVERABLE,
2285
- shaka.util.Error.Category.MANIFEST,
2286
- shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM);
2287
- }
2288
-
2289
- if (mimeType == 'video/mp4' || mimeType == 'audio/mp4') {
2290
- // We also need the init segment to get the correct timescale. But if the
2291
- // stream is self-initializing, use the same response for both.
2292
- const fetches = [this.fetchStartOfSegment_(
2293
- segmentRef,
2294
- type,
2295
- mimeType,
2296
- codecs,
2297
- bandwidth,
2298
- )];
2299
-
2300
- if (initSegmentRef) {
2301
- fetches.push(this.fetchStartOfSegment_(
2302
- initSegmentRef,
2303
- type,
2304
- mimeType,
2305
- codecs,
2306
- bandwidth,
2307
- ));
2308
- }
2309
-
2310
- const responses = await Promise.all(fetches);
2311
-
2312
- // If the stream is self-initializing, use the main segment in-place of
2313
- // the init segment.
2314
- const segmentResponse = responses[0];
2315
- const initSegmentResponse = responses[1] || responses[0];
2316
-
2317
- return this.getStartTimeFromMp4Segment_(
2318
- verbatimMediaPlaylistUri, segmentResponse.uri,
2319
- segmentResponse.data, initSegmentResponse.data);
2320
- }
2321
-
2322
- if (mimeType == 'video/mp2t') {
2323
- const response = await this.fetchStartOfSegment_(
2324
- segmentRef,
2325
- type,
2326
- mimeType,
2327
- codecs,
2328
- bandwidth,
2329
- );
2330
- goog.asserts.assert(response.data, 'Should have a response body!');
2331
- return this.getStartTimeFromTsSegment_(
2332
- verbatimMediaPlaylistUri, response.uri, response.data);
2333
- }
2334
-
2335
- throw new shaka.util.Error(
2336
- shaka.util.Error.Severity.CRITICAL,
2337
- shaka.util.Error.Category.MANIFEST,
2338
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2339
- verbatimMediaPlaylistUri);
2340
- }
2341
-
2342
- /**
2343
- * Parses an mp4 segment to get its start time.
2344
- *
2345
- * @param {string} playlistUri
2346
- * @param {string} segmentUri
2347
- * @param {BufferSource} mediaData
2348
- * @param {BufferSource} initData
2349
- * @return {number}
2350
- * @private
2351
- */
2352
- getStartTimeFromMp4Segment_(playlistUri, segmentUri, mediaData, initData) {
2353
- const Mp4Parser = shaka.util.Mp4Parser;
2354
-
2355
- let timescale = 0;
2356
- new Mp4Parser()
2357
- .box('moov', Mp4Parser.children)
2358
- .box('trak', Mp4Parser.children)
2359
- .box('mdia', Mp4Parser.children)
2360
- .fullBox('mdhd', (box) => {
2361
- goog.asserts.assert(
2362
- box.version == 0 || box.version == 1,
2363
- 'MDHD version can only be 0 or 1');
2364
-
2365
- const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
2366
- box.reader, box.version);
2367
-
2368
- timescale = parsedMDHDBox.timescale;
2369
- box.parser.stop();
2370
- }).parse(initData, /* partialOkay= */ true);
2371
-
2372
- if (!timescale) {
2373
- shaka.log.error('Unable to find timescale in init segment!');
2374
- throw new shaka.util.Error(
2375
- shaka.util.Error.Severity.CRITICAL,
2376
- shaka.util.Error.Category.MANIFEST,
2377
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2378
- playlistUri, segmentUri);
2379
- }
2380
-
2381
- let startTime = 0;
2382
- let parsedMedia = false;
2383
- new Mp4Parser()
2384
- .box('moof', Mp4Parser.children)
2385
- .box('traf', Mp4Parser.children)
2386
- .fullBox('tfdt', (box) => {
2387
- goog.asserts.assert(
2388
- box.version == 0 || box.version == 1,
2389
- 'TFDT version can only be 0 or 1');
2390
-
2391
- const parsedTFDTBox = shaka.util.Mp4BoxParsers.parseTFDT(
2392
- box.reader, box.version);
2393
- const baseTime = parsedTFDTBox.baseMediaDecodeTime;
2394
- startTime = baseTime / timescale;
2395
- parsedMedia = true;
2396
- box.parser.stop();
2397
- }).parse(mediaData, /* partialOkay= */ true);
2398
-
2399
- if (!parsedMedia) {
2400
- throw new shaka.util.Error(
2401
- shaka.util.Error.Severity.CRITICAL,
2402
- shaka.util.Error.Category.MANIFEST,
2403
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2404
- playlistUri, segmentUri);
2405
- }
2406
- return startTime;
2407
- }
2408
-
2409
- /**
2410
- * Parses a TS segment to get its start time.
2411
- *
2412
- * @param {string} playlistUri
2413
- * @param {string} segmentUri
2414
- * @param {BufferSource} data
2415
- * @return {number}
2416
- * @private
2417
- */
2418
- getStartTimeFromTsSegment_(playlistUri, segmentUri, data) {
2419
- const reader = new shaka.util.DataViewReader(
2420
- data, shaka.util.DataViewReader.Endianness.BIG_ENDIAN);
2421
-
2422
- const fail = () => {
2423
- throw new shaka.util.Error(
2424
- shaka.util.Error.Severity.CRITICAL,
2425
- shaka.util.Error.Category.MANIFEST,
2426
- shaka.util.Error.Code.HLS_COULD_NOT_PARSE_SEGMENT_START_TIME,
2427
- playlistUri, segmentUri);
2428
- };
2429
-
2430
- let packetStart = 0;
2431
- let syncByte = 0;
2432
-
2433
- const skipPacket = () => {
2434
- // 188-byte packets are standard, so assume that.
2435
- reader.seek(packetStart + 188);
2436
- syncByte = reader.readUint8();
2437
- if (syncByte != 0x47) {
2438
- // We haven't found the sync byte, so try it as a 192-byte packet.
2439
- reader.seek(packetStart + 192);
2440
- syncByte = reader.readUint8();
2441
- }
2442
- if (syncByte != 0x47) {
2443
- // We still haven't found the sync byte, so try as a 204-byte packet.
2444
- reader.seek(packetStart + 204);
2445
- syncByte = reader.readUint8();
2446
- }
2447
- if (syncByte != 0x47) {
2448
- // We still haven't found the sync byte, so the packet was of a
2449
- // non-standard size.
2450
- fail();
2451
- }
2452
- // Put the sync byte back so we can read it in the next loop.
2453
- reader.rewind(1);
2454
- };
2455
-
2456
- // We will look a few packet-lengths forward to find the first sync byte.
2457
- // Note that we are using this method on what is already a subset of the
2458
- // file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
2459
- // we can't look too far ahead to begin with.
2460
- let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
2461
-
2462
- // TODO: refactor this while loop for better readability.
2463
- // eslint-disable-next-line no-constant-condition
2464
- while (true) {
2465
- // Format reference: https://bit.ly/TsPacket
2466
- packetStart = reader.getPosition();
2467
-
2468
- syncByte = reader.readUint8();
2469
- if (syncByte != 0x47) {
2470
- if (syncByteScanLength > 0) {
2471
- // This file could have started with a cut-off TS packet. Scan forward
2472
- // until we find a sync byte.
2473
- syncByteScanLength -= 1;
2101
+ const reference = this.createSegmentReference_(
2102
+ initSegmentRef,
2103
+ previousReference,
2104
+ item,
2105
+ startTime,
2106
+ variables,
2107
+ playlist.absoluteUri,
2108
+ type);
2109
+
2110
+ references.push(reference);
2111
+ }
2112
+
2113
+ // If some segments have sync times, but not all, extrapolate the sync
2114
+ // times of the ones with none.
2115
+ const someSyncTime = references.some((ref) => ref.syncTime != null);
2116
+ if (someSyncTime) {
2117
+ for (let i = 0; i < references.length; i++) {
2118
+ const reference = references[i];
2119
+ if (reference.syncTime != null) {
2120
+ // No need to extrapolate.
2474
2121
  continue;
2475
2122
  }
2476
- fail();
2477
- }
2478
- // If we've found a sync byte, stop scanning forward for future packets.
2479
- syncByteScanLength = 0;
2480
-
2481
- const flagsAndPacketId = reader.readUint16();
2482
- const packetId = flagsAndPacketId & 0x1fff;
2483
- if (packetId == 0x1fff) {
2484
- // A "null" TS packet. Skip this TS packet and try again.
2485
- skipPacket();
2486
- continue;
2487
- }
2488
-
2489
- const hasPesPacket = flagsAndPacketId & 0x4000;
2490
- if (!hasPesPacket) {
2491
- // Not a PES packet yet. Skip this TS packet and try again.
2492
- skipPacket();
2493
- continue;
2494
- }
2495
-
2496
- const flags = reader.readUint8();
2497
- const adaptationFieldControl = (flags & 0x30) >> 4;
2498
- if (adaptationFieldControl == 0 /* reserved */ ||
2499
- adaptationFieldControl == 2 /* adaptation field, no payload */) {
2500
- fail();
2501
- }
2502
-
2503
- if (adaptationFieldControl == 3) {
2504
- // Skip over adaptation field.
2505
- const length = reader.readUint8();
2506
- reader.skip(length);
2507
- }
2508
-
2509
- // Now we come to the PES header (hopefully).
2510
- // Format reference: https://bit.ly/TsPES
2511
- const startCode = reader.readUint32();
2512
- const startCodePrefix = startCode >> 8;
2513
- if (startCodePrefix != 1) {
2514
- // Not a PES packet yet. Skip this TS packet and try again.
2515
- skipPacket();
2516
- continue;
2517
- }
2518
-
2519
- // Skip the 16-bit PES length and the first 8 bits of the optional header.
2520
- reader.skip(3);
2521
- // The next 8 bits contain flags about DTS & PTS.
2522
- const ptsDtsIndicator = reader.readUint8() >> 6;
2523
- if (ptsDtsIndicator == 0 /* no timestamp */ ||
2524
- ptsDtsIndicator == 1 /* forbidden */) {
2525
- fail();
2526
- }
2527
-
2528
- const pesHeaderLengthRemaining = reader.readUint8();
2529
- if (pesHeaderLengthRemaining == 0) {
2530
- fail();
2123
+ // Find the nearest segment with syncTime, in either direction.
2124
+ // This looks forward and backward simultaneously, keeping track of what
2125
+ // to offset the syncTime it finds by as it goes.
2126
+ let forwardAdd = 0;
2127
+ let forwardI = i;
2128
+ /**
2129
+ * Look forwards one reference at a time, summing all durations as we
2130
+ * go, until we find a reference with a syncTime to use as a basis.
2131
+ * This DOES count the original reference, but DOESN'T count the first
2132
+ * reference with a syncTime (as we approach it from behind).
2133
+ * @return {?number}
2134
+ */
2135
+ const lookForward = () => {
2136
+ const other = references[forwardI];
2137
+ if (other) {
2138
+ if (other.syncTime != null) {
2139
+ return other.syncTime + forwardAdd;
2140
+ }
2141
+ forwardAdd -= other.endTime - other.startTime;
2142
+ forwardI += 1;
2143
+ }
2144
+ return null;
2145
+ };
2146
+ let backwardAdd = 0;
2147
+ let backwardI = i;
2148
+ /**
2149
+ * Look backwards one reference at a time, summing all durations as we
2150
+ * go, until we find a reference with a syncTime to use as a basis.
2151
+ * This DOESN'T count the original reference, but DOES count the first
2152
+ * reference with a syncTime (as we approach it from ahead).
2153
+ * @return {?number}
2154
+ */
2155
+ const lookBackward = () => {
2156
+ const other = references[backwardI];
2157
+ if (other) {
2158
+ if (other != reference) {
2159
+ backwardAdd += other.endTime - other.startTime;
2160
+ }
2161
+ if (other.syncTime != null) {
2162
+ return other.syncTime + backwardAdd;
2163
+ }
2164
+ backwardI -= 1;
2165
+ }
2166
+ return null;
2167
+ };
2168
+ while (reference.syncTime == null) {
2169
+ reference.syncTime = lookBackward();
2170
+ if (reference.syncTime == null) {
2171
+ reference.syncTime = lookForward();
2172
+ }
2173
+ }
2531
2174
  }
2175
+ }
2532
2176
 
2533
- if (ptsDtsIndicator == 2 /* PTS only */) {
2534
- goog.asserts.assert(pesHeaderLengthRemaining == 5, 'Bad PES header?');
2535
- } else if (ptsDtsIndicator == 3 /* PTS and DTS */) {
2536
- goog.asserts.assert(pesHeaderLengthRemaining == 10, 'Bad PES header?');
2177
+ // Split the sync times properly among partial segments.
2178
+ if (someSyncTime) {
2179
+ for (const reference of references) {
2180
+ let syncTime = reference.syncTime;
2181
+ for (const partial of reference.partialReferences) {
2182
+ partial.syncTime = syncTime;
2183
+ syncTime += partial.endTime - partial.startTime;
2184
+ }
2537
2185
  }
2538
-
2539
- const pts0 = reader.readUint8();
2540
- const pts1 = reader.readUint16();
2541
- const pts2 = reader.readUint16();
2542
- // Reconstruct 33-bit PTS from the 5-byte, padded structure.
2543
- const ptsHigh3 = (pts0 & 0x0e) >> 1;
2544
- const ptsLow30 = ((pts1 & 0xfffe) << 14) | ((pts2 & 0xfffe) >> 1);
2545
- // Reconstruct the PTS as a float. Avoid bitwise operations to combine
2546
- // because bitwise ops treat the values as 32-bit ints.
2547
- const pts = ptsHigh3 * (1 << 30) + ptsLow30;
2548
- return pts / shaka.hls.HlsParser.TS_TIMESCALE_;
2549
2186
  }
2187
+
2188
+ return references;
2550
2189
  }
2551
2190
 
2552
2191
  /**
@@ -2613,11 +2252,10 @@ shaka.hls.HlsParser = class {
2613
2252
 
2614
2253
  if (contentType == ContentType.TEXT) {
2615
2254
  // The extension map didn't work.
2616
- if (!codecs || codecs == 'vtt' || codecs == 'wvtt') {
2255
+ if (codecs == 'vtt' || codecs == 'wvtt') {
2617
2256
  // If codecs is 'vtt', it's WebVTT.
2618
- // If there was no codecs string, assume HLS text streams are WebVTT.
2619
2257
  return 'text/vtt';
2620
- } else {
2258
+ } else if (codecs && codecs !== '') {
2621
2259
  // Otherwise, assume MP4-embedded text, since text-based formats tend
2622
2260
  // not to have a codecs string at all.
2623
2261
  return 'application/mp4';
@@ -2642,6 +2280,11 @@ shaka.hls.HlsParser = class {
2642
2280
  const contentMimeType = response.headers['content-type'];
2643
2281
 
2644
2282
  if (!contentMimeType) {
2283
+ if (contentType == ContentType.TEXT) {
2284
+ // If there was no codecs string and no content-type, assume HLS text
2285
+ // streams are WebVTT.
2286
+ return 'text/vtt';
2287
+ }
2645
2288
  // If the HLS content is lacking in both MIME type metadata and
2646
2289
  // segment file extensions, we fall back to assuming it's MP4.
2647
2290
  const fallbackMimeType = map['mp4'];
@@ -2807,6 +2450,33 @@ shaka.hls.HlsParser = class {
2807
2450
  return op.promise;
2808
2451
  }
2809
2452
 
2453
+ /**
2454
+ * @param {!shaka.hls.Tag} drmTag
2455
+ * @param {string} mimeType
2456
+ * @return {?shaka.extern.DrmInfo}
2457
+ * @private
2458
+ */
2459
+ static fairplayDrmParser_(drmTag, mimeType) {
2460
+ if (mimeType == 'video/mp2t') {
2461
+ throw new shaka.util.Error(
2462
+ shaka.util.Error.Severity.CRITICAL,
2463
+ shaka.util.Error.Category.MANIFEST,
2464
+ shaka.util.Error.Code.HLS_MSE_ENCRYPTED_MP2T_NOT_SUPPORTED);
2465
+ }
2466
+
2467
+ /*
2468
+ * Even if we're not able to construct initData through the HLS tag, adding
2469
+ * a DRMInfo will allow DRM Engine to request a media key system access
2470
+ * with the correct keySystem and initDataType
2471
+ */
2472
+ const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2473
+ 'com.apple.fps', [
2474
+ {initDataType: 'sinf', initData: new Uint8Array(0)},
2475
+ ]);
2476
+
2477
+ return drmInfo;
2478
+ }
2479
+
2810
2480
  /**
2811
2481
  * @param {!shaka.hls.Tag} drmTag
2812
2482
  * @return {?shaka.extern.DrmInfo}
@@ -2869,7 +2539,10 @@ shaka.hls.HlsParser = class {
2869
2539
  0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
2870
2540
  0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
2871
2541
  ]);
2872
- const pssh = shaka.util.Pssh.createPssh(data, systemId);
2542
+ const keyIds = new Set();
2543
+ const psshVersion = 0;
2544
+ const pssh =
2545
+ shaka.util.Pssh.createPssh(data, systemId, keyIds, psshVersion);
2873
2546
  const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
2874
2547
  'com.microsoft.playready', [
2875
2548
  {initDataType: 'cenc', initData: pssh},
@@ -2888,7 +2561,6 @@ shaka.hls.HlsParser = class {
2888
2561
  * minTimestamp: number,
2889
2562
  * maxTimestamp: number,
2890
2563
  * mediaSequenceToStartTime: !Map.<number, number>,
2891
- * discontinuityToMediaSequence: !Map.<number, number>,
2892
2564
  * canSkipSegments: boolean
2893
2565
  * }}
2894
2566
  *
@@ -2911,9 +2583,6 @@ shaka.hls.HlsParser = class {
2911
2583
  * The maximum timestamp found in the stream.
2912
2584
  * @property {!Map.<number, number>} mediaSequenceToStartTime
2913
2585
  * A map of media sequence numbers to media start times.
2914
- * @property {!Map.<number, number>} discontinuityToMediaSequence
2915
- * A map of discontinuity sequence numbers to the media sequence number of the
2916
- * segment starting with that discontinuity sequence number.
2917
2586
  * @property {boolean} canSkipSegments
2918
2587
  * True if the server supports delta playlist updates, and we can send a
2919
2588
  * request for a playlist that can skip older media segments.
@@ -2957,21 +2626,6 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2957
2626
  };
2958
2627
 
2959
2628
 
2960
- /**
2961
- * MIME types of raw formats.
2962
- * TODO(#2337): Support raw formats and share this list among parsers.
2963
- *
2964
- * @const {!Array.<string>}
2965
- * @private
2966
- */
2967
- shaka.hls.HlsParser.RAW_FORMATS_ = [
2968
- 'audio/aac',
2969
- 'audio/ac3',
2970
- 'audio/ec3',
2971
- 'audio/mpeg',
2972
- ];
2973
-
2974
-
2975
2629
  /**
2976
2630
  * @const {!Object.<string, string>}
2977
2631
  * @private
@@ -3029,7 +2683,7 @@ shaka.hls.HlsParser.EXTENSION_MAP_BY_CONTENT_TYPE_ = {
3029
2683
 
3030
2684
 
3031
2685
  /**
3032
- * @typedef {function(!shaka.hls.Tag):?shaka.extern.DrmInfo}
2686
+ * @typedef {function(!shaka.hls.Tag, string):?shaka.extern.DrmInfo}
3033
2687
  * @private
3034
2688
  */
3035
2689
  shaka.hls.HlsParser.DrmParser_;
@@ -3040,10 +2694,8 @@ shaka.hls.HlsParser.DrmParser_;
3040
2694
  * @private
3041
2695
  */
3042
2696
  shaka.hls.HlsParser.KEYFORMATS_TO_DRM_PARSERS_ = {
3043
- /* TODO: https://github.com/google/shaka-player/issues/382
3044
2697
  'com.apple.streamingkeydelivery':
3045
2698
  shaka.hls.HlsParser.fairplayDrmParser_,
3046
- */
3047
2699
  'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed':
3048
2700
  shaka.hls.HlsParser.widevineDrmParser_,
3049
2701
  'com.microsoft.playready':
@@ -3061,28 +2713,13 @@ shaka.hls.HlsParser.PresentationType_ = {
3061
2713
  LIVE: 'LIVE',
3062
2714
  };
3063
2715
 
3064
-
3065
- /**
3066
- * @const {number}
3067
- * @private
3068
- */
3069
- shaka.hls.HlsParser.TS_TIMESCALE_ = 90000;
3070
-
3071
-
3072
- /**
3073
- * The amount of data from the start of a segment we will try to fetch when we
3074
- * need to know the segment start time. This allows us to avoid fetching the
3075
- * entire segment in many cases.
3076
- *
3077
- * @const {number}
3078
- * @private
3079
- */
3080
- shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_ = 2048;
3081
-
3082
-
3083
- shaka.media.ManifestParser.registerParserByExtension(
3084
- 'm3u8', () => new shaka.hls.HlsParser());
3085
- shaka.media.ManifestParser.registerParserByMime(
3086
- 'application/x-mpegurl', () => new shaka.hls.HlsParser());
3087
- shaka.media.ManifestParser.registerParserByMime(
3088
- 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2716
+ if (!shaka.util.Platform.isTizen3() &&
2717
+ !shaka.util.Platform.isTizen2() &&
2718
+ !shaka.util.Platform.isWebOS3()) {
2719
+ shaka.media.ManifestParser.registerParserByExtension(
2720
+ 'm3u8', () => new shaka.hls.HlsParser());
2721
+ shaka.media.ManifestParser.registerParserByMime(
2722
+ 'application/x-mpegurl', () => new shaka.hls.HlsParser());
2723
+ shaka.media.ManifestParser.registerParserByMime(
2724
+ 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
2725
+ }