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. /** @private {!Object.<string, function(string):string>} */
  93. this.parseFrom_ = {
  94. 'width': parsePx,
  95. 'height': parsePx,
  96. 'completionPercent': parsePercent,
  97. 'bufferingTime': parseSeconds,
  98. 'drmTimeSeconds': parseSeconds,
  99. 'licenseTime': parseSeconds,
  100. 'liveLatency': parseSeconds,
  101. 'loadLatency': parseSeconds,
  102. 'manifestTimeSeconds': parseSeconds,
  103. 'estimatedBandwidth': parseBits,
  104. 'streamBandwidth': parseBits,
  105. 'maxSegmentDuration': parseTime,
  106. 'pauseTime': parseTime,
  107. 'playTime': parseTime,
  108. 'corruptedFrames': parseFrames,
  109. 'decodedFrames': parseFrames,
  110. 'droppedFrames': parseFrames,
  111. 'stallsDetected': parseStalls,
  112. 'gapsJumped': parseGaps,
  113. };
  114. /** @private {shaka.util.Timer} */
  115. this.timer_ = new shaka.util.Timer(() => {
  116. this.onTimerTick_();
  117. });
  118. this.updateLocalizedStrings_();
  119. this.loadContainer_();
  120. this.eventManager.listen(
  121. this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
  122. this.updateLocalizedStrings_();
  123. });
  124. this.eventManager.listen(
  125. this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
  126. this.updateLocalizedStrings_();
  127. });
  128. this.eventManager.listen(this.button_, 'click', () => {
  129. this.onClick_();
  130. this.updateLocalizedStrings_();
  131. });
  132. }
  133. /** @private */
  134. onClick_() {
  135. shaka.ui.Utils.setDisplay(this.parent, false);
  136. if (this.container_.classList.contains('shaka-hidden')) {
  137. this.icon_.textContent =
  138. shaka.ui.Enums.MaterialDesignIcons.STATISTICS_OFF;
  139. this.timer_.tickEvery(0.1);
  140. shaka.ui.Utils.setDisplay(this.container_, true);
  141. } else {
  142. this.icon_.textContent =
  143. shaka.ui.Enums.MaterialDesignIcons.STATISTICS_ON;
  144. this.timer_.stop();
  145. shaka.ui.Utils.setDisplay(this.container_, false);
  146. }
  147. }
  148. /** @private */
  149. updateLocalizedStrings_() {
  150. const LocIds = shaka.ui.Locales.Ids;
  151. this.nameSpan_.textContent =
  152. this.localization.resolve(LocIds.STATISTICS);
  153. this.button_.ariaLabel = this.localization.resolve(LocIds.STATISTICS);
  154. const labelText = this.container_.classList.contains('shaka-hidden') ?
  155. LocIds.OFF : LocIds.ON;
  156. this.stateSpan_.textContent = this.localization.resolve(labelText);
  157. }
  158. /** @private */
  159. generateComponent_(name) {
  160. const section = shaka.util.Dom.createHTMLElement('div');
  161. const label = shaka.util.Dom.createHTMLElement('label');
  162. label.textContent = name + ':';
  163. section.appendChild(label);
  164. const value = shaka.util.Dom.createHTMLElement('span');
  165. value.textContent = this.parseFrom_[name](name);
  166. section.appendChild(value);
  167. this.displayedElements_[name] = value;
  168. return section;
  169. }
  170. /** @private */
  171. loadContainer_() {
  172. for (const name of this.controls.getConfig().statisticsList) {
  173. if (name in this.currentStats_ && !this.skippedStats_.includes(name)) {
  174. this.container_.appendChild(this.generateComponent_(name));
  175. this.statisticsList_.push(name);
  176. } else {
  177. shaka.log.alwaysWarn('Unrecognized statistic element:', name);
  178. }
  179. }
  180. }
  181. /** @private */
  182. onTimerTick_() {
  183. this.currentStats_ = this.player.getStats();
  184. for (const name of this.statisticsList_) {
  185. this.displayedElements_[name].textContent =
  186. this.parseFrom_[name](name);
  187. }
  188. }
  189. /** @override */
  190. release() {
  191. this.timer_.stop();
  192. this.timer_ = null;
  193. super.release();
  194. }
  195. };
  196. /**
  197. * @implements {shaka.extern.IUIElement.Factory}
  198. * @final
  199. */
  200. shaka.ui.StatisticsButton.Factory = class {
  201. /** @override */
  202. create(rootElement, controls) {
  203. return new shaka.ui.StatisticsButton(rootElement, controls);
  204. }
  205. };
  206. shaka.ui.OverflowMenu.registerElement(
  207. 'statistics', new shaka.ui.StatisticsButton.Factory());
  208. shaka.ui.ContextMenu.registerElement(
  209. 'statistics', new shaka.ui.StatisticsButton.Factory());