Source: ui/resolution_selection.js

  1. /*! @license
  2. * Shaka Player
  3. * Copyright 2016 Google LLC
  4. * SPDX-License-Identifier: Apache-2.0
  5. */
  6. goog.provide('shaka.ui.ResolutionSelection');
  7. goog.require('goog.asserts');
  8. goog.require('shaka.ui.Controls');
  9. goog.require('shaka.ui.Enums');
  10. goog.require('shaka.ui.Locales');
  11. goog.require('shaka.ui.Localization');
  12. goog.require('shaka.ui.OverflowMenu');
  13. goog.require('shaka.ui.SettingsMenu');
  14. goog.require('shaka.ui.Utils');
  15. goog.require('shaka.util.Dom');
  16. goog.require('shaka.util.FakeEvent');
  17. goog.requireType('shaka.ui.Controls');
  18. /**
  19. * @extends {shaka.ui.SettingsMenu}
  20. * @final
  21. * @export
  22. */
  23. shaka.ui.ResolutionSelection = class extends shaka.ui.SettingsMenu {
  24. /**
  25. * @param {!HTMLElement} parent
  26. * @param {!shaka.ui.Controls} controls
  27. */
  28. constructor(parent, controls) {
  29. super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.RESOLUTION);
  30. this.button.classList.add('shaka-resolution-button');
  31. this.button.classList.add('shaka-tooltip-status');
  32. this.menu.classList.add('shaka-resolutions');
  33. this.eventManager.listen(
  34. this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
  35. this.updateLocalizedStrings_();
  36. });
  37. this.eventManager.listen(
  38. this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
  39. this.updateLocalizedStrings_();
  40. });
  41. this.eventManager.listen(this.player, 'variantchanged', () => {
  42. this.updateResolutionSelection_();
  43. });
  44. this.eventManager.listen(this.player, 'trackschanged', () => {
  45. this.updateResolutionSelection_();
  46. });
  47. this.eventManager.listen(this.player, 'abrstatuschanged', () => {
  48. this.updateResolutionSelection_();
  49. });
  50. this.updateResolutionSelection_();
  51. // Set up all the strings in the user's preferred language.
  52. this.updateLocalizedStrings_();
  53. }
  54. /** @private */
  55. updateResolutionSelection_() {
  56. /** @type {!Array.<shaka.extern.Track>} */
  57. let tracks = this.player.getVariantTracks();
  58. // Hide resolution menu and button for audio-only content and src= content
  59. // without resolution information.
  60. // TODO: for audio-only content, this should be a bitrate selection menu
  61. // instead.
  62. if (tracks.length && !tracks[0].height) {
  63. shaka.ui.Utils.setDisplay(this.menu, false);
  64. shaka.ui.Utils.setDisplay(this.button, false);
  65. return;
  66. }
  67. // Otherwise, restore it.
  68. shaka.ui.Utils.setDisplay(this.button, true);
  69. tracks.sort((t1, t2) => {
  70. // We have already screened for audio-only content, but the compiler
  71. // doesn't know that.
  72. goog.asserts.assert(t1.height != null, 'Null height');
  73. goog.asserts.assert(t2.height != null, 'Null height');
  74. return t2.height - t1.height;
  75. });
  76. // If there is a selected variant track, then we filter out any tracks in
  77. // a different language. Then we use those remaining tracks to display the
  78. // available resolutions.
  79. const selectedTrack = tracks.find((track) => track.active);
  80. if (selectedTrack) {
  81. // Filter by current audio language and channel count.
  82. tracks = tracks.filter(
  83. (track) => track.language == selectedTrack.language &&
  84. track.channelsCount == selectedTrack.channelsCount);
  85. }
  86. // Remove duplicate entries with the same height. This can happen if
  87. // we have multiple resolutions of audio. Pick an arbitrary one.
  88. tracks = tracks.filter((track, idx) => {
  89. // Keep the first one with the same height.
  90. const otherIdx = tracks.findIndex((t) => t.height == track.height);
  91. return otherIdx == idx;
  92. });
  93. // Remove old shaka-resolutions
  94. // 1. Save the back to menu button
  95. const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
  96. this.menu, 'shaka-back-to-overflow-button');
  97. // 2. Remove everything
  98. shaka.util.Dom.removeAllChildren(this.menu);
  99. // 3. Add the backTo Menu button back
  100. this.menu.appendChild(backButton);
  101. const abrEnabled = this.player.getConfiguration().abr.enabled;
  102. // Add new ones
  103. for (const track of tracks) {
  104. const button = shaka.util.Dom.createButton();
  105. button.classList.add('explicit-resolution');
  106. this.eventManager.listen(button, 'click',
  107. () => this.onTrackSelected_(track));
  108. const span = shaka.util.Dom.createHTMLElement('span');
  109. span.textContent = track.height + 'p';
  110. button.appendChild(span);
  111. if (!abrEnabled && track == selectedTrack) {
  112. // If abr is disabled, mark the selected track's resolution.
  113. button.ariaSelected = 'true';
  114. button.appendChild(shaka.ui.Utils.checkmarkIcon());
  115. span.classList.add('shaka-chosen-item');
  116. this.currentSelection.textContent = span.textContent;
  117. }
  118. this.menu.appendChild(button);
  119. }
  120. // Add the Auto button
  121. const autoButton = shaka.util.Dom.createButton();
  122. autoButton.classList.add('shaka-enable-abr-button');
  123. this.eventManager.listen(autoButton, 'click', () => {
  124. const config = {abr: {enabled: true}};
  125. this.player.configure(config);
  126. this.updateResolutionSelection_();
  127. });
  128. /** @private {!HTMLElement}*/
  129. this.abrOnSpan_ = shaka.util.Dom.createHTMLElement('span');
  130. this.abrOnSpan_.classList.add('shaka-auto-span');
  131. this.abrOnSpan_.textContent =
  132. this.localization.resolve(shaka.ui.Locales.Ids.AUTO_QUALITY);
  133. autoButton.appendChild(this.abrOnSpan_);
  134. // If abr is enabled reflect it by marking 'Auto' as selected.
  135. if (abrEnabled) {
  136. autoButton.ariaSelected = 'true';
  137. autoButton.appendChild(shaka.ui.Utils.checkmarkIcon());
  138. this.abrOnSpan_.classList.add('shaka-chosen-item');
  139. this.currentSelection.textContent =
  140. this.localization.resolve(shaka.ui.Locales.Ids.AUTO_QUALITY);
  141. }
  142. this.button.setAttribute('shaka-status', this.currentSelection.textContent);
  143. this.menu.appendChild(autoButton);
  144. shaka.ui.Utils.focusOnTheChosenItem(this.menu);
  145. this.controls.dispatchEvent(
  146. new shaka.util.FakeEvent('resolutionselectionupdated'));
  147. }
  148. /**
  149. * @param {!shaka.extern.Track} track
  150. * @private
  151. */
  152. onTrackSelected_(track) {
  153. // Disable abr manager before changing tracks.
  154. const config = {abr: {enabled: false}};
  155. this.player.configure(config);
  156. const clearBuffer = this.controls.getConfig().clearBufferOnQualityChange;
  157. this.player.selectVariantTrack(track, clearBuffer);
  158. }
  159. /**
  160. * @private
  161. */
  162. updateLocalizedStrings_() {
  163. const LocIds = shaka.ui.Locales.Ids;
  164. this.button.ariaLabel = this.localization.resolve(LocIds.RESOLUTION);
  165. this.backButton.ariaLabel = this.localization.resolve(LocIds.RESOLUTION);
  166. this.backSpan.textContent =
  167. this.localization.resolve(LocIds.RESOLUTION);
  168. this.nameSpan.textContent =
  169. this.localization.resolve(LocIds.RESOLUTION);
  170. this.abrOnSpan_.textContent =
  171. this.localization.resolve(LocIds.AUTO_QUALITY);
  172. if (this.player.getConfiguration().abr.enabled) {
  173. this.currentSelection.textContent =
  174. this.localization.resolve(shaka.ui.Locales.Ids.AUTO_QUALITY);
  175. }
  176. }
  177. };
  178. /**
  179. * @implements {shaka.extern.IUIElement.Factory}
  180. * @final
  181. */
  182. shaka.ui.ResolutionSelection.Factory = class {
  183. /** @override */
  184. create(rootElement, controls) {
  185. return new shaka.ui.ResolutionSelection(rootElement, controls);
  186. }
  187. };
  188. shaka.ui.OverflowMenu.registerElement(
  189. 'quality', new shaka.ui.ResolutionSelection.Factory());
  190. shaka.ui.Controls.registerElement(
  191. 'quality', new shaka.ui.ResolutionSelection.Factory());