shaka-player 3.2.2 → 3.3.2

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 (188) 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/.github/workflows/build_and_test.yaml +73 -0
  5. package/.github/workflows/release.yaml +53 -0
  6. package/AUTHORS +6 -5
  7. package/CHANGELOG.md +314 -1
  8. package/CONTRIBUTORS +4 -2
  9. package/build/checkversion.py +1 -1
  10. package/build/conformance.textproto +183 -169
  11. package/build/test.py +6 -0
  12. package/build/types/core +3 -0
  13. package/build/types/offline +1 -0
  14. package/build/types/polyfill +1 -0
  15. package/build/types/ui +2 -0
  16. package/demo/common/assets.js +18 -0
  17. package/demo/common/message_ids.js +13 -1
  18. package/demo/config.js +31 -1
  19. package/demo/index.html +1 -1
  20. package/demo/locales/en.json +12 -0
  21. package/demo/locales/source.json +48 -0
  22. package/dist/controls.css +26 -2
  23. package/dist/controls.css.map +1 -1
  24. package/dist/demo.compiled.debug.js +19 -16
  25. package/dist/demo.compiled.debug.map +3 -3
  26. package/dist/demo.compiled.js +19 -16
  27. package/dist/demo.compiled.map +3 -3
  28. package/dist/deps.js +21 -14
  29. package/dist/locales.js +31 -29
  30. package/dist/receiver.compiled.debug.js +11 -9
  31. package/dist/receiver.compiled.debug.map +3 -3
  32. package/dist/receiver.compiled.js +11 -9
  33. package/dist/receiver.compiled.map +3 -3
  34. package/dist/shaka-player.compiled.d.ts +232 -18
  35. package/dist/shaka-player.compiled.debug.d.ts +232 -18
  36. package/dist/shaka-player.compiled.debug.externs.js +293 -17
  37. package/dist/shaka-player.compiled.debug.js +481 -422
  38. package/dist/shaka-player.compiled.debug.map +4 -4
  39. package/dist/shaka-player.compiled.externs.js +293 -17
  40. package/dist/shaka-player.compiled.js +846 -817
  41. package/dist/shaka-player.compiled.map +4 -4
  42. package/dist/shaka-player.ui.d.ts +250 -30
  43. package/dist/shaka-player.ui.debug.d.ts +250 -30
  44. package/dist/shaka-player.ui.debug.externs.js +322 -28
  45. package/dist/shaka-player.ui.debug.js +630 -553
  46. package/dist/shaka-player.ui.debug.map +4 -4
  47. package/dist/shaka-player.ui.externs.js +322 -28
  48. package/dist/shaka-player.ui.js +1048 -1006
  49. package/dist/shaka-player.ui.map +4 -4
  50. package/docs/design/bg-fetch-after.gv +29 -0
  51. package/docs/design/bg-fetch-after.gv.png +0 -0
  52. package/docs/design/bg-fetch-before.gv +18 -0
  53. package/docs/design/bg-fetch-before.gv.png +0 -0
  54. package/docs/design/bg-fetch.md +134 -0
  55. package/docs/tutorials/blob-url.md +15 -0
  56. package/docs/tutorials/index.json +1 -0
  57. package/docs/tutorials/ui-customization.md +66 -0
  58. package/docs/tutorials/ui.md +5 -5
  59. package/externs/cmcd.js +186 -0
  60. package/externs/ima.js +3 -0
  61. package/externs/shaka/ads.js +5 -0
  62. package/externs/shaka/manifest.js +12 -1
  63. package/externs/shaka/manifest_parser.js +10 -1
  64. package/externs/shaka/net.js +20 -4
  65. package/externs/shaka/offline.js +31 -2
  66. package/externs/shaka/player.js +138 -5
  67. package/externs/shaka/text.js +24 -0
  68. package/externs/webcrypto.js +18 -0
  69. package/karma.conf.js +19 -15
  70. package/lib/abr/ewma.js +14 -0
  71. package/lib/abr/ewma_bandwidth_estimator.js +16 -2
  72. package/lib/abr/simple_abr_manager.js +6 -0
  73. package/lib/ads/ad_manager.js +1 -0
  74. package/lib/ads/client_side_ad.js +8 -0
  75. package/lib/ads/client_side_ad_manager.js +8 -4
  76. package/lib/ads/server_side_ad.js +7 -0
  77. package/lib/cast/cast_proxy.js +3 -0
  78. package/lib/cast/cast_receiver.js +3 -0
  79. package/lib/cast/cast_utils.js +1 -0
  80. package/lib/dash/dash_parser.js +79 -33
  81. package/lib/dash/segment_base.js +25 -1
  82. package/lib/dash/segment_list.js +18 -9
  83. package/lib/dash/segment_template.js +23 -10
  84. package/lib/hls/hls_parser.js +116 -23
  85. package/lib/hls/manifest_text_parser.js +1 -0
  86. package/lib/media/quality_observer.js +274 -0
  87. package/lib/media/region_observer.js +39 -33
  88. package/lib/media/region_timeline.js +17 -19
  89. package/lib/media/segment_index.js +8 -1
  90. package/lib/media/segment_reference.js +37 -4
  91. package/lib/media/streaming_engine.js +47 -6
  92. package/lib/net/http_fetch_plugin.js +31 -11
  93. package/lib/net/http_plugin_utils.js +1 -0
  94. package/lib/net/http_xhr_plugin.js +40 -14
  95. package/lib/net/networking_engine.js +85 -18
  96. package/lib/offline/download_info.js +68 -0
  97. package/lib/offline/download_manager.js +13 -5
  98. package/lib/offline/indexeddb/base_storage_cell.js +35 -0
  99. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  100. package/lib/offline/indexeddb/v5_storage_cell.js +5 -0
  101. package/lib/offline/storage.js +401 -293
  102. package/lib/offline/stored_content_utils.js +2 -0
  103. package/lib/player.js +254 -27
  104. package/lib/polyfill/media_capabilities.js +30 -15
  105. package/lib/polyfill/random_uuid.js +52 -0
  106. package/lib/text/cue.js +18 -0
  107. package/lib/text/ttml_text_parser.js +109 -60
  108. package/lib/text/ui_text_displayer.js +19 -13
  109. package/lib/util/cmcd_manager.js +672 -0
  110. package/lib/util/error.js +6 -0
  111. package/lib/util/fake_event_target.js +21 -1
  112. package/lib/util/language_utils.js +12 -0
  113. package/lib/util/mutex.js +52 -0
  114. package/lib/util/periods.js +98 -64
  115. package/lib/util/player_configuration.js +18 -0
  116. package/lib/util/stream_utils.js +86 -10
  117. package/lib/util/xml_utils.js +19 -0
  118. package/package.json +3 -6
  119. package/shaka-player.uncompiled.js +3 -0
  120. package/test/dash/dash_parser_content_protection_unit.js +2 -0
  121. package/test/dash/dash_parser_live_unit.js +144 -1
  122. package/test/dash/dash_parser_manifest_unit.js +37 -2
  123. package/test/dash/dash_parser_segment_base_unit.js +2 -0
  124. package/test/dash/dash_parser_segment_list_unit.js +2 -1
  125. package/test/dash/dash_parser_segment_template_unit.js +2 -1
  126. package/test/hls/hls_live_unit.js +2 -0
  127. package/test/hls/hls_parser_unit.js +76 -0
  128. package/test/media/drm_engine_unit.js +3 -3
  129. package/test/media/quality_observer_unit.js +142 -0
  130. package/test/media/region_observer_unit.js +40 -5
  131. package/test/media/region_timeline_unit.js +40 -22
  132. package/test/media/segment_reference_unit.js +16 -1
  133. package/test/media/streaming_engine_integration.js +4 -2
  134. package/test/media/streaming_engine_unit.js +147 -0
  135. package/test/net/http_plugin_unit.js +20 -15
  136. package/test/offline/storage_integration.js +108 -4
  137. package/test/player_integration.js +140 -8
  138. package/test/player_src_equals_integration.js +41 -8
  139. package/test/player_unit.js +19 -9
  140. package/test/test/assets/chapters.vtt +2 -2
  141. package/test/test/assets/chapters2.vtt +10 -0
  142. package/test/test/util/dash_parser_util.js +4 -0
  143. package/test/test/util/fake_ad.js +7 -0
  144. package/test/test/util/fake_drm_engine.js +9 -2
  145. package/test/test/util/manifest_generator.js +3 -2
  146. package/test/test/util/test_scheme.js +14 -1
  147. package/test/test/util/ttml_utils.js +81 -0
  148. package/test/test/util/util.js +10 -7
  149. package/test/text/mp4_ttml_parser_unit.js +11 -18
  150. package/test/text/ttml_text_parser_unit.js +544 -228
  151. package/test/ui/text_displayer_layout_unit.js +8 -7
  152. package/test/ui/ui_unit.js +250 -0
  153. package/test/util/cmcd_manager_unit.js +291 -0
  154. package/test/util/stream_utils_unit.js +36 -30
  155. package/third_party/closure-uri/uri.js +21 -0
  156. package/ui/airplay_button.js +1 -0
  157. package/ui/audio_language_selection.js +7 -0
  158. package/ui/cast_button.js +1 -0
  159. package/ui/context_menu.js +109 -0
  160. package/ui/controls.js +44 -7
  161. package/ui/controls.less +1 -0
  162. package/ui/enums.js +4 -0
  163. package/ui/externs/ui.js +28 -1
  164. package/ui/fast_forward_button.js +14 -5
  165. package/ui/fullscreen_button.js +1 -0
  166. package/ui/less/buttons.less +0 -4
  167. package/ui/less/containers.less +74 -0
  168. package/ui/less/overflow_menu.less +0 -10
  169. package/ui/less/tooltip.less +97 -0
  170. package/ui/locales/en.json +1 -0
  171. package/ui/locales/source.json +4 -0
  172. package/ui/localization.js +12 -36
  173. package/ui/loop_button.js +5 -0
  174. package/ui/mute_button.js +2 -1
  175. package/ui/overflow_menu.js +5 -25
  176. package/ui/pip_button.js +5 -0
  177. package/ui/play_button.js +2 -2
  178. package/ui/playback_rate_selection.js +8 -9
  179. package/ui/resolution_selection.js +7 -0
  180. package/ui/rewind_button.js +15 -6
  181. package/ui/seek_bar.js +4 -2
  182. package/ui/settings_menu.js +36 -4
  183. package/ui/small_play_button.js +1 -0
  184. package/ui/statistics_button.js +253 -0
  185. package/ui/text_selection.js +12 -4
  186. package/ui/ui.js +30 -0
  187. package/ui/volume_bar.js +1 -1
  188. package/externs/media_capabilities.js +0 -16
@@ -16,14 +16,14 @@ describe('StreamUtils', () => {
16
16
  /** @type {!jasmine.Spy} */
17
17
  let decodingInfoSpy;
18
18
 
19
- const originalDecodingInfo = window.shakaMediaCapabilities.decodingInfo;
19
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
20
20
 
21
21
  beforeEach(() => {
22
22
  decodingInfoSpy = jasmine.createSpy('decodingInfo');
23
23
  });
24
24
 
25
25
  afterEach(() => {
26
- window.shakaMediaCapabilities.decodingInfo = originalDecodingInfo;
26
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
27
27
  });
28
28
 
29
29
  describe('filterStreamsByLanguageAndRole', () => {
@@ -511,7 +511,7 @@ describe('StreamUtils', () => {
511
511
  });
512
512
 
513
513
  it('handles decodingInfo exception', async () => {
514
- window.shakaMediaCapabilities.decodingInfo =
514
+ navigator.mediaCapabilities.decodingInfo =
515
515
  shaka.test.Util.spyFunc(decodingInfoSpy);
516
516
  // If decodingInfo() fails, setDecodingInfo should finish without throwing
517
517
  // an exception, and the variant should have no decodingInfo result.
@@ -539,38 +539,44 @@ describe('StreamUtils', () => {
539
539
  });
540
540
 
541
541
  it('includes transferFunction in config when hdr', async () => {
542
- window.shakaMediaCapabilities.decodingInfo =
543
- shaka.test.Util.spyFunc(decodingInfoSpy);
542
+ const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
544
543
 
545
- manifest = shaka.test.ManifestGenerator.generate((manifest) => {
546
- manifest.addVariant(0, (variant) => {
547
- variant.addVideo(0, (stream) => {
548
- stream.mime('video/mp4', 'avc1.640028');
549
- stream.hdr = 'SDR';
544
+ try {
545
+ navigator.mediaCapabilities.decodingInfo =
546
+ shaka.test.Util.spyFunc(decodingInfoSpy);
547
+
548
+ manifest = shaka.test.ManifestGenerator.generate((manifest) => {
549
+ manifest.addVariant(0, (variant) => {
550
+ variant.addVideo(0, (stream) => {
551
+ stream.mime('video/mp4', 'avc1.640028');
552
+ stream.hdr = 'SDR';
553
+ });
550
554
  });
551
- });
552
- manifest.addVariant(1, (variant) => {
553
- variant.addVideo(1, (stream) => {
554
- stream.mime('video/mp4', 'hvc1.2.4.L150.90');
555
- stream.hdr = 'PQ';
555
+ manifest.addVariant(1, (variant) => {
556
+ variant.addVideo(1, (stream) => {
557
+ stream.mime('video/mp4', 'hvc1.2.4.L150.90');
558
+ stream.hdr = 'PQ';
559
+ });
556
560
  });
557
- });
558
- manifest.addVariant(2, (variant) => {
559
- variant.addVideo(2, (stream) => {
560
- stream.mime('video/mp4', 'hvc1.2.4.L153.B0');
561
- stream.hdr = 'HLG';
561
+ manifest.addVariant(2, (variant) => {
562
+ variant.addVideo(2, (stream) => {
563
+ stream.mime('video/mp4', 'hvc1.2.4.L153.B0');
564
+ stream.hdr = 'HLG';
565
+ });
562
566
  });
563
567
  });
564
- });
565
568
 
566
- await StreamUtils.getDecodingInfosForVariants(manifest.variants,
567
- /* usePersistentLicenses= */ false, /* srcEquals= */ false);
568
- expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction)
569
- .toBe('srgb');
570
- expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction)
571
- .toBe('pq');
572
- expect(decodingInfoSpy.calls.argsFor(2)[0].video.transferFunction)
573
- .toBe('hlg');
569
+ await StreamUtils.getDecodingInfosForVariants(manifest.variants,
570
+ /* usePersistentLicenses= */ false, /* srcEquals= */ false);
571
+ expect(decodingInfoSpy.calls.argsFor(0)[0].video.transferFunction)
572
+ .toBe('srgb');
573
+ expect(decodingInfoSpy.calls.argsFor(1)[0].video.transferFunction)
574
+ .toBe('pq');
575
+ expect(decodingInfoSpy.calls.argsFor(2)[0].video.transferFunction)
576
+ .toBe('hlg');
577
+ } finally {
578
+ navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
579
+ }
574
580
  });
575
581
  });
576
582
 
@@ -861,7 +867,7 @@ describe('StreamUtils', () => {
861
867
  });
862
868
  });
863
869
  });
864
- window.shakaMediaCapabilities.decodingInfo =
870
+ navigator.mediaCapabilities.decodingInfo =
865
871
  shaka.test.Util.spyFunc(decodingInfoSpy);
866
872
  decodingInfoSpy.and.callFake((config) => {
867
873
  const res = config.video.contentType.includes('notsmooth') ?
@@ -808,6 +808,27 @@ goog.Uri.QueryData.prototype.add = function(key, value) {
808
808
  return this;
809
809
  };
810
810
 
811
+ /**
812
+ * Sets a key value pair and removes all other keys with the same value.
813
+ *
814
+ * @param {string} key Name.
815
+ * @param {string} value Value.
816
+ * @return {!goog.Uri.QueryData} Instance of this object.
817
+ */
818
+ goog.Uri.QueryData.prototype.set = function(key, value) {
819
+ this.ensureKeyMapInitialized_();
820
+ // Invalidate the cache.
821
+ this.encodedQuery_ = null;
822
+
823
+ if (!this.keyMap_.hasOwnProperty(key)) {
824
+ this.add(key, value);
825
+ } else {
826
+ this.keyMap_[key] = [value];
827
+ }
828
+
829
+ return this;
830
+ };
831
+
811
832
 
812
833
  /**
813
834
  * @return {string} Encoded query string.
@@ -36,6 +36,7 @@ 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_.classList.add('shaka-tooltip');
39
40
  this.airplayButton_.ariaPressed = 'false';
40
41
 
41
42
  /** @private {!HTMLElement} */
@@ -7,6 +7,7 @@
7
7
 
8
8
  goog.provide('shaka.ui.AudioLanguageSelection');
9
9
 
10
+ goog.require('shaka.ui.Controls');
10
11
  goog.require('shaka.ui.Enums');
11
12
  goog.require('shaka.ui.LanguageUtils');
12
13
  goog.require('shaka.ui.Locales');
@@ -31,6 +32,7 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
31
32
  super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.LANGUAGE);
32
33
 
33
34
  this.button.classList.add('shaka-language-button');
35
+ this.button.classList.add('shaka-tooltip-status');
34
36
  this.menu.classList.add('shaka-audio-languages');
35
37
 
36
38
  this.eventManager.listen(
@@ -71,6 +73,8 @@ shaka.ui.AudioLanguageSelection = class extends shaka.ui.SettingsMenu {
71
73
 
72
74
  this.controls.dispatchEvent(
73
75
  new shaka.util.FakeEvent('languageselectionupdated'));
76
+
77
+ this.button.setAttribute('shaka-status', this.currentSelection.innerText);
74
78
  }
75
79
 
76
80
  /** @private */
@@ -118,3 +122,6 @@ shaka.ui.AudioLanguageSelection.Factory = class {
118
122
 
119
123
  shaka.ui.OverflowMenu.registerElement(
120
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
@@ -41,6 +41,7 @@ shaka.ui.CastButton = class extends shaka.ui.Element {
41
41
  /** @private {!HTMLButtonElement} */
42
42
  this.castButton_ = shaka.util.Dom.createButton();
43
43
  this.castButton_.classList.add('shaka-cast-button');
44
+ this.castButton_.classList.add('shaka-tooltip');
44
45
  this.castButton_.ariaPressed = 'false';
45
46
 
46
47
  /** @private {!HTMLElement} */
@@ -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');
@@ -253,6 +254,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
253
254
 
254
255
  this.localization_ = null;
255
256
  this.pressedKeys_.clear();
257
+
258
+ // FakeEventTarget implements IReleasable
259
+ super.release();
256
260
  }
257
261
 
258
262
 
@@ -323,6 +327,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
323
327
  this.playButton_ = null;
324
328
  }
325
329
 
330
+ if (this.contextMenu_) {
331
+ this.contextMenu_ = null;
332
+ }
333
+
326
334
  if (this.controlsContainer_) {
327
335
  shaka.util.Dom.removeAllChildren(this.controlsContainer_);
328
336
  this.releaseChildElements_();
@@ -650,6 +658,10 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
650
658
  this.addPlayButton_();
651
659
  }
652
660
 
661
+ if (this.config_.customContextMenu) {
662
+ this.addContextMenu_();
663
+ }
664
+
653
665
  if (!this.spinnerContainer_) {
654
666
  this.addBufferingSpinner_();
655
667
  }
@@ -708,6 +720,14 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
708
720
  this.elements_.push(this.playButton_);
709
721
  }
710
722
 
723
+ /** @private */
724
+ addContextMenu_() {
725
+ /** @private {shaka.ui.ContextMenu} */
726
+ this.contextMenu_ =
727
+ new shaka.ui.ContextMenu(this.controlsButtonPanel_, this);
728
+ this.elements_.push(this.contextMenu_);
729
+ }
730
+
711
731
  /** @private */
712
732
  addScrimContainer_() {
713
733
  // This is the container that gets styled by CSS to have the
@@ -722,7 +742,8 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
722
742
  /** @private {!HTMLElement} */
723
743
  this.adPanel_ = shaka.util.Dom.createHTMLElement('div');
724
744
  this.adPanel_.classList.add('shaka-ad-controls');
725
- shaka.ui.Utils.setDisplay(this.adPanel_, this.ad_ != null);
745
+ const showAdPanel = this.ad_ != null && this.ad_.isLinear();
746
+ shaka.ui.Utils.setDisplay(this.adPanel_, showAdPanel);
726
747
  this.bottomControls_.appendChild(this.adPanel_);
727
748
 
728
749
  const adPosition = new shaka.ui.AdPosition(this.adPanel_, this);
@@ -779,8 +800,12 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
779
800
  // on the page. The click event listener on window ensures that.
780
801
  // However, clicks on the bottom controls don't propagate to the container,
781
802
  // so we have to explicitly hide the menus onclick here.
782
- this.eventManager_.listen(this.bottomControls_, 'click', () => {
783
- this.hideSettingsMenus();
803
+ this.eventManager_.listen(this.bottomControls_, 'click', (e) => {
804
+ // We explicitly deny this measure when clicking on buttons that
805
+ // open submenus in the control panel.
806
+ if (!e.target['closest']('.shaka-overflow-button')) {
807
+ this.hideSettingsMenus();
808
+ }
784
809
  });
785
810
 
786
811
  this.addAdControls_();
@@ -790,6 +815,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
790
815
  this.controlsButtonPanel_.classList.add('shaka-controls-button-panel');
791
816
  this.controlsButtonPanel_.classList.add(
792
817
  'shaka-show-controls-on-mouse-over');
818
+ if (this.config_.enableTooltips) {
819
+ this.controlsButtonPanel_.classList.add('shaka-tooltips-on');
820
+ }
793
821
  this.bottomControls_.appendChild(this.controlsButtonPanel_);
794
822
 
795
823
  // Create the elements specified by controlPanelElements
@@ -859,7 +887,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
859
887
 
860
888
 
861
889
  /**
862
- * Adds a container for server side ad UI with IMA SDK.
890
+ * Adds a container for client side ad UI with IMA SDK.
863
891
  *
864
892
  * @private
865
893
  */
@@ -868,6 +896,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
868
896
  this.clientAdContainer_ = shaka.util.Dom.createHTMLElement('div');
869
897
  this.clientAdContainer_.classList.add('shaka-client-side-ad-container');
870
898
  shaka.ui.Utils.setDisplay(this.clientAdContainer_, false);
899
+ this.eventManager_.listen(this.clientAdContainer_, 'click', () => {
900
+ this.onContainerClick_();
901
+ });
871
902
  this.videoContainer_.appendChild(this.clientAdContainer_);
872
903
  }
873
904
 
@@ -894,6 +925,12 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
894
925
  // Listen for click events to dismiss the settings menus.
895
926
  this.eventManager_.listen(window, 'click', () => this.hideSettingsMenus());
896
927
 
928
+ // Avoid having multiple submenus open at the same time.
929
+ this.eventManager_.listen(
930
+ this, 'submenuopen', () => {
931
+ this.hideSettingsMenus();
932
+ });
933
+
897
934
  this.eventManager_.listen(this.video_, 'play', () => {
898
935
  this.onPlayStateChange_();
899
936
  });
@@ -1100,7 +1137,7 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1100
1137
  // recent mouse movement, we're in keyboard navigation, or one of a special
1101
1138
  // class of elements is hovered.
1102
1139
  if (adIsPaused ||
1103
- (!this.ad_ && videoIsPaused) ||
1140
+ ((!this.ad_ || !this.ad_.isLinear()) && videoIsPaused) ||
1104
1141
  this.recentMouseMovement_ ||
1105
1142
  keyboardNavigationMode ||
1106
1143
  this.isHovered_()) {
@@ -1144,14 +1181,14 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
1144
1181
 
1145
1182
  if (this.anySettingsMenusAreOpen()) {
1146
1183
  this.hideSettingsMenusTimer_.tickNow();
1147
- } else {
1184
+ } else if (this.config_.singleClickForPlayAndPause) {
1148
1185
  this.onPlayPauseClick_();
1149
1186
  }
1150
1187
  }
1151
1188
 
1152
1189
  /** @private */
1153
1190
  onPlayPauseClick_() {
1154
- if (this.ad_) {
1191
+ if (this.ad_ && this.ad_.isLinear()) {
1155
1192
  this.playPauseAd();
1156
1193
  } else {
1157
1194
  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;
@@ -32,11 +32,16 @@ shaka.ui.FastForwardButton = class extends shaka.ui.Element {
32
32
  this.button_ = shaka.util.Dom.createButton();
33
33
  this.button_.classList.add('material-icons-round');
34
34
  this.button_.classList.add('shaka-fast-forward-button');
35
+ this.button_.classList.add('shaka-tooltip-status');
36
+ this.button_.setAttribute('shaka-status', '1x');
35
37
  this.button_.textContent =
36
38
  shaka.ui.Enums.MaterialDesignIcons.FAST_FORWARD;
37
39
  this.parent.appendChild(this.button_);
38
40
  this.updateAriaLabel_();
39
41
 
42
+ /** @private {!Array.<number>} */
43
+ this.fastForwardRates_ = this.controls.getConfig().fastForwardRates;
44
+
40
45
  this.eventManager.listen(
41
46
  this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
42
47
  this.updateAriaLabel_();
@@ -61,7 +66,7 @@ shaka.ui.FastForwardButton = class extends shaka.ui.Element {
61
66
  }
62
67
 
63
68
  /**
64
- * Cycles trick play rate between 1, 2, 4, and 8.
69
+ * Cycles trick play rate between the selected fast forward rates.
65
70
  * @private
66
71
  */
67
72
  fastForward_() {
@@ -70,11 +75,15 @@ shaka.ui.FastForwardButton = class extends shaka.ui.Element {
70
75
  }
71
76
 
72
77
  const trickPlayRate = this.player.getPlaybackRate();
73
- // Every time the button is clicked, the rate is multiplied by 2,
74
- // unless the rate is at max (8), in which case it is dropped back to 1.
75
- const newRate = (trickPlayRate < 0 || trickPlayRate > 4) ?
76
- 1 : trickPlayRate * 2;
78
+ const newRateIndex = this.fastForwardRates_.indexOf(trickPlayRate) + 1;
79
+
80
+ // When the button is clicked, the next rate in this.fastForwardRates_ is
81
+ // selected. If no more rates are available, the first one is set.
82
+ const newRate = (newRateIndex != this.fastForwardRates_.length) ?
83
+ this.fastForwardRates_[newRateIndex] : this.fastForwardRates_[0];
77
84
  this.player.trickPlay(newRate);
85
+
86
+ this.button_.setAttribute('shaka-status', newRate + 'x');
78
87
  }
79
88
  };
80
89
 
@@ -32,6 +32,7 @@ shaka.ui.FullscreenButton = class extends shaka.ui.Element {
32
32
  this.button_ = shaka.util.Dom.createButton();
33
33
  this.button_.classList.add('shaka-fullscreen-button');
34
34
  this.button_.classList.add('material-icons-round');
35
+ this.button_.classList.add('shaka-tooltip');
35
36
 
36
37
  // Don't show the button if fullscreen is not supported
37
38
  if (!document.fullscreenEnabled) {
@@ -75,10 +75,6 @@
75
75
  .shaka-current-time {
76
76
  font-size: 14px;
77
77
  color: rgb(255, 255, 255);
78
-
79
- /* Make the time element the right size for the text, instead of defaulting
80
- * to the same size as the rest of the controls. */
81
- height: auto;
82
78
  cursor: pointer;
83
79
 
84
80
  &[disabled] {
@@ -195,6 +195,80 @@
195
195
  align-items: center;
196
196
  }
197
197
 
198
+ .shaka-statistics-container {
199
+ overflow-x: hidden;
200
+ overflow-y: auto;
201
+
202
+ min-width: 300px;
203
+
204
+ color: white;
205
+ background-color: rgba(35, 35, 35, 0.9);
206
+
207
+ font-size: 14px;
208
+
209
+ padding: 5px 10px;
210
+ border-radius: 2px;
211
+
212
+ position: absolute;
213
+ z-index: 2;
214
+ left: 15px;
215
+ top: 15px;
216
+
217
+ /* Fades out with the other controls. */
218
+ .show-when-controls-shown();
219
+
220
+ div {
221
+ display: flex;
222
+ justify-content: space-between;
223
+ }
224
+
225
+ span {
226
+ color: rgb(150, 150, 150);
227
+ }
228
+ }
229
+
230
+ .shaka-context-menu {
231
+ background-color: rgba(35, 35, 35, 0.9);
232
+
233
+ border-radius: 2px;
234
+
235
+ position: absolute;
236
+ z-index: 3;
237
+
238
+ button {
239
+ padding: 5px 10px;
240
+
241
+ width: 100%;
242
+ display: flex;
243
+ align-items: center;
244
+
245
+ color: white;
246
+ background: transparent;
247
+ border: 0;
248
+ cursor: pointer;
249
+
250
+ &:hover {
251
+ background-color: rgba(50, 50, 50, 0.9);
252
+ }
253
+ }
254
+
255
+ label {
256
+ padding: 0 20px;
257
+
258
+ align-items: flex-start;
259
+
260
+ color: white;
261
+ cursor: pointer;
262
+ }
263
+
264
+ .shaka-current-selection-span {
265
+ align-items: flex-start;
266
+
267
+ color: white;
268
+ cursor: pointer;
269
+ }
270
+ }
271
+
198
272
  .shaka-scrim-container {
199
273
  margin: 0;
200
274
  .fill-container();