shaka-player 4.4.2 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release-please.yaml +2 -27
- package/CHANGELOG.md +67 -25
- package/README.md +16 -10
- package/demo/asset_card.js +18 -21
- package/demo/cast_receiver/index.html +0 -1
- package/demo/close_button.js +0 -3
- package/demo/common/asset.js +27 -5
- package/demo/common/assets.js +150 -52
- package/demo/config.js +192 -253
- package/demo/custom.js +39 -98
- package/demo/front.js +7 -10
- package/demo/index.html +19 -22
- package/demo/input.js +6 -11
- package/demo/input_container.js +8 -9
- package/demo/main.js +52 -124
- package/demo/search.js +23 -35
- package/demo/service_worker.js +0 -3
- package/demo/tooltip.js +3 -5
- package/demo/visualizer.js +3 -5
- package/demo/visualizer_button.js +1 -20
- package/dist/demo.compiled.debug.js +187 -211
- package/dist/demo.compiled.debug.map +4 -4
- package/dist/demo.compiled.js +187 -211
- package/dist/demo.compiled.map +4 -4
- package/dist/deps.js +27 -24
- package/dist/locales.js +31 -31
- package/dist/receiver.compiled.debug.js +42 -61
- package/dist/receiver.compiled.debug.map +4 -4
- package/dist/receiver.compiled.js +42 -61
- package/dist/receiver.compiled.map +4 -4
- package/dist/shaka-player.compiled.d.ts +728 -365
- package/dist/shaka-player.compiled.debug.d.ts +728 -365
- package/dist/shaka-player.compiled.debug.externs.js +475 -51
- package/dist/shaka-player.compiled.debug.js +576 -480
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +475 -51
- package/dist/shaka-player.compiled.js +1117 -1056
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +783 -410
- package/dist/shaka-player.ui.debug.d.ts +783 -410
- package/dist/shaka-player.ui.debug.externs.js +496 -51
- package/dist/shaka-player.ui.debug.js +647 -542
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +496 -51
- package/dist/shaka-player.ui.js +1260 -1194
- package/dist/shaka-player.ui.map +4 -4
- package/docs/design/lcevc-integration.md +14 -14
- package/docs/tutorials/faq.md +27 -55
- package/docs/tutorials/index.json +2 -1
- package/docs/tutorials/lcevc.md +118 -0
- package/docs/tutorials/offline.md +1 -1
- package/docs/tutorials/ui-customization.md +4 -0
- package/externs/css_style_declaration.js +19 -0
- package/externs/lcevc.js +37 -13
- package/externs/media_remote.js +68 -0
- package/externs/mediatailor.js +193 -0
- package/externs/pictureinpicture.js +11 -0
- package/externs/shaka/ads.js +27 -0
- package/externs/shaka/manifest.js +10 -5
- package/externs/shaka/manifest_parser.js +7 -1
- package/externs/shaka/offline.js +4 -0
- package/externs/shaka/player.js +12 -1
- package/karma.conf.js +7 -3
- package/lib/abr/simple_abr_manager.js +43 -3
- package/lib/ads/ad_manager.js +104 -50
- package/lib/ads/media_tailor_ad.js +297 -0
- package/lib/ads/media_tailor_ad_manager.js +828 -0
- package/lib/cea/ts_cea_parser.js +15 -2
- package/lib/config/codec_switching_strategy.js +24 -0
- package/lib/dash/content_protection.js +120 -2
- package/lib/dash/dash_parser.js +79 -13
- package/lib/dash/mpd_utils.js +10 -6
- package/lib/dash/segment_base.js +13 -5
- package/lib/dash/segment_list.js +13 -5
- package/lib/dash/segment_template.js +43 -85
- package/lib/dependencies/all.js +8 -7
- package/lib/hls/hls_parser.js +101 -37
- package/lib/lcevc/lcevc_dec.js +220 -0
- package/lib/media/adaptation_set.js +22 -11
- package/lib/media/adaptation_set_criteria.js +25 -7
- package/lib/media/media_source_engine.js +215 -27
- package/lib/media/segment_index.js +5 -5
- package/lib/media/segment_reference.js +10 -5
- package/lib/media/streaming_engine.js +44 -39
- package/lib/mss/mss_parser.js +1 -0
- package/lib/net/networking_engine.js +1 -0
- package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
- package/lib/offline/manifest_converter.js +1 -0
- package/lib/offline/storage.js +3 -0
- package/lib/player.js +142 -72
- package/lib/routing/payload.js +5 -1
- package/lib/text/cue.js +62 -47
- package/lib/text/ttml_text_parser.js +17 -0
- package/lib/text/ui_text_displayer.js +4 -0
- package/lib/text/vtt_text_parser.js +35 -22
- package/lib/transmuxer/h265.js +862 -0
- package/lib/transmuxer/mpeg_ts_transmuxer.js +3 -2
- package/lib/transmuxer/ts_transmuxer.js +160 -15
- package/lib/util/cmcd_manager.js +6 -1
- package/lib/util/error.js +28 -9
- package/lib/util/exp_golomb.js +30 -2
- package/lib/util/fake_event.js +3 -0
- package/lib/util/manifest_parser_utils.js +10 -4
- package/lib/util/mp4_generator.js +4 -4
- package/lib/util/periods.js +2 -0
- package/lib/util/platform.js +54 -0
- package/lib/util/player_configuration.js +8 -0
- package/lib/util/state_history.js +6 -2
- package/lib/util/stream_utils.js +133 -217
- package/lib/util/ts_parser.js +229 -43
- package/maintained-branches.md +3 -2
- package/package.json +4 -6
- package/roadmap.md +15 -5
- package/shaka-player.uncompiled.js +2 -1
- package/support.html +0 -1
- package/test/cast/cast_receiver_integration.js +47 -0
- package/test/codec_switching/codec_switching_integration.js +181 -0
- package/test/dash/dash_parser_content_protection_unit.js +2 -0
- package/test/dash/dash_parser_integration.js +73 -0
- package/test/dash/dash_parser_live_unit.js +2 -0
- package/test/dash/dash_parser_manifest_unit.js +47 -0
- package/test/dash/dash_parser_segment_base_unit.js +7 -0
- package/test/dash/dash_parser_segment_list_unit.js +23 -17
- package/test/dash/dash_parser_segment_template_unit.js +49 -0
- package/test/demo/demo_unit.js +0 -16
- package/test/hls/hls_live_unit.js +35 -0
- package/test/hls/hls_parser_unit.js +8 -61
- package/test/media/adaptation_set_criteria_unit.js +118 -0
- package/test/media/adaptation_set_unit.js +1 -0
- package/test/media/media_source_engine_unit.js +165 -1
- package/test/media/segment_index_unit.js +4 -4
- package/test/media/segment_prefetch_unit.js +1 -1
- package/test/media/streaming_engine_unit.js +17 -17
- package/test/mss/mss_parser_unit.js +2 -0
- package/test/offline/manifest_convert_unit.js +5 -0
- package/test/offline/storage_integration.js +1 -4
- package/test/player_unit.js +102 -131
- package/test/routing/walker_unit.js +1 -0
- package/test/test/assets/dash-aes-128/Fragments(video=0,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=20000000,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=40000000,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=60000000,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/Fragments(video=i,format=mpd-time-csf) +0 -0
- package/test/test/assets/dash-aes-128/dash.mpd +18 -0
- package/test/test/assets/dash-aes-128/license +1 -0
- package/test/test/assets/dash-multi-codec/audio_en_2c_128k_aac.mp4 +0 -0
- package/test/test/assets/dash-multi-codec/audio_es_2c_64k_opus.webm +0 -0
- package/test/test/assets/dash-multi-codec/dash.mpd +32 -0
- package/test/test/assets/dash-multi-codec/video_144p_108k_h264.mp4 +0 -0
- package/test/test/assets/hls-ts-h265/0.ts +0 -0
- package/test/test/assets/hls-ts-h265/hevc.m3u8 +6 -0
- package/test/test/assets/hls-ts-h265/index.m3u8 +7 -0
- package/test/test/assets/hls-ts-muxed-aac-h265/0.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h265/1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-aac-h265/media.m3u8 +9 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/media.m3u8 +24 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00000.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00001.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00002.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00003.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00004.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ac3-h264/s_00005.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0000.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0001.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0002.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0003.ts +0 -0
- package/test/test/assets/hls-ts-muxed-ec3-h264/prog_index.m3u8 +14 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/index.m3u8 +23 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-1-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-2-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-3-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-4-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-5-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-6-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-7-v1-a1.ts +0 -0
- package/test/test/assets/hls-ts-muxed-mp3-h264/seg-8-v1-a1.ts +0 -0
- package/test/test/util/dash_parser_util.js +20 -7
- package/test/test/util/fake_ad_manager.js +20 -0
- package/test/test/util/fake_media_source_engine.js +8 -2
- package/test/test/util/manifest_generator.js +7 -3
- package/test/test/util/mss_parser_util.js +4 -0
- package/test/test/util/offline_utils.js +1 -0
- package/test/test/util/streaming_engine_util.js +7 -4
- package/test/text/ttml_text_parser_unit.js +52 -0
- package/test/text/vtt_text_parser_unit.js +105 -48
- package/test/transmuxer/transmuxer_integration.js +121 -4
- package/test/ui/ui_unit.js +3 -10
- package/test/util/stream_utils_unit.js +208 -74
- package/test/util/ts_parser_unit.js +1 -1
- package/ui/controls.js +125 -0
- package/ui/externs/ui.js +8 -1
- package/ui/pip_button.js +22 -120
- package/ui/remote_button.js +201 -0
- package/ui/ui.js +5 -2
- package/demo/common/message_ids.js +0 -309
- package/demo/locales/en.json +0 -287
- package/demo/locales/source.json +0 -1142
- package/externs/mux.js +0 -69
- package/lib/lcevc/lcevc_dil.js +0 -152
- package/lib/transmuxer/muxjs_transmuxer.js +0 -242
- package/test/transmuxer/muxjs_transmuxer_integration.js +0 -177
package/ui/controls.js
CHANGED
|
@@ -643,6 +643,131 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
|
|
|
643
643
|
}
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
/**
|
|
647
|
+
* @return {boolean}
|
|
648
|
+
* @export
|
|
649
|
+
*/
|
|
650
|
+
isPiPAllowed() {
|
|
651
|
+
if ('documentPictureInPicture' in window &&
|
|
652
|
+
this.config_.preferDocumentPictureInPicture) {
|
|
653
|
+
return true;
|
|
654
|
+
}
|
|
655
|
+
if (document.pictureInPictureEnabled) {
|
|
656
|
+
const video = /** @type {HTMLVideoElement} */(this.localVideo_);
|
|
657
|
+
return !video.disablePictureInPicture;
|
|
658
|
+
}
|
|
659
|
+
return false;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* @return {boolean}
|
|
664
|
+
* @export
|
|
665
|
+
*/
|
|
666
|
+
isPiPEnabled() {
|
|
667
|
+
if ('documentPictureInPicture' in window &&
|
|
668
|
+
this.config_.preferDocumentPictureInPicture) {
|
|
669
|
+
return !!window.documentPictureInPicture.window;
|
|
670
|
+
} else {
|
|
671
|
+
return !!document.pictureInPictureElement;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/** @export */
|
|
676
|
+
async togglePiP() {
|
|
677
|
+
try {
|
|
678
|
+
if ('documentPictureInPicture' in window &&
|
|
679
|
+
this.config_.preferDocumentPictureInPicture) {
|
|
680
|
+
await this.toggleDocumentPictureInPicture_();
|
|
681
|
+
} else if (!document.pictureInPictureElement) {
|
|
682
|
+
// If you were fullscreen, leave fullscreen first.
|
|
683
|
+
if (document.fullscreenElement) {
|
|
684
|
+
document.exitFullscreen();
|
|
685
|
+
}
|
|
686
|
+
const video = /** @type {HTMLVideoElement} */(this.localVideo_);
|
|
687
|
+
await video.requestPictureInPicture();
|
|
688
|
+
} else {
|
|
689
|
+
await document.exitPictureInPicture();
|
|
690
|
+
}
|
|
691
|
+
} catch (error) {
|
|
692
|
+
this.dispatchEvent(new shaka.util.FakeEvent(
|
|
693
|
+
'error', (new Map()).set('detail', error)));
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* The Document Picture-in-Picture API makes it possible to open an
|
|
699
|
+
* always-on-top window that can be populated with arbitrary HTML content.
|
|
700
|
+
* https://developer.chrome.com/docs/web-platform/document-picture-in-picture
|
|
701
|
+
* @private
|
|
702
|
+
*/
|
|
703
|
+
async toggleDocumentPictureInPicture_() {
|
|
704
|
+
// Close Picture-in-Picture window if any.
|
|
705
|
+
if (window.documentPictureInPicture.window) {
|
|
706
|
+
window.documentPictureInPicture.window.close();
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Open a Picture-in-Picture window.
|
|
711
|
+
const pipPlayer = this.videoContainer_;
|
|
712
|
+
const rectPipPlayer = pipPlayer.getBoundingClientRect();
|
|
713
|
+
const pipWindow = await window.documentPictureInPicture.requestWindow({
|
|
714
|
+
width: rectPipPlayer.width,
|
|
715
|
+
height: rectPipPlayer.height,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// Copy style sheets to the Picture-in-Picture window.
|
|
719
|
+
this.copyStyleSheetsToWindow_(pipWindow);
|
|
720
|
+
|
|
721
|
+
// Add placeholder for the player.
|
|
722
|
+
const parentPlayer = pipPlayer.parentNode || document.body;
|
|
723
|
+
const placeholder = this.videoContainer_.cloneNode(true);
|
|
724
|
+
placeholder.style.visibility = 'hidden';
|
|
725
|
+
placeholder.style.height = getComputedStyle(pipPlayer).height;
|
|
726
|
+
parentPlayer.appendChild(placeholder);
|
|
727
|
+
|
|
728
|
+
// Make sure player fits in the Picture-in-Picture window.
|
|
729
|
+
const styles = document.createElement('style');
|
|
730
|
+
styles.append(`[data-shaka-player-container] {
|
|
731
|
+
width: 100% !important; max-height: 100%}`);
|
|
732
|
+
pipWindow.document.head.append(styles);
|
|
733
|
+
|
|
734
|
+
// Move player to the Picture-in-Picture window.
|
|
735
|
+
pipWindow.document.body.append(pipPlayer);
|
|
736
|
+
|
|
737
|
+
// Listen for the PiP closing event to move the player back.
|
|
738
|
+
this.eventManager_.listenOnce(pipWindow, 'pagehide', () => {
|
|
739
|
+
placeholder.replaceWith(/** @type {!Node} */(pipPlayer));
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/** @private */
|
|
744
|
+
copyStyleSheetsToWindow_(win) {
|
|
745
|
+
const styleSheets = /** @type {!Iterable<*>} */(document.styleSheets);
|
|
746
|
+
const allCSS = [...styleSheets]
|
|
747
|
+
.map((sheet) => {
|
|
748
|
+
try {
|
|
749
|
+
return [...sheet.cssRules].map((rule) => rule.cssText).join('');
|
|
750
|
+
} catch (e) {
|
|
751
|
+
const link = /** @type {!HTMLLinkElement} */(
|
|
752
|
+
document.createElement('link'));
|
|
753
|
+
|
|
754
|
+
link.rel = 'stylesheet';
|
|
755
|
+
link.type = sheet.type;
|
|
756
|
+
link.media = sheet.media;
|
|
757
|
+
link.href = sheet.href;
|
|
758
|
+
win.document.head.appendChild(link);
|
|
759
|
+
}
|
|
760
|
+
return '';
|
|
761
|
+
})
|
|
762
|
+
.filter(Boolean)
|
|
763
|
+
.join('\n');
|
|
764
|
+
const style = document.createElement('style');
|
|
765
|
+
|
|
766
|
+
style.textContent = allCSS;
|
|
767
|
+
win.document.head.appendChild(style);
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
|
|
646
771
|
/** @export */
|
|
647
772
|
showAdUI() {
|
|
648
773
|
shaka.ui.Utils.setDisplay(this.adPanel_, true);
|
package/ui/externs/ui.js
CHANGED
|
@@ -87,7 +87,8 @@ shaka.extern.UIVolumeBarColors;
|
|
|
87
87
|
* enableTooltips: boolean,
|
|
88
88
|
* keyboardSeekDistance: number,
|
|
89
89
|
* keyboardLargeSeekDistance: number,
|
|
90
|
-
* fullScreenElement: HTMLElement
|
|
90
|
+
* fullScreenElement: HTMLElement,
|
|
91
|
+
* preferDocumentPictureInPicture: boolean
|
|
91
92
|
* }}
|
|
92
93
|
*
|
|
93
94
|
* @property {!Array.<string>} controlPanelElements
|
|
@@ -193,6 +194,12 @@ shaka.extern.UIVolumeBarColors;
|
|
|
193
194
|
* @property {HTMLElement} fullScreenElement
|
|
194
195
|
* DOM element on which fullscreen will be done.
|
|
195
196
|
* Defaults to Shaka Player Container.
|
|
197
|
+
* @property {boolean} preferDocumentPictureInPicture
|
|
198
|
+
* Indicates whether the Document Picture in Picture API is preferred or the
|
|
199
|
+
* Video Element Picture in Picture API is preferred.
|
|
200
|
+
* Changing this property in mid-playback may produce undesired behavior if
|
|
201
|
+
* you are already in PiP.
|
|
202
|
+
* Defaults to true.
|
|
196
203
|
* @exportDoc
|
|
197
204
|
*/
|
|
198
205
|
shaka.extern.UIConfiguration;
|
package/ui/pip_button.js
CHANGED
|
@@ -16,7 +16,6 @@ goog.require('shaka.ui.Localization');
|
|
|
16
16
|
goog.require('shaka.ui.OverflowMenu');
|
|
17
17
|
goog.require('shaka.ui.Utils');
|
|
18
18
|
goog.require('shaka.util.Dom');
|
|
19
|
-
goog.require('shaka.util.FakeEvent');
|
|
20
19
|
goog.requireType('shaka.ui.Controls');
|
|
21
20
|
|
|
22
21
|
|
|
@@ -73,7 +72,7 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
|
|
|
73
72
|
// Don't display the button if PiP is not supported or not allowed.
|
|
74
73
|
// TODO: Can this ever change? Is it worth creating the button if the below
|
|
75
74
|
// condition is true?
|
|
76
|
-
if (!this.
|
|
75
|
+
if (!this.controls.isPiPAllowed()) {
|
|
77
76
|
shaka.ui.Utils.setDisplay(this.pipButton_, false);
|
|
78
77
|
}
|
|
79
78
|
|
|
@@ -88,7 +87,7 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
|
|
|
88
87
|
});
|
|
89
88
|
|
|
90
89
|
this.eventManager.listen(this.pipButton_, 'click', () => {
|
|
91
|
-
this.
|
|
90
|
+
this.controls.togglePiP();
|
|
92
91
|
});
|
|
93
92
|
|
|
94
93
|
this.eventManager.listen(this.localVideo_, 'enterpictureinpicture', () => {
|
|
@@ -106,119 +105,21 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
|
|
|
106
105
|
this.eventManager.listen(this.player, 'trackschanged', () => {
|
|
107
106
|
this.onTracksChanged_();
|
|
108
107
|
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* @return {boolean}
|
|
114
|
-
* @private
|
|
115
|
-
*/
|
|
116
|
-
isPipAllowed_() {
|
|
117
|
-
return ('documentPictureInPicture' in window) ||
|
|
118
|
-
(document.pictureInPictureEnabled && !this.video.disablePictureInPicture);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
108
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// If you were fullscreen, leave fullscreen first.
|
|
134
|
-
if (document.fullscreenElement) {
|
|
135
|
-
document.exitFullscreen();
|
|
136
|
-
}
|
|
137
|
-
await this.video.requestPictureInPicture();
|
|
138
|
-
} else {
|
|
139
|
-
await document.exitPictureInPicture();
|
|
140
|
-
}
|
|
141
|
-
} catch (error) {
|
|
142
|
-
this.controls.dispatchEvent(new shaka.util.FakeEvent(
|
|
143
|
-
'error', (new Map()).set('detail', error)));
|
|
109
|
+
if ('documentPictureInPicture' in window) {
|
|
110
|
+
this.eventManager.listen(window.documentPictureInPicture, 'enter',
|
|
111
|
+
(e) => {
|
|
112
|
+
this.onEnterPictureInPicture_();
|
|
113
|
+
|
|
114
|
+
const event = /** @type {DocumentPictureInPictureEvent} */(e);
|
|
115
|
+
const pipWindow = event.window;
|
|
116
|
+
this.eventManager.listenOnce(pipWindow, 'pagehide', () => {
|
|
117
|
+
this.onLeavePictureInPicture_();
|
|
118
|
+
});
|
|
119
|
+
});
|
|
144
120
|
}
|
|
145
121
|
}
|
|
146
122
|
|
|
147
|
-
/**
|
|
148
|
-
* The Document Picture-in-Picture API makes it possible to open an
|
|
149
|
-
* always-on-top window that can be populated with arbitrary HTML content.
|
|
150
|
-
* https://developer.chrome.com/docs/web-platform/document-picture-in-picture
|
|
151
|
-
* @private
|
|
152
|
-
*/
|
|
153
|
-
async toggleDocumentPictureInPicture_() {
|
|
154
|
-
// Close Picture-in-Picture window if any.
|
|
155
|
-
if (window.documentPictureInPicture.window) {
|
|
156
|
-
window.documentPictureInPicture.window.close();
|
|
157
|
-
this.onLeavePictureInPicture_();
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Open a Picture-in-Picture window.
|
|
162
|
-
const pipPlayer = this.videoContainer_;
|
|
163
|
-
const rectPipPlayer = pipPlayer.getBoundingClientRect();
|
|
164
|
-
const pipWindow = await window.documentPictureInPicture.requestWindow({
|
|
165
|
-
width: rectPipPlayer.width,
|
|
166
|
-
height: rectPipPlayer.height,
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
// Copy style sheets to the Picture-in-Picture window.
|
|
170
|
-
this.copyStyleSheetsToWindow_(pipWindow);
|
|
171
|
-
|
|
172
|
-
// Add placeholder for the player.
|
|
173
|
-
const parentPlayer = pipPlayer.parentNode || document.body;
|
|
174
|
-
const placeholder = this.videoContainer_.cloneNode(true);
|
|
175
|
-
placeholder.style.visibility = 'hidden';
|
|
176
|
-
placeholder.style.height = getComputedStyle(pipPlayer).height;
|
|
177
|
-
parentPlayer.appendChild(placeholder);
|
|
178
|
-
|
|
179
|
-
// Make sure player fits in the Picture-in-Picture window.
|
|
180
|
-
const styles = document.createElement('style');
|
|
181
|
-
styles.append(`[data-shaka-player-container] {
|
|
182
|
-
width: 100% !important; max-height: 100%}`);
|
|
183
|
-
pipWindow.document.head.append(styles);
|
|
184
|
-
|
|
185
|
-
// Move player to the Picture-in-Picture window.
|
|
186
|
-
pipWindow.document.body.append(pipPlayer);
|
|
187
|
-
this.onEnterPictureInPicture_();
|
|
188
|
-
|
|
189
|
-
// Listen for the PiP closing event to move the player back.
|
|
190
|
-
this.eventManager.listenOnce(pipWindow, 'pagehide', () => {
|
|
191
|
-
placeholder.replaceWith(/** @type {!Node} */(pipPlayer));
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** @private */
|
|
196
|
-
copyStyleSheetsToWindow_(win) {
|
|
197
|
-
const styleSheets = /** @type {!Iterable<*>} */(document.styleSheets);
|
|
198
|
-
const allCSS = [...styleSheets]
|
|
199
|
-
.map((sheet) => {
|
|
200
|
-
try {
|
|
201
|
-
return [...sheet.cssRules].map((rule) => rule.cssText).join('');
|
|
202
|
-
} catch (e) {
|
|
203
|
-
const link = /** @type {!HTMLLinkElement} */(
|
|
204
|
-
document.createElement('link'));
|
|
205
|
-
|
|
206
|
-
link.rel = 'stylesheet';
|
|
207
|
-
link.type = sheet.type;
|
|
208
|
-
link.media = sheet.media;
|
|
209
|
-
link.href = sheet.href;
|
|
210
|
-
win.document.head.appendChild(link);
|
|
211
|
-
}
|
|
212
|
-
return '';
|
|
213
|
-
})
|
|
214
|
-
.filter(Boolean)
|
|
215
|
-
.join('\n');
|
|
216
|
-
const style = document.createElement('style');
|
|
217
|
-
|
|
218
|
-
style.textContent = allCSS;
|
|
219
|
-
win.document.head.appendChild(style);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
123
|
/** @private */
|
|
223
124
|
onEnterPictureInPicture_() {
|
|
224
125
|
const LocIds = shaka.ui.Locales.Ids;
|
|
@@ -250,13 +151,14 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
|
|
|
250
151
|
this.pipNameSpan_.textContent =
|
|
251
152
|
this.localization.resolve(LocIds.PICTURE_IN_PICTURE);
|
|
252
153
|
|
|
253
|
-
const
|
|
154
|
+
const enabled = this.controls.isPiPEnabled();
|
|
155
|
+
|
|
156
|
+
const ariaLabel = enabled ?
|
|
254
157
|
LocIds.EXIT_PICTURE_IN_PICTURE :
|
|
255
158
|
LocIds.ENTER_PICTURE_IN_PICTURE;
|
|
256
159
|
this.pipButton_.ariaLabel = this.localization.resolve(ariaLabel);
|
|
257
160
|
|
|
258
|
-
const currentPipState =
|
|
259
|
-
LocIds.ON : LocIds.OFF;
|
|
161
|
+
const currentPipState = enabled ? LocIds.ON : LocIds.OFF;
|
|
260
162
|
|
|
261
163
|
this.currentPipState_.textContent =
|
|
262
164
|
this.localization.resolve(currentPipState);
|
|
@@ -271,11 +173,11 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
|
|
|
271
173
|
|
|
272
174
|
if (isCasting) {
|
|
273
175
|
// Picture-in-picture is not applicable if we're casting
|
|
274
|
-
if (this.
|
|
176
|
+
if (this.controls.isPiPAllowed()) {
|
|
275
177
|
shaka.ui.Utils.setDisplay(this.pipButton_, false);
|
|
276
178
|
}
|
|
277
179
|
} else {
|
|
278
|
-
if (this.
|
|
180
|
+
if (this.controls.isPiPAllowed()) {
|
|
279
181
|
shaka.ui.Utils.setDisplay(this.pipButton_, true);
|
|
280
182
|
}
|
|
281
183
|
}
|
|
@@ -290,12 +192,12 @@ shaka.ui.PipButton = class extends shaka.ui.Element {
|
|
|
290
192
|
* @private
|
|
291
193
|
*/
|
|
292
194
|
async onTracksChanged_() {
|
|
293
|
-
if (!this.
|
|
195
|
+
if (!this.controls.isPiPAllowed()) {
|
|
294
196
|
shaka.ui.Utils.setDisplay(this.pipButton_, false);
|
|
295
197
|
} else if (this.player && this.player.isAudioOnly()) {
|
|
296
198
|
shaka.ui.Utils.setDisplay(this.pipButton_, false);
|
|
297
|
-
if (
|
|
298
|
-
await
|
|
199
|
+
if (this.controls.isPiPEnabled()) {
|
|
200
|
+
await this.controls.togglePiP();
|
|
299
201
|
}
|
|
300
202
|
} else {
|
|
301
203
|
shaka.ui.Utils.setDisplay(this.pipButton_, true);
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
goog.provide('shaka.ui.RemoteButton');
|
|
9
|
+
|
|
10
|
+
goog.require('shaka.Player');
|
|
11
|
+
goog.require('shaka.ui.Controls');
|
|
12
|
+
goog.require('shaka.ui.Element');
|
|
13
|
+
goog.require('shaka.ui.Enums');
|
|
14
|
+
goog.require('shaka.ui.Locales');
|
|
15
|
+
goog.require('shaka.ui.Localization');
|
|
16
|
+
goog.require('shaka.ui.OverflowMenu');
|
|
17
|
+
goog.require('shaka.ui.Utils');
|
|
18
|
+
goog.require('shaka.util.Dom');
|
|
19
|
+
goog.require('shaka.util.Platform');
|
|
20
|
+
goog.requireType('shaka.ui.Controls');
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @extends {shaka.ui.Element}
|
|
25
|
+
* @final
|
|
26
|
+
* @export
|
|
27
|
+
*/
|
|
28
|
+
shaka.ui.RemoteButton = class extends shaka.ui.Element {
|
|
29
|
+
/**
|
|
30
|
+
* @param {!HTMLElement} parent
|
|
31
|
+
* @param {!shaka.ui.Controls} controls
|
|
32
|
+
*/
|
|
33
|
+
constructor(parent, controls) {
|
|
34
|
+
super(parent, controls);
|
|
35
|
+
|
|
36
|
+
/** @private {!HTMLButtonElement} */
|
|
37
|
+
this.remoteButton_ = shaka.util.Dom.createButton();
|
|
38
|
+
this.remoteButton_.classList.add('shaka-remote-button');
|
|
39
|
+
this.remoteButton_.classList.add('shaka-tooltip');
|
|
40
|
+
this.remoteButton_.ariaPressed = 'false';
|
|
41
|
+
|
|
42
|
+
/** @private {!HTMLElement} */
|
|
43
|
+
this.remoteIcon_ = shaka.util.Dom.createHTMLElement('i');
|
|
44
|
+
this.remoteIcon_.classList.add('material-icons-round');
|
|
45
|
+
let icon = shaka.ui.Enums.MaterialDesignIcons.CAST;
|
|
46
|
+
const safariVersion = shaka.util.Platform.safariVersion();
|
|
47
|
+
if (safariVersion && safariVersion >= 13) {
|
|
48
|
+
icon = shaka.ui.Enums.MaterialDesignIcons.AIRPLAY;
|
|
49
|
+
}
|
|
50
|
+
this.remoteIcon_.textContent = icon;
|
|
51
|
+
this.remoteButton_.appendChild(this.remoteIcon_);
|
|
52
|
+
|
|
53
|
+
const label = shaka.util.Dom.createHTMLElement('label');
|
|
54
|
+
label.classList.add('shaka-overflow-button-label');
|
|
55
|
+
label.classList.add('shaka-overflow-menu-only');
|
|
56
|
+
this.remoteNameSpan_ = shaka.util.Dom.createHTMLElement('span');
|
|
57
|
+
label.appendChild(this.remoteNameSpan_);
|
|
58
|
+
|
|
59
|
+
this.remoteCurrentSelectionSpan_ =
|
|
60
|
+
shaka.util.Dom.createHTMLElement('span');
|
|
61
|
+
this.remoteCurrentSelectionSpan_.classList.add(
|
|
62
|
+
'shaka-current-selection-span');
|
|
63
|
+
label.appendChild(this.remoteCurrentSelectionSpan_);
|
|
64
|
+
this.remoteButton_.appendChild(label);
|
|
65
|
+
this.parent.appendChild(this.remoteButton_);
|
|
66
|
+
|
|
67
|
+
/** @private {number} */
|
|
68
|
+
this.callbackId_ = -1;
|
|
69
|
+
|
|
70
|
+
// Setup strings in the correct language
|
|
71
|
+
this.updateLocalizedStrings_();
|
|
72
|
+
|
|
73
|
+
shaka.ui.Utils.setDisplay(this.remoteButton_, false);
|
|
74
|
+
|
|
75
|
+
if (!this.video.remote || this.video.disableRemotePlayback) {
|
|
76
|
+
this.remoteButton_.classList.add('shaka-hidden');
|
|
77
|
+
} else {
|
|
78
|
+
this.eventManager.listen(
|
|
79
|
+
this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
|
|
80
|
+
this.updateLocalizedStrings_();
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.eventManager.listen(
|
|
84
|
+
this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
|
|
85
|
+
this.updateLocalizedStrings_();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.eventManager.listen(this.controls, 'caststatuschanged', () => {
|
|
89
|
+
this.updateRemoteState_();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.eventManager.listen(this.remoteButton_, 'click', () => {
|
|
93
|
+
this.video.remote.prompt();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
this.eventManager.listen(this.video.remote, 'connect', () => {
|
|
97
|
+
this.updateRemoteState_();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
this.eventManager.listen(this.video.remote, 'connecting', () => {
|
|
101
|
+
this.updateRemoteState_();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
this.eventManager.listen(this.video.remote, 'disconnect', () => {
|
|
105
|
+
this.updateRemoteState_();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.eventManager.listen(this.player, 'loaded', () => {
|
|
109
|
+
this.updateRemoteState_();
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
this.updateRemoteState_();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** @override */
|
|
117
|
+
release() {
|
|
118
|
+
if (this.video.remote && this.callbackId_ != -1) {
|
|
119
|
+
this.video.remote.cancelWatchAvailability(this.callbackId_);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
super.release();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
async updateRemoteState_() {
|
|
129
|
+
if (this.controls.getCastProxy().canCast() &&
|
|
130
|
+
this.controls.isCastAllowed()) {
|
|
131
|
+
shaka.ui.Utils.setDisplay(this.remoteButton_, false);
|
|
132
|
+
if (this.callbackId_ != -1) {
|
|
133
|
+
this.video.remote.cancelWatchAvailability(this.callbackId_);
|
|
134
|
+
this.callbackId_ = -1;
|
|
135
|
+
}
|
|
136
|
+
} else if (this.video.remote.state == 'disconnected') {
|
|
137
|
+
const handleAvailabilityChange = (availability) => {
|
|
138
|
+
if (this.player) {
|
|
139
|
+
const loadMode = this.player.getLoadMode();
|
|
140
|
+
const srcMode = loadMode == shaka.Player.LoadMode.SRC_EQUALS;
|
|
141
|
+
shaka.ui.Utils.setDisplay(
|
|
142
|
+
this.remoteButton_, srcMode && availability);
|
|
143
|
+
} else {
|
|
144
|
+
shaka.ui.Utils.setDisplay(this.remoteButton_, false);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
if (this.callbackId_ != -1) {
|
|
149
|
+
await this.video.remote.cancelWatchAvailability(this.callbackId_);
|
|
150
|
+
this.callbackId_ = -1;
|
|
151
|
+
}
|
|
152
|
+
} catch (e) {
|
|
153
|
+
// Ignore this error.
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const id = await this.video.remote.watchAvailability(
|
|
157
|
+
handleAvailabilityChange);
|
|
158
|
+
this.callbackId_ = id;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
handleAvailabilityChange(/* availability= */ true);
|
|
161
|
+
}
|
|
162
|
+
} else if (this.callbackId_ != -1) {
|
|
163
|
+
// If remote device is connecting or connected, we should stop
|
|
164
|
+
// watching remote device availability to save power.
|
|
165
|
+
await this.video.remote.cancelWatchAvailability(this.callbackId_);
|
|
166
|
+
this.callbackId_ = -1;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
updateLocalizedStrings_() {
|
|
174
|
+
const LocIds = shaka.ui.Locales.Ids;
|
|
175
|
+
let text = this.localization.resolve(LocIds.CAST);
|
|
176
|
+
const safariVersion = shaka.util.Platform.safariVersion();
|
|
177
|
+
if (safariVersion && safariVersion >= 13) {
|
|
178
|
+
text = this.localization.resolve(LocIds.AIRPLAY);
|
|
179
|
+
}
|
|
180
|
+
this.remoteButton_.ariaLabel = text;
|
|
181
|
+
this.remoteNameSpan_.textContent = text;
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @implements {shaka.extern.IUIElement.Factory}
|
|
188
|
+
* @final
|
|
189
|
+
*/
|
|
190
|
+
shaka.ui.RemoteButton.Factory = class {
|
|
191
|
+
/** @override */
|
|
192
|
+
create(rootElement, controls) {
|
|
193
|
+
return new shaka.ui.RemoteButton(rootElement, controls);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
shaka.ui.OverflowMenu.registerElement(
|
|
198
|
+
'remote', new shaka.ui.RemoteButton.Factory());
|
|
199
|
+
|
|
200
|
+
shaka.ui.Controls.registerElement(
|
|
201
|
+
'remote', new shaka.ui.RemoteButton.Factory());
|
package/ui/ui.js
CHANGED
|
@@ -245,10 +245,13 @@ shaka.ui.Overlay = class {
|
|
|
245
245
|
keyboardSeekDistance: 5,
|
|
246
246
|
keyboardLargeSeekDistance: 60,
|
|
247
247
|
fullScreenElement: this.videoContainer_,
|
|
248
|
+
preferDocumentPictureInPicture: true,
|
|
248
249
|
};
|
|
249
250
|
|
|
250
|
-
//
|
|
251
|
-
if (
|
|
251
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
252
|
+
if ('remote' in HTMLMediaElement.prototype) {
|
|
253
|
+
config.overflowMenuButtons.push('remote');
|
|
254
|
+
} else if (window.WebKitPlaybackTargetAvailabilityEvent) {
|
|
252
255
|
config.overflowMenuButtons.push('airplay');
|
|
253
256
|
}
|
|
254
257
|
|