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.isSafari();
  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. this.video.remote.prompt();
  77. });
  78. this.eventManager.listen(this.video.remote, 'connect', () => {
  79. this.updateRemoteState_();
  80. this.updateIcon_();
  81. });
  82. this.eventManager.listen(this.video.remote, 'connecting', () => {
  83. this.updateRemoteState_();
  84. this.updateIcon_();
  85. });
  86. this.eventManager.listen(this.video.remote, 'disconnect', () => {
  87. this.updateRemoteState_();
  88. this.updateIcon_();
  89. });
  90. this.eventManager.listen(this.player, 'loaded', () => {
  91. this.updateRemoteState_();
  92. });
  93. this.updateRemoteState_(/* force= */ true);
  94. this.updateIcon_();
  95. }
  96. }
  97. /** @override */
  98. release() {
  99. if (this.video.remote && this.callbackId_ != -1) {
  100. this.video.remote.cancelWatchAvailability(this.callbackId_).catch(() => {
  101. // Ignore this error.
  102. });
  103. }
  104. super.release();
  105. }
  106. /**
  107. * @param {boolean=} force
  108. * @private
  109. */
  110. async updateRemoteState_(force = false) {
  111. if (this.controls.getCastProxy().canCast() &&
  112. this.controls.isCastAllowed()) {
  113. shaka.ui.Utils.setDisplay(this.remoteButton_, false);
  114. if (this.callbackId_ != -1) {
  115. this.video.remote.cancelWatchAvailability(this.callbackId_);
  116. this.callbackId_ = -1;
  117. }
  118. } else if (this.video.remote.state == 'disconnected' || force) {
  119. const handleAvailabilityChange = (availability) => {
  120. if (this.player) {
  121. const disableRemote = this.video.disableRemotePlayback;
  122. let canCast = true;
  123. if (shaka.util.Platform.isSafari()) {
  124. const loadMode = this.player.getLoadMode();
  125. const mseMode = loadMode == shaka.Player.LoadMode.MEDIA_SOURCE;
  126. if (mseMode && this.player.getManifestType() != 'HLS') {
  127. canCast = false;
  128. }
  129. }
  130. shaka.ui.Utils.setDisplay(
  131. this.remoteButton_, canCast && availability && !disableRemote);
  132. } else {
  133. shaka.ui.Utils.setDisplay(this.remoteButton_, false);
  134. }
  135. };
  136. try {
  137. if (this.callbackId_ != -1) {
  138. await this.video.remote.cancelWatchAvailability(this.callbackId_);
  139. this.callbackId_ = -1;
  140. }
  141. } catch (e) {
  142. // Ignore this error.
  143. }
  144. try {
  145. const id = await this.video.remote.watchAvailability(
  146. handleAvailabilityChange);
  147. this.callbackId_ = id;
  148. } catch (e) {
  149. handleAvailabilityChange(/* availability= */ true);
  150. }
  151. } else if (this.callbackId_ != -1) {
  152. // If remote device is connecting or connected, we should stop
  153. // watching remote device availability to save power.
  154. await this.video.remote.cancelWatchAvailability(this.callbackId_);
  155. this.callbackId_ = -1;
  156. }
  157. }
  158. /**
  159. * @private
  160. */
  161. updateLocalizedStrings_() {
  162. const LocIds = shaka.ui.Locales.Ids;
  163. const text = this.isAirPlay_ ?
  164. this.localization.resolve(LocIds.AIRPLAY) :
  165. this.localization.resolve(LocIds.CAST);
  166. this.remoteButton_.ariaLabel = text;
  167. this.remoteNameSpan_.textContent = text;
  168. }
  169. /**
  170. * @private
  171. */
  172. updateIcon_() {
  173. if (this.isAirPlay_) {
  174. return;
  175. }
  176. if (this.video.remote.state == 'disconnected') {
  177. this.remoteIcon_.textContent =
  178. shaka.ui.Enums.MaterialDesignIcons.CAST;
  179. } else {
  180. this.remoteIcon_.textContent =
  181. shaka.ui.Enums.MaterialDesignIcons.EXIT_CAST;
  182. }
  183. }
  184. };
  185. /**
  186. * @implements {shaka.extern.IUIElement.Factory}
  187. * @final
  188. */
  189. shaka.ui.RemoteButton.Factory = class {
  190. /** @override */
  191. create(rootElement, controls) {
  192. return new shaka.ui.RemoteButton(rootElement, controls);
  193. }
  194. };
  195. shaka.ui.OverflowMenu.registerElement(
  196. 'remote', new shaka.ui.RemoteButton.Factory());
  197. shaka.ui.Controls.registerElement(
  198. 'remote', new shaka.ui.RemoteButton.Factory());