Source: ui/chapter_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.ChapterSelection');
  7. goog.require('shaka.ui.Controls');
  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.requireType('shaka.ui.Controls');
  16. /**
  17. * @extends {shaka.ui.SettingsMenu}
  18. * @final
  19. * @export
  20. */
  21. shaka.ui.ChapterSelection = class extends shaka.ui.SettingsMenu {
  22. /**
  23. * @param {!HTMLElement} parent
  24. * @param {!shaka.ui.Controls} controls
  25. */
  26. constructor(parent, controls) {
  27. super(parent, controls, shaka.ui.Enums.MaterialDesignIcons.CHAPTER);
  28. this.button.classList.add('shaka-chapter-button');
  29. this.menu.classList.add('shaka-chapters');
  30. this.button.classList.add('shaka-tooltip-status');
  31. /** @type {!Array<shaka.extern.Chapter>} */
  32. this.chapters_ = [];
  33. this.chaptersLanguage_ = 'und';
  34. this.eventManager.listen(
  35. this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
  36. this.updateLocalizedStrings_();
  37. this.updateChapters_();
  38. });
  39. this.eventManager.listen(
  40. this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
  41. this.updateLocalizedStrings_();
  42. this.updateChapters_();
  43. });
  44. this.eventManager.listen(this.player, 'unloading', () => {
  45. this.deletePreviousChapters_();
  46. this.chaptersLanguage_ = 'und';
  47. this.chapters_ = [];
  48. });
  49. this.eventManager.listen(this.player, 'trackschanged', () => {
  50. this.updateChapters_();
  51. });
  52. // Set up all the strings in the user's preferred language.
  53. this.updateLocalizedStrings_();
  54. this.updateChapters_();
  55. }
  56. /**
  57. * @private
  58. */
  59. updateLocalizedStrings_() {
  60. const LocIds = shaka.ui.Locales.Ids;
  61. this.backButton.ariaLabel = this.localization.resolve(LocIds.BACK);
  62. this.button.ariaLabel = this.localization.resolve(LocIds.CHAPTERS);
  63. this.nameSpan.textContent = this.localization.resolve(LocIds.CHAPTERS);
  64. this.backSpan.textContent = this.localization.resolve(LocIds.CHAPTERS);
  65. }
  66. /**
  67. * @private
  68. */
  69. deletePreviousChapters_() {
  70. // 1. Save the back to menu button
  71. const backButton = shaka.ui.Utils.getFirstDescendantWithClassName(
  72. this.menu, 'shaka-back-to-overflow-button');
  73. // 2. Remove everything
  74. shaka.util.Dom.removeAllChildren(this.menu);
  75. // 3. Add the backTo Menu button back
  76. this.menu.appendChild(backButton);
  77. // 4. Hidden button
  78. shaka.ui.Utils.setDisplay(this.button, false);
  79. }
  80. /**
  81. * @private
  82. */
  83. async updateChapters_() {
  84. /**
  85. * Does a value compare on chapters.
  86. * @param {shaka.extern.Chapter} a
  87. * @param {shaka.extern.Chapter} b
  88. * @return {boolean}
  89. */
  90. const chaptersEqual = (a, b) => {
  91. return (!a && !b) || (a.id === b.id && a.title === b.title &&
  92. a.startTime === b.startTime && a.endTime === b.endTime);
  93. };
  94. let nextLanguage = 'und';
  95. /** @type {!Array<shaka.extern.Chapter>} */
  96. let nextChapters = [];
  97. const currentLocales = this.localization.getCurrentLocales();
  98. for (const locale of Array.from(currentLocales)) {
  99. nextLanguage = locale;
  100. // If player is a proxy, and the cast receiver doesn't support this
  101. // method, you get back undefined.
  102. if (this.player) {
  103. // eslint-disable-next-line no-await-in-loop
  104. nextChapters = (await this.player.getChaptersAsync(nextLanguage)) || [];
  105. }
  106. if (nextChapters.length) {
  107. break;
  108. }
  109. }
  110. if (!nextChapters.length) {
  111. nextLanguage = 'und';
  112. if (this.player) {
  113. // If player is a proxy, and the cast receiver doesn't support this
  114. // method, you get back undefined.
  115. nextChapters = (await this.player.getChaptersAsync(nextLanguage)) || [];
  116. }
  117. }
  118. const languageChanged = nextLanguage !== this.chaptersLanguage_;
  119. const chaptersChanged = this.chapters_.length !== nextChapters.length ||
  120. !this.chapters_.some((c, idx) => {
  121. const n = nextChapters.at(idx);
  122. return chaptersEqual(c, n) ||
  123. nextChapters.some((n) => chaptersEqual(c, n));
  124. });
  125. this.chaptersLanguage_ = nextLanguage;
  126. this.chapters_ = nextChapters;
  127. if (!nextChapters.length) {
  128. this.deletePreviousChapters_();
  129. } else if (languageChanged || chaptersChanged) {
  130. for (const chapter of this.chapters_) {
  131. const button = shaka.util.Dom.createButton();
  132. const span = shaka.util.Dom.createHTMLElement('span');
  133. span.classList.add('shaka-chapter');
  134. span.textContent = chapter.title;
  135. button.appendChild(span);
  136. this.eventManager.listen(button, 'click', () => {
  137. if (!this.controls.isOpaque()) {
  138. return;
  139. }
  140. this.video.currentTime = chapter.startTime;
  141. });
  142. this.menu.appendChild(button);
  143. }
  144. shaka.ui.Utils.setDisplay(this.button, true);
  145. shaka.ui.Utils.focusOnTheChosenItem(this.menu);
  146. }
  147. }
  148. };
  149. /**
  150. * @implements {shaka.extern.IUIElement.Factory}
  151. * @final
  152. */
  153. shaka.ui.ChapterSelection.Factory = class {
  154. /** @override */
  155. create(rootElement, controls) {
  156. return new shaka.ui.ChapterSelection(rootElement, controls);
  157. }
  158. };
  159. shaka.ui.OverflowMenu.registerElement(
  160. 'chapter', new shaka.ui.ChapterSelection.Factory());
  161. shaka.ui.Controls.registerElement(
  162. 'chapter', new shaka.ui.ChapterSelection.Factory());