shaka-player 4.5.0 → 4.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
- package/AUTHORS +2 -0
- package/CHANGELOG.md +109 -0
- package/CODE_OF_CONDUCT.md +1 -1
- package/CONTRIBUTORS +2 -0
- package/README.md +38 -32
- package/demo/common/asset.js +11 -17
- package/demo/common/assets.js +117 -11
- package/demo/config.js +28 -1
- package/demo/custom.js +52 -38
- package/demo/main.js +11 -3
- package/demo/search.js +7 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +133 -124
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +133 -124
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +20 -18
- package/dist/locales.js +34 -30
- package/dist/receiver.compiled.debug.js +58 -50
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +58 -50
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +440 -390
- package/dist/shaka-player.compiled.debug.d.ts +440 -390
- package/dist/shaka-player.compiled.debug.externs.js +162 -77
- package/dist/shaka-player.compiled.debug.js +789 -738
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +162 -77
- package/dist/shaka-player.compiled.js +1147 -1113
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +491 -437
- package/dist/shaka-player.ui.debug.d.ts +491 -437
- package/dist/shaka-player.ui.debug.externs.js +167 -77
- package/dist/shaka-player.ui.debug.js +857 -791
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +167 -77
- package/dist/shaka-player.ui.js +1359 -1314
- package/dist/shaka-player.ui.map +4 -4
- 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/offline.md +10 -8
- package/docs/tutorials/plugins.md +5 -0
- package/docs/tutorials/ui-customization.md +13 -0
- package/docs/tutorials/ui.md +2 -1
- package/docs/tutorials/upgrade.md +3 -0
- package/externs/managedmediasource.js +24 -0
- package/externs/shaka/abr_manager.js +6 -2
- package/externs/shaka/cea.js +7 -0
- package/externs/shaka/codecs.js +5 -3
- package/externs/shaka/manifest.js +21 -5
- package/externs/shaka/manifest_parser.js +17 -0
- package/externs/shaka/net.js +2 -2
- package/externs/shaka/offline.js +9 -2
- package/externs/shaka/player.js +53 -7
- package/externs/shaka/text.js +3 -1
- package/externs/shaka/transmuxer.js +1 -1
- package/lib/abr/simple_abr_manager.js +25 -8
- package/lib/ads/media_tailor_ad_manager.js +14 -3
- 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/dash/dash_parser.js +251 -84
- package/lib/dash/mpd_utils.js +52 -31
- package/lib/dash/segment_base.js +4 -5
- package/lib/dash/segment_list.js +5 -4
- package/lib/dash/segment_template.js +92 -29
- package/lib/hls/hls_classes.js +28 -43
- package/lib/hls/hls_parser.js +770 -652
- package/lib/hls/hls_utils.js +64 -5
- package/lib/hls/manifest_text_parser.js +7 -11
- package/lib/media/adaptation_set.js +20 -13
- package/lib/media/adaptation_set_criteria.js +70 -18
- 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 +75 -21
- 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 +5 -5
- package/lib/media/segment_prefetch.js +92 -24
- package/lib/media/segment_reference.js +67 -1
- package/lib/media/segment_utils.js +343 -0
- package/lib/media/stall_detector.js +0 -3
- package/lib/media/streaming_engine.js +72 -44
- package/lib/mss/mss_parser.js +18 -2
- package/lib/net/networking_engine.js +66 -12
- package/lib/net/networking_utils.js +95 -0
- package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
- package/lib/offline/manifest_converter.js +2 -0
- package/lib/offline/offline_manifest_parser.js +10 -0
- package/lib/offline/storage.js +11 -7
- package/lib/player.js +998 -1503
- package/lib/polyfill/mediasource.js +22 -1
- 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 +28 -5
- package/lib/text/ui_text_displayer.js +4 -0
- package/lib/text/vtt_text_parser.js +7 -2
- 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 +5 -4
- package/lib/transmuxer/mp3_transmuxer.js +1 -1
- package/lib/transmuxer/mpeg_ts_transmuxer.js +1 -1
- package/lib/transmuxer/mss_transmuxer.js +1 -1
- package/lib/transmuxer/ts_transmuxer.js +33 -13
- package/lib/util/buffer_utils.js +26 -4
- package/lib/util/content_steering_manager.js +358 -0
- package/lib/util/error.js +2 -6
- package/lib/util/fairplay_utils.js +1 -1
- package/lib/util/fake_event.js +5 -1
- package/lib/util/id3_utils.js +50 -1
- package/lib/util/manifest_parser_utils.js +12 -8
- package/lib/util/mp4_generator.js +2 -4
- package/lib/util/mutex.js +59 -0
- package/lib/util/object_utils.js +1 -1
- package/lib/util/periods.js +154 -179
- package/lib/util/platform.js +4 -3
- package/lib/util/player_configuration.js +35 -6
- package/lib/util/stream_utils.js +105 -98
- package/lib/util/ts_parser.js +124 -73
- package/lib/util/uint8array_utils.js +5 -1
- package/lib/util/xml_utils.js +5 -0
- package/maintained-branches.md +2 -2
- package/package.json +1 -1
- package/roadmap.md +15 -5
- package/shaka-player.uncompiled.js +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 +3 -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 +2 -1
- package/test/dash/dash_parser_integration.js +2 -1
- package/test/dash/dash_parser_live_unit.js +0 -19
- package/test/dash/dash_parser_manifest_unit.js +494 -2
- package/test/dash/mpd_utils_unit.js +61 -30
- package/test/hls/hls_live_unit.js +12 -3
- package/test/hls/hls_parser_unit.js +417 -27
- package/test/hls/manifest_text_parser_unit.js +32 -99
- package/test/media/adaptation_set_criteria_unit.js +172 -21
- package/test/media/adaptation_set_unit.js +1 -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 +1 -1
- package/test/media/play_rate_controller_unit.js +0 -48
- package/test/media/segment_index_unit.js +2 -1
- 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 +9 -0
- package/test/offline/offline_integration.js +2 -1
- package/test/offline/storage_integration.js +21 -9
- 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 +80 -27
- package/test/test/util/fake_media_source_engine.js +5 -0
- 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 +4 -0
- package/test/test/util/manifest_parser_util.js +14 -2
- package/test/test/util/offline_utils.js +1 -0
- package/test/test/util/simple_fakes.js +2 -2
- package/test/test/util/streaming_engine_util.js +3 -0
- 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 +33 -2
- package/test/transmuxer/transmuxer_engine_integration.js +9 -0
- package/test/transmuxer/transmuxer_integration.js +33 -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 +25 -19
- 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 +17 -113
- 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/externs/ui.js +17 -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/remote_button.js +3 -1
- 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 +6 -2
- package/lib/routing/node.js +0 -23
- package/lib/routing/payload.js +0 -47
- package/lib/routing/walker.js +0 -587
- package/test/routing/walker_unit.js +0 -505
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');
|
|
@@ -58,8 +60,6 @@ goog.require('shaka.util.StreamUtils');
|
|
|
58
60
|
goog.require('shaka.util.Timer');
|
|
59
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.
|
|
@@ -455,6 +443,56 @@ goog.requireType('shaka.routing.Payload');
|
|
|
455
443
|
*/
|
|
456
444
|
|
|
457
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
|
+
|
|
458
496
|
/**
|
|
459
497
|
* @summary The main player object for Shaka Player.
|
|
460
498
|
*
|
|
@@ -523,6 +561,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
523
561
|
/** @private {shaka.media.Playhead} */
|
|
524
562
|
this.playhead_ = null;
|
|
525
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
|
+
|
|
526
575
|
/**
|
|
527
576
|
* The playhead observers are used to monitor the position of the playhead
|
|
528
577
|
* and some other source of data (e.g. buffered content), and raise events.
|
|
@@ -540,12 +589,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
540
589
|
*/
|
|
541
590
|
this.playRateController_ = null;
|
|
542
591
|
|
|
543
|
-
// We use the buffering observer and timer to track when we move from having
|
|
544
|
-
// enough buffered content to not enough. They only exist when content has
|
|
545
|
-
// been loaded and are not re-used between loads.
|
|
546
|
-
/** @private {shaka.util.Timer} */
|
|
547
|
-
this.bufferPoller_ = null;
|
|
548
|
-
|
|
549
592
|
/** @private {shaka.media.BufferingObserver} */
|
|
550
593
|
this.bufferObserver_ = null;
|
|
551
594
|
|
|
@@ -582,6 +625,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
582
625
|
/** @private {?string} */
|
|
583
626
|
this.assetUri_ = null;
|
|
584
627
|
|
|
628
|
+
/** @private {?number} */
|
|
629
|
+
this.startTime_ = null;
|
|
630
|
+
|
|
585
631
|
/** @private {boolean} */
|
|
586
632
|
this.fullyLoaded_ = false;
|
|
587
633
|
|
|
@@ -601,6 +647,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
601
647
|
*/
|
|
602
648
|
this.nextExternalStreamId_ = 1e9;
|
|
603
649
|
|
|
650
|
+
/** @private {!Array.<shaka.extern.Stream>} */
|
|
651
|
+
this.externalSrcEqualsThumbnailsStreams_ = [];
|
|
652
|
+
|
|
653
|
+
/** @private {number} */
|
|
654
|
+
this.completionPercent_ = NaN;
|
|
655
|
+
|
|
604
656
|
/** @private {?shaka.extern.PlayerConfiguration} */
|
|
605
657
|
this.config_ = this.defaultConfig_();
|
|
606
658
|
|
|
@@ -624,7 +676,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
624
676
|
this.config_.preferredVariantRole,
|
|
625
677
|
this.config_.preferredAudioChannelCount,
|
|
626
678
|
this.config_.preferredVideoHdrLevel,
|
|
627
|
-
this.config_.
|
|
679
|
+
this.config_.preferredVideoLayout,
|
|
680
|
+
this.config_.preferredAudioLabel,
|
|
681
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
682
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
628
683
|
|
|
629
684
|
/** @private {string} */
|
|
630
685
|
this.currentTextLanguage_ = this.config_.preferredTextLanguage;
|
|
@@ -638,15 +693,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
638
693
|
/** @private {!Array.<function():(!Promise|undefined)>} */
|
|
639
694
|
this.cleanupOnUnload_ = [];
|
|
640
695
|
|
|
641
|
-
/**
|
|
642
|
-
* This playback start position will be used when
|
|
643
|
-
* <code>updateStartTime()</code> has been called to provide an updated
|
|
644
|
-
* start position during the media loading process.
|
|
645
|
-
*
|
|
646
|
-
* @private {?number}
|
|
647
|
-
*/
|
|
648
|
-
this.updatedStartTime_ = null;
|
|
649
|
-
|
|
650
696
|
if (dependencyInjector) {
|
|
651
697
|
dependencyInjector(this);
|
|
652
698
|
}
|
|
@@ -672,117 +718,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
672
718
|
this.retryStreaming();
|
|
673
719
|
});
|
|
674
720
|
|
|
675
|
-
/** @private {shaka.routing.Node} */
|
|
676
|
-
this.detachNode_ = {name: 'detach'};
|
|
677
|
-
/** @private {shaka.routing.Node} */
|
|
678
|
-
this.attachNode_ = {name: 'attach'};
|
|
679
|
-
/** @private {shaka.routing.Node} */
|
|
680
|
-
this.unloadNode_ = {name: 'unload'};
|
|
681
|
-
/** @private {shaka.routing.Node} */
|
|
682
|
-
this.parserNode_ = {name: 'manifest-parser'};
|
|
683
|
-
/** @private {shaka.routing.Node} */
|
|
684
|
-
this.manifestNode_ = {name: 'manifest'};
|
|
685
|
-
/** @private {shaka.routing.Node} */
|
|
686
|
-
this.mediaSourceNode_ = {name: 'media-source'};
|
|
687
|
-
/** @private {shaka.routing.Node} */
|
|
688
|
-
this.drmNode_ = {name: 'drm-engine'};
|
|
689
|
-
/** @private {shaka.routing.Node} */
|
|
690
|
-
this.loadNode_ = {name: 'load'};
|
|
691
|
-
/** @private {shaka.routing.Node} */
|
|
692
|
-
this.srcEqualsDrmNode_ = {name: 'src-equals-drm-engine'};
|
|
693
|
-
/** @private {shaka.routing.Node} */
|
|
694
|
-
this.srcEqualsNode_ = {name: 'src-equals'};
|
|
695
|
-
|
|
696
|
-
const AbortableOperation = shaka.util.AbortableOperation;
|
|
697
|
-
|
|
698
|
-
const actions = new Map();
|
|
699
|
-
actions.set(this.attachNode_, (has, wants) => {
|
|
700
|
-
return AbortableOperation.notAbortable(this.onAttach_(has, wants));
|
|
701
|
-
});
|
|
702
|
-
actions.set(this.detachNode_, (has, wants) => {
|
|
703
|
-
return AbortableOperation.notAbortable(this.onDetach_(has, wants));
|
|
704
|
-
});
|
|
705
|
-
actions.set(this.unloadNode_, (has, wants) => {
|
|
706
|
-
return AbortableOperation.notAbortable(this.onUnload_(has, wants));
|
|
707
|
-
});
|
|
708
|
-
actions.set(this.mediaSourceNode_, (has, wants) => {
|
|
709
|
-
const p = this.onInitializeMediaSourceEngine_(has, wants);
|
|
710
|
-
return AbortableOperation.notAbortable(p);
|
|
711
|
-
});
|
|
712
|
-
actions.set(this.parserNode_, (has, wants) => {
|
|
713
|
-
const p = this.onInitializeParser_(has, wants);
|
|
714
|
-
return AbortableOperation.notAbortable(p);
|
|
715
|
-
});
|
|
716
|
-
actions.set(this.manifestNode_, (has, wants) => {
|
|
717
|
-
// This action is actually abortable, so unlike the other callbacks, this
|
|
718
|
-
// one will return an abortable operation.
|
|
719
|
-
return this.onParseManifest_(has, wants);
|
|
720
|
-
});
|
|
721
|
-
actions.set(this.drmNode_, (has, wants) => {
|
|
722
|
-
const p = this.onInitializeDrm_(has, wants);
|
|
723
|
-
return AbortableOperation.notAbortable(p);
|
|
724
|
-
});
|
|
725
|
-
actions.set(this.loadNode_, (has, wants) => {
|
|
726
|
-
return AbortableOperation.notAbortable(this.onLoad_(has, wants));
|
|
727
|
-
});
|
|
728
|
-
|
|
729
|
-
actions.set(this.srcEqualsDrmNode_, (has, wants) => {
|
|
730
|
-
const p = this.onInitializeSrcEqualsDrm_(has, wants);
|
|
731
|
-
return AbortableOperation.notAbortable(p);
|
|
732
|
-
});
|
|
733
|
-
actions.set(this.srcEqualsNode_, (has, wants) => {
|
|
734
|
-
return this.onSrcEquals_(has, wants);
|
|
735
|
-
});
|
|
736
|
-
|
|
737
|
-
/** @private {shaka.routing.Walker.Implementation} */
|
|
738
|
-
const walkerImplementation = {
|
|
739
|
-
getNext: (at, has, goingTo, wants) => {
|
|
740
|
-
return this.getNextStep_(at, has, goingTo, wants);
|
|
741
|
-
},
|
|
742
|
-
enterNode: (node, has, wants) => {
|
|
743
|
-
this.dispatchEvent(this.makeEvent_(
|
|
744
|
-
/* name= */ shaka.util.FakeEvent.EventName.OnStateChange,
|
|
745
|
-
/* data= */ (new Map()).set('state', node.name)));
|
|
746
|
-
|
|
747
|
-
const action = actions.get(node);
|
|
748
|
-
return action(has, wants);
|
|
749
|
-
},
|
|
750
|
-
handleError: async (has, error) => {
|
|
751
|
-
shaka.log.warning('The walker saw an error:');
|
|
752
|
-
if (error instanceof shaka.util.Error) {
|
|
753
|
-
shaka.log.warning('Error Code:', error.code);
|
|
754
|
-
} else {
|
|
755
|
-
shaka.log.warning('Error Message:', error.message);
|
|
756
|
-
shaka.log.warning('Error Stack:', error.stack);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
// Regardless of what state we were in, if there is an error, we unload.
|
|
760
|
-
// This ensures that any initialized system will be torn-down and we
|
|
761
|
-
// will go back to a safe foundation. We assume that the media element
|
|
762
|
-
// is always safe to use after an error.
|
|
763
|
-
await this.onUnload_(has, shaka.Player.createEmptyPayload_());
|
|
764
|
-
|
|
765
|
-
// There are only two nodes that come before we start loading content,
|
|
766
|
-
// attach and detach. If we have a media element, it means we were
|
|
767
|
-
// attached to the element, and we can safely return to the attach state
|
|
768
|
-
// (we assume that the video element is always re-usable). We favor
|
|
769
|
-
// returning to the attach node since it means that the app won't need
|
|
770
|
-
// to re-attach if it saw an error.
|
|
771
|
-
return has.mediaElement ? this.attachNode_ : this.detachNode_;
|
|
772
|
-
},
|
|
773
|
-
onIdle: (node) => {
|
|
774
|
-
this.dispatchEvent(this.makeEvent_(
|
|
775
|
-
/* name= */ shaka.util.FakeEvent.EventName.OnStateIdle,
|
|
776
|
-
/* data= */ (new Map()).set('state', node.name)));
|
|
777
|
-
},
|
|
778
|
-
};
|
|
779
|
-
|
|
780
|
-
/** @private {shaka.routing.Walker} */
|
|
781
|
-
this.walker_ = new shaka.routing.Walker(
|
|
782
|
-
this.detachNode_,
|
|
783
|
-
shaka.Player.createEmptyPayload_(),
|
|
784
|
-
walkerImplementation);
|
|
785
|
-
|
|
786
721
|
/** @private {shaka.util.Timer} */
|
|
787
722
|
this.checkVariantsTimer_ =
|
|
788
723
|
new shaka.util.Timer(() => this.checkVariants_());
|
|
@@ -791,6 +726,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
791
726
|
// the LAST thing we do in the constructor because conceptually it relies on
|
|
792
727
|
// player having been initialized.
|
|
793
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.');
|
|
794
733
|
this.attach(mediaElement, /* initializeMediaSource= */ true);
|
|
795
734
|
}
|
|
796
735
|
}
|
|
@@ -833,9 +772,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
833
772
|
setupLcevc_(config) {
|
|
834
773
|
if (config.lcevc.enabled) {
|
|
835
774
|
const tracks = this.getVariantTracks();
|
|
836
|
-
if (tracks && tracks[0] &&
|
|
837
|
-
tracks[0].videoMimeType ==
|
|
838
|
-
shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_['ts']) {
|
|
775
|
+
if (tracks && tracks[0] && tracks[0].videoMimeType == 'video/mp2t') {
|
|
839
776
|
const edge = shaka.util.Platform.isEdge() ||
|
|
840
777
|
shaka.util.Platform.isLegacyEdge();
|
|
841
778
|
if (edge) {
|
|
@@ -881,51 +818,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
881
818
|
// If LCEVC Decoder exists close it.
|
|
882
819
|
this.closeLcevcDec_();
|
|
883
820
|
|
|
821
|
+
const detachPromise = this.detach();
|
|
822
|
+
|
|
884
823
|
// Mark as "dead". This should stop external-facing calls from changing our
|
|
885
824
|
// internal state any more. This will stop calls to |attach|, |detach|, etc.
|
|
886
825
|
// from interrupting our final move to the detached state.
|
|
887
826
|
this.loadMode_ = shaka.Player.LoadMode.DESTROYED;
|
|
888
827
|
|
|
889
|
-
|
|
890
|
-
payload.keepAdManager = false;
|
|
891
|
-
|
|
892
|
-
// Because we have set |loadMode_| to |DESTROYED| we can't call |detach|. We
|
|
893
|
-
// must talk to |this.walker_| directly.
|
|
894
|
-
const events = this.walker_.startNewRoute((currentPayload) => {
|
|
895
|
-
return {
|
|
896
|
-
node: this.detachNode_,
|
|
897
|
-
payload: payload,
|
|
898
|
-
interruptible: false,
|
|
899
|
-
};
|
|
900
|
-
});
|
|
901
|
-
|
|
902
|
-
// Wait until the detach has finished so that we don't interrupt it by
|
|
903
|
-
// calling |destroy| on |this.walker_|. To avoid failing here, we always
|
|
904
|
-
// resolve the promise.
|
|
905
|
-
await new Promise((resolve) => {
|
|
906
|
-
events.onStart = () => {
|
|
907
|
-
shaka.log.info('Preparing to destroy walker...');
|
|
908
|
-
};
|
|
909
|
-
events.onEnd = () => {
|
|
910
|
-
resolve();
|
|
911
|
-
};
|
|
912
|
-
events.onCancel = () => {
|
|
913
|
-
goog.asserts.assert(false,
|
|
914
|
-
'Our final detach call should never be cancelled.');
|
|
915
|
-
resolve();
|
|
916
|
-
};
|
|
917
|
-
events.onError = () => {
|
|
918
|
-
goog.asserts.assert(false,
|
|
919
|
-
'Our final detach call should never see an error');
|
|
920
|
-
resolve();
|
|
921
|
-
};
|
|
922
|
-
events.onSkip = () => {
|
|
923
|
-
goog.asserts.assert(false,
|
|
924
|
-
'Our final detach call should never be skipped');
|
|
925
|
-
resolve();
|
|
926
|
-
};
|
|
927
|
-
});
|
|
928
|
-
await this.walker_.destroy();
|
|
828
|
+
await detachPromise;
|
|
929
829
|
|
|
930
830
|
// Tear-down the event managers to ensure handlers stop firing.
|
|
931
831
|
if (this.globalEventManager_) {
|
|
@@ -1011,10 +911,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1011
911
|
return false;
|
|
1012
912
|
}
|
|
1013
913
|
|
|
1014
|
-
// We do not support iOS 9, 10, 11 or 12, nor those same versions of
|
|
1015
|
-
// desktop Safari.
|
|
1016
914
|
const safariVersion = shaka.util.Platform.safariVersion();
|
|
1017
|
-
if (safariVersion && safariVersion <
|
|
915
|
+
if (safariVersion && safariVersion < 9) {
|
|
1018
916
|
return false;
|
|
1019
917
|
}
|
|
1020
918
|
|
|
@@ -1073,56 +971,60 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1073
971
|
}
|
|
1074
972
|
|
|
1075
973
|
/**
|
|
1076
|
-
*
|
|
1077
|
-
*
|
|
1078
|
-
*
|
|
1079
|
-
*
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
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.
|
|
1088
989
|
*
|
|
1089
990
|
* @param {!HTMLMediaElement} mediaElement
|
|
1090
991
|
* @param {boolean=} initializeMediaSource
|
|
1091
992
|
* @return {!Promise}
|
|
1092
993
|
* @export
|
|
1093
994
|
*/
|
|
1094
|
-
attach(mediaElement, initializeMediaSource = true) {
|
|
995
|
+
async attach(mediaElement, initializeMediaSource = true) {
|
|
1095
996
|
// Do not allow the player to be used after |destroy| is called.
|
|
1096
997
|
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
1097
|
-
|
|
998
|
+
throw this.createAbortLoadError_();
|
|
1098
999
|
}
|
|
1099
1000
|
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
// If the platform does not support media source, we will never want to
|
|
1104
|
-
// initialize media source.
|
|
1105
|
-
if (!shaka.util.Platform.supportsMediaSource()) {
|
|
1106
|
-
initializeMediaSource = false;
|
|
1001
|
+
if (this.video_ && this.video_ != mediaElement) {
|
|
1002
|
+
await this.detach();
|
|
1107
1003
|
}
|
|
1108
1004
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1005
|
+
if (await this.atomicOperationAcquireMutex_('attach')) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1112
1008
|
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
const events = this.walker_.startNewRoute((currentPayload) => {
|
|
1116
|
-
return {
|
|
1117
|
-
node: destination,
|
|
1118
|
-
payload: payload,
|
|
1119
|
-
interruptible: false,
|
|
1120
|
-
};
|
|
1121
|
-
});
|
|
1009
|
+
try {
|
|
1010
|
+
this.makeStateChangeEvent_('attach');
|
|
1122
1011
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
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
|
+
}
|
|
1126
1028
|
}
|
|
1127
1029
|
|
|
1128
1030
|
|
|
@@ -1138,131 +1040,246 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1138
1040
|
}
|
|
1139
1041
|
|
|
1140
1042
|
/**
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
* <li>attached, this will release the media element,
|
|
1145
|
-
* <li>loading, this will abort loading, unload, and release the media
|
|
1146
|
-
* element,
|
|
1147
|
-
* <li>playing content, this will stop playback, unload, and release the
|
|
1148
|
-
* media element.
|
|
1149
|
-
* </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.
|
|
1150
1046
|
*
|
|
1151
|
-
*
|
|
1152
|
-
* Calls to <code>detach</code> will interrupt any in-progress calls to
|
|
1153
|
-
* <code>load</code> but cannot interrupt calls to <code>attach</code>,
|
|
1154
|
-
* <code>detach</code>, or <code>unload</code>.
|
|
1047
|
+
* @param {boolean=} keepAdManager
|
|
1155
1048
|
*
|
|
1156
1049
|
* @return {!Promise}
|
|
1157
1050
|
* @export
|
|
1158
1051
|
*/
|
|
1159
|
-
detach() {
|
|
1052
|
+
async detach(keepAdManager = false) {
|
|
1160
1053
|
// Do not allow the player to be used after |destroy| is called.
|
|
1161
1054
|
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
1162
|
-
|
|
1055
|
+
throw this.createAbortLoadError_();
|
|
1163
1056
|
}
|
|
1164
1057
|
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1058
|
+
await this.unload(/* initializeMediaSource= */ false, keepAdManager);
|
|
1059
|
+
|
|
1060
|
+
if (await this.atomicOperationAcquireMutex_('detach')) {
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
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
|
+
}
|
|
1175
1071
|
|
|
1176
|
-
|
|
1177
|
-
|
|
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
|
+
}
|
|
1178
1081
|
}
|
|
1179
1082
|
|
|
1180
1083
|
/**
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
1184
|
-
*
|
|
1185
|
-
*
|
|
1186
|
-
*
|
|
1187
|
-
*
|
|
1188
|
-
*
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
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.
|
|
1194
1105
|
*
|
|
1195
1106
|
* @param {boolean=} initializeMediaSource
|
|
1196
1107
|
* @param {boolean=} keepAdManager
|
|
1197
1108
|
* @return {!Promise}
|
|
1198
1109
|
* @export
|
|
1199
1110
|
*/
|
|
1200
|
-
unload(initializeMediaSource = true, keepAdManager = false) {
|
|
1201
|
-
//
|
|
1202
|
-
|
|
1203
|
-
|
|
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;
|
|
1204
1118
|
}
|
|
1205
1119
|
|
|
1206
|
-
this.
|
|
1207
|
-
|
|
1208
|
-
// If the platform does not support media source, we will never want to
|
|
1209
|
-
// initialize media source.
|
|
1210
|
-
if (!shaka.util.Platform.supportsMediaSource()) {
|
|
1211
|
-
initializeMediaSource = false;
|
|
1120
|
+
if (await this.atomicOperationAcquireMutex_('unload')) {
|
|
1121
|
+
return;
|
|
1212
1122
|
}
|
|
1213
1123
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1124
|
+
try {
|
|
1125
|
+
this.fullyLoaded_ = false;
|
|
1126
|
+
this.makeStateChangeEvent_('unload');
|
|
1216
1127
|
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
//
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
//
|
|
1234
|
-
|
|
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.
|
|
1235
1228
|
//
|
|
1236
|
-
//
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
} else {
|
|
1248
|
-
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();
|
|
1249
1240
|
}
|
|
1250
1241
|
|
|
1251
|
-
|
|
1242
|
+
if (this.drmEngine_) {
|
|
1243
|
+
await this.drmEngine_.destroy();
|
|
1244
|
+
this.drmEngine_ = null;
|
|
1245
|
+
}
|
|
1252
1246
|
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
payload.mediaElement = currentPayload.mediaElement;
|
|
1247
|
+
this.assetUri_ = null;
|
|
1248
|
+
this.bufferObserver_ = null;
|
|
1256
1249
|
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
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_ = [];
|
|
1263
1270
|
|
|
1264
|
-
|
|
1265
|
-
|
|
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
|
+
}
|
|
1266
1283
|
}
|
|
1267
1284
|
|
|
1268
1285
|
/**
|
|
@@ -1275,120 +1292,164 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1275
1292
|
* @export
|
|
1276
1293
|
*/
|
|
1277
1294
|
updateStartTime(startTime) {
|
|
1278
|
-
this.
|
|
1295
|
+
this.startTime_ = startTime;
|
|
1279
1296
|
}
|
|
1280
1297
|
|
|
1281
1298
|
/**
|
|
1282
|
-
*
|
|
1283
|
-
*
|
|
1284
|
-
* a call to <code>attach</code> must have succeeded.
|
|
1285
|
-
*
|
|
1286
|
-
* <p>
|
|
1287
|
-
* Calls to <code>load</code> will interrupt any in-progress calls to
|
|
1288
|
-
* <code>load</code> but cannot interrupt calls to <code>attach</code>,
|
|
1289
|
-
* <code>detach</code>, or <code>unload</code>.
|
|
1299
|
+
* Loads a new stream.
|
|
1300
|
+
* If another stream was already playing, first unloads that stream.
|
|
1290
1301
|
*
|
|
1291
1302
|
* @param {string} assetUri
|
|
1292
1303
|
* @param {?number=} startTime
|
|
1293
1304
|
* When <code>startTime</code> is <code>null</code> or
|
|
1294
1305
|
* <code>undefined</code>, playback will start at the default start time (0
|
|
1295
1306
|
* for VOD and liveEdge for LIVE).
|
|
1296
|
-
* @param {string=} mimeType
|
|
1307
|
+
* @param {?string=} mimeType
|
|
1297
1308
|
* @return {!Promise}
|
|
1298
1309
|
* @export
|
|
1299
1310
|
*/
|
|
1300
|
-
load(assetUri, startTime, mimeType) {
|
|
1301
|
-
this.updatedStartTime_ = null;
|
|
1302
|
-
|
|
1303
|
-
this.fullyLoaded_ = false;
|
|
1304
|
-
|
|
1311
|
+
async load(assetUri, startTime = null, mimeType) {
|
|
1305
1312
|
// Do not allow the player to be used after |destroy| is called.
|
|
1306
1313
|
if (this.loadMode_ == shaka.Player.LoadMode.DESTROYED) {
|
|
1307
|
-
|
|
1314
|
+
throw this.createAbortLoadError_();
|
|
1308
1315
|
}
|
|
1309
1316
|
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
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();
|
|
1313
1321
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
if (mimeType) {
|
|
1320
|
-
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);
|
|
1321
1327
|
}
|
|
1322
1328
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
// |undefined| to |null| while preserving |0| as a meaningful value.
|
|
1326
|
-
if (startTime !== undefined) {
|
|
1327
|
-
payload.startTime = startTime;
|
|
1329
|
+
if (this.assetUri_) {
|
|
1330
|
+
await this.unload(/* initializeMediaSource= */ false);
|
|
1328
1331
|
}
|
|
1329
1332
|
|
|
1330
|
-
//
|
|
1331
|
-
//
|
|
1332
|
-
|
|
1333
|
-
const
|
|
1334
|
-
|
|
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
|
+
};
|
|
1335
1341
|
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
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();
|
|
1342
1358
|
}
|
|
1359
|
+
};
|
|
1343
1360
|
|
|
1344
|
-
|
|
1345
|
-
|
|
1361
|
+
try {
|
|
1362
|
+
this.startTime_ = startTime;
|
|
1363
|
+
this.fullyLoaded_ = false;
|
|
1346
1364
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
};
|
|
1352
|
-
});
|
|
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));
|
|
1353
1369
|
|
|
1354
|
-
|
|
1355
|
-
// before we allow calls to |updateStateHistory|.
|
|
1356
|
-
this.stats_ = new shaka.util.Stats();
|
|
1357
|
-
|
|
1358
|
-
// Load's request is a little different, so we can't use our normal
|
|
1359
|
-
// listeners-to-promise method. It is the only request where we may skip the
|
|
1360
|
-
// request, so we need to set the on skip callback to reject with a specific
|
|
1361
|
-
// error.
|
|
1362
|
-
events.onStart =
|
|
1363
|
-
() => shaka.log.info('Starting load of ' + assetUri + '...');
|
|
1364
|
-
return new Promise((resolve, reject) => {
|
|
1365
|
-
events.onSkip = () => reject(new shaka.util.Error(
|
|
1366
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
1367
|
-
shaka.util.Error.Category.PLAYER,
|
|
1368
|
-
shaka.util.Error.Code.NO_VIDEO_ELEMENT));
|
|
1370
|
+
const startTimeOfLoad = Date.now() / 1000;
|
|
1369
1371
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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;
|
|
1379
1439
|
}
|
|
1380
1440
|
|
|
1381
1441
|
/**
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1442
|
+
* Determines if we should use src equals, based on the the mimeType (if
|
|
1443
|
+
* known), the URI, and platform information.
|
|
1384
1444
|
*
|
|
1385
|
-
* @param {
|
|
1445
|
+
* @param {string} assetUri
|
|
1446
|
+
* @param {?string=} mimeType
|
|
1386
1447
|
* @return {boolean}
|
|
1387
1448
|
* |true| if the content should be loaded with src=, |false| if the content
|
|
1388
1449
|
* should be loaded with MediaSource.
|
|
1389
1450
|
* @private
|
|
1390
1451
|
*/
|
|
1391
|
-
shouldUseSrcEquals_(
|
|
1452
|
+
shouldUseSrcEquals_(assetUri, mimeType) {
|
|
1392
1453
|
const Platform = shaka.util.Platform;
|
|
1393
1454
|
const MimeUtils = shaka.util.MimeUtils;
|
|
1394
1455
|
|
|
@@ -1398,29 +1459,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1398
1459
|
return true;
|
|
1399
1460
|
}
|
|
1400
1461
|
|
|
1401
|
-
// The most accurate way to tell the player how to load the content is via
|
|
1402
|
-
// MIME type. We can fall back to features of the URI if needed.
|
|
1403
|
-
let mimeType = payload.mimeType;
|
|
1404
|
-
const uri = payload.uri || '';
|
|
1405
|
-
|
|
1406
|
-
// If we don't have a MIME type, try to guess based on the file extension.
|
|
1407
|
-
// TODO: Too generic to belong to ManifestParser now. Refactor.
|
|
1408
|
-
if (!mimeType) {
|
|
1409
|
-
// Try using the uri extension.
|
|
1410
|
-
const extension = shaka.media.ManifestParser.getExtension(uri);
|
|
1411
|
-
mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
1412
|
-
}
|
|
1413
|
-
|
|
1414
|
-
// TODO: The load graph system has a design limitation that requires routing
|
|
1415
|
-
// destination to be chosen synchronously. This means we can only make the
|
|
1416
|
-
// right choice about src= consistently if we have a well-known file
|
|
1417
|
-
// extension or API-provided MIME type. Detection of MIME type from a HEAD
|
|
1418
|
-
// request (as is done for manifest types) can't be done yet.
|
|
1419
|
-
|
|
1420
1462
|
if (mimeType) {
|
|
1421
1463
|
// If we have a MIME type, check if the browser can play it natively.
|
|
1422
1464
|
// This will cover both single files and native HLS.
|
|
1423
|
-
const mediaElement =
|
|
1465
|
+
const mediaElement = this.video_ || Platform.anyMediaElement();
|
|
1424
1466
|
const canPlayNatively = mediaElement.canPlayType(mimeType) != '';
|
|
1425
1467
|
|
|
1426
1468
|
// If we can't play natively, then src= isn't an option.
|
|
@@ -1429,7 +1471,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1429
1471
|
}
|
|
1430
1472
|
|
|
1431
1473
|
const canPlayMediaSource =
|
|
1432
|
-
shaka.media.ManifestParser.isSupported(
|
|
1474
|
+
shaka.media.ManifestParser.isSupported(mimeType);
|
|
1433
1475
|
|
|
1434
1476
|
// If MediaSource isn't an option, the native option is our only chance.
|
|
1435
1477
|
if (!canPlayMediaSource) {
|
|
@@ -1443,17 +1485,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1443
1485
|
// We would prefer MediaSource in some cases, and src= in others. For
|
|
1444
1486
|
// example, Android has native HLS, but we'd prefer our own MediaSource
|
|
1445
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
|
+
}
|
|
1446
1493
|
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
// For Safari, we have an older flag which only applies to this one
|
|
1454
|
-
// browser:
|
|
1455
|
-
if (Platform.isApple()) {
|
|
1456
|
-
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
|
+
}
|
|
1457
1499
|
}
|
|
1458
1500
|
|
|
1459
1501
|
// In all other cases, we prefer MediaSource.
|
|
@@ -1467,293 +1509,35 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1467
1509
|
}
|
|
1468
1510
|
|
|
1469
1511
|
/**
|
|
1470
|
-
*
|
|
1471
|
-
* a media element. The only times this may be called are when we are being
|
|
1472
|
-
* asked to re-attach to the current media element, or attach to a new media
|
|
1473
|
-
* element while not attached to a media element.
|
|
1474
|
-
*
|
|
1475
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1476
|
-
* responsible for ensuring all assumptions are true.
|
|
1477
|
-
*
|
|
1478
|
-
* Attaching to a media element is defined as:
|
|
1479
|
-
* - Registering error listeners to the media element.
|
|
1480
|
-
* - Caching the video element for use outside of the load graph.
|
|
1481
|
-
*
|
|
1482
|
-
* @param {shaka.routing.Payload} has
|
|
1483
|
-
* @param {shaka.routing.Payload} wants
|
|
1484
|
-
* @return {!Promise}
|
|
1485
|
-
* @private
|
|
1486
|
-
*/
|
|
1487
|
-
onAttach_(has, wants) {
|
|
1488
|
-
// If we don't have a media element yet, it means we are entering
|
|
1489
|
-
// "attach" from another node.
|
|
1490
|
-
//
|
|
1491
|
-
// If we have a media element, it should match |wants.mediaElement|
|
|
1492
|
-
// because it means we are going from "attach" to "attach".
|
|
1493
|
-
//
|
|
1494
|
-
// These constraints should be maintained and guaranteed by the routing
|
|
1495
|
-
// logic in |getNextStep_|.
|
|
1496
|
-
goog.asserts.assert(
|
|
1497
|
-
has.mediaElement == null || has.mediaElement == wants.mediaElement,
|
|
1498
|
-
'The routing logic failed. MediaElement requirement failed.');
|
|
1499
|
-
|
|
1500
|
-
if (has.mediaElement == null) {
|
|
1501
|
-
has.mediaElement = wants.mediaElement;
|
|
1502
|
-
|
|
1503
|
-
const onError = (error) => this.onVideoError_(error);
|
|
1504
|
-
this.attachEventManager_.listen(has.mediaElement, 'error', onError);
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
this.video_ = has.mediaElement;
|
|
1508
|
-
|
|
1509
|
-
return Promise.resolve();
|
|
1510
|
-
}
|
|
1511
|
-
|
|
1512
|
-
/**
|
|
1513
|
-
* This should only be called by the load graph when it is time to detach from
|
|
1514
|
-
* a media element. The only times this may be called are when we are being
|
|
1515
|
-
* asked to detach from the current media element, or detach when we are
|
|
1516
|
-
* already detached.
|
|
1517
|
-
*
|
|
1518
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1519
|
-
* responsible for ensuring all assumptions are true.
|
|
1520
|
-
*
|
|
1521
|
-
* Detaching from a media element is defined as:
|
|
1522
|
-
* - Removing error listeners from the media element.
|
|
1523
|
-
* - Dropping the cached reference to the video element.
|
|
1524
|
-
*
|
|
1525
|
-
* @param {shaka.routing.Payload} has
|
|
1526
|
-
* @param {shaka.routing.Payload} wants
|
|
1527
|
-
* @return {!Promise}
|
|
1528
|
-
* @private
|
|
1529
|
-
*/
|
|
1530
|
-
onDetach_(has, wants) {
|
|
1531
|
-
// If we were going from "detached" to "detached" we wouldn't have
|
|
1532
|
-
// a media element to detach from.
|
|
1533
|
-
if (has.mediaElement) {
|
|
1534
|
-
this.attachEventManager_.removeAll();
|
|
1535
|
-
has.mediaElement = null;
|
|
1536
|
-
}
|
|
1537
|
-
|
|
1538
|
-
if (this.adManager_ && !wants.keepAdManager) {
|
|
1539
|
-
// The ad manager is specific to the video, so detach it too.
|
|
1540
|
-
this.adManager_.release();
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
// Clear our cached copy of the media element.
|
|
1544
|
-
this.video_ = null;
|
|
1545
|
-
|
|
1546
|
-
return Promise.resolve();
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
/**
|
|
1550
|
-
* This should only be called by the load graph when it is time to unload all
|
|
1551
|
-
* currently initialized playback components. Unlike the other load actions,
|
|
1552
|
-
* this action is built to be more general. We need to do this because we
|
|
1553
|
-
* don't know what state the player will be in before unloading (including
|
|
1554
|
-
* after an error occurred in the middle of a transition).
|
|
1555
|
-
*
|
|
1556
|
-
* This method assumes that any component could be |null| and should be safe
|
|
1557
|
-
* to call from any point in the load graph.
|
|
1558
|
-
*
|
|
1559
|
-
* @param {shaka.routing.Payload} has
|
|
1560
|
-
* @param {shaka.routing.Payload} wants
|
|
1561
|
-
* @return {!Promise}
|
|
1562
|
-
* @private
|
|
1563
|
-
*/
|
|
1564
|
-
async onUnload_(has, wants) {
|
|
1565
|
-
// Set the load mode to unload right away so that all the public methods
|
|
1566
|
-
// will stop using the internal components. We need to make sure that we
|
|
1567
|
-
// are not overriding the destroyed state because we will unload when we are
|
|
1568
|
-
// destroying the player.
|
|
1569
|
-
if (this.loadMode_ != shaka.Player.LoadMode.DESTROYED) {
|
|
1570
|
-
this.loadMode_ = shaka.Player.LoadMode.NOT_LOADED;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
// Run any general cleanup tasks now. This should be here at the top, right
|
|
1574
|
-
// after setting loadMode_, so that internal components still exist as they
|
|
1575
|
-
// did when the cleanup tasks were registered in the array.
|
|
1576
|
-
const cleanupTasks = this.cleanupOnUnload_.map((cb) => cb());
|
|
1577
|
-
this.cleanupOnUnload_ = [];
|
|
1578
|
-
await Promise.all(cleanupTasks);
|
|
1579
|
-
|
|
1580
|
-
// Dispatch the unloading event.
|
|
1581
|
-
this.dispatchEvent(
|
|
1582
|
-
this.makeEvent_(shaka.util.FakeEvent.EventName.Unloading));
|
|
1583
|
-
|
|
1584
|
-
// Remove everything that has to do with loading content from our payload
|
|
1585
|
-
// since we are releasing everything that depended on it.
|
|
1586
|
-
has.mimeType = null;
|
|
1587
|
-
has.startTime = null;
|
|
1588
|
-
has.uri = null;
|
|
1589
|
-
|
|
1590
|
-
// Release the region timeline, which is created when parsing the manifest.
|
|
1591
|
-
if (this.regionTimeline_) {
|
|
1592
|
-
this.regionTimeline_.release();
|
|
1593
|
-
this.regionTimeline_ = null;
|
|
1594
|
-
}
|
|
1595
|
-
|
|
1596
|
-
// In most cases we should have a media element. The one exception would
|
|
1597
|
-
// be if there was an error and we, by chance, did not have a media element.
|
|
1598
|
-
if (has.mediaElement) {
|
|
1599
|
-
this.loadEventManager_.removeAll();
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
// Stop the variant checker timer
|
|
1603
|
-
this.checkVariantsTimer_.stop();
|
|
1604
|
-
|
|
1605
|
-
// Some observers use some playback components, shutting down the observers
|
|
1606
|
-
// first ensures that they don't try to use the playback components
|
|
1607
|
-
// mid-destroy.
|
|
1608
|
-
if (this.playheadObservers_) {
|
|
1609
|
-
this.playheadObservers_.release();
|
|
1610
|
-
this.playheadObservers_ = null;
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
|
-
if (this.bufferPoller_) {
|
|
1614
|
-
this.bufferPoller_.stop();
|
|
1615
|
-
this.bufferPoller_ = null;
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
// Stop the parser early. Since it is at the start of the pipeline, it
|
|
1619
|
-
// should be start early to avoid is pushing new data downstream.
|
|
1620
|
-
if (this.parser_) {
|
|
1621
|
-
await this.parser_.stop();
|
|
1622
|
-
this.parser_ = null;
|
|
1623
|
-
this.parserFactory_ = null;
|
|
1624
|
-
}
|
|
1625
|
-
|
|
1626
|
-
// Abr Manager will tell streaming engine what to do, so we need to stop
|
|
1627
|
-
// it before we destroy streaming engine. Unlike with the other components,
|
|
1628
|
-
// we do not release the instance, we will reuse it in later loads.
|
|
1629
|
-
if (this.abrManager_) {
|
|
1630
|
-
await this.abrManager_.stop();
|
|
1631
|
-
}
|
|
1632
|
-
|
|
1633
|
-
// Streaming engine will push new data to media source engine, so we need
|
|
1634
|
-
// to shut it down before destroy media source engine.
|
|
1635
|
-
if (this.streamingEngine_) {
|
|
1636
|
-
await this.streamingEngine_.destroy();
|
|
1637
|
-
this.streamingEngine_ = null;
|
|
1638
|
-
}
|
|
1639
|
-
|
|
1640
|
-
if (this.playRateController_) {
|
|
1641
|
-
this.playRateController_.release();
|
|
1642
|
-
this.playRateController_ = null;
|
|
1643
|
-
}
|
|
1644
|
-
|
|
1645
|
-
// Playhead is used by StreamingEngine, so we can't destroy this until after
|
|
1646
|
-
// StreamingEngine has stopped.
|
|
1647
|
-
if (this.playhead_) {
|
|
1648
|
-
this.playhead_.release();
|
|
1649
|
-
this.playhead_ = null;
|
|
1650
|
-
}
|
|
1651
|
-
|
|
1652
|
-
// Media source engine holds onto the media element, and in order to detach
|
|
1653
|
-
// the media keys (with drm engine), we need to break the connection between
|
|
1654
|
-
// media source engine and the media element.
|
|
1655
|
-
if (this.mediaSourceEngine_) {
|
|
1656
|
-
await this.mediaSourceEngine_.destroy();
|
|
1657
|
-
this.mediaSourceEngine_ = null;
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
if (this.adManager_ && !wants.keepAdManager) {
|
|
1661
|
-
this.adManager_.onAssetUnload();
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
// In order to unload a media element, we need to remove the src attribute
|
|
1665
|
-
// and then load again. When we destroy media source engine, this will be
|
|
1666
|
-
// done for us, but for src=, we need to do it here.
|
|
1667
|
-
//
|
|
1668
|
-
// DrmEngine requires this to be done before we destroy DrmEngine itself.
|
|
1669
|
-
if (has.mediaElement && has.mediaElement.src) {
|
|
1670
|
-
// TODO: Investigate this more. Only reproduces on Firefox 69.
|
|
1671
|
-
// Introduce a delay before detaching the video source. We are seeing
|
|
1672
|
-
// spurious Promise rejections involving an AbortError in our tests
|
|
1673
|
-
// otherwise.
|
|
1674
|
-
await new Promise(
|
|
1675
|
-
(resolve) => new shaka.util.Timer(resolve).tickAfter(0.1));
|
|
1676
|
-
|
|
1677
|
-
has.mediaElement.removeAttribute('src');
|
|
1678
|
-
has.mediaElement.load();
|
|
1679
|
-
// Remove all track nodes
|
|
1680
|
-
while (has.mediaElement.lastChild) {
|
|
1681
|
-
has.mediaElement.removeChild(has.mediaElement.firstChild);
|
|
1682
|
-
}
|
|
1683
|
-
}
|
|
1684
|
-
|
|
1685
|
-
if (this.drmEngine_) {
|
|
1686
|
-
await this.drmEngine_.destroy();
|
|
1687
|
-
this.drmEngine_ = null;
|
|
1688
|
-
}
|
|
1689
|
-
|
|
1690
|
-
this.assetUri_ = null;
|
|
1691
|
-
this.bufferObserver_ = null;
|
|
1692
|
-
|
|
1693
|
-
if (this.manifest_) {
|
|
1694
|
-
for (const variant of this.manifest_.variants) {
|
|
1695
|
-
for (const stream of [variant.audio, variant.video]) {
|
|
1696
|
-
if (stream && stream.segmentIndex) {
|
|
1697
|
-
stream.segmentIndex.release();
|
|
1698
|
-
}
|
|
1699
|
-
}
|
|
1700
|
-
}
|
|
1701
|
-
for (const stream of this.manifest_.textStreams) {
|
|
1702
|
-
if (stream.segmentIndex) {
|
|
1703
|
-
stream.segmentIndex.release();
|
|
1704
|
-
}
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
this.manifest_ = null;
|
|
1709
|
-
this.stats_ = new shaka.util.Stats(); // Replace with a clean stats object.
|
|
1710
|
-
this.lastTextFactory_ = null;
|
|
1711
|
-
|
|
1712
|
-
// Make sure that the app knows of the new buffering state.
|
|
1713
|
-
this.updateBufferState_();
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
/**
|
|
1717
|
-
* This should only be called by the load graph when it is time to initialize
|
|
1718
|
-
* media source engine. The only time this may be called is when we are
|
|
1719
|
-
* attached to the same media element as in the request.
|
|
1720
|
-
*
|
|
1721
|
-
* This method assumes that it is safe for it to execute. The load-graph is
|
|
1722
|
-
* responsible for ensuring all assumptions are true.
|
|
1723
|
-
*
|
|
1724
|
-
* @param {shaka.routing.Payload} has
|
|
1725
|
-
* @param {shaka.routing.Payload} wants
|
|
1512
|
+
* Initializes the media source engine.
|
|
1726
1513
|
*
|
|
1727
1514
|
* @return {!Promise}
|
|
1728
1515
|
* @private
|
|
1729
1516
|
*/
|
|
1730
|
-
async
|
|
1517
|
+
async initializeMediaSourceEngineInner_() {
|
|
1731
1518
|
goog.asserts.assert(
|
|
1732
1519
|
shaka.util.Platform.supportsMediaSource(),
|
|
1733
|
-
'We should not be initializing media source on a platform that
|
|
1734
|
-
'not support media source.');
|
|
1520
|
+
'We should not be initializing media source on a platform that ' +
|
|
1521
|
+
'does not support media source.');
|
|
1735
1522
|
goog.asserts.assert(
|
|
1736
|
-
|
|
1523
|
+
this.video_,
|
|
1737
1524
|
'We should have a media element when initializing media source.');
|
|
1738
|
-
goog.asserts.assert(
|
|
1739
|
-
has.mediaElement == wants.mediaElement,
|
|
1740
|
-
'|has| and |wants| should have the same media element when ' +
|
|
1741
|
-
'initializing media source.');
|
|
1742
|
-
|
|
1743
1525
|
goog.asserts.assert(
|
|
1744
1526
|
this.mediaSourceEngine_ == null,
|
|
1745
1527
|
'We should not have a media source engine yet.');
|
|
1746
1528
|
|
|
1529
|
+
this.makeStateChangeEvent_('media-source');
|
|
1530
|
+
|
|
1747
1531
|
// When changing text visibility we need to update both the text displayer
|
|
1748
|
-
// and streaming engine because we don't always stream text. To ensure
|
|
1749
|
-
// text displayer and streaming engine are always in sync, wait
|
|
1750
|
-
// 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.
|
|
1751
1535
|
const textDisplayerFactory = this.config_.textDisplayFactory;
|
|
1752
1536
|
const textDisplayer = textDisplayerFactory();
|
|
1753
1537
|
this.lastTextFactory_ = textDisplayerFactory;
|
|
1754
1538
|
|
|
1755
1539
|
const mediaSourceEngine = this.createMediaSourceEngine(
|
|
1756
|
-
|
|
1540
|
+
this.video_,
|
|
1757
1541
|
textDisplayer,
|
|
1758
1542
|
(metadata, offset, endTime) => {
|
|
1759
1543
|
this.processTimedMetadataMediaSrc_(metadata, offset, endTime);
|
|
@@ -1772,114 +1556,51 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1772
1556
|
}
|
|
1773
1557
|
|
|
1774
1558
|
/**
|
|
1775
|
-
*
|
|
1776
|
-
*
|
|
1777
|
-
*
|
|
1778
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1779
|
-
* responsible for ensuring all assumptions are true.
|
|
1559
|
+
* Pick an initialize a manifest parser, then have it download and parse the
|
|
1560
|
+
* manifest.
|
|
1780
1561
|
*
|
|
1781
|
-
* @param {
|
|
1782
|
-
* @param {shaka.routing.Payload} wants
|
|
1562
|
+
* @param {?string=} mimeType
|
|
1783
1563
|
* @return {!Promise}
|
|
1784
1564
|
* @private
|
|
1785
1565
|
*/
|
|
1786
|
-
async
|
|
1566
|
+
async parseManifestInner_(mimeType) {
|
|
1787
1567
|
goog.asserts.assert(
|
|
1788
|
-
|
|
1789
|
-
'
|
|
1568
|
+
this.assetUri_,
|
|
1569
|
+
'should have a uri when making the parser.');
|
|
1790
1570
|
goog.asserts.assert(
|
|
1791
|
-
|
|
1792
|
-
'
|
|
1793
|
-
'initializing the parser.');
|
|
1794
|
-
|
|
1571
|
+
this.video_,
|
|
1572
|
+
'We should have a media element when initializing the parser.');
|
|
1795
1573
|
goog.asserts.assert(
|
|
1796
1574
|
this.networkingEngine_,
|
|
1797
1575
|
'Need networking engine when initializing the parser.');
|
|
1576
|
+
goog.asserts.assert(
|
|
1577
|
+
this.cmcdManager_,
|
|
1578
|
+
'Need CMCD manager to populate manifest request data.');
|
|
1798
1579
|
goog.asserts.assert(
|
|
1799
1580
|
this.config_,
|
|
1800
1581
|
'Need player config when initializing the parser.');
|
|
1801
1582
|
|
|
1802
|
-
//
|
|
1803
|
-
//
|
|
1804
|
-
has.mimeType = wants.mimeType;
|
|
1805
|
-
has.uri = wants.uri;
|
|
1806
|
-
|
|
1807
|
-
goog.asserts.assert(
|
|
1808
|
-
has.uri,
|
|
1809
|
-
'We should have an asset uri when initializing the parsing.');
|
|
1810
|
-
|
|
1811
|
-
// Store references to things we asserted so that we don't need to reassert
|
|
1812
|
-
// them again later.
|
|
1813
|
-
const assetUri = has.uri;
|
|
1583
|
+
// Store references to things we asserted so that we don't need to
|
|
1584
|
+
// reassert them again later.
|
|
1814
1585
|
const networkingEngine = this.networkingEngine_;
|
|
1815
1586
|
|
|
1816
|
-
|
|
1817
|
-
this.assetUri_ = assetUri;
|
|
1587
|
+
this.makeStateChangeEvent_('manifest-parser');
|
|
1818
1588
|
|
|
1819
1589
|
// Create the parser that we will use to parse the manifest.
|
|
1820
|
-
this.parserFactory_ =
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
this.config_.manifest.retryParameters,
|
|
1824
|
-
has.mimeType);
|
|
1590
|
+
this.parserFactory_ = shaka.media.ManifestParser.getFactory(
|
|
1591
|
+
this.assetUri_,
|
|
1592
|
+
mimeType || null);
|
|
1825
1593
|
goog.asserts.assert(this.parserFactory_, 'Must have manifest parser');
|
|
1826
1594
|
this.parser_ = this.parserFactory_();
|
|
1827
1595
|
|
|
1828
1596
|
const manifestConfig =
|
|
1829
1597
|
shaka.util.ObjectUtils.cloneObject(this.config_.manifest);
|
|
1830
1598
|
// Don't read video segments if the player is attached to an audio element
|
|
1831
|
-
if (
|
|
1599
|
+
if (this.video_ && this.video_.nodeName === 'AUDIO') {
|
|
1832
1600
|
manifestConfig.disableVideo = true;
|
|
1833
1601
|
}
|
|
1834
1602
|
|
|
1835
1603
|
this.parser_.configure(manifestConfig);
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
|
-
/**
|
|
1839
|
-
* Parse the manifest at |has.uri| using the parser that should have already
|
|
1840
|
-
* been created. This should only be called as part of the load graph.
|
|
1841
|
-
*
|
|
1842
|
-
* This method assumes that it is safe for it to execute, the load-graph is
|
|
1843
|
-
* responsible for ensuring all assumptions are true.
|
|
1844
|
-
*
|
|
1845
|
-
* @param {shaka.routing.Payload} has
|
|
1846
|
-
* @param {shaka.routing.Payload} wants
|
|
1847
|
-
* @return {!shaka.util.AbortableOperation}
|
|
1848
|
-
* @private
|
|
1849
|
-
*/
|
|
1850
|
-
onParseManifest_(has, wants) {
|
|
1851
|
-
goog.asserts.assert(
|
|
1852
|
-
has.mimeType == wants.mimeType,
|
|
1853
|
-
'|has| and |wants| should have the same mime type when parsing.');
|
|
1854
|
-
goog.asserts.assert(
|
|
1855
|
-
has.uri == wants.uri,
|
|
1856
|
-
'|has| and |wants| should have the same uri when parsing.');
|
|
1857
|
-
|
|
1858
|
-
goog.asserts.assert(
|
|
1859
|
-
has.uri,
|
|
1860
|
-
'|has| should have a valid uri when parsing.');
|
|
1861
|
-
goog.asserts.assert(
|
|
1862
|
-
has.uri == this.assetUri_,
|
|
1863
|
-
'|has.uri| should match the cached asset uri.');
|
|
1864
|
-
|
|
1865
|
-
goog.asserts.assert(
|
|
1866
|
-
this.networkingEngine_,
|
|
1867
|
-
'Need networking engine to parse manifest.');
|
|
1868
|
-
goog.asserts.assert(
|
|
1869
|
-
this.cmcdManager_,
|
|
1870
|
-
'Need CMCD manager to populate manifest request data.');
|
|
1871
|
-
goog.asserts.assert(
|
|
1872
|
-
this.config_,
|
|
1873
|
-
'Need player config to parse manifest.');
|
|
1874
|
-
|
|
1875
|
-
goog.asserts.assert(
|
|
1876
|
-
this.parser_,
|
|
1877
|
-
'|this.parser_| should have been set in an earlier step.');
|
|
1878
|
-
|
|
1879
|
-
// Store references to things we asserted so that we don't need to reassert
|
|
1880
|
-
// them again later.
|
|
1881
|
-
const assetUri = has.uri;
|
|
1882
|
-
const networkingEngine = this.networkingEngine_;
|
|
1883
1604
|
|
|
1884
1605
|
// This will be needed by the parser once it starts parsing, so we will
|
|
1885
1606
|
// initialize it now even through it appears a little out-of-place.
|
|
@@ -1920,7 +1641,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1920
1641
|
// Called when the parser finds a timeline region. This can be called
|
|
1921
1642
|
// before we start playback or during playback (live/in-progress
|
|
1922
1643
|
// manifest).
|
|
1923
|
-
onTimelineRegionAdded: (region) =>
|
|
1644
|
+
onTimelineRegionAdded: (region) => {
|
|
1645
|
+
this.regionTimeline_.addRegion(region);
|
|
1646
|
+
},
|
|
1924
1647
|
|
|
1925
1648
|
onEvent: (event) => this.dispatchEvent(event),
|
|
1926
1649
|
onError: (error) => this.onError_(error),
|
|
@@ -1956,57 +1679,41 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1956
1679
|
|
|
1957
1680
|
const startTime = Date.now() / 1000;
|
|
1958
1681
|
|
|
1959
|
-
|
|
1960
|
-
this.manifest_ = await this.parser_.start(assetUri, playerInterface);
|
|
1682
|
+
this.makeStateChangeEvent_('manifest');
|
|
1961
1683
|
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
const event =
|
|
1965
|
-
this.makeEvent_(shaka.util.FakeEvent.EventName.ManifestParsed);
|
|
1966
|
-
this.dispatchEvent(event);
|
|
1684
|
+
this.manifest_ = await this.parser_.start(
|
|
1685
|
+
this.assetUri_, playerInterface);
|
|
1967
1686
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
shaka.util.Error.Code.NO_VARIANTS);
|
|
1974
|
-
}
|
|
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);
|
|
1975
1692
|
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
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
|
+
}
|
|
1979
1700
|
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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);
|
|
1987
1708
|
}
|
|
1988
1709
|
|
|
1989
1710
|
/**
|
|
1990
|
-
*
|
|
1991
|
-
* drmEngine. The only time this may be called is when we are attached a
|
|
1992
|
-
* media element and have parsed a manifest.
|
|
1993
|
-
*
|
|
1994
|
-
* The load-graph is responsible for ensuring all assumptions made by this
|
|
1995
|
-
* method are valid before executing it.
|
|
1711
|
+
* Initializes the DRM engine.
|
|
1996
1712
|
*
|
|
1997
|
-
* @param {shaka.routing.Payload} has
|
|
1998
|
-
* @param {shaka.routing.Payload} wants
|
|
1999
1713
|
* @return {!Promise}
|
|
2000
1714
|
* @private
|
|
2001
1715
|
*/
|
|
2002
|
-
async
|
|
2003
|
-
goog.asserts.assert(
|
|
2004
|
-
has.mimeType == wants.mimeType,
|
|
2005
|
-
'The load graph should have ensured the mime types matched.');
|
|
2006
|
-
goog.asserts.assert(
|
|
2007
|
-
has.uri == wants.uri,
|
|
2008
|
-
'The load graph should have ensured the uris matched');
|
|
2009
|
-
|
|
1716
|
+
async initializeDrmInner_() {
|
|
2010
1717
|
goog.asserts.assert(
|
|
2011
1718
|
this.networkingEngine_,
|
|
2012
1719
|
'|onInitializeDrm_| should never be called after |destroy|');
|
|
@@ -2017,9 +1724,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2017
1724
|
this.manifest_,
|
|
2018
1725
|
'|this.manifest_| should have been set in an earlier step.');
|
|
2019
1726
|
goog.asserts.assert(
|
|
2020
|
-
|
|
1727
|
+
this.video_,
|
|
2021
1728
|
'We should have a media element when initializing the DRM Engine.');
|
|
2022
1729
|
|
|
1730
|
+
this.makeStateChangeEvent_('drm-engine');
|
|
1731
|
+
|
|
2023
1732
|
const startTime = Date.now() / 1000;
|
|
2024
1733
|
let firstEvent = true;
|
|
2025
1734
|
|
|
@@ -2042,12 +1751,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2042
1751
|
const now = Date.now() / 1000;
|
|
2043
1752
|
const delta = now - startTime;
|
|
2044
1753
|
this.stats_.setDrmTime(delta);
|
|
2045
|
-
// LCEVC data by itself is not encrypted in DRM protected streams
|
|
2046
|
-
// can therefore be accessed and decoded as normal. However,
|
|
2047
|
-
// decoder needs access to the VideoElement output in
|
|
2048
|
-
// the enhancement. In DRM contexts where the
|
|
2049
|
-
// access from our decoder, the enhancement
|
|
2050
|
-
// therefore the LCEVC output canvas is
|
|
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.
|
|
2051
1761
|
if (this.lcevcDec_) {
|
|
2052
1762
|
this.lcevcDec_.hideCanvas();
|
|
2053
1763
|
}
|
|
@@ -2061,66 +1771,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2061
1771
|
this.manifest_.variants,
|
|
2062
1772
|
this.manifest_.offlineSessionIds);
|
|
2063
1773
|
|
|
2064
|
-
await this.drmEngine_.attach(
|
|
1774
|
+
await this.drmEngine_.attach(this.video_);
|
|
2065
1775
|
|
|
2066
|
-
// Now that we have drm information, filter the manifest (again) so that
|
|
2067
|
-
// 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.
|
|
2068
1778
|
await this.filterManifest_(this.manifest_);
|
|
2069
1779
|
}
|
|
2070
1780
|
|
|
2071
1781
|
/**
|
|
2072
|
-
*
|
|
2073
|
-
* playback components needed for playback. The only times this may be called
|
|
2074
|
-
* is when we are attached to the same media element as in the request.
|
|
1782
|
+
* Starts loading the content described by the parsed manifest.
|
|
2075
1783
|
*
|
|
2076
|
-
*
|
|
2077
|
-
*
|
|
2078
|
-
*
|
|
2079
|
-
* Loading is defined as:
|
|
2080
|
-
* - Attaching all playback-related listeners to the media element
|
|
2081
|
-
* - Initializing playback and observers
|
|
2082
|
-
* - Initializing ABR Manager
|
|
2083
|
-
* - Initializing Streaming Engine
|
|
2084
|
-
* - Starting playback at |wants.startTime|
|
|
2085
|
-
*
|
|
2086
|
-
* @param {shaka.routing.Payload} has
|
|
2087
|
-
* @param {shaka.routing.Payload} wants
|
|
1784
|
+
* @param {number} startTimeOfLoad
|
|
1785
|
+
* @return {!Promise}
|
|
2088
1786
|
* @private
|
|
2089
1787
|
*/
|
|
2090
|
-
async
|
|
2091
|
-
goog.asserts.assert(
|
|
2092
|
-
has.mimeType == wants.mimeType,
|
|
2093
|
-
'|has| and |wants| should have the same mime type when loading.');
|
|
1788
|
+
async loadInner_(startTimeOfLoad) {
|
|
2094
1789
|
goog.asserts.assert(
|
|
2095
|
-
|
|
2096
|
-
'|has| and |wants| should have the same uri when loading.');
|
|
2097
|
-
|
|
1790
|
+
this.video_, 'We should have a media element by now.');
|
|
2098
1791
|
goog.asserts.assert(
|
|
2099
|
-
|
|
2100
|
-
'We should have a media element when loading.');
|
|
1792
|
+
this.manifest_, 'The manifest should already be parsed.');
|
|
2101
1793
|
goog.asserts.assert(
|
|
2102
|
-
|
|
2103
|
-
'|wants| should tell us when the load was originally requested');
|
|
2104
|
-
|
|
2105
|
-
// Since we are about to start playback, we will lock in the start time as
|
|
2106
|
-
// something we are now depending on.
|
|
2107
|
-
has.startTime = wants.startTime;
|
|
2108
|
-
|
|
2109
|
-
// If updateStartTime() has been called since load() was invoked use the
|
|
2110
|
-
// requested startTime
|
|
2111
|
-
if (this.updatedStartTime_ != null) {
|
|
2112
|
-
has.startTime = this.updatedStartTime_;
|
|
2113
|
-
this.updatedStartTime_ = null;
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
// Store a reference to values in |has| after asserting so that closure will
|
|
2117
|
-
// know that they will still be non-null between calls to await.
|
|
2118
|
-
const mediaElement = has.mediaElement;
|
|
2119
|
-
const assetUri = has.uri;
|
|
1794
|
+
this.assetUri_, 'We should have an asset uri by now.');
|
|
2120
1795
|
|
|
2121
|
-
|
|
2122
|
-
this.assetUri_ = assetUri;
|
|
1796
|
+
this.makeStateChangeEvent_('load');
|
|
2123
1797
|
|
|
1798
|
+
const mediaElement = this.video_;
|
|
2124
1799
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
2125
1800
|
getRate: () => mediaElement.playbackRate,
|
|
2126
1801
|
getDefaultRate: () => mediaElement.defaultPlaybackRate,
|
|
@@ -2130,13 +1805,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2130
1805
|
|
|
2131
1806
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
2132
1807
|
const onRateChange = () => this.onRateChange_();
|
|
2133
|
-
this.loadEventManager_.listen(
|
|
1808
|
+
this.loadEventManager_.listen(
|
|
1809
|
+
mediaElement, 'playing', updateStateHistory);
|
|
2134
1810
|
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
2135
1811
|
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
2136
1812
|
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
2137
1813
|
|
|
2138
|
-
// Check status of the LCEVC Dec Object
|
|
2139
|
-
//
|
|
1814
|
+
// Check the status of the LCEVC Dec Object. Reset, create, or close
|
|
1815
|
+
// depending on the config.
|
|
2140
1816
|
this.setupLcevc_(this.config_);
|
|
2141
1817
|
|
|
2142
1818
|
const abrFactory = this.config_.abrFactory;
|
|
@@ -2160,8 +1836,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2160
1836
|
this.config_.preferredVariantRole,
|
|
2161
1837
|
this.config_.preferredAudioChannelCount,
|
|
2162
1838
|
this.config_.preferredVideoHdrLevel,
|
|
1839
|
+
this.config_.preferredVideoLayout,
|
|
2163
1840
|
this.config_.preferredAudioLabel,
|
|
2164
|
-
this.config_.mediaSource.codecSwitchingStrategy
|
|
1841
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
1842
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
2165
1843
|
|
|
2166
1844
|
this.currentTextLanguage_ = this.config_.preferredTextLanguage;
|
|
2167
1845
|
this.currentTextRole_ = this.config_.preferredTextRole;
|
|
@@ -2182,7 +1860,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2182
1860
|
this.manifest_,
|
|
2183
1861
|
this.config_.preferredVideoCodecs,
|
|
2184
1862
|
this.config_.preferredAudioCodecs,
|
|
2185
|
-
this.config_.preferredAudioChannelCount,
|
|
2186
1863
|
this.config_.preferredDecodingAttributes);
|
|
2187
1864
|
|
|
2188
1865
|
this.streamingEngine_ = this.createStreamingEngine();
|
|
@@ -2224,7 +1901,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2224
1901
|
let initialVariant = null;
|
|
2225
1902
|
const activeVariant = this.streamingEngine_.getCurrentVariant();
|
|
2226
1903
|
if (!activeVariant) {
|
|
2227
|
-
initialVariant = this.chooseVariant_();
|
|
1904
|
+
initialVariant = this.chooseVariant_(/* initialSelection= */ true);
|
|
2228
1905
|
goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
|
|
2229
1906
|
}
|
|
2230
1907
|
|
|
@@ -2240,20 +1917,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2240
1917
|
await Promise.all(createSegmentIndexPromises);
|
|
2241
1918
|
}
|
|
2242
1919
|
|
|
1920
|
+
if (this.parser_ && this.parser_.onInitialVariantChosen) {
|
|
1921
|
+
this.parser_.onInitialVariantChosen(toLazyLoad);
|
|
1922
|
+
}
|
|
1923
|
+
|
|
2243
1924
|
shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
|
|
2244
1925
|
this.config_.playRangeStart,
|
|
2245
1926
|
this.config_.playRangeEnd);
|
|
2246
1927
|
|
|
2247
|
-
this.playhead_ = this.createPlayhead(
|
|
1928
|
+
this.playhead_ = this.createPlayhead(this.startTime_);
|
|
2248
1929
|
this.playheadObservers_ =
|
|
2249
|
-
this.createPlayheadObserversForMSE_(
|
|
1930
|
+
this.createPlayheadObserversForMSE_(startTimeOfLoad);
|
|
2250
1931
|
|
|
2251
|
-
// We need to start the buffer management code near the end because it
|
|
2252
|
-
// set the initial buffering state and that depends on other
|
|
2253
|
-
// 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.
|
|
2254
1935
|
const rebufferThreshold = Math.max(
|
|
2255
1936
|
this.manifest_.minBufferTime, this.config_.streaming.rebufferingGoal);
|
|
2256
|
-
this.startBufferManagement_(rebufferThreshold);
|
|
1937
|
+
this.startBufferManagement_(mediaElement, rebufferThreshold);
|
|
2257
1938
|
|
|
2258
1939
|
// Now we can switch to the initial variant.
|
|
2259
1940
|
if (!activeVariant) {
|
|
@@ -2263,14 +1944,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2263
1944
|
this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
|
|
2264
1945
|
/* clearBuffer= */ false, /* safeMargin= */ 0);
|
|
2265
1946
|
|
|
2266
|
-
// Now that we have initial streams, we may adjust the start time to
|
|
2267
|
-
// to a segment boundary.
|
|
1947
|
+
// Now that we have initial streams, we may adjust the start time to
|
|
1948
|
+
// align to a segment boundary.
|
|
2268
1949
|
if (this.config_.streaming.startAtSegmentBoundary) {
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
await this.adjustStartTime_(initialVariant, startTime);
|
|
2272
|
-
|
|
2273
|
-
this.playhead_.setStartTime(adjustedTime);
|
|
1950
|
+
this.playhead_.setStartTime(await this.adjustStartTime_(
|
|
1951
|
+
initialVariant, this.playhead_.getTime()));
|
|
2274
1952
|
}
|
|
2275
1953
|
}
|
|
2276
1954
|
|
|
@@ -2293,7 +1971,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2293
1971
|
this.setInitialTextState_(initialVariant, initialTextStream);
|
|
2294
1972
|
}
|
|
2295
1973
|
|
|
2296
|
-
// 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.
|
|
2297
1976
|
if (initialTextStream && this.shouldStreamText_()) {
|
|
2298
1977
|
this.streamingEngine_.switchTextStream(initialTextStream);
|
|
2299
1978
|
}
|
|
@@ -2309,7 +1988,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2309
1988
|
this.onAbrStatusChanged_();
|
|
2310
1989
|
}
|
|
2311
1990
|
|
|
2312
|
-
// Dispatch a 'trackschanged' event now that all initial filtering is
|
|
1991
|
+
// Dispatch a 'trackschanged' event now that all initial filtering is
|
|
1992
|
+
// done.
|
|
2313
1993
|
this.onTracksChanged_();
|
|
2314
1994
|
|
|
2315
1995
|
// Now that we've filtered out variants that aren't compatible with the
|
|
@@ -2323,8 +2003,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2323
2003
|
|
|
2324
2004
|
const hasPrimary = this.manifest_.variants.some((v) => v.primary);
|
|
2325
2005
|
if (!this.config_.preferredAudioLanguage && !hasPrimary) {
|
|
2326
|
-
shaka.log.warning('No preferred audio language set.
|
|
2327
|
-
|
|
2006
|
+
shaka.log.warning('No preferred audio language set. ' +
|
|
2007
|
+
'We have chosen an arbitrary language initially');
|
|
2328
2008
|
}
|
|
2329
2009
|
|
|
2330
2010
|
const isLive = this.isLive();
|
|
@@ -2333,6 +2013,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2333
2013
|
this.manifest_.serviceDescription)) {
|
|
2334
2014
|
const onTimeUpdate = () => this.onTimeUpdate_();
|
|
2335
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_();
|
|
2336
2021
|
}
|
|
2337
2022
|
|
|
2338
2023
|
if (this.adManager_) {
|
|
@@ -2344,24 +2029,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2344
2029
|
// Wait for the 'loadedmetadata' event to measure load() latency.
|
|
2345
2030
|
this.loadEventManager_.listenOnce(mediaElement, 'loadedmetadata', () => {
|
|
2346
2031
|
const now = Date.now() / 1000;
|
|
2347
|
-
const delta = now -
|
|
2032
|
+
const delta = now - startTimeOfLoad;
|
|
2348
2033
|
this.stats_.setLoadLatency(delta);
|
|
2349
2034
|
});
|
|
2350
2035
|
}
|
|
2351
2036
|
|
|
2352
2037
|
/**
|
|
2353
|
-
*
|
|
2354
|
-
* drmEngine for src= playbacks.
|
|
2355
|
-
*
|
|
2356
|
-
* The load-graph is responsible for ensuring all assumptions made by this
|
|
2357
|
-
* method are valid before executing it.
|
|
2038
|
+
* Initializes the DRM engine for use by src equals.
|
|
2358
2039
|
*
|
|
2359
|
-
* @param {
|
|
2360
|
-
* @param {shaka.routing.Payload} wants
|
|
2040
|
+
* @param {string} mimeType
|
|
2361
2041
|
* @return {!Promise}
|
|
2362
2042
|
* @private
|
|
2363
2043
|
*/
|
|
2364
|
-
async
|
|
2044
|
+
async initializeSrcEqualsDrmInner_(mimeType) {
|
|
2365
2045
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2366
2046
|
|
|
2367
2047
|
goog.asserts.assert(
|
|
@@ -2399,20 +2079,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2399
2079
|
|
|
2400
2080
|
this.drmEngine_.configure(this.config_.drm);
|
|
2401
2081
|
|
|
2402
|
-
const uri = wants.uri || '';
|
|
2403
|
-
const extension = shaka.media.ManifestParser.getExtension(uri);
|
|
2404
|
-
let mimeType = shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
2405
|
-
if (mimeType == 'application/x-mpegurl' && shaka.util.Platform.isApple()) {
|
|
2406
|
-
mimeType = 'application/vnd.apple.mpegurl';
|
|
2407
|
-
}
|
|
2408
|
-
if (!mimeType) {
|
|
2409
|
-
mimeType = 'video/mp4';
|
|
2410
|
-
}
|
|
2411
|
-
|
|
2412
2082
|
// TODO: Instead of feeding DrmEngine with Variants, we should refactor
|
|
2413
2083
|
// DrmEngine so that it takes a minimal config derived from Variants. In
|
|
2414
2084
|
// cases like this one or in removal of stored content, the details are
|
|
2415
|
-
// largely unimportant. We should have a saner way to initialize
|
|
2085
|
+
// largely unimportant. We should have a saner way to initialize
|
|
2086
|
+
// DrmEngine.
|
|
2416
2087
|
// That would also insulate DrmEngine from manifest changes in the future.
|
|
2417
2088
|
// For now, that is time-consuming and this synthetic Variant is easy, so
|
|
2418
2089
|
// I'm putting it off. Since this is only expected to be used for native
|
|
@@ -2430,10 +2101,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2430
2101
|
groupId: null,
|
|
2431
2102
|
createSegmentIndex: () => Promise.resolve(),
|
|
2432
2103
|
segmentIndex: null,
|
|
2433
|
-
mimeType:
|
|
2434
|
-
|
|
2435
|
-
codecs: wants.mimeType ?
|
|
2436
|
-
shaka.util.MimeUtils.getCodecs(wants.mimeType) : '',
|
|
2104
|
+
mimeType: mimeType ? shaka.util.MimeUtils.getBasicType(mimeType) : '',
|
|
2105
|
+
codecs: mimeType ? shaka.util.MimeUtils.getCodecs(mimeType) : '',
|
|
2437
2106
|
encrypted: true,
|
|
2438
2107
|
drmInfos: [], // Filled in by DrmEngine config.
|
|
2439
2108
|
keyIds: new Set(),
|
|
@@ -2452,6 +2121,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2452
2121
|
closedCaptions: null,
|
|
2453
2122
|
accessibilityPurpose: null,
|
|
2454
2123
|
external: false,
|
|
2124
|
+
fastSwitching: false,
|
|
2455
2125
|
},
|
|
2456
2126
|
bandwidth: 100,
|
|
2457
2127
|
allowedByApplication: true,
|
|
@@ -2462,46 +2132,28 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2462
2132
|
this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
|
|
2463
2133
|
await this.drmEngine_.initForPlayback(
|
|
2464
2134
|
[variant], /* offlineSessionIds= */ []);
|
|
2465
|
-
await this.drmEngine_.attach(
|
|
2135
|
+
await this.drmEngine_.attach(this.video_);
|
|
2466
2136
|
}
|
|
2467
2137
|
|
|
2468
2138
|
/**
|
|
2469
|
-
*
|
|
2470
|
-
*
|
|
2471
|
-
* is when we are attached to the same media element as in the request.
|
|
2139
|
+
* Passes the asset URI along to the media element, so it can be played src
|
|
2140
|
+
* equals style.
|
|
2472
2141
|
*
|
|
2473
|
-
*
|
|
2474
|
-
*
|
|
2475
|
-
*
|
|
2476
|
-
* @param {shaka.routing.Payload} has
|
|
2477
|
-
* @param {shaka.routing.Payload} wants
|
|
2478
|
-
* @return {!shaka.util.AbortableOperation}
|
|
2142
|
+
* @param {number} startTimeOfLoad
|
|
2143
|
+
* @param {string} mimeType
|
|
2144
|
+
* @return {!Promise}
|
|
2479
2145
|
*
|
|
2480
2146
|
* @private
|
|
2481
2147
|
*/
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
'We should have a media element when loading.');
|
|
2486
|
-
goog.asserts.assert(
|
|
2487
|
-
wants.uri,
|
|
2488
|
-
'|has| should have a valid uri when loading.');
|
|
2148
|
+
async srcEqualsInner_(startTimeOfLoad, mimeType) {
|
|
2149
|
+
this.makeStateChangeEvent_('src-equals');
|
|
2150
|
+
|
|
2489
2151
|
goog.asserts.assert(
|
|
2490
|
-
|
|
2491
|
-
'|wants| should tell us when the load was originally requested');
|
|
2152
|
+
this.video_, 'We should have a media element when loading.');
|
|
2492
2153
|
goog.asserts.assert(
|
|
2493
|
-
this.
|
|
2494
|
-
'The video element should match our media element');
|
|
2495
|
-
|
|
2496
|
-
// Lock-in the values that we are using so that the routing logic knows what
|
|
2497
|
-
// we have.
|
|
2498
|
-
has.uri = wants.uri;
|
|
2499
|
-
has.startTime = wants.startTime;
|
|
2154
|
+
this.assetUri_, 'We should have a valid uri when loading.');
|
|
2500
2155
|
|
|
2501
|
-
|
|
2502
|
-
this.assetUri_ = has.uri;
|
|
2503
|
-
|
|
2504
|
-
const mediaElement = has.mediaElement;
|
|
2156
|
+
const mediaElement = this.video_;
|
|
2505
2157
|
|
|
2506
2158
|
this.playhead_ = new shaka.media.SrcEqualsPlayhead(mediaElement);
|
|
2507
2159
|
|
|
@@ -2512,8 +2164,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2512
2164
|
unloaded = true;
|
|
2513
2165
|
});
|
|
2514
2166
|
|
|
2515
|
-
if (
|
|
2516
|
-
this.playhead_.setStartTime(
|
|
2167
|
+
if (this.startTime_ != null) {
|
|
2168
|
+
this.playhead_.setStartTime(this.startTime_);
|
|
2517
2169
|
}
|
|
2518
2170
|
|
|
2519
2171
|
this.playRateController_ = new shaka.media.PlayRateController({
|
|
@@ -2523,16 +2175,17 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2523
2175
|
movePlayhead: (delta) => { mediaElement.currentTime += delta; },
|
|
2524
2176
|
});
|
|
2525
2177
|
|
|
2526
|
-
// We need to start the buffer management code near the end because it
|
|
2527
|
-
// set the initial buffering state and that depends on other
|
|
2528
|
-
// 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.
|
|
2529
2181
|
const rebufferThreshold = this.config_.streaming.rebufferingGoal;
|
|
2530
|
-
this.startBufferManagement_(rebufferThreshold);
|
|
2182
|
+
this.startBufferManagement_(mediaElement, rebufferThreshold);
|
|
2531
2183
|
|
|
2532
2184
|
// Add all media element listeners.
|
|
2533
2185
|
const updateStateHistory = () => this.updateStateHistory_();
|
|
2534
2186
|
const onRateChange = () => this.onRateChange_();
|
|
2535
|
-
this.loadEventManager_.listen(
|
|
2187
|
+
this.loadEventManager_.listen(
|
|
2188
|
+
mediaElement, 'playing', updateStateHistory);
|
|
2536
2189
|
this.loadEventManager_.listen(mediaElement, 'pause', updateStateHistory);
|
|
2537
2190
|
this.loadEventManager_.listen(mediaElement, 'ended', updateStateHistory);
|
|
2538
2191
|
this.loadEventManager_.listen(mediaElement, 'ratechange', onRateChange);
|
|
@@ -2542,24 +2195,24 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2542
2195
|
// automatically.
|
|
2543
2196
|
// See https://github.com/shaka-project/shaka-player/issues/2483
|
|
2544
2197
|
if (mediaElement.preload != 'none') {
|
|
2545
|
-
this.loadEventManager_.listenOnce(
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2198
|
+
this.loadEventManager_.listenOnce(
|
|
2199
|
+
mediaElement, 'loadedmetadata', () => {
|
|
2200
|
+
const now = Date.now() / 1000;
|
|
2201
|
+
const delta = now - startTimeOfLoad;
|
|
2202
|
+
this.stats_.setLoadLatency(delta);
|
|
2203
|
+
});
|
|
2550
2204
|
}
|
|
2551
2205
|
|
|
2552
2206
|
// The audio tracks are only available on Safari at the moment, but this
|
|
2553
2207
|
// drives the tracks API for Safari's native HLS. So when they change,
|
|
2554
2208
|
// fire the corresponding Shaka Player event.
|
|
2555
2209
|
if (mediaElement.audioTracks) {
|
|
2556
|
-
this.loadEventManager_.listen(
|
|
2557
|
-
|
|
2558
|
-
this.loadEventManager_.listen(
|
|
2559
|
-
|
|
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',
|
|
2560
2215
|
() => this.onTracksChanged_());
|
|
2561
|
-
this.loadEventManager_.listen(
|
|
2562
|
-
mediaElement.audioTracks, 'change', () => this.onTracksChanged_());
|
|
2563
2216
|
}
|
|
2564
2217
|
|
|
2565
2218
|
if (mediaElement.textTracks) {
|
|
@@ -2595,13 +2248,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2595
2248
|
() => this.onTracksChanged_());
|
|
2596
2249
|
}
|
|
2597
2250
|
|
|
2598
|
-
const extension = shaka.media.ManifestParser.getExtension(has.uri);
|
|
2599
|
-
const mimeType =
|
|
2600
|
-
shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
2601
|
-
|
|
2602
2251
|
// By setting |src| we are done "loading" with src=. We don't need to set
|
|
2603
2252
|
// the current time because |playhead| will do that for us.
|
|
2604
|
-
mediaElement.src = this.cmcdManager_.appendSrcData(
|
|
2253
|
+
mediaElement.src = this.cmcdManager_.appendSrcData(
|
|
2254
|
+
this.assetUri_, mimeType);
|
|
2605
2255
|
|
|
2606
2256
|
// Tizen 3 / WebOS won't load anything unless you call load() explicitly,
|
|
2607
2257
|
// no matter the value of the preload attribute. This is harmful on some
|
|
@@ -2615,15 +2265,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2615
2265
|
// initialized.
|
|
2616
2266
|
this.loadMode_ = shaka.Player.LoadMode.SRC_EQUALS;
|
|
2617
2267
|
|
|
2618
|
-
// The event doesn't mean as much for src= playback, since we don't
|
|
2619
|
-
// streaming. But we should fire it in this path anyway since
|
|
2620
|
-
// 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.
|
|
2621
2271
|
this.dispatchEvent(
|
|
2622
2272
|
this.makeEvent_(shaka.util.FakeEvent.EventName.Streaming));
|
|
2623
2273
|
|
|
2624
2274
|
// The "load" Promise is resolved when we have loaded the metadata. If we
|
|
2625
|
-
// wait for the full data, that won't happen on Safari until the play
|
|
2626
|
-
// is hit.
|
|
2275
|
+
// wait for the full data, that won't happen on Safari until the play
|
|
2276
|
+
// button is hit.
|
|
2627
2277
|
const fullyLoaded = new shaka.util.PublicPromise();
|
|
2628
2278
|
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2629
2279
|
HTMLMediaElement.HAVE_METADATA,
|
|
@@ -2633,7 +2283,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2633
2283
|
fullyLoaded.resolve();
|
|
2634
2284
|
});
|
|
2635
2285
|
|
|
2636
|
-
// 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.
|
|
2637
2288
|
shaka.util.MediaReadyState.waitForReadyState(mediaElement,
|
|
2638
2289
|
HTMLMediaElement.HAVE_CURRENT_DATA,
|
|
2639
2290
|
this.loadEventManager_,
|
|
@@ -2674,9 +2325,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2674
2325
|
} else if (mediaElement.preload == 'none') {
|
|
2675
2326
|
shaka.log.alwaysWarn(
|
|
2676
2327
|
'With <video preload="none">, the browser will not load anything ' +
|
|
2677
|
-
'until play() is called. We are unable to measure load latency
|
|
2678
|
-
'a meaningful way, and we cannot provide track info yet.
|
|
2679
|
-
'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.');
|
|
2680
2331
|
// We can't wait for an event load loadedmetadata, since that will be
|
|
2681
2332
|
// blocked until a user interaction. So resolve the Promise now.
|
|
2682
2333
|
fullyLoaded.resolve();
|
|
@@ -2686,38 +2337,37 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2686
2337
|
fullyLoaded.reject(this.videoErrorToShakaError_());
|
|
2687
2338
|
});
|
|
2688
2339
|
|
|
2689
|
-
|
|
2690
|
-
const abortedError = new shaka.util.Error(
|
|
2691
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
2692
|
-
shaka.util.Error.Category.PLAYER,
|
|
2693
|
-
shaka.util.Error.Code.OPERATION_ABORTED);
|
|
2694
|
-
fullyLoaded.reject(abortedError);
|
|
2695
|
-
return Promise.resolve(); // Abort complete.
|
|
2696
|
-
}).chain(() => {
|
|
2697
|
-
if (this.isLive() && this.config_.streaming.liveSync) {
|
|
2698
|
-
const onTimeUpdate = () => this.onTimeUpdate_();
|
|
2699
|
-
this.loadEventManager_.listen(mediaElement, 'timeupdate', onTimeUpdate);
|
|
2700
|
-
}
|
|
2340
|
+
await fullyLoaded;
|
|
2701
2341
|
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
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
|
+
});
|
|
2717
2367
|
}
|
|
2368
|
+
}
|
|
2718
2369
|
|
|
2719
|
-
|
|
2720
|
-
});
|
|
2370
|
+
this.fullyLoaded_ = true;
|
|
2721
2371
|
}
|
|
2722
2372
|
|
|
2723
2373
|
/**
|
|
@@ -2910,6 +2560,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2910
2560
|
}
|
|
2911
2561
|
}
|
|
2912
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
|
+
|
|
2913
2571
|
/**
|
|
2914
2572
|
* Create a new DrmEngine instance. This may be replaced by tests to create
|
|
2915
2573
|
* fake instances. Configuration and initialization will be handled after
|
|
@@ -2930,13 +2588,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2930
2588
|
* @return {!shaka.net.NetworkingEngine}
|
|
2931
2589
|
*/
|
|
2932
2590
|
createNetworkingEngine() {
|
|
2933
|
-
/** @type {function(number, number)} */
|
|
2934
|
-
const onProgressUpdated_ = (deltaTimeMs, bytesDownloaded) => {
|
|
2591
|
+
/** @type {function(number, number, boolean)} */
|
|
2592
|
+
const onProgressUpdated_ = (deltaTimeMs, bytesDownloaded, allowSwitch) => {
|
|
2935
2593
|
// In some situations, such as during offline storage, the abr manager
|
|
2936
2594
|
// might not yet exist. Therefore, we need to check if abr manager has
|
|
2937
2595
|
// been initialized before using it.
|
|
2938
2596
|
if (this.abrManager_) {
|
|
2939
|
-
this.abrManager_.segmentDownloaded(
|
|
2597
|
+
this.abrManager_.segmentDownloaded(
|
|
2598
|
+
deltaTimeMs, bytesDownloaded, allowSwitch);
|
|
2940
2599
|
}
|
|
2941
2600
|
};
|
|
2942
2601
|
/** @type {shaka.net.NetworkingEngine.OnHeadersReceived} */
|
|
@@ -2965,8 +2624,16 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2965
2624
|
this.cmcdManager_.applyData(type, request, context);
|
|
2966
2625
|
};
|
|
2967
2626
|
|
|
2627
|
+
|
|
2628
|
+
const onRetry_ = (type, context, newUrl, oldUrl) => {
|
|
2629
|
+
if (this.parser_ && this.parser_.banLocation) {
|
|
2630
|
+
this.parser_.banLocation(oldUrl);
|
|
2631
|
+
}
|
|
2632
|
+
};
|
|
2633
|
+
|
|
2968
2634
|
return new shaka.net.NetworkingEngine(
|
|
2969
|
-
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_
|
|
2635
|
+
onProgressUpdated_, onHeadersReceived_, onDownloadFailed_, onRequest_,
|
|
2636
|
+
onRetry_);
|
|
2970
2637
|
}
|
|
2971
2638
|
|
|
2972
2639
|
/**
|
|
@@ -3050,18 +2717,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3050
2717
|
* Initialize and start the buffering system (observer and timer) so that we
|
|
3051
2718
|
* can monitor our buffer lead during playback.
|
|
3052
2719
|
*
|
|
2720
|
+
* @param {!HTMLMediaElement} mediaElement
|
|
3053
2721
|
* @param {number} rebufferingGoal
|
|
3054
2722
|
* @private
|
|
3055
2723
|
*/
|
|
3056
|
-
startBufferManagement_(rebufferingGoal) {
|
|
2724
|
+
startBufferManagement_(mediaElement, rebufferingGoal) {
|
|
3057
2725
|
goog.asserts.assert(
|
|
3058
2726
|
!this.bufferObserver_,
|
|
3059
2727
|
'No buffering observer should exist before initialization.');
|
|
3060
2728
|
|
|
3061
|
-
goog.asserts.assert(
|
|
3062
|
-
!this.bufferPoller_,
|
|
3063
|
-
'No buffer timer should exist before initialization.');
|
|
3064
|
-
|
|
3065
2729
|
// Give dummy values, will be updated below.
|
|
3066
2730
|
this.bufferObserver_ = new shaka.media.BufferingObserver(1, 2);
|
|
3067
2731
|
|
|
@@ -3071,12 +2735,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3071
2735
|
this.updateBufferingSettings_(rebufferingGoal);
|
|
3072
2736
|
this.updateBufferState_();
|
|
3073
2737
|
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
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_());
|
|
3080
2746
|
}
|
|
3081
2747
|
|
|
3082
2748
|
/**
|
|
@@ -3651,17 +3317,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3651
3317
|
|
|
3652
3318
|
/**
|
|
3653
3319
|
* Get the range of time (in seconds) that seeking is allowed. If the player
|
|
3654
|
-
* 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.
|
|
3655
3322
|
*
|
|
3656
3323
|
* @return {{start: number, end: number}}
|
|
3657
3324
|
* @export
|
|
3658
3325
|
*/
|
|
3659
3326
|
seekRange() {
|
|
3660
|
-
if (!this.fullyLoaded_) {
|
|
3661
|
-
return {'start': 0, 'end': 0};
|
|
3662
|
-
}
|
|
3663
|
-
|
|
3664
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
|
+
}
|
|
3665
3336
|
const timeline = this.manifest_.presentationTimeline;
|
|
3666
3337
|
|
|
3667
3338
|
return {
|
|
@@ -3965,13 +3636,82 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3965
3636
|
* @export
|
|
3966
3637
|
*/
|
|
3967
3638
|
getImageTracks() {
|
|
3639
|
+
const StreamUtils = shaka.util.StreamUtils;
|
|
3640
|
+
let imageStreams = this.externalSrcEqualsThumbnailsStreams_;
|
|
3968
3641
|
if (this.manifest_) {
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
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;
|
|
3974
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;
|
|
3711
|
+
}
|
|
3712
|
+
const columns = parseInt(match[1], 10);
|
|
3713
|
+
const rows = parseInt(match[2], 10);
|
|
3714
|
+
return {columns, rows};
|
|
3975
3715
|
}
|
|
3976
3716
|
|
|
3977
3717
|
/**
|
|
@@ -3985,84 +3725,83 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3985
3725
|
* @export
|
|
3986
3726
|
*/
|
|
3987
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_;
|
|
3988
3733
|
if (this.manifest_) {
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
sprite = true;
|
|
4047
|
-
height = thumbnailSprite.height;
|
|
4048
|
-
positionX = thumbnailSprite.positionX;
|
|
4049
|
-
positionY = thumbnailSprite.positionY;
|
|
4050
|
-
width = thumbnailSprite.width;
|
|
4051
|
-
}
|
|
4052
|
-
return {
|
|
4053
|
-
imageHeight: fullImageHeight,
|
|
4054
|
-
imageWidth: fullImageWidth,
|
|
4055
|
-
height: height,
|
|
4056
|
-
positionX: positionX,
|
|
4057
|
-
positionY: positionY,
|
|
4058
|
-
startTime: thumbnailTime,
|
|
4059
|
-
duration: thumbnailDuration,
|
|
4060
|
-
uris: reference.getUris(),
|
|
4061
|
-
width: width,
|
|
4062
|
-
sprite: sprite,
|
|
4063
|
-
};
|
|
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;
|
|
4064
3791
|
}
|
|
4065
|
-
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
|
+
};
|
|
4066
3805
|
}
|
|
4067
3806
|
|
|
4068
3807
|
/**
|
|
@@ -4179,7 +3918,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4179
3918
|
// When track is selected, back-propagate the language to
|
|
4180
3919
|
// currentAudioLanguage_.
|
|
4181
3920
|
this.currentAdaptationSetCriteria_ = new shaka.media.ExampleBasedCriteria(
|
|
4182
|
-
variant
|
|
3921
|
+
variant,
|
|
3922
|
+
this.config_.mediaSource.codecSwitchingStrategy,
|
|
3923
|
+
this.config_.manifest.dash.enableAudioGroups);
|
|
4183
3924
|
|
|
4184
3925
|
// Update AbrManager variants to match these new settings.
|
|
4185
3926
|
this.updateAbrManagerVariants_();
|
|
@@ -4255,9 +3996,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4255
3996
|
selectAudioLanguage(language, role, channelsCount = 0, safeMargin = 0) {
|
|
4256
3997
|
if (this.manifest_ && this.playhead_) {
|
|
4257
3998
|
this.currentAdaptationSetCriteria_ =
|
|
4258
|
-
new shaka.media.PreferenceBasedCriteria(
|
|
4259
|
-
|
|
4260
|
-
|
|
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);
|
|
4261
4008
|
|
|
4262
4009
|
const diff = (a, b) => {
|
|
4263
4010
|
if (!a.video && !b.video) {
|
|
@@ -4374,8 +4121,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4374
4121
|
// label have the same language.
|
|
4375
4122
|
this.currentAdaptationSetCriteria_ =
|
|
4376
4123
|
new shaka.media.PreferenceBasedCriteria(
|
|
4377
|
-
firstVariantWithLabel.language,
|
|
4378
|
-
|
|
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);
|
|
4379
4132
|
|
|
4380
4133
|
this.chooseVariantAndSwitch_(clearBuffer, safeMargin);
|
|
4381
4134
|
} else if (this.video_ && this.video_.audioTracks) {
|
|
@@ -4403,7 +4156,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4403
4156
|
isTextTrackVisible() {
|
|
4404
4157
|
const expected = this.isTextVisible_;
|
|
4405
4158
|
|
|
4406
|
-
if (this.mediaSourceEngine_
|
|
4159
|
+
if (this.mediaSourceEngine_ &&
|
|
4160
|
+
this.loadMode_ == shaka.Player.LoadMode.MEDIA_SOURCE) {
|
|
4407
4161
|
// Make sure our values are still in-sync.
|
|
4408
4162
|
const actual = this.mediaSourceEngine_.getTextDisplayer().isTextVisible();
|
|
4409
4163
|
goog.asserts.assert(
|
|
@@ -4444,6 +4198,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4444
4198
|
* @export
|
|
4445
4199
|
*/
|
|
4446
4200
|
getChapters(language) {
|
|
4201
|
+
if (!this.video_ || !this.video_.src || !this.video_.textTracks) {
|
|
4202
|
+
return [];
|
|
4203
|
+
}
|
|
4447
4204
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
4448
4205
|
const inputlanguage = LanguageUtils.normalize(language);
|
|
4449
4206
|
const chaptersTracks = this.getChaptersTracks_();
|
|
@@ -4602,22 +4359,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4602
4359
|
return null;
|
|
4603
4360
|
}
|
|
4604
4361
|
|
|
4605
|
-
const walkerPayload = this.walker_.getCurrentPayload();
|
|
4606
|
-
|
|
4607
4362
|
let presentationTime = 0;
|
|
4608
4363
|
if (this.playhead_) {
|
|
4609
4364
|
presentationTime = this.playhead_.getTime();
|
|
4610
|
-
} else if (
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
presentationTime = walkerPayload.startTime;
|
|
4620
|
-
}
|
|
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_;
|
|
4621
4374
|
}
|
|
4622
4375
|
|
|
4623
4376
|
if (this.manifest_) {
|
|
@@ -4842,9 +4595,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4842
4595
|
}
|
|
4843
4596
|
await this.addSrcTrackElement_(uri, language, kind, mimeType, label || '',
|
|
4844
4597
|
adCuePoints);
|
|
4598
|
+
|
|
4599
|
+
const LanguageUtils = shaka.util.LanguageUtils;
|
|
4600
|
+
const languageNormalized = LanguageUtils.normalize(language);
|
|
4601
|
+
|
|
4845
4602
|
const textTracks = this.getTextTracks();
|
|
4846
4603
|
const srcTrack = textTracks.find((t) => {
|
|
4847
|
-
return t.language ==
|
|
4604
|
+
return LanguageUtils.normalize(t.language) == languageNormalized &&
|
|
4848
4605
|
t.label == (label || '') &&
|
|
4849
4606
|
t.kind == kind;
|
|
4850
4607
|
});
|
|
@@ -4863,7 +4620,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4863
4620
|
|
|
4864
4621
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
4865
4622
|
|
|
4866
|
-
|
|
4623
|
+
let duration = this.video_.duration;
|
|
4624
|
+
if (this.manifest_) {
|
|
4625
|
+
duration = this.manifest_.presentationTimeline.getDuration();
|
|
4626
|
+
}
|
|
4867
4627
|
if (duration == Infinity) {
|
|
4868
4628
|
throw new shaka.util.Error(
|
|
4869
4629
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
@@ -4914,6 +4674,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4914
4674
|
closedCaptions: null,
|
|
4915
4675
|
accessibilityPurpose: null,
|
|
4916
4676
|
external: true,
|
|
4677
|
+
fastSwitching: false,
|
|
4917
4678
|
};
|
|
4918
4679
|
|
|
4919
4680
|
const fullMimeType = shaka.util.MimeUtils.getFullType(
|
|
@@ -4957,14 +4718,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4957
4718
|
shaka.util.Error.Code.CONTENT_NOT_LOADED);
|
|
4958
4719
|
}
|
|
4959
4720
|
|
|
4960
|
-
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
4961
|
-
shaka.log.error('Cannot add this thumbnail track when loaded with src=');
|
|
4962
|
-
throw new shaka.util.Error(
|
|
4963
|
-
shaka.util.Error.Severity.RECOVERABLE,
|
|
4964
|
-
shaka.util.Error.Category.TEXT,
|
|
4965
|
-
shaka.util.Error.Code.CANNOT_ADD_EXTERNAL_THUMBNAILS_TO_SRC_EQUALS);
|
|
4966
|
-
}
|
|
4967
|
-
|
|
4968
4721
|
if (!mimeType) {
|
|
4969
4722
|
mimeType = await this.getTextMimetype_(uri);
|
|
4970
4723
|
}
|
|
@@ -4978,8 +4731,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4978
4731
|
}
|
|
4979
4732
|
|
|
4980
4733
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
4981
|
-
|
|
4982
|
-
|
|
4734
|
+
let duration = this.video_.duration;
|
|
4735
|
+
if (this.manifest_) {
|
|
4736
|
+
duration = this.manifest_.presentationTimeline.getDuration();
|
|
4737
|
+
}
|
|
4983
4738
|
if (duration == Infinity) {
|
|
4984
4739
|
throw new shaka.util.Error(
|
|
4985
4740
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
@@ -5009,7 +4764,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5009
4764
|
vttOffset: 0,
|
|
5010
4765
|
};
|
|
5011
4766
|
const data = shaka.util.BufferUtils.toUint8(buffer);
|
|
5012
|
-
const cues = TextParser.parseMedia(data, time);
|
|
4767
|
+
const cues = TextParser.parseMedia(data, time, uri);
|
|
5013
4768
|
|
|
5014
4769
|
const references = [];
|
|
5015
4770
|
for (const cue of cues) {
|
|
@@ -5069,9 +4824,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5069
4824
|
tilesLayout: '1x1',
|
|
5070
4825
|
accessibilityPurpose: null,
|
|
5071
4826
|
external: true,
|
|
4827
|
+
fastSwitching: false,
|
|
5072
4828
|
};
|
|
5073
4829
|
|
|
5074
|
-
this.
|
|
4830
|
+
if (this.loadMode_ == shaka.Player.LoadMode.SRC_EQUALS) {
|
|
4831
|
+
this.externalSrcEqualsThumbnailsStreams_.push(stream);
|
|
4832
|
+
} else {
|
|
4833
|
+
this.manifest_.imageStreams.push(stream);
|
|
4834
|
+
}
|
|
5075
4835
|
this.onTracksChanged_();
|
|
5076
4836
|
return shaka.util.StreamUtils.imageStreamToTrack(stream);
|
|
5077
4837
|
}
|
|
@@ -5133,6 +4893,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5133
4893
|
});
|
|
5134
4894
|
});
|
|
5135
4895
|
|
|
4896
|
+
this.onTracksChanged_();
|
|
4897
|
+
|
|
5136
4898
|
return chaptersTrack;
|
|
5137
4899
|
}
|
|
5138
4900
|
|
|
@@ -5151,19 +4913,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5151
4913
|
* @private
|
|
5152
4914
|
*/
|
|
5153
4915
|
async getTextMimetype_(uri) {
|
|
5154
|
-
|
|
5155
|
-
const extension = shaka.media.ManifestParser.getExtension(uri);
|
|
5156
|
-
let mimeType = shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
5157
|
-
|
|
5158
|
-
if (mimeType) {
|
|
5159
|
-
return mimeType;
|
|
5160
|
-
}
|
|
5161
|
-
|
|
4916
|
+
let mimeType;
|
|
5162
4917
|
try {
|
|
5163
4918
|
goog.asserts.assert(
|
|
5164
4919
|
this.networkingEngine_, 'Need networking engine.');
|
|
5165
4920
|
// eslint-disable-next-line require-atomic-updates
|
|
5166
|
-
mimeType = await shaka.
|
|
4921
|
+
mimeType = await shaka.net.NetworkingUtils.getMimeType(uri,
|
|
5167
4922
|
this.networkingEngine_,
|
|
5168
4923
|
this.config_.streaming.retryParameters);
|
|
5169
4924
|
} catch (error) {}
|
|
@@ -5174,12 +4929,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5174
4929
|
|
|
5175
4930
|
shaka.log.error(
|
|
5176
4931
|
'The mimeType has not been provided and it could not be deduced ' +
|
|
5177
|
-
'from its
|
|
4932
|
+
'from its uri.');
|
|
5178
4933
|
throw new shaka.util.Error(
|
|
5179
4934
|
shaka.util.Error.Severity.RECOVERABLE,
|
|
5180
4935
|
shaka.util.Error.Category.TEXT,
|
|
5181
4936
|
shaka.util.Error.Code.TEXT_COULD_NOT_GUESS_MIME_TYPE,
|
|
5182
|
-
|
|
4937
|
+
uri);
|
|
5183
4938
|
}
|
|
5184
4939
|
|
|
5185
4940
|
/**
|
|
@@ -5268,7 +5023,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5268
5023
|
vttOffset: 0,
|
|
5269
5024
|
};
|
|
5270
5025
|
const data = shaka.util.BufferUtils.toUint8(buffer);
|
|
5271
|
-
const cues = obj.parseMedia(data, time);
|
|
5026
|
+
const cues = obj.parseMedia(data, time, /* uri= */ null);
|
|
5272
5027
|
return shaka.text.WebVttGenerator.convert(cues, adCuePoints);
|
|
5273
5028
|
}
|
|
5274
5029
|
throw new shaka.util.Error(
|
|
@@ -5387,7 +5142,14 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5387
5142
|
return new shaka.text.UITextDisplayer(
|
|
5388
5143
|
this.video_, this.videoContainer_);
|
|
5389
5144
|
} else {
|
|
5390
|
-
|
|
5145
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
5146
|
+
if (HTMLMediaElement.prototype.addTextTrack) {
|
|
5147
|
+
return new shaka.text.SimpleTextDisplayer(this.video_);
|
|
5148
|
+
} else {
|
|
5149
|
+
shaka.log.warning('Text tracks are not supported by the ' +
|
|
5150
|
+
'browser, disabling.');
|
|
5151
|
+
return new shaka.text.StubTextDisplayer();
|
|
5152
|
+
}
|
|
5391
5153
|
}
|
|
5392
5154
|
};
|
|
5393
5155
|
return config;
|
|
@@ -5491,6 +5253,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5491
5253
|
closedCaptions: null,
|
|
5492
5254
|
accessibilityPurpose: null,
|
|
5493
5255
|
external: false,
|
|
5256
|
+
fastSwitching: false,
|
|
5494
5257
|
};
|
|
5495
5258
|
manifest.textStreams.push(textStream);
|
|
5496
5259
|
closedCaptionsSet.add(id);
|
|
@@ -5519,15 +5282,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5519
5282
|
*/
|
|
5520
5283
|
async filterManifestWithStreamUtils_(manifest) {
|
|
5521
5284
|
goog.asserts.assert(manifest, 'Manifest should exist!');
|
|
5522
|
-
|
|
5523
|
-
|
|
5524
|
-
/** @type {?shaka.extern.Variant} */
|
|
5525
|
-
const currentVariant = this.streamingEngine_ ?
|
|
5526
|
-
this.streamingEngine_.getCurrentVariant() : null;
|
|
5527
|
-
|
|
5528
|
-
await shaka.util.StreamUtils.filterManifest(
|
|
5529
|
-
this.drmEngine_, currentVariant, manifest,
|
|
5530
|
-
this.config_.mediaSource.codecSwitchingStrategy);
|
|
5285
|
+
await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest);
|
|
5531
5286
|
this.checkPlayableVariants_(manifest);
|
|
5532
5287
|
}
|
|
5533
5288
|
|
|
@@ -5652,7 +5407,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5652
5407
|
const loaded = this.stats_ && this.bufferObserver_ && this.playhead_;
|
|
5653
5408
|
|
|
5654
5409
|
if (loaded) {
|
|
5655
|
-
this.playRateController_.setBuffering(isBuffering);
|
|
5656
5410
|
if (this.cmcdManager_) {
|
|
5657
5411
|
this.cmcdManager_.setBuffering(isBuffering);
|
|
5658
5412
|
}
|
|
@@ -5773,6 +5527,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5773
5527
|
}
|
|
5774
5528
|
}
|
|
5775
5529
|
|
|
5530
|
+
let liveSyncMinLatency;
|
|
5531
|
+
let liveSyncMinPlaybackRate;
|
|
5532
|
+
if (this.config_.streaming.liveSync) {
|
|
5533
|
+
liveSyncMinLatency = this.config_.streaming.liveSyncMinLatency;
|
|
5534
|
+
liveSyncMinPlaybackRate = this.config_.streaming.liveSyncMinPlaybackRate;
|
|
5535
|
+
} else {
|
|
5536
|
+
// serviceDescription must override if it is defined in the MPD and
|
|
5537
|
+
// liveSync configuration is not set.
|
|
5538
|
+
if (this.manifest_ && this.manifest_.serviceDescription) {
|
|
5539
|
+
liveSyncMinLatency = this.manifest_.serviceDescription.minLatency ||
|
|
5540
|
+
this.config_.streaming.liveSyncMinLatency;
|
|
5541
|
+
liveSyncMinPlaybackRate =
|
|
5542
|
+
this.manifest_.serviceDescription.minPlaybackRate ||
|
|
5543
|
+
this.config_.streaming.liveSyncMinPlaybackRate;
|
|
5544
|
+
}
|
|
5545
|
+
}
|
|
5546
|
+
|
|
5776
5547
|
const playbackRate = this.video_.playbackRate;
|
|
5777
5548
|
const latency = seekRange.end - this.video_.currentTime;
|
|
5778
5549
|
let offset = 0;
|
|
@@ -5796,11 +5567,62 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5796
5567
|
'Updating playbackRate to ' + liveSyncPlaybackRate);
|
|
5797
5568
|
this.trickPlay(liveSyncPlaybackRate);
|
|
5798
5569
|
}
|
|
5799
|
-
} else if (
|
|
5570
|
+
} else if (liveSyncMinLatency && liveSyncMinPlaybackRate &&
|
|
5571
|
+
(latency - offset) < liveSyncMinLatency) {
|
|
5572
|
+
if (playbackRate != liveSyncMinPlaybackRate) {
|
|
5573
|
+
shaka.log.debug('Latency (' + latency + 's) ' +
|
|
5574
|
+
'is smaller than liveSyncMinLatency (' + liveSyncMinLatency + 's). ' +
|
|
5575
|
+
'Updating playbackRate to ' + liveSyncMinPlaybackRate);
|
|
5576
|
+
this.trickPlay(liveSyncMinPlaybackRate);
|
|
5577
|
+
}
|
|
5578
|
+
} else if (playbackRate !== this.playRateController_.getDefaultRate()) {
|
|
5800
5579
|
this.cancelTrickPlay();
|
|
5801
5580
|
}
|
|
5802
5581
|
}
|
|
5803
5582
|
|
|
5583
|
+
/**
|
|
5584
|
+
* Callback for video progress events
|
|
5585
|
+
*
|
|
5586
|
+
* @private
|
|
5587
|
+
*/
|
|
5588
|
+
onVideoProgress_() {
|
|
5589
|
+
if (!this.video_) {
|
|
5590
|
+
return;
|
|
5591
|
+
}
|
|
5592
|
+
let hasNewCompletionPercent = false;
|
|
5593
|
+
const completionRatio = this.video_.currentTime / this.video_.duration;
|
|
5594
|
+
if (!isNaN(completionRatio)) {
|
|
5595
|
+
const percent = Math.round(100 * completionRatio);
|
|
5596
|
+
if (isNaN(this.completionPercent_)) {
|
|
5597
|
+
this.completionPercent_ = percent;
|
|
5598
|
+
hasNewCompletionPercent = true;
|
|
5599
|
+
} else {
|
|
5600
|
+
const newCompletionPercent = Math.max(this.completionPercent_, percent);
|
|
5601
|
+
if (this.completionPercent_ != newCompletionPercent) {
|
|
5602
|
+
this.completionPercent_ = newCompletionPercent;
|
|
5603
|
+
hasNewCompletionPercent = true;
|
|
5604
|
+
}
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
if (hasNewCompletionPercent) {
|
|
5608
|
+
let event;
|
|
5609
|
+
if (this.completionPercent_ == 0) {
|
|
5610
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.Started);
|
|
5611
|
+
} else if (this.completionPercent_ == 25) {
|
|
5612
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.FirstQuartile);
|
|
5613
|
+
} else if (this.completionPercent_ == 50) {
|
|
5614
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.Midpoint);
|
|
5615
|
+
} else if (this.completionPercent_ == 75) {
|
|
5616
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.ThirdQuartile);
|
|
5617
|
+
} else if (this.completionPercent_ == 100) {
|
|
5618
|
+
event = this.makeEvent_(shaka.util.FakeEvent.EventName.Complete);
|
|
5619
|
+
}
|
|
5620
|
+
if (event) {
|
|
5621
|
+
this.dispatchEvent(event);
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5625
|
+
|
|
5804
5626
|
/**
|
|
5805
5627
|
* Callback from Playhead.
|
|
5806
5628
|
*
|
|
@@ -5855,12 +5677,13 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5855
5677
|
*
|
|
5856
5678
|
* On error, this dispatches an error event and returns null.
|
|
5857
5679
|
*
|
|
5680
|
+
* @param {boolean=} initialSelection
|
|
5858
5681
|
* @return {?shaka.extern.Variant}
|
|
5859
5682
|
* @private
|
|
5860
5683
|
*/
|
|
5861
|
-
chooseVariant_() {
|
|
5684
|
+
chooseVariant_(initialSelection = false) {
|
|
5862
5685
|
if (this.updateAbrManagerVariants_()) {
|
|
5863
|
-
return this.abrManager_.chooseVariant();
|
|
5686
|
+
return this.abrManager_.chooseVariant(initialSelection);
|
|
5864
5687
|
} else {
|
|
5865
5688
|
return null;
|
|
5866
5689
|
}
|
|
@@ -6901,8 +6724,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6901
6724
|
// Because Safari's native HLS reports slightly inaccurate values for
|
|
6902
6725
|
// bufferEnd here, we use a fudge factor. Without this, we can end up in a
|
|
6903
6726
|
// buffering state at the end of the stream. See issue #2117.
|
|
6904
|
-
// TODO: Try to remove the fudge here once we no longer manage buffering
|
|
6905
|
-
// state above the browser with playbackRate=0.
|
|
6906
6727
|
const fudge = 1; // 1000 ms
|
|
6907
6728
|
return bufferEnd != null && bufferEnd >= this.video_.duration - fudge;
|
|
6908
6729
|
}
|
|
@@ -6919,291 +6740,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
6919
6740
|
shaka.util.Error.Category.PLAYER,
|
|
6920
6741
|
shaka.util.Error.Code.LOAD_INTERRUPTED);
|
|
6921
6742
|
}
|
|
6922
|
-
|
|
6923
|
-
/**
|
|
6924
|
-
* Key
|
|
6925
|
-
* ----------------------
|
|
6926
|
-
* D : Detach Node
|
|
6927
|
-
* A : Attach Node
|
|
6928
|
-
* MS : Media Source Node
|
|
6929
|
-
* P : Manifest Parser Node
|
|
6930
|
-
* M : Manifest Node
|
|
6931
|
-
* DRM : Drm Engine Node
|
|
6932
|
-
* L : Load Node
|
|
6933
|
-
* U : Unloading Node
|
|
6934
|
-
* SRC : Src Equals Node
|
|
6935
|
-
*
|
|
6936
|
-
* Graph Topology
|
|
6937
|
-
* ----------------------
|
|
6938
|
-
*
|
|
6939
|
-
* [SRC]-----+
|
|
6940
|
-
* ^ |
|
|
6941
|
-
* | v
|
|
6942
|
-
* [D]<-->[A]<-----[U]
|
|
6943
|
-
* | ^
|
|
6944
|
-
* v |
|
|
6945
|
-
* [MS]------+
|
|
6946
|
-
* | |
|
|
6947
|
-
* v |
|
|
6948
|
-
* [P]-------+
|
|
6949
|
-
* | |
|
|
6950
|
-
* v |
|
|
6951
|
-
* [M]-------+
|
|
6952
|
-
* | |
|
|
6953
|
-
* v |
|
|
6954
|
-
* [DRM]-----+
|
|
6955
|
-
* | |
|
|
6956
|
-
* v |
|
|
6957
|
-
* [L]-------+
|
|
6958
|
-
*
|
|
6959
|
-
* @param {!shaka.routing.Node} currentlyAt
|
|
6960
|
-
* @param {shaka.routing.Payload} currentlyWith
|
|
6961
|
-
* @param {!shaka.routing.Node} wantsToBeAt
|
|
6962
|
-
* @param {shaka.routing.Payload} wantsToHave
|
|
6963
|
-
* @return {?shaka.routing.Node}
|
|
6964
|
-
* @private
|
|
6965
|
-
*/
|
|
6966
|
-
getNextStep_(currentlyAt, currentlyWith, wantsToBeAt, wantsToHave) {
|
|
6967
|
-
let next = null;
|
|
6968
|
-
|
|
6969
|
-
// Detach is very simple, either stay in detach (because |detach| was called
|
|
6970
|
-
// while in detached) or go somewhere that requires us to attach to an
|
|
6971
|
-
// element.
|
|
6972
|
-
if (currentlyAt == this.detachNode_) {
|
|
6973
|
-
next = wantsToBeAt == this.detachNode_ ?
|
|
6974
|
-
this.detachNode_ :
|
|
6975
|
-
this.attachNode_;
|
|
6976
|
-
}
|
|
6977
|
-
|
|
6978
|
-
if (currentlyAt == this.attachNode_) {
|
|
6979
|
-
next = this.getNextAfterAttach_(wantsToBeAt, currentlyWith, wantsToHave);
|
|
6980
|
-
}
|
|
6981
|
-
|
|
6982
|
-
if (currentlyAt == this.mediaSourceNode_) {
|
|
6983
|
-
next = this.getNextAfterMediaSource_(
|
|
6984
|
-
wantsToBeAt, currentlyWith, wantsToHave);
|
|
6985
|
-
}
|
|
6986
|
-
|
|
6987
|
-
if (currentlyAt == this.parserNode_) {
|
|
6988
|
-
next = this.getNextMatchingAllDependencies_(
|
|
6989
|
-
/* destination= */ this.loadNode_,
|
|
6990
|
-
/* next= */ this.manifestNode_,
|
|
6991
|
-
/* reset= */ this.unloadNode_,
|
|
6992
|
-
/* goingTo= */ wantsToBeAt,
|
|
6993
|
-
/* has= */ currentlyWith,
|
|
6994
|
-
/* wants= */ wantsToHave);
|
|
6995
|
-
}
|
|
6996
|
-
|
|
6997
|
-
if (currentlyAt == this.manifestNode_) {
|
|
6998
|
-
next = this.getNextMatchingAllDependencies_(
|
|
6999
|
-
/* destination= */ this.loadNode_,
|
|
7000
|
-
/* next= */ this.drmNode_,
|
|
7001
|
-
/* reset= */ this.unloadNode_,
|
|
7002
|
-
/* goingTo= */ wantsToBeAt,
|
|
7003
|
-
/* has= */ currentlyWith,
|
|
7004
|
-
/* wants= */ wantsToHave);
|
|
7005
|
-
}
|
|
7006
|
-
|
|
7007
|
-
// For DRM, we have two options "load" or "unload". If all our constraints
|
|
7008
|
-
// are met, we can go to "load". If anything is off, we must go back to
|
|
7009
|
-
// "unload" to reset.
|
|
7010
|
-
if (currentlyAt == this.drmNode_) {
|
|
7011
|
-
next = this.getNextMatchingAllDependencies_(
|
|
7012
|
-
/* destination= */ this.loadNode_,
|
|
7013
|
-
/* next= */ this.loadNode_,
|
|
7014
|
-
/* reset= */ this.unloadNode_,
|
|
7015
|
-
/* goingTo= */ wantsToBeAt,
|
|
7016
|
-
/* has= */ currentlyWith,
|
|
7017
|
-
/* wants= */ wantsToHave);
|
|
7018
|
-
}
|
|
7019
|
-
|
|
7020
|
-
// For DRM w/ src= playback, we only care about destination and media
|
|
7021
|
-
// element.
|
|
7022
|
-
if (currentlyAt == this.srcEqualsDrmNode_) {
|
|
7023
|
-
if (wantsToBeAt == this.srcEqualsNode_ &&
|
|
7024
|
-
currentlyWith.mediaElement == wantsToHave.mediaElement) {
|
|
7025
|
-
next = this.srcEqualsNode_;
|
|
7026
|
-
} else {
|
|
7027
|
-
next = this.unloadNode_;
|
|
7028
|
-
}
|
|
7029
|
-
}
|
|
7030
|
-
|
|
7031
|
-
// After we load content, always go through unload because we can't safely
|
|
7032
|
-
// use components after we have started playback.
|
|
7033
|
-
if (currentlyAt == this.loadNode_ || currentlyAt == this.srcEqualsNode_) {
|
|
7034
|
-
next = this.unloadNode_;
|
|
7035
|
-
}
|
|
7036
|
-
|
|
7037
|
-
if (currentlyAt == this.unloadNode_) {
|
|
7038
|
-
next = this.getNextAfterUnload_(wantsToBeAt, currentlyWith, wantsToHave);
|
|
7039
|
-
}
|
|
7040
|
-
|
|
7041
|
-
goog.asserts.assert(next, 'Missing next step!');
|
|
7042
|
-
return next;
|
|
7043
|
-
}
|
|
7044
|
-
|
|
7045
|
-
/**
|
|
7046
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7047
|
-
* @param {shaka.routing.Payload} has
|
|
7048
|
-
* @param {shaka.routing.Payload} wants
|
|
7049
|
-
* @return {?shaka.routing.Node}
|
|
7050
|
-
* @private
|
|
7051
|
-
*/
|
|
7052
|
-
getNextAfterAttach_(goingTo, has, wants) {
|
|
7053
|
-
// Attach and detach are the only two nodes that we can directly go
|
|
7054
|
-
// back-and-forth between.
|
|
7055
|
-
if (goingTo == this.detachNode_) {
|
|
7056
|
-
return this.detachNode_;
|
|
7057
|
-
}
|
|
7058
|
-
|
|
7059
|
-
// If we are going anywhere other than detach, then we need the media
|
|
7060
|
-
// element to match, if they don't match, we need to go through detach
|
|
7061
|
-
// first.
|
|
7062
|
-
if (has.mediaElement != wants.mediaElement) {
|
|
7063
|
-
return this.detachNode_;
|
|
7064
|
-
}
|
|
7065
|
-
|
|
7066
|
-
// If we are already in attached, and someone calls |attach| again (to the
|
|
7067
|
-
// same video element), we can handle the redundant request by re-entering
|
|
7068
|
-
// our current state.
|
|
7069
|
-
if (goingTo == this.attachNode_) {
|
|
7070
|
-
return this.attachNode_;
|
|
7071
|
-
}
|
|
7072
|
-
|
|
7073
|
-
// The next step from attached to loaded is through media source.
|
|
7074
|
-
if (goingTo == this.mediaSourceNode_ || goingTo == this.loadNode_) {
|
|
7075
|
-
return this.mediaSourceNode_;
|
|
7076
|
-
}
|
|
7077
|
-
|
|
7078
|
-
// If we are going to src=, then we should set up DRM first. This will
|
|
7079
|
-
// support cases like FairPlay HLS on Safari.
|
|
7080
|
-
if (goingTo == this.srcEqualsNode_) {
|
|
7081
|
-
return this.srcEqualsDrmNode_;
|
|
7082
|
-
}
|
|
7083
|
-
|
|
7084
|
-
// We are missing a rule, the null will get caught by a common check in
|
|
7085
|
-
// the routing system.
|
|
7086
|
-
return null;
|
|
7087
|
-
}
|
|
7088
|
-
|
|
7089
|
-
/**
|
|
7090
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7091
|
-
* @param {shaka.routing.Payload} has
|
|
7092
|
-
* @param {shaka.routing.Payload} wants
|
|
7093
|
-
* @return {?shaka.routing.Node}
|
|
7094
|
-
* @private
|
|
7095
|
-
*/
|
|
7096
|
-
getNextAfterMediaSource_(goingTo, has, wants) {
|
|
7097
|
-
// We can only go to parse manifest or unload. If we want to go to load and
|
|
7098
|
-
// we have the right media element, we can go to parse manifest. If we
|
|
7099
|
-
// don't, no matter where we want to go, we must go through unload.
|
|
7100
|
-
if (goingTo == this.loadNode_ && has.mediaElement == wants.mediaElement) {
|
|
7101
|
-
return this.parserNode_;
|
|
7102
|
-
}
|
|
7103
|
-
|
|
7104
|
-
// Right now the unload node is responsible for tearing down all playback
|
|
7105
|
-
// components (including media source). So since we have created media
|
|
7106
|
-
// source, we need to unload since our dependencies are not compatible.
|
|
7107
|
-
//
|
|
7108
|
-
// TODO: We are structured this way to maintain a historic structure. Going
|
|
7109
|
-
// forward, there is no reason to restrict ourselves to this. Going
|
|
7110
|
-
// forward we should explore breaking apart |onUnload| and develop
|
|
7111
|
-
// more meaningful terminology around tearing down playback resources.
|
|
7112
|
-
return this.unloadNode_;
|
|
7113
|
-
}
|
|
7114
|
-
|
|
7115
|
-
/**
|
|
7116
|
-
* After unload there are only two options, attached or detached. This choice
|
|
7117
|
-
* is based on whether or not we have a media element. If we have a media
|
|
7118
|
-
* element, then we go to attach. If we don't have a media element, we go to
|
|
7119
|
-
* detach.
|
|
7120
|
-
*
|
|
7121
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7122
|
-
* @param {shaka.routing.Payload} has
|
|
7123
|
-
* @param {shaka.routing.Payload} wants
|
|
7124
|
-
* @return {?shaka.routing.Node}
|
|
7125
|
-
* @private
|
|
7126
|
-
*/
|
|
7127
|
-
getNextAfterUnload_(goingTo, has, wants) {
|
|
7128
|
-
// If we don't want a media element, detach.
|
|
7129
|
-
// If we have the wrong media element, detach.
|
|
7130
|
-
// Otherwise it means we want to attach to a media element and it is safe to
|
|
7131
|
-
// do so.
|
|
7132
|
-
return !wants.mediaElement || has.mediaElement != wants.mediaElement ?
|
|
7133
|
-
this.detachNode_ :
|
|
7134
|
-
this.attachNode_;
|
|
7135
|
-
}
|
|
7136
|
-
|
|
7137
|
-
/**
|
|
7138
|
-
* A general method used to handle routing when we can either than one step
|
|
7139
|
-
* toward our destination (while all our dependencies match) or go to a node
|
|
7140
|
-
* that will reset us so we can try again.
|
|
7141
|
-
*
|
|
7142
|
-
* @param {!shaka.routing.Node} destinationNode
|
|
7143
|
-
* What |goingTo| must be for us to step toward |nextNode|. Otherwise we
|
|
7144
|
-
* will go to |resetNode|.
|
|
7145
|
-
* @param {!shaka.routing.Node} nextNode
|
|
7146
|
-
* The node we will go to next if |goingTo == destinationNode| and all
|
|
7147
|
-
* dependencies match.
|
|
7148
|
-
* @param {!shaka.routing.Node} resetNode
|
|
7149
|
-
* The node we will go to next if |goingTo != destinationNode| or any
|
|
7150
|
-
* dependency does not match.
|
|
7151
|
-
* @param {!shaka.routing.Node} goingTo
|
|
7152
|
-
* The node that the walker is trying to go to.
|
|
7153
|
-
* @param {shaka.routing.Payload} has
|
|
7154
|
-
* The payload that the walker currently has.
|
|
7155
|
-
* @param {shaka.routing.Payload} wants
|
|
7156
|
-
* The payload that the walker wants to have when iy gets to |goingTo|.
|
|
7157
|
-
* @return {shaka.routing.Node}
|
|
7158
|
-
* @private
|
|
7159
|
-
*/
|
|
7160
|
-
getNextMatchingAllDependencies_(destinationNode, nextNode, resetNode, goingTo,
|
|
7161
|
-
has, wants) {
|
|
7162
|
-
if (goingTo == destinationNode &&
|
|
7163
|
-
has.mediaElement == wants.mediaElement &&
|
|
7164
|
-
has.uri == wants.uri &&
|
|
7165
|
-
has.mimeType == wants.mimeType) {
|
|
7166
|
-
return nextNode;
|
|
7167
|
-
}
|
|
7168
|
-
|
|
7169
|
-
return resetNode;
|
|
7170
|
-
}
|
|
7171
|
-
|
|
7172
|
-
/**
|
|
7173
|
-
* @return {shaka.routing.Payload}
|
|
7174
|
-
* @private
|
|
7175
|
-
*/
|
|
7176
|
-
static createEmptyPayload_() {
|
|
7177
|
-
return {
|
|
7178
|
-
mediaElement: null,
|
|
7179
|
-
mimeType: null,
|
|
7180
|
-
startTime: null,
|
|
7181
|
-
startTimeOfLoad: NaN,
|
|
7182
|
-
uri: null,
|
|
7183
|
-
keepAdManager: true,
|
|
7184
|
-
};
|
|
7185
|
-
}
|
|
7186
|
-
|
|
7187
|
-
/**
|
|
7188
|
-
* Using a promise, wrap the listeners returned by |Walker.startNewRoute|.
|
|
7189
|
-
* This will work for most usages in |Player| but should not be used for
|
|
7190
|
-
* special cases.
|
|
7191
|
-
*
|
|
7192
|
-
* This will connect |onCancel|, |onEnd|, |onError|, and |onSkip| with
|
|
7193
|
-
* |resolve| and |reject| but will leave |onStart| unset.
|
|
7194
|
-
*
|
|
7195
|
-
* @param {shaka.routing.Walker.Listeners} listeners
|
|
7196
|
-
* @return {!Promise}
|
|
7197
|
-
* @private
|
|
7198
|
-
*/
|
|
7199
|
-
wrapWalkerListenersWithPromise_(listeners) {
|
|
7200
|
-
return new Promise((resolve, reject) => {
|
|
7201
|
-
listeners.onCancel = () => reject(this.createAbortLoadError_());
|
|
7202
|
-
listeners.onEnd = () => resolve();
|
|
7203
|
-
listeners.onError = (e) => reject(e);
|
|
7204
|
-
listeners.onSkip = () => reject(this.createAbortLoadError_());
|
|
7205
|
-
});
|
|
7206
|
-
}
|
|
7207
6743
|
};
|
|
7208
6744
|
|
|
7209
6745
|
/**
|
|
@@ -7245,7 +6781,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
7245
6781
|
* @export
|
|
7246
6782
|
*/
|
|
7247
6783
|
// eslint-disable-next-line no-useless-concat
|
|
7248
|
-
shaka.Player.version = 'v4.
|
|
6784
|
+
shaka.Player.version = 'v4.6.1' + '-uncompiled'; // x-release-please-version
|
|
7249
6785
|
|
|
7250
6786
|
// Initialize the deprecation system using the version string we just set
|
|
7251
6787
|
// on the player.
|
|
@@ -7272,47 +6808,6 @@ shaka.Player.supportPlugins_ = {};
|
|
|
7272
6808
|
shaka.Player.adManagerFactory_ = null;
|
|
7273
6809
|
|
|
7274
6810
|
|
|
7275
|
-
/**
|
|
7276
|
-
* @const {!Object.<string, string>}
|
|
7277
|
-
* @private
|
|
7278
|
-
*/
|
|
7279
|
-
shaka.Player.SRC_EQUAL_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
7280
|
-
'mp4': 'video/mp4',
|
|
7281
|
-
'm4v': 'video/mp4',
|
|
7282
|
-
'm4a': 'audio/mp4',
|
|
7283
|
-
'webm': 'video/webm',
|
|
7284
|
-
'weba': 'audio/webm',
|
|
7285
|
-
'mkv': 'video/webm', // Chromium browsers supports it.
|
|
7286
|
-
'ts': 'video/mp2t',
|
|
7287
|
-
'ogv': 'video/ogg',
|
|
7288
|
-
'ogg': 'audio/ogg',
|
|
7289
|
-
'mpg': 'video/mpeg',
|
|
7290
|
-
'mpeg': 'video/mpeg',
|
|
7291
|
-
'm3u8': 'application/x-mpegurl',
|
|
7292
|
-
'mpd': 'application/dash+xml',
|
|
7293
|
-
'mp3': 'audio/mpeg',
|
|
7294
|
-
'aac': 'audio/aac',
|
|
7295
|
-
'flac': 'audio/flac',
|
|
7296
|
-
'wav': 'audio/wav',
|
|
7297
|
-
};
|
|
7298
|
-
|
|
7299
|
-
|
|
7300
|
-
/**
|
|
7301
|
-
* @const {!Object.<string, string>}
|
|
7302
|
-
* @private
|
|
7303
|
-
*/
|
|
7304
|
-
shaka.Player.TEXT_EXTENSIONS_TO_MIME_TYPES_ = {
|
|
7305
|
-
'sbv': 'text/x-subviewer',
|
|
7306
|
-
'srt': 'text/srt',
|
|
7307
|
-
'vtt': 'text/vtt',
|
|
7308
|
-
'webvtt': 'text/vtt',
|
|
7309
|
-
'ttml': 'application/ttml+xml',
|
|
7310
|
-
'lrc': 'application/x-subtitle-lrc',
|
|
7311
|
-
'ssa': 'text/x-ssa',
|
|
7312
|
-
'ass': 'text/x-ssa',
|
|
7313
|
-
};
|
|
7314
|
-
|
|
7315
|
-
|
|
7316
6811
|
/**
|
|
7317
6812
|
* @const {string}
|
|
7318
6813
|
*/
|