shaka-player 4.4.3 → 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 (198) hide show
  1. package/.github/workflows/release-please.yaml +2 -27
  2. package/CHANGELOG.md +64 -45
  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 +22 -5
  8. package/demo/common/assets.js +132 -49
  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 +51 -123
  16. package/demo/search.js +22 -34
  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 +152 -178
  22. package/dist/demo.compiled.debug.map +4 -4
  23. package/dist/demo.compiled.js +152 -178
  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 +22 -43
  28. package/dist/receiver.compiled.debug.map +4 -4
  29. package/dist/receiver.compiled.js +22 -43
  30. package/dist/receiver.compiled.map +4 -4
  31. package/dist/shaka-player.compiled.d.ts +507 -365
  32. package/dist/shaka-player.compiled.debug.d.ts +507 -365
  33. package/dist/shaka-player.compiled.debug.externs.js +205 -51
  34. package/dist/shaka-player.compiled.debug.js +541 -450
  35. package/dist/shaka-player.compiled.debug.map +4 -4
  36. package/dist/shaka-player.compiled.externs.js +205 -51
  37. package/dist/shaka-player.compiled.js +1120 -1066
  38. package/dist/shaka-player.compiled.map +4 -4
  39. package/dist/shaka-player.ui.d.ts +562 -410
  40. package/dist/shaka-player.ui.debug.d.ts +562 -410
  41. package/dist/shaka-player.ui.debug.externs.js +226 -51
  42. package/dist/shaka-player.ui.debug.js +606 -506
  43. package/dist/shaka-player.ui.debug.map +4 -4
  44. package/dist/shaka-player.ui.externs.js +226 -51
  45. package/dist/shaka-player.ui.js +1266 -1207
  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/ui-customization.md +4 -0
  52. package/externs/css_style_declaration.js +19 -0
  53. package/externs/lcevc.js +37 -13
  54. package/externs/media_remote.js +68 -0
  55. package/externs/mediatailor.js +193 -0
  56. package/externs/pictureinpicture.js +11 -0
  57. package/externs/shaka/ads.js +27 -0
  58. package/externs/shaka/manifest.js +10 -5
  59. package/externs/shaka/manifest_parser.js +7 -1
  60. package/externs/shaka/offline.js +4 -0
  61. package/externs/shaka/player.js +12 -1
  62. package/karma.conf.js +7 -3
  63. package/lib/abr/simple_abr_manager.js +43 -3
  64. package/lib/ads/ad_manager.js +104 -50
  65. package/lib/ads/media_tailor_ad.js +297 -0
  66. package/lib/ads/media_tailor_ad_manager.js +828 -0
  67. package/lib/cea/ts_cea_parser.js +15 -2
  68. package/lib/config/codec_switching_strategy.js +24 -0
  69. package/lib/dash/content_protection.js +120 -2
  70. package/lib/dash/dash_parser.js +75 -12
  71. package/lib/dash/mpd_utils.js +10 -6
  72. package/lib/dash/segment_base.js +13 -5
  73. package/lib/dash/segment_list.js +13 -5
  74. package/lib/dash/segment_template.js +43 -85
  75. package/lib/dependencies/all.js +8 -7
  76. package/lib/hls/hls_parser.js +44 -16
  77. package/lib/lcevc/lcevc_dec.js +220 -0
  78. package/lib/media/adaptation_set.js +22 -11
  79. package/lib/media/adaptation_set_criteria.js +25 -7
  80. package/lib/media/media_source_engine.js +215 -27
  81. package/lib/media/segment_index.js +1 -1
  82. package/lib/media/segment_reference.js +7 -7
  83. package/lib/media/streaming_engine.js +33 -18
  84. package/lib/mss/mss_parser.js +1 -0
  85. package/lib/net/networking_engine.js +1 -0
  86. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  87. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  88. package/lib/offline/manifest_converter.js +1 -0
  89. package/lib/offline/storage.js +3 -0
  90. package/lib/player.js +142 -72
  91. package/lib/routing/payload.js +5 -1
  92. package/lib/text/cue.js +15 -0
  93. package/lib/text/ttml_text_parser.js +17 -0
  94. package/lib/text/ui_text_displayer.js +4 -0
  95. package/lib/text/vtt_text_parser.js +33 -20
  96. package/lib/transmuxer/h265.js +862 -0
  97. package/lib/transmuxer/mpeg_ts_transmuxer.js +3 -2
  98. package/lib/transmuxer/ts_transmuxer.js +160 -15
  99. package/lib/util/error.js +28 -9
  100. package/lib/util/exp_golomb.js +30 -2
  101. package/lib/util/fake_event.js +3 -0
  102. package/lib/util/mp4_generator.js +4 -4
  103. package/lib/util/periods.js +2 -0
  104. package/lib/util/platform.js +40 -0
  105. package/lib/util/player_configuration.js +8 -0
  106. package/lib/util/state_history.js +6 -2
  107. package/lib/util/stream_utils.js +133 -217
  108. package/lib/util/ts_parser.js +226 -40
  109. package/maintained-branches.md +3 -2
  110. package/package.json +4 -6
  111. package/roadmap.md +15 -5
  112. package/shaka-player.uncompiled.js +2 -1
  113. package/support.html +0 -1
  114. package/test/cast/cast_receiver_integration.js +47 -0
  115. package/test/codec_switching/codec_switching_integration.js +181 -0
  116. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  117. package/test/dash/dash_parser_integration.js +73 -0
  118. package/test/dash/dash_parser_live_unit.js +2 -0
  119. package/test/dash/dash_parser_manifest_unit.js +42 -0
  120. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  121. package/test/dash/dash_parser_segment_list_unit.js +2 -0
  122. package/test/dash/dash_parser_segment_template_unit.js +2 -0
  123. package/test/demo/demo_unit.js +0 -16
  124. package/test/hls/hls_live_unit.js +2 -0
  125. package/test/hls/hls_parser_unit.js +8 -6
  126. package/test/media/adaptation_set_criteria_unit.js +118 -0
  127. package/test/media/adaptation_set_unit.js +1 -0
  128. package/test/media/media_source_engine_unit.js +165 -1
  129. package/test/media/segment_index_unit.js +4 -4
  130. package/test/media/segment_prefetch_unit.js +1 -1
  131. package/test/media/streaming_engine_unit.js +17 -17
  132. package/test/mss/mss_parser_unit.js +2 -0
  133. package/test/offline/manifest_convert_unit.js +5 -0
  134. package/test/offline/storage_integration.js +1 -4
  135. package/test/player_unit.js +102 -131
  136. package/test/routing/walker_unit.js +1 -0
  137. package/test/test/assets/dash-aes-128/Fragments(video=0,format=mpd-time-csf) +0 -0
  138. package/test/test/assets/dash-aes-128/Fragments(video=20000000,format=mpd-time-csf) +0 -0
  139. package/test/test/assets/dash-aes-128/Fragments(video=40000000,format=mpd-time-csf) +0 -0
  140. package/test/test/assets/dash-aes-128/Fragments(video=60000000,format=mpd-time-csf) +0 -0
  141. package/test/test/assets/dash-aes-128/Fragments(video=i,format=mpd-time-csf) +0 -0
  142. package/test/test/assets/dash-aes-128/dash.mpd +18 -0
  143. package/test/test/assets/dash-aes-128/license +1 -0
  144. package/test/test/assets/dash-multi-codec/audio_en_2c_128k_aac.mp4 +0 -0
  145. package/test/test/assets/dash-multi-codec/audio_es_2c_64k_opus.webm +0 -0
  146. package/test/test/assets/dash-multi-codec/dash.mpd +32 -0
  147. package/test/test/assets/dash-multi-codec/video_144p_108k_h264.mp4 +0 -0
  148. package/test/test/assets/hls-ts-h265/0.ts +0 -0
  149. package/test/test/assets/hls-ts-h265/hevc.m3u8 +6 -0
  150. package/test/test/assets/hls-ts-h265/index.m3u8 +7 -0
  151. package/test/test/assets/hls-ts-muxed-aac-h265/0.ts +0 -0
  152. package/test/test/assets/hls-ts-muxed-aac-h265/1.ts +0 -0
  153. package/test/test/assets/hls-ts-muxed-aac-h265/media.m3u8 +9 -0
  154. package/test/test/assets/hls-ts-muxed-ac3-h264/media.m3u8 +24 -0
  155. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00000.ts +0 -0
  156. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00001.ts +0 -0
  157. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00002.ts +0 -0
  158. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00003.ts +0 -0
  159. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00004.ts +0 -0
  160. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00005.ts +0 -0
  161. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0000.ts +0 -0
  162. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0001.ts +0 -0
  163. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0002.ts +0 -0
  164. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0003.ts +0 -0
  165. package/test/test/assets/hls-ts-muxed-ec3-h264/prog_index.m3u8 +14 -0
  166. package/test/test/assets/hls-ts-muxed-mp3-h264/index.m3u8 +23 -0
  167. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-1-v1-a1.ts +0 -0
  168. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-2-v1-a1.ts +0 -0
  169. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-3-v1-a1.ts +0 -0
  170. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-4-v1-a1.ts +0 -0
  171. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-5-v1-a1.ts +0 -0
  172. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-6-v1-a1.ts +0 -0
  173. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-7-v1-a1.ts +0 -0
  174. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-8-v1-a1.ts +0 -0
  175. package/test/test/util/dash_parser_util.js +4 -0
  176. package/test/test/util/fake_ad_manager.js +20 -0
  177. package/test/test/util/fake_media_source_engine.js +8 -2
  178. package/test/test/util/manifest_generator.js +7 -3
  179. package/test/test/util/mss_parser_util.js +4 -0
  180. package/test/test/util/offline_utils.js +1 -0
  181. package/test/test/util/streaming_engine_util.js +7 -4
  182. package/test/text/ttml_text_parser_unit.js +52 -0
  183. package/test/text/vtt_text_parser_unit.js +103 -31
  184. package/test/transmuxer/transmuxer_integration.js +117 -0
  185. package/test/ui/ui_unit.js +3 -10
  186. package/test/util/stream_utils_unit.js +208 -74
  187. package/ui/controls.js +125 -0
  188. package/ui/externs/ui.js +8 -1
  189. package/ui/pip_button.js +22 -120
  190. package/ui/remote_button.js +201 -0
  191. package/ui/ui.js +5 -2
  192. package/demo/common/message_ids.js +0 -309
  193. package/demo/locales/en.json +0 -288
  194. package/demo/locales/source.json +0 -1146
  195. package/externs/mux.js +0 -69
  196. package/lib/lcevc/lcevc_dil.js +0 -152
  197. package/lib/transmuxer/muxjs_transmuxer.js +0 -242
  198. package/test/transmuxer/muxjs_transmuxer_integration.js +0 -177
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