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
@@ -8,7 +8,7 @@
8
8
  goog.provide('shaka.ui.ResolutionSelection');
9
9
 
10
10
  goog.require('goog.asserts');
11
- goog.require('shaka.ui.Constants');
11
+ goog.require('shaka.ui.Controls');
12
12
  goog.require('shaka.ui.Enums');
13
13
  goog.require('shaka.ui.Locales');
14
14
  goog.require('shaka.ui.Localization');
@@ -34,6 +34,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
34
34
  super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.RESOLUTION);
35
35
 
36
36
  this.button.classList.add('shaka-resolution-button');
37
+ this.button.classList.add('shaka-tooltip-status');
37
38
  this.menu.classList.add('shaka-resolutions');
38
39
 
39
40
  this.eventManager.listen(
@@ -137,7 +138,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
137
138
 
138
139
  if (!abrEnabled && track == selectedTrack) {
139
140
  // If abr is disabled, mark the selected track's resolution.
140
- button.setAttribute('aria-selected', 'true');
141
+ button.ariaSelected = 'true';
141
142
  button.appendChild(shaka.ui.Utils.checkmarkIcon());
142
143
  span.classList.add('shaka-chosen-item');
143
144
  this.currentSelection.textContent = span.textContent;
@@ -163,7 +164,7 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
163
164
 
164
165
  // If abr is enabled reflect it by marking 'Auto' as selected.
165
166
  if (abrEnabled) {
166
- autoButton.setAttribute('aria-selected', 'true');
167
+ autoButton.ariaSelected = 'true';
167
168
  autoButton.appendChild(shaka.ui.Utils.checkmarkIcon());
168
169
 
169
170
  this.abrOnSpan_.classList.add('shaka-chosen-item');
@@ -172,6 +173,8 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
172
173
  this.localization.resolve(shaka.ui.Locales.Ids.AUTO_QUALITY);
173
174
  }
174
175
 
176
+ this.button.setAttribute('shaka-status', this.currentSelection.textContent);
177
+
175
178
  this.menu.appendChild(autoButton);
176
179
  shaka.ui.Utils.focusOnTheChosenItem(this.menu);
177
180
  this.controls.dispatchEvent(
@@ -198,10 +201,8 @@ shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
198
201
  updateLocalizedStrings_() {
199
202
  const LocIds = shaka.ui.Locales.Ids;
200
203
 
201
- this.button.setAttribute(shaka.ui.Constants.ARIA_LABEL,
202
- this.localization.resolve(LocIds.RESOLUTION));
203
- this.backButton.setAttribute(shaka.ui.Constants.ARIA_LABEL,
204
- this.localization.resolve(LocIds.RESOLUTION));
204
+ this.button.ariaLabel = this.localization.resolve(LocIds.RESOLUTION);
205
+ this.backButton.ariaLabel = this.localization.resolve(LocIds.RESOLUTION);
205
206
  this.backSpan.textContent =
206
207
  this.localization.resolve(LocIds.RESOLUTION);
207
208
  this.nameSpan.textContent =
@@ -230,3 +231,6 @@ shaka.ui.ResolutionSelection.Factory = class {
230
231
 
231
232
  shaka.ui.OverflowMenu.registerElement(
232
233
  'quality', new shaka.ui.ResolutionSelection.Factory());
234
+
235
+ shaka.ui.Controls.registerElement(
236
+ 'quality', new shaka.ui.ResolutionSelection.Factory());
@@ -7,7 +7,6 @@
7
7
 
8
8
  goog.provide('shaka.ui.RewindButton');
9
9
 
10
- goog.require('shaka.ui.Constants');
11
10
  goog.require('shaka.ui.Controls');
12
11
  goog.require('shaka.ui.Element');
13
12
  goog.require('shaka.ui.Enums');
@@ -33,11 +32,17 @@ shaka.ui.RewindButton = class extends shaka.ui.Element {
33
32
  this.button_ = shaka.util.Dom.createButton();
34
33
  this.button_.classList.add('material-icons-round');
35
34
  this.button_.classList.add('shaka-rewind-button');
35
+ this.button_.classList.add('shaka-tooltip-status');
36
+ this.button_.setAttribute('shaka-status',
37
+ this.localization.resolve(shaka.ui.Locales.Ids.OFF));
36
38
  this.button_.textContent =
37
39
  shaka.ui.Enums.MaterialDesignIcons.REWIND;
38
40
  this.parent.appendChild(this.button_);
39
41
  this.updateAriaLabel_();
40
42
 
43
+ /** @private {!Array.<number>} */
44
+ this.rewindRates_ = this.controls.getConfig().rewindRates;
45
+
41
46
  this.eventManager.listen(
42
47
  this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
43
48
  this.updateAriaLabel_();
@@ -57,12 +62,12 @@ shaka.ui.RewindButton = class extends shaka.ui.Element {
57
62
  * @private
58
63
  */
59
64
  updateAriaLabel_() {
60
- this.button_.setAttribute(shaka.ui.Constants.ARIA_LABEL,
61
- this.localization.resolve(shaka.ui.Locales.Ids.REWIND));
65
+ this.button_.ariaLabel =
66
+ this.localization.resolve(shaka.ui.Locales.Ids.REWIND);
62
67
  }
63
68
 
64
69
  /**
65
- * Cycles trick play rate between -1, -2, -4, and -8.
70
+ * Cycles trick play rate between the selected rewind rates.
66
71
  * @private
67
72
  */
68
73
  rewind_() {
@@ -71,12 +76,15 @@ shaka.ui.RewindButton = class extends shaka.ui.Element {
71
76
  }
72
77
 
73
78
  const trickPlayRate = this.player.getPlaybackRate();
74
- // Every time the button is clicked, the rate is multiplied by 2,
75
- // unless the rate is at it's slowest (-8), in which case it is
76
- // dropped back to 1.
77
- const newRate = (trickPlayRate > 0 || trickPlayRate < -4) ?
78
- -1 : trickPlayRate * 2;
79
+ const newRateIndex = this.rewindRates_.indexOf(trickPlayRate) + 1;
80
+
81
+ // When the button is clicked, the next rate in this.rewindRates_ is
82
+ // selected. If no more rates are available, the first one is set.
83
+ const newRate = (newRateIndex != this.rewindRates_.length) ?
84
+ this.rewindRates_[newRateIndex] : this.rewindRates_[0];
79
85
  this.player.trickPlay(newRate);
86
+
87
+ this.button_.setAttribute('shaka-status', newRate + 'x');
80
88
  }
81
89
  };
82
90
 
package/ui/seek_bar.js CHANGED
@@ -15,7 +15,6 @@ goog.require('shaka.ui.RangeElement');
15
15
  goog.require('shaka.ui.Utils');
16
16
  goog.require('shaka.util.Dom');
17
17
  goog.require('shaka.util.Timer');
18
- goog.requireType('shaka.ads.CuePoint');
19
18
  goog.requireType('shaka.ui.Controls');
20
19
 
21
20
 
@@ -83,7 +82,7 @@ shaka.ui.SeekBar = class extends shaka.ui.RangeElement {
83
82
  */
84
83
  this.wasPlaying_ = false;
85
84
 
86
- /** @private {!Array.<!shaka.ads.CuePoint>} */
85
+ /** @private {!Array.<!shaka.extern.AdCuePoint>} */
87
86
  this.adCuePoints_ = [];
88
87
 
89
88
  this.eventManager.listen(this.localization,
@@ -96,7 +95,9 @@ shaka.ui.SeekBar = class extends shaka.ui.RangeElement {
96
95
 
97
96
  this.eventManager.listen(
98
97
  this.adManager, shaka.ads.AdManager.AD_STARTED, () => {
99
- shaka.ui.Utils.setDisplay(this.container, false);
98
+ if (!this.shouldBeDisplayed_()) {
99
+ shaka.ui.Utils.setDisplay(this.container, false);
100
+ }
100
101
  });
101
102
 
102
103
  this.eventManager.listen(
@@ -367,13 +368,12 @@ shaka.ui.SeekBar = class extends shaka.ui.RangeElement {
367
368
  return false;
368
369
  }
369
370
 
370
- return this.ad == null;
371
+ return this.ad == null || !this.ad.isLinear();
371
372
  }
372
373
 
373
374
  /** @private */
374
375
  updateAriaLabel_() {
375
- this.bar.setAttribute(shaka.ui.Constants.ARIA_LABEL,
376
- this.localization.resolve(shaka.ui.Locales.Ids.SEEK));
376
+ this.bar.ariaLabel = this.localization.resolve(shaka.ui.Locales.Ids.SEEK);
377
377
  }
378
378
  };
379
379
 
@@ -33,6 +33,8 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
33
33
 
34
34
  this.addMenu_();
35
35
 
36
+ this.inOverflowMenu_();
37
+
36
38
  this.eventManager.listen(this.button, 'click', () => {
37
39
  this.onButtonClick_();
38
40
  });
@@ -46,6 +48,7 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
46
48
  addButton_(iconText) {
47
49
  /** @protected {!HTMLButtonElement} */
48
50
  this.button = shaka.util.Dom.createButton();
51
+ this.button.classList.add('shaka-overflow-button');
49
52
 
50
53
  /** @protected {!HTMLElement}*/
51
54
  this.icon = shaka.util.Dom.createHTMLElement('i');
@@ -55,6 +58,7 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
55
58
 
56
59
  const label = shaka.util.Dom.createHTMLElement('label');
57
60
  label.classList.add('shaka-overflow-button-label');
61
+ label.classList.add('shaka-overflow-menu-only');
58
62
 
59
63
  /** @protected {!HTMLElement}*/
60
64
  this.nameSpan = shaka.util.Dom.createHTMLElement('span');
@@ -83,10 +87,13 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
83
87
  this.backButton = shaka.util.Dom.createButton();
84
88
  this.backButton.classList.add('shaka-back-to-overflow-button');
85
89
  this.menu.appendChild(this.backButton);
90
+ this.eventManager.listen(this.backButton, 'click', () => {
91
+ this.controls.hideSettingsMenus();
92
+ });
86
93
 
87
94
  const backIcon = shaka.util.Dom.createHTMLElement('i');
88
95
  backIcon.classList.add('material-icons-round');
89
- backIcon.textContent = shaka.ui.Enums.MaterialDesignIcons.BACK;
96
+ backIcon.textContent = shaka.ui.Enums.MaterialDesignIcons.CLOSE;
90
97
  this.backButton.appendChild(backIcon);
91
98
 
92
99
  /** @protected {!HTMLElement}*/
@@ -97,11 +104,36 @@ shaka.ui.SettingsMenu = class extends shaka.ui.Element {
97
104
  controlsContainer.appendChild(this.menu);
98
105
  }
99
106
 
107
+ /** @private */
108
+ inOverflowMenu_() {
109
+ // Initially, submenus are created with a "Close" option. When present
110
+ // inside of the overflow menu, that option must be replaced with a
111
+ // "Back" arrow that returns the user to the main menu.
112
+ if (this.parent.classList.contains('shaka-overflow-menu')) {
113
+ this.backButton.firstChild.textContent =
114
+ shaka.ui.Enums.MaterialDesignIcons.BACK;
115
+
116
+ this.eventManager.listen(this.backButton, 'click', () => {
117
+ shaka.ui.Utils.setDisplay(this.parent, true);
118
+
119
+ /** @type {!HTMLElement} */
120
+ (this.parent.childNodes[0]).focus();
121
+
122
+ // Make sure controls are displayed
123
+ this.controls.computeOpacity();
124
+ });
125
+ }
126
+ }
127
+
100
128
 
101
129
  /** @private */
102
130
  onButtonClick_() {
103
- this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen'));
104
- shaka.ui.Utils.setDisplay(this.menu, true);
105
- shaka.ui.Utils.focusOnTheChosenItem(this.menu);
131
+ if (this.menu.classList.contains('shaka-hidden')) {
132
+ this.controls.dispatchEvent(new shaka.util.FakeEvent('submenuopen'));
133
+ shaka.ui.Utils.setDisplay(this.menu, true);
134
+ shaka.ui.Utils.focusOnTheChosenItem(this.menu);
135
+ } else {
136
+ shaka.ui.Utils.setDisplay(this.menu, false);
137
+ }
106
138
  }
107
139
  };
@@ -28,6 +28,7 @@ shaka.ui.SmallPlayButton = class extends shaka.ui.PlayButton {
28
28
 
29
29
  this.button.classList.add('shaka-small-play-button');
30
30
  this.button.classList.add('material-icons-round');
31
+ this.button.classList.add('shaka-tooltip');
31
32
 
32
33
  this.updateIcon();
33
34
  this.updateAriaLabel();
package/ui/spacer.js CHANGED
@@ -29,7 +29,7 @@ shaka.ui.Spacer = class extends shaka.ui.Element {
29
29
  const div = shaka.util.Dom.createHTMLElement('div');
30
30
  div.classList.add('shaka-spacer');
31
31
  // Make screen readers ignore the spacer
32
- div.setAttribute('aria-hidden', true);
32
+ div.ariaHidden = 'true';
33
33
  this.parent.appendChild(div);
34
34
  }
35
35
  };
@@ -0,0 +1,253 @@
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.StatisticsButton');
9
+
10
+ goog.require('shaka.log');
11
+ goog.require('shaka.ui.ContextMenu');
12
+ goog.require('shaka.ui.Controls');
13
+ goog.require('shaka.ui.Element');
14
+ goog.require('shaka.ui.Enums');
15
+ goog.require('shaka.ui.Locales');
16
+ goog.require('shaka.ui.Localization');
17
+ goog.require('shaka.ui.OverflowMenu');
18
+ goog.require('shaka.ui.Utils');
19
+ goog.require('shaka.util.Dom');
20
+ goog.require('shaka.util.Timer');
21
+ goog.requireType('shaka.ui.Controls');
22
+
23
+
24
+ /**
25
+ * @extends {shaka.ui.Element}
26
+ * @final
27
+ * @export
28
+ */
29
+ shaka.ui.StatisticsButton = class extends shaka.ui.Element {
30
+ /**
31
+ * @param {!HTMLElement} parent
32
+ * @param {!shaka.ui.Controls} controls
33
+ */
34
+ constructor(parent, controls) {
35
+ super(parent, controls);
36
+
37
+ /** @private {!HTMLButtonElement} */
38
+ this.button_ = shaka.util.Dom.createButton();
39
+ this.button_.classList.add('shaka-statistics-button');
40
+
41
+ /** @private {!HTMLElement} */
42
+ this.icon_ = shaka.util.Dom.createHTMLElement('i');
43
+ this.icon_.classList.add('material-icons-round');
44
+ this.icon_.textContent =
45
+ shaka.ui.Enums.MaterialDesignIcons.STATISTICS_ON;
46
+ this.button_.appendChild(this.icon_);
47
+
48
+ const label = shaka.util.Dom.createHTMLElement('label');
49
+ label.classList.add('shaka-overflow-button-label');
50
+
51
+ /** @private {!HTMLElement} */
52
+ this.nameSpan_ = shaka.util.Dom.createHTMLElement('span');
53
+ label.appendChild(this.nameSpan_);
54
+
55
+ /** @private {!HTMLElement} */
56
+ this.stateSpan_ = shaka.util.Dom.createHTMLElement('span');
57
+ this.stateSpan_.classList.add('shaka-current-selection-span');
58
+ label.appendChild(this.stateSpan_);
59
+
60
+ this.button_.appendChild(label);
61
+
62
+ this.parent.appendChild(this.button_);
63
+
64
+ /** @private {!HTMLElement} */
65
+ this.container_ = shaka.util.Dom.createHTMLElement('div');
66
+ this.container_.classList.add('shaka-no-propagation');
67
+ this.container_.classList.add('shaka-show-controls-on-mouse-over');
68
+ this.container_.classList.add('shaka-statistics-container');
69
+ this.container_.classList.add('shaka-hidden');
70
+
71
+ const controlsContainer = this.controls.getControlsContainer();
72
+ controlsContainer.appendChild(this.container_);
73
+
74
+ /** @private {!Array} */
75
+ this.statisticsList_ = [];
76
+
77
+ /** @private {!Array} */
78
+ this.skippedStats_ = ['stateHistory', 'switchHistory'];
79
+
80
+ /** @private {!Object.<string, number>} */
81
+ this.currentStats_ = this.player.getStats();
82
+
83
+ /** @private {!Object.<string, HTMLElement>} */
84
+ this.displayedElements_ = {};
85
+
86
+
87
+ const parsePx = (name) => {
88
+ return this.currentStats_[name] + ' (px)';
89
+ };
90
+
91
+ const parsePercent = (name) => {
92
+ return this.currentStats_[name] + ' (%)';
93
+ };
94
+
95
+ const parseFrames = (name) => {
96
+ return this.currentStats_[name] + ' (frames)';
97
+ };
98
+
99
+ const parseSeconds = (name) => {
100
+ return this.currentStats_[name].toFixed(2) + ' (s)';
101
+ };
102
+
103
+ const parseBits = (name) => {
104
+ return Math.round(this.currentStats_[name] / 1000) + ' (kbits/s)';
105
+ };
106
+
107
+ const parseTime = (name) => {
108
+ return shaka.ui.Utils.buildTimeString(
109
+ this.currentStats_[name], false) + ' (m)';
110
+ };
111
+
112
+ /** @private {!Object.<string, function(string):string>} */
113
+ this.parseFrom_ = {
114
+ 'width': parsePx,
115
+ 'height': parsePx,
116
+ 'completionPercent': parsePercent,
117
+ 'bufferingTime': parseSeconds,
118
+ 'drmTimeSeconds': parseSeconds,
119
+ 'licenseTime': parseSeconds,
120
+ 'liveLatency': parseSeconds,
121
+ 'loadLatency': parseSeconds,
122
+ 'manifestTimeSeconds': parseSeconds,
123
+ 'estimatedBandwidth': parseBits,
124
+ 'streamBandwidth': parseBits,
125
+ 'maxSegmentDuration': parseTime,
126
+ 'pauseTime': parseTime,
127
+ 'playTime': parseTime,
128
+ 'corruptedFrames': parseFrames,
129
+ 'decodedFrames': parseFrames,
130
+ 'droppedFrames': parseFrames,
131
+ };
132
+
133
+ /** @private {shaka.util.Timer} */
134
+ this.timer_ = new shaka.util.Timer(() => {
135
+ this.onTimerTick_();
136
+ });
137
+
138
+ this.updateLocalizedStrings_();
139
+
140
+ this.loadContainer_();
141
+
142
+ this.eventManager.listen(
143
+ this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
144
+ this.updateLocalizedStrings_();
145
+ });
146
+
147
+ this.eventManager.listen(
148
+ this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
149
+ this.updateLocalizedStrings_();
150
+ });
151
+
152
+ this.eventManager.listen(this.button_, 'click', () => {
153
+ this.onClick_();
154
+ this.updateLocalizedStrings_();
155
+ });
156
+ }
157
+
158
+ /** @private */
159
+ onClick_() {
160
+ shaka.ui.Utils.setDisplay(this.parent, false);
161
+
162
+ if (this.container_.classList.contains('shaka-hidden')) {
163
+ this.icon_.textContent =
164
+ shaka.ui.Enums.MaterialDesignIcons.STATISTICS_OFF;
165
+ this.timer_.tickEvery(0.1);
166
+ shaka.ui.Utils.setDisplay(this.container_, true);
167
+ } else {
168
+ this.icon_.textContent =
169
+ shaka.ui.Enums.MaterialDesignIcons.STATISTICS_ON;
170
+ this.timer_.stop();
171
+ shaka.ui.Utils.setDisplay(this.container_, false);
172
+ }
173
+ }
174
+
175
+ /** @private */
176
+ updateLocalizedStrings_() {
177
+ const LocIds = shaka.ui.Locales.Ids;
178
+
179
+ this.nameSpan_.textContent =
180
+ this.localization.resolve(LocIds.STATISTICS);
181
+
182
+ this.button_.ariaLabel = this.localization.resolve(LocIds.STATISTICS);
183
+
184
+ const labelText = this.container_.classList.contains('shaka-hidden') ?
185
+ LocIds.OFF : LocIds.ON;
186
+ this.stateSpan_.textContent = this.localization.resolve(labelText);
187
+ }
188
+
189
+ /** @private */
190
+ generateComponent_(name) {
191
+ const section = shaka.util.Dom.createHTMLElement('div');
192
+
193
+ const label = shaka.util.Dom.createHTMLElement('label');
194
+ label.textContent = name + ':';
195
+ section.appendChild(label);
196
+
197
+ const value = shaka.util.Dom.createHTMLElement('span');
198
+ value.textContent = this.parseFrom_[name](name);
199
+ section.appendChild(value);
200
+
201
+ this.displayedElements_[name] = value;
202
+
203
+ return section;
204
+ }
205
+
206
+ /** @private */
207
+ loadContainer_() {
208
+ for (const name of this.controls.getConfig().statisticsList) {
209
+ if (name in this.currentStats_ && !this.skippedStats_.includes(name)) {
210
+ this.container_.appendChild(this.generateComponent_(name));
211
+ this.statisticsList_.push(name);
212
+ } else {
213
+ shaka.log.alwaysWarn('Unrecognized statistic element:', name);
214
+ }
215
+ }
216
+ }
217
+
218
+ /** @private */
219
+ onTimerTick_() {
220
+ this.currentStats_ = this.player.getStats();
221
+
222
+ for (const name of this.statisticsList_) {
223
+ this.displayedElements_[name].textContent =
224
+ this.parseFrom_[name](name);
225
+ }
226
+ }
227
+
228
+ /** @override */
229
+ release() {
230
+ this.timer_.stop();
231
+ this.timer_ = null;
232
+ super.release();
233
+ }
234
+ };
235
+
236
+
237
+ /**
238
+ * @implements {shaka.extern.IUIElement.Factory}
239
+ * @final
240
+ */
241
+ shaka.ui.StatisticsButton.Factory = class {
242
+ /** @override */
243
+ create(rootElement, controls) {
244
+ return new shaka.ui.StatisticsButton(rootElement, controls);
245
+ }
246
+ };
247
+
248
+
249
+ shaka.ui.OverflowMenu.registerElement(
250
+ 'statistics', new shaka.ui.StatisticsButton.Factory());
251
+
252
+ shaka.ui.ContextMenu.registerElement(
253
+ 'statistics', new shaka.ui.StatisticsButton.Factory());
@@ -7,7 +7,7 @@
7
7
 
8
8
  goog.provide('shaka.ui.TextSelection');
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');
@@ -35,12 +35,13 @@ shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
35
35
  controls, shaka.ui.Enums.MaterialDesignIcons.CLOSED_CAPTIONS);
36
36
 
37
37
  this.button.classList.add('shaka-caption-button');
38
+ this.button.classList.add('shaka-tooltip-status');
38
39
  this.menu.classList.add('shaka-text-languages');
39
40
 
40
41
  if (this.player && this.player.isTextTrackVisible()) {
41
- this.button.setAttribute('aria-pressed', 'true');
42
+ this.button.ariaPressed = 'true';
42
43
  } else {
43
- this.button.setAttribute('aria-pressed', 'false');
44
+ this.button.ariaPressed = 'false';
44
45
  }
45
46
 
46
47
  this.addOffOption_();
@@ -92,7 +93,7 @@ shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
92
93
  */
93
94
  addOffOption_() {
94
95
  const off = shaka.util.Dom.createButton();
95
- off.setAttribute('aria-selected', 'true');
96
+ off.ariaSelected = 'true';
96
97
  this.menu.appendChild(off);
97
98
 
98
99
  off.appendChild(shaka.ui.Utils.checkmarkIcon());
@@ -108,13 +109,13 @@ shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
108
109
  /** @private */
109
110
  onCaptionStateChange_() {
110
111
  if (this.player.isTextTrackVisible()) {
111
- this.icon.classList.add('shaka-captions-on');
112
- this.icon.classList.remove('shaka-captions-off');
113
- this.button.setAttribute('aria-pressed', 'true');
112
+ this.icon.textContent =
113
+ shaka.ui.Enums.MaterialDesignIcons.CLOSED_CAPTIONS_OFF;
114
+ this.button.ariaPressed = 'true';
114
115
  } else {
115
- this.icon.classList.add('shaka-captions-off');
116
- this.icon.classList.remove('shaka-captions-on');
117
- this.button.setAttribute('aria-pressed', 'false');
116
+ this.icon.textContent =
117
+ shaka.ui.Enums.MaterialDesignIcons.CLOSED_CAPTIONS;
118
+ this.button.ariaPressed = 'false';
118
119
  }
119
120
 
120
121
  this.controls.dispatchEvent(
@@ -148,13 +149,15 @@ shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
148
149
  this.menu.appendChild(offButton);
149
150
 
150
151
  if (!this.player.isTextTrackVisible()) {
151
- offButton.setAttribute('aria-selected', 'true');
152
+ offButton.ariaSelected = 'true';
152
153
  offButton.appendChild(shaka.ui.Utils.checkmarkIcon());
153
154
  this.captionsOffSpan_.classList.add('shaka-chosen-item');
154
155
  this.currentSelection.textContent =
155
156
  this.localization.resolve(shaka.ui.Locales.Ids.OFF);
156
157
  }
157
158
 
159
+ this.button.setAttribute('shaka-status', this.currentSelection.textContent);
160
+
158
161
  shaka.ui.Utils.focusOnTheChosenItem(this.menu);
159
162
 
160
163
  this.controls.dispatchEvent(
@@ -184,10 +187,8 @@ shaka.ui.TextSelection = class extends shaka.ui.SettingsMenu {
184
187
  updateLocalizedStrings_() {
185
188
  const LocIds = shaka.ui.Locales.Ids;
186
189
 
187
- this.button.setAttribute(shaka.ui.Constants.ARIA_LABEL,
188
- this.localization.resolve(LocIds.CAPTIONS));
189
- this.backButton.setAttribute(shaka.ui.Constants.ARIA_LABEL,
190
- this.localization.resolve(LocIds.BACK));
190
+ this.button.ariaLabel = this.localization.resolve(LocIds.CAPTIONS);
191
+ this.backButton.ariaLabel = this.localization.resolve(LocIds.BACK);
191
192
  this.nameSpan.textContent =
192
193
  this.localization.resolve(LocIds.CAPTIONS);
193
194
  this.backSpan.textContent =
@@ -219,3 +220,6 @@ shaka.ui.TextSelection.Factory = class {
219
220
 
220
221
  shaka.ui.OverflowMenu.registerElement(
221
222
  'captions', new shaka.ui.TextSelection.Factory());
223
+
224
+ shaka.ui.Controls.registerElement(
225
+ 'captions', new shaka.ui.TextSelection.Factory());
package/ui/ui.js CHANGED
@@ -187,8 +187,36 @@ shaka.ui.Overlay = class {
187
187
  'cast',
188
188
  'playback_rate',
189
189
  ],
190
+ statisticsList: [
191
+ 'width',
192
+ 'height',
193
+ 'corruptedFrames',
194
+ 'decodedFrames',
195
+ 'droppedFrames',
196
+ 'drmTimeSeconds',
197
+ 'licenseTime',
198
+ 'liveLatency',
199
+ 'loadLatency',
200
+ 'bufferingTime',
201
+ 'manifestTimeSeconds',
202
+ 'estimatedBandwidth',
203
+ 'streamBandwidth',
204
+ 'maxSegmentDuration',
205
+ 'pauseTime',
206
+ 'playTime',
207
+ 'completionPercent',
208
+ ],
209
+ contextMenuElements: [
210
+ 'loop',
211
+ 'picture_in_picture',
212
+ 'statistics',
213
+ ],
214
+ playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 1.75, 2],
215
+ fastForwardRates: [2, 4, 8, 1],
216
+ rewindRates: [-1, -2, -4, -8],
190
217
  addSeekBar: true,
191
218
  addBigPlayButton: false,
219
+ customContextMenu: false,
192
220
  castReceiverAppId: '',
193
221
  clearBufferOnQualityChange: true,
194
222
  showUnbufferedStart: false,
@@ -205,9 +233,11 @@ shaka.ui.Overlay = class {
205
233
  trackLabelFormat: shaka.ui.Overlay.TrackLabelFormat.LANGUAGE,
206
234
  fadeDelay: 0,
207
235
  doubleClickForFullscreen: true,
236
+ singleClickForPlayAndPause: true,
208
237
  enableKeyboardPlaybackControls: true,
209
238
  enableFullscreenOnRotation: true,
210
239
  forceLandscapeOnFullscreen: true,
240
+ enableTooltips: false,
211
241
  };
212
242
 
213
243
  // Check AirPlay support
package/ui/ui_utils.js CHANGED
@@ -70,7 +70,7 @@ shaka.ui.Utils = class {
70
70
  icon.classList.add('shaka-chosen-item');
71
71
  icon.textContent = shaka.ui.Enums.MaterialDesignIcons.CHECKMARK;
72
72
  // Screen reader should ignore icon text.
73
- icon.setAttribute('aria-hidden', 'true');
73
+ icon.ariaHidden = 'true';
74
74
  return icon;
75
75
  }
76
76