Source: ui/statistics_button.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.ui.StatisticsButton');
  7. goog.require('shaka.log');
  8. goog.require('shaka.ui.ContextMenu');
  9. goog.require('shaka.ui.Controls');
  10. goog.require('shaka.ui.Element');
  11. goog.require('shaka.ui.Enums');
  12. goog.require('shaka.ui.Locales');
  13. goog.require('shaka.ui.Localization');
  14. goog.require('shaka.ui.OverflowMenu');
  15. goog.require('shaka.ui.Utils');
  16. goog.require('shaka.util.Dom');
  17. goog.require('shaka.util.Timer');
  18. goog.requireType('shaka.ui.Controls');
  19. /**
  20. * @extends {shaka.ui.Element}
  21. * @final
  22. * @export
  23. */
  24. shaka.ui.StatisticsButton = class extends shaka.ui.Element {
  25. /**
  26. * @param {!HTMLElement} parent
  27. * @param {!shaka.ui.Controls} controls
  28. */
  29. constructor(parent, controls) {
  30. super(parent, controls);
  31. /** @private {!HTMLButtonElement} */
  32. this.button_ = shaka.util.Dom.createButton();
  33. this.button_.classList.add('shaka-statistics-button');
  34. /** @private {!HTMLElement} */
  35. this.icon_ = shaka.util.Dom.createHTMLElement('i');
  36. this.icon_.classList.add('material-icons-round');
  37. this.icon_.textContent =
  38. shaka.ui.Enums.MaterialDesignIcons.STATISTICS_ON;
  39. this.button_.appendChild(this.icon_);
  40. const label = shaka.util.Dom.createHTMLElement('label');
  41. label.classList.add('shaka-overflow-button-label');
  42. /** @private {!HTMLElement} */
  43. this.nameSpan_ = shaka.util.Dom.createHTMLElement('span');
  44. label.appendChild(this.nameSpan_);
  45. /** @private {!HTMLElement} */
  46. this.stateSpan_ = shaka.util.Dom.createHTMLElement('span');
  47. this.stateSpan_.classList.add('shaka-current-selection-span');
  48. label.appendChild(this.stateSpan_);
  49. this.button_.appendChild(label);
  50. this.parent.appendChild(this.button_);
  51. /** @private {!HTMLElement} */
  52. this.container_ = shaka.util.Dom.createHTMLElement('div');
  53. this.container_.classList.add('shaka-no-propagation');
  54. this.container_.classList.add('shaka-show-controls-on-mouse-over');
  55. this.container_.classList.add('shaka-statistics-container');
  56. this.container_.classList.add('shaka-hidden');
  57. const controlsContainer = this.controls.getControlsContainer();
  58. controlsContainer.appendChild(this.container_);
  59. /** @private {!Array} */
  60. this.statisticsList_ = [];
  61. /** @private {!Array} */
  62. this.skippedStats_ = ['stateHistory', 'switchHistory'];
  63. /** @private {!Object.<string, number>} */
  64. this.currentStats_ = this.player.getStats();
  65. /** @private {!Object.<string, HTMLElement>} */
  66. this.displayedElements_ = {};
  67. const parsePx = (name) => {
  68. return this.currentStats_[name] + ' (px)';
  69. };
  70. const parsePercent = (name) => {
  71. return this.currentStats_[name] + ' (%)';
  72. };
  73. const parseFrames = (name) => {
  74. return this.currentStats_[name] + ' (frames)';
  75. };
  76. const parseSeconds = (name) => {
  77. return this.currentStats_[name].toFixed(2) + ' (s)';
  78. };
  79. const parseBits = (name) => {
  80. return Math.round(this.currentStats_[name] / 1000) + ' (kbits/s)';
  81. };
  82. const parseTime = (name) => {
  83. return shaka.ui.Utils.buildTimeString(
  84. this.currentStats_[name], false) + ' (m)';
  85. };
  86. const parseGaps = (name) => {
  87. return this.currentStats_[name] + ' (gaps)';
  88. };
  89. const parseStalls = (name) => {
  90. return this.currentStats_[name] + ' (stalls)';
  91. };
  92. const parseBytesDownloaded = (name) => {
  93. const bytes = parseInt(this.currentStats_[name], 10);
  94. if (bytes > 1e6) {
  95. return (bytes / 1e6).toFixed(2) + 'MB';
  96. } else if (bytes > 1e3) {
  97. return (bytes / 1e3).toFixed(2) + 'KB';
  98. } else {
  99. return bytes + 'B';
  100. }
  101. };
  102. /** @private {!Object.<string, function(string):string>} */
  103. this.parseFrom_ = {
  104. 'width': parsePx,
  105. 'height': parsePx,
  106. 'completionPercent': parsePercent,
  107. 'bufferingTime': parseSeconds,
  108. 'drmTimeSeconds': parseSeconds,
  109. 'licenseTime': parseSeconds,
  110. 'liveLatency': parseSeconds,
  111. 'loadLatency': parseSeconds,
  112. 'manifestTimeSeconds': parseSeconds,
  113. 'estimatedBandwidth': parseBits,
  114. 'streamBandwidth': parseBits,
  115. 'maxSegmentDuration': parseTime,
  116. 'pauseTime': parseTime,
  117. 'playTime': parseTime,
  118. 'corruptedFrames': parseFrames,
  119. 'decodedFrames': parseFrames,
  120. 'droppedFrames': parseFrames,
  121. 'stallsDetected': parseStalls,
  122. 'gapsJumped': parseGaps,
  123. 'bytesDownloaded': parseBytesDownloaded,
  124. };
  125. /** @private {shaka.util.Timer} */
  126. this.timer_ = new shaka.util.Timer(() => {
  127. this.onTimerTick_();
  128. });
  129. this.updateLocalizedStrings_();
  130. this.loadContainer_();
  131. this.eventManager.listen(
  132. this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
  133. this.updateLocalizedStrings_();
  134. });
  135. this.eventManager.listen(
  136. this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
  137. this.updateLocalizedStrings_();
  138. });
  139. this.eventManager.listen(this.button_, 'click', () => {
  140. this.onClick_();
  141. this.updateLocalizedStrings_();
  142. });
  143. }
  144. /** @private */
  145. onClick_() {
  146. shaka.ui.Utils.setDisplay(this.parent, false);
  147. if (this.container_.classList.contains('shaka-hidden')) {
  148. this.icon_.textContent =
  149. shaka.ui.Enums.MaterialDesignIcons.STATISTICS_OFF;
  150. this.timer_.tickEvery(0.1);
  151. shaka.ui.Utils.setDisplay(this.container_, true);
  152. } else {
  153. this.icon_.textContent =
  154. shaka.ui.Enums.MaterialDesignIcons.STATISTICS_ON;
  155. this.timer_.stop();
  156. shaka.ui.Utils.setDisplay(this.container_, false);
  157. }
  158. }
  159. /** @private */
  160. updateLocalizedStrings_() {
  161. const LocIds = shaka.ui.Locales.Ids;
  162. this.nameSpan_.textContent =
  163. this.localization.resolve(LocIds.STATISTICS);
  164. this.button_.ariaLabel = this.localization.resolve(LocIds.STATISTICS);
  165. const labelText = this.container_.classList.contains('shaka-hidden') ?
  166. LocIds.OFF : LocIds.ON;
  167. this.stateSpan_.textContent = this.localization.resolve(labelText);
  168. }
  169. /** @private */
  170. generateComponent_(name) {
  171. const section = shaka.util.Dom.createHTMLElement('div');
  172. const label = shaka.util.Dom.createHTMLElement('label');
  173. label.textContent = name + ':';
  174. section.appendChild(label);
  175. const value = shaka.util.Dom.createHTMLElement('span');
  176. value.textContent = this.parseFrom_[name](name);
  177. section.appendChild(value);
  178. this.displayedElements_[name] = value;
  179. return section;
  180. }
  181. /** @private */
  182. loadContainer_() {
  183. for (const name of this.controls.getConfig().statisticsList) {
  184. if (name in this.currentStats_ && !this.skippedStats_.includes(name)) {
  185. const element = this.generateComponent_(name);
  186. this.container_.appendChild(element);
  187. this.statisticsList_.push(name);
  188. shaka.ui.Utils.setDisplay(element.parentElement,
  189. !isNaN(this.currentStats_[name]));
  190. } else {
  191. shaka.log.alwaysWarn('Unrecognized statistic element:', name);
  192. }
  193. }
  194. }
  195. /** @private */
  196. onTimerTick_() {
  197. this.currentStats_ = this.player.getStats();
  198. for (const name of this.statisticsList_) {
  199. const element = this.displayedElements_[name];
  200. element.textContent = this.parseFrom_[name](name);
  201. if (element && element.parentElement) {
  202. shaka.ui.Utils.setDisplay(element.parentElement,
  203. !isNaN(this.currentStats_[name]));
  204. }
  205. }
  206. }
  207. /** @override */
  208. release() {
  209. this.timer_.stop();
  210. this.timer_ = null;
  211. super.release();
  212. }
  213. };
  214. /**
  215. * @implements {shaka.extern.IUIElement.Factory}
  216. * @final
  217. */
  218. shaka.ui.StatisticsButton.Factory = class {
  219. /** @override */
  220. create(rootElement, controls) {
  221. return new shaka.ui.StatisticsButton(rootElement, controls);
  222. }
  223. };
  224. shaka.ui.OverflowMenu.registerElement(
  225. 'statistics', new shaka.ui.StatisticsButton.Factory());
  226. shaka.ui.ContextMenu.registerElement(
  227. 'statistics', new shaka.ui.StatisticsButton.Factory());