Source: ui/ui.js

  1. /**
  2. * @license
  3. * Copyright 2016 Google Inc.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. goog.provide('shaka.ui.Overlay');
  18. goog.require('goog.asserts');
  19. goog.require('shaka.polyfill.installAll');
  20. goog.require('shaka.ui.Controls');
  21. goog.require('shaka.ui.TextDisplayer');
  22. /**
  23. * @param {!shaka.Player} player
  24. * @param {!HTMLElement} videoContainer
  25. * @param {!HTMLMediaElement} video
  26. * @param {!Object=} config This should follow the form of
  27. * {@link shaka.extern.UIConfiguration}, but you may omit
  28. * any field you do not wish to change.
  29. * @constructor
  30. * @export
  31. */
  32. shaka.ui.Overlay = function(player, videoContainer, video, config) {
  33. /** @private {!shaka.Player} */
  34. this.player_ = player;
  35. /** @private {!shaka.extern.UIConfiguration} */
  36. this.config_ = this.defaultConfig_();
  37. if (config) {
  38. shaka.util.ConfigUtils.mergeConfigObjects(
  39. this.config_, config, this.defaultConfig_(),
  40. /* overrides (only used for player config)*/ {}, /* path */ '');
  41. }
  42. // If a cast receiver app id has been given, add a cast button to the UI
  43. if (this.config_.castReceiverAppId &&
  44. !this.config_.overflowMenuButtons.includes('cast')) {
  45. this.config_.overflowMenuButtons.push('cast');
  46. }
  47. /** @private {!shaka.ui.Controls} */
  48. this.controls_ = new shaka.ui.Controls(
  49. player, videoContainer, video, this.config_);
  50. };
  51. /**
  52. * @return {!shaka.Player}
  53. * @export
  54. */
  55. shaka.ui.Overlay.prototype.getPlayer = function() {
  56. return this.player_;
  57. };
  58. /**
  59. * @return {!shaka.ui.Controls}
  60. * @export
  61. */
  62. shaka.ui.Overlay.prototype.getControls = function() {
  63. return this.controls_;
  64. };
  65. /**
  66. * Enable or disable the custom controls.
  67. *
  68. * @param {boolean} enabled
  69. * @export
  70. */
  71. shaka.ui.Overlay.prototype.setEnabled = function(enabled) {
  72. this.controls_.setEnabledShakaControls(enabled);
  73. };
  74. /**
  75. * @return {!shaka.extern.UIConfiguration}
  76. * @private
  77. */
  78. shaka.ui.Overlay.prototype.defaultConfig_ = function() {
  79. return {
  80. controlPanelElements: [
  81. 'time_and_duration',
  82. 'spacer',
  83. 'mute',
  84. 'volume',
  85. 'fullscreen',
  86. 'overflow_menu',
  87. ],
  88. overflowMenuButtons: [
  89. 'captions',
  90. 'quality',
  91. 'language',
  92. 'picture_in_picture',
  93. ],
  94. addSeekBar: true,
  95. castReceiverAppId: '',
  96. };
  97. };
  98. /**
  99. * @private
  100. */
  101. shaka.ui.Overlay.scanPageForShakaElements_ = function() {
  102. // Install built-in polyfills to patch browser incompatibilities.
  103. shaka.polyfill.installAll();
  104. // Check to see if the browser supports the basic APIs Shaka needs.
  105. if (!shaka.Player.isBrowserSupported()) {
  106. shaka.log.error('Shaka Player does not support this browser. ' +
  107. 'Please see https://tinyurl.com/y7s4j9tr for the list of ' +
  108. 'supported browsers.');
  109. return;
  110. }
  111. // Look for elements marked 'data-shaka-player-container'
  112. // on the page. These will be used to create our default
  113. // UI.
  114. const containers = document.querySelectorAll(
  115. '[data-shaka-player-container]');
  116. // Look for elements marked 'data-shaka-player'. They will
  117. // either be used in our default UI or with native browser
  118. // controls.
  119. const videos = document.querySelectorAll(
  120. '[data-shaka-player]');
  121. if (!videos.length && !containers.length) {
  122. // No elements have been tagged with shaka attributes.
  123. } else if (videos.length && !containers.length) {
  124. // Just the video elements were provided.
  125. for (let i = 0; i < videos.length; i++) {
  126. const video = videos[i];
  127. video.classList.add('video');
  128. goog.asserts.assert(video.tagName.toLowerCase() == 'video',
  129. 'Should be a video element!');
  130. const container = document.createElement('div');
  131. const videoParent = video.parentElement;
  132. videoParent.replaceChild(container, video);
  133. container.appendChild(video);
  134. let castAppId = '';
  135. // If cast receiver application id was provided, pass it to the
  136. // UI constructor.
  137. if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
  138. castAppId = video['dataset']['shakaPlayerCastReceiverId'];
  139. }
  140. const videoAsMediaElement = /** @type {!HTMLMediaElement} */ (video);
  141. const ui = shaka.ui.Overlay.createUI_(
  142. /** @type {!HTMLElement} */ (container),
  143. videoAsMediaElement,
  144. {castReceiverAppId: castAppId});
  145. if (videoAsMediaElement.controls) {
  146. ui.getControls().setEnabledNativeControls(true);
  147. }
  148. }
  149. } else {
  150. for (let i = 0; i < containers.length; i++) {
  151. const container = containers[i];
  152. goog.asserts.assert(container.tagName.toLowerCase() == 'div',
  153. 'Container should be a div!');
  154. let castAppId = '';
  155. // Cast receiver id can be specified on either container or video.
  156. // It should not be provided on both. If it was, we will use the last
  157. // one we saw.
  158. if (container['dataset'] &&
  159. container['dataset']['shakaPlayerCastReceiverId']) {
  160. castAppId = container['dataset']['shakaPlayerCastReceiverId'];
  161. }
  162. let video = null;
  163. for (let j = 0; j < videos.length; j++) {
  164. goog.asserts.assert(videos[j].tagName.toLowerCase() == 'video',
  165. 'Should be a video element!');
  166. if (videos[j].parentElement == container) {
  167. video = videos[j];
  168. break;
  169. }
  170. }
  171. if (!video) {
  172. video = document.createElement('video');
  173. container.appendChild(video);
  174. }
  175. if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
  176. castAppId = video['dataset']['shakaPlayerCastReceiverId'];
  177. }
  178. shaka.ui.Overlay.createUI_(/** @type {!HTMLElement} */ (container),
  179. /** @type {!HTMLMediaElement} */ (video),
  180. {castReceiverAppId: castAppId});
  181. }
  182. }
  183. // After scanning the page for elements, fire the "loaded" event. This will
  184. // let apps know they can use the UI library programatically now, even if they
  185. // didn't have any Shaka-related elements declared in their HTML.
  186. // "Event" is not constructable on IE, so we use this CustomEvent pattern.
  187. const uiLoadedEvent = /** @type {!CustomEvent} */(
  188. document.createEvent('CustomEvent'));
  189. uiLoadedEvent.initCustomEvent('shaka-ui-loaded', false, false, null);
  190. document.dispatchEvent(uiLoadedEvent);
  191. };
  192. /**
  193. * @param {!HTMLElement} container
  194. * @param {!HTMLMediaElement} video
  195. * @param {!Object} config (Possibly partial) config in the form of
  196. * {@link shaka.extern.UIConfiguration}
  197. * @return {!shaka.ui.Overlay}
  198. * @private
  199. */
  200. shaka.ui.Overlay.createUI_ = function(container, video, config) {
  201. const player = new shaka.Player(video);
  202. const ui = new shaka.ui.Overlay(player, container, video, config);
  203. // If the browser's native controls are disabled, use UI TextDisplayer.
  204. if (!video.controls) {
  205. player.configure('textDisplayFactory',
  206. function() { return new shaka.ui.TextDisplayer(video, container); });
  207. }
  208. container['ui'] = ui;
  209. video['ui'] = ui;
  210. return ui;
  211. };
  212. if (document.readyState == 'complete') {
  213. // Don't fire this event synchronously. In a compiled bundle, the "shaka"
  214. // namespace might not be exported to the window until after this point.
  215. Promise.resolve().then(shaka.ui.Overlay.scanPageForShakaElements_);
  216. } else {
  217. window.addEventListener('load', shaka.ui.Overlay.scanPageForShakaElements_);
  218. }