Source: ui/remote_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.RemoteButton');
  7. goog.require('shaka.Player');
  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.require('shaka.util.Platform');
  17. goog.requireType('shaka.ui.Controls');
  18. /**
  19. * @extends {shaka.ui.Element}
  20. * @final
  21. * @export
  22. */
  23. shaka.ui.RemoteButton = class extends shaka.ui.Element {
  24. /**
  25. * @param {!HTMLElement} parent
  26. * @param {!shaka.ui.Controls} controls
  27. */
  28. constructor(parent, controls) {
  29. super(parent, controls);
  30. /** @private {boolean} */
  31. this.isAirPlay_ = shaka.util.Platform.isApple();
  32. /** @private {!HTMLButtonElement} */
  33. this.remoteButton_ = shaka.util.Dom.createButton();
  34. this.remoteButton_.classList.add('shaka-remote-button');
  35. this.remoteButton_.classList.add('shaka-tooltip');
  36. this.remoteButton_.ariaPressed = 'false';
  37. /** @private {!HTMLElement} */
  38. this.remoteIcon_ = shaka.util.Dom.createHTMLElement('i');
  39. this.remoteIcon_.classList.add('material-icons-round');
  40. this.remoteIcon_.textContent = this.isAirPlay_ ?
  41. shaka.ui.Enums.MaterialDesignIcons.AIRPLAY :
  42. shaka.ui.Enums.MaterialDesignIcons.CAST;
  43. this.remoteButton_.appendChild(this.remoteIcon_);
  44. const label = shaka.util.Dom.createHTMLElement('label');
  45. label.classList.add('shaka-overflow-button-label');
  46. label.classList.add('shaka-overflow-menu-only');
  47. this.remoteNameSpan_ = shaka.util.Dom.createHTMLElement('span');
  48. label.appendChild(this.remoteNameSpan_);
  49. this.remoteCurrentSelectionSpan_ =
  50. shaka.util.Dom.createHTMLElement('span');
  51. this.remoteCurrentSelectionSpan_.classList.add(
  52. 'shaka-current-selection-span');
  53. label.appendChild(this.remoteCurrentSelectionSpan_);
  54. this.remoteButton_.appendChild(label);
  55. this.parent.appendChild(this.remoteButton_);
  56. /** @private {number} */
  57. this.callbackId_ = -1;
  58. // Setup strings in the correct language
  59. this.updateLocalizedStrings_();
  60. shaka.ui.Utils.setDisplay(this.remoteButton_, false);
  61. if (!this.video.remote) {
  62. this.remoteButton_.classList.add('shaka-hidden');
  63. } else {
  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.controls, 'caststatuschanged', () => {
  73. this.updateRemoteState_();
  74. });
  75. this.eventManager.listen(this.remoteButton_, 'click', () => {
  76. if (!this.controls.isOpaque()) {
  77. return;
  78. }
  79. this.video.remote.prompt();
  80. });
  81. this.eventManager.listen(this.video.remote, 'connect', () => {
  82. this.updateRemoteState_();
  83. this.updateIcon_();
  84. });
  85. this.eventManager.listen(this.video.remote, 'connecting', () => {
  86. this.updateRemoteState_();
  87. this.updateIcon_();
  88. });
  89. this.eventManager.listen(this.video.remote, 'disconnect', () => {
  90. this.updateRemoteState_();
  91. this.updateIcon_();
  92. });
  93. this.eventManager.listen(this.player, 'loaded', () => {
  94. this.updateRemoteState_();
  95. });
  96. this.updateRemoteState_(/* force= */ true);
  97. this.updateIcon_();
  98. }
  99. }
  100. /** @override */
  101. release() {
  102. if (this.video.remote && this.callbackId_ != -1) {
  103. this.video.remote.cancelWatchAvailability(this.callbackId_).catch(() => {
  104. // Ignore this error.
  105. });
  106. }
  107. super.release();
  108. }
  109. /**
  110. * @param {boolean=} force
  111. * @private
  112. */
  113. async updateRemoteState_(force = false) {
  114. if (this.controls.getCastProxy().canCast() &&
  115. this.controls.isCastAllowed()) {
  116. shaka.ui.Utils.setDisplay(this.remoteButton_, false);
  117. if (this.callbackId_ != -1) {
  118. this.video.remote.cancelWatchAvailability(this.callbackId_);
  119. this.callbackId_ = -1;
  120. }
  121. } else if (this.video.remote.state == 'disconnected' || force) {
  122. const handleAvailabilityChange = (availability) => {
  123. if (this.player) {
  124. const disableRemote = this.video.disableRemotePlayback;
  125. let canCast = true;
  126. if (shaka.util.Platform.isApple()) {
  127. const loadMode = this.player.getLoadMode();
  128. const mseMode = loadMode == shaka.Player.LoadMode.MEDIA_SOURCE;
  129. if (mseMode && this.player.getManifestType() != 'HLS') {
  130. canCast = false;
  131. }
  132. }
  133. shaka.ui.Utils.setDisplay(
  134. this.remoteButton_, canCast && availability && !disableRemote);
  135. } else {
  136. shaka.ui.Utils.setDisplay(this.remoteButton_, false);
  137. }
  138. };
  139. try {
  140. if (this.callbackId_ != -1) {
  141. await this.video.remote.cancelWatchAvailability(this.callbackId_);
  142. this.callbackId_ = -1;
  143. }
  144. } catch (e) {
  145. // Ignore this error.
  146. }
  147. try {
  148. const id = await this.video.remote.watchAvailability(
  149. handleAvailabilityChange);
  150. this.callbackId_ = id;
  151. } catch (e) {
  152. handleAvailabilityChange(/* availability= */ true);
  153. }
  154. } else if (this.callbackId_ != -1) {
  155. // If remote device is connecting or connected, we should stop
  156. // watching remote device availability to save power.
  157. await this.video.remote.cancelWatchAvailability(this.callbackId_);
  158. this.callbackId_ = -1;
  159. }
  160. }
  161. /**
  162. * @private
  163. */
  164. updateLocalizedStrings_() {
  165. const LocIds = shaka.ui.Locales.Ids;
  166. const text = this.isAirPlay_ ?
  167. this.localization.resolve(LocIds.AIRPLAY) :
  168. this.localization.resolve(LocIds.CAST);
  169. this.remoteButton_.ariaLabel = text;
  170. this.remoteNameSpan_.textContent = text;
  171. }
  172. /**
  173. * @private
  174. */
  175. updateIcon_() {
  176. if (this.isAirPlay_) {
  177. return;
  178. }
  179. if (this.video.remote.state == 'disconnected') {
  180. this.remoteIcon_.textContent =
  181. shaka.ui.Enums.MaterialDesignIcons.CAST;
  182. } else {
  183. this.remoteIcon_.textContent =
  184. shaka.ui.Enums.MaterialDesignIcons.EXIT_CAST;
  185. }
  186. }
  187. };
  188. /**
  189. * @implements {shaka.extern.IUIElement.Factory}
  190. * @final
  191. */
  192. shaka.ui.RemoteButton.Factory = class {
  193. /** @override */
  194. create(rootElement, controls) {
  195. return new shaka.ui.RemoteButton(rootElement, controls);
  196. }
  197. };
  198. shaka.ui.OverflowMenu.registerElement(
  199. 'remote', new shaka.ui.RemoteButton.Factory());
  200. shaka.ui.Controls.registerElement(
  201. 'remote', new shaka.ui.RemoteButton.Factory());