shaka-player 4.3.9 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +11 -18
- package/.github/workflows/compute-incremental-coverage.py +3 -0
- package/.github/workflows/release-please.yaml +7 -0
- package/.github/workflows/selenium-lab-tests.yaml +45 -10
- package/AUTHORS +9 -0
- package/CHANGELOG.md +295 -190
- 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 +112 -28
- 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 +57 -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 +132 -118
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +132 -118
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +46 -28
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +60 -52
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +60 -52
- 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 +1046 -700
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +1188 -616
- package/dist/shaka-player.compiled.js +1094 -933
- 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 +1101 -738
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +1023 -451
- package/dist/shaka-player.ui.js +1244 -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 +59 -0
- package/karma.conf.js +15 -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 +93 -5
- 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 +475 -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 +1081 -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 +2 -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 +212 -0
- package/lib/transmuxer/adts.js +232 -0
- package/lib/transmuxer/ec3.js +103 -0
- package/lib/transmuxer/ec3_transmuxer.js +212 -0
- package/lib/transmuxer/h264.js +324 -0
- package/lib/transmuxer/mp3_transmuxer.js +204 -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 +738 -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 +1381 -0
- package/lib/util/periods.js +40 -34
- package/lib/util/platform.js +25 -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 +470 -47
- package/lib/util/xml_utils.js +16 -3
- package/maintained-branches.md +2 -2
- package/package.json +4 -2
- 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 +122 -38
- 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_integration.js +4 -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/test/util/ui_utils.js +5 -1
- 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 +13 -3
- 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 +230 -1
- 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
|
|
@@ -1209,6 +1520,16 @@ shaka.hls.HlsParser = class {
|
|
|
1209
1520
|
/** @type {!Array.<string>} */
|
|
1210
1521
|
const codecs = codecsString.split(/\s*,\s*/);
|
|
1211
1522
|
|
|
1523
|
+
return this.filterDuplicateCodecs_(codecs);
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
|
|
1527
|
+
/**
|
|
1528
|
+
* @param {!Array.<string>} codecs
|
|
1529
|
+
* @return {!Array.<string>} codecs
|
|
1530
|
+
* @private
|
|
1531
|
+
*/
|
|
1532
|
+
filterDuplicateCodecs_(codecs) {
|
|
1212
1533
|
// Filter out duplicate codecs.
|
|
1213
1534
|
const seen = new Set();
|
|
1214
1535
|
const ret = [];
|
|
@@ -1250,6 +1571,21 @@ shaka.hls.HlsParser = class {
|
|
|
1250
1571
|
return count;
|
|
1251
1572
|
}
|
|
1252
1573
|
|
|
1574
|
+
/**
|
|
1575
|
+
* Get the sample rate information for an HLS audio track.
|
|
1576
|
+
*
|
|
1577
|
+
* @param {!shaka.hls.Tag} tag
|
|
1578
|
+
* @return {?number}
|
|
1579
|
+
* @private
|
|
1580
|
+
*/
|
|
1581
|
+
getSampleRate_(tag) {
|
|
1582
|
+
const sampleRate = tag.getAttributeValue('SAMPLE-RATE');
|
|
1583
|
+
if (!sampleRate) {
|
|
1584
|
+
return null;
|
|
1585
|
+
}
|
|
1586
|
+
return parseInt(sampleRate, 10);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1253
1589
|
/**
|
|
1254
1590
|
* Get the spatial audio information for an HLS audio track.
|
|
1255
1591
|
* In HLS the channels field indicates the number of audio channels that the
|
|
@@ -1298,16 +1634,15 @@ shaka.hls.HlsParser = class {
|
|
|
1298
1634
|
}
|
|
1299
1635
|
|
|
1300
1636
|
/**
|
|
1301
|
-
* Get the language value.
|
|
1637
|
+
* Get the normalized language value.
|
|
1302
1638
|
*
|
|
1303
|
-
* @param {
|
|
1639
|
+
* @param {?string} languageValue
|
|
1304
1640
|
* @return {string}
|
|
1305
1641
|
* @private
|
|
1306
1642
|
*/
|
|
1307
|
-
getLanguage_(
|
|
1643
|
+
getLanguage_(languageValue) {
|
|
1308
1644
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
1309
|
-
|
|
1310
|
-
return LanguageUtils.normalize(languageValue);
|
|
1645
|
+
return LanguageUtils.normalize(languageValue || 'und');
|
|
1311
1646
|
}
|
|
1312
1647
|
|
|
1313
1648
|
/**
|
|
@@ -1409,6 +1744,7 @@ shaka.hls.HlsParser = class {
|
|
|
1409
1744
|
const variant = {
|
|
1410
1745
|
id: this.globalId_++,
|
|
1411
1746
|
language: audioStream ? audioStream.language : 'und',
|
|
1747
|
+
disabledUntilTime: 0,
|
|
1412
1748
|
primary: (!!audioStream && audioStream.primary) ||
|
|
1413
1749
|
(!!videoStream && videoStream.primary),
|
|
1414
1750
|
audio: audioStream,
|
|
@@ -1439,7 +1775,8 @@ shaka.hls.HlsParser = class {
|
|
|
1439
1775
|
for (const tag of closedCaptionsTags) {
|
|
1440
1776
|
goog.asserts.assert(tag.name == 'EXT-X-MEDIA',
|
|
1441
1777
|
'Should only be called on media tags!');
|
|
1442
|
-
const
|
|
1778
|
+
const languageValue = tag.getAttributeValue('LANGUAGE');
|
|
1779
|
+
const language = this.getLanguage_(languageValue);
|
|
1443
1780
|
|
|
1444
1781
|
// The GROUP-ID value is a quoted-string that specifies the group to which
|
|
1445
1782
|
// the Rendition belongs.
|
|
@@ -1485,7 +1822,7 @@ shaka.hls.HlsParser = class {
|
|
|
1485
1822
|
return this.uriToStreamInfosMap_.get(verbatimMediaPlaylistUri);
|
|
1486
1823
|
}
|
|
1487
1824
|
|
|
1488
|
-
const language =
|
|
1825
|
+
const language = tag.getAttributeValue('LANGUAGE');
|
|
1489
1826
|
const name = tag.getAttributeValue('NAME');
|
|
1490
1827
|
|
|
1491
1828
|
// NOTE: According to the HLS spec, "DEFAULT=YES" requires "AUTOSELECT=YES".
|
|
@@ -1503,12 +1840,13 @@ shaka.hls.HlsParser = class {
|
|
|
1503
1840
|
|
|
1504
1841
|
const forcedAttrValue = tag.getAttributeValue('FORCED');
|
|
1505
1842
|
const forced = forcedAttrValue == 'YES';
|
|
1843
|
+
const sampleRate = type == 'audio' ? this.getSampleRate_(tag) : null;
|
|
1506
1844
|
// TODO: Should we take into account some of the currently ignored
|
|
1507
1845
|
// attributes: INSTREAM-ID, Attribute descriptions: https://bit.ly/2lpjOhj
|
|
1508
1846
|
const streamInfo = this.createStreamInfo_(
|
|
1509
1847
|
verbatimMediaPlaylistUri, codecs, type, language, primary, name,
|
|
1510
1848
|
channelsCount, /* closedCaptions= */ null, characteristics, forced,
|
|
1511
|
-
spatialAudio);
|
|
1849
|
+
sampleRate, spatialAudio);
|
|
1512
1850
|
if (this.groupIdToStreamInfosMap_.has(groupId)) {
|
|
1513
1851
|
this.groupIdToStreamInfosMap_.get(groupId).push(streamInfo);
|
|
1514
1852
|
} else {
|
|
@@ -1547,7 +1885,7 @@ shaka.hls.HlsParser = class {
|
|
|
1547
1885
|
return this.uriToStreamInfosMap_.get(verbatimImagePlaylistUri);
|
|
1548
1886
|
}
|
|
1549
1887
|
|
|
1550
|
-
const language =
|
|
1888
|
+
const language = tag.getAttributeValue('LANGUAGE');
|
|
1551
1889
|
const name = tag.getAttributeValue('NAME');
|
|
1552
1890
|
|
|
1553
1891
|
const characteristics = tag.getAttributeValue('CHARACTERISTICS');
|
|
@@ -1555,7 +1893,8 @@ shaka.hls.HlsParser = class {
|
|
|
1555
1893
|
const streamInfo = this.createStreamInfo_(
|
|
1556
1894
|
verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
|
|
1557
1895
|
name, /* channelsCount= */ null, /* closedCaptions= */ null,
|
|
1558
|
-
characteristics, /* forced= */ false, /*
|
|
1896
|
+
characteristics, /* forced= */ false, /* sampleRate= */ null,
|
|
1897
|
+
/* spatialAudio= */ false);
|
|
1559
1898
|
|
|
1560
1899
|
// TODO: This check is necessary because of the possibility of multiple
|
|
1561
1900
|
// calls to createStreamInfoFromImageTag_ before either has resolved.
|
|
@@ -1616,10 +1955,10 @@ shaka.hls.HlsParser = class {
|
|
|
1616
1955
|
const closedCaptions = this.getClosedCaptions_(tag, type);
|
|
1617
1956
|
const codecs = shaka.util.ManifestParserUtils.guessCodecs(type, allCodecs);
|
|
1618
1957
|
const streamInfo = this.createStreamInfo_(verbatimMediaPlaylistUri,
|
|
1619
|
-
codecs, type, /* language= */
|
|
1958
|
+
codecs, type, /* language= */ null, /* primary= */ false,
|
|
1620
1959
|
/* name= */ null, /* channelcount= */ null, closedCaptions,
|
|
1621
1960
|
/* characteristics= */ null, /* forced= */ false,
|
|
1622
|
-
/* spatialAudio= */ false);
|
|
1961
|
+
/* sampleRate= */ null, /* spatialAudio= */ false);
|
|
1623
1962
|
// TODO: This check is necessary because of the possibility of multiple
|
|
1624
1963
|
// calls to createStreamInfoFromVariantTag_ before either has resolved.
|
|
1625
1964
|
if (this.uriToStreamInfosMap_.has(verbatimMediaPlaylistUri)) {
|
|
@@ -1635,20 +1974,21 @@ shaka.hls.HlsParser = class {
|
|
|
1635
1974
|
* @param {string} verbatimMediaPlaylistUri
|
|
1636
1975
|
* @param {string} codecs
|
|
1637
1976
|
* @param {string} type
|
|
1638
|
-
* @param {string}
|
|
1977
|
+
* @param {?string} languageValue
|
|
1639
1978
|
* @param {boolean} primary
|
|
1640
1979
|
* @param {?string} name
|
|
1641
1980
|
* @param {?number} channelsCount
|
|
1642
1981
|
* @param {Map.<string, string>} closedCaptions
|
|
1643
1982
|
* @param {?string} characteristics
|
|
1644
1983
|
* @param {boolean} forced
|
|
1984
|
+
* @param {?number} sampleRate
|
|
1645
1985
|
* @param {boolean} spatialAudio
|
|
1646
1986
|
* @return {!shaka.hls.HlsParser.StreamInfo}
|
|
1647
1987
|
* @private
|
|
1648
1988
|
*/
|
|
1649
|
-
createStreamInfo_(verbatimMediaPlaylistUri, codecs, type,
|
|
1989
|
+
createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, languageValue,
|
|
1650
1990
|
primary, name, channelsCount, closedCaptions, characteristics, forced,
|
|
1651
|
-
spatialAudio) {
|
|
1991
|
+
sampleRate, spatialAudio) {
|
|
1652
1992
|
// TODO: Refactor, too many parameters
|
|
1653
1993
|
const initialMediaPlaylistUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
1654
1994
|
this.masterPlaylistUri_, verbatimMediaPlaylistUri);
|
|
@@ -1656,11 +1996,9 @@ shaka.hls.HlsParser = class {
|
|
|
1656
1996
|
// This stream is lazy-loaded inside the createSegmentIndex function.
|
|
1657
1997
|
// So we start out with a stream object that does not contain the actual
|
|
1658
1998
|
// segment index, then download when createSegmentIndex is called.
|
|
1659
|
-
const stream = this.makeStreamObject_(codecs, type,
|
|
1660
|
-
channelsCount, closedCaptions, characteristics, forced,
|
|
1661
|
-
|
|
1662
|
-
stream.codecs = '';
|
|
1663
|
-
}
|
|
1999
|
+
const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
|
|
2000
|
+
name, channelsCount, closedCaptions, characteristics, forced,
|
|
2001
|
+
sampleRate, spatialAudio);
|
|
1664
2002
|
const streamInfo = {
|
|
1665
2003
|
stream,
|
|
1666
2004
|
type,
|
|
@@ -1671,8 +2009,11 @@ shaka.hls.HlsParser = class {
|
|
|
1671
2009
|
maxTimestamp: 0,
|
|
1672
2010
|
mediaSequenceToStartTime: new Map(),
|
|
1673
2011
|
canSkipSegments: false,
|
|
2012
|
+
canBlockReload: false,
|
|
1674
2013
|
hasEndList: false,
|
|
1675
2014
|
firstSequenceNumber: -1,
|
|
2015
|
+
nextMediaSequence: -1,
|
|
2016
|
+
nextPart: -1,
|
|
1676
2017
|
loadedOnce: false,
|
|
1677
2018
|
};
|
|
1678
2019
|
|
|
@@ -1680,7 +2021,7 @@ shaka.hls.HlsParser = class {
|
|
|
1680
2021
|
const downloadSegmentIndex = async (abortSignal) => {
|
|
1681
2022
|
// Download the actual manifest.
|
|
1682
2023
|
const response = await this.requestManifest_(
|
|
1683
|
-
streamInfo.absoluteMediaPlaylistUri);
|
|
2024
|
+
streamInfo.absoluteMediaPlaylistUri, /* isPlaylist= */ true);
|
|
1684
2025
|
if (abortSignal.aborted) {
|
|
1685
2026
|
return;
|
|
1686
2027
|
}
|
|
@@ -1697,8 +2038,8 @@ shaka.hls.HlsParser = class {
|
|
|
1697
2038
|
const wasLive = this.isLive_();
|
|
1698
2039
|
const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
|
|
1699
2040
|
playlist, verbatimMediaPlaylistUri, absoluteMediaPlaylistUri, codecs,
|
|
1700
|
-
type,
|
|
1701
|
-
characteristics, forced, spatialAudio);
|
|
2041
|
+
type, languageValue, primary, name, channelsCount, closedCaptions,
|
|
2042
|
+
characteristics, forced, sampleRate, spatialAudio);
|
|
1702
2043
|
if (abortSignal.aborted) {
|
|
1703
2044
|
return;
|
|
1704
2045
|
}
|
|
@@ -1716,18 +2057,19 @@ shaka.hls.HlsParser = class {
|
|
|
1716
2057
|
streamInfo.minTimestamp = realStreamInfo.minTimestamp;
|
|
1717
2058
|
streamInfo.maxTimestamp = realStreamInfo.maxTimestamp;
|
|
1718
2059
|
streamInfo.canSkipSegments = realStreamInfo.canSkipSegments;
|
|
2060
|
+
streamInfo.canBlockReload = realStreamInfo.canBlockReload;
|
|
1719
2061
|
streamInfo.hasEndList = realStreamInfo.hasEndList;
|
|
1720
2062
|
streamInfo.mediaSequenceToStartTime =
|
|
1721
2063
|
realStreamInfo.mediaSequenceToStartTime;
|
|
2064
|
+
streamInfo.nextMediaSequence = realStreamInfo.nextMediaSequence;
|
|
2065
|
+
streamInfo.nextPart = realStreamInfo.nextPart;
|
|
1722
2066
|
streamInfo.loadedOnce = true;
|
|
1723
2067
|
stream.segmentIndex = realStream.segmentIndex;
|
|
1724
2068
|
stream.encrypted = realStream.encrypted;
|
|
1725
2069
|
stream.drmInfos = realStream.drmInfos;
|
|
1726
2070
|
stream.keyIds = realStream.keyIds;
|
|
1727
2071
|
stream.mimeType = realStream.mimeType;
|
|
1728
|
-
|
|
1729
|
-
stream.codecs = '';
|
|
1730
|
-
}
|
|
2072
|
+
stream.bandwidth = realStream.bandwidth;
|
|
1731
2073
|
|
|
1732
2074
|
// Since we lazy-loaded this content, the player may need to create new
|
|
1733
2075
|
// sessions for the DRM info in this stream.
|
|
@@ -1742,10 +2084,6 @@ shaka.hls.HlsParser = class {
|
|
|
1742
2084
|
// To aid manifest filtering, assume before loading that all video
|
|
1743
2085
|
// renditions have the same MIME type. (And likewise for audio.)
|
|
1744
2086
|
otherStreamInfo.stream.mimeType = realStream.mimeType;
|
|
1745
|
-
if (shaka.media.MediaSourceEngine.RAW_FORMATS
|
|
1746
|
-
.includes(otherStreamInfo.stream.mimeType)) {
|
|
1747
|
-
otherStreamInfo.stream.codecs = '';
|
|
1748
|
-
}
|
|
1749
2087
|
}
|
|
1750
2088
|
}
|
|
1751
2089
|
}
|
|
@@ -1775,7 +2113,7 @@ shaka.hls.HlsParser = class {
|
|
|
1775
2113
|
this.determineDuration_();
|
|
1776
2114
|
// Finally, start the update timer, if this asset has been determined
|
|
1777
2115
|
// to be a livestream.
|
|
1778
|
-
const delay = this.
|
|
2116
|
+
const delay = this.getUpdatePlaylistDelay_();
|
|
1779
2117
|
if (delay > 0) {
|
|
1780
2118
|
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
1781
2119
|
}
|
|
@@ -1892,13 +2230,6 @@ shaka.hls.HlsParser = class {
|
|
|
1892
2230
|
streamInfo.stream.segmentIndex.fit(/* periodStart= */ 0, minDuration);
|
|
1893
2231
|
}
|
|
1894
2232
|
}
|
|
1895
|
-
// MediaSource expects no codec strings combined with raw formats.
|
|
1896
|
-
for (const streamInfo of streamInfos) {
|
|
1897
|
-
const stream = streamInfo.stream;
|
|
1898
|
-
if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
|
|
1899
|
-
stream.codecs = '';
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
2233
|
this.notifySegmentsForStreams_(streamInfos.map((s) => s.stream));
|
|
1903
2234
|
if (this.config_.hls.ignoreManifestProgramDateTime) {
|
|
1904
2235
|
this.syncStreamsWithSequenceNumber_(streamInfos);
|
|
@@ -1945,22 +2276,23 @@ shaka.hls.HlsParser = class {
|
|
|
1945
2276
|
* @param {string} absoluteMediaPlaylistUri
|
|
1946
2277
|
* @param {string} codecs
|
|
1947
2278
|
* @param {string} type
|
|
1948
|
-
* @param {string}
|
|
2279
|
+
* @param {?string} languageValue
|
|
1949
2280
|
* @param {boolean} primary
|
|
1950
2281
|
* @param {?string} name
|
|
1951
2282
|
* @param {?number} channelsCount
|
|
1952
2283
|
* @param {Map.<string, string>} closedCaptions
|
|
1953
2284
|
* @param {?string} characteristics
|
|
1954
2285
|
* @param {boolean} forced
|
|
2286
|
+
* @param {?number} sampleRate
|
|
1955
2287
|
* @param {boolean} spatialAudio
|
|
1956
2288
|
* @param {(string|undefined)} mimeType
|
|
1957
2289
|
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
1958
2290
|
* @private
|
|
1959
2291
|
*/
|
|
1960
2292
|
async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
|
|
1961
|
-
absoluteMediaPlaylistUri, codecs, type,
|
|
1962
|
-
channelsCount, closedCaptions, characteristics, forced,
|
|
1963
|
-
mimeType = undefined) {
|
|
2293
|
+
absoluteMediaPlaylistUri, codecs, type, languageValue, primary, name,
|
|
2294
|
+
channelsCount, closedCaptions, characteristics, forced, sampleRate,
|
|
2295
|
+
spatialAudio, mimeType = undefined) {
|
|
1964
2296
|
if (playlist.type != shaka.hls.PlaylistType.MEDIA) {
|
|
1965
2297
|
// EXT-X-MEDIA and EXT-X-IMAGE-STREAM-INF tags should point to media
|
|
1966
2298
|
// playlists.
|
|
@@ -1981,6 +2313,10 @@ shaka.hls.HlsParser = class {
|
|
|
1981
2313
|
|
|
1982
2314
|
this.determinePresentationType_(playlist);
|
|
1983
2315
|
|
|
2316
|
+
if (this.isLive_()) {
|
|
2317
|
+
this.determineLastTargetDuration_(playlist);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
1984
2320
|
if (!mimeType) {
|
|
1985
2321
|
mimeType = await this.guessMimeType_(type, codecs, playlist,
|
|
1986
2322
|
mediaVariables);
|
|
@@ -1998,18 +2334,18 @@ shaka.hls.HlsParser = class {
|
|
|
1998
2334
|
|
|
1999
2335
|
const mediaSequenceToStartTime = this.isLive_() ?
|
|
2000
2336
|
this.mediaSequenceToStartTimeByType_.get(type) : new Map();
|
|
2001
|
-
const
|
|
2002
|
-
|
|
2003
|
-
|
|
2337
|
+
const {segments, bandwidth} = this.createSegments_(
|
|
2338
|
+
verbatimMediaPlaylistUri, playlist, type, mimeType,
|
|
2339
|
+
mediaSequenceToStartTime, mediaVariables);
|
|
2004
2340
|
|
|
2005
|
-
// This
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
this.determinePresentationType_(playlist);
|
|
2341
|
+
// This new calculation is necessary for Low Latency streams.
|
|
2342
|
+
if (this.isLive_()) {
|
|
2343
|
+
this.determineLastTargetDuration_(playlist);
|
|
2009
2344
|
}
|
|
2010
2345
|
|
|
2011
2346
|
const firstStartTime = segments[0].startTime;
|
|
2012
|
-
const
|
|
2347
|
+
const lastSegment = segments[segments.length - 1];
|
|
2348
|
+
const lastEndTime = lastSegment.endTime;
|
|
2013
2349
|
/** @type {!shaka.media.SegmentIndex} */
|
|
2014
2350
|
const segmentIndex = new shaka.media.SegmentIndex(segments);
|
|
2015
2351
|
|
|
@@ -2017,14 +2353,24 @@ shaka.hls.HlsParser = class {
|
|
|
2017
2353
|
playlist.tags, 'EXT-X-SERVER-CONTROL');
|
|
2018
2354
|
const canSkipSegments = serverControlTag ?
|
|
2019
2355
|
serverControlTag.getAttribute('CAN-SKIP-UNTIL') != null : false;
|
|
2356
|
+
const canBlockReload = serverControlTag ?
|
|
2357
|
+
serverControlTag.getAttribute('CAN-BLOCK-RELOAD') != null : false;
|
|
2020
2358
|
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2359
|
+
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2360
|
+
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
|
|
2361
|
+
|
|
2362
|
+
const {nextMediaSequence, nextPart} =
|
|
2363
|
+
this.getNextMediaSequenceAndPart_(mediaSequenceNumber, segments);
|
|
2364
|
+
|
|
2365
|
+
const stream = this.makeStreamObject_(codecs, type, languageValue, primary,
|
|
2366
|
+
name, channelsCount, closedCaptions, characteristics, forced,
|
|
2367
|
+
sampleRate, spatialAudio);
|
|
2023
2368
|
stream.segmentIndex = segmentIndex;
|
|
2024
2369
|
stream.encrypted = encrypted;
|
|
2025
2370
|
stream.drmInfos = drmInfos;
|
|
2026
2371
|
stream.keyIds = keyIds;
|
|
2027
2372
|
stream.mimeType = mimeType;
|
|
2373
|
+
stream.bandwidth = bandwidth;
|
|
2028
2374
|
|
|
2029
2375
|
return {
|
|
2030
2376
|
stream,
|
|
@@ -2034,13 +2380,60 @@ shaka.hls.HlsParser = class {
|
|
|
2034
2380
|
minTimestamp: firstStartTime,
|
|
2035
2381
|
maxTimestamp: lastEndTime,
|
|
2036
2382
|
canSkipSegments,
|
|
2383
|
+
canBlockReload,
|
|
2037
2384
|
hasEndList: false,
|
|
2038
2385
|
firstSequenceNumber: -1,
|
|
2386
|
+
nextMediaSequence,
|
|
2387
|
+
nextPart,
|
|
2039
2388
|
mediaSequenceToStartTime,
|
|
2040
2389
|
loadedOnce: false,
|
|
2041
2390
|
};
|
|
2042
2391
|
}
|
|
2043
2392
|
|
|
2393
|
+
/**
|
|
2394
|
+
* Get the next msn and part
|
|
2395
|
+
*
|
|
2396
|
+
* @param {number} mediaSequenceNumber
|
|
2397
|
+
* @param {!Array.<!shaka.media.SegmentReference>} segments
|
|
2398
|
+
* @return {{nextMediaSequence: number, nextPart:number}}}
|
|
2399
|
+
* @private
|
|
2400
|
+
*/
|
|
2401
|
+
getNextMediaSequenceAndPart_(mediaSequenceNumber, segments) {
|
|
2402
|
+
const currentMediaSequence = mediaSequenceNumber + segments.length - 1;
|
|
2403
|
+
let nextMediaSequence = currentMediaSequence;
|
|
2404
|
+
let nextPart = -1;
|
|
2405
|
+
if (!segments.length) {
|
|
2406
|
+
nextMediaSequence++;
|
|
2407
|
+
return {
|
|
2408
|
+
nextMediaSequence,
|
|
2409
|
+
nextPart,
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
const lastSegment = segments[segments.length - 1];
|
|
2413
|
+
const partialReferences = lastSegment.partialReferences;
|
|
2414
|
+
if (!lastSegment.partialReferences.length) {
|
|
2415
|
+
nextMediaSequence++;
|
|
2416
|
+
if (lastSegment.hasByterangeOptimization()) {
|
|
2417
|
+
nextPart = 0;
|
|
2418
|
+
}
|
|
2419
|
+
return {
|
|
2420
|
+
nextMediaSequence,
|
|
2421
|
+
nextPart,
|
|
2422
|
+
};
|
|
2423
|
+
}
|
|
2424
|
+
nextPart = partialReferences.length - 1;
|
|
2425
|
+
const lastPartialReference =
|
|
2426
|
+
partialReferences[partialReferences.length - 1];
|
|
2427
|
+
if (!lastPartialReference.isPreload()) {
|
|
2428
|
+
nextMediaSequence++;
|
|
2429
|
+
nextPart = 0;
|
|
2430
|
+
}
|
|
2431
|
+
return {
|
|
2432
|
+
nextMediaSequence,
|
|
2433
|
+
nextPart,
|
|
2434
|
+
};
|
|
2435
|
+
}
|
|
2436
|
+
|
|
2044
2437
|
|
|
2045
2438
|
/**
|
|
2046
2439
|
* Creates a stream object with the given parameters.
|
|
@@ -2049,19 +2442,20 @@ shaka.hls.HlsParser = class {
|
|
|
2049
2442
|
* manually on the object after creation.
|
|
2050
2443
|
* @param {string} codecs
|
|
2051
2444
|
* @param {string} type
|
|
2052
|
-
* @param {string}
|
|
2445
|
+
* @param {?string} languageValue
|
|
2053
2446
|
* @param {boolean} primary
|
|
2054
2447
|
* @param {?string} name
|
|
2055
2448
|
* @param {?number} channelsCount
|
|
2056
2449
|
* @param {Map.<string, string>} closedCaptions
|
|
2057
2450
|
* @param {?string} characteristics
|
|
2058
2451
|
* @param {boolean} forced
|
|
2452
|
+
* @param {?number} sampleRate
|
|
2059
2453
|
* @param {boolean} spatialAudio
|
|
2060
2454
|
* @return {!shaka.extern.Stream}
|
|
2061
2455
|
* @private
|
|
2062
2456
|
*/
|
|
2063
|
-
makeStreamObject_(codecs, type,
|
|
2064
|
-
closedCaptions, characteristics, forced, spatialAudio) {
|
|
2457
|
+
makeStreamObject_(codecs, type, languageValue, primary, name, channelsCount,
|
|
2458
|
+
closedCaptions, characteristics, forced, sampleRate, spatialAudio) {
|
|
2065
2459
|
// Fill out a "best-guess" mimeType, for now. It will be replaced once the
|
|
2066
2460
|
// stream is lazy-loaded.
|
|
2067
2461
|
const mimeType = this.guessMimeTypeBeforeLoading_(type, codecs) ||
|
|
@@ -2090,11 +2484,13 @@ shaka.hls.HlsParser = class {
|
|
|
2090
2484
|
segmentIndex: null,
|
|
2091
2485
|
mimeType,
|
|
2092
2486
|
codecs,
|
|
2093
|
-
kind
|
|
2487
|
+
kind: (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
|
|
2488
|
+
shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined,
|
|
2094
2489
|
encrypted: false,
|
|
2095
2490
|
drmInfos: [],
|
|
2096
2491
|
keyIds: new Set(),
|
|
2097
|
-
language,
|
|
2492
|
+
language: this.getLanguage_(languageValue),
|
|
2493
|
+
originalLanguage: languageValue,
|
|
2098
2494
|
label: name, // For historical reasons, since before "originalId".
|
|
2099
2495
|
type,
|
|
2100
2496
|
primary,
|
|
@@ -2109,11 +2505,12 @@ shaka.hls.HlsParser = class {
|
|
|
2109
2505
|
roles,
|
|
2110
2506
|
forced,
|
|
2111
2507
|
channelsCount,
|
|
2112
|
-
audioSamplingRate:
|
|
2508
|
+
audioSamplingRate: sampleRate,
|
|
2113
2509
|
spatialAudio,
|
|
2114
2510
|
closedCaptions,
|
|
2115
2511
|
hdr: undefined,
|
|
2116
2512
|
tilesLayout: undefined,
|
|
2513
|
+
accessibilityPurpose: null,
|
|
2117
2514
|
external: false,
|
|
2118
2515
|
};
|
|
2119
2516
|
}
|
|
@@ -2284,43 +2681,89 @@ shaka.hls.HlsParser = class {
|
|
|
2284
2681
|
} else {
|
|
2285
2682
|
this.setPresentationType_(PresentationType.EVENT);
|
|
2286
2683
|
}
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
|
|
2687
|
+
|
|
2688
|
+
/**
|
|
2689
|
+
* @param {!shaka.hls.Playlist} playlist
|
|
2690
|
+
* @private
|
|
2691
|
+
*/
|
|
2692
|
+
determineLastTargetDuration_(playlist) {
|
|
2693
|
+
let lastTargetDuration = Infinity;
|
|
2694
|
+
const segments = playlist.segments;
|
|
2695
|
+
if (segments.length) {
|
|
2696
|
+
let segmentIndex = segments.length - 1;
|
|
2697
|
+
while (segmentIndex >= 0) {
|
|
2698
|
+
const segment = segments[segmentIndex];
|
|
2699
|
+
const extinfTag =
|
|
2700
|
+
shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXTINF');
|
|
2701
|
+
if (extinfTag) {
|
|
2702
|
+
// The EXTINF tag format is '#EXTINF:<duration>,[<title>]'.
|
|
2703
|
+
// We're interested in the duration part.
|
|
2704
|
+
const extinfValues = extinfTag.value.split(',');
|
|
2705
|
+
lastTargetDuration = Number(extinfValues[0]);
|
|
2706
|
+
break;
|
|
2707
|
+
}
|
|
2708
|
+
segmentIndex--;
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2287
2711
|
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2712
|
+
const targetDurationTag = this.getRequiredTag_(playlist.tags,
|
|
2713
|
+
'EXT-X-TARGETDURATION');
|
|
2714
|
+
const targetDuration = Number(targetDurationTag.value);
|
|
2715
|
+
const partialTargetDurationTag =
|
|
2716
|
+
shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-PART-INF');
|
|
2717
|
+
if (partialTargetDurationTag) {
|
|
2718
|
+
this.partialTargetDuration_ = Number(
|
|
2719
|
+
partialTargetDurationTag.getRequiredAttrValue('PART-TARGET'));
|
|
2720
|
+
}
|
|
2721
|
+
// Get the server-recommended min distance from the live edge.
|
|
2722
|
+
const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2723
|
+
playlist.tags, 'EXT-X-SERVER-CONTROL');
|
|
2724
|
+
// According to the HLS spec, updates should not happen more often than
|
|
2725
|
+
// once in targetDuration. It also requires us to only update the active
|
|
2726
|
+
// variant. We might implement that later, but for now every variant
|
|
2727
|
+
// will be updated. To get the update period, choose the smallest
|
|
2728
|
+
// targetDuration value across all playlists.
|
|
2729
|
+
// 1. Update the shortest one to use as update period and segment
|
|
2730
|
+
// availability time (for LIVE).
|
|
2731
|
+
if (this.lowLatencyMode_ && this.partialTargetDuration_) {
|
|
2732
|
+
// For low latency streaming, use the partial segment target duration.
|
|
2733
|
+
if (this.lowLatencyByterangeOptimization_) {
|
|
2734
|
+
// We always have at least 1 partial segment part, and most servers
|
|
2735
|
+
// allow you to make a request with _HLS_msn=X&_HLS_part=0 with a
|
|
2736
|
+
// distance of 4 partial segments. With this we ensure that we
|
|
2737
|
+
// obtain the minimum latency in this type of case.
|
|
2738
|
+
if (this.partialTargetDuration_ * 5 <= lastTargetDuration) {
|
|
2739
|
+
this.lastTargetDuration_ = Math.min(
|
|
2740
|
+
this.partialTargetDuration_, this.lastTargetDuration_);
|
|
2741
|
+
} else {
|
|
2742
|
+
this.lastTargetDuration_ = Math.min(
|
|
2743
|
+
lastTargetDuration, this.lastTargetDuration_);
|
|
2744
|
+
}
|
|
2312
2745
|
} else {
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
targetDuration, this.minTargetDuration_);
|
|
2746
|
+
this.lastTargetDuration_ = Math.min(
|
|
2747
|
+
this.partialTargetDuration_, this.lastTargetDuration_);
|
|
2316
2748
|
}
|
|
2317
|
-
//
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2749
|
+
// Use 'PART-HOLD-BACK' as the presentation delay for low latency mode.
|
|
2750
|
+
this.lowLatencyPresentationDelay_ = serverControlTag ? Number(
|
|
2751
|
+
serverControlTag.getRequiredAttrValue('PART-HOLD-BACK')) : 0;
|
|
2752
|
+
} else {
|
|
2753
|
+
this.lastTargetDuration_ = Math.min(
|
|
2754
|
+
lastTargetDuration, this.lastTargetDuration_);
|
|
2755
|
+
// Use 'HOLD-BACK' as the presentation delay for default if defined.
|
|
2756
|
+
const holdBack = serverControlTag ?
|
|
2757
|
+
serverControlTag.getAttribute('HOLD-BACK') : null;
|
|
2758
|
+
this.presentationDelay_ = holdBack ? Number(holdBack.value) : 0;
|
|
2321
2759
|
}
|
|
2760
|
+
// 2. Update the longest target duration if need be to use as a
|
|
2761
|
+
// presentation delay later.
|
|
2762
|
+
this.maxTargetDuration_ = Math.max(
|
|
2763
|
+
targetDuration, this.maxTargetDuration_);
|
|
2322
2764
|
}
|
|
2323
2765
|
|
|
2766
|
+
|
|
2324
2767
|
/**
|
|
2325
2768
|
* @private
|
|
2326
2769
|
*/
|
|
@@ -2344,6 +2787,8 @@ shaka.hls.HlsParser = class {
|
|
|
2344
2787
|
presentationDelay = this.config_.defaultPresentationDelay;
|
|
2345
2788
|
} else if (this.lowLatencyPresentationDelay_) {
|
|
2346
2789
|
presentationDelay = this.lowLatencyPresentationDelay_;
|
|
2790
|
+
} else if (this.presentationDelay_) {
|
|
2791
|
+
presentationDelay = this.presentationDelay_;
|
|
2347
2792
|
} else {
|
|
2348
2793
|
const numberOfSegments = this.config_.hls.liveSegmentsDelay;
|
|
2349
2794
|
presentationDelay = this.maxTargetDuration_ * numberOfSegments;
|
|
@@ -2426,6 +2871,7 @@ shaka.hls.HlsParser = class {
|
|
|
2426
2871
|
* @param {!Map.<string, string>} variables
|
|
2427
2872
|
* @param {string} absoluteMediaPlaylistUri
|
|
2428
2873
|
* @param {string} type
|
|
2874
|
+
* @param {string} mimeType
|
|
2429
2875
|
* @param {number} timestampOffset
|
|
2430
2876
|
* @param {shaka.extern.HlsAes128Key=} hlsAes128Key
|
|
2431
2877
|
* @return {shaka.media.SegmentReference}
|
|
@@ -2433,11 +2879,9 @@ shaka.hls.HlsParser = class {
|
|
|
2433
2879
|
*/
|
|
2434
2880
|
createSegmentReference_(
|
|
2435
2881
|
initSegmentReference, previousReference, hlsSegment, startTime,
|
|
2436
|
-
variables, absoluteMediaPlaylistUri, type, timestampOffset,
|
|
2882
|
+
variables, absoluteMediaPlaylistUri, type, mimeType, timestampOffset,
|
|
2437
2883
|
hlsAes128Key) {
|
|
2438
2884
|
const tags = hlsSegment.tags;
|
|
2439
|
-
const absoluteSegmentUri = this.variableSubstitution_(
|
|
2440
|
-
hlsSegment.absoluteUri, variables);
|
|
2441
2885
|
const extinfTag =
|
|
2442
2886
|
shaka.hls.Utils.getFirstTagWithName(tags, 'EXTINF');
|
|
2443
2887
|
|
|
@@ -2483,8 +2927,20 @@ shaka.hls.HlsParser = class {
|
|
|
2483
2927
|
}
|
|
2484
2928
|
|
|
2485
2929
|
// Create SegmentReferences for the partial segments.
|
|
2486
|
-
|
|
2487
|
-
|
|
2930
|
+
let partialSegmentRefs = [];
|
|
2931
|
+
|
|
2932
|
+
// Optimization for LL-HLS with byterange
|
|
2933
|
+
// More info in https://tinyurl.com/hls-open-byte-range
|
|
2934
|
+
let segmentWithByteRangeOptimization = false;
|
|
2935
|
+
let getUrisOptimization = null;
|
|
2936
|
+
let somePartialSegmentWithGap = false;
|
|
2937
|
+
let isPreloadSegment = false;
|
|
2938
|
+
|
|
2939
|
+
if (this.lowLatencyMode_ && hlsSegment.partialSegments.length) {
|
|
2940
|
+
const byterangeOptimizationSupport = (mimeType == 'video/mp4' ||
|
|
2941
|
+
mimeType == 'audio/mp4') && window.ReadableStream;
|
|
2942
|
+
|
|
2943
|
+
let partialSyncTime = syncTime;
|
|
2488
2944
|
for (let i = 0; i < hlsSegment.partialSegments.length; i++) {
|
|
2489
2945
|
const item = hlsSegment.partialSegments[i];
|
|
2490
2946
|
const pPreviousReference = i == 0 ?
|
|
@@ -2512,6 +2968,16 @@ shaka.hls.HlsParser = class {
|
|
|
2512
2968
|
// A preload hinted partial segment may have byterange start info.
|
|
2513
2969
|
const pByterangeStart = item.getAttributeValue('BYTERANGE-START');
|
|
2514
2970
|
pStartByte = pByterangeStart ? Number(pByterangeStart) : 0;
|
|
2971
|
+
// A preload hinted partial segment may have byterange length info.
|
|
2972
|
+
const pByterangeLength = item.getAttributeValue('BYTERANGE-LENGTH');
|
|
2973
|
+
if (pByterangeLength) {
|
|
2974
|
+
pEndByte = pStartByte + Number(pByterangeLength) - 1;
|
|
2975
|
+
} else if (pStartByte) {
|
|
2976
|
+
// If we have a non-zero start byte, but no end byte, follow the
|
|
2977
|
+
// recommendation of https://tinyurl.com/hls-open-byte-range and
|
|
2978
|
+
// set the end byte explicitly to a large integer.
|
|
2979
|
+
pEndByte = Number.MAX_SAFE_INTEGER;
|
|
2980
|
+
}
|
|
2515
2981
|
} else {
|
|
2516
2982
|
const pByterange = item.getAttributeValue('BYTERANGE');
|
|
2517
2983
|
[pStartByte, pEndByte] =
|
|
@@ -2521,20 +2987,33 @@ shaka.hls.HlsParser = class {
|
|
|
2521
2987
|
if (!pUri) {
|
|
2522
2988
|
continue;
|
|
2523
2989
|
}
|
|
2524
|
-
const pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
2525
|
-
absoluteMediaPlaylistUri, pUri);
|
|
2526
2990
|
|
|
2527
2991
|
let partialStatus = shaka.media.SegmentReference.Status.AVAILABLE;
|
|
2528
2992
|
if (item.getAttributeValue('GAP') == 'YES') {
|
|
2529
2993
|
partialStatus = shaka.media.SegmentReference.Status.MISSING;
|
|
2994
|
+
somePartialSegmentWithGap = true;
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2997
|
+
let pAbsoluteUri = null;
|
|
2998
|
+
const getPartialUris = () => {
|
|
2999
|
+
if (pAbsoluteUri == null) {
|
|
3000
|
+
goog.asserts.assert(pUri, 'Partial uri should be defined!');
|
|
3001
|
+
pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
|
|
3002
|
+
absoluteMediaPlaylistUri, pUri);
|
|
3003
|
+
}
|
|
3004
|
+
return [pAbsoluteUri];
|
|
3005
|
+
};
|
|
3006
|
+
|
|
3007
|
+
if (byterangeOptimizationSupport &&
|
|
3008
|
+
pStartByte >= 0 && pEndByte != null) {
|
|
3009
|
+
getUrisOptimization = getPartialUris;
|
|
3010
|
+
segmentWithByteRangeOptimization = true;
|
|
2530
3011
|
}
|
|
2531
3012
|
|
|
2532
|
-
// We do not set the AES-128 key information for partial segments, as we
|
|
2533
|
-
// do not support AES-128 and low-latency at the same time.
|
|
2534
3013
|
const partial = new shaka.media.SegmentReference(
|
|
2535
3014
|
pStartTime,
|
|
2536
3015
|
pEndTime,
|
|
2537
|
-
|
|
3016
|
+
getPartialUris,
|
|
2538
3017
|
pStartByte,
|
|
2539
3018
|
pEndByte,
|
|
2540
3019
|
initSegmentReference,
|
|
@@ -2544,12 +3023,25 @@ shaka.hls.HlsParser = class {
|
|
|
2544
3023
|
/* partialReferences= */ [],
|
|
2545
3024
|
/* tilesLayout= */ '',
|
|
2546
3025
|
/* tileDuration= */ null,
|
|
2547
|
-
|
|
2548
|
-
partialStatus
|
|
3026
|
+
partialSyncTime,
|
|
3027
|
+
partialStatus,
|
|
3028
|
+
hlsAes128Key);
|
|
2549
3029
|
if (item.name == 'EXT-X-PRELOAD-HINT') {
|
|
2550
3030
|
partial.markAsPreload();
|
|
3031
|
+
isPreloadSegment = true;
|
|
3032
|
+
}
|
|
3033
|
+
// The spec doesn't say that we can assume INDEPENDENT=YES for the
|
|
3034
|
+
// first partial segment. It does call the flag "optional", though, and
|
|
3035
|
+
// that cases where there are no such flags on any partial segments, it
|
|
3036
|
+
// is sensible to assume the first one is independent.
|
|
3037
|
+
if (item.getAttributeValue('INDEPENDENT') != 'YES' && i > 0) {
|
|
3038
|
+
partial.markAsNonIndependent();
|
|
2551
3039
|
}
|
|
2552
3040
|
partialSegmentRefs.push(partial);
|
|
3041
|
+
|
|
3042
|
+
if (partialSyncTime) {
|
|
3043
|
+
partialSyncTime += pDuration;
|
|
3044
|
+
}
|
|
2553
3045
|
} // for-loop of hlsSegment.partialSegments
|
|
2554
3046
|
}
|
|
2555
3047
|
|
|
@@ -2574,6 +3066,17 @@ shaka.hls.HlsParser = class {
|
|
|
2574
3066
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
2575
3067
|
}
|
|
2576
3068
|
|
|
3069
|
+
if (segmentWithByteRangeOptimization) {
|
|
3070
|
+
// We cannot optimize segments with gaps, or with a start byte that is
|
|
3071
|
+
// not 0.
|
|
3072
|
+
if (somePartialSegmentWithGap || partialSegmentRefs[0].startByte != 0) {
|
|
3073
|
+
segmentWithByteRangeOptimization = false;
|
|
3074
|
+
getUrisOptimization = null;
|
|
3075
|
+
} else {
|
|
3076
|
+
partialSegmentRefs = [];
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
|
|
2577
3080
|
// If the segment has EXT-X-BYTERANGE tag, set the start byte and end byte
|
|
2578
3081
|
// base on the byterange information. If segment has no EXT-X-BYTERANGE tag
|
|
2579
3082
|
// and has partial segments, set the start byte and end byte base on the
|
|
@@ -2604,10 +3107,22 @@ shaka.hls.HlsParser = class {
|
|
|
2604
3107
|
}
|
|
2605
3108
|
}
|
|
2606
3109
|
|
|
2607
|
-
|
|
3110
|
+
let absoluteSegmentUri = null;
|
|
3111
|
+
const getUris = () => {
|
|
3112
|
+
if (getUrisOptimization) {
|
|
3113
|
+
return getUrisOptimization();
|
|
3114
|
+
}
|
|
3115
|
+
if (absoluteSegmentUri == null) {
|
|
3116
|
+
absoluteSegmentUri = this.variableSubstitution_(
|
|
3117
|
+
hlsSegment.absoluteUri, variables);
|
|
3118
|
+
}
|
|
3119
|
+
return absoluteSegmentUri.length ? [absoluteSegmentUri] : [];
|
|
3120
|
+
};
|
|
3121
|
+
|
|
3122
|
+
const reference = new shaka.media.SegmentReference(
|
|
2608
3123
|
startTime,
|
|
2609
3124
|
endTime,
|
|
2610
|
-
|
|
3125
|
+
getUris,
|
|
2611
3126
|
startByte,
|
|
2612
3127
|
endByte,
|
|
2613
3128
|
initSegmentReference,
|
|
@@ -2621,6 +3136,17 @@ shaka.hls.HlsParser = class {
|
|
|
2621
3136
|
status,
|
|
2622
3137
|
hlsAes128Key,
|
|
2623
3138
|
);
|
|
3139
|
+
|
|
3140
|
+
if (segmentWithByteRangeOptimization) {
|
|
3141
|
+
this.lowLatencyByterangeOptimization_ = true;
|
|
3142
|
+
reference.markAsByterangeOptimization();
|
|
3143
|
+
|
|
3144
|
+
if (isPreloadSegment) {
|
|
3145
|
+
reference.markAsPreload();
|
|
3146
|
+
}
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
return reference;
|
|
2624
3150
|
}
|
|
2625
3151
|
|
|
2626
3152
|
|
|
@@ -2652,7 +3178,9 @@ shaka.hls.HlsParser = class {
|
|
|
2652
3178
|
}
|
|
2653
3179
|
|
|
2654
3180
|
/**
|
|
2655
|
-
* Parses shaka.hls.Segment objects into shaka.media.SegmentReferences
|
|
3181
|
+
* Parses shaka.hls.Segment objects into shaka.media.SegmentReferences and
|
|
3182
|
+
* get the bandwidth necessary for this segments If it's defined in the
|
|
3183
|
+
* playlist.
|
|
2656
3184
|
*
|
|
2657
3185
|
* @param {string} verbatimMediaPlaylistUri
|
|
2658
3186
|
* @param {!shaka.hls.Playlist} playlist
|
|
@@ -2660,7 +3188,8 @@ shaka.hls.HlsParser = class {
|
|
|
2660
3188
|
* @param {string} mimeType
|
|
2661
3189
|
* @param {!Map.<number, number>} mediaSequenceToStartTime
|
|
2662
3190
|
* @param {!Map.<string, string>} variables
|
|
2663
|
-
* @return {!Array.<!shaka.media.SegmentReference
|
|
3191
|
+
* @return {{segments: !Array.<!shaka.media.SegmentReference>,
|
|
3192
|
+
* bandwidth: (number|undefined)}}
|
|
2664
3193
|
* @private
|
|
2665
3194
|
*/
|
|
2666
3195
|
createSegments_(verbatimMediaPlaylistUri, playlist, type, mimeType,
|
|
@@ -2698,6 +3227,9 @@ shaka.hls.HlsParser = class {
|
|
|
2698
3227
|
let previousReference = null;
|
|
2699
3228
|
let lastDiscontinuityStartTime = firstStartTime;
|
|
2700
3229
|
|
|
3230
|
+
/** @type {!Array.<{bitrate: number, duration: number}>} */
|
|
3231
|
+
const bitrates = [];
|
|
3232
|
+
|
|
2701
3233
|
for (let i = 0; i < hlsSegments.length; i++) {
|
|
2702
3234
|
const item = hlsSegments[i];
|
|
2703
3235
|
const startTime =
|
|
@@ -2747,10 +3279,26 @@ shaka.hls.HlsParser = class {
|
|
|
2747
3279
|
variables,
|
|
2748
3280
|
playlist.absoluteUri,
|
|
2749
3281
|
type,
|
|
3282
|
+
mimeType,
|
|
2750
3283
|
lastDiscontinuityStartTime,
|
|
2751
3284
|
hlsAes128Key);
|
|
2752
3285
|
|
|
2753
3286
|
if (reference) {
|
|
3287
|
+
const bitrate = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
3288
|
+
item.tags, 'EXT-X-BITRATE');
|
|
3289
|
+
if (bitrate) {
|
|
3290
|
+
bitrates.push({
|
|
3291
|
+
bitrate,
|
|
3292
|
+
duration: reference.endTime - reference.startTime,
|
|
3293
|
+
});
|
|
3294
|
+
} else if (bitrates.length) {
|
|
3295
|
+
// It applies to every segment between it and the next EXT-X-BITRATE,
|
|
3296
|
+
// so we use the latest bitrate value
|
|
3297
|
+
const prevBitrate = bitrates.pop();
|
|
3298
|
+
prevBitrate.duration += reference.endTime - reference.startTime;
|
|
3299
|
+
bitrates.push(prevBitrate);
|
|
3300
|
+
}
|
|
3301
|
+
|
|
2754
3302
|
previousReference = reference;
|
|
2755
3303
|
reference.discontinuitySequence = discontinuitySequence;
|
|
2756
3304
|
|
|
@@ -2765,6 +3313,16 @@ shaka.hls.HlsParser = class {
|
|
|
2765
3313
|
}
|
|
2766
3314
|
}
|
|
2767
3315
|
|
|
3316
|
+
let bandwidth = undefined;
|
|
3317
|
+
if (bitrates.length) {
|
|
3318
|
+
const duration = bitrates.reduce((sum, value) => {
|
|
3319
|
+
return sum + value.duration;
|
|
3320
|
+
}, 0);
|
|
3321
|
+
bandwidth = Math.round(bitrates.reduce((sum, value) => {
|
|
3322
|
+
return sum + value.bitrate * value.duration;
|
|
3323
|
+
}, 0) / duration * 1000);
|
|
3324
|
+
}
|
|
3325
|
+
|
|
2768
3326
|
// If some segments have sync times, but not all, extrapolate the sync
|
|
2769
3327
|
// times of the ones with none.
|
|
2770
3328
|
const someSyncTime = references.some((ref) => ref.syncTime != null);
|
|
@@ -2850,7 +3408,10 @@ shaka.hls.HlsParser = class {
|
|
|
2850
3408
|
}
|
|
2851
3409
|
}
|
|
2852
3410
|
|
|
2853
|
-
return
|
|
3411
|
+
return {
|
|
3412
|
+
segments: references,
|
|
3413
|
+
bandwidth,
|
|
3414
|
+
};
|
|
2854
3415
|
}
|
|
2855
3416
|
|
|
2856
3417
|
/**
|
|
@@ -2995,9 +3556,9 @@ shaka.hls.HlsParser = class {
|
|
|
2995
3556
|
const headRequest = shaka.net.NetworkingEngine.makeRequest(
|
|
2996
3557
|
[middleSegmentUri], this.config_.retryParameters);
|
|
2997
3558
|
headRequest.method = 'HEAD';
|
|
2998
|
-
|
|
3559
|
+
const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
|
|
2999
3560
|
const response = await this.makeNetworkRequest_(
|
|
3000
|
-
headRequest, requestType);
|
|
3561
|
+
headRequest, requestType, {type});
|
|
3001
3562
|
|
|
3002
3563
|
const contentMimeType = response.headers['content-type'];
|
|
3003
3564
|
if (contentMimeType) {
|
|
@@ -3051,19 +3612,20 @@ shaka.hls.HlsParser = class {
|
|
|
3051
3612
|
* with the resulting data.
|
|
3052
3613
|
*
|
|
3053
3614
|
* @param {string} absoluteUri
|
|
3615
|
+
* @param {boolean=} isPlaylist
|
|
3054
3616
|
* @return {!Promise.<!shaka.extern.Response>}
|
|
3055
3617
|
* @private
|
|
3056
3618
|
*/
|
|
3057
|
-
requestManifest_(absoluteUri) {
|
|
3619
|
+
requestManifest_(absoluteUri, isPlaylist) {
|
|
3058
3620
|
const requestType = shaka.net.NetworkingEngine.RequestType.MANIFEST;
|
|
3059
3621
|
|
|
3060
3622
|
const request = shaka.net.NetworkingEngine.makeRequest(
|
|
3061
3623
|
[absoluteUri], this.config_.retryParameters);
|
|
3062
3624
|
|
|
3063
|
-
const
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
return this.makeNetworkRequest_(request, requestType);
|
|
3625
|
+
const type = isPlaylist ?
|
|
3626
|
+
shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_PLAYLIST :
|
|
3627
|
+
shaka.net.NetworkingEngine.AdvancedRequestType.MASTER_PLAYLIST;
|
|
3628
|
+
return this.makeNetworkRequest_(request, requestType, {type});
|
|
3067
3629
|
}
|
|
3068
3630
|
|
|
3069
3631
|
/**
|
|
@@ -3078,7 +3640,7 @@ shaka.hls.HlsParser = class {
|
|
|
3078
3640
|
shaka.log.info('Updating manifest...');
|
|
3079
3641
|
|
|
3080
3642
|
goog.asserts.assert(
|
|
3081
|
-
this.
|
|
3643
|
+
this.getUpdatePlaylistDelay_() > 0,
|
|
3082
3644
|
'We should only call |onUpdate_| when we are suppose to be updating.');
|
|
3083
3645
|
|
|
3084
3646
|
// Detect a call to stop()
|
|
@@ -3091,7 +3653,7 @@ shaka.hls.HlsParser = class {
|
|
|
3091
3653
|
|
|
3092
3654
|
// This may have converted to VOD, in which case we stop updating.
|
|
3093
3655
|
if (this.isLive_()) {
|
|
3094
|
-
const delay = this.
|
|
3656
|
+
const delay = this.getUpdatePlaylistDelay_();
|
|
3095
3657
|
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
3096
3658
|
}
|
|
3097
3659
|
} catch (error) {
|
|
@@ -3103,6 +3665,11 @@ shaka.hls.HlsParser = class {
|
|
|
3103
3665
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
3104
3666
|
'Should only receive a Shaka error');
|
|
3105
3667
|
|
|
3668
|
+
if (this.config_.raiseFatalErrorOnManifestUpdateRequestFailure) {
|
|
3669
|
+
this.playerInterface_.onError(error);
|
|
3670
|
+
return;
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3106
3673
|
// We will retry updating, so override the severity of the error.
|
|
3107
3674
|
error.severity = shaka.util.Error.Severity.RECOVERABLE;
|
|
3108
3675
|
this.playerInterface_.onError(error);
|
|
@@ -3123,6 +3690,20 @@ shaka.hls.HlsParser = class {
|
|
|
3123
3690
|
}
|
|
3124
3691
|
|
|
3125
3692
|
|
|
3693
|
+
/**
|
|
3694
|
+
* @return {number}
|
|
3695
|
+
* @private
|
|
3696
|
+
*/
|
|
3697
|
+
getUpdatePlaylistDelay_() {
|
|
3698
|
+
// The HLS spec (RFC 8216) states in 6.3.4:
|
|
3699
|
+
// "the client MUST wait for at least the target duration before
|
|
3700
|
+
// attempting to reload the Playlist file again".
|
|
3701
|
+
// For LL-HLS, the server must add a new partial segment to the Playlist
|
|
3702
|
+
// every part target duration.
|
|
3703
|
+
return this.lastTargetDuration_;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
|
|
3126
3707
|
/**
|
|
3127
3708
|
* @param {shaka.hls.HlsParser.PresentationType_} type
|
|
3128
3709
|
* @private
|
|
@@ -3149,10 +3730,11 @@ shaka.hls.HlsParser = class {
|
|
|
3149
3730
|
*
|
|
3150
3731
|
* @param {shaka.extern.Request} request
|
|
3151
3732
|
* @param {shaka.net.NetworkingEngine.RequestType} type
|
|
3733
|
+
* @param {shaka.extern.RequestContext=} context
|
|
3152
3734
|
* @return {!Promise.<shaka.extern.Response>}
|
|
3153
3735
|
* @private
|
|
3154
3736
|
*/
|
|
3155
|
-
makeNetworkRequest_(request, type) {
|
|
3737
|
+
makeNetworkRequest_(request, type, context) {
|
|
3156
3738
|
if (!this.operationManager_) {
|
|
3157
3739
|
throw new shaka.util.Error(
|
|
3158
3740
|
shaka.util.Error.Severity.CRITICAL,
|
|
@@ -3160,7 +3742,8 @@ shaka.hls.HlsParser = class {
|
|
|
3160
3742
|
shaka.util.Error.Code.OPERATION_ABORTED);
|
|
3161
3743
|
}
|
|
3162
3744
|
|
|
3163
|
-
const op = this.playerInterface_.networkingEngine.request(
|
|
3745
|
+
const op = this.playerInterface_.networkingEngine.request(
|
|
3746
|
+
type, request, context);
|
|
3164
3747
|
this.operationManager_.manage(op);
|
|
3165
3748
|
|
|
3166
3749
|
return op.promise;
|
|
@@ -3314,8 +3897,11 @@ shaka.hls.HlsParser = class {
|
|
|
3314
3897
|
* maxTimestamp: number,
|
|
3315
3898
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
3316
3899
|
* canSkipSegments: boolean,
|
|
3900
|
+
* canBlockReload: boolean,
|
|
3317
3901
|
* hasEndList: boolean,
|
|
3318
3902
|
* firstSequenceNumber: number,
|
|
3903
|
+
* nextMediaSequence: number,
|
|
3904
|
+
* nextPart: number,
|
|
3319
3905
|
* loadedOnce: boolean
|
|
3320
3906
|
* }}
|
|
3321
3907
|
*
|
|
@@ -3344,10 +3930,18 @@ shaka.hls.HlsParser = class {
|
|
|
3344
3930
|
* @property {boolean} canSkipSegments
|
|
3345
3931
|
* True if the server supports delta playlist updates, and we can send a
|
|
3346
3932
|
* request for a playlist that can skip older media segments.
|
|
3933
|
+
* @property {boolean} canBlockReload
|
|
3934
|
+
* True if the server supports blocking playlist reload, and we can send a
|
|
3935
|
+
* request for a playlist that can block reload until some segments are
|
|
3936
|
+
* present.
|
|
3347
3937
|
* @property {boolean} hasEndList
|
|
3348
3938
|
* True if the stream has an EXT-X-ENDLIST tag.
|
|
3349
3939
|
* @property {number} firstSequenceNumber
|
|
3350
3940
|
* The sequence number of the first reference. Only calculated if needed.
|
|
3941
|
+
* @property {number} nextMediaSequence
|
|
3942
|
+
* The next media sequence.
|
|
3943
|
+
* @property {number} nextPart
|
|
3944
|
+
* The next part.
|
|
3351
3945
|
* @property {boolean} loadedOnce
|
|
3352
3946
|
* True if the stream has been loaded at least once.
|
|
3353
3947
|
*/
|
|
@@ -3367,6 +3961,30 @@ shaka.hls.HlsParser.StreamInfo;
|
|
|
3367
3961
|
shaka.hls.HlsParser.StreamInfos;
|
|
3368
3962
|
|
|
3369
3963
|
|
|
3964
|
+
/**
|
|
3965
|
+
* @typedef {{
|
|
3966
|
+
* type: string,
|
|
3967
|
+
* mimeType: string,
|
|
3968
|
+
* codecs: string,
|
|
3969
|
+
* language: ?string,
|
|
3970
|
+
* height: ?string,
|
|
3971
|
+
* width: ?string,
|
|
3972
|
+
* channelCount: ?number,
|
|
3973
|
+
* sampleRate: ?number
|
|
3974
|
+
* }}
|
|
3975
|
+
*
|
|
3976
|
+
* @property {string} type
|
|
3977
|
+
* @property {string} mimeType
|
|
3978
|
+
* @property {string} codecs
|
|
3979
|
+
* @property {?string} language
|
|
3980
|
+
* @property {?string} height
|
|
3981
|
+
* @property {?string} width
|
|
3982
|
+
* @property {?number} channelCount
|
|
3983
|
+
* @property {?number} sampleRate
|
|
3984
|
+
*/
|
|
3985
|
+
shaka.hls.HlsParser.BasicInfo;
|
|
3986
|
+
|
|
3987
|
+
|
|
3370
3988
|
/**
|
|
3371
3989
|
* @const {!Object.<string, string>}
|
|
3372
3990
|
* @private
|
|
@@ -3488,13 +4106,9 @@ shaka.hls.HlsParser.PresentationType_ = {
|
|
|
3488
4106
|
LIVE: 'LIVE',
|
|
3489
4107
|
};
|
|
3490
4108
|
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
'application/x-mpegurl', () => new shaka.hls.HlsParser());
|
|
3498
|
-
shaka.media.ManifestParser.registerParserByMime(
|
|
3499
|
-
'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
|
|
3500
|
-
}
|
|
4109
|
+
shaka.media.ManifestParser.registerParserByExtension(
|
|
4110
|
+
'm3u8', () => new shaka.hls.HlsParser());
|
|
4111
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
4112
|
+
'application/x-mpegurl', () => new shaka.hls.HlsParser());
|
|
4113
|
+
shaka.media.ManifestParser.registerParserByMime(
|
|
4114
|
+
'application/vnd.apple.mpegurl', () => new shaka.hls.HlsParser());
|