shaka-player 4.4.3 → 4.6.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/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/.github/workflows/release-please.yaml +2 -27
- package/AUTHORS +2 -0
- package/CHANGELOG.md +156 -39
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTORS +2 -0
- package/README.md +54 -42
- package/demo/asset_card.js +18 -21
- package/demo/cast_receiver/index.html +0 -1
- package/demo/close_button.js +0 -3
- package/demo/common/asset.js +33 -22
- package/demo/common/assets.js +249 -60
- package/demo/config.js +219 -253
- package/demo/custom.js +88 -133
- package/demo/front.js +7 -10
- package/demo/index.html +19 -22
- package/demo/input.js +6 -11
- package/demo/input_container.js +8 -9
- package/demo/main.js +62 -126
- package/demo/search.js +29 -34
- package/demo/service_worker.js +0 -3
- package/demo/tooltip.js +3 -5
- package/demo/visualizer.js +3 -5
- package/demo/visualizer_button.js +1 -20
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +211 -228
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +211 -228
- package/dist/demo.compiled.map +4 -4
- package/dist/deps.js +42 -37
- package/dist/locales.js +34 -30
- package/dist/receiver.compiled.debug.js +64 -77
- package/dist/receiver.compiled.debug.map +4 -4
- package/dist/receiver.compiled.js +64 -77
- package/dist/receiver.compiled.map +4 -4
- package/dist/shaka-player.compiled.d.ts +343 -151
- package/dist/shaka-player.compiled.debug.d.ts +343 -151
- package/dist/shaka-player.compiled.debug.externs.js +356 -117
- package/dist/shaka-player.compiled.debug.js +952 -811
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +356 -117
- package/dist/shaka-player.compiled.js +1165 -1078
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +359 -153
- package/dist/shaka-player.ui.debug.d.ts +359 -153
- package/dist/shaka-player.ui.debug.externs.js +382 -117
- package/dist/shaka-player.ui.debug.js +1084 -918
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +382 -117
- package/dist/shaka-player.ui.js +1377 -1274
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/lcevc-integration.md +14 -14
- package/docs/tutorials/ad_monetization.md +54 -0
- package/docs/tutorials/basic-usage.md +2 -1
- package/docs/tutorials/errors.md +2 -1
- package/docs/tutorials/faq.md +27 -55
- package/docs/tutorials/index.json +2 -1
- package/docs/tutorials/lcevc.md +118 -0
- package/docs/tutorials/offline.md +10 -8
- package/docs/tutorials/plugins.md +5 -0
- package/docs/tutorials/ui-customization.md +17 -0
- package/docs/tutorials/ui.md +2 -1
- package/docs/tutorials/upgrade.md +3 -0
- package/externs/css_style_declaration.js +19 -0
- package/externs/lcevc.js +37 -13
- package/externs/managedmediasource.js +24 -0
- package/externs/media_remote.js +68 -0
- package/externs/mediatailor.js +193 -0
- package/externs/pictureinpicture.js +11 -0
- package/externs/shaka/abr_manager.js +6 -2
- package/externs/shaka/ads.js +27 -0
- package/externs/shaka/cea.js +7 -0
- package/externs/shaka/codecs.js +5 -3
- package/externs/shaka/manifest.js +31 -10
- package/externs/shaka/manifest_parser.js +24 -1
- package/externs/shaka/net.js +2 -2
- package/externs/shaka/offline.js +13 -2
- package/externs/shaka/player.js +65 -8
- package/externs/shaka/text.js +3 -1
- package/externs/shaka/transmuxer.js +1 -1
- package/karma.conf.js +7 -3
- package/lib/abr/simple_abr_manager.js +66 -9
- package/lib/ads/ad_manager.js +104 -50
- package/lib/ads/media_tailor_ad.js +297 -0
- package/lib/ads/media_tailor_ad_manager.js +839 -0
- package/lib/cast/cast_receiver.js +1 -1
- package/lib/cea/cea_decoder.js +28 -0
- package/lib/cea/dummy_caption_decoder.js +5 -0
- package/lib/cea/ts_cea_parser.js +15 -2
- package/lib/config/codec_switching_strategy.js +24 -0
- package/lib/dash/content_protection.js +120 -2
- package/lib/dash/dash_parser.js +322 -93
- package/lib/dash/mpd_utils.js +62 -37
- package/lib/dash/segment_base.js +17 -10
- package/lib/dash/segment_list.js +18 -9
- package/lib/dash/segment_template.js +122 -107
- package/lib/dependencies/all.js +8 -7
- package/lib/hls/hls_classes.js +28 -43
- package/lib/hls/hls_parser.js +798 -658
- package/lib/hls/hls_utils.js +64 -5
- package/lib/hls/manifest_text_parser.js +7 -11
- package/lib/lcevc/lcevc_dec.js +220 -0
- package/lib/media/adaptation_set.js +32 -14
- package/lib/media/adaptation_set_criteria.js +86 -16
- package/lib/media/closed_caption_parser.js +13 -0
- package/lib/media/content_workarounds.js +6 -0
- package/lib/media/drm_engine.js +8 -13
- package/lib/media/manifest_parser.js +8 -113
- package/lib/media/media_source_capabilities.js +10 -3
- package/lib/media/media_source_engine.js +286 -44
- package/lib/media/play_rate_controller.js +8 -38
- package/lib/media/presentation_timeline.js +7 -1
- package/lib/media/quality_observer.js +1 -1
- package/lib/media/segment_index.js +6 -6
- package/lib/media/segment_prefetch.js +92 -24
- package/lib/media/segment_reference.js +73 -7
- package/lib/media/segment_utils.js +343 -0
- package/lib/media/stall_detector.js +0 -3
- package/lib/media/streaming_engine.js +73 -38
- package/lib/mss/mss_parser.js +19 -2
- package/lib/net/networking_engine.js +67 -12
- package/lib/net/networking_utils.js +95 -0
- package/lib/offline/indexeddb/v1_storage_cell.js +3 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +3 -0
- package/lib/offline/manifest_converter.js +3 -0
- package/lib/offline/offline_manifest_parser.js +10 -0
- package/lib/offline/storage.js +14 -7
- package/lib/player.js +1098 -1537
- package/lib/polyfill/mediasource.js +22 -1
- package/lib/text/cue.js +15 -0
- package/lib/text/mp4_ttml_parser.js +2 -2
- package/lib/text/srt_text_parser.js +2 -2
- package/lib/text/stub_text_displayer.js +51 -0
- package/lib/text/text_engine.js +3 -2
- package/lib/text/ttml_text_parser.js +45 -5
- package/lib/text/ui_text_displayer.js +8 -0
- package/lib/text/vtt_text_parser.js +40 -22
- package/lib/transmuxer/aac_transmuxer.js +1 -1
- package/lib/transmuxer/ac3_transmuxer.js +1 -1
- package/lib/transmuxer/ec3_transmuxer.js +1 -1
- package/lib/transmuxer/h264.js +5 -4
- package/lib/transmuxer/h265.js +863 -0
- package/lib/transmuxer/mp3_transmuxer.js +1 -1
- package/lib/transmuxer/mpeg_ts_transmuxer.js +4 -3
- package/lib/transmuxer/mss_transmuxer.js +1 -1
- package/lib/transmuxer/ts_transmuxer.js +187 -22
- package/lib/util/buffer_utils.js +26 -4
- package/lib/util/content_steering_manager.js +358 -0
- package/lib/util/error.js +30 -15
- package/lib/util/exp_golomb.js +30 -2
- package/lib/util/fairplay_utils.js +1 -1
- package/lib/util/fake_event.js +8 -1
- package/lib/util/id3_utils.js +50 -1
- package/lib/util/manifest_parser_utils.js +12 -8
- package/lib/util/mp4_generator.js +6 -8
- package/lib/util/mutex.js +59 -0
- package/lib/util/object_utils.js +1 -1
- package/lib/util/periods.js +156 -179
- package/lib/util/platform.js +44 -3
- package/lib/util/player_configuration.js +43 -6
- package/lib/util/state_history.js +6 -2
- package/lib/util/stream_utils.js +206 -283
- package/lib/util/ts_parser.js +335 -98
- package/lib/util/uint8array_utils.js +5 -1
- package/lib/util/xml_utils.js +5 -0
- package/maintained-branches.md +3 -2
- package/package.json +4 -6
- package/roadmap.md +25 -5
- package/shaka-player.uncompiled.js +2 -2
- package/support.html +0 -1
- package/test/abr/simple_abr_manager_unit.js +33 -33
- package/test/ads/ad_manager_unit.js +3 -2
- package/test/cast/cast_receiver_integration.js +50 -4
- package/test/cast/cast_utils_unit.js +2 -0
- package/test/cea/cea_decoder_unit.js +7 -0
- package/test/codec_switching/codec_switching_integration.js +182 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_integration.js +74 -0
- package/test/dash/dash_parser_live_unit.js +2 -19
- package/test/dash/dash_parser_manifest_unit.js +536 -2
- package/test/dash/dash_parser_segment_base_unit.js +2 -0
- package/test/dash/dash_parser_segment_list_unit.js +2 -0
- package/test/dash/dash_parser_segment_template_unit.js +2 -0
- package/test/dash/mpd_utils_unit.js +61 -30
- package/test/demo/demo_unit.js +0 -16
- package/test/hls/hls_live_unit.js +14 -3
- package/test/hls/hls_parser_unit.js +385 -33
- package/test/hls/manifest_text_parser_unit.js +32 -99
- package/test/media/adaptation_set_criteria_unit.js +284 -15
- package/test/media/adaptation_set_unit.js +2 -0
- package/test/media/content_workarounds_unit.js +1 -1
- package/test/media/drm_engine_unit.js +3 -3
- package/test/media/media_source_engine_unit.js +166 -2
- package/test/media/play_rate_controller_unit.js +0 -48
- package/test/media/segment_index_unit.js +5 -4
- package/test/media/segment_prefetch_unit.js +1 -1
- package/test/media/streaming_engine_unit.js +17 -17
- package/test/mss/mss_parser_unit.js +2 -0
- package/test/mss/mss_player_integration.js +3 -2
- package/test/net/networking_engine_unit.js +3 -3
- package/test/{media/manifest_parser_unit.js → net/networking_utils_unit.js} +2 -2
- package/test/offline/manifest_convert_unit.js +14 -0
- package/test/offline/offline_integration.js +2 -1
- package/test/offline/storage_integration.js +22 -13
- package/test/player_external.js +4 -2
- package/test/player_integration.js +15 -4
- package/test/player_load_graph_integration.js +47 -151
- package/test/player_src_equals_external.js +1 -0
- package/test/player_src_equals_integration.js +71 -4
- package/test/player_unit.js +182 -158
- package/test/test/assets/dash-aes-128/Fragments(video=0,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=20000000,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=40000000,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=60000000,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=i,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/dash.mpd +18 -0
- package/test/test/assets/dash-aes-128/license +1 -0
- package/test/test/assets/dash-multi-codec/audio_en_2c_128k_aac.mp4 +0 -0
- package/test/test/assets/dash-multi-codec/audio_es_2c_64k_opus.webm +0 -0
- package/test/test/assets/dash-multi-codec/dash.mpd +32 -0
- package/test/test/assets/dash-multi-codec/video_144p_108k_h264.mp4 +0 -0
- package/test/test/assets/hls-ts-h265/0.ts +0 -0
- package/test/test/assets/hls-ts-h265/hevc.m3u8 +6 -0
- package/test/test/assets/hls-ts-h265/index.m3u8 +7 -0
- package/test/test/assets/hls-ts-muxed-aac-h265/0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h265/1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h265/media.m3u8 +9 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/media.m3u8 +24 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00000.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00001.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00002.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00003.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00004.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00005.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0000.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0001.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0002.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0003.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/prog_index.m3u8 +14 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/index.m3u8 +23 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-1-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-2-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-3-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-4-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-5-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-6-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-7-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-8-v1-a1.ts +0 -0
- package/test/test/util/dash_parser_util.js +4 -0
- package/test/test/util/fake_ad_manager.js +20 -0
- package/test/test/util/fake_media_source_engine.js +13 -2
- package/test/test/util/fake_networking_engine.js +9 -1
- package/test/test/util/fake_segment_prefetch.js +3 -0
- package/test/test/util/manifest_generator.js +11 -3
- package/test/test/util/manifest_parser_util.js +14 -2
- package/test/test/util/mss_parser_util.js +4 -0
- package/test/test/util/offline_utils.js +2 -0
- package/test/test/util/simple_fakes.js +2 -2
- package/test/test/util/streaming_engine_util.js +10 -4
- package/test/test/util/test_scheme.js +6 -0
- package/test/test/util/ui_utils.js +4 -3
- package/test/text/mp4_ttml_parser_unit.js +4 -4
- package/test/text/srt_text_parser_unit.js +1 -1
- package/test/text/text_displayer_layout_unit.js +1 -1
- package/test/text/text_engine_unit.js +6 -0
- package/test/text/ttml_text_parser_unit.js +85 -2
- package/test/text/vtt_text_parser_unit.js +103 -31
- package/test/transmuxer/transmuxer_engine_integration.js +9 -0
- package/test/transmuxer/transmuxer_integration.js +150 -3
- package/test/ui/ad_ui_unit.js +2 -2
- package/test/ui/ui_customization_unit.js +17 -16
- package/test/ui/ui_integration.js +30 -20
- package/test/ui/ui_unit.js +28 -29
- package/test/util/buffer_utils_unit.js +1 -1
- package/test/util/content_steering_manager_unit.js +427 -0
- package/test/util/id3_utils_unit.js +52 -0
- package/test/util/stream_utils_unit.js +207 -169
- package/test/util/ts_parser_unit.js +3 -3
- package/third_party/closure-uri/uri.js +13 -1
- package/ui/audio_language_selection.js +9 -2
- package/ui/controls.js +125 -0
- package/ui/externs/ui.js +24 -2
- package/ui/language_utils.js +43 -8
- package/ui/less/range_elements.less +33 -0
- package/ui/locales/en.json +1 -0
- package/ui/locales/es-419.json +1 -0
- package/ui/locales/es.json +1 -0
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +10 -0
- package/ui/pip_button.js +22 -120
- package/ui/remote_button.js +203 -0
- package/ui/resolution_selection.js +47 -10
- package/ui/seek_bar.js +296 -3
- package/ui/text_selection.js +2 -1
- package/ui/ui.js +11 -4
- package/demo/common/message_ids.js +0 -309
- package/demo/locales/en.json +0 -288
- package/demo/locales/source.json +0 -1146
- package/externs/mux.js +0 -69
- package/lib/lcevc/lcevc_dil.js +0 -152
- package/lib/routing/node.js +0 -23
- package/lib/routing/payload.js +0 -43
- package/lib/routing/walker.js +0 -587
- package/lib/transmuxer/muxjs_transmuxer.js +0 -242
- package/test/routing/walker_unit.js +0 -504
- package/test/transmuxer/muxjs_transmuxer_integration.js +0 -177
package/lib/player.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
goog.provide('shaka.Player');
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
|
-
goog.require('shaka.Deprecate');
|
|
11
10
|
goog.require('shaka.config.AutoShowText');
|
|
11
|
+
goog.require('shaka.Deprecate');
|
|
12
12
|
goog.require('shaka.log');
|
|
13
13
|
goog.require('shaka.media.AdaptationSetCriteria');
|
|
14
14
|
goog.require('shaka.media.BufferingObserver');
|
|
@@ -31,15 +31,16 @@ goog.require('shaka.media.SrcEqualsPlayhead');
|
|
|
31
31
|
goog.require('shaka.media.StreamingEngine');
|
|
32
32
|
goog.require('shaka.media.TimeRangesUtils');
|
|
33
33
|
goog.require('shaka.net.NetworkingEngine');
|
|
34
|
-
goog.require('shaka.
|
|
34
|
+
goog.require('shaka.net.NetworkingUtils');
|
|
35
35
|
goog.require('shaka.text.SimpleTextDisplayer');
|
|
36
|
+
goog.require('shaka.text.StubTextDisplayer');
|
|
36
37
|
goog.require('shaka.text.TextEngine');
|
|
37
38
|
goog.require('shaka.text.UITextDisplayer');
|
|
38
39
|
goog.require('shaka.text.WebVttGenerator');
|
|
39
|
-
goog.require('shaka.util.AbortableOperation');
|
|
40
40
|
goog.require('shaka.util.BufferUtils');
|
|
41
41
|
goog.require('shaka.util.CmcdManager');
|
|
42
42
|
goog.require('shaka.util.ConfigUtils');
|
|
43
|
+
goog.require('shaka.util.Dom');
|
|
43
44
|
goog.require('shaka.util.Error');
|
|
44
45
|
goog.require('shaka.util.EventManager');
|
|
45
46
|
goog.require('shaka.util.FakeEvent');
|
|
@@ -49,6 +50,7 @@ goog.require('shaka.util.LanguageUtils');
|
|
|
49
50
|
goog.require('shaka.util.ManifestParserUtils');
|
|
50
51
|
goog.require('shaka.util.MediaReadyState');
|
|
51
52
|
goog.require('shaka.util.MimeUtils');
|
|
53
|
+
goog.require('shaka.util.Mutex');
|
|
52
54
|
goog.require('shaka.util.ObjectUtils');
|
|
53
55
|
goog.require('shaka.util.Platform');
|
|
54
56
|
goog.require('shaka.util.PlayerConfiguration');
|
|
@@ -56,10 +58,8 @@ goog.require('shaka.util.PublicPromise');
|
|
|
56
58
|
goog.require('shaka.util.Stats');
|
|
57
59
|
goog.require('shaka.util.StreamUtils');
|
|
58
60
|
goog.require('shaka.util.Timer');
|
|
59
|
-
goog.require('shaka.lcevc.
|
|
61
|
+
goog.require('shaka.lcevc.Dec');
|
|
60
62
|
goog.requireType('shaka.media.PresentationTimeline');
|
|
61
|
-
goog.requireType('shaka.routing.Node');
|
|
62
|
-
goog.requireType('shaka.routing.Payload');
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
/**
|
|
@@ -85,18 +85,6 @@ goog.requireType('shaka.routing.Payload');
|
|
|
85
85
|
* @exportDoc
|
|
86
86
|
*/
|
|
87
87
|
|
|
88
|
-
/**
|
|
89
|
-
* @event shaka.Player.StateIdleEvent
|
|
90
|
-
* @description Fired when the player has stopped changing states and will
|
|
91
|
-
* remain idle until a new state change request (e.g. <code>load</code>,
|
|
92
|
-
* <code>attach</code>, etc.) is made.
|
|
93
|
-
* @property {string} type
|
|
94
|
-
* 'onstateidle'
|
|
95
|
-
* @property {string} state
|
|
96
|
-
* The name of the state that the player stopped in.
|
|
97
|
-
* @exportDoc
|
|
98
|
-
*/
|
|
99
|
-
|
|
100
88
|
/**
|
|
101
89
|
* @event shaka.Player.EmsgEvent
|
|
102
90
|
* @description Fired when an emsg box is found in a segment.
|
|
@@ -314,6 +302,18 @@ goog.requireType('shaka.routing.Payload');
|
|
|
314
302
|
*/
|
|
315
303
|
|
|
316
304
|
|
|
305
|
+
/**
|
|
306
|
+
* @event shaka.Player.ManifestUpdatedEvent
|
|
307
|
+
* @description Fired after the manifest has been updated (live streams).
|
|
308
|
+
* @property {string} type
|
|
309
|
+
* 'manifestupdated'
|
|
310
|
+
* @property {boolean} isLive
|
|
311
|
+
* True when the playlist is live. Useful to detect transition from live
|
|
312
|
+
* to static playlist..
|
|
313
|
+
* @exportDoc
|
|
314
|
+
*/
|
|
315
|
+
|
|
316
|
+
|
|
317
317
|
/**
|
|
318
318
|
* @event shaka.Player.MetadataEvent
|
|
319
319
|
* @description Triggers after metadata associated with the stream is found.
|
|
@@ -423,6 +423,76 @@ goog.requireType('shaka.routing.Payload');
|
|
|
423
423
|
*/
|
|
424
424
|
|
|
425
425
|
|
|
426
|
+
/**
|
|
427
|
+
* @event shaka.Player.KeyStatusChanged
|
|
428
|
+
* @description Fired when the key status changed.
|
|
429
|
+
* @property {string} type
|
|
430
|
+
* 'keystatuschanged'
|
|
431
|
+
* @exportDoc
|
|
432
|
+
*/
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @event shaka.Player.StateChanged
|
|
437
|
+
* @description Fired when player state is changed.
|
|
438
|
+
* @property {string} type
|
|
439
|
+
* 'statechanged'
|
|
440
|
+
* @property {string} newstate
|
|
441
|
+
* The new state.
|
|
442
|
+
* @exportDoc
|
|
443
|
+
*/
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* @event shaka.Player.Started
|
|
448
|
+
* @description Fires when the content starts playing.
|
|
449
|
+
* Only for VoD.
|
|
450
|
+
* @property {string} type
|
|
451
|
+
* 'started'
|
|
452
|
+
* @exportDoc
|
|
453
|
+
*/
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* @event shaka.Player.FirstQuartile
|
|
458
|
+
* @description Fires when the content playhead crosses first quartile.
|
|
459
|
+
* Only for VoD.
|
|
460
|
+
* @property {string} type
|
|
461
|
+
* 'firstquartile'
|
|
462
|
+
* @exportDoc
|
|
463
|
+
*/
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @event shaka.Player.Midpoint
|
|
468
|
+
* @description Fires when the content playhead crosses midpoint.
|
|
469
|
+
* Only for VoD.
|
|
470
|
+
* @property {string} type
|
|
471
|
+
* 'midpoint'
|
|
472
|
+
* @exportDoc
|
|
473
|
+
*/
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* @event shaka.Player.ThirdQuartile
|
|
478
|
+
* @description Fires when the content playhead crosses third quartile.
|
|
479
|
+
* Only for VoD.
|
|
480
|
+
* @property {string} type
|
|
481
|
+
* 'thirdquartile'
|
|
482
|
+
* @exportDoc
|
|
483
|
+
*/
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* @event shaka.Player.Complete
|
|
488
|
+
* @description Fires when the content completes playing.
|
|
489
|
+
* Only for VoD.
|
|
490
|
+
* @property {string} type
|
|
491
|
+
* 'complete'
|
|
492
|
+
* @exportDoc
|
|
493
|
+
*/
|
|
494
|
+
|
|
495
|
+
|
|
426
496
|
/**
|
|
427
497
|
* @summary The main player object for Shaka Player.
|
|
428
498
|
*
|
|
@@ -491,6 +561,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
491
561
|
/** @private {shaka.media.Playhead} */
|
|
492
562
|
this.playhead_ = null;
|
|
493
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Incremented whenever a top-level operation (load, attach, etc) is
|
|
566
|
+
* performed.
|
|
567
|
+
* Used to determine if a load operation has been interrupted.
|
|
568
|
+
* @private {number}
|
|
569
|
+
*/
|
|
570
|
+
this.operationId_ = 0;
|
|
571
|
+
|
|
572
|
+
/** @private {!shaka.util.Mutex} */
|
|
573
|
+
this.mutex_ = new shaka.util.Mutex();
|
|
574
|
+
|
|
494
575
|
/**
|
|
495
576
|
* The playhead observers are used to monitor the position of the playhead
|
|
496
577
|
* and some other source of data (e.g. buffered content), and raise events.
|
|
@@ -508,12 +589,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
508
589
|
*/
|
|
509
590
|
this.playRateController_ = null;
|
|
510
591
|
|
|
511
|
-
// We use the buffering observer and timer to track when we move from having
|
|
512
|
-
// enough buffered content to not enough. They only exist when content has
|
|
513
|
-
// been loaded and are not re-used between loads.
|
|
514
|
-
/** @private {shaka.util.Timer} */
|
|
515
|
-
this.bufferPoller_ = null;
|
|
516
|
-
|
|
517
592
|
/** @private {shaka.media.BufferingObserver} */
|
|
518
593
|
this.bufferObserver_ = null;
|
|
519
594
|
|
|
@@ -528,9 +603,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
528
603
|
/** @private {?HTMLCanvasElement} */
|
|
529
604
|
this.lcevcCanvas_ = null;
|
|
530
605
|
|
|
531
|
-
// This is the LCEVC
|
|
532
|
-
/** @private {?shaka.lcevc.
|
|
533
|
-
this.
|
|
606
|
+
// This is the LCEVC Decoder object to decode LCEVC.
|
|
607
|
+
/** @private {?shaka.lcevc.Dec} */
|
|
608
|
+
this.lcevcDec_ = null;
|
|
534
609
|
|
|
535
610
|
/** @private {shaka.media.QualityObserver} */
|
|
536
611
|
this.qualityObserver_ = null;
|
|
@@ -550,6 +625,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
550
625
|
/** @private {?string} */
|
|
551
626
|
this.assetUri_ = null;
|
|
552
627
|
|
|
628
|
+
/** @private {?number} */
|
|
629
|
+
this.startTime_ = null;
|
|
630
|
+
|
|
553
631
|
/** @private {boolean} */
|
|
554
632
|
this.fullyLoaded_ = false;
|
|
555
633
|
|
|
@@ -569,6 +647,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
569
647
|
*/
|
|
570
648
|
this.nextExternalStreamId_ = 1e9;
|
|
571
649
|
|
|
650
|
+
/** @private {!Array.<shaka.extern.Stream>} */
|
|
651
|
+
this.externalSrcEqualsThumbnailsStreams_ = [];
|
|
652
|
+
|
|
653
|
+
/** @private {number} */
|
|
654
|
+
this.completionPercent_ = NaN;
|
|
655
|
+
|
|
572
656
|
/** @private {?shaka.extern.PlayerConfiguration} */
|
|
573
657
|
this.config_ = this.defaultConfig_();
|
|
574
658
|
|
|
@@ -591,7 +675,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
591
675
|
this.config_.preferredAudioLanguage,
|
|
592
676
|
this.config_.preferredVariantRole,
|
|
593
677
|
this.config_.preferredAudioChannelCount,
|
|
594
|
-
this.config_.preferredVideoHdrLevel
|
|
678
|
+
this.config_.preferredVideoHdrLevel,
|
|
679
|
+
this.config_.preferredVideoLayout,
|
|
680
|
+
this.config_.preferredAudioLabel,
|
|
681
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
682
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
595
683
|
|
|
596
684
|
/** @private {string} */
|
|
597
685
|
this.currentTextLanguage_ = this.config_.preferredTextLanguage;
|
|
@@ -605,15 +693,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
605
693
|
/** @private {!Array.<function():(!Promise|undefined)>} */
|
|
606
694
|
this.cleanupOnUnload_ = [];
|
|
607
695
|
|
|
608
|
-
/**
|
|
609
|
-
* This playback start position will be used when
|
|
610
|
-
* <code>updateStartTime()</code> has been called to provide an updated
|
|
611
|
-
* start position during the media loading process.
|
|
612
|
-
*
|
|
613
|
-
* @private {?number}
|
|
614
|
-
*/
|
|
615
|
-
this.updatedStartTime_ = null;
|
|
616
|
-
|
|
617
696
|
if (dependencyInjector) {
|
|
618
697
|
dependencyInjector(this);
|
|
619
698
|
}
|
|
@@ -639,117 +718,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
639
718
|
this.retryStreaming();
|
|
640
719
|
});
|
|
641
720
|
|
|
642
|
-
/** @private {shaka.routing.Node} */
|
|
643
|
-
this.detachNode_ = {name: 'detach'};
|
|
644
|
-
/** @private {shaka.routing.Node} */
|
|
645
|
-
this.attachNode_ = {name: 'attach'};
|
|
646
|
-
/** @private {shaka.routing.Node} */
|
|
647
|
-
this.unloadNode_ = {name: 'unload'};
|
|
648
|
-
/** @private {shaka.routing.Node} */
|
|
649
|
-
this.parserNode_ = {name: 'manifest-parser'};
|
|
650
|
-
/** @private {shaka.routing.Node} */
|
|
651
|
-
this.manifestNode_ = {name: 'manifest'};
|
|
652
|
-
/** @private {shaka.routing.Node} */
|
|
653
|
-
this.mediaSourceNode_ = {name: 'media-source'};
|
|
654
|
-
/** @private {shaka.routing.Node} */
|
|
655
|
-
this.drmNode_ = {name: 'drm-engine'};
|
|
656
|
-
/** @private {shaka.routing.Node} */
|
|
657
|
-
this.loadNode_ = {name: 'load'};
|
|
658
|
-
/** @private {shaka.routing.Node} */
|
|
659
|
-
this.srcEqualsDrmNode_ = {name: 'src-equals-drm-engine'};
|
|
660
|
-
/** @private {shaka.routing.Node} */
|
|
661
|
-
this.srcEqualsNode_ = {name: 'src-equals'};
|
|
662
|
-
|
|
663
|
-
const AbortableOperation = shaka.util.AbortableOperation;
|
|
664
|
-
|
|
665
|
-
const actions = new Map();
|
|
666
|
-
actions.set(this.attachNode_, (has, wants) => {
|
|
667
|
-
return AbortableOperation.notAbortable(this.onAttach_(has, wants));
|
|
668
|
-
});
|
|
669
|
-
actions.set(this.detachNode_, (has, wants) => {
|
|
670
|
-
return AbortableOperation.notAbortable(this.onDetach_(has, wants));
|
|
671
|
-
});
|
|
672
|
-
actions.set(this.unloadNode_, (has, wants) => {
|
|
673
|
-
return AbortableOperation.notAbortable(this.onUnload_(has, wants));
|
|
674
|
-
});
|
|
675
|
-
actions.set(this.mediaSourceNode_, (has, wants) => {
|
|
676
|
-
const p = this.onInitializeMediaSourceEngine_(has, wants);
|
|
677
|
-
return AbortableOperation.notAbortable(p);
|
|
678
|
-
});
|
|
679
|
-
actions.set(this.parserNode_, (has, wants) => {
|
|
680
|
-
const p = this.onInitializeParser_(has, wants);
|
|
681
|
-
return AbortableOperation.notAbortable(p);
|
|
682
|
-
});
|
|
683
|
-
actions.set(this.manifestNode_, (has, wants) => {
|
|
684
|
-
// This action is actually abortable, so unlike the other callbacks, this
|
|
685
|
-
// one will return an abortable operation.
|
|
686
|
-
return this.onParseManifest_(has, wants);
|
|
687
|
-
});
|
|
688
|
-
actions.set(this.drmNode_, (has, wants) => {
|
|
689
|
-
const p = this.onInitializeDrm_(has, wants);
|
|
690
|
-
return AbortableOperation.notAbortable(p);
|
|
691
|
-
});
|
|
692
|
-
actions.set(this.loadNode_, (has, wants) => {
|
|
693
|
-
return AbortableOperation.notAbortable(this.onLoad_(has, wants));
|
|
694
|
-
});
|
|
695
|
-
|
|
696
|
-
actions.set(this.srcEqualsDrmNode_, (has, wants) => {
|
|
697
|
-
const p = this.onInitializeSrcEqualsDrm_(has, wants);
|
|
698
|
-
return AbortableOperation.notAbortable(p);
|
|
699
|
-
});
|
|
700
|
-
actions.set(this.srcEqualsNode_, (has, wants) => {
|
|
701
|
-
return this.onSrcEquals_(has, wants);
|
|
702
|
-
});
|
|
703
|
-
|
|
704
|
-
/** @private {shaka.routing.Walker.Implementation} */
|
|
705
|
-
const walkerImplementation = {
|
|
706
|
-
getNext: (at, has, goingTo, wants) => {
|
|
707
|
-
return this.getNextStep_(at, has, goingTo, wants);
|
|
708
|
-
},
|
|
709
|
-
enterNode: (node, has, wants) => {
|
|
710
|
-
this.dispatchEvent(this.makeEvent_(
|
|
711
|
-
/* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
|
|
712
|
-
/* data= */ (new Map()).set('state', node.name)));
|
|
713
|
-
|
|
714
|
-
const action = actions.get(node);
|
|
715
|
-
return action(has, wants);
|
|
716
|
-
},
|
|
717
|
-
handleError: async (has, error) => {
|
|
718
|
-
shaka.log.warning('The walker saw an error:');
|
|
719
|
-
if (error instanceof shaka.util.Error) {
|
|
720
|
-
shaka.log.warning('Error Code:', error.code);
|
|
721
|
-
} else {
|
|
722
|
-
shaka.log.warning('Error Message:', error.message);
|
|
723
|
-
shaka.log.warning('Error Stack:', error.stack);
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
// Regardless of what state we were in, if there is an error, we unload.
|
|
727
|
-
// This ensures that any initialized system will be torn-down and we
|
|
728
|
-
// will go back to a safe foundation. We assume that the media element
|
|
729
|
-
// is always safe to use after an error.
|
|
730
|
-
await this.onUnload_(has, shaka.Player.createEmptyPayload_());
|
|
731
|
-
|
|
732
|
-
// There are only two nodes that come before we start loading content,
|
|
733
|
-
// attach and detach. If we have a media element, it means we were
|
|
734
|
-
// attached to the element, and we can safely return to the attach state
|
|
735
|
-
// (we assume that the video element is always re-usable). We favor
|
|
736
|
-
// returning to the attach node since it means that the app won't need
|
|
737
|
-
// to re-attach if it saw an error.
|
|
738
|
-
return has.mediaElement ? this.attachNode_ : this.detachNode_;
|
|
739
|
-
},
|
|
740
|
-
onIdle: (node) => {
|
|
741
|
-
this.dispatchEvent(this.makeEvent_(
|
|
742
|
-
/* name= */ shaka.util.FakeEvent.EventName.OnStateIdle,
|
|
743
|
-
/* data= */ (new Map()).set('state', node.name)));
|
|
744
|
-
},
|
|
745
|
-
};
|
|
746
|
-
|
|
747
|
-
/** @private {shaka.routing.Walker} */
|
|
748
|
-
this.walker_ = new shaka.routing.Walker(
|
|
749
|
-
this.detachNode_,
|
|
750
|
-
shaka.Player.createEmptyPayload_(),
|
|
751
|
-
walkerImplementation);
|
|
752
|
-
|
|
753
721
|
/** @private {shaka.util.Timer} */
|
|
754
722
|
this.checkVariantsTimer_ =
|
|
755
723
|
new shaka.util.Timer(() => this.checkVariants_());
|
|
@@ -758,50 +726,53 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
758
726
|
// the LAST thing we do in the constructor because conceptually it relies on
|
|
759
727
|
// player having been initialized.
|
|
760
728
|
if (mediaElement) {
|
|
729
|
+
shaka.Deprecate.deprecateFeature(5,
|
|
730
|
+
'Player',
|
|
731
|
+
'Please migrate from initializing Player with a mediaElement; ' +
|
|
732
|
+
'use the attach method instead.');
|
|
761
733
|
this.attach(mediaElement, /* initializeMediaSource= */ true);
|
|
762
734
|
}
|
|
763
735
|
}
|
|
764
736
|
|
|
765
737
|
/**
|
|
766
|
-
* Create a shaka.lcevc.
|
|
738
|
+
* Create a shaka.lcevc.Dec object
|
|
767
739
|
* @param {shaka.extern.LcevcConfiguration} config
|
|
768
740
|
* @private
|
|
769
741
|
*/
|
|
770
|
-
|
|
771
|
-
if (this.
|
|
772
|
-
this.
|
|
742
|
+
createLcevcDec_(config) {
|
|
743
|
+
if (this.lcevcDec_ == null) {
|
|
744
|
+
this.lcevcDec_ = new shaka.lcevc.Dec(
|
|
773
745
|
/** @type {HTMLVideoElement} */ (this.video_),
|
|
774
746
|
this.lcevcCanvas_,
|
|
775
747
|
config,
|
|
776
748
|
);
|
|
777
749
|
if (this.mediaSourceEngine_) {
|
|
778
|
-
this.mediaSourceEngine_.
|
|
750
|
+
this.mediaSourceEngine_.updateLcevcDec(this.lcevcDec_);
|
|
779
751
|
}
|
|
780
752
|
}
|
|
781
753
|
}
|
|
782
754
|
|
|
783
755
|
/**
|
|
784
|
-
* Close a shaka.lcevc.
|
|
756
|
+
* Close a shaka.lcevc.Dec object if present and hide the canvas.
|
|
785
757
|
* @private
|
|
786
758
|
*/
|
|
787
|
-
|
|
788
|
-
if (this.
|
|
789
|
-
this.
|
|
790
|
-
this.
|
|
759
|
+
closeLcevcDec_() {
|
|
760
|
+
if (this.lcevcDec_ != null) {
|
|
761
|
+
this.lcevcDec_.hideCanvas();
|
|
762
|
+
this.lcevcDec_.release();
|
|
763
|
+
this.lcevcDec_ = null;
|
|
791
764
|
}
|
|
792
765
|
}
|
|
793
766
|
|
|
794
767
|
/**
|
|
795
|
-
* Setup shaka.lcevc.
|
|
768
|
+
* Setup shaka.lcevc.Dec object
|
|
796
769
|
* @param {?shaka.extern.PlayerConfiguration} config
|
|
797
770
|
* @private
|
|
798
771
|
*/
|
|
799
772
|
setupLcevc_(config) {
|
|
800
773
|
if (config.lcevc.enabled) {
|
|
801
774
|
const tracks = this.getVariantTracks();
|
|
802
|
-
if (tracks && tracks[0] &&
|
|
803
|
-
tracks[0].videoMimeType ==
|
|
804
|
-
shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_['ts']) {
|
|
775
|
+
if (tracks && tracks[0] && tracks[0].videoMimeType == 'video/mp2t') {
|
|
805
776
|
const edge = shaka.util.Platform.isEdge() ||
|
|
806
777
|
shaka.util.Platform.isLegacyEdge();
|
|
807
778
|
if (edge) {
|
|
@@ -815,10 +786,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
815
786
|
}
|
|
816
787
|
}
|
|
817
788
|
}
|
|
818
|
-
this.
|
|
819
|
-
this.
|
|
789
|
+
this.closeLcevcDec_();
|
|
790
|
+
this.createLcevcDec_(config.lcevc);
|
|
820
791
|
} else {
|
|
821
|
-
this.
|
|
792
|
+
this.closeLcevcDec_();
|
|
822
793
|
}
|
|
823
794
|
}
|
|
824
795
|
|
|
@@ -844,51 +815,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
844
815
|
return;
|
|
845
816
|
}
|
|
846
817
|
|
|
847
|
-
// If LCEVC
|
|
848
|
-
this.
|
|
818
|
+
// If LCEVC Decoder exists close it.
|
|
819
|
+
this.closeLcevcDec_();
|
|
820
|
+
|
|
821
|
+
const detachPromise = this.detach();
|
|
849
822
|
|
|
850
823
|
// Mark as "dead". This should stop external-facing calls from changing our
|
|
851
824
|
// internal state any more. This will stop calls to |attach|, |detach|, etc.
|
|
852
825
|
// from interrupting our final move to the detached state.
|
|
853
826
|
this.loadMode_ = shaka.Player.LoadMode.DESTROYED;
|
|
854
827
|
|
|
855
|
-
|
|
856
|
-
// must talk to |this.walker_| directly.
|
|
857
|
-
const events = this.walker_.startNewRoute((currentPayload) => {
|
|
858
|
-
return {
|
|
859
|
-
node: this.detachNode_,
|
|
860
|
-
payload: shaka.Player.createEmptyPayload_(),
|
|
861
|
-
interruptible: false,
|
|
862
|
-
};
|
|
863
|
-
});
|
|
864
|
-
|
|
865
|
-
// Wait until the detach has finished so that we don't interrupt it by
|
|
866
|
-
// calling |destroy| on |this.walker_|. To avoid failing here, we always
|
|
867
|
-
// resolve the promise.
|
|
868
|
-
await new Promise((resolve) => {
|
|
869
|
-
events.onStart = () => {
|
|
870
|
-
shaka.log.info('Preparing to destroy walker...');
|
|
871
|
-
};
|
|
872
|
-
events.onEnd = () => {
|
|
873
|
-
resolve();
|
|
874
|
-
};
|
|
875
|
-
events.onCancel = () => {
|
|
876
|
-
goog.asserts.assert(false,
|
|
877
|
-
'Our final detach call should never be cancelled.');
|
|
878
|
-
resolve();
|
|
879
|
-
};
|
|
880
|
-
events.onError = () => {
|
|
881
|
-
goog.asserts.assert(false,
|
|
882
|
-
'Our final detach call should never see an error');
|
|
883
|
-
resolve();
|
|
884
|
-
};
|
|
885
|
-
events.onSkip = () => {
|
|
886
|
-
goog.asserts.assert(false,
|
|
887
|
-
'Our final detach call should never be skipped');
|
|
888
|
-
resolve();
|
|
889
|
-
};
|
|
890
|
-
});
|
|
891
|
-
await this.walker_.destroy();
|
|
828
|
+
await detachPromise;
|
|
892
829
|
|
|
893
830
|
// Tear-down the event managers to ensure handlers stop firing.
|
|
894
831
|
if (this.globalEventManager_) {
|
|
@@ -974,10 +911,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
974
911
|
return false;
|
|
975
912
|
}
|
|
976
913
|
|
|
977
|
-
// We do not support iOS 9, 10, 11 or 12, nor those same versions of
|
|
978
|
-
// desktop Safari.
|
|
979
914
|
const safariVersion = shaka.util.Platform.safariVersion();
|
|
980
|
-
if (safariVersion && safariVersion <
|
|
915
|
+
if (safariVersion && safariVersion < 9) {
|
|
981
916
|
return false;
|
|
982
917
|
}
|
|
983
918
|
|
|
@@ -1036,56 +971,60 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1036
971
|
}
|
|
1037
972
|
|
|
1038
973
|
/**
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1041
|
-
*
|
|
1042
|
-
*
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
974
|
+
* Makes a fires an event corresponding to entering a state of the loading
|
|
975
|
+
* process.
|
|
976
|
+
* @param {string} nodeName
|
|
977
|
+
* @private
|
|
978
|
+
*/
|
|
979
|
+
makeStateChangeEvent_(nodeName) {
|
|
980
|
+
this.dispatchEvent(this.makeEvent_(
|
|
981
|
+
/* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
|
|
982
|
+
/* data= */ (new Map()).set('state', nodeName)));
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* Attaches the player to a media element.
|
|
987
|
+
* If the player was already attached to a media element, first detaches from
|
|
988
|
+
* that media element.
|
|
1051
989
|
*
|
|
1052
990
|
* @param {!HTMLMediaElement} mediaElement
|
|
1053
991
|
* @param {boolean=} initializeMediaSource
|
|
1054
992
|
* @return {!Promise}
|
|
1055
993
|
* @export
|
|
1056
994
|
*/
|
|
1057
|
-
attach(mediaElement, initializeMediaSource = true) {
|
|
995
|
+
async attach(mediaElement, initializeMediaSource = true) {
|
|
1058
996
|
// Do not allow the player to be used after |destroy| is called.
|
|
1059
997
|
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
1060
|
-
|
|
998
|
+
throw this.createAbortLoadError_();
|
|
1061
999
|
}
|
|
1062
1000
|
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
// If the platform does not support media source, we will never want to
|
|
1067
|
-
// initialize media source.
|
|
1068
|
-
if (!shaka.util.Platform.supportsMediaSource()) {
|
|
1069
|
-
initializeMediaSource = false;
|
|
1001
|
+
if (this.video_ && this.video_ != mediaElement) {
|
|
1002
|
+
await this.detach();
|
|
1070
1003
|
}
|
|
1071
1004
|
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1005
|
+
if (await this.atomicOperationAcquireMutex_('attach')) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1075
1008
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
const events = this.walker_.startNewRoute((currentPayload) => {
|
|
1079
|
-
return {
|
|
1080
|
-
node: destination,
|
|
1081
|
-
payload: payload,
|
|
1082
|
-
interruptible: false,
|
|
1083
|
-
};
|
|
1084
|
-
});
|
|
1009
|
+
try {
|
|
1010
|
+
this.makeStateChangeEvent_('attach');
|
|
1085
1011
|
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1012
|
+
const onError = (error) => this.onVideoError_(error);
|
|
1013
|
+
this.attachEventManager_.listen(mediaElement, 'error', onError);
|
|
1014
|
+
this.video_ = mediaElement;
|
|
1015
|
+
|
|
1016
|
+
// Only initialize media source if the platform supports it.
|
|
1017
|
+
if (initializeMediaSource &&
|
|
1018
|
+
shaka.util.Platform.supportsMediaSource() &&
|
|
1019
|
+
!this.mediaSourceEngine_) {
|
|
1020
|
+
await this.initializeMediaSourceEngineInner_();
|
|
1021
|
+
}
|
|
1022
|
+
} catch (error) {
|
|
1023
|
+
await this.detach();
|
|
1024
|
+
throw error;
|
|
1025
|
+
} finally {
|
|
1026
|
+
this.mutex_.release();
|
|
1027
|
+
}
|
|
1089
1028
|
}
|
|
1090
1029
|
|
|
1091
1030
|
|
|
@@ -1101,129 +1040,246 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1101
1040
|
}
|
|
1102
1041
|
|
|
1103
1042
|
/**
|
|
1104
|
-
*
|
|
1105
|
-
*
|
|
1106
|
-
*
|
|
1107
|
-
* <li>attached, this will release the media element,
|
|
1108
|
-
* <li>loading, this will abort loading, unload, and release the media
|
|
1109
|
-
* element,
|
|
1110
|
-
* <li>playing content, this will stop playback, unload, and release the
|
|
1111
|
-
* media element.
|
|
1112
|
-
* </ul>
|
|
1043
|
+
* Detach the player from the current media element. Leaves the player in a
|
|
1044
|
+
* state where it cannot play media, until it has been attached to something
|
|
1045
|
+
* else.
|
|
1113
1046
|
*
|
|
1114
|
-
*
|
|
1115
|
-
* Calls to <code>detach</code> will interrupt any in-progress calls to
|
|
1116
|
-
* <code>load</code> but cannot interrupt calls to <code>attach</code>,
|
|
1117
|
-
* <code>detach</code>, or <code>unload</code>.
|
|
1047
|
+
* @param {boolean=} keepAdManager
|
|
1118
1048
|
*
|
|
1119
1049
|
* @return {!Promise}
|
|
1120
1050
|
* @export
|
|
1121
1051
|
*/
|
|
1122
|
-
detach() {
|
|
1052
|
+
async detach(keepAdManager = false) {
|
|
1123
1053
|
// Do not allow the player to be used after |destroy| is called.
|
|
1124
1054
|
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
1125
|
-
|
|
1055
|
+
throw this.createAbortLoadError_();
|
|
1126
1056
|
}
|
|
1127
1057
|
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
node: this.detachNode_,
|
|
1134
|
-
payload: shaka.Player.createEmptyPayload_(),
|
|
1135
|
-
interruptible: false,
|
|
1136
|
-
};
|
|
1137
|
-
});
|
|
1058
|
+
await this.unload(/* initializeMediaSource= */ false, keepAdManager);
|
|
1059
|
+
|
|
1060
|
+
if (await this.atomicOperationAcquireMutex_('detach')) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1138
1063
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1064
|
+
try {
|
|
1065
|
+
// If we were going from "detached" to "detached" we wouldn't have
|
|
1066
|
+
// a media element to detach from.
|
|
1067
|
+
if (this.video_) {
|
|
1068
|
+
this.attachEventManager_.removeAll();
|
|
1069
|
+
this.video_ = null;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
this.makeStateChangeEvent_('detach');
|
|
1073
|
+
|
|
1074
|
+
if (this.adManager_ && !keepAdManager) {
|
|
1075
|
+
// The ad manager is specific to the video, so detach it too.
|
|
1076
|
+
this.adManager_.release();
|
|
1077
|
+
}
|
|
1078
|
+
} finally {
|
|
1079
|
+
this.mutex_.release();
|
|
1080
|
+
}
|
|
1141
1081
|
}
|
|
1142
1082
|
|
|
1143
1083
|
/**
|
|
1144
|
-
*
|
|
1145
|
-
*
|
|
1146
|
-
*
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
-
*
|
|
1151
|
-
*
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1084
|
+
* Tries to acquire the mutex, and then returns if the operation should end
|
|
1085
|
+
* early due to someone else starting a mutex-acquiring operation.
|
|
1086
|
+
* Meant for operations that can't be interrupted midway through (e.g.
|
|
1087
|
+
* everything but load).
|
|
1088
|
+
* @param {string} mutexIdentifier
|
|
1089
|
+
* @return {!Promise.<boolean>} endEarly If false, the calling context will
|
|
1090
|
+
* need to release the mutex.
|
|
1091
|
+
* @private
|
|
1092
|
+
*/
|
|
1093
|
+
async atomicOperationAcquireMutex_(mutexIdentifier) {
|
|
1094
|
+
const operationId = ++this.operationId_;
|
|
1095
|
+
await this.mutex_.acquire(mutexIdentifier);
|
|
1096
|
+
if (operationId != this.operationId_) {
|
|
1097
|
+
this.mutex_.release();
|
|
1098
|
+
return true;
|
|
1099
|
+
}
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
/**
|
|
1104
|
+
* Unloads the currently playing stream, if any.
|
|
1157
1105
|
*
|
|
1158
1106
|
* @param {boolean=} initializeMediaSource
|
|
1107
|
+
* @param {boolean=} keepAdManager
|
|
1159
1108
|
* @return {!Promise}
|
|
1160
1109
|
* @export
|
|
1161
1110
|
*/
|
|
1162
|
-
unload(initializeMediaSource = true) {
|
|
1163
|
-
//
|
|
1164
|
-
|
|
1165
|
-
|
|
1111
|
+
async unload(initializeMediaSource = true, keepAdManager = false) {
|
|
1112
|
+
// Set the load mode to unload right away so that all the public methods
|
|
1113
|
+
// will stop using the internal components. We need to make sure that we
|
|
1114
|
+
// are not overriding the destroyed state because we will unload when we are
|
|
1115
|
+
// destroying the player.
|
|
1116
|
+
if (this.loadMode_ != shaka.Player.LoadMode.DESTROYED) {
|
|
1117
|
+
this.loadMode_ = shaka.Player.LoadMode.NOT_LOADED;
|
|
1166
1118
|
}
|
|
1167
1119
|
|
|
1168
|
-
this.
|
|
1169
|
-
|
|
1170
|
-
// If the platform does not support media source, we will never want to
|
|
1171
|
-
// initialize media source.
|
|
1172
|
-
if (!shaka.util.Platform.supportsMediaSource()) {
|
|
1173
|
-
initializeMediaSource = false;
|
|
1120
|
+
if (await this.atomicOperationAcquireMutex_('unload')) {
|
|
1121
|
+
return;
|
|
1174
1122
|
}
|
|
1175
1123
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1124
|
+
try {
|
|
1125
|
+
this.fullyLoaded_ = false;
|
|
1126
|
+
this.makeStateChangeEvent_('unload');
|
|
1178
1127
|
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
//
|
|
1190
|
-
//
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
//
|
|
1128
|
+
// If the platform does not support media source, we will never want to
|
|
1129
|
+
// initialize media source.
|
|
1130
|
+
if (initializeMediaSource && !shaka.util.Platform.supportsMediaSource()) {
|
|
1131
|
+
initializeMediaSource = false;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// If LCEVC Decoder exists close it.
|
|
1135
|
+
this.closeLcevcDec_();
|
|
1136
|
+
|
|
1137
|
+
// Run any general cleanup tasks now. This should be here at the top,
|
|
1138
|
+
// right after setting loadMode_, so that internal components still exist
|
|
1139
|
+
// as they did when the cleanup tasks were registered in the array.
|
|
1140
|
+
const cleanupTasks = this.cleanupOnUnload_.map((cb) => cb());
|
|
1141
|
+
this.cleanupOnUnload_ = [];
|
|
1142
|
+
await Promise.all(cleanupTasks);
|
|
1143
|
+
|
|
1144
|
+
// Dispatch the unloading event.
|
|
1145
|
+
this.dispatchEvent(
|
|
1146
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.Unloading));
|
|
1147
|
+
|
|
1148
|
+
// Release the region timeline, which is created when parsing the
|
|
1149
|
+
// manifest.
|
|
1150
|
+
if (this.regionTimeline_) {
|
|
1151
|
+
this.regionTimeline_.release();
|
|
1152
|
+
this.regionTimeline_ = null;
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// In most cases we should have a media element. The one exception would
|
|
1156
|
+
// be if there was an error and we, by chance, did not have a media
|
|
1157
|
+
// element.
|
|
1158
|
+
if (this.video_) {
|
|
1159
|
+
this.loadEventManager_.removeAll();
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Stop the variant checker timer
|
|
1163
|
+
this.checkVariantsTimer_.stop();
|
|
1164
|
+
|
|
1165
|
+
// Some observers use some playback components, shutting down the
|
|
1166
|
+
// observers first ensures that they don't try to use the playback
|
|
1167
|
+
// components mid-destroy.
|
|
1168
|
+
if (this.playheadObservers_) {
|
|
1169
|
+
this.playheadObservers_.release();
|
|
1170
|
+
this.playheadObservers_ = null;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Stop the parser early. Since it is at the start of the pipeline, it
|
|
1174
|
+
// should be start early to avoid is pushing new data downstream.
|
|
1175
|
+
if (this.parser_) {
|
|
1176
|
+
await this.parser_.stop();
|
|
1177
|
+
this.parser_ = null;
|
|
1178
|
+
this.parserFactory_ = null;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Abr Manager will tell streaming engine what to do, so we need to stop
|
|
1182
|
+
// it before we destroy streaming engine. Unlike with the other
|
|
1183
|
+
// components, we do not release the instance, we will reuse it in later
|
|
1184
|
+
// loads.
|
|
1185
|
+
if (this.abrManager_) {
|
|
1186
|
+
await this.abrManager_.stop();
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Streaming engine will push new data to media source engine, so we need
|
|
1190
|
+
// to shut it down before destroy media source engine.
|
|
1191
|
+
if (this.streamingEngine_) {
|
|
1192
|
+
await this.streamingEngine_.destroy();
|
|
1193
|
+
this.streamingEngine_ = null;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (this.playRateController_) {
|
|
1197
|
+
this.playRateController_.release();
|
|
1198
|
+
this.playRateController_ = null;
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Playhead is used by StreamingEngine, so we can't destroy this until
|
|
1202
|
+
// after StreamingEngine has stopped.
|
|
1203
|
+
if (this.playhead_) {
|
|
1204
|
+
this.playhead_.release();
|
|
1205
|
+
this.playhead_ = null;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
// Media source engine holds onto the media element, and in order to
|
|
1209
|
+
// detach the media keys (with drm engine), we need to break the
|
|
1210
|
+
// connection between media source engine and the media element.
|
|
1211
|
+
if (this.mediaSourceEngine_) {
|
|
1212
|
+
await this.mediaSourceEngine_.destroy();
|
|
1213
|
+
this.mediaSourceEngine_ = null;
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
if (this.adManager_ && !keepAdManager) {
|
|
1217
|
+
this.adManager_.onAssetUnload();
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (this.video_) {
|
|
1221
|
+
// Remove all track nodes
|
|
1222
|
+
shaka.util.Dom.removeAllChildren(this.video_);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
// In order to unload a media element, we need to remove the src attribute
|
|
1226
|
+
// and then load again. When we destroy media source engine, this will be
|
|
1227
|
+
// done for us, but for src=, we need to do it here.
|
|
1196
1228
|
//
|
|
1197
|
-
//
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
} else {
|
|
1209
|
-
destination = this.detachNode_;
|
|
1229
|
+
// DrmEngine requires this to be done before we destroy DrmEngine itself.
|
|
1230
|
+
if (this.video_ && this.video_.src) {
|
|
1231
|
+
// TODO: Investigate this more. Only reproduces on Firefox 69.
|
|
1232
|
+
// Introduce a delay before detaching the video source. We are seeing
|
|
1233
|
+
// spurious Promise rejections involving an AbortError in our tests
|
|
1234
|
+
// otherwise.
|
|
1235
|
+
await new Promise(
|
|
1236
|
+
(resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
|
|
1237
|
+
|
|
1238
|
+
this.video_.removeAttribute('src');
|
|
1239
|
+
this.video_.load();
|
|
1210
1240
|
}
|
|
1211
1241
|
|
|
1212
|
-
|
|
1242
|
+
if (this.drmEngine_) {
|
|
1243
|
+
await this.drmEngine_.destroy();
|
|
1244
|
+
this.drmEngine_ = null;
|
|
1245
|
+
}
|
|
1213
1246
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
payload.mediaElement = currentPayload.mediaElement;
|
|
1247
|
+
this.assetUri_ = null;
|
|
1248
|
+
this.bufferObserver_ = null;
|
|
1217
1249
|
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1250
|
+
if (this.manifest_) {
|
|
1251
|
+
for (const variant of this.manifest_.variants) {
|
|
1252
|
+
for (const stream of [variant.audio, variant.video]) {
|
|
1253
|
+
if (stream && stream.segmentIndex) {
|
|
1254
|
+
stream.segmentIndex.release();
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
for (const stream of this.manifest_.textStreams) {
|
|
1259
|
+
if (stream.segmentIndex) {
|
|
1260
|
+
stream.segmentIndex.release();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
this.manifest_ = null;
|
|
1266
|
+
this.stats_ = new shaka.util.Stats(); // Replace with a clean object.
|
|
1267
|
+
this.lastTextFactory_ = null;
|
|
1268
|
+
|
|
1269
|
+
this.externalSrcEqualsThumbnailsStreams_ = [];
|
|
1224
1270
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1271
|
+
this.completionPercent_ = NaN;
|
|
1272
|
+
|
|
1273
|
+
// Make sure that the app knows of the new buffering state.
|
|
1274
|
+
this.updateBufferState_();
|
|
1275
|
+
} finally {
|
|
1276
|
+
this.mutex_.release();
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if (initializeMediaSource && shaka.util.Platform.supportsMediaSource() &&
|
|
1280
|
+
!this.mediaSourceEngine_) {
|
|
1281
|
+
await this.initializeMediaSourceEngineInner_();
|
|
1282
|
+
}
|
|
1227
1283
|
}
|
|
1228
1284
|
|
|
1229
1285
|
/**
|
|
@@ -1236,120 +1292,164 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1236
1292
|
* @export
|
|
1237
1293
|
*/
|
|
1238
1294
|
updateStartTime(startTime) {
|
|
1239
|
-
this.
|
|
1295
|
+
this.startTime_ = startTime;
|
|
1240
1296
|
}
|
|
1241
1297
|
|
|
1242
1298
|
/**
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
* a call to <code>attach</code> must have succeeded.
|
|
1246
|
-
*
|
|
1247
|
-
* <p>
|
|
1248
|
-
* Calls to <code>load</code> will interrupt any in-progress calls to
|
|
1249
|
-
* <code>load</code> but cannot interrupt calls to <code>attach</code>,
|
|
1250
|
-
* <code>detach</code>, or <code>unload</code>.
|
|
1299
|
+
* Loads a new stream.
|
|
1300
|
+
* If another stream was already playing, first unloads that stream.
|
|
1251
1301
|
*
|
|
1252
1302
|
* @param {string} assetUri
|
|
1253
1303
|
* @param {?number=} startTime
|
|
1254
1304
|
* When <code>startTime</code> is <code>null</code> or
|
|
1255
1305
|
* <code>undefined</code>, playback will start at the default start time (0
|
|
1256
1306
|
* for VOD and liveEdge for LIVE).
|
|
1257
|
-
* @param {string=} mimeType
|
|
1307
|
+
* @param {?string=} mimeType
|
|
1258
1308
|
* @return {!Promise}
|
|
1259
1309
|
* @export
|
|
1260
1310
|
*/
|
|
1261
|
-
load(assetUri, startTime, mimeType) {
|
|
1262
|
-
this.updatedStartTime_ = null;
|
|
1263
|
-
|
|
1264
|
-
this.fullyLoaded_ = false;
|
|
1265
|
-
|
|
1311
|
+
async load(assetUri, startTime = null, mimeType) {
|
|
1266
1312
|
// Do not allow the player to be used after |destroy| is called.
|
|
1267
1313
|
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
1268
|
-
|
|
1314
|
+
throw this.createAbortLoadError_();
|
|
1269
1315
|
}
|
|
1270
1316
|
|
|
1271
|
-
//
|
|
1272
|
-
//
|
|
1273
|
-
this.
|
|
1317
|
+
// Quickly acquire the mutex, so this will wait for other top-level
|
|
1318
|
+
// operations.
|
|
1319
|
+
await this.mutex_.acquire('load');
|
|
1320
|
+
this.mutex_.release();
|
|
1274
1321
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
if (mimeType) {
|
|
1281
|
-
payload.mimeType = mimeType;
|
|
1322
|
+
if (!this.video_) {
|
|
1323
|
+
throw new shaka.util.Error(
|
|
1324
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1325
|
+
shaka.util.Error.Category.PLAYER,
|
|
1326
|
+
shaka.util.Error.Code.NO_VIDEO_ELEMENT);
|
|
1282
1327
|
}
|
|
1283
1328
|
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
// |undefined| to |null| while preserving |0| as a meaningful value.
|
|
1287
|
-
if (startTime !== undefined) {
|
|
1288
|
-
payload.startTime = startTime;
|
|
1329
|
+
if (this.assetUri_) {
|
|
1330
|
+
await this.unload(/* initializeMediaSource= */ false);
|
|
1289
1331
|
}
|
|
1290
1332
|
|
|
1291
|
-
//
|
|
1292
|
-
//
|
|
1293
|
-
|
|
1294
|
-
const
|
|
1295
|
-
|
|
1333
|
+
// Add a mechanism to detect if the load process has been interrupted by a
|
|
1334
|
+
// call to another top-level operation (unload, load, etc).
|
|
1335
|
+
const operationId = ++this.operationId_;
|
|
1336
|
+
const detectInterruption = () => {
|
|
1337
|
+
if (this.operationId_ != operationId) {
|
|
1338
|
+
throw this.createAbortLoadError_();
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1296
1341
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1342
|
+
/**
|
|
1343
|
+
* Wraps a given operation with mutex.acquire and mutex.release, along with
|
|
1344
|
+
* calls to detectInterruption, to catch any other top-level calls happening
|
|
1345
|
+
* while waiting for the mutex.
|
|
1346
|
+
* @param {function():!Promise} operation
|
|
1347
|
+
* @param {string} mutexIdentifier
|
|
1348
|
+
* @return {!Promise}
|
|
1349
|
+
*/
|
|
1350
|
+
const mutexWrapOperation = async (operation, mutexIdentifier) => {
|
|
1351
|
+
try {
|
|
1352
|
+
await this.mutex_.acquire(mutexIdentifier);
|
|
1353
|
+
detectInterruption();
|
|
1354
|
+
await operation();
|
|
1355
|
+
detectInterruption();
|
|
1356
|
+
} finally {
|
|
1357
|
+
this.mutex_.release();
|
|
1303
1358
|
}
|
|
1359
|
+
};
|
|
1304
1360
|
|
|
1305
|
-
|
|
1306
|
-
|
|
1361
|
+
try {
|
|
1362
|
+
this.startTime_ = startTime;
|
|
1363
|
+
this.fullyLoaded_ = false;
|
|
1307
1364
|
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
};
|
|
1313
|
-
});
|
|
1365
|
+
// We dispatch the loading event when someone calls |load| because we want
|
|
1366
|
+
// to surface the user intent.
|
|
1367
|
+
this.dispatchEvent(this.makeEvent_(
|
|
1368
|
+
shaka.util.FakeEvent.EventName.Loading));
|
|
1314
1369
|
|
|
1315
|
-
|
|
1316
|
-
// before we allow calls to |updateStateHistory|.
|
|
1317
|
-
this.stats_ = new shaka.util.Stats();
|
|
1318
|
-
|
|
1319
|
-
// Load's request is a little different, so we can't use our normal
|
|
1320
|
-
// listeners-to-promise method. It is the only request where we may skip the
|
|
1321
|
-
// request, so we need to set the on skip callback to reject with a specific
|
|
1322
|
-
// error.
|
|
1323
|
-
events.onStart =
|
|
1324
|
-
() => shaka.log.info('Starting load of ' + assetUri + '...');
|
|
1325
|
-
return new Promise((resolve, reject) => {
|
|
1326
|
-
events.onSkip = () => reject(new shaka.util.Error(
|
|
1327
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
1328
|
-
shaka.util.Error.Category.PLAYER,
|
|
1329
|
-
shaka.util.Error.Code.NO_VIDEO_ELEMENT));
|
|
1370
|
+
const startTimeOfLoad = Date.now() / 1000;
|
|
1330
1371
|
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1372
|
+
// Stats are for a single playback/load session. Stats must be initialized
|
|
1373
|
+
// before we allow calls to |updateStateHistory|.
|
|
1374
|
+
this.stats_ = new shaka.util.Stats();
|
|
1375
|
+
|
|
1376
|
+
this.assetUri_ = assetUri;
|
|
1377
|
+
|
|
1378
|
+
if (!mimeType) {
|
|
1379
|
+
await mutexWrapOperation(async () => {
|
|
1380
|
+
mimeType = await this.guessMimeType_();
|
|
1381
|
+
}, 'guessMimeType_');
|
|
1382
|
+
}
|
|
1383
|
+
const shouldUseSrcEquals = this.shouldUseSrcEquals_(assetUri, mimeType);
|
|
1384
|
+
if (shouldUseSrcEquals) {
|
|
1385
|
+
await mutexWrapOperation(async () => {
|
|
1386
|
+
goog.asserts.assert(mimeType, 'We should know the mimeType by now!');
|
|
1387
|
+
await this.initializeSrcEqualsDrmInner_(mimeType);
|
|
1388
|
+
}, 'initializeSrcEqualsDrmInner_');
|
|
1389
|
+
await mutexWrapOperation(async () => {
|
|
1390
|
+
goog.asserts.assert(mimeType, 'We should know the mimeType by now!');
|
|
1391
|
+
await this.srcEqualsInner_(startTimeOfLoad, mimeType);
|
|
1392
|
+
}, 'srcEqualsInner_');
|
|
1393
|
+
} else {
|
|
1394
|
+
if (!this.mediaSourceEngine_) {
|
|
1395
|
+
await mutexWrapOperation(async () => {
|
|
1396
|
+
await this.initializeMediaSourceEngineInner_();
|
|
1397
|
+
}, 'initializeMediaSourceEngineInner_');
|
|
1398
|
+
}
|
|
1399
|
+
await mutexWrapOperation(async () => {
|
|
1400
|
+
await this.parseManifestInner_(mimeType);
|
|
1401
|
+
}, 'parseManifestInner_');
|
|
1402
|
+
await mutexWrapOperation(async () => {
|
|
1403
|
+
await this.initializeDrmInner_();
|
|
1404
|
+
}, 'initializeDrmInner_');
|
|
1405
|
+
await mutexWrapOperation(async () => {
|
|
1406
|
+
await this.loadInner_(startTimeOfLoad);
|
|
1407
|
+
}, 'loadInner_');
|
|
1408
|
+
}
|
|
1409
|
+
this.dispatchEvent(this.makeEvent_(
|
|
1410
|
+
shaka.util.FakeEvent.EventName.Loaded));
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
if (error.code != shaka.util.Error.Code.LOAD_INTERRUPTED) {
|
|
1413
|
+
await this.unload(/* initializeMediaSource= */ false);
|
|
1414
|
+
}
|
|
1415
|
+
throw error;
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Determines the mimeType of the given asset, if we are not told that inside
|
|
1421
|
+
* the loading process.
|
|
1422
|
+
*
|
|
1423
|
+
* @return {!Promise.<?string>} mimeType
|
|
1424
|
+
* @private
|
|
1425
|
+
*/
|
|
1426
|
+
async guessMimeType_() {
|
|
1427
|
+
goog.asserts.assert(this.assetUri_, 'should have a uri by now.');
|
|
1428
|
+
|
|
1429
|
+
// If no MIME type is provided, and we can't base it on extension, make a
|
|
1430
|
+
// HEAD request to determine it.
|
|
1431
|
+
goog.asserts.assert(this.networkingEngine_, 'Should have a net engine!');
|
|
1432
|
+
const retryParams = this.config_.manifest.retryParameters;
|
|
1433
|
+
let mimeType = await shaka.net.NetworkingUtils.getMimeType(
|
|
1434
|
+
this.assetUri_, this.networkingEngine_, retryParams);
|
|
1435
|
+
if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
|
|
1436
|
+
mimeType = 'application/vnd.apple.mpegurl';
|
|
1437
|
+
}
|
|
1438
|
+
return mimeType;
|
|
1340
1439
|
}
|
|
1341
1440
|
|
|
1342
1441
|
/**
|
|
1343
|
-
*
|
|
1344
|
-
*
|
|
1442
|
+
* Determines if we should use src equals, based on the the mimeType (if
|
|
1443
|
+
* known), the URI, and platform information.
|
|
1345
1444
|
*
|
|
1346
|
-
* @param {
|
|
1445
|
+
* @param {string} assetUri
|
|
1446
|
+
* @param {?string=} mimeType
|
|
1347
1447
|
* @return {boolean}
|
|
1348
1448
|
* |true| if the content should be loaded with src=, |false| if the content
|
|
1349
1449
|
* should be loaded with MediaSource.
|
|
1350
1450
|
* @private
|
|
1351
1451
|
*/
|
|
1352
|
-
shouldUseSrcEquals_(
|
|
1452
|
+
shouldUseSrcEquals_(assetUri, mimeType) {
|
|
1353
1453
|
const Platform = shaka.util.Platform;
|
|
1354
1454
|
const MimeUtils = shaka.util.MimeUtils;
|
|
1355
1455
|
|
|
@@ -1359,29 +1459,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1359
1459
|
return true;
|
|
1360
1460
|
}
|
|
1361
1461
|
|
|
1362
|
-
// The most accurate way to tell the player how to load the content is via
|
|
1363
|
-
// MIME type. We can fall back to features of the URI if needed.
|
|
1364
|
-
let mimeType = payload.mimeType;
|
|
1365
|
-
const uri = payload.uri || '';
|
|
1366
|
-
|
|
1367
|
-
// If we don't have a MIME type, try to guess based on the file extension.
|
|
1368
|
-
// TODO: Too generic to belong to ManifestParser now. Refactor.
|
|
1369
|
-
if (!mimeType) {
|
|
1370
|
-
// Try using the uri extension.
|
|
1371
|
-
const extension = shaka.media.ManifestParser.getExtension(uri);
|
|
1372
|
-
mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
// TODO: The load graph system has a design limitation that requires routing
|
|
1376
|
-
// destination to be chosen synchronously. This means we can only make the
|
|
1377
|
-
// right choice about src= consistently if we have a well-known file
|
|
1378
|
-
// extension or API-provided MIME type. Detection of MIME type from a HEAD
|
|
1379
|
-
// request (as is done for manifest types) can't be done yet.
|
|
1380
|
-
|
|
1381
1462
|
if (mimeType) {
|
|
1382
1463
|
// If we have a MIME type, check if the browser can play it natively.
|
|
1383
1464
|
// This will cover both single files and native HLS.
|
|
1384
|
-
const mediaElement =
|
|
1465
|
+
const mediaElement = this.video_ || Platform.anyMediaElement();
|
|
1385
1466
|
const canPlayNatively = mediaElement.canPlayType(mimeType) != '';
|
|
1386
1467
|
|
|
1387
1468
|
// If we can't play natively, then src= isn't an option.
|
|
@@ -1390,7 +1471,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1390
1471
|
}
|
|
1391
1472
|
|
|
1392
1473
|
const canPlayMediaSource =
|
|
1393
|
-
shaka.media.ManifestParser.isSupported(
|
|
1474
|
+
shaka.media.ManifestParser.isSupported(mimeType);
|
|
1394
1475
|
|
|
1395
1476
|
// If MediaSource isn't an option, the native option is our only chance.
|
|
1396
1477
|
if (!canPlayMediaSource) {
|
|
@@ -1404,17 +1485,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1404
1485
|
// We would prefer MediaSource in some cases, and src= in others. For
|
|
1405
1486
|
// example, Android has native HLS, but we'd prefer our own MediaSource
|
|
1406
1487
|
// version there.
|
|
1488
|
+
if (MimeUtils.isHlsType(mimeType)) {
|
|
1489
|
+
// Native HLS can be preferred on any platform via this flag:
|
|
1490
|
+
if (this.config_.streaming.preferNativeHls) {
|
|
1491
|
+
return true;
|
|
1492
|
+
}
|
|
1407
1493
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
// For Safari, we have an older flag which only applies to this one
|
|
1415
|
-
// browser:
|
|
1416
|
-
if (Platform.isApple()) {
|
|
1417
|
-
return this.config_.streaming.useNativeHlsOnSafari;
|
|
1494
|
+
// For Safari, we have an older flag which only applies to this one
|
|
1495
|
+
// browser:
|
|
1496
|
+
if (Platform.isApple()) {
|
|
1497
|
+
return this.config_.streaming.useNativeHlsOnSafari;
|
|
1498
|
+
}
|
|
1418
1499
|
}
|
|
1419
1500
|
|
|
1420
1501
|
// In all other cases, we prefer MediaSource.
|
|
@@ -1428,298 +1509,40 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1428
1509
|
}
|
|
1429
1510
|
|
|
1430
1511
|
/**
|
|
1431
|
-
*
|
|
1432
|
-
* a media element. The only times this may be called are when we are being
|
|
1433
|
-
* asked to re-attach to the current media element, or attach to a new media
|
|
1434
|
-
* element while not attached to a media element.
|
|
1435
|
-
*
|
|
1436
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1437
|
-
* responsible for ensuring all assumptions are true.
|
|
1512
|
+
* Initializes the media source engine.
|
|
1438
1513
|
*
|
|
1439
|
-
* Attaching to a media element is defined as:
|
|
1440
|
-
* - Registering error listeners to the media element.
|
|
1441
|
-
* - Caching the video element for use outside of the load graph.
|
|
1442
|
-
*
|
|
1443
|
-
* @param {shaka.routing.Payload} has
|
|
1444
|
-
* @param {shaka.routing.Payload} wants
|
|
1445
1514
|
* @return {!Promise}
|
|
1446
1515
|
* @private
|
|
1447
1516
|
*/
|
|
1448
|
-
|
|
1449
|
-
// If we don't have a media element yet, it means we are entering
|
|
1450
|
-
// "attach" from another node.
|
|
1451
|
-
//
|
|
1452
|
-
// If we have a media element, it should match |wants.mediaElement|
|
|
1453
|
-
// because it means we are going from "attach" to "attach".
|
|
1454
|
-
//
|
|
1455
|
-
// These constraints should be maintained and guaranteed by the routing
|
|
1456
|
-
// logic in |getNextStep_|.
|
|
1457
|
-
goog.asserts.assert(
|
|
1458
|
-
has.mediaElement == null || has.mediaElement == wants.mediaElement,
|
|
1459
|
-
'The routing logic failed. MediaElement requirement failed.');
|
|
1460
|
-
|
|
1461
|
-
if (has.mediaElement == null) {
|
|
1462
|
-
has.mediaElement = wants.mediaElement;
|
|
1463
|
-
|
|
1464
|
-
const onError = (error) => this.onVideoError_(error);
|
|
1465
|
-
this.attachEventManager_.listen(has.mediaElement, 'error', onError);
|
|
1466
|
-
}
|
|
1467
|
-
|
|
1468
|
-
this.video_ = has.mediaElement;
|
|
1469
|
-
|
|
1470
|
-
return Promise.resolve();
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
/**
|
|
1474
|
-
* This should only be called by the load graph when it is time to detach from
|
|
1475
|
-
* a media element. The only times this may be called are when we are being
|
|
1476
|
-
* asked to detach from the current media element, or detach when we are
|
|
1477
|
-
* already detached.
|
|
1478
|
-
*
|
|
1479
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1480
|
-
* responsible for ensuring all assumptions are true.
|
|
1481
|
-
*
|
|
1482
|
-
* Detaching from a media element is defined as:
|
|
1483
|
-
* - Removing error listeners from the media element.
|
|
1484
|
-
* - Dropping the cached reference to the video element.
|
|
1485
|
-
*
|
|
1486
|
-
* @param {shaka.routing.Payload} has
|
|
1487
|
-
* @param {shaka.routing.Payload} wants
|
|
1488
|
-
* @return {!Promise}
|
|
1489
|
-
* @private
|
|
1490
|
-
*/
|
|
1491
|
-
onDetach_(has, wants) {
|
|
1492
|
-
// If we were going from "detached" to "detached" we wouldn't have
|
|
1493
|
-
// a media element to detach from.
|
|
1494
|
-
if (has.mediaElement) {
|
|
1495
|
-
this.attachEventManager_.removeAll();
|
|
1496
|
-
has.mediaElement = null;
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
if (this.adManager_) {
|
|
1500
|
-
// The ad manager is specific to the video, so detach it too.
|
|
1501
|
-
this.adManager_.release();
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
// Clear our cached copy of the media element.
|
|
1505
|
-
this.video_ = null;
|
|
1506
|
-
|
|
1507
|
-
return Promise.resolve();
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
/**
|
|
1511
|
-
* This should only be called by the load graph when it is time to unload all
|
|
1512
|
-
* currently initialized playback components. Unlike the other load actions,
|
|
1513
|
-
* this action is built to be more general. We need to do this because we
|
|
1514
|
-
* don't know what state the player will be in before unloading (including
|
|
1515
|
-
* after an error occurred in the middle of a transition).
|
|
1516
|
-
*
|
|
1517
|
-
* This method assumes that any component could be |null| and should be safe
|
|
1518
|
-
* to call from any point in the load graph.
|
|
1519
|
-
*
|
|
1520
|
-
* @param {shaka.routing.Payload} has
|
|
1521
|
-
* @param {shaka.routing.Payload} wants
|
|
1522
|
-
* @return {!Promise}
|
|
1523
|
-
* @private
|
|
1524
|
-
*/
|
|
1525
|
-
async onUnload_(has, wants) {
|
|
1526
|
-
// Set the load mode to unload right away so that all the public methods
|
|
1527
|
-
// will stop using the internal components. We need to make sure that we
|
|
1528
|
-
// are not overriding the destroyed state because we will unload when we are
|
|
1529
|
-
// destroying the player.
|
|
1530
|
-
if (this.loadMode_ != shaka.Player.LoadMode.DESTROYED) {
|
|
1531
|
-
this.loadMode_ = shaka.Player.LoadMode.NOT_LOADED;
|
|
1532
|
-
}
|
|
1533
|
-
|
|
1534
|
-
// Run any general cleanup tasks now. This should be here at the top, right
|
|
1535
|
-
// after setting loadMode_, so that internal components still exist as they
|
|
1536
|
-
// did when the cleanup tasks were registered in the array.
|
|
1537
|
-
const cleanupTasks = this.cleanupOnUnload_.map((cb) => cb());
|
|
1538
|
-
this.cleanupOnUnload_ = [];
|
|
1539
|
-
await Promise.all(cleanupTasks);
|
|
1540
|
-
|
|
1541
|
-
// Dispatch the unloading event.
|
|
1542
|
-
this.dispatchEvent(
|
|
1543
|
-
this.makeEvent_(shaka.util.FakeEvent.EventName.Unloading));
|
|
1544
|
-
|
|
1545
|
-
// Remove everything that has to do with loading content from our payload
|
|
1546
|
-
// since we are releasing everything that depended on it.
|
|
1547
|
-
has.mimeType = null;
|
|
1548
|
-
has.startTime = null;
|
|
1549
|
-
has.uri = null;
|
|
1550
|
-
|
|
1551
|
-
// Release the region timeline, which is created when parsing the manifest.
|
|
1552
|
-
if (this.regionTimeline_) {
|
|
1553
|
-
this.regionTimeline_.release();
|
|
1554
|
-
this.regionTimeline_ = null;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
// In most cases we should have a media element. The one exception would
|
|
1558
|
-
// be if there was an error and we, by chance, did not have a media element.
|
|
1559
|
-
if (has.mediaElement) {
|
|
1560
|
-
this.loadEventManager_.removeAll();
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
// Stop the variant checker timer
|
|
1564
|
-
this.checkVariantsTimer_.stop();
|
|
1565
|
-
|
|
1566
|
-
// Some observers use some playback components, shutting down the observers
|
|
1567
|
-
// first ensures that they don't try to use the playback components
|
|
1568
|
-
// mid-destroy.
|
|
1569
|
-
if (this.playheadObservers_) {
|
|
1570
|
-
this.playheadObservers_.release();
|
|
1571
|
-
this.playheadObservers_ = null;
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
if (this.bufferPoller_) {
|
|
1575
|
-
this.bufferPoller_.stop();
|
|
1576
|
-
this.bufferPoller_ = null;
|
|
1577
|
-
}
|
|
1578
|
-
|
|
1579
|
-
// Stop the parser early. Since it is at the start of the pipeline, it
|
|
1580
|
-
// should be start early to avoid is pushing new data downstream.
|
|
1581
|
-
if (this.parser_) {
|
|
1582
|
-
await this.parser_.stop();
|
|
1583
|
-
this.parser_ = null;
|
|
1584
|
-
this.parserFactory_ = null;
|
|
1585
|
-
}
|
|
1586
|
-
|
|
1587
|
-
// Abr Manager will tell streaming engine what to do, so we need to stop
|
|
1588
|
-
// it before we destroy streaming engine. Unlike with the other components,
|
|
1589
|
-
// we do not release the instance, we will reuse it in later loads.
|
|
1590
|
-
if (this.abrManager_) {
|
|
1591
|
-
await this.abrManager_.stop();
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
// Streaming engine will push new data to media source engine, so we need
|
|
1595
|
-
// to shut it down before destroy media source engine.
|
|
1596
|
-
if (this.streamingEngine_) {
|
|
1597
|
-
await this.streamingEngine_.destroy();
|
|
1598
|
-
this.streamingEngine_ = null;
|
|
1599
|
-
}
|
|
1600
|
-
|
|
1601
|
-
if (this.playRateController_) {
|
|
1602
|
-
this.playRateController_.release();
|
|
1603
|
-
this.playRateController_ = null;
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
// Playhead is used by StreamingEngine, so we can't destroy this until after
|
|
1607
|
-
// StreamingEngine has stopped.
|
|
1608
|
-
if (this.playhead_) {
|
|
1609
|
-
this.playhead_.release();
|
|
1610
|
-
this.playhead_ = null;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
// Media source engine holds onto the media element, and in order to detach
|
|
1614
|
-
// the media keys (with drm engine), we need to break the connection between
|
|
1615
|
-
// media source engine and the media element.
|
|
1616
|
-
if (this.mediaSourceEngine_) {
|
|
1617
|
-
await this.mediaSourceEngine_.destroy();
|
|
1618
|
-
this.mediaSourceEngine_ = null;
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
if (this.adManager_) {
|
|
1622
|
-
this.adManager_.onAssetUnload();
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
// In order to unload a media element, we need to remove the src attribute
|
|
1626
|
-
// and then load again. When we destroy media source engine, this will be
|
|
1627
|
-
// done for us, but for src=, we need to do it here.
|
|
1628
|
-
//
|
|
1629
|
-
// DrmEngine requires this to be done before we destroy DrmEngine itself.
|
|
1630
|
-
if (has.mediaElement && has.mediaElement.src) {
|
|
1631
|
-
// TODO: Investigate this more. Only reproduces on Firefox 69.
|
|
1632
|
-
// Introduce a delay before detaching the video source. We are seeing
|
|
1633
|
-
// spurious Promise rejections involving an AbortError in our tests
|
|
1634
|
-
// otherwise.
|
|
1635
|
-
await new Promise(
|
|
1636
|
-
(resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
|
|
1637
|
-
|
|
1638
|
-
has.mediaElement.removeAttribute('src');
|
|
1639
|
-
has.mediaElement.load();
|
|
1640
|
-
// Remove all track nodes
|
|
1641
|
-
while (has.mediaElement.lastChild) {
|
|
1642
|
-
has.mediaElement.removeChild(has.mediaElement.firstChild);
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
if (this.drmEngine_) {
|
|
1647
|
-
await this.drmEngine_.destroy();
|
|
1648
|
-
this.drmEngine_ = null;
|
|
1649
|
-
}
|
|
1650
|
-
|
|
1651
|
-
this.assetUri_ = null;
|
|
1652
|
-
this.bufferObserver_ = null;
|
|
1653
|
-
|
|
1654
|
-
if (this.manifest_) {
|
|
1655
|
-
for (const variant of this.manifest_.variants) {
|
|
1656
|
-
for (const stream of [variant.audio, variant.video]) {
|
|
1657
|
-
if (stream && stream.segmentIndex) {
|
|
1658
|
-
stream.segmentIndex.release();
|
|
1659
|
-
}
|
|
1660
|
-
}
|
|
1661
|
-
}
|
|
1662
|
-
for (const stream of this.manifest_.textStreams) {
|
|
1663
|
-
if (stream.segmentIndex) {
|
|
1664
|
-
stream.segmentIndex.release();
|
|
1665
|
-
}
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
|
|
1669
|
-
this.manifest_ = null;
|
|
1670
|
-
this.stats_ = new shaka.util.Stats(); // Replace with a clean stats object.
|
|
1671
|
-
this.lastTextFactory_ = null;
|
|
1672
|
-
|
|
1673
|
-
// Make sure that the app knows of the new buffering state.
|
|
1674
|
-
this.updateBufferState_();
|
|
1675
|
-
}
|
|
1676
|
-
|
|
1677
|
-
/**
|
|
1678
|
-
* This should only be called by the load graph when it is time to initialize
|
|
1679
|
-
* media source engine. The only time this may be called is when we are
|
|
1680
|
-
* attached to the same media element as in the request.
|
|
1681
|
-
*
|
|
1682
|
-
* This method assumes that it is safe for it to execute. The load-graph is
|
|
1683
|
-
* responsible for ensuring all assumptions are true.
|
|
1684
|
-
*
|
|
1685
|
-
* @param {shaka.routing.Payload} has
|
|
1686
|
-
* @param {shaka.routing.Payload} wants
|
|
1687
|
-
*
|
|
1688
|
-
* @return {!Promise}
|
|
1689
|
-
* @private
|
|
1690
|
-
*/
|
|
1691
|
-
async onInitializeMediaSourceEngine_(has, wants) {
|
|
1517
|
+
async initializeMediaSourceEngineInner_() {
|
|
1692
1518
|
goog.asserts.assert(
|
|
1693
1519
|
shaka.util.Platform.supportsMediaSource(),
|
|
1694
|
-
'We should not be initializing media source on a platform that
|
|
1695
|
-
'not support media source.');
|
|
1520
|
+
'We should not be initializing media source on a platform that ' +
|
|
1521
|
+
'does not support media source.');
|
|
1696
1522
|
goog.asserts.assert(
|
|
1697
|
-
|
|
1523
|
+
this.video_,
|
|
1698
1524
|
'We should have a media element when initializing media source.');
|
|
1699
|
-
goog.asserts.assert(
|
|
1700
|
-
has.mediaElement == wants.mediaElement,
|
|
1701
|
-
'|has| and |wants| should have the same media element when ' +
|
|
1702
|
-
'initializing media source.');
|
|
1703
|
-
|
|
1704
1525
|
goog.asserts.assert(
|
|
1705
1526
|
this.mediaSourceEngine_ == null,
|
|
1706
1527
|
'We should not have a media source engine yet.');
|
|
1707
1528
|
|
|
1529
|
+
this.makeStateChangeEvent_('media-source');
|
|
1530
|
+
|
|
1708
1531
|
// When changing text visibility we need to update both the text displayer
|
|
1709
|
-
// and streaming engine because we don't always stream text. To ensure
|
|
1710
|
-
// text displayer and streaming engine are always in sync, wait
|
|
1711
|
-
// are both initialized before setting the initial value.
|
|
1532
|
+
// and streaming engine because we don't always stream text. To ensure
|
|
1533
|
+
// that the text displayer and streaming engine are always in sync, wait
|
|
1534
|
+
// until they are both initialized before setting the initial value.
|
|
1712
1535
|
const textDisplayerFactory = this.config_.textDisplayFactory;
|
|
1713
1536
|
const textDisplayer = textDisplayerFactory();
|
|
1714
1537
|
this.lastTextFactory_ = textDisplayerFactory;
|
|
1715
1538
|
|
|
1716
1539
|
const mediaSourceEngine = this.createMediaSourceEngine(
|
|
1717
|
-
|
|
1540
|
+
this.video_,
|
|
1718
1541
|
textDisplayer,
|
|
1719
1542
|
(metadata, offset, endTime) => {
|
|
1720
1543
|
this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
|
|
1721
1544
|
},
|
|
1722
|
-
this.
|
|
1545
|
+
this.lcevcDec_);
|
|
1723
1546
|
mediaSourceEngine.configure(this.config_.mediaSource);
|
|
1724
1547
|
const {segmentRelativeVttTiming} = this.config_.manifest;
|
|
1725
1548
|
mediaSourceEngine.setSegmentRelativeVttTiming(segmentRelativeVttTiming);
|
|
@@ -1733,114 +1556,51 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1733
1556
|
}
|
|
1734
1557
|
|
|
1735
1558
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1738
|
-
*
|
|
1739
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1740
|
-
* responsible for ensuring all assumptions are true.
|
|
1559
|
+
* Pick an initialize a manifest parser, then have it download and parse the
|
|
1560
|
+
* manifest.
|
|
1741
1561
|
*
|
|
1742
|
-
* @param {
|
|
1743
|
-
* @param {shaka.routing.Payload} wants
|
|
1562
|
+
* @param {?string=} mimeType
|
|
1744
1563
|
* @return {!Promise}
|
|
1745
1564
|
* @private
|
|
1746
1565
|
*/
|
|
1747
|
-
async
|
|
1566
|
+
async parseManifestInner_(mimeType) {
|
|
1748
1567
|
goog.asserts.assert(
|
|
1749
|
-
|
|
1750
|
-
'
|
|
1568
|
+
this.assetUri_,
|
|
1569
|
+
'should have a uri when making the parser.');
|
|
1751
1570
|
goog.asserts.assert(
|
|
1752
|
-
|
|
1753
|
-
'
|
|
1754
|
-
'initializing the parser.');
|
|
1755
|
-
|
|
1571
|
+
this.video_,
|
|
1572
|
+
'We should have a media element when initializing the parser.');
|
|
1756
1573
|
goog.asserts.assert(
|
|
1757
1574
|
this.networkingEngine_,
|
|
1758
1575
|
'Need networking engine when initializing the parser.');
|
|
1576
|
+
goog.asserts.assert(
|
|
1577
|
+
this.cmcdManager_,
|
|
1578
|
+
'Need CMCD manager to populate manifest request data.');
|
|
1759
1579
|
goog.asserts.assert(
|
|
1760
1580
|
this.config_,
|
|
1761
1581
|
'Need player config when initializing the parser.');
|
|
1762
1582
|
|
|
1763
|
-
//
|
|
1764
|
-
//
|
|
1765
|
-
has.mimeType = wants.mimeType;
|
|
1766
|
-
has.uri = wants.uri;
|
|
1767
|
-
|
|
1768
|
-
goog.asserts.assert(
|
|
1769
|
-
has.uri,
|
|
1770
|
-
'We should have an asset uri when initializing the parsing.');
|
|
1771
|
-
|
|
1772
|
-
// Store references to things we asserted so that we don't need to reassert
|
|
1773
|
-
// them again later.
|
|
1774
|
-
const assetUri = has.uri;
|
|
1583
|
+
// Store references to things we asserted so that we don't need to
|
|
1584
|
+
// reassert them again later.
|
|
1775
1585
|
const networkingEngine = this.networkingEngine_;
|
|
1776
1586
|
|
|
1777
|
-
|
|
1778
|
-
this.assetUri_ = assetUri;
|
|
1587
|
+
this.makeStateChangeEvent_('manifest-parser');
|
|
1779
1588
|
|
|
1780
1589
|
// Create the parser that we will use to parse the manifest.
|
|
1781
|
-
this.parserFactory_ =
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
this.config_.manifest.retryParameters,
|
|
1785
|
-
has.mimeType);
|
|
1590
|
+
this.parserFactory_ = shaka.media.ManifestParser.getFactory(
|
|
1591
|
+
this.assetUri_,
|
|
1592
|
+
mimeType || null);
|
|
1786
1593
|
goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
|
|
1787
1594
|
this.parser_ = this.parserFactory_();
|
|
1788
1595
|
|
|
1789
1596
|
const manifestConfig =
|
|
1790
1597
|
shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
|
|
1791
1598
|
// Don't read video segments if the player is attached to an audio element
|
|
1792
|
-
if (
|
|
1599
|
+
if (this.video_ && this.video_.nodeName === 'AUDIO') {
|
|
1793
1600
|
manifestConfig.disableVideo = true;
|
|
1794
1601
|
}
|
|
1795
1602
|
|
|
1796
1603
|
this.parser_.configure(manifestConfig);
|
|
1797
|
-
}
|
|
1798
|
-
|
|
1799
|
-
/**
|
|
1800
|
-
* Parse the manifest at |has.uri| using the parser that should have already
|
|
1801
|
-
* been created. This should only be called as part of the load graph.
|
|
1802
|
-
*
|
|
1803
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1804
|
-
* responsible for ensuring all assumptions are true.
|
|
1805
|
-
*
|
|
1806
|
-
* @param {shaka.routing.Payload} has
|
|
1807
|
-
* @param {shaka.routing.Payload} wants
|
|
1808
|
-
* @return {!shaka.util.AbortableOperation}
|
|
1809
|
-
* @private
|
|
1810
|
-
*/
|
|
1811
|
-
onParseManifest_(has, wants) {
|
|
1812
|
-
goog.asserts.assert(
|
|
1813
|
-
has.mimeType == wants.mimeType,
|
|
1814
|
-
'|has| and |wants| should have the same mime type when parsing.');
|
|
1815
|
-
goog.asserts.assert(
|
|
1816
|
-
has.uri == wants.uri,
|
|
1817
|
-
'|has| and |wants| should have the same uri when parsing.');
|
|
1818
|
-
|
|
1819
|
-
goog.asserts.assert(
|
|
1820
|
-
has.uri,
|
|
1821
|
-
'|has| should have a valid uri when parsing.');
|
|
1822
|
-
goog.asserts.assert(
|
|
1823
|
-
has.uri == this.assetUri_,
|
|
1824
|
-
'|has.uri| should match the cached asset uri.');
|
|
1825
|
-
|
|
1826
|
-
goog.asserts.assert(
|
|
1827
|
-
this.networkingEngine_,
|
|
1828
|
-
'Need networking engine to parse manifest.');
|
|
1829
|
-
goog.asserts.assert(
|
|
1830
|
-
this.cmcdManager_,
|
|
1831
|
-
'Need CMCD manager to populate manifest request data.');
|
|
1832
|
-
goog.asserts.assert(
|
|
1833
|
-
this.config_,
|
|
1834
|
-
'Need player config to parse manifest.');
|
|
1835
|
-
|
|
1836
|
-
goog.asserts.assert(
|
|
1837
|
-
this.parser_,
|
|
1838
|
-
'|this.parser_| should have been set in an earlier step.');
|
|
1839
|
-
|
|
1840
|
-
// Store references to things we asserted so that we don't need to reassert
|
|
1841
|
-
// them again later.
|
|
1842
|
-
const assetUri = has.uri;
|
|
1843
|
-
const networkingEngine = this.networkingEngine_;
|
|
1844
1604
|
|
|
1845
1605
|
// This will be needed by the parser once it starts parsing, so we will
|
|
1846
1606
|
// initialize it now even through it appears a little out-of-place.
|
|
@@ -1881,7 +1641,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1881
1641
|
// Called when the parser finds a timeline region. This can be called
|
|
1882
1642
|
// before we start playback or during playback (live/in-progress
|
|
1883
1643
|
// manifest).
|
|
1884
|
-
onTimelineRegionAdded: (region) =>
|
|
1644
|
+
onTimelineRegionAdded: (region) => {
|
|
1645
|
+
this.regionTimeline_.addRegion(region);
|
|
1646
|
+
},
|
|
1885
1647
|
|
|
1886
1648
|
onEvent: (event) => this.dispatchEvent(event),
|
|
1887
1649
|
onError: (error) => this.onError_(error),
|
|
@@ -1904,61 +1666,54 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1904
1666
|
this.processDrmInfos_(currentDrmInfo.keySystem, stream);
|
|
1905
1667
|
}
|
|
1906
1668
|
},
|
|
1669
|
+
onManifestUpdated: () => {
|
|
1670
|
+
const eventName = shaka.util.FakeEvent.EventName.ManifestUpdated;
|
|
1671
|
+
const data = (new Map()).set('isLive', this.isLive());
|
|
1672
|
+
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
1673
|
+
if (this.adManager_) {
|
|
1674
|
+
this.adManager_.onManifestUpdated(this.isLive());
|
|
1675
|
+
}
|
|
1676
|
+
},
|
|
1677
|
+
getBandwidthEstimate: () => this.abrManager_.getBandwidthEstimate(),
|
|
1907
1678
|
};
|
|
1908
1679
|
|
|
1909
1680
|
const startTime = Date.now() / 1000;
|
|
1910
1681
|
|
|
1911
|
-
|
|
1912
|
-
this.manifest_ = await this.parser_.start(assetUri, playerInterface);
|
|
1682
|
+
this.makeStateChangeEvent_('manifest');
|
|
1913
1683
|
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
const event =
|
|
1917
|
-
this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
|
|
1918
|
-
this.dispatchEvent(event);
|
|
1684
|
+
this.manifest_ = await this.parser_.start(
|
|
1685
|
+
this.assetUri_, playerInterface);
|
|
1919
1686
|
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
shaka.util.Error.Code.NO_VARIANTS);
|
|
1926
|
-
}
|
|
1687
|
+
// This event is fired after the manifest is parsed, but before any
|
|
1688
|
+
// filtering takes place.
|
|
1689
|
+
const event =
|
|
1690
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
|
|
1691
|
+
this.dispatchEvent(event);
|
|
1927
1692
|
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
shaka.
|
|
1693
|
+
// We require all manifests to have at least one variant.
|
|
1694
|
+
if (this.manifest_.variants.length == 0) {
|
|
1695
|
+
throw new shaka.util.Error(
|
|
1696
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1697
|
+
shaka.util.Error.Category.MANIFEST,
|
|
1698
|
+
shaka.util.Error.Code.NO_VARIANTS);
|
|
1699
|
+
}
|
|
1931
1700
|
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1701
|
+
// Make sure that all variants are either: audio-only, video-only, or
|
|
1702
|
+
// audio-video.
|
|
1703
|
+
shaka.Player.filterForAVVariants_(this.manifest_);
|
|
1704
|
+
|
|
1705
|
+
const now = Date.now() / 1000;
|
|
1706
|
+
const delta = now - startTime;
|
|
1707
|
+
this.stats_.setManifestTime(delta);
|
|
1939
1708
|
}
|
|
1940
1709
|
|
|
1941
1710
|
/**
|
|
1942
|
-
*
|
|
1943
|
-
* drmEngine. The only time this may be called is when we are attached a
|
|
1944
|
-
* media element and have parsed a manifest.
|
|
1945
|
-
*
|
|
1946
|
-
* The load-graph is responsible for ensuring all assumptions made by this
|
|
1947
|
-
* method are valid before executing it.
|
|
1711
|
+
* Initializes the DRM engine.
|
|
1948
1712
|
*
|
|
1949
|
-
* @param {shaka.routing.Payload} has
|
|
1950
|
-
* @param {shaka.routing.Payload} wants
|
|
1951
1713
|
* @return {!Promise}
|
|
1952
1714
|
* @private
|
|
1953
1715
|
*/
|
|
1954
|
-
async
|
|
1955
|
-
goog.asserts.assert(
|
|
1956
|
-
has.mimeType == wants.mimeType,
|
|
1957
|
-
'The load graph should have ensured the mime types matched.');
|
|
1958
|
-
goog.asserts.assert(
|
|
1959
|
-
has.uri == wants.uri,
|
|
1960
|
-
'The load graph should have ensured the uris matched');
|
|
1961
|
-
|
|
1716
|
+
async initializeDrmInner_() {
|
|
1962
1717
|
goog.asserts.assert(
|
|
1963
1718
|
this.networkingEngine_,
|
|
1964
1719
|
'|onInitializeDrm_| should never be called after |destroy|');
|
|
@@ -1969,9 +1724,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1969
1724
|
this.manifest_,
|
|
1970
1725
|
'|this.manifest_| should have been set in an earlier step.');
|
|
1971
1726
|
goog.asserts.assert(
|
|
1972
|
-
|
|
1727
|
+
this.video_,
|
|
1973
1728
|
'We should have a media element when initializing the DRM Engine.');
|
|
1974
1729
|
|
|
1730
|
+
this.makeStateChangeEvent_('drm-engine');
|
|
1731
|
+
|
|
1975
1732
|
const startTime = Date.now() / 1000;
|
|
1976
1733
|
let firstEvent = true;
|
|
1977
1734
|
|
|
@@ -1994,14 +1751,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1994
1751
|
const now = Date.now() / 1000;
|
|
1995
1752
|
const delta = now - startTime;
|
|
1996
1753
|
this.stats_.setDrmTime(delta);
|
|
1997
|
-
// LCEVC data by itself is not encrypted in DRM protected streams
|
|
1998
|
-
// can therefore be accessed and decoded as normal. However,
|
|
1999
|
-
// decoder needs access to the VideoElement output in
|
|
2000
|
-
// the enhancement. In DRM contexts where the
|
|
2001
|
-
// access from our decoder, the enhancement
|
|
2002
|
-
// therefore the LCEVC output canvas is
|
|
2003
|
-
|
|
2004
|
-
|
|
1754
|
+
// LCEVC data by itself is not encrypted in DRM protected streams
|
|
1755
|
+
// and can therefore be accessed and decoded as normal. However,
|
|
1756
|
+
// the LCEVC decoder needs access to the VideoElement output in
|
|
1757
|
+
// order to apply the enhancement. In DRM contexts where the
|
|
1758
|
+
// browser CDM restricts access from our decoder, the enhancement
|
|
1759
|
+
// cannot be applied and therefore the LCEVC output canvas is
|
|
1760
|
+
// hidden accordingly.
|
|
1761
|
+
if (this.lcevcDec_) {
|
|
1762
|
+
this.lcevcDec_.hideCanvas();
|
|
2005
1763
|
}
|
|
2006
1764
|
}
|
|
2007
1765
|
},
|
|
@@ -2013,66 +1771,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2013
1771
|
this.manifest_.variants,
|
|
2014
1772
|
this.manifest_.offlineSessionIds);
|
|
2015
1773
|
|
|
2016
|
-
await this.drmEngine_.attach(
|
|
1774
|
+
await this.drmEngine_.attach(this.video_);
|
|
2017
1775
|
|
|
2018
|
-
// Now that we have drm information, filter the manifest (again) so that
|
|
2019
|
-
// can ensure we only use variants with the selected key system.
|
|
1776
|
+
// Now that we have drm information, filter the manifest (again) so that
|
|
1777
|
+
// we can ensure we only use variants with the selected key system.
|
|
2020
1778
|
await this.filterManifest_(this.manifest_);
|
|
2021
1779
|
}
|
|
2022
1780
|
|
|
2023
1781
|
/**
|
|
2024
|
-
*
|
|
2025
|
-
* playback components needed for playback. The only times this may be called
|
|
2026
|
-
* is when we are attached to the same media element as in the request.
|
|
2027
|
-
*
|
|
2028
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
2029
|
-
* responsible for ensuring all assumptions are true.
|
|
2030
|
-
*
|
|
2031
|
-
* Loading is defined as:
|
|
2032
|
-
* - Attaching all playback-related listeners to the media element
|
|
2033
|
-
* - Initializing playback and observers
|
|
2034
|
-
* - Initializing ABR Manager
|
|
2035
|
-
* - Initializing Streaming Engine
|
|
2036
|
-
* - Starting playback at |wants.startTime|
|
|
1782
|
+
* Starts loading the content described by the parsed manifest.
|
|
2037
1783
|
*
|
|
2038
|
-
* @param {
|
|
2039
|
-
* @
|
|
1784
|
+
* @param {number} startTimeOfLoad
|
|
1785
|
+
* @return {!Promise}
|
|
2040
1786
|
* @private
|
|
2041
1787
|
*/
|
|
2042
|
-
async
|
|
1788
|
+
async loadInner_(startTimeOfLoad) {
|
|
2043
1789
|
goog.asserts.assert(
|
|
2044
|
-
|
|
2045
|
-
'|has| and |wants| should have the same mime type when loading.');
|
|
2046
|
-
goog.asserts.assert(
|
|
2047
|
-
has.uri == wants.uri,
|
|
2048
|
-
'|has| and |wants| should have the same uri when loading.');
|
|
2049
|
-
|
|
1790
|
+
this.video_, 'We should have a media element by now.');
|
|
2050
1791
|
goog.asserts.assert(
|
|
2051
|
-
|
|
2052
|
-
'We should have a media element when loading.');
|
|
1792
|
+
this.manifest_, 'The manifest should already be parsed.');
|
|
2053
1793
|
goog.asserts.assert(
|
|
2054
|
-
|
|
2055
|
-
'|wants| should tell us when the load was originally requested');
|
|
1794
|
+
this.assetUri_, 'We should have an asset uri by now.');
|
|
2056
1795
|
|
|
2057
|
-
|
|
2058
|
-
// something we are now depending on.
|
|
2059
|
-
has.startTime = wants.startTime;
|
|
2060
|
-
|
|
2061
|
-
// If updateStartTime() has been called since load() was invoked use the
|
|
2062
|
-
// requested startTime
|
|
2063
|
-
if (this.updatedStartTime_ != null) {
|
|
2064
|
-
has.startTime = this.updatedStartTime_;
|
|
2065
|
-
this.updatedStartTime_ = null;
|
|
2066
|
-
}
|
|
2067
|
-
|
|
2068
|
-
// Store a reference to values in |has| after asserting so that closure will
|
|
2069
|
-
// know that they will still be non-null between calls to await.
|
|
2070
|
-
const mediaElement = has.mediaElement;
|
|
2071
|
-
const assetUri = has.uri;
|
|
2072
|
-
|
|
2073
|
-
// Save the uri so that it can be used outside of the load-graph.
|
|
2074
|
-
this.assetUri_ = assetUri;
|
|
1796
|
+
this.makeStateChangeEvent_('load');
|
|
2075
1797
|
|
|
1798
|
+
const mediaElement = this.video_;
|
|
2076
1799
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
2077
1800
|
getRate: () => mediaElement.playbackRate,
|
|
2078
1801
|
getDefaultRate: () => mediaElement.defaultPlaybackRate,
|
|
@@ -2082,13 +1805,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2082
1805
|
|
|
2083
1806
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
2084
1807
|
const onRateChange = () => this.onRateChange_();
|
|
2085
|
-
this.loadEventManager_.listen(
|
|
1808
|
+
this.loadEventManager_.listen(
|
|
1809
|
+
mediaElement, 'playing', updateStateHistory);
|
|
2086
1810
|
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
2087
1811
|
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
2088
1812
|
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
2089
1813
|
|
|
2090
|
-
// Check status of the LCEVC
|
|
2091
|
-
//
|
|
1814
|
+
// Check the status of the LCEVC Dec Object. Reset, create, or close
|
|
1815
|
+
// depending on the config.
|
|
2092
1816
|
this.setupLcevc_(this.config_);
|
|
2093
1817
|
|
|
2094
1818
|
const abrFactory = this.config_.abrFactory;
|
|
@@ -2112,7 +1836,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2112
1836
|
this.config_.preferredVariantRole,
|
|
2113
1837
|
this.config_.preferredAudioChannelCount,
|
|
2114
1838
|
this.config_.preferredVideoHdrLevel,
|
|
2115
|
-
this.config_.
|
|
1839
|
+
this.config_.preferredVideoLayout,
|
|
1840
|
+
this.config_.preferredAudioLabel,
|
|
1841
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
1842
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
2116
1843
|
|
|
2117
1844
|
this.currentTextLanguage_ = this.config_.preferredTextLanguage;
|
|
2118
1845
|
this.currentTextRole_ = this.config_.preferredTextRole;
|
|
@@ -2133,7 +1860,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2133
1860
|
this.manifest_,
|
|
2134
1861
|
this.config_.preferredVideoCodecs,
|
|
2135
1862
|
this.config_.preferredAudioCodecs,
|
|
2136
|
-
this.config_.preferredAudioChannelCount,
|
|
2137
1863
|
this.config_.preferredDecodingAttributes);
|
|
2138
1864
|
|
|
2139
1865
|
this.streamingEngine_ = this.createStreamingEngine();
|
|
@@ -2175,7 +1901,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2175
1901
|
let initialVariant = null;
|
|
2176
1902
|
const activeVariant = this.streamingEngine_.getCurrentVariant();
|
|
2177
1903
|
if (!activeVariant) {
|
|
2178
|
-
initialVariant = this.chooseVariant_();
|
|
1904
|
+
initialVariant = this.chooseVariant_(/* initialSelection= */ true);
|
|
2179
1905
|
goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
|
|
2180
1906
|
}
|
|
2181
1907
|
|
|
@@ -2191,20 +1917,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2191
1917
|
await Promise.all(createSegmentIndexPromises);
|
|
2192
1918
|
}
|
|
2193
1919
|
|
|
1920
|
+
if (this.parser_ && this.parser_.onInitialVariantChosen) {
|
|
1921
|
+
this.parser_.onInitialVariantChosen(toLazyLoad);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
2194
1924
|
shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
|
|
2195
1925
|
this.config_.playRangeStart,
|
|
2196
1926
|
this.config_.playRangeEnd);
|
|
2197
1927
|
|
|
2198
|
-
this.playhead_ = this.createPlayhead(
|
|
1928
|
+
this.playhead_ = this.createPlayhead(this.startTime_);
|
|
2199
1929
|
this.playheadObservers_ =
|
|
2200
|
-
this.createPlayheadObserversForMSE_(
|
|
1930
|
+
this.createPlayheadObserversForMSE_(startTimeOfLoad);
|
|
2201
1931
|
|
|
2202
|
-
// We need to start the buffer management code near the end because it
|
|
2203
|
-
// set the initial buffering state and that depends on other
|
|
2204
|
-
// being initialized.
|
|
1932
|
+
// We need to start the buffer management code near the end because it
|
|
1933
|
+
// will set the initial buffering state and that depends on other
|
|
1934
|
+
// components being initialized.
|
|
2205
1935
|
const rebufferThreshold = Math.max(
|
|
2206
1936
|
this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
|
|
2207
|
-
this.startBufferManagement_(rebufferThreshold);
|
|
1937
|
+
this.startBufferManagement_(mediaElement, rebufferThreshold);
|
|
2208
1938
|
|
|
2209
1939
|
// Now we can switch to the initial variant.
|
|
2210
1940
|
if (!activeVariant) {
|
|
@@ -2214,14 +1944,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2214
1944
|
this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
|
|
2215
1945
|
/* clearBuffer= */ false, /* safeMargin= */ 0);
|
|
2216
1946
|
|
|
2217
|
-
// Now that we have initial streams, we may adjust the start time to
|
|
2218
|
-
// to a segment boundary.
|
|
1947
|
+
// Now that we have initial streams, we may adjust the start time to
|
|
1948
|
+
// align to a segment boundary.
|
|
2219
1949
|
if (this.config_.streaming.startAtSegmentBoundary) {
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
await this.adjustStartTime_(initialVariant, startTime);
|
|
2223
|
-
|
|
2224
|
-
this.playhead_.setStartTime(adjustedTime);
|
|
1950
|
+
this.playhead_.setStartTime(await this.adjustStartTime_(
|
|
1951
|
+
initialVariant, this.playhead_.getTime()));
|
|
2225
1952
|
}
|
|
2226
1953
|
}
|
|
2227
1954
|
|
|
@@ -2244,7 +1971,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2244
1971
|
this.setInitialTextState_(initialVariant, initialTextStream);
|
|
2245
1972
|
}
|
|
2246
1973
|
|
|
2247
|
-
// Don't initialize with a text stream unless we should be streaming
|
|
1974
|
+
// Don't initialize with a text stream unless we should be streaming
|
|
1975
|
+
// text.
|
|
2248
1976
|
if (initialTextStream && this.shouldStreamText_()) {
|
|
2249
1977
|
this.streamingEngine_.switchTextStream(initialTextStream);
|
|
2250
1978
|
}
|
|
@@ -2260,9 +1988,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2260
1988
|
this.onAbrStatusChanged_();
|
|
2261
1989
|
}
|
|
2262
1990
|
|
|
2263
|
-
//
|
|
2264
|
-
|
|
2265
|
-
// Dispatch a 'trackschanged' event now that all initial filtering is done.
|
|
1991
|
+
// Dispatch a 'trackschanged' event now that all initial filtering is
|
|
1992
|
+
// done.
|
|
2266
1993
|
this.onTracksChanged_();
|
|
2267
1994
|
|
|
2268
1995
|
// Now that we've filtered out variants that aren't compatible with the
|
|
@@ -2276,14 +2003,25 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2276
2003
|
|
|
2277
2004
|
const hasPrimary = this.manifest_.variants.some((v) => v.primary);
|
|
2278
2005
|
if (!this.config_.preferredAudioLanguage && !hasPrimary) {
|
|
2279
|
-
shaka.log.warning('No preferred audio language set.
|
|
2280
|
-
|
|
2006
|
+
shaka.log.warning('No preferred audio language set. ' +
|
|
2007
|
+
'We have chosen an arbitrary language initially');
|
|
2281
2008
|
}
|
|
2282
2009
|
|
|
2283
|
-
|
|
2010
|
+
const isLive = this.isLive();
|
|
2011
|
+
|
|
2012
|
+
if (isLive && (this.config_.streaming.liveSync ||
|
|
2284
2013
|
this.manifest_.serviceDescription)) {
|
|
2285
2014
|
const onTimeUpdate = () => this.onTimeUpdate_();
|
|
2286
2015
|
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
|
|
2016
|
+
} else if (!isLive) {
|
|
2017
|
+
const onVideoProgress = () => this.onVideoProgress_();
|
|
2018
|
+
this.loadEventManager_.listen(
|
|
2019
|
+
mediaElement, 'timeupdate', onVideoProgress);
|
|
2020
|
+
this.onVideoProgress_();
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
if (this.adManager_) {
|
|
2024
|
+
this.adManager_.onManifestUpdated(isLive);
|
|
2287
2025
|
}
|
|
2288
2026
|
|
|
2289
2027
|
this.fullyLoaded_ = true;
|
|
@@ -2291,24 +2029,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2291
2029
|
// Wait for the 'loadedmetadata' event to measure load() latency.
|
|
2292
2030
|
this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
|
|
2293
2031
|
const now = Date.now() / 1000;
|
|
2294
|
-
const delta = now -
|
|
2032
|
+
const delta = now - startTimeOfLoad;
|
|
2295
2033
|
this.stats_.setLoadLatency(delta);
|
|
2296
2034
|
});
|
|
2297
2035
|
}
|
|
2298
2036
|
|
|
2299
2037
|
/**
|
|
2300
|
-
*
|
|
2301
|
-
* drmEngine for src= playbacks.
|
|
2302
|
-
*
|
|
2303
|
-
* The load-graph is responsible for ensuring all assumptions made by this
|
|
2304
|
-
* method are valid before executing it.
|
|
2038
|
+
* Initializes the DRM engine for use by src equals.
|
|
2305
2039
|
*
|
|
2306
|
-
* @param {
|
|
2307
|
-
* @param {shaka.routing.Payload} wants
|
|
2040
|
+
* @param {string} mimeType
|
|
2308
2041
|
* @return {!Promise}
|
|
2309
2042
|
* @private
|
|
2310
2043
|
*/
|
|
2311
|
-
async
|
|
2044
|
+
async initializeSrcEqualsDrmInner_(mimeType) {
|
|
2312
2045
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2313
2046
|
|
|
2314
2047
|
goog.asserts.assert(
|
|
@@ -2346,20 +2079,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2346
2079
|
|
|
2347
2080
|
this.drmEngine_.configure(this.config_.drm);
|
|
2348
2081
|
|
|
2349
|
-
const uri = wants.uri || '';
|
|
2350
|
-
const extension = shaka.media.ManifestParser.getExtension(uri);
|
|
2351
|
-
let mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
2352
|
-
if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
|
|
2353
|
-
mimeType = 'application/vnd.apple.mpegurl';
|
|
2354
|
-
}
|
|
2355
|
-
if (!mimeType) {
|
|
2356
|
-
mimeType = 'video/mp4';
|
|
2357
|
-
}
|
|
2358
|
-
|
|
2359
2082
|
// TODO: Instead of feeding DrmEngine with Variants, we should refactor
|
|
2360
2083
|
// DrmEngine so that it takes a minimal config derived from Variants. In
|
|
2361
2084
|
// cases like this one or in removal of stored content, the details are
|
|
2362
|
-
// largely unimportant. We should have a saner way to initialize
|
|
2085
|
+
// largely unimportant. We should have a saner way to initialize
|
|
2086
|
+
// DrmEngine.
|
|
2363
2087
|
// That would also insulate DrmEngine from manifest changes in the future.
|
|
2364
2088
|
// For now, that is time-consuming and this synthetic Variant is easy, so
|
|
2365
2089
|
// I'm putting it off. Since this is only expected to be used for native
|
|
@@ -2374,12 +2098,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2374
2098
|
video: {
|
|
2375
2099
|
id: 0,
|
|
2376
2100
|
originalId: null,
|
|
2101
|
+
groupId: null,
|
|
2377
2102
|
createSegmentIndex: () => Promise.resolve(),
|
|
2378
2103
|
segmentIndex: null,
|
|
2379
|
-
mimeType:
|
|
2380
|
-
|
|
2381
|
-
codecs: wants.mimeType ?
|
|
2382
|
-
shaka.util.MimeUtils.getCodecs(wants.mimeType) : '',
|
|
2104
|
+
mimeType: mimeType ? shaka.util.MimeUtils.getBasicType(mimeType) : '',
|
|
2105
|
+
codecs: mimeType ? shaka.util.MimeUtils.getCodecs(mimeType) : '',
|
|
2383
2106
|
encrypted: true,
|
|
2384
2107
|
drmInfos: [], // Filled in by DrmEngine config.
|
|
2385
2108
|
keyIds: new Set(),
|
|
@@ -2398,6 +2121,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2398
2121
|
closedCaptions: null,
|
|
2399
2122
|
accessibilityPurpose: null,
|
|
2400
2123
|
external: false,
|
|
2124
|
+
fastSwitching: false,
|
|
2401
2125
|
},
|
|
2402
2126
|
bandwidth: 100,
|
|
2403
2127
|
allowedByApplication: true,
|
|
@@ -2408,46 +2132,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2408
2132
|
this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
|
|
2409
2133
|
await this.drmEngine_.initForPlayback(
|
|
2410
2134
|
[variant], /* offlineSessionIds= */ []);
|
|
2411
|
-
await this.drmEngine_.attach(
|
|
2135
|
+
await this.drmEngine_.attach(this.video_);
|
|
2412
2136
|
}
|
|
2413
2137
|
|
|
2414
2138
|
/**
|
|
2415
|
-
*
|
|
2416
|
-
*
|
|
2417
|
-
* is when we are attached to the same media element as in the request.
|
|
2418
|
-
*
|
|
2419
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
2420
|
-
* responsible for ensuring all assumptions are true.
|
|
2139
|
+
* Passes the asset URI along to the media element, so it can be played src
|
|
2140
|
+
* equals style.
|
|
2421
2141
|
*
|
|
2422
|
-
* @param {
|
|
2423
|
-
* @param {
|
|
2424
|
-
* @return {!
|
|
2142
|
+
* @param {number} startTimeOfLoad
|
|
2143
|
+
* @param {string} mimeType
|
|
2144
|
+
* @return {!Promise}
|
|
2425
2145
|
*
|
|
2426
2146
|
* @private
|
|
2427
2147
|
*/
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
'We should have a media element when loading.');
|
|
2432
|
-
goog.asserts.assert(
|
|
2433
|
-
wants.uri,
|
|
2434
|
-
'|has| should have a valid uri when loading.');
|
|
2148
|
+
async srcEqualsInner_(startTimeOfLoad, mimeType) {
|
|
2149
|
+
this.makeStateChangeEvent_('src-equals');
|
|
2150
|
+
|
|
2435
2151
|
goog.asserts.assert(
|
|
2436
|
-
|
|
2437
|
-
'|wants| should tell us when the load was originally requested');
|
|
2152
|
+
this.video_, 'We should have a media element when loading.');
|
|
2438
2153
|
goog.asserts.assert(
|
|
2439
|
-
this.
|
|
2440
|
-
'The video element should match our media element');
|
|
2441
|
-
|
|
2442
|
-
// Lock-in the values that we are using so that the routing logic knows what
|
|
2443
|
-
// we have.
|
|
2444
|
-
has.uri = wants.uri;
|
|
2445
|
-
has.startTime = wants.startTime;
|
|
2446
|
-
|
|
2447
|
-
// Save the uri so that it can be used outside of the load-graph.
|
|
2448
|
-
this.assetUri_ = has.uri;
|
|
2154
|
+
this.assetUri_, 'We should have a valid uri when loading.');
|
|
2449
2155
|
|
|
2450
|
-
const mediaElement =
|
|
2156
|
+
const mediaElement = this.video_;
|
|
2451
2157
|
|
|
2452
2158
|
this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
|
|
2453
2159
|
|
|
@@ -2458,8 +2164,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2458
2164
|
unloaded = true;
|
|
2459
2165
|
});
|
|
2460
2166
|
|
|
2461
|
-
if (
|
|
2462
|
-
this.playhead_.setStartTime(
|
|
2167
|
+
if (this.startTime_ != null) {
|
|
2168
|
+
this.playhead_.setStartTime(this.startTime_);
|
|
2463
2169
|
}
|
|
2464
2170
|
|
|
2465
2171
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
@@ -2469,16 +2175,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2469
2175
|
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
|
|
2470
2176
|
});
|
|
2471
2177
|
|
|
2472
|
-
// We need to start the buffer management code near the end because it
|
|
2473
|
-
// set the initial buffering state and that depends on other
|
|
2474
|
-
// being initialized.
|
|
2178
|
+
// We need to start the buffer management code near the end because it
|
|
2179
|
+
// will set the initial buffering state and that depends on other
|
|
2180
|
+
// components being initialized.
|
|
2475
2181
|
const rebufferThreshold = this.config_.streaming.rebufferingGoal;
|
|
2476
|
-
this.startBufferManagement_(rebufferThreshold);
|
|
2182
|
+
this.startBufferManagement_(mediaElement, rebufferThreshold);
|
|
2477
2183
|
|
|
2478
2184
|
// Add all media element listeners.
|
|
2479
2185
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
2480
2186
|
const onRateChange = () => this.onRateChange_();
|
|
2481
|
-
this.loadEventManager_.listen(
|
|
2187
|
+
this.loadEventManager_.listen(
|
|
2188
|
+
mediaElement, 'playing', updateStateHistory);
|
|
2482
2189
|
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
2483
2190
|
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
2484
2191
|
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
@@ -2488,24 +2195,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2488
2195
|
// automatically.
|
|
2489
2196
|
// See https://github.com/shaka-project/shaka-player/issues/2483
|
|
2490
2197
|
if (mediaElement.preload != 'none') {
|
|
2491
|
-
this.loadEventManager_.listenOnce(
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2198
|
+
this.loadEventManager_.listenOnce(
|
|
2199
|
+
mediaElement, 'loadedmetadata', () => {
|
|
2200
|
+
const now = Date.now() / 1000;
|
|
2201
|
+
const delta = now - startTimeOfLoad;
|
|
2202
|
+
this.stats_.setLoadLatency(delta);
|
|
2203
|
+
});
|
|
2496
2204
|
}
|
|
2497
2205
|
|
|
2498
2206
|
// The audio tracks are only available on Safari at the moment, but this
|
|
2499
2207
|
// drives the tracks API for Safari's native HLS. So when they change,
|
|
2500
2208
|
// fire the corresponding Shaka Player event.
|
|
2501
2209
|
if (mediaElement.audioTracks) {
|
|
2502
|
-
this.loadEventManager_.listen(
|
|
2503
|
-
|
|
2504
|
-
this.loadEventManager_.listen(
|
|
2505
|
-
|
|
2210
|
+
this.loadEventManager_.listen(mediaElement.audioTracks, 'addtrack',
|
|
2211
|
+
() => this.onTracksChanged_());
|
|
2212
|
+
this.loadEventManager_.listen(mediaElement.audioTracks, 'removetrack',
|
|
2213
|
+
() => this.onTracksChanged_());
|
|
2214
|
+
this.loadEventManager_.listen(mediaElement.audioTracks, 'change',
|
|
2506
2215
|
() => this.onTracksChanged_());
|
|
2507
|
-
this.loadEventManager_.listen(
|
|
2508
|
-
mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
|
|
2509
2216
|
}
|
|
2510
2217
|
|
|
2511
2218
|
if (mediaElement.textTracks) {
|
|
@@ -2541,13 +2248,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2541
2248
|
() => this.onTracksChanged_());
|
|
2542
2249
|
}
|
|
2543
2250
|
|
|
2544
|
-
const extension = shaka.media.ManifestParser.getExtension(has.uri);
|
|
2545
|
-
const mimeType =
|
|
2546
|
-
shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
2547
|
-
|
|
2548
2251
|
// By setting |src| we are done "loading" with src=. We don't need to set
|
|
2549
2252
|
// the current time because |playhead| will do that for us.
|
|
2550
|
-
mediaElement.src = this.cmcdManager_.appendSrcData(
|
|
2253
|
+
mediaElement.src = this.cmcdManager_.appendSrcData(
|
|
2254
|
+
this.assetUri_, mimeType);
|
|
2551
2255
|
|
|
2552
2256
|
// Tizen 3 / WebOS won't load anything unless you call load() explicitly,
|
|
2553
2257
|
// no matter the value of the preload attribute. This is harmful on some
|
|
@@ -2561,15 +2265,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2561
2265
|
// initialized.
|
|
2562
2266
|
this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
|
|
2563
2267
|
|
|
2564
|
-
// The event doesn't mean as much for src= playback, since we don't
|
|
2565
|
-
// streaming. But we should fire it in this path anyway since
|
|
2566
|
-
// applications may be expecting it as a life-cycle event.
|
|
2268
|
+
// The event doesn't mean as much for src= playback, since we don't
|
|
2269
|
+
// control streaming. But we should fire it in this path anyway since
|
|
2270
|
+
// some applications may be expecting it as a life-cycle event.
|
|
2567
2271
|
this.dispatchEvent(
|
|
2568
2272
|
this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
|
|
2569
2273
|
|
|
2570
2274
|
// The "load" Promise is resolved when we have loaded the metadata. If we
|
|
2571
|
-
// wait for the full data, that won't happen on Safari until the play
|
|
2572
|
-
// is hit.
|
|
2275
|
+
// wait for the full data, that won't happen on Safari until the play
|
|
2276
|
+
// button is hit.
|
|
2573
2277
|
const fullyLoaded = new shaka.util.PublicPromise();
|
|
2574
2278
|
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2575
2279
|
HTMLMediaElement.HAVE_METADATA,
|
|
@@ -2579,7 +2283,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2579
2283
|
fullyLoaded.resolve();
|
|
2580
2284
|
});
|
|
2581
2285
|
|
|
2582
|
-
// We can't switch to preferred languages, though, until the data is
|
|
2286
|
+
// We can't switch to preferred languages, though, until the data is
|
|
2287
|
+
// loaded.
|
|
2583
2288
|
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2584
2289
|
HTMLMediaElement.HAVE_CURRENT_DATA,
|
|
2585
2290
|
this.loadEventManager_,
|
|
@@ -2620,9 +2325,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2620
2325
|
} else if (mediaElement.preload == 'none') {
|
|
2621
2326
|
shaka.log.alwaysWarn(
|
|
2622
2327
|
'With <video preload="none">, the browser will not load anything ' +
|
|
2623
|
-
'until play() is called. We are unable to measure load latency
|
|
2624
|
-
'a meaningful way, and we cannot provide track info yet.
|
|
2625
|
-
'not use preload="none" with Shaka Player.');
|
|
2328
|
+
'until play() is called. We are unable to measure load latency ' +
|
|
2329
|
+
'in a meaningful way, and we cannot provide track info yet. ' +
|
|
2330
|
+
'Please do not use preload="none" with Shaka Player.');
|
|
2626
2331
|
// We can't wait for an event load loadedmetadata, since that will be
|
|
2627
2332
|
// blocked until a user interaction. So resolve the Promise now.
|
|
2628
2333
|
fullyLoaded.resolve();
|
|
@@ -2632,21 +2337,37 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2632
2337
|
fullyLoaded.reject(this.videoErrorToShakaError_());
|
|
2633
2338
|
});
|
|
2634
2339
|
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
}
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2340
|
+
await fullyLoaded;
|
|
2341
|
+
|
|
2342
|
+
const isLive = this.isLive();
|
|
2343
|
+
|
|
2344
|
+
if (isLive && this.config_.streaming.liveSync) {
|
|
2345
|
+
const onTimeUpdate = () => this.onTimeUpdate_();
|
|
2346
|
+
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
|
|
2347
|
+
} else if (!isLive) {
|
|
2348
|
+
const onVideoProgress = () => this.onVideoProgress_();
|
|
2349
|
+
this.loadEventManager_.listen(
|
|
2350
|
+
mediaElement, 'timeupdate', onVideoProgress);
|
|
2351
|
+
this.onVideoProgress_();
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
if (this.adManager_) {
|
|
2355
|
+
this.adManager_.onManifestUpdated(isLive);
|
|
2356
|
+
// There is no good way to detect when the manifest has been updated,
|
|
2357
|
+
// so we use seekRange().end so we can tell when it has been updated.
|
|
2358
|
+
if (isLive) {
|
|
2359
|
+
let prevSeekRangeEnd = this.seekRange().end;
|
|
2360
|
+
this.loadEventManager_.listen(mediaElement, 'progress', () => {
|
|
2361
|
+
const newSeekRangeEnd = this.seekRange().end;
|
|
2362
|
+
if (prevSeekRangeEnd != newSeekRangeEnd) {
|
|
2363
|
+
this.adManager_.onManifestUpdated(this.isLive());
|
|
2364
|
+
prevSeekRangeEnd = newSeekRangeEnd;
|
|
2365
|
+
}
|
|
2366
|
+
});
|
|
2646
2367
|
}
|
|
2368
|
+
}
|
|
2647
2369
|
|
|
2648
|
-
|
|
2649
|
-
});
|
|
2370
|
+
this.fullyLoaded_ = true;
|
|
2650
2371
|
}
|
|
2651
2372
|
|
|
2652
2373
|
/**
|
|
@@ -2839,6 +2560,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2839
2560
|
}
|
|
2840
2561
|
}
|
|
2841
2562
|
|
|
2563
|
+
/**
|
|
2564
|
+
* Releases all of the mutexes of the player. Meant for use by the tests.
|
|
2565
|
+
* @export
|
|
2566
|
+
*/
|
|
2567
|
+
releaseAllMutexes() {
|
|
2568
|
+
this.mutex_.releaseAll();
|
|
2569
|
+
}
|
|
2570
|
+
|
|
2842
2571
|
/**
|
|
2843
2572
|
* Create a new DrmEngine instance. This may be replaced by tests to create
|
|
2844
2573
|
* fake instances. Configuration and initialization will be handled after
|
|
@@ -2859,13 +2588,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2859
2588
|
* @return {!shaka.net.NetworkingEngine}
|
|
2860
2589
|
*/
|
|
2861
2590
|
createNetworkingEngine() {
|
|
2862
|
-
/** @type {function(number, number)} */
|
|
2863
|
-
const onProgressUpdated_ = (deltaTimeMs, bytesDownloaded) => {
|
|
2591
|
+
/** @type {function(number, number, boolean)} */
|
|
2592
|
+
const onProgressUpdated_ = (deltaTimeMs, bytesDownloaded, allowSwitch) => {
|
|
2864
2593
|
// In some situations, such as during offline storage, the abr manager
|
|
2865
2594
|
// might not yet exist. Therefore, we need to check if abr manager has
|
|
2866
2595
|
// been initialized before using it.
|
|
2867
2596
|
if (this.abrManager_) {
|
|
2868
|
-
this.abrManager_.segmentDownloaded(
|
|
2597
|
+
this.abrManager_.segmentDownloaded(
|
|
2598
|
+
deltaTimeMs, bytesDownloaded, allowSwitch);
|
|
2869
2599
|
}
|
|
2870
2600
|
};
|
|
2871
2601
|
/** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
|
|
@@ -2894,8 +2624,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2894
2624
|
this.cmcdManager_.applyData(type, request, context);
|
|
2895
2625
|
};
|
|
2896
2626
|
|
|
2627
|
+
|
|
2628
|
+
const onRetry_ = (type, context, newUrl, oldUrl) => {
|
|
2629
|
+
if (this.parser_ && this.parser_.banLocation) {
|
|
2630
|
+
this.parser_.banLocation(oldUrl);
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2897
2634
|
return new shaka.net.NetworkingEngine(
|
|
2898
|
-
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_
|
|
2635
|
+
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_,
|
|
2636
|
+
onRetry_);
|
|
2899
2637
|
}
|
|
2900
2638
|
|
|
2901
2639
|
/**
|
|
@@ -2979,18 +2717,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2979
2717
|
* Initialize and start the buffering system (observer and timer) so that we
|
|
2980
2718
|
* can monitor our buffer lead during playback.
|
|
2981
2719
|
*
|
|
2720
|
+
* @param {!HTMLMediaElement} mediaElement
|
|
2982
2721
|
* @param {number} rebufferingGoal
|
|
2983
2722
|
* @private
|
|
2984
2723
|
*/
|
|
2985
|
-
startBufferManagement_(rebufferingGoal) {
|
|
2724
|
+
startBufferManagement_(mediaElement, rebufferingGoal) {
|
|
2986
2725
|
goog.asserts.assert(
|
|
2987
2726
|
!this.bufferObserver_,
|
|
2988
2727
|
'No buffering observer should exist before initialization.');
|
|
2989
2728
|
|
|
2990
|
-
goog.asserts.assert(
|
|
2991
|
-
!this.bufferPoller_,
|
|
2992
|
-
'No buffer timer should exist before initialization.');
|
|
2993
|
-
|
|
2994
2729
|
// Give dummy values, will be updated below.
|
|
2995
2730
|
this.bufferObserver_ = new shaka.media.BufferingObserver(1, 2);
|
|
2996
2731
|
|
|
@@ -3000,12 +2735,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3000
2735
|
this.updateBufferingSettings_(rebufferingGoal);
|
|
3001
2736
|
this.updateBufferState_();
|
|
3002
2737
|
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
2738
|
+
this.loadEventManager_.listen(mediaElement, 'waiting',
|
|
2739
|
+
(e) => this.pollBufferState_());
|
|
2740
|
+
this.loadEventManager_.listen(mediaElement, 'stalled',
|
|
2741
|
+
(e) => this.pollBufferState_());
|
|
2742
|
+
this.loadEventManager_.listen(mediaElement, 'canplaythrough',
|
|
2743
|
+
(e) => this.pollBufferState_());
|
|
2744
|
+
this.loadEventManager_.listen(mediaElement, 'progress',
|
|
2745
|
+
(e) => this.pollBufferState_());
|
|
3009
2746
|
}
|
|
3010
2747
|
|
|
3011
2748
|
/**
|
|
@@ -3077,16 +2814,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3077
2814
|
* @param {!shaka.extern.TextDisplayer} textDisplayer
|
|
3078
2815
|
* @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)}
|
|
3079
2816
|
* onMetadata
|
|
3080
|
-
* @param {shaka.lcevc.
|
|
2817
|
+
* @param {shaka.lcevc.Dec} lcevcDec
|
|
3081
2818
|
*
|
|
3082
2819
|
* @return {!shaka.media.MediaSourceEngine}
|
|
3083
2820
|
*/
|
|
3084
|
-
createMediaSourceEngine(mediaElement, textDisplayer, onMetadata,
|
|
2821
|
+
createMediaSourceEngine(mediaElement, textDisplayer, onMetadata, lcevcDec) {
|
|
3085
2822
|
return new shaka.media.MediaSourceEngine(
|
|
3086
2823
|
mediaElement,
|
|
3087
2824
|
textDisplayer,
|
|
3088
2825
|
onMetadata,
|
|
3089
|
-
|
|
2826
|
+
lcevcDec);
|
|
3090
2827
|
}
|
|
3091
2828
|
|
|
3092
2829
|
/**
|
|
@@ -3580,17 +3317,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3580
3317
|
|
|
3581
3318
|
/**
|
|
3582
3319
|
* Get the range of time (in seconds) that seeking is allowed. If the player
|
|
3583
|
-
* has not loaded content, this will return a range
|
|
3320
|
+
* has not loaded content and the manifest is HLS, this will return a range
|
|
3321
|
+
* from 0 to 0.
|
|
3584
3322
|
*
|
|
3585
3323
|
* @return {{start: number, end: number}}
|
|
3586
3324
|
* @export
|
|
3587
3325
|
*/
|
|
3588
3326
|
seekRange() {
|
|
3589
|
-
if (!this.fullyLoaded_) {
|
|
3590
|
-
return {'start': 0, 'end': 0};
|
|
3591
|
-
}
|
|
3592
|
-
|
|
3593
3327
|
if (this.manifest_) {
|
|
3328
|
+
// With HLS lazy-loading, there were some situations where the manifest
|
|
3329
|
+
// had partially loaded, enough to move onto further load stages, but no
|
|
3330
|
+
// segments had been loaded, so the timeline is still unknown.
|
|
3331
|
+
// See: https://github.com/shaka-project/shaka-player/pull/4590
|
|
3332
|
+
if (!this.fullyLoaded_ &&
|
|
3333
|
+
this.manifest_.type == shaka.media.ManifestParser.HLS) {
|
|
3334
|
+
return {'start': 0, 'end': 0};
|
|
3335
|
+
}
|
|
3594
3336
|
const timeline = this.manifest_.presentationTimeline;
|
|
3595
3337
|
|
|
3596
3338
|
return {
|
|
@@ -3894,13 +3636,82 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3894
3636
|
* @export
|
|
3895
3637
|
*/
|
|
3896
3638
|
getImageTracks() {
|
|
3639
|
+
const StreamUtils = shaka.util.StreamUtils;
|
|
3640
|
+
let imageStreams = this.externalSrcEqualsThumbnailsStreams_;
|
|
3897
3641
|
if (this.manifest_) {
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
3902
|
-
|
|
3642
|
+
imageStreams = this.manifest_.imageStreams;
|
|
3643
|
+
}
|
|
3644
|
+
return imageStreams.map((image) => StreamUtils.imageStreamToTrack(image));
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
/**
|
|
3648
|
+
* Returns Thumbnail objects for each thumbnail for a given image track ID.
|
|
3649
|
+
*
|
|
3650
|
+
* If the player has not loaded content, this will return a null.
|
|
3651
|
+
*
|
|
3652
|
+
* @param {number} trackId
|
|
3653
|
+
* @return {!Promise.<?Array<!shaka.extern.Thumbnail>>}
|
|
3654
|
+
* @export
|
|
3655
|
+
*/
|
|
3656
|
+
async getAllThumbnails(trackId) {
|
|
3657
|
+
if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE &&
|
|
3658
|
+
this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) {
|
|
3659
|
+
return null;
|
|
3660
|
+
}
|
|
3661
|
+
let imageStreams = this.externalSrcEqualsThumbnailsStreams_;
|
|
3662
|
+
if (this.manifest_) {
|
|
3663
|
+
imageStreams = this.manifest_.imageStreams;
|
|
3664
|
+
}
|
|
3665
|
+
const imageStream = imageStreams.find(
|
|
3666
|
+
(stream) => stream.id == trackId);
|
|
3667
|
+
if (!imageStream) {
|
|
3668
|
+
return null;
|
|
3669
|
+
}
|
|
3670
|
+
if (!imageStream.segmentIndex) {
|
|
3671
|
+
await imageStream.createSegmentIndex();
|
|
3672
|
+
}
|
|
3673
|
+
const promises = [];
|
|
3674
|
+
imageStream.segmentIndex.forEachTopLevelReference((reference) => {
|
|
3675
|
+
const dimensions = this.parseTilesLayout_(
|
|
3676
|
+
reference.getTilesLayout() || imageStream.tilesLayout);
|
|
3677
|
+
if (dimensions) {
|
|
3678
|
+
const numThumbnails = dimensions.rows * dimensions.columns;
|
|
3679
|
+
const duration = reference.trueEndTime - reference.startTime;
|
|
3680
|
+
for (let i = 0; i < numThumbnails; i++) {
|
|
3681
|
+
const sampleTime = reference.startTime + duration * i / numThumbnails;
|
|
3682
|
+
promises.push(this.getThumbnails(trackId, sampleTime));
|
|
3683
|
+
}
|
|
3684
|
+
}
|
|
3685
|
+
});
|
|
3686
|
+
const thumbnails = await Promise.all(promises);
|
|
3687
|
+
return thumbnails.filter((t) => t);
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
/**
|
|
3691
|
+
* Parses a tiles layout.
|
|
3692
|
+
*
|
|
3693
|
+
* @param {string|undefined} tilesLayout
|
|
3694
|
+
* @return {?{
|
|
3695
|
+
* columns: number,
|
|
3696
|
+
* rows: number
|
|
3697
|
+
* }}
|
|
3698
|
+
* @private
|
|
3699
|
+
*/
|
|
3700
|
+
parseTilesLayout_(tilesLayout) {
|
|
3701
|
+
if (!tilesLayout) {
|
|
3702
|
+
return null;
|
|
3703
|
+
}
|
|
3704
|
+
// This expression is used to detect one or more numbers (0-9) followed
|
|
3705
|
+
// by an x and after one or more numbers (0-9)
|
|
3706
|
+
const match = /(\d+)x(\d+)/.exec(tilesLayout);
|
|
3707
|
+
if (!match) {
|
|
3708
|
+
shaka.log.warning('Tiles layout does not contain a valid format ' +
|
|
3709
|
+
' (columns x rows)');
|
|
3710
|
+
return null;
|
|
3903
3711
|
}
|
|
3712
|
+
const columns = parseInt(match[1], 10);
|
|
3713
|
+
const rows = parseInt(match[2], 10);
|
|
3714
|
+
return {columns, rows};
|
|
3904
3715
|
}
|
|
3905
3716
|
|
|
3906
3717
|
/**
|
|
@@ -3914,84 +3725,83 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3914
3725
|
* @export
|
|
3915
3726
|
*/
|
|
3916
3727
|
async getThumbnails(trackId, time) {
|
|
3728
|
+
if (this.loadMode_ != shaka.Player.LoadMode.MEDIA_SOURCE &&
|
|
3729
|
+
this.loadMode_ != shaka.Player.LoadMode.SRC_EQUALS) {
|
|
3730
|
+
return null;
|
|
3731
|
+
}
|
|
3732
|
+
let imageStreams = this.externalSrcEqualsThumbnailsStreams_;
|
|
3917
3733
|
if (this.manifest_) {
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
|
|
3959
|
-
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
3965
|
-
|
|
3966
|
-
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
sprite = true;
|
|
3976
|
-
height = thumbnailSprite.height;
|
|
3977
|
-
positionX = thumbnailSprite.positionX;
|
|
3978
|
-
positionY = thumbnailSprite.positionY;
|
|
3979
|
-
width = thumbnailSprite.width;
|
|
3980
|
-
}
|
|
3981
|
-
return {
|
|
3982
|
-
imageHeight: fullImageHeight,
|
|
3983
|
-
imageWidth: fullImageWidth,
|
|
3984
|
-
height: height,
|
|
3985
|
-
positionX: positionX,
|
|
3986
|
-
positionY: positionY,
|
|
3987
|
-
startTime: thumbnailTime,
|
|
3988
|
-
duration: thumbnailDuration,
|
|
3989
|
-
uris: reference.getUris(),
|
|
3990
|
-
width: width,
|
|
3991
|
-
sprite: sprite,
|
|
3992
|
-
};
|
|
3734
|
+
imageStreams = this.manifest_.imageStreams;
|
|
3735
|
+
}
|
|
3736
|
+
const imageStream = imageStreams.find(
|
|
3737
|
+
(stream) => stream.id == trackId);
|
|
3738
|
+
if (!imageStream) {
|
|
3739
|
+
return null;
|
|
3740
|
+
}
|
|
3741
|
+
if (!imageStream.segmentIndex) {
|
|
3742
|
+
await imageStream.createSegmentIndex();
|
|
3743
|
+
}
|
|
3744
|
+
const referencePosition = imageStream.segmentIndex.find(time);
|
|
3745
|
+
if (referencePosition == null) {
|
|
3746
|
+
return null;
|
|
3747
|
+
}
|
|
3748
|
+
const reference = imageStream.segmentIndex.get(referencePosition);
|
|
3749
|
+
const dimensions = this.parseTilesLayout_(
|
|
3750
|
+
reference.getTilesLayout() || imageStream.tilesLayout);
|
|
3751
|
+
if (!dimensions) {
|
|
3752
|
+
return null;
|
|
3753
|
+
}
|
|
3754
|
+
const fullImageWidth = imageStream.width || 0;
|
|
3755
|
+
const fullImageHeight = imageStream.height || 0;
|
|
3756
|
+
let width = fullImageWidth / dimensions.columns;
|
|
3757
|
+
let height = fullImageHeight / dimensions.rows;
|
|
3758
|
+
const totalImages = dimensions.columns * dimensions.rows;
|
|
3759
|
+
const segmentDuration = reference.trueEndTime - reference.startTime;
|
|
3760
|
+
const thumbnailDuration =
|
|
3761
|
+
reference.getTileDuration() || (segmentDuration / totalImages);
|
|
3762
|
+
let thumbnailTime = reference.startTime;
|
|
3763
|
+
let positionX = 0;
|
|
3764
|
+
let positionY = 0;
|
|
3765
|
+
// If the number of images in the segment is greater than 1, we have to
|
|
3766
|
+
// find the correct image. For that we will return to the app the
|
|
3767
|
+
// coordinates of the position of the correct image.
|
|
3768
|
+
// Image search is always from left to right and top to bottom.
|
|
3769
|
+
// Note: The time between images within the segment is always
|
|
3770
|
+
// equidistant.
|
|
3771
|
+
//
|
|
3772
|
+
// Eg: Total images 5, tileLayout 5x1, segmentDuration 5, thumbnailTime 2
|
|
3773
|
+
// positionX = 0.4 * fullImageWidth
|
|
3774
|
+
// positionY = 0
|
|
3775
|
+
if (totalImages > 1) {
|
|
3776
|
+
const thumbnailPosition =
|
|
3777
|
+
Math.floor((time - reference.startTime) / thumbnailDuration);
|
|
3778
|
+
thumbnailTime = reference.startTime +
|
|
3779
|
+
(thumbnailPosition * thumbnailDuration);
|
|
3780
|
+
positionX = (thumbnailPosition % dimensions.columns) * width;
|
|
3781
|
+
positionY = Math.floor(thumbnailPosition / dimensions.columns) * height;
|
|
3782
|
+
}
|
|
3783
|
+
let sprite = false;
|
|
3784
|
+
const thumbnailSprite = reference.getThumbnailSprite();
|
|
3785
|
+
if (thumbnailSprite) {
|
|
3786
|
+
sprite = true;
|
|
3787
|
+
height = thumbnailSprite.height;
|
|
3788
|
+
positionX = thumbnailSprite.positionX;
|
|
3789
|
+
positionY = thumbnailSprite.positionY;
|
|
3790
|
+
width = thumbnailSprite.width;
|
|
3993
3791
|
}
|
|
3994
|
-
return
|
|
3792
|
+
return {
|
|
3793
|
+
segment: reference,
|
|
3794
|
+
imageHeight: fullImageHeight,
|
|
3795
|
+
imageWidth: fullImageWidth,
|
|
3796
|
+
height: height,
|
|
3797
|
+
positionX: positionX,
|
|
3798
|
+
positionY: positionY,
|
|
3799
|
+
startTime: thumbnailTime,
|
|
3800
|
+
duration: thumbnailDuration,
|
|
3801
|
+
uris: reference.getUris(),
|
|
3802
|
+
width: width,
|
|
3803
|
+
sprite: sprite,
|
|
3804
|
+
};
|
|
3995
3805
|
}
|
|
3996
3806
|
|
|
3997
3807
|
/**
|
|
@@ -4108,7 +3918,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4108
3918
|
// When track is selected, back-propagate the language to
|
|
4109
3919
|
// currentAudioLanguage_.
|
|
4110
3920
|
this.currentAdaptationSetCriteria_ = new shaka.media.ExampleBasedCriteria(
|
|
4111
|
-
variant
|
|
3921
|
+
variant,
|
|
3922
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
3923
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
4112
3924
|
|
|
4113
3925
|
// Update AbrManager variants to match these new settings.
|
|
4114
3926
|
this.updateAbrManagerVariants_();
|
|
@@ -4184,8 +3996,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4184
3996
|
selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0) {
|
|
4185
3997
|
if (this.manifest_ && this.playhead_) {
|
|
4186
3998
|
this.currentAdaptationSetCriteria_ =
|
|
4187
|
-
new shaka.media.PreferenceBasedCriteria(
|
|
4188
|
-
|
|
3999
|
+
new shaka.media.PreferenceBasedCriteria(
|
|
4000
|
+
language,
|
|
4001
|
+
role || '',
|
|
4002
|
+
channelsCount,
|
|
4003
|
+
/* hdrLevel= */ '',
|
|
4004
|
+
/* videoLayout= */ '',
|
|
4005
|
+
/* label= */ '',
|
|
4006
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
4007
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
4189
4008
|
|
|
4190
4009
|
const diff = (a, b) => {
|
|
4191
4010
|
if (!a.video && !b.video) {
|
|
@@ -4302,7 +4121,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4302
4121
|
// label have the same language.
|
|
4303
4122
|
this.currentAdaptationSetCriteria_ =
|
|
4304
4123
|
new shaka.media.PreferenceBasedCriteria(
|
|
4305
|
-
firstVariantWithLabel.language,
|
|
4124
|
+
firstVariantWithLabel.language,
|
|
4125
|
+
/* role= */ '',
|
|
4126
|
+
/* channelCount= */ 0,
|
|
4127
|
+
/* hdrLevel= */ '',
|
|
4128
|
+
/* videoLayout= */ '',
|
|
4129
|
+
label,
|
|
4130
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
4131
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
4306
4132
|
|
|
4307
4133
|
this.chooseVariantAndSwitch_(clearBuffer, safeMargin);
|
|
4308
4134
|
} else if (this.video_ && this.video_.audioTracks) {
|
|
@@ -4330,7 +4156,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4330
4156
|
isTextTrackVisible() {
|
|
4331
4157
|
const expected = this.isTextVisible_;
|
|
4332
4158
|
|
|
4333
|
-
if (this.mediaSourceEngine_
|
|
4159
|
+
if (this.mediaSourceEngine_ &&
|
|
4160
|
+
this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
|
|
4334
4161
|
// Make sure our values are still in-sync.
|
|
4335
4162
|
const actual = this.mediaSourceEngine_.getTextDisplayer().isTextVisible();
|
|
4336
4163
|
goog.asserts.assert(
|
|
@@ -4371,6 +4198,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4371
4198
|
* @export
|
|
4372
4199
|
*/
|
|
4373
4200
|
getChapters(language) {
|
|
4201
|
+
if (!this.video_ || !this.video_.src || !this.video_.textTracks) {
|
|
4202
|
+
return [];
|
|
4203
|
+
}
|
|
4374
4204
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
4375
4205
|
const inputlanguage = LanguageUtils.normalize(language);
|
|
4376
4206
|
const chaptersTracks = this.getChaptersTracks_();
|
|
@@ -4529,22 +4359,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4529
4359
|
return null;
|
|
4530
4360
|
}
|
|
4531
4361
|
|
|
4532
|
-
const walkerPayload = this.walker_.getCurrentPayload();
|
|
4533
|
-
|
|
4534
4362
|
let presentationTime = 0;
|
|
4535
4363
|
if (this.playhead_) {
|
|
4536
4364
|
presentationTime = this.playhead_.getTime();
|
|
4537
|
-
} else if (
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
presentationTime = walkerPayload.startTime;
|
|
4547
|
-
}
|
|
4365
|
+
} else if (this.startTime_ == null) {
|
|
4366
|
+
// A live stream with no requested start time and no playhead yet. We
|
|
4367
|
+
// would start at the live edge, but we don't have that yet, so return
|
|
4368
|
+
// the current date & time.
|
|
4369
|
+
return new Date();
|
|
4370
|
+
} else {
|
|
4371
|
+
// A specific start time has been requested. This is what Playhead will
|
|
4372
|
+
// use once it is created.
|
|
4373
|
+
presentationTime = this.startTime_;
|
|
4548
4374
|
}
|
|
4549
4375
|
|
|
4550
4376
|
if (this.manifest_) {
|
|
@@ -4759,9 +4585,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4759
4585
|
|
|
4760
4586
|
let adCuePoints = [];
|
|
4761
4587
|
if (this.adManager_) {
|
|
4762
|
-
|
|
4763
|
-
adCuePoints = this.adManager_.getServerSideCuePoints();
|
|
4764
|
-
} catch (error) {}
|
|
4588
|
+
adCuePoints = this.adManager_.getCuePoints();
|
|
4765
4589
|
}
|
|
4766
4590
|
|
|
4767
4591
|
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
@@ -4792,7 +4616,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4792
4616
|
|
|
4793
4617
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
4794
4618
|
|
|
4795
|
-
|
|
4619
|
+
let duration = this.video_.duration;
|
|
4620
|
+
if (this.manifest_) {
|
|
4621
|
+
duration = this.manifest_.presentationTimeline.getDuration();
|
|
4622
|
+
}
|
|
4796
4623
|
if (duration == Infinity) {
|
|
4797
4624
|
throw new shaka.util.Error(
|
|
4798
4625
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
@@ -4816,6 +4643,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4816
4643
|
const stream = {
|
|
4817
4644
|
id: this.nextExternalStreamId_++,
|
|
4818
4645
|
originalId: null,
|
|
4646
|
+
groupId: null,
|
|
4819
4647
|
createSegmentIndex: () => Promise.resolve(),
|
|
4820
4648
|
segmentIndex: shaka.media.SegmentIndex.forSingleSegment(
|
|
4821
4649
|
/* startTime= */ 0,
|
|
@@ -4842,6 +4670,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4842
4670
|
closedCaptions: null,
|
|
4843
4671
|
accessibilityPurpose: null,
|
|
4844
4672
|
external: true,
|
|
4673
|
+
fastSwitching: false,
|
|
4845
4674
|
};
|
|
4846
4675
|
|
|
4847
4676
|
const fullMimeType = shaka.util.MimeUtils.getFullType(
|
|
@@ -4885,14 +4714,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4885
4714
|
shaka.util.Error.Code.CONTENT_NOT_LOADED);
|
|
4886
4715
|
}
|
|
4887
4716
|
|
|
4888
|
-
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
4889
|
-
shaka.log.error('Cannot add this thumbnail track when loaded with src=');
|
|
4890
|
-
throw new shaka.util.Error(
|
|
4891
|
-
shaka.util.Error.Severity.RECOVERABLE,
|
|
4892
|
-
shaka.util.Error.Category.TEXT,
|
|
4893
|
-
shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_SRC_EQUALS);
|
|
4894
|
-
}
|
|
4895
|
-
|
|
4896
4717
|
if (!mimeType) {
|
|
4897
4718
|
mimeType = await this.getTextMimetype_(uri);
|
|
4898
4719
|
}
|
|
@@ -4906,8 +4727,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4906
4727
|
}
|
|
4907
4728
|
|
|
4908
4729
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
4909
|
-
|
|
4910
|
-
|
|
4730
|
+
let duration = this.video_.duration;
|
|
4731
|
+
if (this.manifest_) {
|
|
4732
|
+
duration = this.manifest_.presentationTimeline.getDuration();
|
|
4733
|
+
}
|
|
4911
4734
|
if (duration == Infinity) {
|
|
4912
4735
|
throw new shaka.util.Error(
|
|
4913
4736
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
@@ -4937,7 +4760,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4937
4760
|
vttOffset: 0,
|
|
4938
4761
|
};
|
|
4939
4762
|
const data = shaka.util.BufferUtils.toUint8(buffer);
|
|
4940
|
-
const cues = TextParser.parseMedia(data, time);
|
|
4763
|
+
const cues = TextParser.parseMedia(data, time, uri);
|
|
4941
4764
|
|
|
4942
4765
|
const references = [];
|
|
4943
4766
|
for (const cue of cues) {
|
|
@@ -4972,6 +4795,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4972
4795
|
const stream = {
|
|
4973
4796
|
id: this.nextExternalStreamId_++,
|
|
4974
4797
|
originalId: null,
|
|
4798
|
+
groupId: null,
|
|
4975
4799
|
createSegmentIndex: () => Promise.resolve(),
|
|
4976
4800
|
segmentIndex: new shaka.media.SegmentIndex(references),
|
|
4977
4801
|
mimeType: mimeType || '',
|
|
@@ -4996,9 +4820,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4996
4820
|
tilesLayout: '1x1',
|
|
4997
4821
|
accessibilityPurpose: null,
|
|
4998
4822
|
external: true,
|
|
4823
|
+
fastSwitching: false,
|
|
4999
4824
|
};
|
|
5000
4825
|
|
|
5001
|
-
this.
|
|
4826
|
+
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
4827
|
+
this.externalSrcEqualsThumbnailsStreams_.push(stream);
|
|
4828
|
+
} else {
|
|
4829
|
+
this.manifest_.imageStreams.push(stream);
|
|
4830
|
+
}
|
|
5002
4831
|
this.onTracksChanged_();
|
|
5003
4832
|
return shaka.util.StreamUtils.imageStreamToTrack(stream);
|
|
5004
4833
|
}
|
|
@@ -5033,9 +4862,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5033
4862
|
|
|
5034
4863
|
let adCuePoints = [];
|
|
5035
4864
|
if (this.adManager_) {
|
|
5036
|
-
|
|
5037
|
-
adCuePoints = this.adManager_.getServerSideCuePoints();
|
|
5038
|
-
} catch (error) {}
|
|
4865
|
+
adCuePoints = this.adManager_.getCuePoints();
|
|
5039
4866
|
}
|
|
5040
4867
|
|
|
5041
4868
|
/** @type {!HTMLTrackElement} */
|
|
@@ -5062,6 +4889,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5062
4889
|
});
|
|
5063
4890
|
});
|
|
5064
4891
|
|
|
4892
|
+
this.onTracksChanged_();
|
|
4893
|
+
|
|
5065
4894
|
return chaptersTrack;
|
|
5066
4895
|
}
|
|
5067
4896
|
|
|
@@ -5080,19 +4909,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5080
4909
|
* @private
|
|
5081
4910
|
*/
|
|
5082
4911
|
async getTextMimetype_(uri) {
|
|
5083
|
-
|
|
5084
|
-
const extension = shaka.media.ManifestParser.getExtension(uri);
|
|
5085
|
-
let mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
5086
|
-
|
|
5087
|
-
if (mimeType) {
|
|
5088
|
-
return mimeType;
|
|
5089
|
-
}
|
|
5090
|
-
|
|
4912
|
+
let mimeType;
|
|
5091
4913
|
try {
|
|
5092
4914
|
goog.asserts.assert(
|
|
5093
4915
|
this.networkingEngine_, 'Need networking engine.');
|
|
5094
4916
|
// eslint-disable-next-line require-atomic-updates
|
|
5095
|
-
mimeType = await shaka.
|
|
4917
|
+
mimeType = await shaka.net.NetworkingUtils.getMimeType(uri,
|
|
5096
4918
|
this.networkingEngine_,
|
|
5097
4919
|
this.config_.streaming.retryParameters);
|
|
5098
4920
|
} catch (error) {}
|
|
@@ -5103,12 +4925,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5103
4925
|
|
|
5104
4926
|
shaka.log.error(
|
|
5105
4927
|
'The mimeType has not been provided and it could not be deduced ' +
|
|
5106
|
-
'from its
|
|
4928
|
+
'from its uri.');
|
|
5107
4929
|
throw new shaka.util.Error(
|
|
5108
4930
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
5109
4931
|
shaka.util.Error.Category.TEXT,
|
|
5110
4932
|
shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
|
|
5111
|
-
|
|
4933
|
+
uri);
|
|
5112
4934
|
}
|
|
5113
4935
|
|
|
5114
4936
|
/**
|
|
@@ -5197,7 +5019,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5197
5019
|
vttOffset: 0,
|
|
5198
5020
|
};
|
|
5199
5021
|
const data = shaka.util.BufferUtils.toUint8(buffer);
|
|
5200
|
-
const cues = obj.parseMedia(data, time);
|
|
5022
|
+
const cues = obj.parseMedia(data, time, /* uri= */ null);
|
|
5201
5023
|
return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
|
|
5202
5024
|
}
|
|
5203
5025
|
throw new shaka.util.Error(
|
|
@@ -5316,7 +5138,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5316
5138
|
return new shaka.text.UITextDisplayer(
|
|
5317
5139
|
this.video_, this.videoContainer_);
|
|
5318
5140
|
} else {
|
|
5319
|
-
|
|
5141
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
5142
|
+
if (HTMLMediaElement.prototype.addTextTrack) {
|
|
5143
|
+
return new shaka.text.SimpleTextDisplayer(this.video_);
|
|
5144
|
+
} else {
|
|
5145
|
+
shaka.log.warning('Text tracks are not supported by the ' +
|
|
5146
|
+
'browser, disabling.');
|
|
5147
|
+
return new shaka.text.StubTextDisplayer();
|
|
5148
|
+
}
|
|
5320
5149
|
}
|
|
5321
5150
|
};
|
|
5322
5151
|
return config;
|
|
@@ -5396,6 +5225,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5396
5225
|
const textStream = {
|
|
5397
5226
|
id: this.nextExternalStreamId_++, // A globally unique ID.
|
|
5398
5227
|
originalId: id, // The CC ID string, like 'CC1', 'CC3', etc.
|
|
5228
|
+
groupId: null,
|
|
5399
5229
|
createSegmentIndex: () => Promise.resolve(),
|
|
5400
5230
|
segmentIndex,
|
|
5401
5231
|
mimeType,
|
|
@@ -5419,6 +5249,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5419
5249
|
closedCaptions: null,
|
|
5420
5250
|
accessibilityPurpose: null,
|
|
5421
5251
|
external: false,
|
|
5252
|
+
fastSwitching: false,
|
|
5422
5253
|
};
|
|
5423
5254
|
manifest.textStreams.push(textStream);
|
|
5424
5255
|
closedCaptionsSet.add(id);
|
|
@@ -5447,14 +5278,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5447
5278
|
*/
|
|
5448
5279
|
async filterManifestWithStreamUtils_(manifest) {
|
|
5449
5280
|
goog.asserts.assert(manifest, 'Manifest should exist!');
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
/** @type {?shaka.extern.Variant} */
|
|
5453
|
-
const currentVariant = this.streamingEngine_ ?
|
|
5454
|
-
this.streamingEngine_.getCurrentVariant() : null;
|
|
5455
|
-
|
|
5456
|
-
await shaka.util.StreamUtils.filterManifest(
|
|
5457
|
-
this.drmEngine_, currentVariant, manifest);
|
|
5281
|
+
await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest);
|
|
5458
5282
|
this.checkPlayableVariants_(manifest);
|
|
5459
5283
|
}
|
|
5460
5284
|
|
|
@@ -5511,21 +5335,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5511
5335
|
}
|
|
5512
5336
|
}
|
|
5513
5337
|
|
|
5514
|
-
/**
|
|
5515
|
-
* @private
|
|
5516
|
-
*/
|
|
5517
|
-
filterManifestByCurrentVariant_() {
|
|
5518
|
-
goog.asserts.assert(this.manifest_, 'Manifest should be valid');
|
|
5519
|
-
goog.asserts.assert(this.streamingEngine_,
|
|
5520
|
-
'StreamingEngine should be valid');
|
|
5521
|
-
|
|
5522
|
-
const currentVariant = this.streamingEngine_ ?
|
|
5523
|
-
this.streamingEngine_.getCurrentVariant() : null;
|
|
5524
|
-
shaka.util.StreamUtils.filterManifestByCurrentVariant(currentVariant,
|
|
5525
|
-
this.manifest_);
|
|
5526
|
-
this.checkPlayableVariants_(this.manifest_);
|
|
5527
|
-
}
|
|
5528
|
-
|
|
5529
5338
|
/**
|
|
5530
5339
|
* @param {shaka.extern.Variant} initialVariant
|
|
5531
5340
|
* @param {number} time
|
|
@@ -5594,7 +5403,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5594
5403
|
const loaded = this.stats_ && this.bufferObserver_ && this.playhead_;
|
|
5595
5404
|
|
|
5596
5405
|
if (loaded) {
|
|
5597
|
-
this.playRateController_.setBuffering(isBuffering);
|
|
5598
5406
|
if (this.cmcdManager_) {
|
|
5599
5407
|
this.cmcdManager_.setBuffering(isBuffering);
|
|
5600
5408
|
}
|
|
@@ -5661,14 +5469,20 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5661
5469
|
|
|
5662
5470
|
const history = this.stats_.getStateHistory();
|
|
5663
5471
|
|
|
5472
|
+
let updateState = 'playing';
|
|
5664
5473
|
if (this.bufferObserver_.getState() == State.STARVING) {
|
|
5665
|
-
|
|
5474
|
+
updateState = 'buffering';
|
|
5666
5475
|
} else if (this.video_.paused) {
|
|
5667
|
-
|
|
5476
|
+
updateState = 'paused';
|
|
5668
5477
|
} else if (this.video_.ended) {
|
|
5669
|
-
|
|
5670
|
-
}
|
|
5671
|
-
|
|
5478
|
+
updateState = 'ended';
|
|
5479
|
+
}
|
|
5480
|
+
const stateChanged = history.update(updateState);
|
|
5481
|
+
|
|
5482
|
+
if (stateChanged) {
|
|
5483
|
+
const eventName = shaka.util.FakeEvent.EventName.StateChanged;
|
|
5484
|
+
const data = (new Map()).set('newstate', updateState);
|
|
5485
|
+
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
5672
5486
|
}
|
|
5673
5487
|
}
|
|
5674
5488
|
|
|
@@ -5709,6 +5523,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5709
5523
|
}
|
|
5710
5524
|
}
|
|
5711
5525
|
|
|
5526
|
+
let liveSyncMinLatency;
|
|
5527
|
+
let liveSyncMinPlaybackRate;
|
|
5528
|
+
if (this.config_.streaming.liveSync) {
|
|
5529
|
+
liveSyncMinLatency = this.config_.streaming.liveSyncMinLatency;
|
|
5530
|
+
liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
|
|
5531
|
+
} else {
|
|
5532
|
+
// serviceDescription must override if it is defined in the MPD and
|
|
5533
|
+
// liveSync configuration is not set.
|
|
5534
|
+
if (this.manifest_ && this.manifest_.serviceDescription) {
|
|
5535
|
+
liveSyncMinLatency = this.manifest_.serviceDescription.minLatency ||
|
|
5536
|
+
this.config_.streaming.liveSyncMinLatency;
|
|
5537
|
+
liveSyncMinPlaybackRate =
|
|
5538
|
+
this.manifest_.serviceDescription.minPlaybackRate ||
|
|
5539
|
+
this.config_.streaming.liveSyncMinPlaybackRate;
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
|
|
5712
5543
|
const playbackRate = this.video_.playbackRate;
|
|
5713
5544
|
const latency = seekRange.end - this.video_.currentTime;
|
|
5714
5545
|
let offset = 0;
|
|
@@ -5732,11 +5563,62 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5732
5563
|
'Updating playbackRate to ' + liveSyncPlaybackRate);
|
|
5733
5564
|
this.trickPlay(liveSyncPlaybackRate);
|
|
5734
5565
|
}
|
|
5735
|
-
} else if (
|
|
5566
|
+
} else if (liveSyncMinLatency && liveSyncMinPlaybackRate &&
|
|
5567
|
+
(latency - offset) < liveSyncMinLatency) {
|
|
5568
|
+
if (playbackRate != liveSyncMinPlaybackRate) {
|
|
5569
|
+
shaka.log.debug('Latency (' + latency + 's) ' +
|
|
5570
|
+
'is smaller than liveSyncMinLatency (' + liveSyncMinLatency + 's). ' +
|
|
5571
|
+
'Updating playbackRate to ' + liveSyncMinPlaybackRate);
|
|
5572
|
+
this.trickPlay(liveSyncMinPlaybackRate);
|
|
5573
|
+
}
|
|
5574
|
+
} else if (playbackRate !== this.playRateController_.getDefaultRate()) {
|
|
5736
5575
|
this.cancelTrickPlay();
|
|
5737
5576
|
}
|
|
5738
5577
|
}
|
|
5739
5578
|
|
|
5579
|
+
/**
|
|
5580
|
+
* Callback for video progress events
|
|
5581
|
+
*
|
|
5582
|
+
* @private
|
|
5583
|
+
*/
|
|
5584
|
+
onVideoProgress_() {
|
|
5585
|
+
if (!this.video_) {
|
|
5586
|
+
return;
|
|
5587
|
+
}
|
|
5588
|
+
let hasNewCompletionPercent = false;
|
|
5589
|
+
const completionRatio = this.video_.currentTime / this.video_.duration;
|
|
5590
|
+
if (!isNaN(completionRatio)) {
|
|
5591
|
+
const percent = Math.round(100 * completionRatio);
|
|
5592
|
+
if (isNaN(this.completionPercent_)) {
|
|
5593
|
+
this.completionPercent_ = percent;
|
|
5594
|
+
hasNewCompletionPercent = true;
|
|
5595
|
+
} else {
|
|
5596
|
+
const newCompletionPercent = Math.max(this.completionPercent_, percent);
|
|
5597
|
+
if (this.completionPercent_ != newCompletionPercent) {
|
|
5598
|
+
this.completionPercent_ = newCompletionPercent;
|
|
5599
|
+
hasNewCompletionPercent = true;
|
|
5600
|
+
}
|
|
5601
|
+
}
|
|
5602
|
+
}
|
|
5603
|
+
if (hasNewCompletionPercent) {
|
|
5604
|
+
let event;
|
|
5605
|
+
if (this.completionPercent_ == 0) {
|
|
5606
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.Started);
|
|
5607
|
+
} else if (this.completionPercent_ == 25) {
|
|
5608
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.FirstQuartile);
|
|
5609
|
+
} else if (this.completionPercent_ == 50) {
|
|
5610
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.Midpoint);
|
|
5611
|
+
} else if (this.completionPercent_ == 75) {
|
|
5612
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.ThirdQuartile);
|
|
5613
|
+
} else if (this.completionPercent_ == 100) {
|
|
5614
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.Complete);
|
|
5615
|
+
}
|
|
5616
|
+
if (event) {
|
|
5617
|
+
this.dispatchEvent(event);
|
|
5618
|
+
}
|
|
5619
|
+
}
|
|
5620
|
+
}
|
|
5621
|
+
|
|
5740
5622
|
/**
|
|
5741
5623
|
* Callback from Playhead.
|
|
5742
5624
|
*
|
|
@@ -5791,12 +5673,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5791
5673
|
*
|
|
5792
5674
|
* On error, this dispatches an error event and returns null.
|
|
5793
5675
|
*
|
|
5676
|
+
* @param {boolean=} initialSelection
|
|
5794
5677
|
* @return {?shaka.extern.Variant}
|
|
5795
5678
|
* @private
|
|
5796
5679
|
*/
|
|
5797
|
-
chooseVariant_() {
|
|
5680
|
+
chooseVariant_(initialSelection = false) {
|
|
5798
5681
|
if (this.updateAbrManagerVariants_()) {
|
|
5799
|
-
return this.abrManager_.chooseVariant();
|
|
5682
|
+
return this.abrManager_.chooseVariant(initialSelection);
|
|
5800
5683
|
} else {
|
|
5801
5684
|
return null;
|
|
5802
5685
|
}
|
|
@@ -6132,8 +6015,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6132
6015
|
const data = new Map()
|
|
6133
6016
|
.set('oldTrack', from)
|
|
6134
6017
|
.set('newTrack', to);
|
|
6135
|
-
if (this.
|
|
6136
|
-
this.
|
|
6018
|
+
if (this.lcevcDec_) {
|
|
6019
|
+
this.lcevcDec_.updateVariant(to, this.getManifestType());
|
|
6137
6020
|
}
|
|
6138
6021
|
const event =
|
|
6139
6022
|
this.makeEvent_(shaka.util.FakeEvent.EventName.Adaptation, data);
|
|
@@ -6163,8 +6046,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6163
6046
|
const data = new Map()
|
|
6164
6047
|
.set('oldTrack', from)
|
|
6165
6048
|
.set('newTrack', to);
|
|
6166
|
-
if (this.
|
|
6167
|
-
this.
|
|
6049
|
+
if (this.lcevcDec_) {
|
|
6050
|
+
this.lcevcDec_.updateVariant(to, this.getManifestType());
|
|
6168
6051
|
}
|
|
6169
6052
|
|
|
6170
6053
|
const event =
|
|
@@ -6445,6 +6328,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6445
6328
|
return;
|
|
6446
6329
|
}
|
|
6447
6330
|
|
|
6331
|
+
const event =
|
|
6332
|
+
this.makeEvent_(shaka.util.FakeEvent.EventName.KeyStatusChanged);
|
|
6333
|
+
this.dispatchEvent(event);
|
|
6334
|
+
|
|
6448
6335
|
const keyIds = Object.keys(keyStatusMap);
|
|
6449
6336
|
if (keyIds.length == 0) {
|
|
6450
6337
|
shaka.log.warning(
|
|
@@ -6833,8 +6720,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6833
6720
|
// Because Safari's native HLS reports slightly inaccurate values for
|
|
6834
6721
|
// bufferEnd here, we use a fudge factor. Without this, we can end up in a
|
|
6835
6722
|
// buffering state at the end of the stream. See issue #2117.
|
|
6836
|
-
// TODO: Try to remove the fudge here once we no longer manage buffering
|
|
6837
|
-
// state above the browser with playbackRate=0.
|
|
6838
6723
|
const fudge = 1; // 1000 ms
|
|
6839
6724
|
return bufferEnd != null && bufferEnd >= this.video_.duration - fudge;
|
|
6840
6725
|
}
|
|
@@ -6851,290 +6736,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6851
6736
|
shaka.util.Error.Category.PLAYER,
|
|
6852
6737
|
shaka.util.Error.Code.LOAD_INTERRUPTED);
|
|
6853
6738
|
}
|
|
6854
|
-
|
|
6855
|
-
/**
|
|
6856
|
-
* Key
|
|
6857
|
-
* ----------------------
|
|
6858
|
-
* D : Detach Node
|
|
6859
|
-
* A : Attach Node
|
|
6860
|
-
* MS : Media Source Node
|
|
6861
|
-
* P : Manifest Parser Node
|
|
6862
|
-
* M : Manifest Node
|
|
6863
|
-
* DRM : Drm Engine Node
|
|
6864
|
-
* L : Load Node
|
|
6865
|
-
* U : Unloading Node
|
|
6866
|
-
* SRC : Src Equals Node
|
|
6867
|
-
*
|
|
6868
|
-
* Graph Topology
|
|
6869
|
-
* ----------------------
|
|
6870
|
-
*
|
|
6871
|
-
* [SRC]-----+
|
|
6872
|
-
* ^ |
|
|
6873
|
-
* | v
|
|
6874
|
-
* [D]<-->[A]<-----[U]
|
|
6875
|
-
* | ^
|
|
6876
|
-
* v |
|
|
6877
|
-
* [MS]------+
|
|
6878
|
-
* | |
|
|
6879
|
-
* v |
|
|
6880
|
-
* [P]-------+
|
|
6881
|
-
* | |
|
|
6882
|
-
* v |
|
|
6883
|
-
* [M]-------+
|
|
6884
|
-
* | |
|
|
6885
|
-
* v |
|
|
6886
|
-
* [DRM]-----+
|
|
6887
|
-
* | |
|
|
6888
|
-
* v |
|
|
6889
|
-
* [L]-------+
|
|
6890
|
-
*
|
|
6891
|
-
* @param {!shaka.routing.Node} currentlyAt
|
|
6892
|
-
* @param {shaka.routing.Payload} currentlyWith
|
|
6893
|
-
* @param {!shaka.routing.Node} wantsToBeAt
|
|
6894
|
-
* @param {shaka.routing.Payload} wantsToHave
|
|
6895
|
-
* @return {?shaka.routing.Node}
|
|
6896
|
-
* @private
|
|
6897
|
-
*/
|
|
6898
|
-
getNextStep_(currentlyAt, currentlyWith, wantsToBeAt, wantsToHave) {
|
|
6899
|
-
let next = null;
|
|
6900
|
-
|
|
6901
|
-
// Detach is very simple, either stay in detach (because |detach| was called
|
|
6902
|
-
// while in detached) or go somewhere that requires us to attach to an
|
|
6903
|
-
// element.
|
|
6904
|
-
if (currentlyAt == this.detachNode_) {
|
|
6905
|
-
next = wantsToBeAt == this.detachNode_ ?
|
|
6906
|
-
this.detachNode_ :
|
|
6907
|
-
this.attachNode_;
|
|
6908
|
-
}
|
|
6909
|
-
|
|
6910
|
-
if (currentlyAt == this.attachNode_) {
|
|
6911
|
-
next = this.getNextAfterAttach_(wantsToBeAt, currentlyWith, wantsToHave);
|
|
6912
|
-
}
|
|
6913
|
-
|
|
6914
|
-
if (currentlyAt == this.mediaSourceNode_) {
|
|
6915
|
-
next = this.getNextAfterMediaSource_(
|
|
6916
|
-
wantsToBeAt, currentlyWith, wantsToHave);
|
|
6917
|
-
}
|
|
6918
|
-
|
|
6919
|
-
if (currentlyAt == this.parserNode_) {
|
|
6920
|
-
next = this.getNextMatchingAllDependencies_(
|
|
6921
|
-
/* destination= */ this.loadNode_,
|
|
6922
|
-
/* next= */ this.manifestNode_,
|
|
6923
|
-
/* reset= */ this.unloadNode_,
|
|
6924
|
-
/* goingTo= */ wantsToBeAt,
|
|
6925
|
-
/* has= */ currentlyWith,
|
|
6926
|
-
/* wants= */ wantsToHave);
|
|
6927
|
-
}
|
|
6928
|
-
|
|
6929
|
-
if (currentlyAt == this.manifestNode_) {
|
|
6930
|
-
next = this.getNextMatchingAllDependencies_(
|
|
6931
|
-
/* destination= */ this.loadNode_,
|
|
6932
|
-
/* next= */ this.drmNode_,
|
|
6933
|
-
/* reset= */ this.unloadNode_,
|
|
6934
|
-
/* goingTo= */ wantsToBeAt,
|
|
6935
|
-
/* has= */ currentlyWith,
|
|
6936
|
-
/* wants= */ wantsToHave);
|
|
6937
|
-
}
|
|
6938
|
-
|
|
6939
|
-
// For DRM, we have two options "load" or "unload". If all our constraints
|
|
6940
|
-
// are met, we can go to "load". If anything is off, we must go back to
|
|
6941
|
-
// "unload" to reset.
|
|
6942
|
-
if (currentlyAt == this.drmNode_) {
|
|
6943
|
-
next = this.getNextMatchingAllDependencies_(
|
|
6944
|
-
/* destination= */ this.loadNode_,
|
|
6945
|
-
/* next= */ this.loadNode_,
|
|
6946
|
-
/* reset= */ this.unloadNode_,
|
|
6947
|
-
/* goingTo= */ wantsToBeAt,
|
|
6948
|
-
/* has= */ currentlyWith,
|
|
6949
|
-
/* wants= */ wantsToHave);
|
|
6950
|
-
}
|
|
6951
|
-
|
|
6952
|
-
// For DRM w/ src= playback, we only care about destination and media
|
|
6953
|
-
// element.
|
|
6954
|
-
if (currentlyAt == this.srcEqualsDrmNode_) {
|
|
6955
|
-
if (wantsToBeAt == this.srcEqualsNode_ &&
|
|
6956
|
-
currentlyWith.mediaElement == wantsToHave.mediaElement) {
|
|
6957
|
-
next = this.srcEqualsNode_;
|
|
6958
|
-
} else {
|
|
6959
|
-
next = this.unloadNode_;
|
|
6960
|
-
}
|
|
6961
|
-
}
|
|
6962
|
-
|
|
6963
|
-
// After we load content, always go through unload because we can't safely
|
|
6964
|
-
// use components after we have started playback.
|
|
6965
|
-
if (currentlyAt == this.loadNode_ || currentlyAt == this.srcEqualsNode_) {
|
|
6966
|
-
next = this.unloadNode_;
|
|
6967
|
-
}
|
|
6968
|
-
|
|
6969
|
-
if (currentlyAt == this.unloadNode_) {
|
|
6970
|
-
next = this.getNextAfterUnload_(wantsToBeAt, currentlyWith, wantsToHave);
|
|
6971
|
-
}
|
|
6972
|
-
|
|
6973
|
-
goog.asserts.assert(next, 'Missing next step!');
|
|
6974
|
-
return next;
|
|
6975
|
-
}
|
|
6976
|
-
|
|
6977
|
-
/**
|
|
6978
|
-
* @param {!shaka.routing.Node} goingTo
|
|
6979
|
-
* @param {shaka.routing.Payload} has
|
|
6980
|
-
* @param {shaka.routing.Payload} wants
|
|
6981
|
-
* @return {?shaka.routing.Node}
|
|
6982
|
-
* @private
|
|
6983
|
-
*/
|
|
6984
|
-
getNextAfterAttach_(goingTo, has, wants) {
|
|
6985
|
-
// Attach and detach are the only two nodes that we can directly go
|
|
6986
|
-
// back-and-forth between.
|
|
6987
|
-
if (goingTo == this.detachNode_) {
|
|
6988
|
-
return this.detachNode_;
|
|
6989
|
-
}
|
|
6990
|
-
|
|
6991
|
-
// If we are going anywhere other than detach, then we need the media
|
|
6992
|
-
// element to match, if they don't match, we need to go through detach
|
|
6993
|
-
// first.
|
|
6994
|
-
if (has.mediaElement != wants.mediaElement) {
|
|
6995
|
-
return this.detachNode_;
|
|
6996
|
-
}
|
|
6997
|
-
|
|
6998
|
-
// If we are already in attached, and someone calls |attach| again (to the
|
|
6999
|
-
// same video element), we can handle the redundant request by re-entering
|
|
7000
|
-
// our current state.
|
|
7001
|
-
if (goingTo == this.attachNode_) {
|
|
7002
|
-
return this.attachNode_;
|
|
7003
|
-
}
|
|
7004
|
-
|
|
7005
|
-
// The next step from attached to loaded is through media source.
|
|
7006
|
-
if (goingTo == this.mediaSourceNode_ || goingTo == this.loadNode_) {
|
|
7007
|
-
return this.mediaSourceNode_;
|
|
7008
|
-
}
|
|
7009
|
-
|
|
7010
|
-
// If we are going to src=, then we should set up DRM first. This will
|
|
7011
|
-
// support cases like FairPlay HLS on Safari.
|
|
7012
|
-
if (goingTo == this.srcEqualsNode_) {
|
|
7013
|
-
return this.srcEqualsDrmNode_;
|
|
7014
|
-
}
|
|
7015
|
-
|
|
7016
|
-
// We are missing a rule, the null will get caught by a common check in
|
|
7017
|
-
// the routing system.
|
|
7018
|
-
return null;
|
|
7019
|
-
}
|
|
7020
|
-
|
|
7021
|
-
/**
|
|
7022
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7023
|
-
* @param {shaka.routing.Payload} has
|
|
7024
|
-
* @param {shaka.routing.Payload} wants
|
|
7025
|
-
* @return {?shaka.routing.Node}
|
|
7026
|
-
* @private
|
|
7027
|
-
*/
|
|
7028
|
-
getNextAfterMediaSource_(goingTo, has, wants) {
|
|
7029
|
-
// We can only go to parse manifest or unload. If we want to go to load and
|
|
7030
|
-
// we have the right media element, we can go to parse manifest. If we
|
|
7031
|
-
// don't, no matter where we want to go, we must go through unload.
|
|
7032
|
-
if (goingTo == this.loadNode_ && has.mediaElement == wants.mediaElement) {
|
|
7033
|
-
return this.parserNode_;
|
|
7034
|
-
}
|
|
7035
|
-
|
|
7036
|
-
// Right now the unload node is responsible for tearing down all playback
|
|
7037
|
-
// components (including media source). So since we have created media
|
|
7038
|
-
// source, we need to unload since our dependencies are not compatible.
|
|
7039
|
-
//
|
|
7040
|
-
// TODO: We are structured this way to maintain a historic structure. Going
|
|
7041
|
-
// forward, there is no reason to restrict ourselves to this. Going
|
|
7042
|
-
// forward we should explore breaking apart |onUnload| and develop
|
|
7043
|
-
// more meaningful terminology around tearing down playback resources.
|
|
7044
|
-
return this.unloadNode_;
|
|
7045
|
-
}
|
|
7046
|
-
|
|
7047
|
-
/**
|
|
7048
|
-
* After unload there are only two options, attached or detached. This choice
|
|
7049
|
-
* is based on whether or not we have a media element. If we have a media
|
|
7050
|
-
* element, then we go to attach. If we don't have a media element, we go to
|
|
7051
|
-
* detach.
|
|
7052
|
-
*
|
|
7053
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7054
|
-
* @param {shaka.routing.Payload} has
|
|
7055
|
-
* @param {shaka.routing.Payload} wants
|
|
7056
|
-
* @return {?shaka.routing.Node}
|
|
7057
|
-
* @private
|
|
7058
|
-
*/
|
|
7059
|
-
getNextAfterUnload_(goingTo, has, wants) {
|
|
7060
|
-
// If we don't want a media element, detach.
|
|
7061
|
-
// If we have the wrong media element, detach.
|
|
7062
|
-
// Otherwise it means we want to attach to a media element and it is safe to
|
|
7063
|
-
// do so.
|
|
7064
|
-
return !wants.mediaElement || has.mediaElement != wants.mediaElement ?
|
|
7065
|
-
this.detachNode_ :
|
|
7066
|
-
this.attachNode_;
|
|
7067
|
-
}
|
|
7068
|
-
|
|
7069
|
-
/**
|
|
7070
|
-
* A general method used to handle routing when we can either than one step
|
|
7071
|
-
* toward our destination (while all our dependencies match) or go to a node
|
|
7072
|
-
* that will reset us so we can try again.
|
|
7073
|
-
*
|
|
7074
|
-
* @param {!shaka.routing.Node} destinationNode
|
|
7075
|
-
* What |goingTo| must be for us to step toward |nextNode|. Otherwise we
|
|
7076
|
-
* will go to |resetNode|.
|
|
7077
|
-
* @param {!shaka.routing.Node} nextNode
|
|
7078
|
-
* The node we will go to next if |goingTo == destinationNode| and all
|
|
7079
|
-
* dependencies match.
|
|
7080
|
-
* @param {!shaka.routing.Node} resetNode
|
|
7081
|
-
* The node we will go to next if |goingTo != destinationNode| or any
|
|
7082
|
-
* dependency does not match.
|
|
7083
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7084
|
-
* The node that the walker is trying to go to.
|
|
7085
|
-
* @param {shaka.routing.Payload} has
|
|
7086
|
-
* The payload that the walker currently has.
|
|
7087
|
-
* @param {shaka.routing.Payload} wants
|
|
7088
|
-
* The payload that the walker wants to have when iy gets to |goingTo|.
|
|
7089
|
-
* @return {shaka.routing.Node}
|
|
7090
|
-
* @private
|
|
7091
|
-
*/
|
|
7092
|
-
getNextMatchingAllDependencies_(destinationNode, nextNode, resetNode, goingTo,
|
|
7093
|
-
has, wants) {
|
|
7094
|
-
if (goingTo == destinationNode &&
|
|
7095
|
-
has.mediaElement == wants.mediaElement &&
|
|
7096
|
-
has.uri == wants.uri &&
|
|
7097
|
-
has.mimeType == wants.mimeType) {
|
|
7098
|
-
return nextNode;
|
|
7099
|
-
}
|
|
7100
|
-
|
|
7101
|
-
return resetNode;
|
|
7102
|
-
}
|
|
7103
|
-
|
|
7104
|
-
/**
|
|
7105
|
-
* @return {shaka.routing.Payload}
|
|
7106
|
-
* @private
|
|
7107
|
-
*/
|
|
7108
|
-
static createEmptyPayload_() {
|
|
7109
|
-
return {
|
|
7110
|
-
mediaElement: null,
|
|
7111
|
-
mimeType: null,
|
|
7112
|
-
startTime: null,
|
|
7113
|
-
startTimeOfLoad: NaN,
|
|
7114
|
-
uri: null,
|
|
7115
|
-
};
|
|
7116
|
-
}
|
|
7117
|
-
|
|
7118
|
-
/**
|
|
7119
|
-
* Using a promise, wrap the listeners returned by |Walker.startNewRoute|.
|
|
7120
|
-
* This will work for most usages in |Player| but should not be used for
|
|
7121
|
-
* special cases.
|
|
7122
|
-
*
|
|
7123
|
-
* This will connect |onCancel|, |onEnd|, |onError|, and |onSkip| with
|
|
7124
|
-
* |resolve| and |reject| but will leave |onStart| unset.
|
|
7125
|
-
*
|
|
7126
|
-
* @param {shaka.routing.Walker.Listeners} listeners
|
|
7127
|
-
* @return {!Promise}
|
|
7128
|
-
* @private
|
|
7129
|
-
*/
|
|
7130
|
-
wrapWalkerListenersWithPromise_(listeners) {
|
|
7131
|
-
return new Promise((resolve, reject) => {
|
|
7132
|
-
listeners.onCancel = () => reject(this.createAbortLoadError_());
|
|
7133
|
-
listeners.onEnd = () => resolve();
|
|
7134
|
-
listeners.onError = (e) => reject(e);
|
|
7135
|
-
listeners.onSkip = () => reject(this.createAbortLoadError_());
|
|
7136
|
-
});
|
|
7137
|
-
}
|
|
7138
6739
|
};
|
|
7139
6740
|
|
|
7140
6741
|
/**
|
|
@@ -7175,7 +6776,8 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
7175
6776
|
* @define {string} A version number taken from git at compile time.
|
|
7176
6777
|
* @export
|
|
7177
6778
|
*/
|
|
7178
|
-
|
|
6779
|
+
// eslint-disable-next-line no-useless-concat
|
|
6780
|
+
shaka.Player.version = 'v4.6.0' + '-uncompiled'; // x-release-please-version
|
|
7179
6781
|
|
|
7180
6782
|
// Initialize the deprecation system using the version string we just set
|
|
7181
6783
|
// on the player.
|
|
@@ -7202,47 +6804,6 @@ shaka.Player.supportPlugins_ = {};
|
|
|
7202
6804
|
shaka.Player.adManagerFactory_ = null;
|
|
7203
6805
|
|
|
7204
6806
|
|
|
7205
|
-
/**
|
|
7206
|
-
* @const {!Object.<string, string>}
|
|
7207
|
-
* @private
|
|
7208
|
-
*/
|
|
7209
|
-
shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
7210
|
-
'mp4': 'video/mp4',
|
|
7211
|
-
'm4v': 'video/mp4',
|
|
7212
|
-
'm4a': 'audio/mp4',
|
|
7213
|
-
'webm': 'video/webm',
|
|
7214
|
-
'weba': 'audio/webm',
|
|
7215
|
-
'mkv': 'video/webm', // Chromium browsers supports it.
|
|
7216
|
-
'ts': 'video/mp2t',
|
|
7217
|
-
'ogv': 'video/ogg',
|
|
7218
|
-
'ogg': 'audio/ogg',
|
|
7219
|
-
'mpg': 'video/mpeg',
|
|
7220
|
-
'mpeg': 'video/mpeg',
|
|
7221
|
-
'm3u8': 'application/x-mpegurl',
|
|
7222
|
-
'mpd': 'application/dash+xml',
|
|
7223
|
-
'mp3': 'audio/mpeg',
|
|
7224
|
-
'aac': 'audio/aac',
|
|
7225
|
-
'flac': 'audio/flac',
|
|
7226
|
-
'wav': 'audio/wav',
|
|
7227
|
-
};
|
|
7228
|
-
|
|
7229
|
-
|
|
7230
|
-
/**
|
|
7231
|
-
* @const {!Object.<string, string>}
|
|
7232
|
-
* @private
|
|
7233
|
-
*/
|
|
7234
|
-
shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
7235
|
-
'sbv': 'text/x-subviewer',
|
|
7236
|
-
'srt': 'text/srt',
|
|
7237
|
-
'vtt': 'text/vtt',
|
|
7238
|
-
'webvtt': 'text/vtt',
|
|
7239
|
-
'ttml': 'application/ttml+xml',
|
|
7240
|
-
'lrc': 'application/x-subtitle-lrc',
|
|
7241
|
-
'ssa': 'text/x-ssa',
|
|
7242
|
-
'ass': 'text/x-ssa',
|
|
7243
|
-
};
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
6807
|
/**
|
|
7247
6808
|
* @const {string}
|
|
7248
6809
|
*/
|