Source: ui/ui.js

/**
 * @license
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


goog.provide('shaka.ui.Overlay');

goog.require('goog.asserts');
goog.require('shaka.polyfill.installAll');
goog.require('shaka.ui.Controls');
goog.require('shaka.ui.TextDisplayer');


/**
 * @param {!shaka.Player} player
 * @param {!HTMLElement} videoContainer
 * @param {!HTMLMediaElement} video
 * @param {!Object=} config This should follow the form of
 *   {@link shaka.extern.UIConfiguration}, but you may omit
 *   any field you do not wish to change.
 * @constructor
 * @export
 */
shaka.ui.Overlay = function(player, videoContainer, video, config) {
  /** @private {!shaka.Player} */
  this.player_ = player;

  /** @private {!shaka.extern.UIConfiguration} */
  this.config_ = this.defaultConfig_();

  if (config) {
    shaka.util.ConfigUtils.mergeConfigObjects(
        this.config_, config, this.defaultConfig_(),
        /* overrides (only used for player config)*/ {}, /* path */ '');
  }

  // If a cast receiver app id has been given, add a cast button to the UI
  if (this.config_.castReceiverAppId &&
      !this.config_.overflowMenuButtons.includes('cast')) {
    this.config_.overflowMenuButtons.push('cast');
  }

  /** @private {!shaka.ui.Controls} */
  this.controls_ = new shaka.ui.Controls(
      player, videoContainer, video, this.config_);
};


/**
 * @return {!shaka.Player}
 * @export
 */
shaka.ui.Overlay.prototype.getPlayer = function() {
  return this.player_;
};


/**
 * @return {!shaka.ui.Controls}
 * @export
 */
shaka.ui.Overlay.prototype.getControls = function() {
  return this.controls_;
};


/**
 * Enable or disable the custom controls.
 *
 * @param {boolean} enabled
 * @export
 */
shaka.ui.Overlay.prototype.setEnabled = function(enabled) {
  this.controls_.setEnabledShakaControls(enabled);
};


/**
 * @return {!shaka.extern.UIConfiguration}
 * @private
 */
shaka.ui.Overlay.prototype.defaultConfig_ = function() {
  return {
    controlPanelElements: [
      'time_and_duration',
      'spacer',
      'mute',
      'volume',
      'fullscreen',
      'overflow_menu',
    ],
    overflowMenuButtons: [
      'captions',
      'quality',
      'language',
      'picture_in_picture',
    ],
    addSeekBar: true,
    castReceiverAppId: '',
  };
};


/**
 * @private
 */
shaka.ui.Overlay.scanPageForShakaElements_ = function() {
  // Install built-in polyfills to patch browser incompatibilities.
  shaka.polyfill.installAll();
  // Check to see if the browser supports the basic APIs Shaka needs.
  if (!shaka.Player.isBrowserSupported()) {
    shaka.log.error('Shaka Player does not support this browser. ' +
      'Please see https://tinyurl.com/y7s4j9tr for the list of ' +
      'supported browsers.');

    return;
  }

  // Look for elements marked 'data-shaka-player-container'
  // on the page. These will be used to create our default
  // UI.
  const containers = document.querySelectorAll(
      '[data-shaka-player-container]');
  // Look for elements marked 'data-shaka-player'. They will
  // either be used in our default UI or with native browser
  // controls.
  const videos = document.querySelectorAll(
      '[data-shaka-player]');

  if (!videos.length && !containers.length) {
    // No elements have been tagged with shaka attributes.
  } else if (videos.length && !containers.length) {
    // Just the video elements were provided.
    for (let i = 0; i < videos.length; i++) {
      const video = videos[i];
      video.classList.add('video');
      goog.asserts.assert(video.tagName.toLowerCase() == 'video',
        'Should be a video element!');

      const container = document.createElement('div');
      const videoParent = video.parentElement;
      videoParent.replaceChild(container, video);
      container.appendChild(video);

      let castAppId = '';

      // If cast receiver application id was provided, pass it to the
      // UI constructor.
      if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
        castAppId = video['dataset']['shakaPlayerCastReceiverId'];
      }

      const videoAsMediaElement = /** @type {!HTMLMediaElement} */ (video);
      const ui = shaka.ui.Overlay.createUI_(
          /** @type {!HTMLElement} */ (container),
          videoAsMediaElement,
          {castReceiverAppId: castAppId});

      if (videoAsMediaElement.controls) {
        ui.getControls().setEnabledNativeControls(true);
      }
    }
  } else {
      for (let i = 0; i < containers.length; i++) {
      const container = containers[i];
      goog.asserts.assert(container.tagName.toLowerCase() == 'div',
        'Container should be a div!');

      let castAppId = '';

      // Cast receiver id can be specified on either container or video.
      // It should not be provided on both. If it was, we will use the last
      // one we saw.
      if (container['dataset'] &&
          container['dataset']['shakaPlayerCastReceiverId']) {
        castAppId = container['dataset']['shakaPlayerCastReceiverId'];
      }

      let video = null;
      for (let j = 0; j < videos.length; j++) {
        goog.asserts.assert(videos[j].tagName.toLowerCase() == 'video',
          'Should be a video element!');
        if (videos[j].parentElement == container) {
          video = videos[j];
          break;
        }
      }

      if (!video) {
        video = document.createElement('video');
        container.appendChild(video);
      }

      if (video['dataset'] && video['dataset']['shakaPlayerCastReceiverId']) {
        castAppId = video['dataset']['shakaPlayerCastReceiverId'];
      }
      shaka.ui.Overlay.createUI_(/** @type {!HTMLElement} */ (container),
                                 /** @type {!HTMLMediaElement} */ (video),
                                 {castReceiverAppId: castAppId});
    }
  }

  // After scanning the page for elements, fire the "loaded" event.  This will
  // let apps know they can use the UI library programatically now, even if they
  // didn't have any Shaka-related elements declared in their HTML.

  // "Event" is not constructable on IE, so we use this CustomEvent pattern.
  const uiLoadedEvent = /** @type {!CustomEvent} */(
      document.createEvent('CustomEvent'));
  uiLoadedEvent.initCustomEvent('shaka-ui-loaded', false, false, null);

  document.dispatchEvent(uiLoadedEvent);
};


/**
 * @param {!HTMLElement} container
 * @param {!HTMLMediaElement} video
 * @param {!Object} config (Possibly partial) config in the form of
 *   {@link shaka.extern.UIConfiguration}
 * @return {!shaka.ui.Overlay}
 * @private
 */
shaka.ui.Overlay.createUI_ = function(container, video, config) {
  const player = new shaka.Player(video);
  const ui = new shaka.ui.Overlay(player, container, video, config);

  // If the browser's native controls are disabled, use UI TextDisplayer.
  if (!video.controls) {
    player.configure('textDisplayFactory',
        function() { return new shaka.ui.TextDisplayer(video, container); });
  }

  container['ui'] = ui;
  video['ui'] = ui;
  return ui;
};


if (document.readyState == 'complete') {
  // Don't fire this event synchronously.  In a compiled bundle, the "shaka"
  // namespace might not be exported to the window until after this point.
  Promise.resolve().then(shaka.ui.Overlay.scanPageForShakaElements_);
} else {
  window.addEventListener('load', shaka.ui.Overlay.scanPageForShakaElements_);
}