shaka-player 3.1.4 → 3.3.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 (214) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +1 -1
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +1 -1
  3. package/.github/ISSUE_TEMPLATE/question.md +1 -1
  4. package/AUTHORS +6 -5
  5. package/CHANGELOG.md +363 -0
  6. package/CONTRIBUTORS +4 -2
  7. package/README.md +4 -4
  8. package/build/types/core +3 -0
  9. package/build/types/offline +1 -0
  10. package/build/types/polyfill +2 -0
  11. package/build/types/ui +2 -0
  12. package/demo/common/asset.js +9 -7
  13. package/demo/common/assets.js +28 -1
  14. package/demo/common/message_ids.js +15 -2
  15. package/demo/config.js +35 -2
  16. package/demo/index.html +1 -1
  17. package/demo/locales/en.json +14 -1
  18. package/demo/locales/source.json +56 -4
  19. package/demo/main.js +2 -1
  20. package/dist/controls.css +26 -2
  21. package/dist/controls.css.map +1 -1
  22. package/dist/demo.compiled.debug.js +24 -21
  23. package/dist/demo.compiled.debug.map +3 -3
  24. package/dist/demo.compiled.js +24 -21
  25. package/dist/demo.compiled.map +3 -3
  26. package/dist/deps.js +33 -25
  27. package/dist/locales.js +31 -29
  28. package/dist/receiver.compiled.debug.js +14 -12
  29. package/dist/receiver.compiled.debug.map +3 -3
  30. package/dist/receiver.compiled.js +14 -12
  31. package/dist/receiver.compiled.map +3 -3
  32. package/dist/shaka-player.compiled.d.ts +258 -20
  33. package/dist/shaka-player.compiled.debug.d.ts +258 -20
  34. package/dist/shaka-player.compiled.debug.externs.js +1005 -695
  35. package/dist/shaka-player.compiled.debug.js +720 -640
  36. package/dist/shaka-player.compiled.debug.map +4 -4
  37. package/dist/shaka-player.compiled.externs.js +1005 -695
  38. package/dist/shaka-player.compiled.js +850 -817
  39. package/dist/shaka-player.compiled.map +4 -4
  40. package/dist/shaka-player.ui.d.ts +274 -21
  41. package/dist/shaka-player.ui.debug.d.ts +274 -21
  42. package/dist/shaka-player.ui.debug.externs.js +766 -429
  43. package/dist/shaka-player.ui.debug.js +863 -765
  44. package/dist/shaka-player.ui.debug.map +4 -4
  45. package/dist/shaka-player.ui.externs.js +766 -429
  46. package/dist/shaka-player.ui.js +1042 -996
  47. package/dist/shaka-player.ui.map +4 -4
  48. package/docs/design/bg-fetch-after.gv +29 -0
  49. package/docs/design/bg-fetch-after.gv.png +0 -0
  50. package/docs/design/bg-fetch-before.gv +18 -0
  51. package/docs/design/bg-fetch-before.gv.png +0 -0
  52. package/docs/design/bg-fetch.md +134 -0
  53. package/docs/tutorials/blob-url.md +15 -0
  54. package/docs/tutorials/index.json +1 -0
  55. package/docs/tutorials/ui-customization.md +66 -0
  56. package/docs/tutorials/ui.md +5 -5
  57. package/externs/aria.js +6 -0
  58. package/externs/ima.js +3 -0
  59. package/externs/shaka/ads.js +29 -1
  60. package/externs/shaka/manifest.js +18 -2
  61. package/externs/shaka/manifest_parser.js +10 -1
  62. package/externs/shaka/net.js +20 -4
  63. package/externs/shaka/offline.js +36 -2
  64. package/externs/shaka/player.js +182 -13
  65. package/externs/shaka/text.js +24 -10
  66. package/externs/texttrack.js +3 -0
  67. package/externs/webcrypto.js +18 -0
  68. package/lib/abr/ewma.js +14 -0
  69. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  70. package/lib/abr/simple_abr_manager.js +6 -0
  71. package/lib/ads/ad_manager.js +16 -15
  72. package/lib/ads/client_side_ad.js +8 -0
  73. package/lib/ads/client_side_ad_manager.js +14 -6
  74. package/lib/ads/server_side_ad.js +7 -0
  75. package/lib/ads/server_side_ad_manager.js +19 -2
  76. package/lib/cast/cast_proxy.js +8 -0
  77. package/lib/cast/cast_utils.js +7 -1
  78. package/lib/cea/cea608_data_channel.js +56 -56
  79. package/lib/cea/cea608_memory.js +33 -33
  80. package/lib/cea/cea708_service.js +43 -43
  81. package/lib/cea/cea708_window.js +38 -38
  82. package/lib/cea/cea_decoder.js +13 -14
  83. package/lib/cea/cea_utils.js +23 -23
  84. package/lib/cea/dtvcc_packet_builder.js +7 -7
  85. package/lib/cea/i_caption_decoder.js +2 -3
  86. package/lib/cea/i_cea_parser.js +2 -2
  87. package/lib/cea/mp4_cea_parser.js +6 -6
  88. package/lib/dash/dash_parser.js +53 -14
  89. package/lib/dash/segment_base.js +25 -1
  90. package/lib/dash/segment_list.js +18 -9
  91. package/lib/dash/segment_template.js +23 -10
  92. package/lib/hls/hls_parser.js +210 -30
  93. package/lib/media/closed_caption_parser.js +1 -2
  94. package/lib/media/drm_engine.js +162 -192
  95. package/lib/media/quality_observer.js +291 -0
  96. package/lib/media/segment_index.js +9 -1
  97. package/lib/media/segment_reference.js +37 -5
  98. package/lib/media/streaming_engine.js +48 -10
  99. package/lib/net/http_fetch_plugin.js +31 -11
  100. package/lib/net/http_plugin_utils.js +1 -0
  101. package/lib/net/http_xhr_plugin.js +40 -14
  102. package/lib/net/networking_engine.js +82 -18
  103. package/lib/offline/download_info.js +68 -0
  104. package/lib/offline/download_manager.js +14 -7
  105. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  106. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  107. package/lib/offline/indexeddb/v1_storage_cell.js +1 -0
  108. package/lib/offline/indexeddb/v2_storage_cell.js +1 -0
  109. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  110. package/lib/offline/manifest_converter.js +3 -1
  111. package/lib/offline/storage.js +404 -297
  112. package/lib/offline/stored_content_utils.js +2 -0
  113. package/lib/player.js +622 -153
  114. package/lib/polyfill/aria.js +82 -0
  115. package/lib/polyfill/random_uuid.js +52 -0
  116. package/lib/routing/walker.js +2 -3
  117. package/lib/text/cue.js +12 -0
  118. package/lib/text/srt_text_parser.js +4 -4
  119. package/lib/text/text_engine.js +2 -2
  120. package/lib/text/ttml_text_parser.js +25 -2
  121. package/lib/text/ui_text_displayer.js +2 -2
  122. package/lib/text/vtt_text_parser.js +2 -2
  123. package/lib/text/web_vtt_generator.js +55 -12
  124. package/lib/util/cmcd_manager.js +826 -0
  125. package/lib/util/error.js +12 -0
  126. package/lib/util/language_utils.js +12 -0
  127. package/lib/util/manifest_parser_utils.js +1 -0
  128. package/lib/util/mp4_box_parsers.js +20 -20
  129. package/lib/util/multi_map.js +8 -0
  130. package/lib/util/mutex.js +52 -0
  131. package/lib/util/periods.js +98 -64
  132. package/lib/util/player_configuration.js +31 -1
  133. package/lib/util/stream_utils.js +140 -85
  134. package/lib/util/xml_utils.js +31 -12
  135. package/package.json +1 -1
  136. package/roadmap.md +24 -22
  137. package/shaka-player.uncompiled.js +4 -0
  138. package/test/cast/cast_utils_unit.js +1 -0
  139. package/test/cea/cea608_memory_unit.js +1 -1
  140. package/test/cea/cea708_service_unit.js +8 -8
  141. package/test/cea/cea708_window_unit.js +6 -6
  142. package/test/cea/cea_decoder_unit.js +1 -1
  143. package/test/cea/dtvcc_packet_builder_unit.js +2 -2
  144. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  145. package/test/dash/dash_parser_live_unit.js +101 -1
  146. package/test/dash/dash_parser_manifest_unit.js +78 -2
  147. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  148. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  149. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  150. package/test/hls/hls_live_unit.js +2 -0
  151. package/test/hls/hls_parser_unit.js +252 -0
  152. package/test/media/drm_engine_integration.js +86 -12
  153. package/test/media/drm_engine_unit.js +310 -450
  154. package/test/media/quality_observer_unit.js +139 -0
  155. package/test/media/segment_reference_unit.js +16 -1
  156. package/test/media/streaming_engine_integration.js +2 -1
  157. package/test/media/streaming_engine_unit.js +2 -0
  158. package/test/net/http_plugin_unit.js +20 -15
  159. package/test/offline/manifest_convert_unit.js +2 -0
  160. package/test/offline/storage_integration.js +117 -23
  161. package/test/player_integration.js +151 -0
  162. package/test/player_src_equals_integration.js +52 -0
  163. package/test/player_unit.js +11 -33
  164. package/test/test/assets/chapters.srt +11 -0
  165. package/test/test/assets/chapters.vtt +10 -0
  166. package/test/test/util/cea_utils.js +12 -12
  167. package/test/test/util/dash_parser_util.js +4 -0
  168. package/test/test/util/fake_ad.js +7 -0
  169. package/test/test/util/fake_ad_manager.js +7 -0
  170. package/test/test/util/fake_drm_engine.js +9 -2
  171. package/test/test/util/manifest_generator.js +5 -2
  172. package/test/test/util/manifest_parser_util.js +3 -1
  173. package/test/test/util/test_scheme.js +14 -1
  174. package/test/text/ttml_text_parser_unit.js +47 -0
  175. package/test/text/web_vtt_generator_unit.js +81 -7
  176. package/test/ui/ui_unit.js +250 -0
  177. package/test/util/cmcd_manager_unit.js +277 -0
  178. package/test/util/stream_utils_unit.js +118 -10
  179. package/ui/airplay_button.js +2 -1
  180. package/ui/audio_language_selection.js +9 -5
  181. package/ui/cast_button.js +5 -6
  182. package/ui/constants.js +0 -3
  183. package/ui/context_menu.js +109 -0
  184. package/ui/controls.js +41 -7
  185. package/ui/controls.less +1 -0
  186. package/ui/enums.js +4 -0
  187. package/ui/externs/ui.js +28 -1
  188. package/ui/fast_forward_button.js +16 -8
  189. package/ui/fullscreen_button.js +2 -3
  190. package/ui/language_utils.js +1 -1
  191. package/ui/less/buttons.less +0 -4
  192. package/ui/less/containers.less +74 -0
  193. package/ui/less/overflow_menu.less +0 -10
  194. package/ui/less/tooltip.less +97 -0
  195. package/ui/locales/en.json +1 -0
  196. package/ui/locales/source.json +4 -0
  197. package/ui/loop_button.js +6 -3
  198. package/ui/mute_button.js +3 -4
  199. package/ui/overflow_menu.js +7 -28
  200. package/ui/pip_button.js +10 -7
  201. package/ui/play_button.js +2 -2
  202. package/ui/playback_rate_selection.js +11 -15
  203. package/ui/presentation_time.js +1 -3
  204. package/ui/resolution_selection.js +11 -7
  205. package/ui/rewind_button.js +17 -9
  206. package/ui/seek_bar.js +6 -6
  207. package/ui/settings_menu.js +36 -4
  208. package/ui/small_play_button.js +1 -0
  209. package/ui/spacer.js +1 -1
  210. package/ui/statistics_button.js +253 -0
  211. package/ui/text_selection.js +19 -15
  212. package/ui/ui.js +30 -0
  213. package/ui/ui_utils.js +1 -1
  214. package/ui/volume_bar.js +2 -4
@@ -652,8 +652,7 @@ describe('StreamUtils', () => {
652
652
  });
653
653
 
654
654
  await shaka.util.StreamUtils.filterManifest(
655
- fakeDrmEngine, /* currentVariant= */ null, manifest,
656
- /* useMediaCapabilities= */ true);
655
+ fakeDrmEngine, /* currentVariant= */ null, manifest);
657
656
 
658
657
  // Covers a regression in which we would remove streams with codecs.
659
658
  // The last two streams should be removed because their full MIME types
@@ -680,8 +679,7 @@ describe('StreamUtils', () => {
680
679
  });
681
680
 
682
681
  await shaka.util.StreamUtils.filterManifest(
683
- fakeDrmEngine, /* currentVariant= */ null, manifest,
684
- /* useMediaCapabilities= */ true);
682
+ fakeDrmEngine, /* currentVariant= */ null, manifest);
685
683
  expect(manifest.variants.length).toBe(1);
686
684
  });
687
685
 
@@ -698,8 +696,7 @@ describe('StreamUtils', () => {
698
696
  });
699
697
 
700
698
  await shaka.util.StreamUtils.filterManifest(
701
- fakeDrmEngine, /* currentVariant= */ null, manifest,
702
- /* useMediaCapabilities= */ true);
699
+ fakeDrmEngine, /* currentVariant= */ null, manifest);
703
700
 
704
701
  expect(manifest.variants.length).toBe(1);
705
702
  });
@@ -714,6 +711,7 @@ describe('StreamUtils', () => {
714
711
  variant.bandwidth = 5058558;
715
712
  variant.addAudio(1, (stream) => {
716
713
  stream.bandwidth = 129998;
714
+ stream.codecs = 'opus';
717
715
  });
718
716
  variant.addVideo(2, (stream) => {
719
717
  stream.bandwidth = 4928560;
@@ -728,6 +726,7 @@ describe('StreamUtils', () => {
728
726
  variant.bandwidth = 4911000;
729
727
  variant.addAudio(4, (stream) => {
730
728
  stream.bandwidth = 129998;
729
+ stream.codecs = 'vorbis';
731
730
  });
732
731
  variant.addVideo(5, (stream) => {
733
732
  stream.bandwidth = 4781002;
@@ -742,6 +741,7 @@ describe('StreamUtils', () => {
742
741
  variant.bandwidth = 10850316;
743
742
  variant.addAudio(7, (stream) => {
744
743
  stream.bandwidth = 129998;
744
+ stream.codecs = 'opus';
745
745
  });
746
746
  variant.addVideo(8, (stream) => {
747
747
  stream.bandwidth = 10784324;
@@ -751,6 +751,62 @@ describe('StreamUtils', () => {
751
751
  });
752
752
  };
753
753
 
754
+ it('chooses preferred audio and video codecs', () => {
755
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
756
+ addVariant1080Avc1(manifest);
757
+ addVariant1080Vp9(manifest);
758
+ addVariant2160Vp9(manifest);
759
+ });
760
+ const variants =
761
+ shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants,
762
+ /* preferredVideoCodecs= */['vp09'],
763
+ /* preferredAudioCodecs= */['opus']);
764
+
765
+ expect(variants.length).toBe(1);
766
+ expect(variants[0].video.codecs).toBe(vp09Codecs);
767
+ expect(variants[0].audio.codecs).toBe('opus');
768
+ });
769
+
770
+ it('chooses preferred video codecs', () => {
771
+ // If no preferred audio codecs is specified or can be found, choose the
772
+ // variants with preferred video codecs.
773
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
774
+ addVariant1080Avc1(manifest);
775
+ addVariant1080Vp9(manifest);
776
+ addVariant2160Vp9(manifest);
777
+ });
778
+ const variants =
779
+ shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants,
780
+ /* preferredVideoCodecs= */['vp09'],
781
+ /* preferredAudioCodecs= */[]);
782
+
783
+ expect(variants.length).toBe(2);
784
+ expect(variants[0].video.codecs).toBe(vp09Codecs);
785
+ expect(variants[0].audio.codecs).toBe('vorbis');
786
+ expect(variants[1].video.codecs).toBe(vp09Codecs);
787
+ expect(variants[1].audio.codecs).toBe('opus');
788
+ });
789
+
790
+ it('chooses preferred audio codecs', () => {
791
+ // If no preferred video codecs is specified or can be found, choose the
792
+ // variants with preferred audio codecs.
793
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
794
+ addVariant1080Avc1(manifest);
795
+ addVariant1080Vp9(manifest);
796
+ addVariant2160Vp9(manifest);
797
+ });
798
+ const variants =
799
+ shaka.util.StreamUtils.choosePreferredCodecs(manifest.variants,
800
+ /* preferredVideoCodecs= */['foo'],
801
+ /* preferredAudioCodecs= */['opus']);
802
+
803
+ expect(variants.length).toBe(2);
804
+ expect(variants[0].video.codecs).toBe(avc1Codecs);
805
+ expect(variants[0].audio.codecs).toBe('opus');
806
+ expect(variants[1].video.codecs).toBe(vp09Codecs);
807
+ expect(variants[1].audio.codecs).toBe('opus');
808
+ });
809
+
754
810
  it('chooses variants with different sizes (density) by codecs', () => {
755
811
  manifest = shaka.test.ManifestGenerator.generate((manifest) => {
756
812
  addVariant1080Avc1(manifest);
@@ -758,11 +814,14 @@ describe('StreamUtils', () => {
758
814
  addVariant2160Vp9(manifest);
759
815
  });
760
816
 
761
- shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, 2);
817
+ shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
818
+ /* preferredVideoCodecs= */[],
819
+ /* preferredAudioCodecs= */[],
820
+ /* preferredAudioChannelCount= */2,
821
+ /* preferredDecodingAttributes= */[]);
762
822
 
763
- expect(manifest.variants.length).toBe(2);
823
+ expect(manifest.variants.length).toBe(1);
764
824
  expect(manifest.variants[0].video.codecs).toBe(vp09Codecs);
765
- expect(manifest.variants[1].video.codecs).toBe(vp09Codecs);
766
825
  });
767
826
 
768
827
  it('chooses variants with same sizes (density) by codecs', () => {
@@ -771,10 +830,59 @@ describe('StreamUtils', () => {
771
830
  addVariant1080Vp9(manifest);
772
831
  });
773
832
 
774
- shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest, 2);
833
+ shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
834
+ /* preferredVideoCodecs= */[],
835
+ /* preferredAudioCodecs= */[],
836
+ /* preferredAudioChannelCount= */2,
837
+ /* preferredDecodingAttributes= */[]);
775
838
 
776
839
  expect(manifest.variants.length).toBe(1);
777
840
  expect(manifest.variants[0].video.codecs).toBe(vp09Codecs);
778
841
  });
842
+
843
+ it('chooses variants by decoding attributes', async () => {
844
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
845
+ manifest.addVariant(0, (variant) => {
846
+ variant.bandwidth = 4058558;
847
+ variant.addVideo(1, (stream) => {
848
+ stream.mime('video', 'notsmooth');
849
+ });
850
+ });
851
+ manifest.addVariant(1, (variant) => {
852
+ variant.bandwidth = 4781002;
853
+ variant.addVideo(2, (stream) => {
854
+ stream.mime('video', 'smooth');
855
+ });
856
+ });
857
+ manifest.addVariant(3, (variant) => {
858
+ variant.addVideo(4, (stream) => {
859
+ variant.bandwidth = 5058558;
860
+ stream.mime('video', 'smooth-2');
861
+ });
862
+ });
863
+ });
864
+ window.shakaMediaCapabilities.decodingInfo =
865
+ shaka.test.Util.spyFunc(decodingInfoSpy);
866
+ decodingInfoSpy.and.callFake((config) => {
867
+ const res = config.video.contentType.includes('notsmooth') ?
868
+ {supported: true, smooth: false} :
869
+ {supported: true, smooth: true};
870
+ return Promise.resolve(res);
871
+ });
872
+
873
+ await StreamUtils.getDecodingInfosForVariants(manifest.variants,
874
+ /* usePersistentLicenses= */false, /* srcEquals= */ false);
875
+
876
+ shaka.util.StreamUtils.chooseCodecsAndFilterManifest(manifest,
877
+ /* preferredVideoCodecs= */[],
878
+ /* preferredAudioCodecs= */[],
879
+ /* preferredAudioChannelCount= */2,
880
+ /* preferredDecodingAttributes= */
881
+ [shaka.util.StreamUtils.DecodingAttributes.SMOOTH]);
882
+ // 2 video codecs are smooth. Choose the one with the lowest bandwidth.
883
+ expect(manifest.variants.length).toBe(1);
884
+ expect(manifest.variants[0].id).toBe(1);
885
+ expect(manifest.variants[0].video.id).toBe(2);
886
+ });
779
887
  });
780
888
  });
@@ -36,7 +36,8 @@ shaka.ui.AirPlayButton = class extends shaka.ui.Element {
36
36
  /** @private {!HTMLButtonElement} */
37
37
  this.airplayButton_ = shaka.util.Dom.createButton();
38
38
  this.airplayButton_.classList.add('shaka-airplay-button');
39
- this.airplayButton_.setAttribute('aria-pressed', 'false');
39
+ this.airplayButton_.classList.add('shaka-tooltip');
40
+ this.airplayButton_.ariaPressed = 'false';
40
41
 
41
42
  /** @private {!HTMLElement} */
42
43
  this.airplayIcon_ = shaka.util.Dom.createHTMLElement('i');
@@ -7,7 +7,7 @@
7
7
 
8
8
  goog.provide('shaka.ui.AudioLanguageSelection');
9
9
 
10
- goog.require('shaka.ui.Constants');
10
+ goog.require('shaka.ui.Controls');
11
11
  goog.require('shaka.ui.Enums');
12
12
  goog.require('shaka.ui.LanguageUtils');
13
13
  goog.require('shaka.ui.Locales');
@@ -32,6 +32,7 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
32
32
  super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.LANGUAGE);
33
33
 
34
34
  this.button.classList.add('shaka-language-button');
35
+ this.button.classList.add('shaka-tooltip-status');
35
36
  this.menu.classList.add('shaka-audio-languages');
36
37
 
37
38
  this.eventManager.listen(
@@ -72,6 +73,8 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
72
73
 
73
74
  this.controls.dispatchEvent(
74
75
  new shaka.util.FakeEvent('languageselectionupdated'));
76
+
77
+ this.button.setAttribute('shaka-status', this.currentSelection.innerText);
75
78
  }
76
79
 
77
80
  /** @private */
@@ -96,10 +99,8 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
96
99
  updateLocalizedStrings_() {
97
100
  const LocIds = shaka.ui.Locales.Ids;
98
101
 
99
- this.backButton.setAttribute(shaka.ui.Constants.ARIA_LABEL,
100
- this.localization.resolve(LocIds.BACK));
101
- this.button.setAttribute(shaka.ui.Constants.ARIA_LABEL,
102
- this.localization.resolve(LocIds.LANGUAGE));
102
+ this.backButton.ariaLabel = this.localization.resolve(LocIds.BACK);
103
+ this.button.ariaLabel = this.localization.resolve(LocIds.LANGUAGE);
103
104
  this.nameSpan.textContent =
104
105
  this.localization.resolve(LocIds.LANGUAGE);
105
106
  this.backSpan.textContent =
@@ -121,3 +122,6 @@ shaka.ui.AudioLanguageSelection.Factory = class {
121
122
 
122
123
  shaka.ui.OverflowMenu.registerElement(
123
124
  'language', new shaka.ui.AudioLanguageSelection.Factory());
125
+
126
+ shaka.ui.Controls.registerElement(
127
+ 'language', new shaka.ui.AudioLanguageSelection.Factory());
package/ui/cast_button.js CHANGED
@@ -8,7 +8,6 @@
8
8
  goog.provide('shaka.ui.CastButton');
9
9
 
10
10
  goog.require('shaka.cast.CastProxy');
11
- goog.require('shaka.ui.Constants');
12
11
  goog.require('shaka.ui.Controls');
13
12
  goog.require('shaka.ui.Element');
14
13
  goog.require('shaka.ui.Enums');
@@ -42,7 +41,8 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
42
41
  /** @private {!HTMLButtonElement} */
43
42
  this.castButton_ = shaka.util.Dom.createButton();
44
43
  this.castButton_.classList.add('shaka-cast-button');
45
- this.castButton_.setAttribute('aria-pressed', 'false');
44
+ this.castButton_.classList.add('shaka-tooltip');
45
+ this.castButton_.ariaPressed = 'false';
46
46
 
47
47
  /** @private {!HTMLElement} */
48
48
  this.castIcon_ = shaka.util.Dom.createHTMLElement('i');
@@ -125,9 +125,9 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
125
125
  // Aria-pressed set to true when casting, set to false otherwise.
126
126
  if (canCast) {
127
127
  if (isCasting) {
128
- this.castButton_.setAttribute('aria-pressed', 'true');
128
+ this.castButton_.ariaPressed = 'true';
129
129
  } else {
130
- this.castButton_.setAttribute('aria-pressed', 'false');
130
+ this.castButton_.ariaPressed = 'false';
131
131
  }
132
132
  }
133
133
 
@@ -155,8 +155,7 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
155
155
  updateLocalizedStrings_() {
156
156
  const LocIds = shaka.ui.Locales.Ids;
157
157
 
158
- this.castButton_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
159
- this.localization.resolve(LocIds.CAST));
158
+ this.castButton_.ariaLabel = this.localization.resolve(LocIds.CAST);
160
159
  this.castNameSpan_.textContent =
161
160
  this.localization.resolve(LocIds.CAST);
162
161
 
package/ui/constants.js CHANGED
@@ -7,8 +7,5 @@
7
7
 
8
8
  goog.provide('shaka.ui.Constants');
9
9
 
10
- /** @const {string} */
11
- shaka.ui.Constants.ARIA_LABEL = 'aria-label';
12
-
13
10
  /** @const {number} */
14
11
  shaka.ui.Constants.MIN_SEEK_WINDOW_TO_SHOW_SEEKBAR = 5; // seconds
@@ -0,0 +1,109 @@
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.ContextMenu');
9
+
10
+ goog.require('goog.asserts');
11
+ goog.require('shaka.log');
12
+ goog.require('shaka.ui.Element');
13
+ goog.require('shaka.ui.Utils');
14
+ goog.require('shaka.util.Dom');
15
+ goog.requireType('shaka.ui.Controls');
16
+
17
+
18
+ /**
19
+ * @extends {shaka.ui.Element}
20
+ * @final
21
+ * @export
22
+ */
23
+ shaka.ui.ContextMenu = class extends shaka.ui.Element {
24
+ /**
25
+ * @param {!HTMLElement} parent
26
+ * @param {!shaka.ui.Controls} controls
27
+ */
28
+ constructor(parent, controls) {
29
+ super(parent, controls);
30
+
31
+ /** @private {!shaka.extern.UIConfiguration} */
32
+ this.config_ = this.controls.getConfig();
33
+
34
+ /** @private {HTMLElement} */
35
+ this.controlsContainer_ = this.controls.getControlsContainer();
36
+
37
+ /** @private {!Array.<shaka.extern.IUIElement>} */
38
+ this.children_ = [];
39
+
40
+ /** @private {!HTMLElement} */
41
+ this.contextMenu_ = shaka.util.Dom.createHTMLElement('div');
42
+ this.contextMenu_.classList.add('shaka-no-propagation');
43
+ this.contextMenu_.classList.add('shaka-context-menu');
44
+ this.contextMenu_.classList.add('shaka-hidden');
45
+
46
+ this.controlsContainer_.appendChild(this.contextMenu_);
47
+
48
+ this.eventManager.listen(this.controlsContainer_, 'contextmenu', (e) => {
49
+ if (this.contextMenu_.classList.contains('shaka-hidden')) {
50
+ e.preventDefault();
51
+
52
+ const controlsLocation =
53
+ this.controlsContainer_.getBoundingClientRect();
54
+ this.contextMenu_.style.left = `${e.clientX - controlsLocation.left}px`;
55
+ this.contextMenu_.style.top = `${e.clientY - controlsLocation.top}px`;
56
+
57
+ shaka.ui.Utils.setDisplay(this.contextMenu_, true);
58
+ } else {
59
+ shaka.ui.Utils.setDisplay(this.contextMenu_, false);
60
+ }
61
+ });
62
+
63
+ this.eventManager.listen(window, 'click', () => {
64
+ shaka.ui.Utils.setDisplay(this.contextMenu_, false);
65
+ });
66
+
67
+ this.createChildren_();
68
+ }
69
+
70
+ /** @override */
71
+ release() {
72
+ this.controlsContainer_ = null;
73
+
74
+ for (const element of this.children_) {
75
+ element.release();
76
+ }
77
+
78
+ this.children_ = [];
79
+ super.release();
80
+ }
81
+
82
+ /**
83
+ * @param {string} name
84
+ * @param {!shaka.extern.IUIElement.Factory} factory
85
+ * @export
86
+ */
87
+ static registerElement(name, factory) {
88
+ shaka.ui.ContextMenu.elementNamesToFactories_.set(name, factory);
89
+ }
90
+
91
+ /**
92
+ * @private
93
+ */
94
+ createChildren_() {
95
+ for (const name of this.config_.contextMenuElements) {
96
+ const factory =
97
+ shaka.ui.ContextMenu.elementNamesToFactories_.get(name);
98
+ if (factory) {
99
+ goog.asserts.assert(this.controls, 'Controls should not be null!');
100
+ this.children_.push(factory.create(this.contextMenu_, this.controls));
101
+ } else {
102
+ shaka.log.alwaysWarn('Unrecognized context menu element:', name);
103
+ }
104
+ }
105
+ }
106
+ };
107
+
108
+ /** @private {!Map.<string, !shaka.extern.IUIElement.Factory>} */
109
+ shaka.ui.ContextMenu.elementNamesToFactories_ = new Map();
package/ui/controls.js CHANGED
@@ -16,6 +16,7 @@ goog.require('shaka.log');
16
16
  goog.require('shaka.ui.AdCounter');
17
17
  goog.require('shaka.ui.AdPosition');
18
18
  goog.require('shaka.ui.BigPlayButton');
19
+ goog.require('shaka.ui.ContextMenu');
19
20
  goog.require('shaka.ui.Locales');
20
21
  goog.require('shaka.ui.Localization');
21
22
  goog.require('shaka.ui.SeekBar');
@@ -323,6 +324,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
323
324
  this.playButton_ = null;
324
325
  }
325
326
 
327
+ if (this.contextMenu_) {
328
+ this.contextMenu_ = null;
329
+ }
330
+
326
331
  if (this.controlsContainer_) {
327
332
  shaka.util.Dom.removeAllChildren(this.controlsContainer_);
328
333
  this.releaseChildElements_();
@@ -650,6 +655,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
650
655
  this.addPlayButton_();
651
656
  }
652
657
 
658
+ if (this.config_.customContextMenu) {
659
+ this.addContextMenu_();
660
+ }
661
+
653
662
  if (!this.spinnerContainer_) {
654
663
  this.addBufferingSpinner_();
655
664
  }
@@ -708,6 +717,14 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
708
717
  this.elements_.push(this.playButton_);
709
718
  }
710
719
 
720
+ /** @private */
721
+ addContextMenu_() {
722
+ /** @private {shaka.ui.ContextMenu} */
723
+ this.contextMenu_ =
724
+ new shaka.ui.ContextMenu(this.controlsButtonPanel_, this);
725
+ this.elements_.push(this.contextMenu_);
726
+ }
727
+
711
728
  /** @private */
712
729
  addScrimContainer_() {
713
730
  // This is the container that gets styled by CSS to have the
@@ -722,7 +739,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
722
739
  /** @private {!HTMLElement} */
723
740
  this.adPanel_ = shaka.util.Dom.createHTMLElement('div');
724
741
  this.adPanel_.classList.add('shaka-ad-controls');
725
- shaka.ui.Utils.setDisplay(this.adPanel_, this.ad_ != null);
742
+ const showAdPanel = this.ad_ != null && this.ad_.isLinear();
743
+ shaka.ui.Utils.setDisplay(this.adPanel_, showAdPanel);
726
744
  this.bottomControls_.appendChild(this.adPanel_);
727
745
 
728
746
  const adPosition = new shaka.ui.AdPosition(this.adPanel_, this);
@@ -779,8 +797,12 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
779
797
  // on the page. The click event listener on window ensures that.
780
798
  // However, clicks on the bottom controls don't propagate to the container,
781
799
  // so we have to explicitly hide the menus onclick here.
782
- this.eventManager_.listen(this.bottomControls_, 'click', () => {
783
- this.hideSettingsMenus();
800
+ this.eventManager_.listen(this.bottomControls_, 'click', (e) => {
801
+ // We explicitly deny this measure when clicking on buttons that
802
+ // open submenus in the control panel.
803
+ if (!e.target['closest']('.shaka-overflow-button')) {
804
+ this.hideSettingsMenus();
805
+ }
784
806
  });
785
807
 
786
808
  this.addAdControls_();
@@ -790,6 +812,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
790
812
  this.controlsButtonPanel_.classList.add('shaka-controls-button-panel');
791
813
  this.controlsButtonPanel_.classList.add(
792
814
  'shaka-show-controls-on-mouse-over');
815
+ if (this.config_.enableTooltips) {
816
+ this.controlsButtonPanel_.classList.add('shaka-tooltips-on');
817
+ }
793
818
  this.bottomControls_.appendChild(this.controlsButtonPanel_);
794
819
 
795
820
  // Create the elements specified by controlPanelElements
@@ -859,7 +884,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
859
884
 
860
885
 
861
886
  /**
862
- * Adds a container for server side ad UI with IMA SDK.
887
+ * Adds a container for client side ad UI with IMA SDK.
863
888
  *
864
889
  * @private
865
890
  */
@@ -868,6 +893,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
868
893
  this.clientAdContainer_ = shaka.util.Dom.createHTMLElement('div');
869
894
  this.clientAdContainer_.classList.add('shaka-client-side-ad-container');
870
895
  shaka.ui.Utils.setDisplay(this.clientAdContainer_, false);
896
+ this.eventManager_.listen(this.clientAdContainer_, 'click', () => {
897
+ this.onContainerClick_();
898
+ });
871
899
  this.videoContainer_.appendChild(this.clientAdContainer_);
872
900
  }
873
901
 
@@ -894,6 +922,12 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
894
922
  // Listen for click events to dismiss the settings menus.
895
923
  this.eventManager_.listen(window, 'click', () => this.hideSettingsMenus());
896
924
 
925
+ // Avoid having multiple submenus open at the same time.
926
+ this.eventManager_.listen(
927
+ this, 'submenuopen', () => {
928
+ this.hideSettingsMenus();
929
+ });
930
+
897
931
  this.eventManager_.listen(this.video_, 'play', () => {
898
932
  this.onPlayStateChange_();
899
933
  });
@@ -1100,7 +1134,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1100
1134
  // recent mouse movement, we're in keyboard navigation, or one of a special
1101
1135
  // class of elements is hovered.
1102
1136
  if (adIsPaused ||
1103
- (!this.ad_ && videoIsPaused) ||
1137
+ ((!this.ad_ || !this.ad_.isLinear()) && videoIsPaused) ||
1104
1138
  this.recentMouseMovement_ ||
1105
1139
  keyboardNavigationMode ||
1106
1140
  this.isHovered_()) {
@@ -1144,14 +1178,14 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1144
1178
 
1145
1179
  if (this.anySettingsMenusAreOpen()) {
1146
1180
  this.hideSettingsMenusTimer_.tickNow();
1147
- } else {
1181
+ } else if (this.config_.singleClickForPlayAndPause) {
1148
1182
  this.onPlayPauseClick_();
1149
1183
  }
1150
1184
  }
1151
1185
 
1152
1186
  /** @private */
1153
1187
  onPlayPauseClick_() {
1154
- if (this.ad_) {
1188
+ if (this.ad_ && this.ad_.isLinear()) {
1155
1189
  this.playPauseAd();
1156
1190
  } else {
1157
1191
  this.playPausePresentation();
package/ui/controls.less CHANGED
@@ -12,6 +12,7 @@
12
12
  @import "less/other_elements.less";
13
13
  @import "less/overflow_menu.less";
14
14
  @import "less/ad_controls.less";
15
+ @import "less/tooltip.less";
15
16
  @import (css, inline) "https://fonts.googleapis.com/css?family=Roboto";
16
17
  // NOTE: Working around google/material-design-icons#958
17
18
  @import (css, inline) "https://fonts.googleapis.com/icon?family=Material+Icons+Round";
package/ui/enums.js CHANGED
@@ -16,7 +16,9 @@ goog.provide('shaka.ui.Enums');
16
16
  shaka.ui.Enums.MaterialDesignIcons = {
17
17
  'FULLSCREEN': 'fullscreen',
18
18
  'EXIT_FULLSCREEN': 'fullscreen_exit',
19
+ 'CLOSE': 'close',
19
20
  'CLOSED_CAPTIONS': 'closed_caption',
21
+ 'CLOSED_CAPTIONS_OFF': 'closed_caption_disabled',
20
22
  'CHECKMARK': 'done',
21
23
  'LANGUAGE': 'language',
22
24
  'PIP': 'picture_in_picture_alt',
@@ -40,4 +42,6 @@ shaka.ui.Enums.MaterialDesignIcons = {
40
42
  'UNLOOP': 'repeat_on',
41
43
  'AIRPLAY': 'airplay',
42
44
  'REPLAY': 'replay',
45
+ 'STATISTICS_ON': 'insert_chart_outlined',
46
+ 'STATISTICS_OFF': 'insert_chart',
43
47
  };
package/ui/externs/ui.js CHANGED
@@ -63,8 +63,14 @@ shaka.extern.UIVolumeBarColors;
63
63
  * @typedef {{
64
64
  * controlPanelElements: !Array.<string>,
65
65
  * overflowMenuButtons: !Array.<string>,
66
+ * contextMenuElements: !Array.<string>,
67
+ * statisticsList: !Array.<string>,
68
+ * playbackRates: !Array.<number>,
69
+ * fastForwardRates: !Array.<number>,
70
+ * rewindRates: !Array.<number>,
66
71
  * addSeekBar: boolean,
67
72
  * addBigPlayButton: boolean,
73
+ * customContextMenu: boolean,
68
74
  * castReceiverAppId: string,
69
75
  * clearBufferOnQualityChange: boolean,
70
76
  * showUnbufferedStart: boolean,
@@ -73,20 +79,34 @@ shaka.extern.UIVolumeBarColors;
73
79
  * trackLabelFormat: shaka.ui.Overlay.TrackLabelFormat,
74
80
  * fadeDelay: number,
75
81
  * doubleClickForFullscreen: boolean,
82
+ * singleClickForPlayAndPause: boolean,
76
83
  * enableKeyboardPlaybackControls: boolean,
77
84
  * enableFullscreenOnRotation: boolean,
78
- * forceLandscapeOnFullscreen: boolean
85
+ * forceLandscapeOnFullscreen: boolean,
86
+ * enableTooltips: boolean
79
87
  * }}
80
88
  *
81
89
  * @property {!Array.<string>} controlPanelElements
82
90
  * The ordered list of control panel elements of the UI.
83
91
  * @property {!Array.<string>} overflowMenuButtons
84
92
  * The ordered list of the overflow menu buttons.
93
+ * @property {!Array.<string>} contextMenuElements
94
+ * The ordered list of buttons in the context menu.
95
+ * @property {!Array.<string>} statisticsList
96
+ * The ordered list of statistics present in the statistics container.
97
+ * @property {!Array.<number>} playbackRates
98
+ * The ordered list of rates for playback selection.
99
+ * @property {!Array.<number>} fastForwardRates
100
+ * The ordered list of rates for fast forward selection.
101
+ * @property {!Array.<number>} rewindRates
102
+ * The ordered list of rates for rewind selection.
85
103
  * @property {boolean} addSeekBar
86
104
  * Whether or not a seek bar should be part of the UI.
87
105
  * @property {boolean} addBigPlayButton
88
106
  * Whether or not a big play button in the center of the video
89
107
  * should be part of the UI.
108
+ * @property {boolean} customContextMenu
109
+ * Whether or not a custom context menu replaces the default.
90
110
  * @property {string} castReceiverAppId
91
111
  * Receiver app id to use for the Chromecast support.
92
112
  * @property {boolean} clearBufferOnQualityChange
@@ -135,6 +155,9 @@ shaka.extern.UIVolumeBarColors;
135
155
  * Whether or not double-clicking on the UI should cause it to enter
136
156
  * fullscreen.
137
157
  * Defaults to true.
158
+ * @property {boolean} singleClickForPlayAndPause
159
+ * Whether or not clicking on the video should cause it to play or pause.
160
+ * Defaults to true.
138
161
  * @property {boolean} enableKeyboardPlaybackControls
139
162
  * Whether or not playback controls via keyboard is enabled, such as seek
140
163
  * forward, seek backward, jump to the beginning/end of the video.
@@ -147,6 +170,10 @@ shaka.extern.UIVolumeBarColors;
147
170
  * enters fullscreen. Note that this behavior is based on an experimental
148
171
  * browser API, and may not work on all platforms.
149
172
  * Defaults to true.
173
+ * @property {boolean} enableTooltips
174
+ * Whether or not buttons in the control panel display tooltips that contain
175
+ * information about their function.
176
+ * Defaults to false.
150
177
  * @exportDoc
151
178
  */
152
179
  shaka.extern.UIConfiguration;