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.
- package/AUTHORS +9 -0
- package/CHANGELOG.md +303 -202
- package/CONTRIBUTORS +8 -0
- package/LICENSE +32 -0
- package/README.md +63 -10
- package/demo/autoTemplate.txt +40 -5
- package/demo/close_button.js +11 -0
- package/demo/common/asset.js +13 -1
- package/demo/common/assets.js +53 -3
- package/demo/common/message_ids.js +25 -1
- package/demo/config.js +69 -10
- package/demo/customWarning.txt +2 -2
- package/demo/index.html +3 -1
- package/demo/locales/en.json +26 -2
- package/demo/locales/source.json +105 -9
- package/demo/main.js +52 -9
- package/demo/search.js +3 -1
- package/demo/service_worker.js +3 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +124 -112
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +124 -112
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +46 -28
- package/dist/locales.js +33 -33
- package/dist/receiver.compiled.debug.js +52 -46
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +52 -46
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +676 -391
- package/dist/shaka-player.compiled.debug.d.ts +676 -391
- package/dist/shaka-player.compiled.debug.externs.js +1188 -616
- package/dist/shaka-player.compiled.debug.js +1043 -699
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +1188 -616
- package/dist/shaka-player.compiled.js +1091 -929
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +677 -392
- package/dist/shaka-player.ui.debug.d.ts +677 -392
- package/dist/shaka-player.ui.debug.externs.js +1023 -451
- package/dist/shaka-player.ui.debug.js +1098 -737
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +1023 -451
- package/dist/shaka-player.ui.js +1246 -1071
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/ad_monetization.md +15 -1
- package/docs/tutorials/application-level-redirects.md +1 -1
- package/docs/tutorials/config.md +9 -5
- package/docs/tutorials/drm-config.md +61 -0
- package/docs/tutorials/fairplay.md +19 -4
- package/docs/tutorials/license-server-auth.md +4 -4
- package/docs/tutorials/license-wrapping.md +2 -2
- package/docs/tutorials/ui-customization.md +7 -3
- package/docs/tutorials/ui.md +7 -7
- package/docs/tutorials/upgrade.md +3 -0
- package/externs/ima.js +123 -2
- package/externs/isoboxer.js +217 -0
- package/externs/mediakeys.js +18 -0
- package/externs/mux.js +1 -105
- package/externs/pictureinpicture.js +48 -0
- package/externs/shaka/ads.js +61 -1
- package/externs/shaka/cea.js +110 -0
- package/externs/shaka/codecs.js +32 -0
- package/externs/shaka/manifest.js +101 -1
- package/externs/shaka/manifest_parser.js +0 -10
- package/externs/shaka/net.js +36 -7
- package/externs/shaka/offline.js +8 -1
- package/externs/shaka/player.js +220 -21
- package/externs/shaka/text.js +1 -382
- package/externs/shaka/transmuxer.js +60 -0
- package/karma.conf.js +13 -0
- package/lib/abr/simple_abr_manager.js +6 -3
- package/lib/ads/ad_manager.js +60 -2
- package/lib/ads/client_side_ad.js +86 -0
- package/lib/ads/client_side_ad_manager.js +79 -3
- package/lib/ads/server_side_ad.js +86 -0
- package/lib/ads/server_side_ad_manager.js +13 -0
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/cea608_data_channel.js +24 -14
- package/lib/cea/cea608_memory.js +5 -2
- package/lib/cea/cea708_service.js +8 -9
- package/lib/cea/cea708_window.js +1 -2
- package/lib/cea/cea_decoder.js +23 -6
- package/lib/cea/cea_utils.js +25 -2
- package/lib/cea/dummy_caption_decoder.js +21 -0
- package/lib/cea/dummy_cea_parser.js +1 -3
- package/lib/cea/mp4_cea_parser.js +15 -10
- package/lib/cea/ts_cea_parser.js +69 -0
- package/lib/dash/content_protection.js +32 -2
- package/lib/dash/dash_parser.js +93 -31
- package/lib/dash/mpd_utils.js +6 -25
- package/lib/dash/segment_base.js +12 -14
- package/lib/dash/segment_list.js +10 -6
- package/lib/dash/segment_template.js +349 -32
- package/lib/dependencies/all.js +8 -0
- package/lib/hls/hls_classes.js +33 -8
- package/lib/hls/hls_parser.js +805 -191
- package/lib/hls/manifest_text_parser.js +4 -14
- package/lib/media/adaptation_set_criteria.js +43 -3
- package/lib/media/closed_caption_parser.js +74 -12
- package/lib/media/drm_engine.js +189 -49
- package/lib/media/gap_jumping_controller.js +1 -1
- package/lib/media/manifest_parser.js +34 -0
- package/lib/media/media_source_capabilities.js +10 -0
- package/lib/media/media_source_engine.js +495 -231
- package/lib/media/playhead.js +1 -1
- package/lib/media/presentation_timeline.js +62 -0
- package/lib/media/segment_index.js +50 -14
- package/lib/media/segment_prefetch.js +277 -0
- package/lib/media/segment_reference.js +70 -4
- package/lib/media/streaming_engine.js +258 -81
- package/lib/media/video_wrapper.js +15 -7
- package/lib/mss/content_protection.js +353 -0
- package/lib/mss/mss_parser.js +1082 -0
- package/lib/net/networking_engine.js +60 -10
- package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
- package/lib/offline/manifest_converter.js +6 -0
- package/lib/offline/storage.js +2 -4
- package/lib/player.js +309 -105
- package/lib/polyfill/media_capabilities.js +43 -3
- package/lib/polyfill/patchedmediakeys_apple.js +5 -0
- package/lib/polyfill/patchedmediakeys_nop.js +5 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +5 -0
- package/lib/text/cue.js +208 -130
- package/lib/text/lrc_text_parser.js +2 -2
- package/lib/text/mp4_vtt_parser.js +2 -2
- package/lib/text/sbv_text_parser.js +1 -1
- package/lib/text/simple_text_displayer.js +1 -1
- package/lib/text/srt_text_parser.js +9 -1
- package/lib/text/ssa_text_parser.js +2 -2
- package/lib/text/text_engine.js +3 -21
- package/lib/text/text_utils.js +71 -20
- package/lib/text/ui_text_displayer.js +14 -14
- package/lib/text/vtt_text_parser.js +22 -14
- package/lib/transmuxer/aac_transmuxer.js +224 -0
- package/lib/transmuxer/ac3.js +142 -0
- package/lib/transmuxer/ac3_transmuxer.js +213 -0
- package/lib/transmuxer/adts.js +232 -0
- package/lib/transmuxer/ec3.js +103 -0
- package/lib/transmuxer/ec3_transmuxer.js +213 -0
- package/lib/transmuxer/h264.js +324 -0
- package/lib/transmuxer/mp3_transmuxer.js +205 -0
- package/lib/transmuxer/mpeg_audio.js +196 -0
- package/lib/transmuxer/mpeg_ts_transmuxer.js +182 -0
- package/lib/transmuxer/mss_transmuxer.js +389 -0
- package/lib/{media/transmuxer.js → transmuxer/muxjs_transmuxer.js} +34 -80
- package/lib/transmuxer/transmuxer_engine.js +161 -0
- package/lib/transmuxer/ts_transmuxer.js +721 -0
- package/lib/util/cmcd_manager.js +131 -71
- package/lib/util/error.js +62 -26
- package/lib/util/exp_golomb.js +220 -0
- package/lib/util/fairplay_utils.js +64 -13
- package/lib/util/id3_utils.js +56 -3
- package/lib/util/language_utils.js +11 -8
- package/lib/util/manifest_parser_utils.js +3 -0
- package/lib/util/mime_utils.js +44 -7
- package/lib/util/mp4_box_parsers.js +233 -5
- package/lib/util/mp4_generator.js +1388 -0
- package/lib/util/periods.js +40 -34
- package/lib/util/platform.js +17 -3
- package/lib/util/player_configuration.js +38 -1
- package/lib/util/stream_utils.js +138 -27
- package/lib/util/text_parser.js +1 -1
- package/lib/util/ts_parser.js +460 -74
- package/lib/util/xml_utils.js +16 -3
- package/maintained-branches.md +2 -2
- package/package.json +3 -1
- package/roadmap.md +21 -6
- package/shaka-player.uncompiled.js +18 -0
- package/test/abr/simple_abr_manager_unit.js +32 -4
- package/test/ads/ad_manager_unit.js +18 -2
- package/test/cast/cast_utils_unit.js +7 -2
- package/test/cea/cea608_memory_unit.js +24 -0
- package/test/cea/cea_decoder_unit.js +33 -0
- package/test/dash/dash_parser_content_protection_unit.js +93 -3
- package/test/dash/dash_parser_live_unit.js +56 -13
- package/test/dash/dash_parser_manifest_unit.js +70 -2
- package/test/dash/dash_parser_segment_base_unit.js +14 -9
- package/test/dash/dash_parser_segment_template_unit.js +286 -6
- package/test/demo/demo_unit.js +5 -1
- package/test/hls/hls_live_unit.js +188 -28
- package/test/hls/hls_parser_unit.js +250 -109
- package/test/hls/manifest_text_parser_unit.js +18 -13
- package/test/media/adaptation_set_criteria_unit.js +46 -14
- package/test/media/adaptation_set_unit.js +2 -0
- package/test/media/drm_engine_integration.js +11 -8
- package/test/media/drm_engine_unit.js +220 -7
- package/test/media/media_source_engine_integration.js +124 -40
- package/test/media/media_source_engine_unit.js +133 -153
- package/test/media/playhead_unit.js +3 -0
- package/test/media/presentation_timeline_unit.js +51 -0
- package/test/media/segment_index_unit.js +52 -0
- package/test/media/segment_prefetch_unit.js +243 -0
- package/test/media/streaming_engine_integration.js +9 -3
- package/test/media/streaming_engine_unit.js +440 -59
- package/test/mss/mss_parser_content_protection_unit.js +49 -0
- package/test/mss/mss_parser_unit.js +472 -0
- package/test/mss/mss_player_integration.js +123 -0
- package/test/offline/manifest_convert_unit.js +9 -0
- package/test/offline/storage_integration.js +6 -1
- package/test/player_unit.js +386 -133
- package/test/polyfill/media_capabilities_unit.js +18 -0
- package/test/test/assets/hls-raw-aac/fileSequence0.aac +0 -0
- package/test/test/assets/hls-raw-aac/fileSequence1.aac +0 -0
- package/test/test/assets/hls-raw-aac/fileSequence2.aac +0 -0
- package/test/test/assets/hls-raw-aac/fileSequence3.aac +0 -0
- package/test/test/assets/hls-raw-aac/manifest.m3u8 +4 -0
- package/test/test/assets/hls-raw-aac/stream.m4a.m3u8 +14 -0
- package/test/test/assets/hls-raw-ac3/fileSequence0.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence1.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence2.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence3.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence4.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence5.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence6.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/fileSequence7.ac3 +0 -0
- package/test/test/assets/hls-raw-ac3/prog_index.m3u8 +31 -0
- package/test/test/assets/hls-raw-ec3/fileSequence0.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence1.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence2.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence3.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence4.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence5.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence6.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/fileSequence7.ec3 +0 -0
- package/test/test/assets/hls-raw-ec3/prog_index.m3u8 +31 -0
- package/test/test/assets/hls-raw-mp3/chunklist_w905519204.m3u8 +13 -0
- package/test/test/assets/hls-raw-mp3/media_w905519204_0.mp3 +0 -0
- package/test/test/assets/hls-raw-mp3/media_w905519204_1.mp3 +0 -0
- package/test/test/assets/hls-raw-mp3/media_w905519204_2.mp3 +0 -0
- package/test/test/assets/hls-raw-mp3/media_w905519204_3.mp3 +0 -0
- package/test/test/assets/hls-raw-mp3/playlist.m3u8 +4 -0
- package/test/test/assets/hls-ts-aac/playlist.m3u8 +23 -0
- package/test/test/assets/hls-ts-aac/segment_0.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_1.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_2.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_3.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_4.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_5.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_6.ts +0 -0
- package/test/test/assets/hls-ts-aac/segment_7.ts +0 -0
- package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0000.ts +0 -0
- package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0001.ts +0 -0
- package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0002.ts +0 -0
- package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0003.ts +0 -0
- package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0004.ts +0 -0
- package/test/test/assets/hls-ts-ac3/ChID_voices_6ch_640kbps_dd_sub_0005.ts +0 -0
- package/test/test/assets/hls-ts-ac3/prog_index.m3u8 +19 -0
- package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0000.ts +0 -0
- package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0001.ts +0 -0
- package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0002.ts +0 -0
- package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0003.ts +0 -0
- package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0004.ts +0 -0
- package/test/test/assets/hls-ts-ec3/ChID_voices_6ch_256kbps_ddp_sub_0005.ts +0 -0
- package/test/test/assets/hls-ts-ec3/prog_index.m3u8 +19 -0
- package/test/test/assets/hls-ts-h264/fileSequence0.ts +0 -0
- package/test/test/assets/hls-ts-h264/fileSequence1.ts +0 -0
- package/test/test/assets/hls-ts-h264/fileSequence2.ts +0 -0
- package/test/test/assets/hls-ts-h264/fileSequence3.ts +0 -0
- package/test/test/assets/hls-ts-h264/fileSequence4.ts +0 -0
- package/test/test/assets/hls-ts-h264/fileSequence5.ts +0 -0
- package/test/test/assets/hls-ts-h264/prog_index.m3u8 +25 -0
- package/test/test/assets/hls-ts-mp3/manifest.m3u8 +13 -0
- package/test/test/assets/hls-ts-mp3/media_w239891843_0.ts +0 -0
- package/test/test/assets/hls-ts-mp3/media_w239891843_1.ts +0 -0
- package/test/test/assets/hls-ts-mp3/media_w239891843_2.ts +0 -0
- package/test/test/assets/hls-ts-mp3/media_w239891843_3.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/chunk.m3u8 +17 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/n_0_0_0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/n_1_0_0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/n_2_0_0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/n_3_0_0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/n_4_0_0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/n_5_0_0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h264/playlist.m3u8 +4 -0
- package/test/test/boot.js +7 -2
- package/test/test/util/fake_ad.js +99 -0
- package/test/test/util/fake_ad_manager.js +7 -1
- package/test/test/util/fake_networking_engine.js +41 -14
- package/test/test/util/fake_segment_prefetch.js +81 -0
- package/test/test/util/manifest_generator.js +19 -5
- package/test/test/util/mss_parser_util.js +115 -0
- package/test/test/util/offline_utils.js +1 -0
- package/test/test/util/simple_fakes.js +12 -3
- package/test/test/util/streaming_engine_util.js +14 -4
- package/test/test/util/ttml_utils.js +2 -2
- package/test/text/cue_integration.js +1 -1
- package/test/text/srt_text_parser_unit.js +78 -0
- package/test/text/text_engine_unit.js +17 -8
- package/test/text/ui_text_displayer_unit.js +4 -4
- package/test/text/vtt_text_parser_unit.js +59 -40
- package/test/text/web_vtt_generator_unit.js +9 -3
- package/test/{media/transmuxer_integration.js → transmuxer/muxjs_transmuxer_integration.js} +30 -33
- package/test/transmuxer/transmuxer_engine_integration.js +60 -0
- package/test/transmuxer/transmuxer_integration.js +296 -0
- package/test/util/cmcd_manager_unit.js +162 -13
- package/test/util/language_utils_unit.js +9 -2
- package/test/util/mp4_box_parsers_unit.js +39 -4
- package/test/util/periods_unit.js +34 -26
- package/test/util/stream_utils_unit.js +60 -107
- package/ui/controls.js +19 -1
- package/ui/controls.less +1 -0
- package/ui/externs/ui.js +11 -1
- package/ui/language_utils.js +15 -0
- package/ui/less/thumbnails.less +26 -0
- package/ui/locales/oc.json +42 -0
- package/ui/pip_button.js +83 -2
- package/ui/range_element.js +15 -0
- package/ui/resolution_selection.js +9 -2
- package/ui/seek_bar.js +223 -0
- package/ui/ui.js +5 -0
- package/lib/cea/i_caption_decoder.js +0 -51
- package/lib/cea/i_cea_parser.js +0 -68
package/lib/hls/hls_parser.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
368
|
+
queryData.add('_HLS_skip', 'YES');
|
|
354
369
|
}
|
|
355
|
-
|
|
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
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
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,
|
|
687
|
-
channelsCount, closedCaptions, characteristics, forced,
|
|
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:
|
|
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.
|
|
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:
|
|
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 {
|
|
1640
|
+
* @param {?string} languageValue
|
|
1305
1641
|
* @return {string}
|
|
1306
1642
|
* @private
|
|
1307
1643
|
*/
|
|
1308
|
-
getLanguage_(
|
|
1644
|
+
getLanguage_(languageValue) {
|
|
1309
1645
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1310
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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, /*
|
|
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= */
|
|
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}
|
|
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,
|
|
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,
|
|
1661
|
-
channelsCount, closedCaptions, characteristics, forced,
|
|
1662
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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}
|
|
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,
|
|
1963
|
-
channelsCount, closedCaptions, characteristics, forced,
|
|
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
|
|
2003
|
-
|
|
2004
|
-
|
|
2338
|
+
const {segments, bandwidth} = this.createSegments_(
|
|
2339
|
+
verbatimMediaPlaylistUri, playlist, type, mimeType,
|
|
2340
|
+
mediaSequenceToStartTime, mediaVariables);
|
|
2005
2341
|
|
|
2006
|
-
// This
|
|
2007
|
-
|
|
2008
|
-
|
|
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
|
|
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
|
|
2023
|
-
|
|
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}
|
|
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,
|
|
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:
|
|
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
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
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
|
-
|
|
2315
|
-
|
|
2316
|
-
targetDuration, this.minTargetDuration_);
|
|
2747
|
+
this.lastTargetDuration_ = Math.min(
|
|
2748
|
+
this.partialTargetDuration_, this.lastTargetDuration_);
|
|
2317
2749
|
}
|
|
2318
|
-
//
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
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
|
-
|
|
2488
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
3065
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
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());
|