shaka-player 4.4.3 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (307) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/workflows/release-please.yaml +2 -27
  3. package/AUTHORS +2 -0
  4. package/CHANGELOG.md +156 -39
  5. package/CODE_OF_CONDUCT.md +1 -1
  6. package/CONTRIBUTORS +2 -0
  7. package/README.md +54 -42
  8. package/demo/asset_card.js +18 -21
  9. package/demo/cast_receiver/index.html +0 -1
  10. package/demo/close_button.js +0 -3
  11. package/demo/common/asset.js +33 -22
  12. package/demo/common/assets.js +249 -60
  13. package/demo/config.js +219 -253
  14. package/demo/custom.js +88 -133
  15. package/demo/front.js +7 -10
  16. package/demo/index.html +19 -22
  17. package/demo/input.js +6 -11
  18. package/demo/input_container.js +8 -9
  19. package/demo/main.js +62 -126
  20. package/demo/search.js +29 -34
  21. package/demo/service_worker.js +0 -3
  22. package/demo/tooltip.js +3 -5
  23. package/demo/visualizer.js +3 -5
  24. package/demo/visualizer_button.js +1 -20
  25. package/dist/controls.css +1 -1
  26. package/dist/controls.css.map +1 -1
  27. package/dist/demo.compiled.debug.js +211 -228
  28. package/dist/demo.compiled.debug.map +4 -4
  29. package/dist/demo.compiled.js +211 -228
  30. package/dist/demo.compiled.map +4 -4
  31. package/dist/deps.js +42 -37
  32. package/dist/locales.js +34 -30
  33. package/dist/receiver.compiled.debug.js +64 -77
  34. package/dist/receiver.compiled.debug.map +4 -4
  35. package/dist/receiver.compiled.js +64 -77
  36. package/dist/receiver.compiled.map +4 -4
  37. package/dist/shaka-player.compiled.d.ts +343 -151
  38. package/dist/shaka-player.compiled.debug.d.ts +343 -151
  39. package/dist/shaka-player.compiled.debug.externs.js +356 -117
  40. package/dist/shaka-player.compiled.debug.js +952 -811
  41. package/dist/shaka-player.compiled.debug.map +4 -4
  42. package/dist/shaka-player.compiled.externs.js +356 -117
  43. package/dist/shaka-player.compiled.js +1165 -1078
  44. package/dist/shaka-player.compiled.map +4 -4
  45. package/dist/shaka-player.ui.d.ts +359 -153
  46. package/dist/shaka-player.ui.debug.d.ts +359 -153
  47. package/dist/shaka-player.ui.debug.externs.js +382 -117
  48. package/dist/shaka-player.ui.debug.js +1084 -918
  49. package/dist/shaka-player.ui.debug.map +4 -4
  50. package/dist/shaka-player.ui.externs.js +382 -117
  51. package/dist/shaka-player.ui.js +1377 -1274
  52. package/dist/shaka-player.ui.map +4 -4
  53. package/docs/design/lcevc-integration.md +14 -14
  54. package/docs/tutorials/ad_monetization.md +54 -0
  55. package/docs/tutorials/basic-usage.md +2 -1
  56. package/docs/tutorials/errors.md +2 -1
  57. package/docs/tutorials/faq.md +27 -55
  58. package/docs/tutorials/index.json +2 -1
  59. package/docs/tutorials/lcevc.md +118 -0
  60. package/docs/tutorials/offline.md +10 -8
  61. package/docs/tutorials/plugins.md +5 -0
  62. package/docs/tutorials/ui-customization.md +17 -0
  63. package/docs/tutorials/ui.md +2 -1
  64. package/docs/tutorials/upgrade.md +3 -0
  65. package/externs/css_style_declaration.js +19 -0
  66. package/externs/lcevc.js +37 -13
  67. package/externs/managedmediasource.js +24 -0
  68. package/externs/media_remote.js +68 -0
  69. package/externs/mediatailor.js +193 -0
  70. package/externs/pictureinpicture.js +11 -0
  71. package/externs/shaka/abr_manager.js +6 -2
  72. package/externs/shaka/ads.js +27 -0
  73. package/externs/shaka/cea.js +7 -0
  74. package/externs/shaka/codecs.js +5 -3
  75. package/externs/shaka/manifest.js +31 -10
  76. package/externs/shaka/manifest_parser.js +24 -1
  77. package/externs/shaka/net.js +2 -2
  78. package/externs/shaka/offline.js +13 -2
  79. package/externs/shaka/player.js +65 -8
  80. package/externs/shaka/text.js +3 -1
  81. package/externs/shaka/transmuxer.js +1 -1
  82. package/karma.conf.js +7 -3
  83. package/lib/abr/simple_abr_manager.js +66 -9
  84. package/lib/ads/ad_manager.js +104 -50
  85. package/lib/ads/media_tailor_ad.js +297 -0
  86. package/lib/ads/media_tailor_ad_manager.js +839 -0
  87. package/lib/cast/cast_receiver.js +1 -1
  88. package/lib/cea/cea_decoder.js +28 -0
  89. package/lib/cea/dummy_caption_decoder.js +5 -0
  90. package/lib/cea/ts_cea_parser.js +15 -2
  91. package/lib/config/codec_switching_strategy.js +24 -0
  92. package/lib/dash/content_protection.js +120 -2
  93. package/lib/dash/dash_parser.js +322 -93
  94. package/lib/dash/mpd_utils.js +62 -37
  95. package/lib/dash/segment_base.js +17 -10
  96. package/lib/dash/segment_list.js +18 -9
  97. package/lib/dash/segment_template.js +122 -107
  98. package/lib/dependencies/all.js +8 -7
  99. package/lib/hls/hls_classes.js +28 -43
  100. package/lib/hls/hls_parser.js +798 -658
  101. package/lib/hls/hls_utils.js +64 -5
  102. package/lib/hls/manifest_text_parser.js +7 -11
  103. package/lib/lcevc/lcevc_dec.js +220 -0
  104. package/lib/media/adaptation_set.js +32 -14
  105. package/lib/media/adaptation_set_criteria.js +86 -16
  106. package/lib/media/closed_caption_parser.js +13 -0
  107. package/lib/media/content_workarounds.js +6 -0
  108. package/lib/media/drm_engine.js +8 -13
  109. package/lib/media/manifest_parser.js +8 -113
  110. package/lib/media/media_source_capabilities.js +10 -3
  111. package/lib/media/media_source_engine.js +286 -44
  112. package/lib/media/play_rate_controller.js +8 -38
  113. package/lib/media/presentation_timeline.js +7 -1
  114. package/lib/media/quality_observer.js +1 -1
  115. package/lib/media/segment_index.js +6 -6
  116. package/lib/media/segment_prefetch.js +92 -24
  117. package/lib/media/segment_reference.js +73 -7
  118. package/lib/media/segment_utils.js +343 -0
  119. package/lib/media/stall_detector.js +0 -3
  120. package/lib/media/streaming_engine.js +73 -38
  121. package/lib/mss/mss_parser.js +19 -2
  122. package/lib/net/networking_engine.js +67 -12
  123. package/lib/net/networking_utils.js +95 -0
  124. package/lib/offline/indexeddb/v1_storage_cell.js +3 -0
  125. package/lib/offline/indexeddb/v2_storage_cell.js +3 -0
  126. package/lib/offline/manifest_converter.js +3 -0
  127. package/lib/offline/offline_manifest_parser.js +10 -0
  128. package/lib/offline/storage.js +14 -7
  129. package/lib/player.js +1098 -1537
  130. package/lib/polyfill/mediasource.js +22 -1
  131. package/lib/text/cue.js +15 -0
  132. package/lib/text/mp4_ttml_parser.js +2 -2
  133. package/lib/text/srt_text_parser.js +2 -2
  134. package/lib/text/stub_text_displayer.js +51 -0
  135. package/lib/text/text_engine.js +3 -2
  136. package/lib/text/ttml_text_parser.js +45 -5
  137. package/lib/text/ui_text_displayer.js +8 -0
  138. package/lib/text/vtt_text_parser.js +40 -22
  139. package/lib/transmuxer/aac_transmuxer.js +1 -1
  140. package/lib/transmuxer/ac3_transmuxer.js +1 -1
  141. package/lib/transmuxer/ec3_transmuxer.js +1 -1
  142. package/lib/transmuxer/h264.js +5 -4
  143. package/lib/transmuxer/h265.js +863 -0
  144. package/lib/transmuxer/mp3_transmuxer.js +1 -1
  145. package/lib/transmuxer/mpeg_ts_transmuxer.js +4 -3
  146. package/lib/transmuxer/mss_transmuxer.js +1 -1
  147. package/lib/transmuxer/ts_transmuxer.js +187 -22
  148. package/lib/util/buffer_utils.js +26 -4
  149. package/lib/util/content_steering_manager.js +358 -0
  150. package/lib/util/error.js +30 -15
  151. package/lib/util/exp_golomb.js +30 -2
  152. package/lib/util/fairplay_utils.js +1 -1
  153. package/lib/util/fake_event.js +8 -1
  154. package/lib/util/id3_utils.js +50 -1
  155. package/lib/util/manifest_parser_utils.js +12 -8
  156. package/lib/util/mp4_generator.js +6 -8
  157. package/lib/util/mutex.js +59 -0
  158. package/lib/util/object_utils.js +1 -1
  159. package/lib/util/periods.js +156 -179
  160. package/lib/util/platform.js +44 -3
  161. package/lib/util/player_configuration.js +43 -6
  162. package/lib/util/state_history.js +6 -2
  163. package/lib/util/stream_utils.js +206 -283
  164. package/lib/util/ts_parser.js +335 -98
  165. package/lib/util/uint8array_utils.js +5 -1
  166. package/lib/util/xml_utils.js +5 -0
  167. package/maintained-branches.md +3 -2
  168. package/package.json +4 -6
  169. package/roadmap.md +25 -5
  170. package/shaka-player.uncompiled.js +2 -2
  171. package/support.html +0 -1
  172. package/test/abr/simple_abr_manager_unit.js +33 -33
  173. package/test/ads/ad_manager_unit.js +3 -2
  174. package/test/cast/cast_receiver_integration.js +50 -4
  175. package/test/cast/cast_utils_unit.js +2 -0
  176. package/test/cea/cea_decoder_unit.js +7 -0
  177. package/test/codec_switching/codec_switching_integration.js +182 -0
  178. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  179. package/test/dash/dash_parser_integration.js +74 -0
  180. package/test/dash/dash_parser_live_unit.js +2 -19
  181. package/test/dash/dash_parser_manifest_unit.js +536 -2
  182. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  183. package/test/dash/dash_parser_segment_list_unit.js +2 -0
  184. package/test/dash/dash_parser_segment_template_unit.js +2 -0
  185. package/test/dash/mpd_utils_unit.js +61 -30
  186. package/test/demo/demo_unit.js +0 -16
  187. package/test/hls/hls_live_unit.js +14 -3
  188. package/test/hls/hls_parser_unit.js +385 -33
  189. package/test/hls/manifest_text_parser_unit.js +32 -99
  190. package/test/media/adaptation_set_criteria_unit.js +284 -15
  191. package/test/media/adaptation_set_unit.js +2 -0
  192. package/test/media/content_workarounds_unit.js +1 -1
  193. package/test/media/drm_engine_unit.js +3 -3
  194. package/test/media/media_source_engine_unit.js +166 -2
  195. package/test/media/play_rate_controller_unit.js +0 -48
  196. package/test/media/segment_index_unit.js +5 -4
  197. package/test/media/segment_prefetch_unit.js +1 -1
  198. package/test/media/streaming_engine_unit.js +17 -17
  199. package/test/mss/mss_parser_unit.js +2 -0
  200. package/test/mss/mss_player_integration.js +3 -2
  201. package/test/net/networking_engine_unit.js +3 -3
  202. package/test/{media/manifest_parser_unit.js → net/networking_utils_unit.js} +2 -2
  203. package/test/offline/manifest_convert_unit.js +14 -0
  204. package/test/offline/offline_integration.js +2 -1
  205. package/test/offline/storage_integration.js +22 -13
  206. package/test/player_external.js +4 -2
  207. package/test/player_integration.js +15 -4
  208. package/test/player_load_graph_integration.js +47 -151
  209. package/test/player_src_equals_external.js +1 -0
  210. package/test/player_src_equals_integration.js +71 -4
  211. package/test/player_unit.js +182 -158
  212. package/test/test/assets/dash-aes-128/Fragments(video=0,format=mpd-time-csf) +0 -0
  213. package/test/test/assets/dash-aes-128/Fragments(video=20000000,format=mpd-time-csf) +0 -0
  214. package/test/test/assets/dash-aes-128/Fragments(video=40000000,format=mpd-time-csf) +0 -0
  215. package/test/test/assets/dash-aes-128/Fragments(video=60000000,format=mpd-time-csf) +0 -0
  216. package/test/test/assets/dash-aes-128/Fragments(video=i,format=mpd-time-csf) +0 -0
  217. package/test/test/assets/dash-aes-128/dash.mpd +18 -0
  218. package/test/test/assets/dash-aes-128/license +1 -0
  219. package/test/test/assets/dash-multi-codec/audio_en_2c_128k_aac.mp4 +0 -0
  220. package/test/test/assets/dash-multi-codec/audio_es_2c_64k_opus.webm +0 -0
  221. package/test/test/assets/dash-multi-codec/dash.mpd +32 -0
  222. package/test/test/assets/dash-multi-codec/video_144p_108k_h264.mp4 +0 -0
  223. package/test/test/assets/hls-ts-h265/0.ts +0 -0
  224. package/test/test/assets/hls-ts-h265/hevc.m3u8 +6 -0
  225. package/test/test/assets/hls-ts-h265/index.m3u8 +7 -0
  226. package/test/test/assets/hls-ts-muxed-aac-h265/0.ts +0 -0
  227. package/test/test/assets/hls-ts-muxed-aac-h265/1.ts +0 -0
  228. package/test/test/assets/hls-ts-muxed-aac-h265/media.m3u8 +9 -0
  229. package/test/test/assets/hls-ts-muxed-ac3-h264/media.m3u8 +24 -0
  230. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00000.ts +0 -0
  231. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00001.ts +0 -0
  232. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00002.ts +0 -0
  233. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00003.ts +0 -0
  234. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00004.ts +0 -0
  235. package/test/test/assets/hls-ts-muxed-ac3-h264/s_00005.ts +0 -0
  236. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0000.ts +0 -0
  237. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0001.ts +0 -0
  238. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0002.ts +0 -0
  239. package/test/test/assets/hls-ts-muxed-ec3-h264/ChID_voices_1280x720p_25fps_h264_6ch_256kbps_ddp_sub_0003.ts +0 -0
  240. package/test/test/assets/hls-ts-muxed-ec3-h264/prog_index.m3u8 +14 -0
  241. package/test/test/assets/hls-ts-muxed-mp3-h264/index.m3u8 +23 -0
  242. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-1-v1-a1.ts +0 -0
  243. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-2-v1-a1.ts +0 -0
  244. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-3-v1-a1.ts +0 -0
  245. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-4-v1-a1.ts +0 -0
  246. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-5-v1-a1.ts +0 -0
  247. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-6-v1-a1.ts +0 -0
  248. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-7-v1-a1.ts +0 -0
  249. package/test/test/assets/hls-ts-muxed-mp3-h264/seg-8-v1-a1.ts +0 -0
  250. package/test/test/util/dash_parser_util.js +4 -0
  251. package/test/test/util/fake_ad_manager.js +20 -0
  252. package/test/test/util/fake_media_source_engine.js +13 -2
  253. package/test/test/util/fake_networking_engine.js +9 -1
  254. package/test/test/util/fake_segment_prefetch.js +3 -0
  255. package/test/test/util/manifest_generator.js +11 -3
  256. package/test/test/util/manifest_parser_util.js +14 -2
  257. package/test/test/util/mss_parser_util.js +4 -0
  258. package/test/test/util/offline_utils.js +2 -0
  259. package/test/test/util/simple_fakes.js +2 -2
  260. package/test/test/util/streaming_engine_util.js +10 -4
  261. package/test/test/util/test_scheme.js +6 -0
  262. package/test/test/util/ui_utils.js +4 -3
  263. package/test/text/mp4_ttml_parser_unit.js +4 -4
  264. package/test/text/srt_text_parser_unit.js +1 -1
  265. package/test/text/text_displayer_layout_unit.js +1 -1
  266. package/test/text/text_engine_unit.js +6 -0
  267. package/test/text/ttml_text_parser_unit.js +85 -2
  268. package/test/text/vtt_text_parser_unit.js +103 -31
  269. package/test/transmuxer/transmuxer_engine_integration.js +9 -0
  270. package/test/transmuxer/transmuxer_integration.js +150 -3
  271. package/test/ui/ad_ui_unit.js +2 -2
  272. package/test/ui/ui_customization_unit.js +17 -16
  273. package/test/ui/ui_integration.js +30 -20
  274. package/test/ui/ui_unit.js +28 -29
  275. package/test/util/buffer_utils_unit.js +1 -1
  276. package/test/util/content_steering_manager_unit.js +427 -0
  277. package/test/util/id3_utils_unit.js +52 -0
  278. package/test/util/stream_utils_unit.js +207 -169
  279. package/test/util/ts_parser_unit.js +3 -3
  280. package/third_party/closure-uri/uri.js +13 -1
  281. package/ui/audio_language_selection.js +9 -2
  282. package/ui/controls.js +125 -0
  283. package/ui/externs/ui.js +24 -2
  284. package/ui/language_utils.js +43 -8
  285. package/ui/less/range_elements.less +33 -0
  286. package/ui/locales/en.json +1 -0
  287. package/ui/locales/es-419.json +1 -0
  288. package/ui/locales/es.json +1 -0
  289. package/ui/locales/source.json +4 -0
  290. package/ui/localization.js +10 -0
  291. package/ui/pip_button.js +22 -120
  292. package/ui/remote_button.js +203 -0
  293. package/ui/resolution_selection.js +47 -10
  294. package/ui/seek_bar.js +296 -3
  295. package/ui/text_selection.js +2 -1
  296. package/ui/ui.js +11 -4
  297. package/demo/common/message_ids.js +0 -309
  298. package/demo/locales/en.json +0 -288
  299. package/demo/locales/source.json +0 -1146
  300. package/externs/mux.js +0 -69
  301. package/lib/lcevc/lcevc_dil.js +0 -152
  302. package/lib/routing/node.js +0 -23
  303. package/lib/routing/payload.js +0 -43
  304. package/lib/routing/walker.js +0 -587
  305. package/lib/transmuxer/muxjs_transmuxer.js +0 -242
  306. package/test/routing/walker_unit.js +0 -504
  307. 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
@@ -21,7 +21,9 @@ shaka.extern = {};
21
21
  * base: string,
22
22
  * buffered: string,
23
23
  * played: string,
24
- * adBreaks: string
24
+ * adBreaks: string,
25
+ * chapterMarks: string,
26
+ * chapterLabels: string
25
27
  * }}
26
28
  *
27
29
  * @property {string} base
@@ -36,6 +38,13 @@ shaka.extern = {};
36
38
  * @property {string} adBreaks
37
39
  * The CSS background color applied to the portion of the seek bar showing
38
40
  * when the ad breaks are scheduled to occur on the timeline.
41
+ * @property {string} chapterMarks
42
+ * The CSS border color applied to sections of the seek bar showing
43
+ * when the chapters start and end on the timeline.
44
+ * Defaults to dark grey rgb(27, 27, 27).
45
+ * @property {string} chapterLabels
46
+ * The CSS text color applied to the chapter labels that appear above the
47
+ * seek bar on hover. Defaults to white rgb(255, 255, 255).
39
48
  * @exportDoc
40
49
  */
41
50
  shaka.extern.UISeekBarColors;
@@ -87,7 +96,9 @@ shaka.extern.UIVolumeBarColors;
87
96
  * enableTooltips: boolean,
88
97
  * keyboardSeekDistance: number,
89
98
  * keyboardLargeSeekDistance: number,
90
- * fullScreenElement: HTMLElement
99
+ * fullScreenElement: HTMLElement,
100
+ * preferDocumentPictureInPicture: boolean,
101
+ * showAudioChannelCountVariants: boolean
91
102
  * }}
92
103
  *
93
104
  * @property {!Array.<string>} controlPanelElements
@@ -193,6 +204,17 @@ shaka.extern.UIVolumeBarColors;
193
204
  * @property {HTMLElement} fullScreenElement
194
205
  * DOM element on which fullscreen will be done.
195
206
  * Defaults to Shaka Player Container.
207
+ * @property {boolean} preferDocumentPictureInPicture
208
+ * Indicates whether the Document Picture in Picture API is preferred or the
209
+ * Video Element Picture in Picture API is preferred.
210
+ * Changing this property in mid-playback may produce undesired behavior if
211
+ * you are already in PiP.
212
+ * Defaults to true.
213
+ * @property {boolean} showAudioChannelCountVariants
214
+ * Indicates whether the combination of language and channel count should be
215
+ * displayed or if, on the contrary, only the language should be displayed
216
+ * regardless of the channel count.
217
+ * Defaults to true.
196
218
  * @exportDoc
197
219
  */
198
220
  shaka.extern.UIConfiguration;
@@ -26,9 +26,13 @@ shaka.ui.LanguageUtils = class {
26
26
  * @param {!HTMLElement} currentSelectionElement
27
27
  * @param {shaka.ui.Localization} localization
28
28
  * @param {shaka.ui.Overlay.TrackLabelFormat} trackLabelFormat
29
+ * @param {boolean} showAudioChannelCountVariants
29
30
  */
30
31
  static updateTracks(tracks, langMenu, onTrackSelected, updateChosen,
31
- currentSelectionElement, localization, trackLabelFormat) {
32
+ currentSelectionElement, localization, trackLabelFormat,
33
+ showAudioChannelCountVariants) {
34
+ const LocIds = shaka.ui.Locales.Ids;
35
+
32
36
  // TODO: Do the benefits of having this common code in a method still
33
37
  // outweigh the complexity of the parameter list?
34
38
  const selectedTrack = tracks.find((track) => {
@@ -55,8 +59,27 @@ shaka.ui.LanguageUtils = class {
55
59
  }
56
60
  };
57
61
 
58
- const getCombination = (language, rolesString) => {
59
- return language + ': ' + rolesString;
62
+ const getCombination = (language, rolesString, label, channelsCount) => {
63
+ const keys = [
64
+ language,
65
+ rolesString,
66
+ ];
67
+ if (showAudioChannelCountVariants && channelsCount != null) {
68
+ keys.push(channelsCount);
69
+ }
70
+ if (label &&
71
+ trackLabelFormat == shaka.ui.Overlay.TrackLabelFormat.LABEL) {
72
+ keys.push(label);
73
+ }
74
+ return keys.join(': ');
75
+ };
76
+
77
+ const getChannelsCountName = (channelsCount) => {
78
+ let name = '';
79
+ if (channelsCount >= 5) {
80
+ name = ' ' + localization.resolve(LocIds.SURROUND);
81
+ }
82
+ return name;
60
83
  };
61
84
 
62
85
  /** @type {!Map.<string, !Set.<string>>} */
@@ -72,15 +95,18 @@ shaka.ui.LanguageUtils = class {
72
95
  /** @type {!Set.<string>} */
73
96
  const combinationsMade = new Set();
74
97
  const selectedCombination = selectedTrack ? getCombination(
75
- selectedTrack.language, getRolesString(selectedTrack)) : '';
98
+ selectedTrack.language, getRolesString(selectedTrack),
99
+ selectedTrack.label, selectedTrack.channelsCount) : '';
76
100
 
77
101
  for (const track of tracks) {
78
102
  const language = track.language;
79
103
  const forced = track.forced;
80
- const LocIds = shaka.ui.Locales.Ids;
81
104
  const forcedString = localization.resolve(LocIds.SUBTITLE_FORCED);
82
105
  const rolesString = getRolesString(track);
83
- const combinationName = getCombination(language, rolesString);
106
+ const label = track.label;
107
+ const channelsCount = track.channelsCount;
108
+ const combinationName =
109
+ getCombination(language, rolesString, label, channelsCount);
84
110
  if (combinationsMade.has(combinationName)) {
85
111
  continue;
86
112
  }
@@ -98,11 +124,17 @@ shaka.ui.LanguageUtils = class {
98
124
  shaka.ui.LanguageUtils.getLanguageName(language, localization);
99
125
  switch (trackLabelFormat) {
100
126
  case shaka.ui.Overlay.TrackLabelFormat.LANGUAGE:
127
+ if (showAudioChannelCountVariants) {
128
+ span.textContent += getChannelsCountName(channelsCount);
129
+ }
101
130
  if (forced) {
102
131
  span.textContent += ' (' + forcedString + ')';
103
132
  }
104
133
  break;
105
134
  case shaka.ui.Overlay.TrackLabelFormat.ROLE:
135
+ if (showAudioChannelCountVariants) {
136
+ span.textContent += getChannelsCountName(channelsCount);
137
+ }
106
138
  if (!rolesString) {
107
139
  // Fallback behavior. This probably shouldn't happen.
108
140
  shaka.log.alwaysWarn('Track #' + track.id + ' does not have a ' +
@@ -116,6 +148,9 @@ shaka.ui.LanguageUtils = class {
116
148
  }
117
149
  break;
118
150
  case shaka.ui.Overlay.TrackLabelFormat.LANGUAGE_ROLE:
151
+ if (showAudioChannelCountVariants) {
152
+ span.textContent += getChannelsCountName(channelsCount);
153
+ }
119
154
  if (rolesString) {
120
155
  span.textContent += ': ' + rolesString;
121
156
  }
@@ -124,8 +159,8 @@ shaka.ui.LanguageUtils = class {
124
159
  }
125
160
  break;
126
161
  case shaka.ui.Overlay.TrackLabelFormat.LABEL:
127
- if (track.label) {
128
- span.textContent = track.label;
162
+ if (label) {
163
+ span.textContent = label;
129
164
  } else {
130
165
  // Fallback behavior. This probably shouldn't happen.
131
166
  shaka.log.alwaysWarn('Track #' + track.id + ' does not have a ' +
@@ -154,3 +154,36 @@
154
154
  .shaka-ad-markers {
155
155
  .overlay-child();
156
156
  }
157
+
158
+ #shaka-player-ui-chapters-container {
159
+ width: 100%;
160
+ height: 100%;
161
+ display: flex;
162
+ flex-direction: row;
163
+ z-index: 0;
164
+ }
165
+
166
+ .shaka-chapter {
167
+ height: 100%;
168
+ position: relative;
169
+ }
170
+
171
+ .shaka-chapter-label {
172
+ position: relative;
173
+ top: -2.5em;
174
+ width: 100%;
175
+ text-align: center;
176
+ text-shadow: 1px 1px gray;
177
+ line-height: normal;
178
+ }
179
+
180
+ .shaka-chapter > div:first-child {
181
+ width: 100%;
182
+ height: 100%;
183
+ border-right: 1px solid;
184
+ position: absolute;
185
+ }
186
+
187
+ .shaka-chapter:last-child > div:first-child {
188
+ border-right: none;
189
+ }
@@ -36,6 +36,7 @@
36
36
  "SKIP_TO_LIVE": "Skip ahead to live",
37
37
  "STATISTICS": "Statistics",
38
38
  "SUBTITLE_FORCED": "Forced",
39
+ "SURROUND": "Surround",
39
40
  "UNDETERMINED_LANGUAGE": "Undetermined",
40
41
  "UNMUTE": "Unmute",
41
42
  "UNRECOGNIZED_LANGUAGE": "Unrecognized",
@@ -32,6 +32,7 @@
32
32
  "SKIP_AD": "Omitir anuncio",
33
33
  "SKIP_TO_LIVE": "Adelantar hasta la transmisión en vivo",
34
34
  "SUBTITLE_FORCED": "Forzado",
35
+ "SURROUND": "Envolvente",
35
36
  "UNDETERMINED_LANGUAGE": "Sin especificar",
36
37
  "UNMUTE": "Dejar de silenciar",
37
38
  "UNRECOGNIZED_LANGUAGE": "No reconocida",
@@ -32,6 +32,7 @@
32
32
  "SKIP_AD": "Saltar anuncio",
33
33
  "SKIP_TO_LIVE": "Ir al vídeo en directo",
34
34
  "SUBTITLE_FORCED": "Forzado",
35
+ "SURROUND": "Envolvente",
35
36
  "UNDETERMINED_LANGUAGE": "Sin especificar",
36
37
  "UNMUTE": "Activar sonido",
37
38
  "UNRECOGNIZED_LANGUAGE": "No reconocida",
@@ -151,6 +151,10 @@
151
151
  "description": "Label used to identify a subtitle track that is forced to be shown.",
152
152
  "message": "Forced"
153
153
  },
154
+ "SURROUND": {
155
+ "description": "Label used to identify a audio track that has surround audio.",
156
+ "message": "Surround"
157
+ },
154
158
  "UNDETERMINED_LANGUAGE": {
155
159
  "description": "Label for a button used to select an audio track whose language is undetermined or unknown.",
156
160
  "message": "Undetermined"
@@ -230,6 +230,16 @@ shaka.ui.Localization = class extends shaka.util.FakeEventTarget {
230
230
  return '';
231
231
  }
232
232
 
233
+ /**
234
+ * The locales currently used. An empty set means "no preference".
235
+ *
236
+ * @return {!Set.<string>}
237
+ * @export
238
+ */
239
+ getCurrentLocales() {
240
+ return this.currentLocales_;
241
+ }
242
+
233
243
  /**
234
244
  * @private
235
245
  */
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);