shaka-player 4.3.11 → 4.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. package/AUTHORS +9 -0
  2. package/CHANGELOG.md +303 -202
  3. package/CONTRIBUTORS +8 -0
  4. package/LICENSE +32 -0
  5. package/README.md +63 -10
  6. package/demo/autoTemplate.txt +40 -5
  7. package/demo/close_button.js +11 -0
  8. package/demo/common/asset.js +13 -1
  9. package/demo/common/assets.js +53 -3
  10. package/demo/common/message_ids.js +25 -1
  11. package/demo/config.js +69 -10
  12. package/demo/customWarning.txt +2 -2
  13. package/demo/index.html +3 -1
  14. package/demo/locales/en.json +26 -2
  15. package/demo/locales/source.json +105 -9
  16. package/demo/main.js +52 -9
  17. package/demo/search.js +3 -1
  18. package/demo/service_worker.js +3 -0
  19. package/dist/controls.css +1 -1
  20. package/dist/controls.css.map +1 -1
  21. package/dist/demo.compiled.debug.js +124 -112
  22. package/dist/demo.compiled.debug.map +3 -3
  23. package/dist/demo.compiled.js +124 -112
  24. package/dist/demo.compiled.map +3 -3
  25. package/dist/deps.js +46 -28
  26. package/dist/locales.js +33 -33
  27. package/dist/receiver.compiled.debug.js +52 -46
  28. package/dist/receiver.compiled.debug.map +3 -3
  29. package/dist/receiver.compiled.js +52 -46
  30. package/dist/receiver.compiled.map +3 -3
  31. package/dist/shaka-player.compiled.d.ts +676 -391
  32. package/dist/shaka-player.compiled.debug.d.ts +676 -391
  33. package/dist/shaka-player.compiled.debug.externs.js +1188 -616
  34. package/dist/shaka-player.compiled.debug.js +1043 -699
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +1188 -616
  37. package/dist/shaka-player.compiled.js +1091 -929
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +677 -392
  40. package/dist/shaka-player.ui.debug.d.ts +677 -392
  41. package/dist/shaka-player.ui.debug.externs.js +1023 -451
  42. package/dist/shaka-player.ui.debug.js +1098 -737
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +1023 -451
  45. package/dist/shaka-player.ui.js +1246 -1071
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/tutorials/ad_monetization.md +15 -1
  48. package/docs/tutorials/application-level-redirects.md +1 -1
  49. package/docs/tutorials/config.md +9 -5
  50. package/docs/tutorials/drm-config.md +61 -0
  51. package/docs/tutorials/fairplay.md +19 -4
  52. package/docs/tutorials/license-server-auth.md +4 -4
  53. package/docs/tutorials/license-wrapping.md +2 -2
  54. package/docs/tutorials/ui-customization.md +7 -3
  55. package/docs/tutorials/ui.md +7 -7
  56. package/docs/tutorials/upgrade.md +3 -0
  57. package/externs/ima.js +123 -2
  58. package/externs/isoboxer.js +217 -0
  59. package/externs/mediakeys.js +18 -0
  60. package/externs/mux.js +1 -105
  61. package/externs/pictureinpicture.js +48 -0
  62. package/externs/shaka/ads.js +61 -1
  63. package/externs/shaka/cea.js +110 -0
  64. package/externs/shaka/codecs.js +32 -0
  65. package/externs/shaka/manifest.js +101 -1
  66. package/externs/shaka/manifest_parser.js +0 -10
  67. package/externs/shaka/net.js +36 -7
  68. package/externs/shaka/offline.js +8 -1
  69. package/externs/shaka/player.js +220 -21
  70. package/externs/shaka/text.js +1 -382
  71. package/externs/shaka/transmuxer.js +60 -0
  72. package/karma.conf.js +13 -0
  73. package/lib/abr/simple_abr_manager.js +6 -3
  74. package/lib/ads/ad_manager.js +60 -2
  75. package/lib/ads/client_side_ad.js +86 -0
  76. package/lib/ads/client_side_ad_manager.js +79 -3
  77. package/lib/ads/server_side_ad.js +86 -0
  78. package/lib/ads/server_side_ad_manager.js +13 -0
  79. package/lib/cast/cast_utils.js +1 -0
  80. package/lib/cea/cea608_data_channel.js +24 -14
  81. package/lib/cea/cea608_memory.js +5 -2
  82. package/lib/cea/cea708_service.js +8 -9
  83. package/lib/cea/cea708_window.js +1 -2
  84. package/lib/cea/cea_decoder.js +23 -6
  85. package/lib/cea/cea_utils.js +25 -2
  86. package/lib/cea/dummy_caption_decoder.js +21 -0
  87. package/lib/cea/dummy_cea_parser.js +1 -3
  88. package/lib/cea/mp4_cea_parser.js +15 -10
  89. package/lib/cea/ts_cea_parser.js +69 -0
  90. package/lib/dash/content_protection.js +32 -2
  91. package/lib/dash/dash_parser.js +93 -31
  92. package/lib/dash/mpd_utils.js +6 -25
  93. package/lib/dash/segment_base.js +12 -14
  94. package/lib/dash/segment_list.js +10 -6
  95. package/lib/dash/segment_template.js +349 -32
  96. package/lib/dependencies/all.js +8 -0
  97. package/lib/hls/hls_classes.js +33 -8
  98. package/lib/hls/hls_parser.js +805 -191
  99. package/lib/hls/manifest_text_parser.js +4 -14
  100. package/lib/media/adaptation_set_criteria.js +43 -3
  101. package/lib/media/closed_caption_parser.js +74 -12
  102. package/lib/media/drm_engine.js +189 -49
  103. package/lib/media/gap_jumping_controller.js +1 -1
  104. package/lib/media/manifest_parser.js +34 -0
  105. package/lib/media/media_source_capabilities.js +10 -0
  106. package/lib/media/media_source_engine.js +495 -231
  107. package/lib/media/playhead.js +1 -1
  108. package/lib/media/presentation_timeline.js +62 -0
  109. package/lib/media/segment_index.js +50 -14
  110. package/lib/media/segment_prefetch.js +277 -0
  111. package/lib/media/segment_reference.js +70 -4
  112. package/lib/media/streaming_engine.js +258 -81
  113. package/lib/media/video_wrapper.js +15 -7
  114. package/lib/mss/content_protection.js +353 -0
  115. package/lib/mss/mss_parser.js +1082 -0
  116. package/lib/net/networking_engine.js +60 -10
  117. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  118. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  119. package/lib/offline/manifest_converter.js +6 -0
  120. package/lib/offline/storage.js +2 -4
  121. package/lib/player.js +309 -105
  122. package/lib/polyfill/media_capabilities.js +43 -3
  123. package/lib/polyfill/patchedmediakeys_apple.js +5 -0
  124. package/lib/polyfill/patchedmediakeys_nop.js +5 -0
  125. package/lib/polyfill/patchedmediakeys_webkit.js +5 -0
  126. package/lib/text/cue.js +208 -130
  127. package/lib/text/lrc_text_parser.js +2 -2
  128. package/lib/text/mp4_vtt_parser.js +2 -2
  129. package/lib/text/sbv_text_parser.js +1 -1
  130. package/lib/text/simple_text_displayer.js +1 -1
  131. package/lib/text/srt_text_parser.js +9 -1
  132. package/lib/text/ssa_text_parser.js +2 -2
  133. package/lib/text/text_engine.js +3 -21
  134. package/lib/text/text_utils.js +71 -20
  135. package/lib/text/ui_text_displayer.js +14 -14
  136. package/lib/text/vtt_text_parser.js +22 -14
  137. package/lib/transmuxer/aac_transmuxer.js +224 -0
  138. package/lib/transmuxer/ac3.js +142 -0
  139. package/lib/transmuxer/ac3_transmuxer.js +213 -0
  140. package/lib/transmuxer/adts.js +232 -0
  141. package/lib/transmuxer/ec3.js +103 -0
  142. package/lib/transmuxer/ec3_transmuxer.js +213 -0
  143. package/lib/transmuxer/h264.js +324 -0
  144. package/lib/transmuxer/mp3_transmuxer.js +205 -0
  145. package/lib/transmuxer/mpeg_audio.js +196 -0
  146. package/lib/transmuxer/mpeg_ts_transmuxer.js +182 -0
  147. package/lib/transmuxer/mss_transmuxer.js +389 -0
  148. package/lib/{media/transmuxer.js → transmuxer/muxjs_transmuxer.js} +34 -80
  149. package/lib/transmuxer/transmuxer_engine.js +161 -0
  150. package/lib/transmuxer/ts_transmuxer.js +721 -0
  151. package/lib/util/cmcd_manager.js +131 -71
  152. package/lib/util/error.js +62 -26
  153. package/lib/util/exp_golomb.js +220 -0
  154. package/lib/util/fairplay_utils.js +64 -13
  155. package/lib/util/id3_utils.js +56 -3
  156. package/lib/util/language_utils.js +11 -8
  157. package/lib/util/manifest_parser_utils.js +3 -0
  158. package/lib/util/mime_utils.js +44 -7
  159. package/lib/util/mp4_box_parsers.js +233 -5
  160. package/lib/util/mp4_generator.js +1388 -0
  161. package/lib/util/periods.js +40 -34
  162. package/lib/util/platform.js +17 -3
  163. package/lib/util/player_configuration.js +38 -1
  164. package/lib/util/stream_utils.js +138 -27
  165. package/lib/util/text_parser.js +1 -1
  166. package/lib/util/ts_parser.js +460 -74
  167. package/lib/util/xml_utils.js +16 -3
  168. package/maintained-branches.md +2 -2
  169. package/package.json +3 -1
  170. package/roadmap.md +21 -6
  171. package/shaka-player.uncompiled.js +18 -0
  172. package/test/abr/simple_abr_manager_unit.js +32 -4
  173. package/test/ads/ad_manager_unit.js +18 -2
  174. package/test/cast/cast_utils_unit.js +7 -2
  175. package/test/cea/cea608_memory_unit.js +24 -0
  176. package/test/cea/cea_decoder_unit.js +33 -0
  177. package/test/dash/dash_parser_content_protection_unit.js +93 -3
  178. package/test/dash/dash_parser_live_unit.js +56 -13
  179. package/test/dash/dash_parser_manifest_unit.js +70 -2
  180. package/test/dash/dash_parser_segment_base_unit.js +14 -9
  181. package/test/dash/dash_parser_segment_template_unit.js +286 -6
  182. package/test/demo/demo_unit.js +5 -1
  183. package/test/hls/hls_live_unit.js +188 -28
  184. package/test/hls/hls_parser_unit.js +250 -109
  185. package/test/hls/manifest_text_parser_unit.js +18 -13
  186. package/test/media/adaptation_set_criteria_unit.js +46 -14
  187. package/test/media/adaptation_set_unit.js +2 -0
  188. package/test/media/drm_engine_integration.js +11 -8
  189. package/test/media/drm_engine_unit.js +220 -7
  190. package/test/media/media_source_engine_integration.js +124 -40
  191. package/test/media/media_source_engine_unit.js +133 -153
  192. package/test/media/playhead_unit.js +3 -0
  193. package/test/media/presentation_timeline_unit.js +51 -0
  194. package/test/media/segment_index_unit.js +52 -0
  195. package/test/media/segment_prefetch_unit.js +243 -0
  196. package/test/media/streaming_engine_integration.js +9 -3
  197. package/test/media/streaming_engine_unit.js +440 -59
  198. package/test/mss/mss_parser_content_protection_unit.js +49 -0
  199. package/test/mss/mss_parser_unit.js +472 -0
  200. package/test/mss/mss_player_integration.js +123 -0
  201. package/test/offline/manifest_convert_unit.js +9 -0
  202. package/test/offline/storage_integration.js +6 -1
  203. package/test/player_unit.js +386 -133
  204. package/test/polyfill/media_capabilities_unit.js +18 -0
  205. package/test/test/assets/hls-raw-aac/fileSequence0.aac +0 -0
  206. package/test/test/assets/hls-raw-aac/fileSequence1.aac +0 -0
  207. package/test/test/assets/hls-raw-aac/fileSequence2.aac +0 -0
  208. package/test/test/assets/hls-raw-aac/fileSequence3.aac +0 -0
  209. package/test/test/assets/hls-raw-aac/manifest.m3u8 +4 -0
  210. package/test/test/assets/hls-raw-aac/stream.m4a.m3u8 +14 -0
  211. package/test/test/assets/hls-raw-ac3/fileSequence0.ac3 +0 -0
  212. package/test/test/assets/hls-raw-ac3/fileSequence1.ac3 +0 -0
  213. package/test/test/assets/hls-raw-ac3/fileSequence2.ac3 +0 -0
  214. package/test/test/assets/hls-raw-ac3/fileSequence3.ac3 +0 -0
  215. package/test/test/assets/hls-raw-ac3/fileSequence4.ac3 +0 -0
  216. package/test/test/assets/hls-raw-ac3/fileSequence5.ac3 +0 -0
  217. package/test/test/assets/hls-raw-ac3/fileSequence6.ac3 +0 -0
  218. package/test/test/assets/hls-raw-ac3/fileSequence7.ac3 +0 -0
  219. package/test/test/assets/hls-raw-ac3/prog_index.m3u8 +31 -0
  220. package/test/test/assets/hls-raw-ec3/fileSequence0.ec3 +0 -0
  221. package/test/test/assets/hls-raw-ec3/fileSequence1.ec3 +0 -0
  222. package/test/test/assets/hls-raw-ec3/fileSequence2.ec3 +0 -0
  223. package/test/test/assets/hls-raw-ec3/fileSequence3.ec3 +0 -0
  224. package/test/test/assets/hls-raw-ec3/fileSequence4.ec3 +0 -0
  225. package/test/test/assets/hls-raw-ec3/fileSequence5.ec3 +0 -0
  226. package/test/test/assets/hls-raw-ec3/fileSequence6.ec3 +0 -0
  227. package/test/test/assets/hls-raw-ec3/fileSequence7.ec3 +0 -0
  228. package/test/test/assets/hls-raw-ec3/prog_index.m3u8 +31 -0
  229. package/test/test/assets/hls-raw-mp3/chunklist_w905519204.m3u8 +13 -0
  230. package/test/test/assets/hls-raw-mp3/media_w905519204_0.mp3 +0 -0
  231. package/test/test/assets/hls-raw-mp3/media_w905519204_1.mp3 +0 -0
  232. package/test/test/assets/hls-raw-mp3/media_w905519204_2.mp3 +0 -0
  233. package/test/test/assets/hls-raw-mp3/media_w905519204_3.mp3 +0 -0
  234. package/test/test/assets/hls-raw-mp3/playlist.m3u8 +4 -0
  235. package/test/test/assets/hls-ts-aac/playlist.m3u8 +23 -0
  236. package/test/test/assets/hls-ts-aac/segment_0.ts +0 -0
  237. package/test/test/assets/hls-ts-aac/segment_1.ts +0 -0
  238. package/test/test/assets/hls-ts-aac/segment_2.ts +0 -0
  239. package/test/test/assets/hls-ts-aac/segment_3.ts +0 -0
  240. package/test/test/assets/hls-ts-aac/segment_4.ts +0 -0
  241. package/test/test/assets/hls-ts-aac/segment_5.ts +0 -0
  242. package/test/test/assets/hls-ts-aac/segment_6.ts +0 -0
  243. package/test/test/assets/hls-ts-aac/segment_7.ts +0 -0
  244. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0000.ts +0 -0
  245. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0001.ts +0 -0
  246. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0002.ts +0 -0
  247. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0003.ts +0 -0
  248. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0004.ts +0 -0
  249. package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0005.ts +0 -0
  250. package/test/test/assets/hls-ts-ac3/prog_index.m3u8 +19 -0
  251. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0000.ts +0 -0
  252. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0001.ts +0 -0
  253. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0002.ts +0 -0
  254. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0003.ts +0 -0
  255. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0004.ts +0 -0
  256. package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0005.ts +0 -0
  257. package/test/test/assets/hls-ts-ec3/prog_index.m3u8 +19 -0
  258. package/test/test/assets/hls-ts-h264/fileSequence0.ts +0 -0
  259. package/test/test/assets/hls-ts-h264/fileSequence1.ts +0 -0
  260. package/test/test/assets/hls-ts-h264/fileSequence2.ts +0 -0
  261. package/test/test/assets/hls-ts-h264/fileSequence3.ts +0 -0
  262. package/test/test/assets/hls-ts-h264/fileSequence4.ts +0 -0
  263. package/test/test/assets/hls-ts-h264/fileSequence5.ts +0 -0
  264. package/test/test/assets/hls-ts-h264/prog_index.m3u8 +25 -0
  265. package/test/test/assets/hls-ts-mp3/manifest.m3u8 +13 -0
  266. package/test/test/assets/hls-ts-mp3/media_w239891843_0.ts +0 -0
  267. package/test/test/assets/hls-ts-mp3/media_w239891843_1.ts +0 -0
  268. package/test/test/assets/hls-ts-mp3/media_w239891843_2.ts +0 -0
  269. package/test/test/assets/hls-ts-mp3/media_w239891843_3.ts +0 -0
  270. package/test/test/assets/hls-ts-muxed-aac-h264/chunk.m3u8 +17 -0
  271. package/test/test/assets/hls-ts-muxed-aac-h264/n_0_0_0.ts +0 -0
  272. package/test/test/assets/hls-ts-muxed-aac-h264/n_1_0_0.ts +0 -0
  273. package/test/test/assets/hls-ts-muxed-aac-h264/n_2_0_0.ts +0 -0
  274. package/test/test/assets/hls-ts-muxed-aac-h264/n_3_0_0.ts +0 -0
  275. package/test/test/assets/hls-ts-muxed-aac-h264/n_4_0_0.ts +0 -0
  276. package/test/test/assets/hls-ts-muxed-aac-h264/n_5_0_0.ts +0 -0
  277. package/test/test/assets/hls-ts-muxed-aac-h264/playlist.m3u8 +4 -0
  278. package/test/test/boot.js +7 -2
  279. package/test/test/util/fake_ad.js +99 -0
  280. package/test/test/util/fake_ad_manager.js +7 -1
  281. package/test/test/util/fake_networking_engine.js +41 -14
  282. package/test/test/util/fake_segment_prefetch.js +81 -0
  283. package/test/test/util/manifest_generator.js +19 -5
  284. package/test/test/util/mss_parser_util.js +115 -0
  285. package/test/test/util/offline_utils.js +1 -0
  286. package/test/test/util/simple_fakes.js +12 -3
  287. package/test/test/util/streaming_engine_util.js +14 -4
  288. package/test/test/util/ttml_utils.js +2 -2
  289. package/test/text/cue_integration.js +1 -1
  290. package/test/text/srt_text_parser_unit.js +78 -0
  291. package/test/text/text_engine_unit.js +17 -8
  292. package/test/text/ui_text_displayer_unit.js +4 -4
  293. package/test/text/vtt_text_parser_unit.js +59 -40
  294. package/test/text/web_vtt_generator_unit.js +9 -3
  295. package/test/{media/transmuxer_integration.js → transmuxer/muxjs_transmuxer_integration.js} +30 -33
  296. package/test/transmuxer/transmuxer_engine_integration.js +60 -0
  297. package/test/transmuxer/transmuxer_integration.js +296 -0
  298. package/test/util/cmcd_manager_unit.js +162 -13
  299. package/test/util/language_utils_unit.js +9 -2
  300. package/test/util/mp4_box_parsers_unit.js +39 -4
  301. package/test/util/periods_unit.js +34 -26
  302. package/test/util/stream_utils_unit.js +60 -107
  303. package/ui/controls.js +19 -1
  304. package/ui/controls.less +1 -0
  305. package/ui/externs/ui.js +11 -1
  306. package/ui/language_utils.js +15 -0
  307. package/ui/less/thumbnails.less +26 -0
  308. package/ui/locales/oc.json +42 -0
  309. package/ui/pip_button.js +83 -2
  310. package/ui/range_element.js +15 -0
  311. package/ui/resolution_selection.js +9 -2
  312. package/ui/seek_bar.js +223 -0
  313. package/ui/ui.js +5 -0
  314. package/lib/cea/i_caption_decoder.js +0 -51
  315. 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
@@ -1210,6 +1521,16 @@ shaka.hls.HlsParser = class {
1210
1521
  /** @type {!Array.<string>} */
1211
1522
  const codecs = codecsString.split(/\s*,\s*/);
1212
1523
 
1524
+ return this.filterDuplicateCodecs_(codecs);
1525
+ }
1526
+
1527
+
1528
+ /**
1529
+ * @param {!Array.<string>} codecs
1530
+ * @return {!Array.<string>} codecs
1531
+ * @private
1532
+ */
1533
+ filterDuplicateCodecs_(codecs) {
1213
1534
  // Filter out duplicate codecs.
1214
1535
  const seen = new Set();
1215
1536
  const ret = [];
@@ -1251,6 +1572,21 @@ shaka.hls.HlsParser = class {
1251
1572
  return count;
1252
1573
  }
1253
1574
 
1575
+ /**
1576
+ * Get the sample rate information for an HLS audio track.
1577
+ *
1578
+ * @param {!shaka.hls.Tag} tag
1579
+ * @return {?number}
1580
+ * @private
1581
+ */
1582
+ getSampleRate_(tag) {
1583
+ const sampleRate = tag.getAttributeValue('SAMPLE-RATE');
1584
+ if (!sampleRate) {
1585
+ return null;
1586
+ }
1587
+ return parseInt(sampleRate, 10);
1588
+ }
1589
+
1254
1590
  /**
1255
1591
  * Get the spatial audio information for an HLS audio track.
1256
1592
  * In HLS the channels field indicates the number of audio channels that the
@@ -1299,16 +1635,15 @@ shaka.hls.HlsParser = class {
1299
1635
  }
1300
1636
 
1301
1637
  /**
1302
- * Get the language value.
1638
+ * Get the normalized language value.
1303
1639
  *
1304
- * @param {!shaka.hls.Tag} tag
1640
+ * @param {?string} languageValue
1305
1641
  * @return {string}
1306
1642
  * @private
1307
1643
  */
1308
- getLanguage_(tag) {
1644
+ getLanguage_(languageValue) {
1309
1645
  const LanguageUtils = shaka.util.LanguageUtils;
1310
- const languageValue = tag.getAttributeValue('LANGUAGE') || 'und';
1311
- return LanguageUtils.normalize(languageValue);
1646
+ return LanguageUtils.normalize(languageValue || 'und');
1312
1647
  }
1313
1648
 
1314
1649
  /**
@@ -1410,6 +1745,7 @@ shaka.hls.HlsParser = class {
1410
1745
  const variant = {
1411
1746
  id: this.globalId_++,
1412
1747
  language: audioStream ? audioStream.language : 'und',
1748
+ disabledUntilTime: 0,
1413
1749
  primary: (!!audioStream && audioStream.primary) ||
1414
1750
  (!!videoStream && videoStream.primary),
1415
1751
  audio: audioStream,
@@ -1440,7 +1776,8 @@ shaka.hls.HlsParser = class {
1440
1776
  for (const tag of closedCaptionsTags) {
1441
1777
  goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
1442
1778
  'Should only be called on media tags!');
1443
- const language = this.getLanguage_(tag);
1779
+ const languageValue = tag.getAttributeValue('LANGUAGE');
1780
+ const language = this.getLanguage_(languageValue);
1444
1781
 
1445
1782
  // The GROUP-ID value is a quoted-string that specifies the group to which
1446
1783
  // the Rendition belongs.
@@ -1486,7 +1823,7 @@ shaka.hls.HlsParser = class {
1486
1823
  return this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
1487
1824
  }
1488
1825
 
1489
- const language = this.getLanguage_(tag);
1826
+ const language = tag.getAttributeValue('LANGUAGE');
1490
1827
  const name = tag.getAttributeValue('NAME');
1491
1828
 
1492
1829
  // NOTE: According to the HLS spec, "DEFAULT=YES" requires "AUTOSELECT=YES".
@@ -1504,12 +1841,13 @@ shaka.hls.HlsParser = class {
1504
1841
 
1505
1842
  const forcedAttrValue = tag.getAttributeValue('FORCED');
1506
1843
  const forced = forcedAttrValue == 'YES';
1844
+ const sampleRate = type == 'audio' ? this.getSampleRate_(tag) : null;
1507
1845
  // TODO: Should we take into account some of the currently ignored
1508
1846
  // attributes: INSTREAM-ID, Attribute descriptions: https://bit.ly/2lpjOhj
1509
1847
  const streamInfo = this.createStreamInfo_(
1510
1848
  verbatimMediaPlaylistUri, codecs, type, language, primary, name,
1511
1849
  channelsCount, /* closedCaptions= */ null, characteristics, forced,
1512
- spatialAudio);
1850
+ sampleRate, spatialAudio);
1513
1851
  if (this.groupIdToStreamInfosMap_.has(groupId)) {
1514
1852
  this.groupIdToStreamInfosMap_.get(groupId).push(streamInfo);
1515
1853
  } else {
@@ -1548,7 +1886,7 @@ shaka.hls.HlsParser = class {
1548
1886
  return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
1549
1887
  }
1550
1888
 
1551
- const language = this.getLanguage_(tag);
1889
+ const language = tag.getAttributeValue('LANGUAGE');
1552
1890
  const name = tag.getAttributeValue('NAME');
1553
1891
 
1554
1892
  const characteristics = tag.getAttributeValue('CHARACTERISTICS');
@@ -1556,7 +1894,8 @@ shaka.hls.HlsParser = class {
1556
1894
  const streamInfo = this.createStreamInfo_(
1557
1895
  verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
1558
1896
  name, /* channelsCount= */ null, /* closedCaptions= */ null,
1559
- characteristics, /* forced= */ false, /* spatialAudio= */ false);
1897
+ characteristics, /* forced= */ false, /* sampleRate= */ null,
1898
+ /* spatialAudio= */ false);
1560
1899
 
1561
1900
  // TODO: This check is necessary because of the possibility of multiple
1562
1901
  // calls to createStreamInfoFromImageTag_ before either has resolved.
@@ -1617,10 +1956,10 @@ shaka.hls.HlsParser = class {
1617
1956
  const closedCaptions = this.getClosedCaptions_(tag, type);
1618
1957
  const codecs = shaka.util.ManifestParserUtils.guessCodecs(type, allCodecs);
1619
1958
  const streamInfo = this.createStreamInfo_(verbatimMediaPlaylistUri,
1620
- codecs, type, /* language= */ 'und', /* primary= */ false,
1959
+ codecs, type, /* language= */ null, /* primary= */ false,
1621
1960
  /* name= */ null, /* channelcount= */ null, closedCaptions,
1622
1961
  /* characteristics= */ null, /* forced= */ false,
1623
- /* spatialAudio= */ false);
1962
+ /* sampleRate= */ null, /* spatialAudio= */ false);
1624
1963
  // TODO: This check is necessary because of the possibility of multiple
1625
1964
  // calls to createStreamInfoFromVariantTag_ before either has resolved.
1626
1965
  if (this.uriToStreamInfosMap_.has(verbatimMediaPlaylistUri)) {
@@ -1636,20 +1975,21 @@ shaka.hls.HlsParser = class {
1636
1975
  * @param {string} verbatimMediaPlaylistUri
1637
1976
  * @param {string} codecs
1638
1977
  * @param {string} type
1639
- * @param {string} language
1978
+ * @param {?string} languageValue
1640
1979
  * @param {boolean} primary
1641
1980
  * @param {?string} name
1642
1981
  * @param {?number} channelsCount
1643
1982
  * @param {Map.<string, string>} closedCaptions
1644
1983
  * @param {?string} characteristics
1645
1984
  * @param {boolean} forced
1985
+ * @param {?number} sampleRate
1646
1986
  * @param {boolean} spatialAudio
1647
1987
  * @return {!shaka.hls.HlsParser.StreamInfo}
1648
1988
  * @private
1649
1989
  */
1650
- createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
1990
+ createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, languageValue,
1651
1991
  primary, name, channelsCount, closedCaptions, characteristics, forced,
1652
- spatialAudio) {
1992
+ sampleRate, spatialAudio) {
1653
1993
  // TODO: Refactor, too many parameters
1654
1994
  const initialMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
1655
1995
  this.masterPlaylistUri_, verbatimMediaPlaylistUri);
@@ -1657,11 +1997,9 @@ shaka.hls.HlsParser = class {
1657
1997
  // This stream is lazy-loaded inside the createSegmentIndex function.
1658
1998
  // So we start out with a stream object that does not contain the actual
1659
1999
  // segment index, then download when createSegmentIndex is called.
1660
- const stream = this.makeStreamObject_(codecs, type, language, primary, name,
1661
- channelsCount, closedCaptions, characteristics, forced, spatialAudio);
1662
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1663
- stream.codecs = '';
1664
- }
2000
+ const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
2001
+ name, channelsCount, closedCaptions, characteristics, forced,
2002
+ sampleRate, spatialAudio);
1665
2003
  const streamInfo = {
1666
2004
  stream,
1667
2005
  type,
@@ -1672,8 +2010,11 @@ shaka.hls.HlsParser = class {
1672
2010
  maxTimestamp: 0,
1673
2011
  mediaSequenceToStartTime: new Map(),
1674
2012
  canSkipSegments: false,
2013
+ canBlockReload: false,
1675
2014
  hasEndList: false,
1676
2015
  firstSequenceNumber: -1,
2016
+ nextMediaSequence: -1,
2017
+ nextPart: -1,
1677
2018
  loadedOnce: false,
1678
2019
  };
1679
2020
 
@@ -1681,7 +2022,7 @@ shaka.hls.HlsParser = class {
1681
2022
  const downloadSegmentIndex = async (abortSignal) => {
1682
2023
  // Download the actual manifest.
1683
2024
  const response = await this.requestManifest_(
1684
- streamInfo.absoluteMediaPlaylistUri);
2025
+ streamInfo.absoluteMediaPlaylistUri, /* isPlaylist= */ true);
1685
2026
  if (abortSignal.aborted) {
1686
2027
  return;
1687
2028
  }
@@ -1698,8 +2039,8 @@ shaka.hls.HlsParser = class {
1698
2039
  const wasLive = this.isLive_();
1699
2040
  const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
1700
2041
  playlist, verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs,
1701
- type, language, primary, name, channelsCount, closedCaptions,
1702
- characteristics, forced, spatialAudio);
2042
+ type, languageValue, primary, name, channelsCount, closedCaptions,
2043
+ characteristics, forced, sampleRate, spatialAudio);
1703
2044
  if (abortSignal.aborted) {
1704
2045
  return;
1705
2046
  }
@@ -1717,18 +2058,19 @@ shaka.hls.HlsParser = class {
1717
2058
  streamInfo.minTimestamp = realStreamInfo.minTimestamp;
1718
2059
  streamInfo.maxTimestamp = realStreamInfo.maxTimestamp;
1719
2060
  streamInfo.canSkipSegments = realStreamInfo.canSkipSegments;
2061
+ streamInfo.canBlockReload = realStreamInfo.canBlockReload;
1720
2062
  streamInfo.hasEndList = realStreamInfo.hasEndList;
1721
2063
  streamInfo.mediaSequenceToStartTime =
1722
2064
  realStreamInfo.mediaSequenceToStartTime;
2065
+ streamInfo.nextMediaSequence = realStreamInfo.nextMediaSequence;
2066
+ streamInfo.nextPart = realStreamInfo.nextPart;
1723
2067
  streamInfo.loadedOnce = true;
1724
2068
  stream.segmentIndex = realStream.segmentIndex;
1725
2069
  stream.encrypted = realStream.encrypted;
1726
2070
  stream.drmInfos = realStream.drmInfos;
1727
2071
  stream.keyIds = realStream.keyIds;
1728
2072
  stream.mimeType = realStream.mimeType;
1729
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1730
- stream.codecs = '';
1731
- }
2073
+ stream.bandwidth = realStream.bandwidth;
1732
2074
 
1733
2075
  // Since we lazy-loaded this content, the player may need to create new
1734
2076
  // sessions for the DRM info in this stream.
@@ -1743,10 +2085,6 @@ shaka.hls.HlsParser = class {
1743
2085
  // To aid manifest filtering, assume before loading that all video
1744
2086
  // renditions have the same MIME type. (And likewise for audio.)
1745
2087
  otherStreamInfo.stream.mimeType = realStream.mimeType;
1746
- if (shaka.media.MediaSourceEngine.RAW_FORMATS
1747
- .includes(otherStreamInfo.stream.mimeType)) {
1748
- otherStreamInfo.stream.codecs = '';
1749
- }
1750
2088
  }
1751
2089
  }
1752
2090
  }
@@ -1776,7 +2114,7 @@ shaka.hls.HlsParser = class {
1776
2114
  this.determineDuration_();
1777
2115
  // Finally, start the update timer, if this asset has been determined
1778
2116
  // to be a livestream.
1779
- const delay = this.updatePlaylistDelay_;
2117
+ const delay = this.getUpdatePlaylistDelay_();
1780
2118
  if (delay > 0) {
1781
2119
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
1782
2120
  }
@@ -1893,13 +2231,6 @@ shaka.hls.HlsParser = class {
1893
2231
  streamInfo.stream.segmentIndex.fit(/* periodStart= */ 0, minDuration);
1894
2232
  }
1895
2233
  }
1896
- // MediaSource expects no codec strings combined with raw formats.
1897
- for (const streamInfo of streamInfos) {
1898
- const stream = streamInfo.stream;
1899
- if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
1900
- stream.codecs = '';
1901
- }
1902
- }
1903
2234
  this.notifySegmentsForStreams_(streamInfos.map((s) => s.stream));
1904
2235
  if (this.config_.hls.ignoreManifestProgramDateTime) {
1905
2236
  this.syncStreamsWithSequenceNumber_(streamInfos);
@@ -1946,22 +2277,23 @@ shaka.hls.HlsParser = class {
1946
2277
  * @param {string} absoluteMediaPlaylistUri
1947
2278
  * @param {string} codecs
1948
2279
  * @param {string} type
1949
- * @param {string} language
2280
+ * @param {?string} languageValue
1950
2281
  * @param {boolean} primary
1951
2282
  * @param {?string} name
1952
2283
  * @param {?number} channelsCount
1953
2284
  * @param {Map.<string, string>} closedCaptions
1954
2285
  * @param {?string} characteristics
1955
2286
  * @param {boolean} forced
2287
+ * @param {?number} sampleRate
1956
2288
  * @param {boolean} spatialAudio
1957
2289
  * @param {(string|undefined)} mimeType
1958
2290
  * @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
1959
2291
  * @private
1960
2292
  */
1961
2293
  async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
1962
- absoluteMediaPlaylistUri, codecs, type, language, primary, name,
1963
- channelsCount, closedCaptions, characteristics, forced, spatialAudio,
1964
- mimeType = undefined) {
2294
+ absoluteMediaPlaylistUri, codecs, type, languageValue, primary, name,
2295
+ channelsCount, closedCaptions, characteristics, forced, sampleRate,
2296
+ spatialAudio, mimeType = undefined) {
1965
2297
  if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
1966
2298
  // EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
1967
2299
  // playlists.
@@ -1982,6 +2314,10 @@ shaka.hls.HlsParser = class {
1982
2314
 
1983
2315
  this.determinePresentationType_(playlist);
1984
2316
 
2317
+ if (this.isLive_()) {
2318
+ this.determineLastTargetDuration_(playlist);
2319
+ }
2320
+
1985
2321
  if (!mimeType) {
1986
2322
  mimeType = await this.guessMimeType_(type, codecs, playlist,
1987
2323
  mediaVariables);
@@ -1999,18 +2335,18 @@ shaka.hls.HlsParser = class {
1999
2335
 
2000
2336
  const mediaSequenceToStartTime = this.isLive_() ?
2001
2337
  this.mediaSequenceToStartTimeByType_.get(type) : new Map();
2002
- const prevLowLatencyMode = this.lowLatencyMode_;
2003
- const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
2004
- type, mimeType, mediaSequenceToStartTime, mediaVariables);
2338
+ const {segments, bandwidth} = this.createSegments_(
2339
+ verbatimMediaPlaylistUri, playlist, type, mimeType,
2340
+ mediaSequenceToStartTime, mediaVariables);
2005
2341
 
2006
- // This happens when autoLowLatencyMode is true, so we need set the
2007
- // correct lowLatencyPresentationDelay_
2008
- if (prevLowLatencyMode != this.lowLatencyMode_) {
2009
- this.determinePresentationType_(playlist);
2342
+ // This new calculation is necessary for Low Latency streams.
2343
+ if (this.isLive_()) {
2344
+ this.determineLastTargetDuration_(playlist);
2010
2345
  }
2011
2346
 
2012
2347
  const firstStartTime = segments[0].startTime;
2013
- const lastEndTime = segments[segments.length - 1].endTime;
2348
+ const lastSegment = segments[segments.length - 1];
2349
+ const lastEndTime = lastSegment.endTime;
2014
2350
  /** @type {!shaka.media.SegmentIndex} */
2015
2351
  const segmentIndex = new shaka.media.SegmentIndex(segments);
2016
2352
 
@@ -2018,14 +2354,24 @@ shaka.hls.HlsParser = class {
2018
2354
  playlist.tags, 'EXT-X-SERVER-CONTROL');
2019
2355
  const canSkipSegments = serverControlTag ?
2020
2356
  serverControlTag.getAttribute('CAN-SKIP-UNTIL') != null : false;
2357
+ const canBlockReload = serverControlTag ?
2358
+ serverControlTag.getAttribute('CAN-BLOCK-RELOAD') != null : false;
2021
2359
 
2022
- const stream = this.makeStreamObject_(codecs, type, language, primary, name,
2023
- channelsCount, closedCaptions, characteristics, forced, spatialAudio);
2360
+ const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
2361
+ playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
2362
+
2363
+ const {nextMediaSequence, nextPart} =
2364
+ this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);
2365
+
2366
+ const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
2367
+ name, channelsCount, closedCaptions, characteristics, forced,
2368
+ sampleRate, spatialAudio);
2024
2369
  stream.segmentIndex = segmentIndex;
2025
2370
  stream.encrypted = encrypted;
2026
2371
  stream.drmInfos = drmInfos;
2027
2372
  stream.keyIds = keyIds;
2028
2373
  stream.mimeType = mimeType;
2374
+ stream.bandwidth = bandwidth;
2029
2375
 
2030
2376
  return {
2031
2377
  stream,
@@ -2035,13 +2381,60 @@ shaka.hls.HlsParser = class {
2035
2381
  minTimestamp: firstStartTime,
2036
2382
  maxTimestamp: lastEndTime,
2037
2383
  canSkipSegments,
2384
+ canBlockReload,
2038
2385
  hasEndList: false,
2039
2386
  firstSequenceNumber: -1,
2387
+ nextMediaSequence,
2388
+ nextPart,
2040
2389
  mediaSequenceToStartTime,
2041
2390
  loadedOnce: false,
2042
2391
  };
2043
2392
  }
2044
2393
 
2394
+ /**
2395
+ * Get the next msn and part
2396
+ *
2397
+ * @param {number} mediaSequenceNumber
2398
+ * @param {!Array.<!shaka.media.SegmentReference>} segments
2399
+ * @return {{nextMediaSequence: number, nextPart:number}}}
2400
+ * @private
2401
+ */
2402
+ getNextMediaSequenceAndPart_(mediaSequenceNumber, segments) {
2403
+ const currentMediaSequence = mediaSequenceNumber + segments.length - 1;
2404
+ let nextMediaSequence = currentMediaSequence;
2405
+ let nextPart = -1;
2406
+ if (!segments.length) {
2407
+ nextMediaSequence++;
2408
+ return {
2409
+ nextMediaSequence,
2410
+ nextPart,
2411
+ };
2412
+ }
2413
+ const lastSegment = segments[segments.length - 1];
2414
+ const partialReferences = lastSegment.partialReferences;
2415
+ if (!lastSegment.partialReferences.length) {
2416
+ nextMediaSequence++;
2417
+ if (lastSegment.hasByterangeOptimization()) {
2418
+ nextPart = 0;
2419
+ }
2420
+ return {
2421
+ nextMediaSequence,
2422
+ nextPart,
2423
+ };
2424
+ }
2425
+ nextPart = partialReferences.length - 1;
2426
+ const lastPartialReference =
2427
+ partialReferences[partialReferences.length - 1];
2428
+ if (!lastPartialReference.isPreload()) {
2429
+ nextMediaSequence++;
2430
+ nextPart = 0;
2431
+ }
2432
+ return {
2433
+ nextMediaSequence,
2434
+ nextPart,
2435
+ };
2436
+ }
2437
+
2045
2438
 
2046
2439
  /**
2047
2440
  * Creates a stream object with the given parameters.
@@ -2050,19 +2443,20 @@ shaka.hls.HlsParser = class {
2050
2443
  * manually on the object after creation.
2051
2444
  * @param {string} codecs
2052
2445
  * @param {string} type
2053
- * @param {string} language
2446
+ * @param {?string} languageValue
2054
2447
  * @param {boolean} primary
2055
2448
  * @param {?string} name
2056
2449
  * @param {?number} channelsCount
2057
2450
  * @param {Map.<string, string>} closedCaptions
2058
2451
  * @param {?string} characteristics
2059
2452
  * @param {boolean} forced
2453
+ * @param {?number} sampleRate
2060
2454
  * @param {boolean} spatialAudio
2061
2455
  * @return {!shaka.extern.Stream}
2062
2456
  * @private
2063
2457
  */
2064
- makeStreamObject_(codecs, type, language, primary, name, channelsCount,
2065
- closedCaptions, characteristics, forced, spatialAudio) {
2458
+ makeStreamObject_(codecs, type, languageValue, primary, name, channelsCount,
2459
+ closedCaptions, characteristics, forced, sampleRate, spatialAudio) {
2066
2460
  // Fill out a "best-guess" mimeType, for now. It will be replaced once the
2067
2461
  // stream is lazy-loaded.
2068
2462
  const mimeType = this.guessMimeTypeBeforeLoading_(type, codecs) ||
@@ -2091,11 +2485,13 @@ shaka.hls.HlsParser = class {
2091
2485
  segmentIndex: null,
2092
2486
  mimeType,
2093
2487
  codecs,
2094
- kind,
2488
+ kind: (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
2489
+ shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined,
2095
2490
  encrypted: false,
2096
2491
  drmInfos: [],
2097
2492
  keyIds: new Set(),
2098
- language,
2493
+ language: this.getLanguage_(languageValue),
2494
+ originalLanguage: languageValue,
2099
2495
  label: name, // For historical reasons, since before "originalId".
2100
2496
  type,
2101
2497
  primary,
@@ -2110,11 +2506,12 @@ shaka.hls.HlsParser = class {
2110
2506
  roles,
2111
2507
  forced,
2112
2508
  channelsCount,
2113
- audioSamplingRate: null,
2509
+ audioSamplingRate: sampleRate,
2114
2510
  spatialAudio,
2115
2511
  closedCaptions,
2116
2512
  hdr: undefined,
2117
2513
  tilesLayout: undefined,
2514
+ accessibilityPurpose: null,
2118
2515
  external: false,
2119
2516
  };
2120
2517
  }
@@ -2285,43 +2682,89 @@ shaka.hls.HlsParser = class {
2285
2682
  } else {
2286
2683
  this.setPresentationType_(PresentationType.EVENT);
2287
2684
  }
2685
+ }
2686
+ }
2687
+
2688
+
2689
+ /**
2690
+ * @param {!shaka.hls.Playlist} playlist
2691
+ * @private
2692
+ */
2693
+ determineLastTargetDuration_(playlist) {
2694
+ let lastTargetDuration = Infinity;
2695
+ const segments = playlist.segments;
2696
+ if (segments.length) {
2697
+ let segmentIndex = segments.length - 1;
2698
+ while (segmentIndex >= 0) {
2699
+ const segment = segments[segmentIndex];
2700
+ const extinfTag =
2701
+ shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXTINF');
2702
+ if (extinfTag) {
2703
+ // The EXTINF tag format is '#EXTINF:<duration>,[<title>]'.
2704
+ // We're interested in the duration part.
2705
+ const extinfValues = extinfTag.value.split(',');
2706
+ lastTargetDuration = Number(extinfValues[0]);
2707
+ break;
2708
+ }
2709
+ segmentIndex--;
2710
+ }
2711
+ }
2288
2712
 
2289
- const targetDurationTag = this.getRequiredTag_(playlist.tags,
2290
- 'EXT-X-TARGETDURATION');
2291
- const targetDuration = Number(targetDurationTag.value);
2292
- const partialTargetDurationTag =
2293
- shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF');
2294
- // According to the HLS spec, updates should not happen more often than
2295
- // once in targetDuration. It also requires us to only update the active
2296
- // variant. We might implement that later, but for now every variant
2297
- // will be updated. To get the update period, choose the smallest
2298
- // targetDuration value across all playlists.
2299
- // 1. Update the shortest one to use as update period and segment
2300
- // availability time (for LIVE).
2301
- if (this.lowLatencyMode_ && partialTargetDurationTag) {
2302
- // For low latency streaming, use the partial segment target duration.
2303
- this.partialTargetDuration_ = Number(
2304
- partialTargetDurationTag.getRequiredAttrValue('PART-TARGET'));
2305
- this.minTargetDuration_ = Math.min(
2306
- this.partialTargetDuration_, this.minTargetDuration_);
2307
- // Get the server-recommended min distance from the live edge.
2308
- const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
2309
- playlist.tags, 'EXT-X-SERVER-CONTROL');
2310
- // Use 'PART-HOLD-BACK' as the presentation delay for low latency mode.
2311
- this.lowLatencyPresentationDelay_ = serverControlTag ? Number(
2312
- serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0;
2713
+ const targetDurationTag = this.getRequiredTag_(playlist.tags,
2714
+ 'EXT-X-TARGETDURATION');
2715
+ const targetDuration = Number(targetDurationTag.value);
2716
+ const partialTargetDurationTag =
2717
+ shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF');
2718
+ if (partialTargetDurationTag) {
2719
+ this.partialTargetDuration_ = Number(
2720
+ partialTargetDurationTag.getRequiredAttrValue('PART-TARGET'));
2721
+ }
2722
+ // Get the server-recommended min distance from the live edge.
2723
+ const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
2724
+ playlist.tags, 'EXT-X-SERVER-CONTROL');
2725
+ // According to the HLS spec, updates should not happen more often than
2726
+ // once in targetDuration. It also requires us to only update the active
2727
+ // variant. We might implement that later, but for now every variant
2728
+ // will be updated. To get the update period, choose the smallest
2729
+ // targetDuration value across all playlists.
2730
+ // 1. Update the shortest one to use as update period and segment
2731
+ // availability time (for LIVE).
2732
+ if (this.lowLatencyMode_ && this.partialTargetDuration_) {
2733
+ // For low latency streaming, use the partial segment target duration.
2734
+ if (this.lowLatencyByterangeOptimization_) {
2735
+ // We always have at least 1 partial segment part, and most servers
2736
+ // allow you to make a request with _HLS_msn=X&_HLS_part=0 with a
2737
+ // distance of 4 partial segments. With this we ensure that we
2738
+ // obtain the minimum latency in this type of case.
2739
+ if (this.partialTargetDuration_ * 5 <= lastTargetDuration) {
2740
+ this.lastTargetDuration_ = Math.min(
2741
+ this.partialTargetDuration_, this.lastTargetDuration_);
2742
+ } else {
2743
+ this.lastTargetDuration_ = Math.min(
2744
+ lastTargetDuration, this.lastTargetDuration_);
2745
+ }
2313
2746
  } else {
2314
- // For regular HLS, use the target duration of regular segments.
2315
- this.minTargetDuration_ = Math.min(
2316
- targetDuration, this.minTargetDuration_);
2747
+ this.lastTargetDuration_ = Math.min(
2748
+ this.partialTargetDuration_, this.lastTargetDuration_);
2317
2749
  }
2318
- // 2. Update the longest target duration if need be to use as a
2319
- // presentation delay later.
2320
- this.maxTargetDuration_ = Math.max(
2321
- targetDuration, this.maxTargetDuration_);
2750
+ // Use 'PART-HOLD-BACK' as the presentation delay for low latency mode.
2751
+ this.lowLatencyPresentationDelay_ = serverControlTag ? Number(
2752
+ serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0;
2753
+ } else {
2754
+ this.lastTargetDuration_ = Math.min(
2755
+ lastTargetDuration, this.lastTargetDuration_);
2756
+ // Use 'HOLD-BACK' as the presentation delay for default if defined.
2757
+ const holdBack = serverControlTag ?
2758
+ serverControlTag.getAttribute('HOLD-BACK') : null;
2759
+ this.presentationDelay_ = holdBack ? Number(holdBack.value) : 0;
2322
2760
  }
2761
+ // 2. Update the longest target duration if need be to use as a
2762
+ // presentation delay later.
2763
+ this.maxTargetDuration_ = Math.max(
2764
+ targetDuration, this.maxTargetDuration_);
2323
2765
  }
2324
2766
 
2767
+
2325
2768
  /**
2326
2769
  * @private
2327
2770
  */
@@ -2345,6 +2788,8 @@ shaka.hls.HlsParser = class {
2345
2788
  presentationDelay = this.config_.defaultPresentationDelay;
2346
2789
  } else if (this.lowLatencyPresentationDelay_) {
2347
2790
  presentationDelay = this.lowLatencyPresentationDelay_;
2791
+ } else if (this.presentationDelay_) {
2792
+ presentationDelay = this.presentationDelay_;
2348
2793
  } else {
2349
2794
  const numberOfSegments = this.config_.hls.liveSegmentsDelay;
2350
2795
  presentationDelay = this.maxTargetDuration_ * numberOfSegments;
@@ -2427,6 +2872,7 @@ shaka.hls.HlsParser = class {
2427
2872
  * @param {!Map.<string, string>} variables
2428
2873
  * @param {string} absoluteMediaPlaylistUri
2429
2874
  * @param {string} type
2875
+ * @param {string} mimeType
2430
2876
  * @param {number} timestampOffset
2431
2877
  * @param {shaka.extern.HlsAes128Key=} hlsAes128Key
2432
2878
  * @return {shaka.media.SegmentReference}
@@ -2434,11 +2880,9 @@ shaka.hls.HlsParser = class {
2434
2880
  */
2435
2881
  createSegmentReference_(
2436
2882
  initSegmentReference, previousReference, hlsSegment, startTime,
2437
- variables, absoluteMediaPlaylistUri, type, timestampOffset,
2883
+ variables, absoluteMediaPlaylistUri, type, mimeType, timestampOffset,
2438
2884
  hlsAes128Key) {
2439
2885
  const tags = hlsSegment.tags;
2440
- const absoluteSegmentUri = this.variableSubstitution_(
2441
- hlsSegment.absoluteUri, variables);
2442
2886
  const extinfTag =
2443
2887
  shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
2444
2888
 
@@ -2484,8 +2928,20 @@ shaka.hls.HlsParser = class {
2484
2928
  }
2485
2929
 
2486
2930
  // Create SegmentReferences for the partial segments.
2487
- const partialSegmentRefs = [];
2488
- if (this.lowLatencyMode_) {
2931
+ let partialSegmentRefs = [];
2932
+
2933
+ // Optimization for LL-HLS with byterange
2934
+ // More info in https://tinyurl.com/hls-open-byte-range
2935
+ let segmentWithByteRangeOptimization = false;
2936
+ let getUrisOptimization = null;
2937
+ let somePartialSegmentWithGap = false;
2938
+ let isPreloadSegment = false;
2939
+
2940
+ if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
2941
+ const byterangeOptimizationSupport = (mimeType == 'video/mp4' ||
2942
+ mimeType == 'audio/mp4') && window.ReadableStream;
2943
+
2944
+ let partialSyncTime = syncTime;
2489
2945
  for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
2490
2946
  const item = hlsSegment.partialSegments[i];
2491
2947
  const pPreviousReference = i == 0 ?
@@ -2513,6 +2969,16 @@ shaka.hls.HlsParser = class {
2513
2969
  // A preload hinted partial segment may have byterange start info.
2514
2970
  const pByterangeStart = item.getAttributeValue('BYTERANGE-START');
2515
2971
  pStartByte = pByterangeStart ? Number(pByterangeStart) : 0;
2972
+ // A preload hinted partial segment may have byterange length info.
2973
+ const pByterangeLength = item.getAttributeValue('BYTERANGE-LENGTH');
2974
+ if (pByterangeLength) {
2975
+ pEndByte = pStartByte + Number(pByterangeLength) - 1;
2976
+ } else if (pStartByte) {
2977
+ // If we have a non-zero start byte, but no end byte, follow the
2978
+ // recommendation of https://tinyurl.com/hls-open-byte-range and
2979
+ // set the end byte explicitly to a large integer.
2980
+ pEndByte = Number.MAX_SAFE_INTEGER;
2981
+ }
2516
2982
  } else {
2517
2983
  const pByterange = item.getAttributeValue('BYTERANGE');
2518
2984
  [pStartByte, pEndByte] =
@@ -2522,20 +2988,33 @@ shaka.hls.HlsParser = class {
2522
2988
  if (!pUri) {
2523
2989
  continue;
2524
2990
  }
2525
- const pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
2526
- absoluteMediaPlaylistUri, pUri);
2527
2991
 
2528
2992
  let partialStatus = shaka.media.SegmentReference.Status.AVAILABLE;
2529
2993
  if (item.getAttributeValue('GAP') == 'YES') {
2530
2994
  partialStatus = shaka.media.SegmentReference.Status.MISSING;
2995
+ somePartialSegmentWithGap = true;
2996
+ }
2997
+
2998
+ let pAbsoluteUri = null;
2999
+ const getPartialUris = () => {
3000
+ if (pAbsoluteUri == null) {
3001
+ goog.asserts.assert(pUri, 'Partial uri should be defined!');
3002
+ pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
3003
+ absoluteMediaPlaylistUri, pUri);
3004
+ }
3005
+ return [pAbsoluteUri];
3006
+ };
3007
+
3008
+ if (byterangeOptimizationSupport &&
3009
+ pStartByte >= 0 && pEndByte != null) {
3010
+ getUrisOptimization = getPartialUris;
3011
+ segmentWithByteRangeOptimization = true;
2531
3012
  }
2532
3013
 
2533
- // We do not set the AES-128 key information for partial segments, as we
2534
- // do not support AES-128 and low-latency at the same time.
2535
3014
  const partial = new shaka.media.SegmentReference(
2536
3015
  pStartTime,
2537
3016
  pEndTime,
2538
- () => [pAbsoluteUri],
3017
+ getPartialUris,
2539
3018
  pStartByte,
2540
3019
  pEndByte,
2541
3020
  initSegmentReference,
@@ -2545,12 +3024,25 @@ shaka.hls.HlsParser = class {
2545
3024
  /* partialReferences= */ [],
2546
3025
  /* tilesLayout= */ '',
2547
3026
  /* tileDuration= */ null,
2548
- /* syncTime= */ null,
2549
- partialStatus);
3027
+ partialSyncTime,
3028
+ partialStatus,
3029
+ hlsAes128Key);
2550
3030
  if (item.name == 'EXT-X-PRELOAD-HINT') {
2551
3031
  partial.markAsPreload();
3032
+ isPreloadSegment = true;
3033
+ }
3034
+ // The spec doesn't say that we can assume INDEPENDENT=YES for the
3035
+ // first partial segment. It does call the flag "optional", though, and
3036
+ // that cases where there are no such flags on any partial segments, it
3037
+ // is sensible to assume the first one is independent.
3038
+ if (item.getAttributeValue('INDEPENDENT') != 'YES' && i > 0) {
3039
+ partial.markAsNonIndependent();
2552
3040
  }
2553
3041
  partialSegmentRefs.push(partial);
3042
+
3043
+ if (partialSyncTime) {
3044
+ partialSyncTime += pDuration;
3045
+ }
2554
3046
  } // for-loop of hlsSegment.partialSegments
2555
3047
  }
2556
3048
 
@@ -2575,6 +3067,17 @@ shaka.hls.HlsParser = class {
2575
3067
  endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
2576
3068
  }
2577
3069
 
3070
+ if (segmentWithByteRangeOptimization) {
3071
+ // We cannot optimize segments with gaps, or with a start byte that is
3072
+ // not 0.
3073
+ if (somePartialSegmentWithGap || partialSegmentRefs[0].startByte != 0) {
3074
+ segmentWithByteRangeOptimization = false;
3075
+ getUrisOptimization = null;
3076
+ } else {
3077
+ partialSegmentRefs = [];
3078
+ }
3079
+ }
3080
+
2578
3081
  // If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
2579
3082
  // base on the byterange information. If segment has no EXT-X-BYTERANGE tag
2580
3083
  // and has partial segments, set the start byte and end byte base on the
@@ -2605,10 +3108,22 @@ shaka.hls.HlsParser = class {
2605
3108
  }
2606
3109
  }
2607
3110
 
2608
- return new shaka.media.SegmentReference(
3111
+ let absoluteSegmentUri = null;
3112
+ const getUris = () => {
3113
+ if (getUrisOptimization) {
3114
+ return getUrisOptimization();
3115
+ }
3116
+ if (absoluteSegmentUri == null) {
3117
+ absoluteSegmentUri = this.variableSubstitution_(
3118
+ hlsSegment.absoluteUri, variables);
3119
+ }
3120
+ return absoluteSegmentUri.length ? [absoluteSegmentUri] : [];
3121
+ };
3122
+
3123
+ const reference = new shaka.media.SegmentReference(
2609
3124
  startTime,
2610
3125
  endTime,
2611
- () => absoluteSegmentUri.length ? [absoluteSegmentUri] : [],
3126
+ getUris,
2612
3127
  startByte,
2613
3128
  endByte,
2614
3129
  initSegmentReference,
@@ -2622,6 +3137,17 @@ shaka.hls.HlsParser = class {
2622
3137
  status,
2623
3138
  hlsAes128Key,
2624
3139
  );
3140
+
3141
+ if (segmentWithByteRangeOptimization) {
3142
+ this.lowLatencyByterangeOptimization_ = true;
3143
+ reference.markAsByterangeOptimization();
3144
+
3145
+ if (isPreloadSegment) {
3146
+ reference.markAsPreload();
3147
+ }
3148
+ }
3149
+
3150
+ return reference;
2625
3151
  }
2626
3152
 
2627
3153
 
@@ -2653,7 +3179,9 @@ shaka.hls.HlsParser = class {
2653
3179
  }
2654
3180
 
2655
3181
  /**
2656
- * Parses shaka.hls.Segment objects into shaka.media.SegmentReferences.
3182
+ * Parses shaka.hls.Segment objects into shaka.media.SegmentReferences and
3183
+ * get the bandwidth necessary for this segments If it's defined in the
3184
+ * playlist.
2657
3185
  *
2658
3186
  * @param {string} verbatimMediaPlaylistUri
2659
3187
  * @param {!shaka.hls.Playlist} playlist
@@ -2661,7 +3189,8 @@ shaka.hls.HlsParser = class {
2661
3189
  * @param {string} mimeType
2662
3190
  * @param {!Map.<number, number>} mediaSequenceToStartTime
2663
3191
  * @param {!Map.<string, string>} variables
2664
- * @return {!Array.<!shaka.media.SegmentReference>}
3192
+ * @return {{segments: !Array.<!shaka.media.SegmentReference>,
3193
+ * bandwidth: (number|undefined)}}
2665
3194
  * @private
2666
3195
  */
2667
3196
  createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
@@ -2699,6 +3228,9 @@ shaka.hls.HlsParser = class {
2699
3228
  let previousReference = null;
2700
3229
  let lastDiscontinuityStartTime = firstStartTime;
2701
3230
 
3231
+ /** @type {!Array.<{bitrate: number, duration: number}>} */
3232
+ const bitrates = [];
3233
+
2702
3234
  for (let i = 0; i < hlsSegments.length; i++) {
2703
3235
  const item = hlsSegments[i];
2704
3236
  const startTime =
@@ -2748,10 +3280,26 @@ shaka.hls.HlsParser = class {
2748
3280
  variables,
2749
3281
  playlist.absoluteUri,
2750
3282
  type,
3283
+ mimeType,
2751
3284
  lastDiscontinuityStartTime,
2752
3285
  hlsAes128Key);
2753
3286
 
2754
3287
  if (reference) {
3288
+ const bitrate = shaka.hls.Utils.getFirstTagWithNameAsNumber(
3289
+ item.tags, 'EXT-X-BITRATE');
3290
+ if (bitrate) {
3291
+ bitrates.push({
3292
+ bitrate,
3293
+ duration: reference.endTime - reference.startTime,
3294
+ });
3295
+ } else if (bitrates.length) {
3296
+ // It applies to every segment between it and the next EXT-X-BITRATE,
3297
+ // so we use the latest bitrate value
3298
+ const prevBitrate = bitrates.pop();
3299
+ prevBitrate.duration += reference.endTime - reference.startTime;
3300
+ bitrates.push(prevBitrate);
3301
+ }
3302
+
2755
3303
  previousReference = reference;
2756
3304
  reference.discontinuitySequence = discontinuitySequence;
2757
3305
 
@@ -2766,6 +3314,16 @@ shaka.hls.HlsParser = class {
2766
3314
  }
2767
3315
  }
2768
3316
 
3317
+ let bandwidth = undefined;
3318
+ if (bitrates.length) {
3319
+ const duration = bitrates.reduce((sum, value) => {
3320
+ return sum + value.duration;
3321
+ }, 0);
3322
+ bandwidth = Math.round(bitrates.reduce((sum, value) => {
3323
+ return sum + value.bitrate * value.duration;
3324
+ }, 0) / duration * 1000);
3325
+ }
3326
+
2769
3327
  // If some segments have sync times, but not all, extrapolate the sync
2770
3328
  // times of the ones with none.
2771
3329
  const someSyncTime = references.some((ref) => ref.syncTime != null);
@@ -2851,7 +3409,10 @@ shaka.hls.HlsParser = class {
2851
3409
  }
2852
3410
  }
2853
3411
 
2854
- return references;
3412
+ return {
3413
+ segments: references,
3414
+ bandwidth,
3415
+ };
2855
3416
  }
2856
3417
 
2857
3418
  /**
@@ -2996,9 +3557,9 @@ shaka.hls.HlsParser = class {
2996
3557
  const headRequest = shaka.net.NetworkingEngine.makeRequest(
2997
3558
  [middleSegmentUri], this.config_.retryParameters);
2998
3559
  headRequest.method = 'HEAD';
2999
-
3560
+ const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
3000
3561
  const response = await this.makeNetworkRequest_(
3001
- headRequest, requestType);
3562
+ headRequest, requestType, {type});
3002
3563
 
3003
3564
  const contentMimeType = response.headers['content-type'];
3004
3565
  if (contentMimeType) {
@@ -3052,19 +3613,20 @@ shaka.hls.HlsParser = class {
3052
3613
  * with the resulting data.
3053
3614
  *
3054
3615
  * @param {string} absoluteUri
3616
+ * @param {boolean=} isPlaylist
3055
3617
  * @return {!Promise.<!shaka.extern.Response>}
3056
3618
  * @private
3057
3619
  */
3058
- requestManifest_(absoluteUri) {
3620
+ requestManifest_(absoluteUri, isPlaylist) {
3059
3621
  const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
3060
3622
 
3061
3623
  const request = shaka.net.NetworkingEngine.makeRequest(
3062
3624
  [absoluteUri], this.config_.retryParameters);
3063
3625
 
3064
- const format = shaka.util.CmcdManager.StreamingFormat.HLS;
3065
- this.playerInterface_.modifyManifestRequest(request, {format: format});
3066
-
3067
- return this.makeNetworkRequest_(request, requestType);
3626
+ const type = isPlaylist ?
3627
+ shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_PLAYLIST :
3628
+ shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST;
3629
+ return this.makeNetworkRequest_(request, requestType, {type});
3068
3630
  }
3069
3631
 
3070
3632
  /**
@@ -3079,7 +3641,7 @@ shaka.hls.HlsParser = class {
3079
3641
  shaka.log.info('Updating manifest...');
3080
3642
 
3081
3643
  goog.asserts.assert(
3082
- this.updatePlaylistDelay_ > 0,
3644
+ this.getUpdatePlaylistDelay_() > 0,
3083
3645
  'We should only call |onUpdate_| when we are suppose to be updating.');
3084
3646
 
3085
3647
  // Detect a call to stop()
@@ -3092,7 +3654,7 @@ shaka.hls.HlsParser = class {
3092
3654
 
3093
3655
  // This may have converted to VOD, in which case we stop updating.
3094
3656
  if (this.isLive_()) {
3095
- const delay = this.updatePlaylistDelay_;
3657
+ const delay = this.getUpdatePlaylistDelay_();
3096
3658
  this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
3097
3659
  }
3098
3660
  } catch (error) {
@@ -3104,6 +3666,11 @@ shaka.hls.HlsParser = class {
3104
3666
  goog.asserts.assert(error instanceof shaka.util.Error,
3105
3667
  'Should only receive a Shaka error');
3106
3668
 
3669
+ if (this.config_.raiseFatalErrorOnManifestUpdateRequestFailure) {
3670
+ this.playerInterface_.onError(error);
3671
+ return;
3672
+ }
3673
+
3107
3674
  // We will retry updating, so override the severity of the error.
3108
3675
  error.severity = shaka.util.Error.Severity.RECOVERABLE;
3109
3676
  this.playerInterface_.onError(error);
@@ -3124,6 +3691,20 @@ shaka.hls.HlsParser = class {
3124
3691
  }
3125
3692
 
3126
3693
 
3694
+ /**
3695
+ * @return {number}
3696
+ * @private
3697
+ */
3698
+ getUpdatePlaylistDelay_() {
3699
+ // The HLS spec (RFC 8216) states in 6.3.4:
3700
+ // "the client MUST wait for at least the target duration before
3701
+ // attempting to reload the Playlist file again".
3702
+ // For LL-HLS, the server must add a new partial segment to the Playlist
3703
+ // every part target duration.
3704
+ return this.lastTargetDuration_;
3705
+ }
3706
+
3707
+
3127
3708
  /**
3128
3709
  * @param {shaka.hls.HlsParser.PresentationType_} type
3129
3710
  * @private
@@ -3150,10 +3731,11 @@ shaka.hls.HlsParser = class {
3150
3731
  *
3151
3732
  * @param {shaka.extern.Request} request
3152
3733
  * @param {shaka.net.NetworkingEngine.RequestType} type
3734
+ * @param {shaka.extern.RequestContext=} context
3153
3735
  * @return {!Promise.<shaka.extern.Response>}
3154
3736
  * @private
3155
3737
  */
3156
- makeNetworkRequest_(request, type) {
3738
+ makeNetworkRequest_(request, type, context) {
3157
3739
  if (!this.operationManager_) {
3158
3740
  throw new shaka.util.Error(
3159
3741
  shaka.util.Error.Severity.CRITICAL,
@@ -3161,7 +3743,8 @@ shaka.hls.HlsParser = class {
3161
3743
  shaka.util.Error.Code.OPERATION_ABORTED);
3162
3744
  }
3163
3745
 
3164
- const op = this.playerInterface_.networkingEngine.request(type, request);
3746
+ const op = this.playerInterface_.networkingEngine.request(
3747
+ type, request, context);
3165
3748
  this.operationManager_.manage(op);
3166
3749
 
3167
3750
  return op.promise;
@@ -3315,8 +3898,11 @@ shaka.hls.HlsParser = class {
3315
3898
  * maxTimestamp: number,
3316
3899
  * mediaSequenceToStartTime: !Map.<number, number>,
3317
3900
  * canSkipSegments: boolean,
3901
+ * canBlockReload: boolean,
3318
3902
  * hasEndList: boolean,
3319
3903
  * firstSequenceNumber: number,
3904
+ * nextMediaSequence: number,
3905
+ * nextPart: number,
3320
3906
  * loadedOnce: boolean
3321
3907
  * }}
3322
3908
  *
@@ -3345,10 +3931,18 @@ shaka.hls.HlsParser = class {
3345
3931
  * @property {boolean} canSkipSegments
3346
3932
  * True if the server supports delta playlist updates, and we can send a
3347
3933
  * request for a playlist that can skip older media segments.
3934
+ * @property {boolean} canBlockReload
3935
+ * True if the server supports blocking playlist reload, and we can send a
3936
+ * request for a playlist that can block reload until some segments are
3937
+ * present.
3348
3938
  * @property {boolean} hasEndList
3349
3939
  * True if the stream has an EXT-X-ENDLIST tag.
3350
3940
  * @property {number} firstSequenceNumber
3351
3941
  * The sequence number of the first reference. Only calculated if needed.
3942
+ * @property {number} nextMediaSequence
3943
+ * The next media sequence.
3944
+ * @property {number} nextPart
3945
+ * The next part.
3352
3946
  * @property {boolean} loadedOnce
3353
3947
  * True if the stream has been loaded at least once.
3354
3948
  */
@@ -3368,6 +3962,30 @@ shaka.hls.HlsParser.StreamInfo;
3368
3962
  shaka.hls.HlsParser.StreamInfos;
3369
3963
 
3370
3964
 
3965
+ /**
3966
+ * @typedef {{
3967
+ * type: string,
3968
+ * mimeType: string,
3969
+ * codecs: string,
3970
+ * language: ?string,
3971
+ * height: ?string,
3972
+ * width: ?string,
3973
+ * channelCount: ?number,
3974
+ * sampleRate: ?number
3975
+ * }}
3976
+ *
3977
+ * @property {string} type
3978
+ * @property {string} mimeType
3979
+ * @property {string} codecs
3980
+ * @property {?string} language
3981
+ * @property {?string} height
3982
+ * @property {?string} width
3983
+ * @property {?number} channelCount
3984
+ * @property {?number} sampleRate
3985
+ */
3986
+ shaka.hls.HlsParser.BasicInfo;
3987
+
3988
+
3371
3989
  /**
3372
3990
  * @const {!Object.<string, string>}
3373
3991
  * @private
@@ -3489,13 +4107,9 @@ shaka.hls.HlsParser.PresentationType_ = {
3489
4107
  LIVE: 'LIVE',
3490
4108
  };
3491
4109
 
3492
- if (!shaka.util.Platform.isTizen3() &&
3493
- !shaka.util.Platform.isTizen2() &&
3494
- !shaka.util.Platform.isWebOS3()) {
3495
- shaka.media.ManifestParser.registerParserByExtension(
3496
- 'm3u8', () => new shaka.hls.HlsParser());
3497
- shaka.media.ManifestParser.registerParserByMime(
3498
- 'application/x-mpegurl', () => new shaka.hls.HlsParser());
3499
- shaka.media.ManifestParser.registerParserByMime(
3500
- 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
3501
- }
4110
+ shaka.media.ManifestParser.registerParserByExtension(
4111
+ 'm3u8', () => new shaka.hls.HlsParser());
4112
+ shaka.media.ManifestParser.registerParserByMime(
4113
+ 'application/x-mpegurl', () => new shaka.hls.HlsParser());
4114
+ shaka.media.ManifestParser.registerParserByMime(
4115
+ 'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());