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