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