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
@@ -18,7 +18,6 @@ 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');
22
21
  goog.require('shaka.media.PresentationTimeline');
23
22
  goog.require('shaka.media.SegmentIndex');
24
23
  goog.require('shaka.media.SegmentReference');
@@ -26,16 +25,18 @@ goog.require('shaka.net.DataUriPlugin');
26
25
  goog.require('shaka.net.NetworkingEngine');
27
26
  goog.require('shaka.util.ArrayUtils');
28
27
  goog.require('shaka.util.BufferUtils');
29
- goog.require('shaka.util.CmcdManager');
30
28
  goog.require('shaka.util.Error');
31
29
  goog.require('shaka.util.FakeEvent');
32
30
  goog.require('shaka.util.Functional');
33
31
  goog.require('shaka.util.LanguageUtils');
34
32
  goog.require('shaka.util.ManifestParserUtils');
35
33
  goog.require('shaka.util.MimeUtils');
34
+ goog.require('shaka.util.Mp4BoxParsers');
35
+ goog.require('shaka.util.Mp4Parser');
36
36
  goog.require('shaka.util.OperationManager');
37
37
  goog.require('shaka.util.Pssh');
38
38
  goog.require('shaka.util.Timer');
39
+ goog.require('shaka.util.TsParser');
39
40
  goog.require('shaka.util.Platform');
40
41
  goog.require('shaka.util.Uint8ArrayUtils');
41
42
  goog.require('shaka.util.XmlUtils');
@@ -130,15 +131,6 @@ shaka.hls.HlsParser = class {
130
131
  /** @private {shaka.hls.ManifestTextParser} */
131
132
  this.manifestTextParser_ = new shaka.hls.ManifestTextParser();
132
133
 
133
- /**
134
- * This is the number of seconds we want to wait between finishing a
135
- * manifest update and starting the next one. This will be set when we parse
136
- * the manifest.
137
- *
138
- * @private {number}
139
- */
140
- this.updatePlaylistDelay_ = 0;
141
-
142
134
  /**
143
135
  * The minimum sequence number for generated segments, when ignoring
144
136
  * EXT-X-PROGRAM-DATE-TIME.
@@ -186,13 +178,16 @@ shaka.hls.HlsParser = class {
186
178
  this.maxTargetDuration_ = 0;
187
179
 
188
180
  /** @private {number} */
189
- this.minTargetDuration_ = Infinity;
181
+ this.lastTargetDuration_ = Infinity;
190
182
 
191
183
  /** Partial segments target duration.
192
184
  * @private {number}
193
185
  */
194
186
  this.partialTargetDuration_ = 0;
195
187
 
188
+ /** @private {number} */
189
+ this.presentationDelay_ = 0;
190
+
196
191
  /** @private {number} */
197
192
  this.lowLatencyPresentationDelay_ = 0;
198
193
 
@@ -217,6 +212,9 @@ shaka.hls.HlsParser = class {
217
212
 
218
213
  /** @private {boolean} */
219
214
  this.lowLatencyMode_ = false;
215
+
216
+ /** @private {boolean} */
217
+ this.lowLatencyByterangeOptimization_ = false;
220
218
  }
221
219
 
222
220
 
@@ -294,6 +292,9 @@ shaka.hls.HlsParser = class {
294
292
  const updates = [];
295
293
  const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
296
294
 
295
+ // This is necessary to calculate correctly the update time.
296
+ this.lastTargetDuration_ = Infinity;
297
+
297
298
  // Only update active streams.
298
299
  const activeStreamInfos = streamInfos.filter((s) => s.stream.segmentIndex);
299
300
  for (const streamInfo of activeStreamInfos) {
@@ -347,12 +348,30 @@ shaka.hls.HlsParser = class {
347
348
  async updateStream_(streamInfo) {
348
349
  const manifestUri = streamInfo.absoluteMediaPlaylistUri;
349
350
  const uriObj = new goog.Uri(manifestUri);
350
- if (this.lowLatencyMode_ && streamInfo.canSkipSegments) {
351
+ const queryData = new goog.Uri.QueryData();
352
+ if (streamInfo.canBlockReload) {
353
+ if (streamInfo.nextMediaSequence >= 0) {
354
+ // Indicates that the server must hold the request until a Playlist
355
+ // contains a Media Segment with Media Sequence
356
+ queryData.add('_HLS_msn', String(streamInfo.nextMediaSequence));
357
+ }
358
+ if (streamInfo.nextPart >= 0) {
359
+ // Indicates, in combination with _HLS_msn, that the server must hold
360
+ // the request until a Playlist contains Partial Segment N of Media
361
+ // Sequence Number M or later.
362
+ queryData.add('_HLS_part', String(streamInfo.nextPart));
363
+ }
364
+ }
365
+ if (streamInfo.canSkipSegments) {
351
366
  // Enable delta updates. This will replace older segments with
352
367
  // 'EXT-X-SKIP' tag in the media playlist.
353
- uriObj.setQueryData(new goog.Uri.QueryData('_HLS_skip=YES'));
368
+ queryData.add('_HLS_skip', 'YES');
354
369
  }
355
- const response = await this.requestManifest_(uriObj.toString());
370
+ if (queryData.getCount()) {
371
+ uriObj.setQueryData(queryData);
372
+ }
373
+ const response =
374
+ await this.requestManifest_(uriObj.toString(), /* isPlaylist= */ true);
356
375
  if (!streamInfo.stream.segmentIndex) {
357
376
  // The stream was closed since the update was first requested.
358
377
  return;
@@ -390,15 +409,25 @@ shaka.hls.HlsParser = class {
390
409
  this.playerInterface_.newDrmInfo(stream);
391
410
  }
392
411
 
393
- const segments = this.createSegments_(
412
+ const {segments, bandwidth} = this.createSegments_(
394
413
  streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
395
414
  stream.mimeType, mediaSequenceToStartTime, mediaVariables);
396
415
 
416
+ stream.bandwidth = bandwidth;
417
+
397
418
  stream.segmentIndex.mergeAndEvict(
398
419
  segments, this.presentationTimeline_.getSegmentAvailabilityStart());
399
420
  if (segments.length) {
400
421
  const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
401
422
  playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
423
+ const skipTag = shaka.hls.Utils.getFirstTagWithName(
424
+ playlist.tags, 'EXT-X-SKIP');
425
+ const skippedSegments =
426
+ skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
427
+ const {nextMediaSequence, nextPart} =
428
+ this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);
429
+ streamInfo.nextMediaSequence = nextMediaSequence + skippedSegments;
430
+ streamInfo.nextPart = nextPart;
402
431
  const playlistStartTime = mediaSequenceToStartTime.get(
403
432
  mediaSequenceNumber);
404
433
  stream.segmentIndex.evict(playlistStartTime);
@@ -424,6 +453,8 @@ shaka.hls.HlsParser = class {
424
453
  // until we've seen the ENDLIST tag for all active playlists.
425
454
  streamInfo.hasEndList = true;
426
455
  }
456
+
457
+ this.determineLastTargetDuration_(playlist);
427
458
  }
428
459
 
429
460
 
@@ -613,7 +644,6 @@ shaka.hls.HlsParser = class {
613
644
  * @private
614
645
  */
615
646
  async parseManifest_(data, uri) {
616
- const HlsParser = shaka.hls.HlsParser;
617
647
  const Utils = shaka.hls.Utils;
618
648
 
619
649
  goog.asserts.assert(this.masterPlaylistUri_,
@@ -636,44 +666,22 @@ shaka.hls.HlsParser = class {
636
666
 
637
667
  // Parsing a media playlist results in a single-variant stream.
638
668
  if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
639
- // Get necessary info for this stream, from the config. These are things
640
- // we would normally find from the master playlist (e.g. from values on
641
- // EXT-X-MEDIA tags).
642
- let fullMimeType = this.config_.hls.mediaPlaylistFullMimeType;
643
- // Try to infer the full mimetype better.
644
- if (playlist.segments.length) {
645
- const parsedUri = new goog.Uri(playlist.segments[0].absoluteUri);
646
- const extension = parsedUri.getPath().split('.').pop();
647
- let mimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
648
- if (mimeType) {
649
- fullMimeType = mimeType;
650
- } else if (extension === 'ts') {
651
- // TODO: Fetch one segment a use the TsParser to analize if there is
652
- // video, audio or both.
653
- } else if (extension === 'mp4') {
654
- // TODO: Fetch one segment a use the Mp4Parser to analize if there is
655
- // video, audio or both.
656
- } else if (HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_[extension]) {
657
- mimeType = HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_[extension];
658
- const defaultAudioCodec = this.config_.hls.defaultAudioCodec;
659
- fullMimeType = `${mimeType}; codecs="${defaultAudioCodec}"`;
660
- } else if (HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_[extension]) {
661
- mimeType = HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_[extension];
662
- const defaultVideoCodec = this.config_.hls.defaultVideoCodec;
663
- fullMimeType = `${mimeType}; codecs="${defaultVideoCodec}"`;
664
- }
665
- }
666
-
667
- const mimeType = shaka.util.MimeUtils.getBasicType(fullMimeType);
668
- const type = mimeType.split('/')[0];
669
- const codecs = shaka.util.MimeUtils.getCodecs(fullMimeType);
669
+ // Get necessary info for this stream. These are things we would normally
670
+ // find from the master playlist (e.g. from values on EXT-X-MEDIA tags).
671
+ const basicInfo = await this.getMediaPlaylistBasicInfo_(playlist);
672
+ const type = basicInfo.type;
673
+ const mimeType = basicInfo.mimeType;
674
+ const codecs = basicInfo.codecs;
675
+ const languageValue = basicInfo.language;
676
+ const height = basicInfo.height;
677
+ const width = basicInfo.width;
678
+ const channelsCount = basicInfo.channelCount;
679
+ const sampleRate = basicInfo.sampleRate;
670
680
 
671
681
  // Some values we cannot figure out, and aren't important enough to ask
672
682
  // the user to provide through config values. A lot of these are only
673
683
  // relevant to ABR, which isn't necessary if there's only one variant.
674
684
  // So these unknowns should be set to false or null, largely.
675
- const language = '';
676
- const channelsCount = null;
677
685
  const spatialAudio = false;
678
686
  const characteristics = null;
679
687
  const closedCaptions = new Map();
@@ -683,20 +691,25 @@ shaka.hls.HlsParser = class {
683
691
 
684
692
  // Make the stream info, with those values.
685
693
  const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
686
- playlist, uri, uri, codecs, type, language, primary, name,
687
- channelsCount, closedCaptions, characteristics, forced, spatialAudio,
688
- mimeType);
694
+ playlist, uri, uri, codecs, type, languageValue, primary, name,
695
+ channelsCount, closedCaptions, characteristics, forced, sampleRate,
696
+ spatialAudio, mimeType);
689
697
  this.uriToStreamInfosMap_.set(uri, streamInfo);
690
698
 
699
+ if (type == 'video') {
700
+ this.addVideoAttributes_(streamInfo.stream, width, height,
701
+ /* frameRate= */ null, /* videoRange= */ null);
702
+ }
703
+
691
704
  // Wrap the stream from that stream info with a variant.
692
705
  variants.push({
693
706
  id: 0,
694
- language: 'und',
707
+ language: this.getLanguage_(languageValue),
695
708
  disabledUntilTime: 0,
696
709
  primary: true,
697
710
  audio: type == 'audio' ? streamInfo.stream : null,
698
711
  video: type == 'video' ? streamInfo.stream : null,
699
- bandwidth: 0,
712
+ bandwidth: streamInfo.stream.bandwidth || 0,
700
713
  allowedByApplication: true,
701
714
  allowedByKeySystem: true,
702
715
  decodingInfos: [],
@@ -775,7 +788,7 @@ shaka.hls.HlsParser = class {
775
788
  if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
776
789
  if (this.isLive_()) {
777
790
  this.changePresentationTimelineToLive_();
778
- const delay = this.updatePlaylistDelay_;
791
+ const delay = this.getUpdatePlaylistDelay_();
779
792
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
780
793
  }
781
794
  const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
@@ -790,24 +803,322 @@ shaka.hls.HlsParser = class {
790
803
  imageStreams,
791
804
  offlineSessionIds: [],
792
805
  minBufferTime: 0,
793
- sequenceMode: true,
806
+ sequenceMode: this.config_.hls.sequenceMode,
807
+ ignoreManifestTimestampsInSegmentsMode:
808
+ this.config_.hls.ignoreManifestTimestampsInSegmentsMode,
809
+ type: shaka.media.ManifestParser.HLS,
810
+ serviceDescription: null,
794
811
  };
795
812
  this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
796
813
  }
797
814
 
815
+ /**
816
+ * @param {shaka.hls.Playlist} playlist
817
+ * @return {!Promise.<shaka.hls.HlsParser.BasicInfo>}
818
+ * @private
819
+ */
820
+ async getMediaPlaylistBasicInfo_(playlist) {
821
+ const HlsParser = shaka.hls.HlsParser;
822
+ const defaultFullMimeType = this.config_.hls.mediaPlaylistFullMimeType;
823
+ const defaultMimeType =
824
+ shaka.util.MimeUtils.getBasicType(defaultFullMimeType);
825
+ const defaultType = defaultMimeType.split('/')[0];
826
+ const defaultCodecs = shaka.util.MimeUtils.getCodecs(defaultFullMimeType);
827
+ const defaultBasicInfo = {
828
+ type: defaultType,
829
+ mimeType: defaultMimeType,
830
+ codecs: defaultCodecs,
831
+ language: null,
832
+ height: null,
833
+ width: null,
834
+ channelCount: null,
835
+ sampleRate: null,
836
+ };
837
+ if (!playlist.segments.length) {
838
+ return defaultBasicInfo;
839
+ }
840
+ const firstSegment = playlist.segments[0];
841
+ const parsedUri = new goog.Uri(firstSegment.absoluteUri);
842
+ const extension = parsedUri.getPath().split('.').pop();
843
+ const rawMimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
844
+ if (rawMimeType) {
845
+ return {
846
+ type: 'audio',
847
+ mimeType: rawMimeType,
848
+ codecs: '',
849
+ language: null,
850
+ height: null,
851
+ width: null,
852
+ channelCount: null,
853
+ sampleRate: null,
854
+ };
855
+ }
856
+
857
+ let segmentUris = [firstSegment.absoluteUri];
858
+ const initSegmentRef = this.getInitSegmentReference_(
859
+ playlist.absoluteUri, firstSegment.tags, new Map());
860
+ if (initSegmentRef) {
861
+ segmentUris = initSegmentRef.getUris();
862
+ }
863
+
864
+ const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
865
+ const segmentRequest = shaka.net.NetworkingEngine.makeRequest(
866
+ segmentUris, this.config_.retryParameters);
867
+ const type = initSegmentRef ?
868
+ shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT :
869
+ shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
870
+ const response = await this.makeNetworkRequest_(
871
+ segmentRequest, requestType, {type});
872
+
873
+ let contentMimeType = response.headers['content-type'];
874
+ if (contentMimeType) {
875
+ // Split the MIME type in case the server sent additional parameters.
876
+ contentMimeType = contentMimeType.split(';')[0].toLowerCase();
877
+ }
878
+
879
+ if (extension == 'ts' || contentMimeType == 'video/mp2t') {
880
+ const basicInfo = this.getBasicInfoFromTs_(response);
881
+ if (basicInfo) {
882
+ return basicInfo;
883
+ }
884
+ } else if (extension == 'mp4' ||
885
+ contentMimeType == 'video/mp4' || contentMimeType == 'audio/mp4') {
886
+ const basicInfo = this.getBasicInfoFromMp4_(response);
887
+ if (basicInfo) {
888
+ return basicInfo;
889
+ }
890
+ }
891
+ return defaultBasicInfo;
892
+ }
893
+
894
+ /**
895
+ * @param {shaka.extern.Response} response
896
+ * @return {?shaka.hls.HlsParser.BasicInfo}
897
+ * @private
898
+ */
899
+ getBasicInfoFromTs_(response) {
900
+ const uint8ArrayData = shaka.util.BufferUtils.toUint8(response.data);
901
+ const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
902
+ const tsCodecs = tsParser.getCodecs();
903
+ const codecs = [];
904
+ let hasAudio = false;
905
+ let hasVideo = false;
906
+ switch (tsCodecs.audio) {
907
+ case 'aac':
908
+ codecs.push('mp4a.40.2');
909
+ hasAudio = true;
910
+ break;
911
+ case 'mp3':
912
+ codecs.push('mp4a.40.34');
913
+ hasAudio = true;
914
+ break;
915
+ case 'ac3':
916
+ codecs.push('ac-3');
917
+ hasAudio = true;
918
+ break;
919
+ case 'ec3':
920
+ codecs.push('ec-3');
921
+ hasAudio = true;
922
+ break;
923
+ }
924
+ switch (tsCodecs.video) {
925
+ case 'avc':
926
+ codecs.push('avc1.42E01E');
927
+ hasVideo = true;
928
+ break;
929
+ case 'hvc':
930
+ codecs.push('hvc1.1.6.L93.90');
931
+ hasVideo = true;
932
+ break;
933
+ }
934
+ if (!codecs.length) {
935
+ return null;
936
+ }
937
+ const onlyAudio = hasAudio && !hasVideo;
938
+ const videoResolution = tsParser.getVideoResolution();
939
+ return {
940
+ type: onlyAudio ? 'audio' : 'video',
941
+ mimeType: 'video/mp2t',
942
+ codecs: codecs.join(', '),
943
+ language: null,
944
+ height: videoResolution.height,
945
+ width: videoResolution.width,
946
+ channelCount: null,
947
+ sampleRate: null,
948
+ };
949
+ }
950
+
951
+ /**
952
+ * @param {shaka.extern.Response} response
953
+ * @return {?shaka.hls.HlsParser.BasicInfo}
954
+ * @private
955
+ */
956
+ getBasicInfoFromMp4_(response) {
957
+ const Mp4Parser = shaka.util.Mp4Parser;
958
+
959
+ const codecs = [];
960
+
961
+ let hasAudio = false;
962
+ let hasVideo = false;
963
+
964
+ const addCodec = (codec) => {
965
+ const codecLC = codec.toLowerCase();
966
+ switch (codecLC) {
967
+ case 'avc1':
968
+ case 'avc3':
969
+ codecs.push(codecLC + '.42E01E');
970
+ hasVideo = true;
971
+ break;
972
+ case 'hev1':
973
+ case 'hvc1':
974
+ codecs.push(codecLC + '.1.6.L93.90');
975
+ hasVideo = true;
976
+ break;
977
+ case 'dvh1':
978
+ case 'dvhe':
979
+ codecs.push(codecLC + '.05.04');
980
+ hasVideo = true;
981
+ break;
982
+ case 'vp09':
983
+ codecs.push(codecLC + '.00.10.08');
984
+ hasVideo = true;
985
+ break;
986
+ case 'av01':
987
+ codecs.push(codecLC + '.0.01M.08');
988
+ hasVideo = true;
989
+ break;
990
+ case 'mp4a':
991
+ // We assume AAC, but this can be wrong since mp4a supports
992
+ // others codecs
993
+ codecs.push('mp4a.40.2');
994
+ hasAudio = true;
995
+ break;
996
+ case 'ac-3':
997
+ case 'ec-3':
998
+ case 'opus':
999
+ case 'flac':
1000
+ codecs.push(codecLC);
1001
+ hasAudio = true;
1002
+ break;
1003
+ }
1004
+ };
1005
+
1006
+ const codecBoxParser = (box) => addCodec(box.name);
1007
+
1008
+ /** @type {?string} */
1009
+ let language = null;
1010
+ /** @type {?string} */
1011
+ let height = null;
1012
+ /** @type {?string} */
1013
+ let width = null;
1014
+ /** @type {?number} */
1015
+ let channelCount = null;
1016
+ /** @type {?number} */
1017
+ let sampleRate = null;
1018
+
1019
+ new Mp4Parser()
1020
+ .box('moov', Mp4Parser.children)
1021
+ .box('trak', Mp4Parser.children)
1022
+ .fullBox('tkhd', (box) => {
1023
+ goog.asserts.assert(
1024
+ box.version != null,
1025
+ 'TKHD is a full box and should have a valid version.');
1026
+ const parsedTKHDBox = shaka.util.Mp4BoxParsers.parseTKHD(
1027
+ box.reader, box.version);
1028
+ height = String(parsedTKHDBox.height);
1029
+ width = String(parsedTKHDBox.width);
1030
+ })
1031
+ .box('mdia', Mp4Parser.children)
1032
+ .fullBox('mdhd', (box) => {
1033
+ goog.asserts.assert(
1034
+ box.version != null,
1035
+ 'MDHD is a full box and should have a valid version.');
1036
+ const parsedMDHDBox = shaka.util.Mp4BoxParsers.parseMDHD(
1037
+ box.reader, box.version);
1038
+ language = parsedMDHDBox.language;
1039
+ })
1040
+ .box('minf', Mp4Parser.children)
1041
+ .box('stbl', Mp4Parser.children)
1042
+ .fullBox('stsd', Mp4Parser.sampleDescription)
1043
+
1044
+ // AUDIO
1045
+ // These are the various boxes that signal a codec.
1046
+ .box('mp4a', (box) => {
1047
+ const parsedMP4ABox = shaka.util.Mp4BoxParsers.parseMP4A(box.reader);
1048
+ channelCount = parsedMP4ABox.channelCount;
1049
+ sampleRate = parsedMP4ABox.sampleRate;
1050
+ if (box.reader.hasMoreData()) {
1051
+ Mp4Parser.children(box);
1052
+ } else {
1053
+ codecBoxParser(box);
1054
+ }
1055
+ })
1056
+ .box('esds', (box) => {
1057
+ const parsedESDSBox = shaka.util.Mp4BoxParsers.parseESDS(box.reader);
1058
+ codecs.push(parsedESDSBox.codec);
1059
+ hasAudio = true;
1060
+ })
1061
+ .box('ac-3', codecBoxParser)
1062
+ .box('ec-3', codecBoxParser)
1063
+ .box('opus', codecBoxParser)
1064
+ .box('Opus', codecBoxParser)
1065
+ .box('fLaC', codecBoxParser)
1066
+
1067
+ // VIDEO
1068
+ // These are the various boxes that signal a codec.
1069
+ .box('avc1', (box) => {
1070
+ const parsedAVCBox =
1071
+ shaka.util.Mp4BoxParsers.parseAVC(box.reader, box.name);
1072
+ codecs.push(parsedAVCBox.codec);
1073
+ hasVideo = true;
1074
+ })
1075
+ .box('avc3', (box) => {
1076
+ const parsedAVCBox =
1077
+ shaka.util.Mp4BoxParsers.parseAVC(box.reader, box.name);
1078
+ codecs.push(parsedAVCBox.codec);
1079
+ hasVideo = true;
1080
+ })
1081
+ .box('hev1', codecBoxParser)
1082
+ .box('hvc1', codecBoxParser)
1083
+ .box('dvh1', codecBoxParser)
1084
+ .box('dvhe', codecBoxParser)
1085
+ .box('vp09', codecBoxParser)
1086
+ .box('av01', codecBoxParser)
1087
+
1088
+ // This signals an encrypted sample, which we can go inside of to
1089
+ // find the codec used.
1090
+ // Note: If encrypted, you can only have audio or video, not both.
1091
+ .box('enca', Mp4Parser.visualSampleEntry)
1092
+ .box('encv', Mp4Parser.visualSampleEntry)
1093
+ .box('sinf', Mp4Parser.children)
1094
+ .box('frma', (box) => {
1095
+ const {codec} = shaka.util.Mp4BoxParsers.parseFRMA(box.reader);
1096
+ addCodec(codec);
1097
+ })
1098
+
1099
+ .parse(response.data, /* partialOkay= */ true);
1100
+ if (!codecs.length) {
1101
+ return null;
1102
+ }
1103
+ const onlyAudio = hasAudio && !hasVideo;
1104
+ return {
1105
+ type: onlyAudio ? 'audio' : 'video',
1106
+ mimeType: onlyAudio ? 'audio/mp4' : 'video/mp4',
1107
+ codecs: this.filterDuplicateCodecs_(codecs).join(', '),
1108
+ language: language,
1109
+ height: height,
1110
+ width: width,
1111
+ channelCount: channelCount,
1112
+ sampleRate: sampleRate,
1113
+ };
1114
+ }
1115
+
798
1116
  /** @private */
799
1117
  determineDuration_() {
800
1118
  goog.asserts.assert(this.presentationTimeline_,
801
1119
  'Presentation timeline not created!');
802
1120
 
803
1121
  if (this.isLive_()) {
804
- // The HLS spec (RFC 8216) states in 6.3.4:
805
- // "the client MUST wait for at least the target duration before
806
- // attempting to reload the Playlist file again".
807
- // For LL-HLS, the server must add a new partial segment to the Playlist
808
- // every part target duration.
809
- this.updatePlaylistDelay_ = this.minTargetDuration_;
810
-
811
1122
  // The spec says nothing much about seeking in live content, but Safari's
812
1123
  // built-in HLS implementation does not allow it. Therefore we will set
813
1124
  // the availability window equal to the presentation delay. The player
@@ -1209,6 +1520,16 @@ shaka.hls.HlsParser = class {
1209
1520
  /** @type {!Array.<string>} */
1210
1521
  const codecs = codecsString.split(/\s*,\s*/);
1211
1522
 
1523
+ return this.filterDuplicateCodecs_(codecs);
1524
+ }
1525
+
1526
+
1527
+ /**
1528
+ * @param {!Array.<string>} codecs
1529
+ * @return {!Array.<string>} codecs
1530
+ * @private
1531
+ */
1532
+ filterDuplicateCodecs_(codecs) {
1212
1533
  // Filter out duplicate codecs.
1213
1534
  const seen = new Set();
1214
1535
  const ret = [];
@@ -1250,6 +1571,21 @@ shaka.hls.HlsParser = class {
1250
1571
  return count;
1251
1572
  }
1252
1573
 
1574
+ /**
1575
+ * Get the sample rate information for an HLS audio track.
1576
+ *
1577
+ * @param {!shaka.hls.Tag} tag
1578
+ * @return {?number}
1579
+ * @private
1580
+ */
1581
+ getSampleRate_(tag) {
1582
+ const sampleRate = tag.getAttributeValue('SAMPLE-RATE');
1583
+ if (!sampleRate) {
1584
+ return null;
1585
+ }
1586
+ return parseInt(sampleRate, 10);
1587
+ }
1588
+
1253
1589
  /**
1254
1590
  * Get the spatial audio information for an HLS audio track.
1255
1591
  * In HLS the channels field indicates the number of audio channels that the
@@ -1298,16 +1634,15 @@ shaka.hls.HlsParser = class {
1298
1634
  }
1299
1635
 
1300
1636
  /**
1301
- * Get the language value.
1637
+ * Get the normalized language value.
1302
1638
  *
1303
- * @param {!shaka.hls.Tag} tag
1639
+ * @param {?string} languageValue
1304
1640
  * @return {string}
1305
1641
  * @private
1306
1642
  */
1307
- getLanguage_(tag) {
1643
+ getLanguage_(languageValue) {
1308
1644
  const LanguageUtils = shaka.util.LanguageUtils;
1309
- const languageValue = tag.getAttributeValue('LANGUAGE') || 'und';
1310
- return LanguageUtils.normalize(languageValue);
1645
+ return LanguageUtils.normalize(languageValue || 'und');
1311
1646
  }
1312
1647
 
1313
1648
  /**
@@ -1409,6 +1744,7 @@ shaka.hls.HlsParser = class {
1409
1744
  const variant = {
1410
1745
  id: this.globalId_++,
1411
1746
  language: audioStream ? audioStream.language : 'und',
1747
+ disabledUntilTime: 0,
1412
1748
  primary: (!!audioStream && audioStream.primary) ||
1413
1749
  (!!videoStream && videoStream.primary),
1414
1750
  audio: audioStream,
@@ -1439,7 +1775,8 @@ shaka.hls.HlsParser = class {
1439
1775
  for (const tag of closedCaptionsTags) {
1440
1776
  goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
1441
1777
  'Should only be called on media tags!');
1442
- const language = this.getLanguage_(tag);
1778
+ const languageValue = tag.getAttributeValue('LANGUAGE');
1779
+ const language = this.getLanguage_(languageValue);
1443
1780
 
1444
1781
  // The GROUP-ID value is a quoted-string that specifies the group to which
1445
1782
  // the Rendition belongs.
@@ -1485,7 +1822,7 @@ shaka.hls.HlsParser = class {
1485
1822
  return this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
1486
1823
  }
1487
1824
 
1488
- const language = this.getLanguage_(tag);
1825
+ const language = tag.getAttributeValue('LANGUAGE');
1489
1826
  const name = tag.getAttributeValue('NAME');
1490
1827
 
1491
1828
  // NOTE: According to the HLS spec, "DEFAULT=YES" requires "AUTOSELECT=YES".
@@ -1503,12 +1840,13 @@ shaka.hls.HlsParser = class {
1503
1840
 
1504
1841
  const forcedAttrValue = tag.getAttributeValue('FORCED');
1505
1842
  const forced = forcedAttrValue == 'YES';
1843
+ const sampleRate = type == 'audio' ? this.getSampleRate_(tag) : null;
1506
1844
  // TODO: Should we take into account some of the currently ignored
1507
1845
  // attributes: INSTREAM-ID, Attribute descriptions: https://bit.ly/2lpjOhj
1508
1846
  const streamInfo = this.createStreamInfo_(
1509
1847
  verbatimMediaPlaylistUri, codecs, type, language, primary, name,
1510
1848
  channelsCount, /* closedCaptions= */ null, characteristics, forced,
1511
- spatialAudio);
1849
+ sampleRate, spatialAudio);
1512
1850
  if (this.groupIdToStreamInfosMap_.has(groupId)) {
1513
1851
  this.groupIdToStreamInfosMap_.get(groupId).push(streamInfo);
1514
1852
  } else {
@@ -1547,7 +1885,7 @@ shaka.hls.HlsParser = class {
1547
1885
  return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1548
1886
  }
1549
1887
 
1550
- const language = this.getLanguage_(tag);
1888
+ const language = tag.getAttributeValue('LANGUAGE');
1551
1889
  const name = tag.getAttributeValue('NAME');
1552
1890
 
1553
1891
  const characteristics = tag.getAttributeValue('CHARACTERISTICS');
@@ -1555,7 +1893,8 @@ shaka.hls.HlsParser = class {
1555
1893
  const streamInfo = this.createStreamInfo_(
1556
1894
  verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
1557
1895
  name, /* channelsCount= */ null, /* closedCaptions= */ null,
1558
- characteristics, /* forced= */ false, /* spatialAudio= */ false);
1896
+ characteristics, /* forced= */ false, /* sampleRate= */ null,
1897
+ /* spatialAudio= */ false);
1559
1898
 
1560
1899
  // TODO: This check is necessary because of the possibility of multiple
1561
1900
  // calls to createStreamInfoFromImageTag_ before either has resolved.
@@ -1616,10 +1955,10 @@ shaka.hls.HlsParser = class {
1616
1955
  const closedCaptions = this.getClosedCaptions_(tag, type);
1617
1956
  const codecs = shaka.util.ManifestParserUtils.guessCodecs(type, allCodecs);
1618
1957
  const streamInfo = this.createStreamInfo_(verbatimMediaPlaylistUri,
1619
- codecs, type, /* language= */ 'und', /* primary= */ false,
1958
+ codecs, type, /* language= */ null, /* primary= */ false,
1620
1959
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1621
1960
  /* characteristics= */ null, /* forced= */ false,
1622
- /* spatialAudio= */ false);
1961
+ /* sampleRate= */ null, /* spatialAudio= */ false);
1623
1962
  // TODO: This check is necessary because of the possibility of multiple
1624
1963
  // calls to createStreamInfoFromVariantTag_ before either has resolved.
1625
1964
  if (this.uriToStreamInfosMap_.has(verbatimMediaPlaylistUri)) {
@@ -1635,20 +1974,21 @@ shaka.hls.HlsParser = class {
1635
1974
  * @param {string} verbatimMediaPlaylistUri
1636
1975
  * @param {string} codecs
1637
1976
  * @param {string} type
1638
- * @param {string} language
1977
+ * @param {?string} languageValue
1639
1978
  * @param {boolean} primary
1640
1979
  * @param {?string} name
1641
1980
  * @param {?number} channelsCount
1642
1981
  * @param {Map.<string, string>} closedCaptions
1643
1982
  * @param {?string} characteristics
1644
1983
  * @param {boolean} forced
1984
+ * @param {?number} sampleRate
1645
1985
  * @param {boolean} spatialAudio
1646
1986
  * @return {!shaka.hls.HlsParser.StreamInfo}
1647
1987
  * @private
1648
1988
  */
1649
- createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1989
+ createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, languageValue,
1650
1990
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1651
- spatialAudio) {
1991
+ sampleRate, spatialAudio) {
1652
1992
  // TODO: Refactor, too many parameters
1653
1993
  const initialMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1654
1994
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1656,11 +1996,9 @@ shaka.hls.HlsParser = class {
1656
1996
  // This stream is lazy-loaded inside the createSegmentIndex function.
1657
1997
  // So we start out with a stream object that does not contain the actual
1658
1998
  // segment index, then download when createSegmentIndex is called.
1659
- const stream = this.makeStreamObject_(codecs, type, language, primary, name,
1660
- channelsCount, closedCaptions, characteristics, forced, spatialAudio);
1661
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1662
- stream.codecs = '';
1663
- }
1999
+ const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
2000
+ name, channelsCount, closedCaptions, characteristics, forced,
2001
+ sampleRate, spatialAudio);
1664
2002
  const streamInfo = {
1665
2003
  stream,
1666
2004
  type,
@@ -1671,8 +2009,11 @@ shaka.hls.HlsParser = class {
1671
2009
  maxTimestamp: 0,
1672
2010
  mediaSequenceToStartTime: new Map(),
1673
2011
  canSkipSegments: false,
2012
+ canBlockReload: false,
1674
2013
  hasEndList: false,
1675
2014
  firstSequenceNumber: -1,
2015
+ nextMediaSequence: -1,
2016
+ nextPart: -1,
1676
2017
  loadedOnce: false,
1677
2018
  };
1678
2019
 
@@ -1680,7 +2021,7 @@ shaka.hls.HlsParser = class {
1680
2021
  const downloadSegmentIndex = async (abortSignal) => {
1681
2022
  // Download the actual manifest.
1682
2023
  const response = await this.requestManifest_(
1683
- streamInfo.absoluteMediaPlaylistUri);
2024
+ streamInfo.absoluteMediaPlaylistUri, /* isPlaylist= */ true);
1684
2025
  if (abortSignal.aborted) {
1685
2026
  return;
1686
2027
  }
@@ -1697,8 +2038,8 @@ shaka.hls.HlsParser = class {
1697
2038
  const wasLive = this.isLive_();
1698
2039
  const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
1699
2040
  playlist, verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs,
1700
- type, language, primary, name, channelsCount, closedCaptions,
1701
- characteristics, forced, spatialAudio);
2041
+ type, languageValue, primary, name, channelsCount, closedCaptions,
2042
+ characteristics, forced, sampleRate, spatialAudio);
1702
2043
  if (abortSignal.aborted) {
1703
2044
  return;
1704
2045
  }
@@ -1716,18 +2057,19 @@ shaka.hls.HlsParser = class {
1716
2057
  streamInfo.minTimestamp = realStreamInfo.minTimestamp;
1717
2058
  streamInfo.maxTimestamp = realStreamInfo.maxTimestamp;
1718
2059
  streamInfo.canSkipSegments = realStreamInfo.canSkipSegments;
2060
+ streamInfo.canBlockReload = realStreamInfo.canBlockReload;
1719
2061
  streamInfo.hasEndList = realStreamInfo.hasEndList;
1720
2062
  streamInfo.mediaSequenceToStartTime =
1721
2063
  realStreamInfo.mediaSequenceToStartTime;
2064
+ streamInfo.nextMediaSequence = realStreamInfo.nextMediaSequence;
2065
+ streamInfo.nextPart = realStreamInfo.nextPart;
1722
2066
  streamInfo.loadedOnce = true;
1723
2067
  stream.segmentIndex = realStream.segmentIndex;
1724
2068
  stream.encrypted = realStream.encrypted;
1725
2069
  stream.drmInfos = realStream.drmInfos;
1726
2070
  stream.keyIds = realStream.keyIds;
1727
2071
  stream.mimeType = realStream.mimeType;
1728
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1729
- stream.codecs = '';
1730
- }
2072
+ stream.bandwidth = realStream.bandwidth;
1731
2073
 
1732
2074
  // Since we lazy-loaded this content, the player may need to create new
1733
2075
  // sessions for the DRM info in this stream.
@@ -1742,10 +2084,6 @@ shaka.hls.HlsParser = class {
1742
2084
  // To aid manifest filtering, assume before loading that all video
1743
2085
  // renditions have the same MIME type. (And likewise for audio.)
1744
2086
  otherStreamInfo.stream.mimeType = realStream.mimeType;
1745
- if (shaka.media.MediaSourceEngine.RAW_FORMATS
1746
- .includes(otherStreamInfo.stream.mimeType)) {
1747
- otherStreamInfo.stream.codecs = '';
1748
- }
1749
2087
  }
1750
2088
  }
1751
2089
  }
@@ -1775,7 +2113,7 @@ shaka.hls.HlsParser = class {
1775
2113
  this.determineDuration_();
1776
2114
  // Finally, start the update timer, if this asset has been determined
1777
2115
  // to be a livestream.
1778
- const delay = this.updatePlaylistDelay_;
2116
+ const delay = this.getUpdatePlaylistDelay_();
1779
2117
  if (delay > 0) {
1780
2118
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
1781
2119
  }
@@ -1892,13 +2230,6 @@ shaka.hls.HlsParser = class {
1892
2230
  streamInfo.stream.segmentIndex.fit(/* periodStart= */ 0, minDuration);
1893
2231
  }
1894
2232
  }
1895
- // MediaSource expects no codec strings combined with raw formats.
1896
- for (const streamInfo of streamInfos) {
1897
- const stream = streamInfo.stream;
1898
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1899
- stream.codecs = '';
1900
- }
1901
- }
1902
2233
  this.notifySegmentsForStreams_(streamInfos.map((s) => s.stream));
1903
2234
  if (this.config_.hls.ignoreManifestProgramDateTime) {
1904
2235
  this.syncStreamsWithSequenceNumber_(streamInfos);
@@ -1945,22 +2276,23 @@ shaka.hls.HlsParser = class {
1945
2276
  * @param {string} absoluteMediaPlaylistUri
1946
2277
  * @param {string} codecs
1947
2278
  * @param {string} type
1948
- * @param {string} language
2279
+ * @param {?string} languageValue
1949
2280
  * @param {boolean} primary
1950
2281
  * @param {?string} name
1951
2282
  * @param {?number} channelsCount
1952
2283
  * @param {Map.<string, string>} closedCaptions
1953
2284
  * @param {?string} characteristics
1954
2285
  * @param {boolean} forced
2286
+ * @param {?number} sampleRate
1955
2287
  * @param {boolean} spatialAudio
1956
2288
  * @param {(string|undefined)} mimeType
1957
2289
  * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1958
2290
  * @private
1959
2291
  */
1960
2292
  async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
1961
- absoluteMediaPlaylistUri, codecs, type, language, primary, name,
1962
- channelsCount, closedCaptions, characteristics, forced, spatialAudio,
1963
- mimeType = undefined) {
2293
+ absoluteMediaPlaylistUri, codecs, type, languageValue, primary, name,
2294
+ channelsCount, closedCaptions, characteristics, forced, sampleRate,
2295
+ spatialAudio, mimeType = undefined) {
1964
2296
  if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
1965
2297
  // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
1966
2298
  // playlists.
@@ -1981,6 +2313,10 @@ shaka.hls.HlsParser = class {
1981
2313
 
1982
2314
  this.determinePresentationType_(playlist);
1983
2315
 
2316
+ if (this.isLive_()) {
2317
+ this.determineLastTargetDuration_(playlist);
2318
+ }
2319
+
1984
2320
  if (!mimeType) {
1985
2321
  mimeType = await this.guessMimeType_(type, codecs, playlist,
1986
2322
  mediaVariables);
@@ -1998,18 +2334,18 @@ shaka.hls.HlsParser = class {
1998
2334
 
1999
2335
  const mediaSequenceToStartTime = this.isLive_() ?
2000
2336
  this.mediaSequenceToStartTimeByType_.get(type) : new Map();
2001
- const prevLowLatencyMode = this.lowLatencyMode_;
2002
- const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
2003
- type, mimeType, mediaSequenceToStartTime, mediaVariables);
2337
+ const {segments, bandwidth} = this.createSegments_(
2338
+ verbatimMediaPlaylistUri, playlist, type, mimeType,
2339
+ mediaSequenceToStartTime, mediaVariables);
2004
2340
 
2005
- // This happens when autoLowLatencyMode is true, so we need set the
2006
- // correct lowLatencyPresentationDelay_
2007
- if (prevLowLatencyMode != this.lowLatencyMode_) {
2008
- this.determinePresentationType_(playlist);
2341
+ // This new calculation is necessary for Low Latency streams.
2342
+ if (this.isLive_()) {
2343
+ this.determineLastTargetDuration_(playlist);
2009
2344
  }
2010
2345
 
2011
2346
  const firstStartTime = segments[0].startTime;
2012
- const lastEndTime = segments[segments.length - 1].endTime;
2347
+ const lastSegment = segments[segments.length - 1];
2348
+ const lastEndTime = lastSegment.endTime;
2013
2349
  /** @type {!shaka.media.SegmentIndex} */
2014
2350
  const segmentIndex = new shaka.media.SegmentIndex(segments);
2015
2351
 
@@ -2017,14 +2353,24 @@ shaka.hls.HlsParser = class {
2017
2353
  playlist.tags, 'EXT-X-SERVER-CONTROL');
2018
2354
  const canSkipSegments = serverControlTag ?
2019
2355
  serverControlTag.getAttribute('CAN-SKIP-UNTIL') != null : false;
2356
+ const canBlockReload = serverControlTag ?
2357
+ serverControlTag.getAttribute('CAN-BLOCK-RELOAD') != null : false;
2020
2358
 
2021
- const stream = this.makeStreamObject_(codecs, type, language, primary, name,
2022
- channelsCount, closedCaptions, characteristics, forced, spatialAudio);
2359
+ const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2360
+ playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
2361
+
2362
+ const {nextMediaSequence, nextPart} =
2363
+ this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);
2364
+
2365
+ const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
2366
+ name, channelsCount, closedCaptions, characteristics, forced,
2367
+ sampleRate, spatialAudio);
2023
2368
  stream.segmentIndex = segmentIndex;
2024
2369
  stream.encrypted = encrypted;
2025
2370
  stream.drmInfos = drmInfos;
2026
2371
  stream.keyIds = keyIds;
2027
2372
  stream.mimeType = mimeType;
2373
+ stream.bandwidth = bandwidth;
2028
2374
 
2029
2375
  return {
2030
2376
  stream,
@@ -2034,13 +2380,60 @@ shaka.hls.HlsParser = class {
2034
2380
  minTimestamp: firstStartTime,
2035
2381
  maxTimestamp: lastEndTime,
2036
2382
  canSkipSegments,
2383
+ canBlockReload,
2037
2384
  hasEndList: false,
2038
2385
  firstSequenceNumber: -1,
2386
+ nextMediaSequence,
2387
+ nextPart,
2039
2388
  mediaSequenceToStartTime,
2040
2389
  loadedOnce: false,
2041
2390
  };
2042
2391
  }
2043
2392
 
2393
+ /**
2394
+ * Get the next msn and part
2395
+ *
2396
+ * @param {number} mediaSequenceNumber
2397
+ * @param {!Array.<!shaka.media.SegmentReference>} segments
2398
+ * @return {{nextMediaSequence: number, nextPart:number}}}
2399
+ * @private
2400
+ */
2401
+ getNextMediaSequenceAndPart_(mediaSequenceNumber, segments) {
2402
+ const currentMediaSequence = mediaSequenceNumber + segments.length - 1;
2403
+ let nextMediaSequence = currentMediaSequence;
2404
+ let nextPart = -1;
2405
+ if (!segments.length) {
2406
+ nextMediaSequence++;
2407
+ return {
2408
+ nextMediaSequence,
2409
+ nextPart,
2410
+ };
2411
+ }
2412
+ const lastSegment = segments[segments.length - 1];
2413
+ const partialReferences = lastSegment.partialReferences;
2414
+ if (!lastSegment.partialReferences.length) {
2415
+ nextMediaSequence++;
2416
+ if (lastSegment.hasByterangeOptimization()) {
2417
+ nextPart = 0;
2418
+ }
2419
+ return {
2420
+ nextMediaSequence,
2421
+ nextPart,
2422
+ };
2423
+ }
2424
+ nextPart = partialReferences.length - 1;
2425
+ const lastPartialReference =
2426
+ partialReferences[partialReferences.length - 1];
2427
+ if (!lastPartialReference.isPreload()) {
2428
+ nextMediaSequence++;
2429
+ nextPart = 0;
2430
+ }
2431
+ return {
2432
+ nextMediaSequence,
2433
+ nextPart,
2434
+ };
2435
+ }
2436
+
2044
2437
 
2045
2438
  /**
2046
2439
  * Creates a stream object with the given parameters.
@@ -2049,19 +2442,20 @@ shaka.hls.HlsParser = class {
2049
2442
  * manually on the object after creation.
2050
2443
  * @param {string} codecs
2051
2444
  * @param {string} type
2052
- * @param {string} language
2445
+ * @param {?string} languageValue
2053
2446
  * @param {boolean} primary
2054
2447
  * @param {?string} name
2055
2448
  * @param {?number} channelsCount
2056
2449
  * @param {Map.<string, string>} closedCaptions
2057
2450
  * @param {?string} characteristics
2058
2451
  * @param {boolean} forced
2452
+ * @param {?number} sampleRate
2059
2453
  * @param {boolean} spatialAudio
2060
2454
  * @return {!shaka.extern.Stream}
2061
2455
  * @private
2062
2456
  */
2063
- makeStreamObject_(codecs, type, language, primary, name, channelsCount,
2064
- closedCaptions, characteristics, forced, spatialAudio) {
2457
+ makeStreamObject_(codecs, type, languageValue, primary, name, channelsCount,
2458
+ closedCaptions, characteristics, forced, sampleRate, spatialAudio) {
2065
2459
  // Fill out a "best-guess" mimeType, for now. It will be replaced once the
2066
2460
  // stream is lazy-loaded.
2067
2461
  const mimeType = this.guessMimeTypeBeforeLoading_(type, codecs) ||
@@ -2090,11 +2484,13 @@ shaka.hls.HlsParser = class {
2090
2484
  segmentIndex: null,
2091
2485
  mimeType,
2092
2486
  codecs,
2093
- kind,
2487
+ kind: (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
2488
+ shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined,
2094
2489
  encrypted: false,
2095
2490
  drmInfos: [],
2096
2491
  keyIds: new Set(),
2097
- language,
2492
+ language: this.getLanguage_(languageValue),
2493
+ originalLanguage: languageValue,
2098
2494
  label: name, // For historical reasons, since before "originalId".
2099
2495
  type,
2100
2496
  primary,
@@ -2109,11 +2505,12 @@ shaka.hls.HlsParser = class {
2109
2505
  roles,
2110
2506
  forced,
2111
2507
  channelsCount,
2112
- audioSamplingRate: null,
2508
+ audioSamplingRate: sampleRate,
2113
2509
  spatialAudio,
2114
2510
  closedCaptions,
2115
2511
  hdr: undefined,
2116
2512
  tilesLayout: undefined,
2513
+ accessibilityPurpose: null,
2117
2514
  external: false,
2118
2515
  };
2119
2516
  }
@@ -2284,43 +2681,89 @@ shaka.hls.HlsParser = class {
2284
2681
  } else {
2285
2682
  this.setPresentationType_(PresentationType.EVENT);
2286
2683
  }
2684
+ }
2685
+ }
2686
+
2687
+
2688
+ /**
2689
+ * @param {!shaka.hls.Playlist} playlist
2690
+ * @private
2691
+ */
2692
+ determineLastTargetDuration_(playlist) {
2693
+ let lastTargetDuration = Infinity;
2694
+ const segments = playlist.segments;
2695
+ if (segments.length) {
2696
+ let segmentIndex = segments.length - 1;
2697
+ while (segmentIndex >= 0) {
2698
+ const segment = segments[segmentIndex];
2699
+ const extinfTag =
2700
+ shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXTINF');
2701
+ if (extinfTag) {
2702
+ // The EXTINF tag format is '#EXTINF:<duration>,[<title>]'.
2703
+ // We're interested in the duration part.
2704
+ const extinfValues = extinfTag.value.split(',');
2705
+ lastTargetDuration = Number(extinfValues[0]);
2706
+ break;
2707
+ }
2708
+ segmentIndex--;
2709
+ }
2710
+ }
2287
2711
 
2288
- const targetDurationTag = this.getRequiredTag_(playlist.tags,
2289
- 'EXT-X-TARGETDURATION');
2290
- const targetDuration = Number(targetDurationTag.value);
2291
- const partialTargetDurationTag =
2292
- shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF');
2293
- // According to the HLS spec, updates should not happen more often than
2294
- // once in targetDuration. It also requires us to only update the active
2295
- // variant. We might implement that later, but for now every variant
2296
- // will be updated. To get the update period, choose the smallest
2297
- // targetDuration value across all playlists.
2298
- // 1. Update the shortest one to use as update period and segment
2299
- // availability time (for LIVE).
2300
- if (this.lowLatencyMode_ && partialTargetDurationTag) {
2301
- // For low latency streaming, use the partial segment target duration.
2302
- this.partialTargetDuration_ = Number(
2303
- partialTargetDurationTag.getRequiredAttrValue('PART-TARGET'));
2304
- this.minTargetDuration_ = Math.min(
2305
- this.partialTargetDuration_, this.minTargetDuration_);
2306
- // Get the server-recommended min distance from the live edge.
2307
- const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
2308
- playlist.tags, 'EXT-X-SERVER-CONTROL');
2309
- // Use 'PART-HOLD-BACK' as the presentation delay for low latency mode.
2310
- this.lowLatencyPresentationDelay_ = serverControlTag ? Number(
2311
- serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0;
2712
+ const targetDurationTag = this.getRequiredTag_(playlist.tags,
2713
+ 'EXT-X-TARGETDURATION');
2714
+ const targetDuration = Number(targetDurationTag.value);
2715
+ const partialTargetDurationTag =
2716
+ shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF');
2717
+ if (partialTargetDurationTag) {
2718
+ this.partialTargetDuration_ = Number(
2719
+ partialTargetDurationTag.getRequiredAttrValue('PART-TARGET'));
2720
+ }
2721
+ // Get the server-recommended min distance from the live edge.
2722
+ const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
2723
+ playlist.tags, 'EXT-X-SERVER-CONTROL');
2724
+ // According to the HLS spec, updates should not happen more often than
2725
+ // once in targetDuration. It also requires us to only update the active
2726
+ // variant. We might implement that later, but for now every variant
2727
+ // will be updated. To get the update period, choose the smallest
2728
+ // targetDuration value across all playlists.
2729
+ // 1. Update the shortest one to use as update period and segment
2730
+ // availability time (for LIVE).
2731
+ if (this.lowLatencyMode_ && this.partialTargetDuration_) {
2732
+ // For low latency streaming, use the partial segment target duration.
2733
+ if (this.lowLatencyByterangeOptimization_) {
2734
+ // We always have at least 1 partial segment part, and most servers
2735
+ // allow you to make a request with _HLS_msn=X&_HLS_part=0 with a
2736
+ // distance of 4 partial segments. With this we ensure that we
2737
+ // obtain the minimum latency in this type of case.
2738
+ if (this.partialTargetDuration_ * 5 <= lastTargetDuration) {
2739
+ this.lastTargetDuration_ = Math.min(
2740
+ this.partialTargetDuration_, this.lastTargetDuration_);
2741
+ } else {
2742
+ this.lastTargetDuration_ = Math.min(
2743
+ lastTargetDuration, this.lastTargetDuration_);
2744
+ }
2312
2745
  } else {
2313
- // For regular HLS, use the target duration of regular segments.
2314
- this.minTargetDuration_ = Math.min(
2315
- targetDuration, this.minTargetDuration_);
2746
+ this.lastTargetDuration_ = Math.min(
2747
+ this.partialTargetDuration_, this.lastTargetDuration_);
2316
2748
  }
2317
- // 2. Update the longest target duration if need be to use as a
2318
- // presentation delay later.
2319
- this.maxTargetDuration_ = Math.max(
2320
- targetDuration, this.maxTargetDuration_);
2749
+ // Use 'PART-HOLD-BACK' as the presentation delay for low latency mode.
2750
+ this.lowLatencyPresentationDelay_ = serverControlTag ? Number(
2751
+ serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0;
2752
+ } else {
2753
+ this.lastTargetDuration_ = Math.min(
2754
+ lastTargetDuration, this.lastTargetDuration_);
2755
+ // Use 'HOLD-BACK' as the presentation delay for default if defined.
2756
+ const holdBack = serverControlTag ?
2757
+ serverControlTag.getAttribute('HOLD-BACK') : null;
2758
+ this.presentationDelay_ = holdBack ? Number(holdBack.value) : 0;
2321
2759
  }
2760
+ // 2. Update the longest target duration if need be to use as a
2761
+ // presentation delay later.
2762
+ this.maxTargetDuration_ = Math.max(
2763
+ targetDuration, this.maxTargetDuration_);
2322
2764
  }
2323
2765
 
2766
+
2324
2767
  /**
2325
2768
  * @private
2326
2769
  */
@@ -2344,6 +2787,8 @@ shaka.hls.HlsParser = class {
2344
2787
  presentationDelay = this.config_.defaultPresentationDelay;
2345
2788
  } else if (this.lowLatencyPresentationDelay_) {
2346
2789
  presentationDelay = this.lowLatencyPresentationDelay_;
2790
+ } else if (this.presentationDelay_) {
2791
+ presentationDelay = this.presentationDelay_;
2347
2792
  } else {
2348
2793
  const numberOfSegments = this.config_.hls.liveSegmentsDelay;
2349
2794
  presentationDelay = this.maxTargetDuration_ * numberOfSegments;
@@ -2426,6 +2871,7 @@ shaka.hls.HlsParser = class {
2426
2871
  * @param {!Map.<string, string>} variables
2427
2872
  * @param {string} absoluteMediaPlaylistUri
2428
2873
  * @param {string} type
2874
+ * @param {string} mimeType
2429
2875
  * @param {number} timestampOffset
2430
2876
  * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
2431
2877
  * @return {shaka.media.SegmentReference}
@@ -2433,11 +2879,9 @@ shaka.hls.HlsParser = class {
2433
2879
  */
2434
2880
  createSegmentReference_(
2435
2881
  initSegmentReference, previousReference, hlsSegment, startTime,
2436
- variables, absoluteMediaPlaylistUri, type, timestampOffset,
2882
+ variables, absoluteMediaPlaylistUri, type, mimeType, timestampOffset,
2437
2883
  hlsAes128Key) {
2438
2884
  const tags = hlsSegment.tags;
2439
- const absoluteSegmentUri = this.variableSubstitution_(
2440
- hlsSegment.absoluteUri, variables);
2441
2885
  const extinfTag =
2442
2886
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
2443
2887
 
@@ -2483,8 +2927,20 @@ shaka.hls.HlsParser = class {
2483
2927
  }
2484
2928
 
2485
2929
  // Create SegmentReferences for the partial segments.
2486
- const partialSegmentRefs = [];
2487
- if (this.lowLatencyMode_) {
2930
+ let partialSegmentRefs = [];
2931
+
2932
+ // Optimization for LL-HLS with byterange
2933
+ // More info in https://tinyurl.com/hls-open-byte-range
2934
+ let segmentWithByteRangeOptimization = false;
2935
+ let getUrisOptimization = null;
2936
+ let somePartialSegmentWithGap = false;
2937
+ let isPreloadSegment = false;
2938
+
2939
+ if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
2940
+ const byterangeOptimizationSupport = (mimeType == 'video/mp4' ||
2941
+ mimeType == 'audio/mp4') && window.ReadableStream;
2942
+
2943
+ let partialSyncTime = syncTime;
2488
2944
  for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
2489
2945
  const item = hlsSegment.partialSegments[i];
2490
2946
  const pPreviousReference = i == 0 ?
@@ -2512,6 +2968,16 @@ shaka.hls.HlsParser = class {
2512
2968
  // A preload hinted partial segment may have byterange start info.
2513
2969
  const pByterangeStart = item.getAttributeValue('BYTERANGE-START');
2514
2970
  pStartByte = pByterangeStart ? Number(pByterangeStart) : 0;
2971
+ // A preload hinted partial segment may have byterange length info.
2972
+ const pByterangeLength = item.getAttributeValue('BYTERANGE-LENGTH');
2973
+ if (pByterangeLength) {
2974
+ pEndByte = pStartByte + Number(pByterangeLength) - 1;
2975
+ } else if (pStartByte) {
2976
+ // If we have a non-zero start byte, but no end byte, follow the
2977
+ // recommendation of https://tinyurl.com/hls-open-byte-range and
2978
+ // set the end byte explicitly to a large integer.
2979
+ pEndByte = Number.MAX_SAFE_INTEGER;
2980
+ }
2515
2981
  } else {
2516
2982
  const pByterange = item.getAttributeValue('BYTERANGE');
2517
2983
  [pStartByte, pEndByte] =
@@ -2521,20 +2987,33 @@ shaka.hls.HlsParser = class {
2521
2987
  if (!pUri) {
2522
2988
  continue;
2523
2989
  }
2524
- const pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
2525
- absoluteMediaPlaylistUri, pUri);
2526
2990
 
2527
2991
  let partialStatus = shaka.media.SegmentReference.Status.AVAILABLE;
2528
2992
  if (item.getAttributeValue('GAP') == 'YES') {
2529
2993
  partialStatus = shaka.media.SegmentReference.Status.MISSING;
2994
+ somePartialSegmentWithGap = true;
2995
+ }
2996
+
2997
+ let pAbsoluteUri = null;
2998
+ const getPartialUris = () => {
2999
+ if (pAbsoluteUri == null) {
3000
+ goog.asserts.assert(pUri, 'Partial uri should be defined!');
3001
+ pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
3002
+ absoluteMediaPlaylistUri, pUri);
3003
+ }
3004
+ return [pAbsoluteUri];
3005
+ };
3006
+
3007
+ if (byterangeOptimizationSupport &&
3008
+ pStartByte >= 0 && pEndByte != null) {
3009
+ getUrisOptimization = getPartialUris;
3010
+ segmentWithByteRangeOptimization = true;
2530
3011
  }
2531
3012
 
2532
- // We do not set the AES-128 key information for partial segments, as we
2533
- // do not support AES-128 and low-latency at the same time.
2534
3013
  const partial = new shaka.media.SegmentReference(
2535
3014
  pStartTime,
2536
3015
  pEndTime,
2537
- () => [pAbsoluteUri],
3016
+ getPartialUris,
2538
3017
  pStartByte,
2539
3018
  pEndByte,
2540
3019
  initSegmentReference,
@@ -2544,12 +3023,25 @@ shaka.hls.HlsParser = class {
2544
3023
  /* partialReferences= */ [],
2545
3024
  /* tilesLayout= */ '',
2546
3025
  /* tileDuration= */ null,
2547
- /* syncTime= */ null,
2548
- partialStatus);
3026
+ partialSyncTime,
3027
+ partialStatus,
3028
+ hlsAes128Key);
2549
3029
  if (item.name == 'EXT-X-PRELOAD-HINT') {
2550
3030
  partial.markAsPreload();
3031
+ isPreloadSegment = true;
3032
+ }
3033
+ // The spec doesn't say that we can assume INDEPENDENT=YES for the
3034
+ // first partial segment. It does call the flag "optional", though, and
3035
+ // that cases where there are no such flags on any partial segments, it
3036
+ // is sensible to assume the first one is independent.
3037
+ if (item.getAttributeValue('INDEPENDENT') != 'YES' && i > 0) {
3038
+ partial.markAsNonIndependent();
2551
3039
  }
2552
3040
  partialSegmentRefs.push(partial);
3041
+
3042
+ if (partialSyncTime) {
3043
+ partialSyncTime += pDuration;
3044
+ }
2553
3045
  } // for-loop of hlsSegment.partialSegments
2554
3046
  }
2555
3047
 
@@ -2574,6 +3066,17 @@ shaka.hls.HlsParser = class {
2574
3066
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
2575
3067
  }
2576
3068
 
3069
+ if (segmentWithByteRangeOptimization) {
3070
+ // We cannot optimize segments with gaps, or with a start byte that is
3071
+ // not 0.
3072
+ if (somePartialSegmentWithGap || partialSegmentRefs[0].startByte != 0) {
3073
+ segmentWithByteRangeOptimization = false;
3074
+ getUrisOptimization = null;
3075
+ } else {
3076
+ partialSegmentRefs = [];
3077
+ }
3078
+ }
3079
+
2577
3080
  // If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
2578
3081
  // base on the byterange information. If segment has no EXT-X-BYTERANGE tag
2579
3082
  // and has partial segments, set the start byte and end byte base on the
@@ -2604,10 +3107,22 @@ shaka.hls.HlsParser = class {
2604
3107
  }
2605
3108
  }
2606
3109
 
2607
- return new shaka.media.SegmentReference(
3110
+ let absoluteSegmentUri = null;
3111
+ const getUris = () => {
3112
+ if (getUrisOptimization) {
3113
+ return getUrisOptimization();
3114
+ }
3115
+ if (absoluteSegmentUri == null) {
3116
+ absoluteSegmentUri = this.variableSubstitution_(
3117
+ hlsSegment.absoluteUri, variables);
3118
+ }
3119
+ return absoluteSegmentUri.length ? [absoluteSegmentUri] : [];
3120
+ };
3121
+
3122
+ const reference = new shaka.media.SegmentReference(
2608
3123
  startTime,
2609
3124
  endTime,
2610
- () => absoluteSegmentUri.length ? [absoluteSegmentUri] : [],
3125
+ getUris,
2611
3126
  startByte,
2612
3127
  endByte,
2613
3128
  initSegmentReference,
@@ -2621,6 +3136,17 @@ shaka.hls.HlsParser = class {
2621
3136
  status,
2622
3137
  hlsAes128Key,
2623
3138
  );
3139
+
3140
+ if (segmentWithByteRangeOptimization) {
3141
+ this.lowLatencyByterangeOptimization_ = true;
3142
+ reference.markAsByterangeOptimization();
3143
+
3144
+ if (isPreloadSegment) {
3145
+ reference.markAsPreload();
3146
+ }
3147
+ }
3148
+
3149
+ return reference;
2624
3150
  }
2625
3151
 
2626
3152
 
@@ -2652,7 +3178,9 @@ shaka.hls.HlsParser = class {
2652
3178
  }
2653
3179
 
2654
3180
  /**
2655
- * Parses shaka.hls.Segment objects into shaka.media.SegmentReferences.
3181
+ * Parses shaka.hls.Segment objects into shaka.media.SegmentReferences and
3182
+ * get the bandwidth necessary for this segments If it's defined in the
3183
+ * playlist.
2656
3184
  *
2657
3185
  * @param {string} verbatimMediaPlaylistUri
2658
3186
  * @param {!shaka.hls.Playlist} playlist
@@ -2660,7 +3188,8 @@ shaka.hls.HlsParser = class {
2660
3188
  * @param {string} mimeType
2661
3189
  * @param {!Map.<number, number>} mediaSequenceToStartTime
2662
3190
  * @param {!Map.<string, string>} variables
2663
- * @return {!Array.<!shaka.media.SegmentReference>}
3191
+ * @return {{segments: !Array.<!shaka.media.SegmentReference>,
3192
+ * bandwidth: (number|undefined)}}
2664
3193
  * @private
2665
3194
  */
2666
3195
  createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
@@ -2698,6 +3227,9 @@ shaka.hls.HlsParser = class {
2698
3227
  let previousReference = null;
2699
3228
  let lastDiscontinuityStartTime = firstStartTime;
2700
3229
 
3230
+ /** @type {!Array.<{bitrate: number, duration: number}>} */
3231
+ const bitrates = [];
3232
+
2701
3233
  for (let i = 0; i < hlsSegments.length; i++) {
2702
3234
  const item = hlsSegments[i];
2703
3235
  const startTime =
@@ -2747,10 +3279,26 @@ shaka.hls.HlsParser = class {
2747
3279
  variables,
2748
3280
  playlist.absoluteUri,
2749
3281
  type,
3282
+ mimeType,
2750
3283
  lastDiscontinuityStartTime,
2751
3284
  hlsAes128Key);
2752
3285
 
2753
3286
  if (reference) {
3287
+ const bitrate = shaka.hls.Utils.getFirstTagWithNameAsNumber(
3288
+ item.tags, 'EXT-X-BITRATE');
3289
+ if (bitrate) {
3290
+ bitrates.push({
3291
+ bitrate,
3292
+ duration: reference.endTime - reference.startTime,
3293
+ });
3294
+ } else if (bitrates.length) {
3295
+ // It applies to every segment between it and the next EXT-X-BITRATE,
3296
+ // so we use the latest bitrate value
3297
+ const prevBitrate = bitrates.pop();
3298
+ prevBitrate.duration += reference.endTime - reference.startTime;
3299
+ bitrates.push(prevBitrate);
3300
+ }
3301
+
2754
3302
  previousReference = reference;
2755
3303
  reference.discontinuitySequence = discontinuitySequence;
2756
3304
 
@@ -2765,6 +3313,16 @@ shaka.hls.HlsParser = class {
2765
3313
  }
2766
3314
  }
2767
3315
 
3316
+ let bandwidth = undefined;
3317
+ if (bitrates.length) {
3318
+ const duration = bitrates.reduce((sum, value) => {
3319
+ return sum + value.duration;
3320
+ }, 0);
3321
+ bandwidth = Math.round(bitrates.reduce((sum, value) => {
3322
+ return sum + value.bitrate * value.duration;
3323
+ }, 0) / duration * 1000);
3324
+ }
3325
+
2768
3326
  // If some segments have sync times, but not all, extrapolate the sync
2769
3327
  // times of the ones with none.
2770
3328
  const someSyncTime = references.some((ref) => ref.syncTime != null);
@@ -2850,7 +3408,10 @@ shaka.hls.HlsParser = class {
2850
3408
  }
2851
3409
  }
2852
3410
 
2853
- return references;
3411
+ return {
3412
+ segments: references,
3413
+ bandwidth,
3414
+ };
2854
3415
  }
2855
3416
 
2856
3417
  /**
@@ -2995,9 +3556,9 @@ shaka.hls.HlsParser = class {
2995
3556
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
2996
3557
  [middleSegmentUri], this.config_.retryParameters);
2997
3558
  headRequest.method = 'HEAD';
2998
-
3559
+ const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
2999
3560
  const response = await this.makeNetworkRequest_(
3000
- headRequest, requestType);
3561
+ headRequest, requestType, {type});
3001
3562
 
3002
3563
  const contentMimeType = response.headers['content-type'];
3003
3564
  if (contentMimeType) {
@@ -3051,19 +3612,20 @@ shaka.hls.HlsParser = class {
3051
3612
  * with the resulting data.
3052
3613
  *
3053
3614
  * @param {string} absoluteUri
3615
+ * @param {boolean=} isPlaylist
3054
3616
  * @return {!Promise.<!shaka.extern.Response>}
3055
3617
  * @private
3056
3618
  */
3057
- requestManifest_(absoluteUri) {
3619
+ requestManifest_(absoluteUri, isPlaylist) {
3058
3620
  const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
3059
3621
 
3060
3622
  const request = shaka.net.NetworkingEngine.makeRequest(
3061
3623
  [absoluteUri], this.config_.retryParameters);
3062
3624
 
3063
- const format = shaka.util.CmcdManager.StreamingFormat.HLS;
3064
- this.playerInterface_.modifyManifestRequest(request, {format: format});
3065
-
3066
- return this.makeNetworkRequest_(request, requestType);
3625
+ const type = isPlaylist ?
3626
+ shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_PLAYLIST :
3627
+ shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST;
3628
+ return this.makeNetworkRequest_(request, requestType, {type});
3067
3629
  }
3068
3630
 
3069
3631
  /**
@@ -3078,7 +3640,7 @@ shaka.hls.HlsParser = class {
3078
3640
  shaka.log.info('Updating manifest...');
3079
3641
 
3080
3642
  goog.asserts.assert(
3081
- this.updatePlaylistDelay_ > 0,
3643
+ this.getUpdatePlaylistDelay_() > 0,
3082
3644
  'We should only call |onUpdate_| when we are suppose to be updating.');
3083
3645
 
3084
3646
  // Detect a call to stop()
@@ -3091,7 +3653,7 @@ shaka.hls.HlsParser = class {
3091
3653
 
3092
3654
  // This may have converted to VOD, in which case we stop updating.
3093
3655
  if (this.isLive_()) {
3094
- const delay = this.updatePlaylistDelay_;
3656
+ const delay = this.getUpdatePlaylistDelay_();
3095
3657
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
3096
3658
  }
3097
3659
  } catch (error) {
@@ -3103,6 +3665,11 @@ shaka.hls.HlsParser = class {
3103
3665
  goog.asserts.assert(error instanceof shaka.util.Error,
3104
3666
  'Should only receive a Shaka error');
3105
3667
 
3668
+ if (this.config_.raiseFatalErrorOnManifestUpdateRequestFailure) {
3669
+ this.playerInterface_.onError(error);
3670
+ return;
3671
+ }
3672
+
3106
3673
  // We will retry updating, so override the severity of the error.
3107
3674
  error.severity = shaka.util.Error.Severity.RECOVERABLE;
3108
3675
  this.playerInterface_.onError(error);
@@ -3123,6 +3690,20 @@ shaka.hls.HlsParser = class {
3123
3690
  }
3124
3691
 
3125
3692
 
3693
+ /**
3694
+ * @return {number}
3695
+ * @private
3696
+ */
3697
+ getUpdatePlaylistDelay_() {
3698
+ // The HLS spec (RFC 8216) states in 6.3.4:
3699
+ // "the client MUST wait for at least the target duration before
3700
+ // attempting to reload the Playlist file again".
3701
+ // For LL-HLS, the server must add a new partial segment to the Playlist
3702
+ // every part target duration.
3703
+ return this.lastTargetDuration_;
3704
+ }
3705
+
3706
+
3126
3707
  /**
3127
3708
  * @param {shaka.hls.HlsParser.PresentationType_} type
3128
3709
  * @private
@@ -3149,10 +3730,11 @@ shaka.hls.HlsParser = class {
3149
3730
  *
3150
3731
  * @param {shaka.extern.Request} request
3151
3732
  * @param {shaka.net.NetworkingEngine.RequestType} type
3733
+ * @param {shaka.extern.RequestContext=} context
3152
3734
  * @return {!Promise.<shaka.extern.Response>}
3153
3735
  * @private
3154
3736
  */
3155
- makeNetworkRequest_(request, type) {
3737
+ makeNetworkRequest_(request, type, context) {
3156
3738
  if (!this.operationManager_) {
3157
3739
  throw new shaka.util.Error(
3158
3740
  shaka.util.Error.Severity.CRITICAL,
@@ -3160,7 +3742,8 @@ shaka.hls.HlsParser = class {
3160
3742
  shaka.util.Error.Code.OPERATION_ABORTED);
3161
3743
  }
3162
3744
 
3163
- const op = this.playerInterface_.networkingEngine.request(type, request);
3745
+ const op = this.playerInterface_.networkingEngine.request(
3746
+ type, request, context);
3164
3747
  this.operationManager_.manage(op);
3165
3748
 
3166
3749
  return op.promise;
@@ -3314,8 +3897,11 @@ shaka.hls.HlsParser = class {
3314
3897
  * maxTimestamp: number,
3315
3898
  * mediaSequenceToStartTime: !Map.<number, number>,
3316
3899
  * canSkipSegments: boolean,
3900
+ * canBlockReload: boolean,
3317
3901
  * hasEndList: boolean,
3318
3902
  * firstSequenceNumber: number,
3903
+ * nextMediaSequence: number,
3904
+ * nextPart: number,
3319
3905
  * loadedOnce: boolean
3320
3906
  * }}
3321
3907
  *
@@ -3344,10 +3930,18 @@ shaka.hls.HlsParser = class {
3344
3930
  * @property {boolean} canSkipSegments
3345
3931
  * True if the server supports delta playlist updates, and we can send a
3346
3932
  * request for a playlist that can skip older media segments.
3933
+ * @property {boolean} canBlockReload
3934
+ * True if the server supports blocking playlist reload, and we can send a
3935
+ * request for a playlist that can block reload until some segments are
3936
+ * present.
3347
3937
  * @property {boolean} hasEndList
3348
3938
  * True if the stream has an EXT-X-ENDLIST tag.
3349
3939
  * @property {number} firstSequenceNumber
3350
3940
  * The sequence number of the first reference. Only calculated if needed.
3941
+ * @property {number} nextMediaSequence
3942
+ * The next media sequence.
3943
+ * @property {number} nextPart
3944
+ * The next part.
3351
3945
  * @property {boolean} loadedOnce
3352
3946
  * True if the stream has been loaded at least once.
3353
3947
  */
@@ -3367,6 +3961,30 @@ shaka.hls.HlsParser.StreamInfo;
3367
3961
  shaka.hls.HlsParser.StreamInfos;
3368
3962
 
3369
3963
 
3964
+ /**
3965
+ * @typedef {{
3966
+ * type: string,
3967
+ * mimeType: string,
3968
+ * codecs: string,
3969
+ * language: ?string,
3970
+ * height: ?string,
3971
+ * width: ?string,
3972
+ * channelCount: ?number,
3973
+ * sampleRate: ?number
3974
+ * }}
3975
+ *
3976
+ * @property {string} type
3977
+ * @property {string} mimeType
3978
+ * @property {string} codecs
3979
+ * @property {?string} language
3980
+ * @property {?string} height
3981
+ * @property {?string} width
3982
+ * @property {?number} channelCount
3983
+ * @property {?number} sampleRate
3984
+ */
3985
+ shaka.hls.HlsParser.BasicInfo;
3986
+
3987
+
3370
3988
  /**
3371
3989
  * @const {!Object.<string, string>}
3372
3990
  * @private
@@ -3488,13 +4106,9 @@ shaka.hls.HlsParser.PresentationType_ = {
3488
4106
  LIVE: 'LIVE',
3489
4107
  };
3490
4108
 
3491
- if (!shaka.util.Platform.isTizen3() &&
3492
- !shaka.util.Platform.isTizen2() &&
3493
- !shaka.util.Platform.isWebOS3()) {
3494
- shaka.media.ManifestParser.registerParserByExtension(
3495
- 'm3u8', () => new shaka.hls.HlsParser());
3496
- shaka.media.ManifestParser.registerParserByMime(
3497
- 'application/x-mpegurl', () => new shaka.hls.HlsParser());
3498
- shaka.media.ManifestParser.registerParserByMime(
3499
- 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
3500
- }
4109
+ shaka.media.ManifestParser.registerParserByExtension(
4110
+ 'm3u8', () => new shaka.hls.HlsParser());
4111
+ shaka.media.ManifestParser.registerParserByMime(
4112
+ 'application/x-mpegurl', () => new shaka.hls.HlsParser());
4113
+ shaka.media.ManifestParser.registerParserByMime(
4114
+ 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());