Source: ui/pip_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.PipButton');
  7. goog.require('shaka.ui.ContextMenu');
  8. goog.require('shaka.ui.Controls');
  9. goog.require('shaka.ui.Element');
  10. goog.require('shaka.ui.Enums');
  11. goog.require('shaka.ui.Locales');
  12. goog.require('shaka.ui.Localization');
  13. goog.require('shaka.ui.OverflowMenu');
  14. goog.require('shaka.ui.Utils');
  15. goog.require('shaka.util.Dom');
  16. goog.requireType('shaka.ui.Controls');
  17. /**
  18. * @extends {shaka.ui.Element}
  19. * @final
  20. * @export
  21. */
  22. shaka.ui.PipButton = class extends shaka.ui.Element {
  23. /**
  24. * @param {!HTMLElement} parent
  25. * @param {!shaka.ui.Controls} controls
  26. */
  27. constructor(parent, controls) {
  28. super(parent, controls);
  29. /** @private {HTMLMediaElement} */
  30. this.localVideo_ = this.controls.getLocalVideo();
  31. /** @private {HTMLElement } */
  32. this.videoContainer_ = this.controls.getVideoContainer();
  33. const LocIds = shaka.ui.Locales.Ids;
  34. /** @private {!HTMLButtonElement} */
  35. this.pipButton_ = shaka.util.Dom.createButton();
  36. this.pipButton_.classList.add('shaka-pip-button');
  37. this.pipButton_.classList.add('shaka-tooltip');
  38. /** @private {!HTMLElement} */
  39. this.pipIcon_ = shaka.util.Dom.createHTMLElement('i');
  40. this.pipIcon_.classList.add('material-icons-round');
  41. this.pipIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.PIP;
  42. this.pipButton_.appendChild(this.pipIcon_);
  43. const label = shaka.util.Dom.createHTMLElement('label');
  44. label.classList.add('shaka-overflow-button-label');
  45. label.classList.add('shaka-overflow-menu-only');
  46. label.classList.add('shaka-simple-overflow-button-label-inline');
  47. this.pipNameSpan_ = shaka.util.Dom.createHTMLElement('span');
  48. this.pipNameSpan_.textContent =
  49. this.localization.resolve(LocIds.PICTURE_IN_PICTURE);
  50. label.appendChild(this.pipNameSpan_);
  51. /** @private {!HTMLElement} */
  52. this.currentPipState_ = shaka.util.Dom.createHTMLElement('span');
  53. this.currentPipState_.classList.add('shaka-current-selection-span');
  54. label.appendChild(this.currentPipState_);
  55. this.pipButton_.appendChild(label);
  56. this.updateLocalizedStrings_();
  57. this.parent.appendChild(this.pipButton_);
  58. // Don't display the button if PiP is not supported or not allowed.
  59. // TODO: Can this ever change? Is it worth creating the button if the below
  60. // condition is true?
  61. if (!this.controls.isPiPAllowed()) {
  62. shaka.ui.Utils.setDisplay(this.pipButton_, false);
  63. }
  64. this.eventManager.listen(
  65. this.localization, shaka.ui.Localization.LOCALE_UPDATED, () => {
  66. this.updateLocalizedStrings_();
  67. });
  68. this.eventManager.listen(
  69. this.localization, shaka.ui.Localization.LOCALE_CHANGED, () => {
  70. this.updateLocalizedStrings_();
  71. });
  72. this.eventManager.listen(this.pipButton_, 'click', () => {
  73. if (!this.controls.isOpaque()) {
  74. return;
  75. }
  76. this.controls.togglePiP();
  77. });
  78. this.eventManager.listen(this.localVideo_, 'enterpictureinpicture', () => {
  79. this.onEnterPictureInPicture_();
  80. });
  81. this.eventManager.listen(this.localVideo_, 'leavepictureinpicture', () => {
  82. this.onLeavePictureInPicture_();
  83. });
  84. this.eventManager.listen(this.controls, 'caststatuschanged', () => {
  85. this.onTracksChanged_();
  86. });
  87. this.eventManager.listen(this.player, 'trackschanged', () => {
  88. this.onTracksChanged_();
  89. });
  90. if ('documentPictureInPicture' in window) {
  91. this.eventManager.listen(window.documentPictureInPicture, 'enter',
  92. (e) => {
  93. this.onEnterPictureInPicture_();
  94. const event = /** @type {DocumentPictureInPictureEvent} */(e);
  95. const pipWindow = event.window;
  96. this.eventManager.listenOnce(pipWindow, 'pagehide', () => {
  97. this.onLeavePictureInPicture_();
  98. });
  99. });
  100. }
  101. }
  102. /** @private */
  103. onEnterPictureInPicture_() {
  104. const LocIds = shaka.ui.Locales.Ids;
  105. this.pipIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.EXIT_PIP;
  106. this.pipButton_.ariaLabel =
  107. this.localization.resolve(LocIds.EXIT_PICTURE_IN_PICTURE);
  108. this.currentPipState_.textContent =
  109. this.localization.resolve(LocIds.ON);
  110. }
  111. /** @private */
  112. onLeavePictureInPicture_() {
  113. const LocIds = shaka.ui.Locales.Ids;
  114. this.pipIcon_.textContent = shaka.ui.Enums.MaterialDesignIcons.PIP;
  115. this.pipButton_.ariaLabel =
  116. this.localization.resolve(LocIds.ENTER_PICTURE_IN_PICTURE);
  117. this.currentPipState_.textContent =
  118. this.localization.resolve(LocIds.OFF);
  119. }
  120. /**
  121. * @private
  122. */
  123. updateLocalizedStrings_() {
  124. const LocIds = shaka.ui.Locales.Ids;
  125. this.pipNameSpan_.textContent =
  126. this.localization.resolve(LocIds.PICTURE_IN_PICTURE);
  127. const enabled = this.controls.isPiPEnabled();
  128. const ariaLabel = enabled ?
  129. LocIds.EXIT_PICTURE_IN_PICTURE :
  130. LocIds.ENTER_PICTURE_IN_PICTURE;
  131. this.pipButton_.ariaLabel = this.localization.resolve(ariaLabel);
  132. const currentPipState = enabled ? LocIds.ON : LocIds.OFF;
  133. this.currentPipState_.textContent =
  134. this.localization.resolve(currentPipState);
  135. }
  136. /**
  137. * Display the picture-in-picture button only when the content contains video.
  138. * If it's displaying in picture-in-picture mode, and an audio only content is
  139. * loaded, exit the picture-in-picture display.
  140. * @return {!Promise}
  141. * @private
  142. */
  143. async onTracksChanged_() {
  144. if (!this.controls.isPiPAllowed()) {
  145. shaka.ui.Utils.setDisplay(this.pipButton_, false);
  146. if (this.controls.isPiPEnabled()) {
  147. await this.controls.togglePiP();
  148. }
  149. } else if (this.player && this.player.isAudioOnly()) {
  150. shaka.ui.Utils.setDisplay(this.pipButton_, false);
  151. if (this.controls.isPiPEnabled()) {
  152. await this.controls.togglePiP();
  153. }
  154. } else {
  155. shaka.ui.Utils.setDisplay(this.pipButton_, true);
  156. }
  157. }
  158. };
  159. /**
  160. * @implements {shaka.extern.IUIElement.Factory}
  161. * @final
  162. */
  163. shaka.ui.PipButton.Factory = class {
  164. /** @override */
  165. create(rootElement, controls) {
  166. return new shaka.ui.PipButton(rootElement, controls);
  167. }
  168. };
  169. shaka.ui.OverflowMenu.registerElement(
  170. 'picture_in_picture', new shaka.ui.PipButton.Factory());
  171. shaka.ui.Controls.registerElement(
  172. 'picture_in_picture', new shaka.ui.PipButton.Factory());
  173. shaka.ui.ContextMenu.registerElement(
  174. 'picture_in_picture', new shaka.ui.PipButton.Factory());