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.
Files changed (202) hide show
  1. package/.github/workflows/release-please.yaml +2 -27
  2. package/CHANGELOG.md +67 -25
  3. package/README.md +16 -10
  4. package/demo/asset_card.js +18 -21
  5. package/demo/cast_receiver/index.html +0 -1
  6. package/demo/close_button.js +0 -3
  7. package/demo/common/asset.js +27 -5
  8. package/demo/common/assets.js +150 -52
  9. package/demo/config.js +192 -253
  10. package/demo/custom.js +39 -98
  11. package/demo/front.js +7 -10
  12. package/demo/index.html +19 -22
  13. package/demo/input.js +6 -11
  14. package/demo/input_container.js +8 -9
  15. package/demo/main.js +52 -124
  16. package/demo/search.js +23 -35
  17. package/demo/service_worker.js +0 -3
  18. package/demo/tooltip.js +3 -5
  19. package/demo/visualizer.js +3 -5
  20. package/demo/visualizer_button.js +1 -20
  21. package/dist/demo.compiled.debug.js +187 -211
  22. package/dist/demo.compiled.debug.map +4 -4
  23. package/dist/demo.compiled.js +187 -211
  24. package/dist/demo.compiled.map +4 -4
  25. package/dist/deps.js +27 -24
  26. package/dist/locales.js +31 -31
  27. package/dist/receiver.compiled.debug.js +42 -61
  28. package/dist/receiver.compiled.debug.map +4 -4
  29. package/dist/receiver.compiled.js +42 -61
  30. package/dist/receiver.compiled.map +4 -4
  31. package/dist/shaka-player.compiled.d.ts +728 -365
  32. package/dist/shaka-player.compiled.debug.d.ts +728 -365
  33. package/dist/shaka-player.compiled.debug.externs.js +475 -51
  34. package/dist/shaka-player.compiled.debug.js +576 -480
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +475 -51
  37. package/dist/shaka-player.compiled.js +1117 -1056
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +783 -410
  40. package/dist/shaka-player.ui.debug.d.ts +783 -410
  41. package/dist/shaka-player.ui.debug.externs.js +496 -51
  42. package/dist/shaka-player.ui.debug.js +647 -542
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +496 -51
  45. package/dist/shaka-player.ui.js +1260 -1194
  46. package/dist/shaka-player.ui.map +4 -4
  47. package/docs/design/lcevc-integration.md +14 -14
  48. package/docs/tutorials/faq.md +27 -55
  49. package/docs/tutorials/index.json +2 -1
  50. package/docs/tutorials/lcevc.md +118 -0
  51. package/docs/tutorials/offline.md +1 -1
  52. package/docs/tutorials/ui-customization.md +4 -0
  53. package/externs/css_style_declaration.js +19 -0
  54. package/externs/lcevc.js +37 -13
  55. package/externs/media_remote.js +68 -0
  56. package/externs/mediatailor.js +193 -0
  57. package/externs/pictureinpicture.js +11 -0
  58. package/externs/shaka/ads.js +27 -0
  59. package/externs/shaka/manifest.js +10 -5
  60. package/externs/shaka/manifest_parser.js +7 -1
  61. package/externs/shaka/offline.js +4 -0
  62. package/externs/shaka/player.js +12 -1
  63. package/karma.conf.js +7 -3
  64. package/lib/abr/simple_abr_manager.js +43 -3
  65. package/lib/ads/ad_manager.js +104 -50
  66. package/lib/ads/media_tailor_ad.js +297 -0
  67. package/lib/ads/media_tailor_ad_manager.js +828 -0
  68. package/lib/cea/ts_cea_parser.js +15 -2
  69. package/lib/config/codec_switching_strategy.js +24 -0
  70. package/lib/dash/content_protection.js +120 -2
  71. package/lib/dash/dash_parser.js +79 -13
  72. package/lib/dash/mpd_utils.js +10 -6
  73. package/lib/dash/segment_base.js +13 -5
  74. package/lib/dash/segment_list.js +13 -5
  75. package/lib/dash/segment_template.js +43 -85
  76. package/lib/dependencies/all.js +8 -7
  77. package/lib/hls/hls_parser.js +101 -37
  78. package/lib/lcevc/lcevc_dec.js +220 -0
  79. package/lib/media/adaptation_set.js +22 -11
  80. package/lib/media/adaptation_set_criteria.js +25 -7
  81. package/lib/media/media_source_engine.js +215 -27
  82. package/lib/media/segment_index.js +5 -5
  83. package/lib/media/segment_reference.js +10 -5
  84. package/lib/media/streaming_engine.js +44 -39
  85. package/lib/mss/mss_parser.js +1 -0
  86. package/lib/net/networking_engine.js +1 -0
  87. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  88. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  89. package/lib/offline/manifest_converter.js +1 -0
  90. package/lib/offline/storage.js +3 -0
  91. package/lib/player.js +142 -72
  92. package/lib/routing/payload.js +5 -1
  93. package/lib/text/cue.js +62 -47
  94. package/lib/text/ttml_text_parser.js +17 -0
  95. package/lib/text/ui_text_displayer.js +4 -0
  96. package/lib/text/vtt_text_parser.js +35 -22
  97. package/lib/transmuxer/h265.js +862 -0
  98. package/lib/transmuxer/mpeg_ts_transmuxer.js +3 -2
  99. package/lib/transmuxer/ts_transmuxer.js +160 -15
  100. package/lib/util/cmcd_manager.js +6 -1
  101. package/lib/util/error.js +28 -9
  102. package/lib/util/exp_golomb.js +30 -2
  103. package/lib/util/fake_event.js +3 -0
  104. package/lib/util/manifest_parser_utils.js +10 -4
  105. package/lib/util/mp4_generator.js +4 -4
  106. package/lib/util/periods.js +2 -0
  107. package/lib/util/platform.js +54 -0
  108. package/lib/util/player_configuration.js +8 -0
  109. package/lib/util/state_history.js +6 -2
  110. package/lib/util/stream_utils.js +133 -217
  111. package/lib/util/ts_parser.js +229 -43
  112. package/maintained-branches.md +3 -2
  113. package/package.json +4 -6
  114. package/roadmap.md +15 -5
  115. package/shaka-player.uncompiled.js +2 -1
  116. package/support.html +0 -1
  117. package/test/cast/cast_receiver_integration.js +47 -0
  118. package/test/codec_switching/codec_switching_integration.js +181 -0
  119. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  120. package/test/dash/dash_parser_integration.js +73 -0
  121. package/test/dash/dash_parser_live_unit.js +2 -0
  122. package/test/dash/dash_parser_manifest_unit.js +47 -0
  123. package/test/dash/dash_parser_segment_base_unit.js +7 -0
  124. package/test/dash/dash_parser_segment_list_unit.js +23 -17
  125. package/test/dash/dash_parser_segment_template_unit.js +49 -0
  126. package/test/demo/demo_unit.js +0 -16
  127. package/test/hls/hls_live_unit.js +35 -0
  128. package/test/hls/hls_parser_unit.js +8 -61
  129. package/test/media/adaptation_set_criteria_unit.js +118 -0
  130. package/test/media/adaptation_set_unit.js +1 -0
  131. package/test/media/media_source_engine_unit.js +165 -1
  132. package/test/media/segment_index_unit.js +4 -4
  133. package/test/media/segment_prefetch_unit.js +1 -1
  134. package/test/media/streaming_engine_unit.js +17 -17
  135. package/test/mss/mss_parser_unit.js +2 -0
  136. package/test/offline/manifest_convert_unit.js +5 -0
  137. package/test/offline/storage_integration.js +1 -4
  138. package/test/player_unit.js +102 -131
  139. package/test/routing/walker_unit.js +1 -0
  140. package/test/test/assets/dash-aes-128/Fragments(video=0,format=mpd-time-csf) +0 -0
  141. package/test/test/assets/dash-aes-128/Fragments(video=20000000,format=mpd-time-csf) +0 -0
  142. package/test/test/assets/dash-aes-128/Fragments(video=40000000,format=mpd-time-csf) +0 -0
  143. package/test/test/assets/dash-aes-128/Fragments(video=60000000,format=mpd-time-csf) +0 -0
  144. package/test/test/assets/dash-aes-128/Fragments(video=i,format=mpd-time-csf) +0 -0
  145. package/test/test/assets/dash-aes-128/dash.mpd +18 -0
  146. package/test/test/assets/dash-aes-128/license +1 -0
  147. package/test/test/assets/dash-multi-codec/audio_en_2c_128k_aac.mp4 +0 -0
  148. package/test/test/assets/dash-multi-codec/audio_es_2c_64k_opus.webm +0 -0
  149. package/test/test/assets/dash-multi-codec/dash.mpd +32 -0
  150. package/test/test/assets/dash-multi-codec/video_144p_108k_h264.mp4 +0 -0
  151. package/test/test/assets/hls-ts-h265/0.ts +0 -0
  152. package/test/test/assets/hls-ts-h265/hevc.m3u8 +6 -0
  153. package/test/test/assets/hls-ts-h265/index.m3u8 +7 -0
  154. package/test/test/assets/hls-ts-muxed-aac-h265/0.ts +0 -0
  155. package/test/test/assets/hls-ts-muxed-aac-h265/1.ts +0 -0
  156. package/test/test/assets/hls-ts-muxed-aac-h265/media.m3u8 +9 -0
  157. package/test/test/assets/hls-ts-muxed-ac3-h264/media.m3u8 +24 -0
  158. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00000.ts +0 -0
  159. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00001.ts +0 -0
  160. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00002.ts +0 -0
  161. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00003.ts +0 -0
  162. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00004.ts +0 -0
  163. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00005.ts +0 -0
  164. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0000.ts +0 -0
  165. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0001.ts +0 -0
  166. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0002.ts +0 -0
  167. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0003.ts +0 -0
  168. package/test/test/assets/hls-ts-muxed-ec3-h264/prog_index.m3u8 +14 -0
  169. package/test/test/assets/hls-ts-muxed-mp3-h264/index.m3u8 +23 -0
  170. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-1-v1-a1.ts +0 -0
  171. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-2-v1-a1.ts +0 -0
  172. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-3-v1-a1.ts +0 -0
  173. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-4-v1-a1.ts +0 -0
  174. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-5-v1-a1.ts +0 -0
  175. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-6-v1-a1.ts +0 -0
  176. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-7-v1-a1.ts +0 -0
  177. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-8-v1-a1.ts +0 -0
  178. package/test/test/util/dash_parser_util.js +20 -7
  179. package/test/test/util/fake_ad_manager.js +20 -0
  180. package/test/test/util/fake_media_source_engine.js +8 -2
  181. package/test/test/util/manifest_generator.js +7 -3
  182. package/test/test/util/mss_parser_util.js +4 -0
  183. package/test/test/util/offline_utils.js +1 -0
  184. package/test/test/util/streaming_engine_util.js +7 -4
  185. package/test/text/ttml_text_parser_unit.js +52 -0
  186. package/test/text/vtt_text_parser_unit.js +105 -48
  187. package/test/transmuxer/transmuxer_integration.js +121 -4
  188. package/test/ui/ui_unit.js +3 -10
  189. package/test/util/stream_utils_unit.js +208 -74
  190. package/test/util/ts_parser_unit.js +1 -1
  191. package/ui/controls.js +125 -0
  192. package/ui/externs/ui.js +8 -1
  193. package/ui/pip_button.js +22 -120
  194. package/ui/remote_button.js +201 -0
  195. package/ui/ui.js +5 -2
  196. package/demo/common/message_ids.js +0 -309
  197. package/demo/locales/en.json +0 -287
  198. package/demo/locales/source.json +0 -1142
  199. package/externs/mux.js +0 -69
  200. package/lib/lcevc/lcevc_dil.js +0 -152
  201. package/lib/transmuxer/muxjs_transmuxer.js +0 -242
  202. 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.isPipAllowed_()) {
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.onPipClick_();
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
- * @return {!Promise}
124
- * @private
125
- */
126
- async onPipClick_() {
127
- try {
128
- if ('documentPictureInPicture' in window) {
129
- await this.toggleDocumentPictureInPicture_();
130
- return;
131
- }
132
- if (!document.pictureInPictureElement) {
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 ariaLabel = document.pictureInPictureElement ?
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 = document.pictureInPictureElement ?
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.isPipAllowed_()) {
176
+ if (this.controls.isPiPAllowed()) {
275
177
  shaka.ui.Utils.setDisplay(this.pipButton_, false);
276
178
  }
277
179
  } else {
278
- if (this.isPipAllowed_()) {
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.isPipAllowed_()) {
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 (document.pictureInPictureElement) {
298
- await document.exitPictureInPicture();
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
- // Check AirPlay support
251
- if (window.WebKitPlaybackTargetAvailabilityEvent) {
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