greenview.js

(function() {
  /**
   * Meta object for GreenView.
   * @typedef {Object} Meta
   * @property {Object} device
   * @property {string} device.userAgent? The web browser User agent string.
   * @property {string} device.manufacturer? Manufacturer of the device, e.g. `Sony`.
   * @property {string} device.model? Model name of the device.
   * @property {string} device.type? Type of device, i.e. `tv`, `desktop`, `mobile`, `stb`.
   * @property {Object} device.screen? Describes the screen of the device.
   * @property {string} device.screen.size? Size in inches.
   * @property {string} device.screen.resolution? Used resolution, e.g. `1920x1280`.
   * @property {string} device.screen.type? Type of display, i.e. `amoled`, `lcd`, `lcdled`, `qled`.
   */

  /**
   * Content object for GreenView.
   * @typedef {Object} Content
   * @property {string} title? Title for given content.
   * @property {string} url? URL for given content.
   * @property {string} type? Type of content, e.g. `sports` or `movie`.
   * @property {number} duration? Duration in seconds.
   * @property {string} characteristics? Comma separated "tags", e.g. `dark, slow` or `bright, action`.
   * @property {string} playback? Set as `vod` or `live`.
   */

  /**
   * GreenView constructor.
   * @typedef {Object} GreenView
   * @class
   * @classdesc GreenView class.
   * @param {Object} player? Reference to video HTML object or JavaScript player object.
   * @param {Meta} meta? Device related details.
   */
  function GreenView(player, meta) {
    var PLAYER_HTML5 = 1;
    var PLAYER_BITMOVIN = 2;
    var PLAYER_DASH = 3;
    var CSS_ID = 'greenviewcss';
    var LAYER_ID = 'greenviewLayer';

    var __playerType = PLAYER_HTML5;
    var __opacity;
    var __opacityManual;
    var __callbackUseGreenView;
    var __opacityEco;
    var __callbackUseGreenViewEco;
    var __content;
    var __useExternalDiv = false;
    var __externalDiv;

    var __internal_setOpacity = function(isEco) {
      var opacity = isEco ? __opacityEco : __opacity;

      if (__opacityManual !== undefined) {
        opacity = __opacityManual;
      }

      if (__useExternalDiv) {
        __internal_setOpacity_external(__externalDiv, opacity);
        return;
      }

      switch(__playerType) {
        case PLAYER_BITMOVIN:
          __internal_setOpacity_bitmovin(opacity);
          break;

        case PLAYER_DASH:
          __internal_setOpacity_common(document.querySelector('video').parentElement, opacity);
          break;

        case PLAYER_HTML5:
          __internal_setOpacity_common(document.body, opacity);
          break;
      }
    };

    var __internal_setOpacity_bitmovin = function(opacity) {
      var styles = '.bmpui-ui-uicontainer { background: #000000 !important; opacity: ' + opacity + ' !important; }';

      var old = document.getElementById(CSS_ID);
      if (old) {
        old.parentNode.removeChild(old);
      }

      var css = document.createElement('style');
      css.type = 'text/css';
      css.id = CSS_ID;

      if (css.styleSheet) {
        css.styleSheet.cssText = styles;
      } else {
        css.appendChild(document.createTextNode(styles));
      }
      document.getElementsByTagName('head')[0].appendChild(css);
    };

    var __internal_setOpacity_external = function(layer, opacity) {
      if (!layer) return;

      layer.setAttribute('style', 'display: block; background: #000000; opacity: ' + opacity + ';');
    };

    var __internal_setOpacity_common = function(parentElement, opacity) {
      var layer = document.getElementById(LAYER_ID);
      var isNewLayer = false;
      if (!layer) {
        layer = document.createElement('div');
        layer.id = LAYER_ID;
        isNewLayer = true;
      }

      layer.setAttribute('style', 'display: block; position: absolute; width: 100%; height: 100%; top: 0; left: 0; margin: 0px; background: #000000; z-index: 1000; opacity: ' + opacity + '; pointer-events: none;');

      if (isNewLayer) {
        parentElement.appendChild(layer);
      }
    };

    var __internal_removeOpacity = function() {
      var style = document.getElementById(CSS_ID);
      if (style) {
        style.parentNode.removeChild(style);
      }

      var layer = document.getElementById(LAYER_ID);
      if (layer) {
        layer.setAttribute('style', 'display: none');
      }

      if (__useExternalDiv) {
        __externalDiv.setAttribute('style', 'display: none');
      }
    };

    var __internal_send = function(isEco) {
      var query = '';
      if (__content) {
        var keys = Object.keys(__content);
        for (var i = 0; i < keys.length; i++) {
          var key = keys[i];
          query += key + '=' + encodeURIComponent(__content[key]);

          if (i - 1 == keys.length) continue;
          query += '&';
        }
      }
      var protocol = document.location.protocol || 'http:';
      var req = new XMLHttpRequest();
      req.addEventListener('load', listener.bind(req, isEco));
      req.open('GET', protocol + '//<%= host %>/v1/api/brightness?' + query);
      req.send();
    };

    var listener = function(isEco) {
      var resp = JSON.parse(this.responseText);
      __opacity = parseInt(resp.opacity, 10) / 100;
      __opacityEco = parseInt(resp.opacityEco, 10) / 100;
      __internal_setOpacity(isEco);
      if (__callbackUseGreenView) {
        if (__callbackUseGreenView.call) __callbackUseGreenView();
        __callbackUseGreenView = undefined;
        return;
      }
      if (__callbackUseGreenViewEco) {
        if (__callbackUseGreenViewEco.call) __callbackUseGreenViewEco();
        __callbackUseGreenViewEco = undefined;
        return;
      }
    };

    /**
     * Allows to provide a custom DOM `<div>` node element that brightness will be set on.
     *
     * This `<div>` should be positioned in responsability outside of GreenView. GreenView
     * will apply brightness settings to this element only.
     *
     * Calling this function as `setElementLayer(null)` will tell GreenView to set
     * brightness internally again instead. GreenView will not remove the element itself.
     *
     * @param div {Node} The div node element that GreenView will use.
     * @name setElementLayer
     * @function
     * @memberof GreenView.prototype
     */
    this.setElementLayer = function(div) {
      if (div) {
        __useExternalDiv = true;
        __externalDiv = div;
      } else {
        __useExternalDiv = false;
        __externalDiv = undefined;
      }
    };

    /**
     * Controls status of GreenView mode.
     *
     * Will apply a fullscreen layer with default opacity.
     * @param enable {boolean} Enable or disable GreenView mode.
     * @param [cb] {Function} Callback.
     * @name useGreenView
     * @function
     * @memberof GreenView.prototype
     */
    this.useGreenView = function(enable, cb) {
      if (typeof enable === 'boolean' && !enable) {
        __internal_removeOpacity();
        if (cb && cb.call) {
          cb();
        }
        return;
      }
      if (__opacity !== undefined) {
        __internal_setOpacity();
        if (cb && cb.call) {
          cb();
        }
        return;
      }

      __callbackUseGreenView = cb;
      __internal_send('');
    };

    /**
     * Controls status of GreenView "Eco" mode.
     *
     * Will apply a fullscreen layer with opacity for "Eco" mode.
     * @param enable {boolean} Enable or disable GreenView "Eco" mode.
     * @param [cb] {Function} Callback.
     * @name useGreenViewEco
     * @function
     * @memberof GreenView.prototype
     */
    this.useGreenViewEco = function(enable, cb) {
      if (typeof enable === 'boolean' && !enable) {
        __internal_removeOpacity();
        if (cb && cb.call) {
          cb();
        }
        return;
      }
      if (__opacityEco !== undefined) {
        __internal_setOpacity(enable);
        if (cb && cb.call) {
          cb();
        }
        return;
      }

      __callbackUseGreenViewEco = cb;
      __internal_send(enable);
    };

    /**
     * Let GreenView know what content is used for playback.
     *
     * This method is optional.
     *
     * @param {Content} content
     * @name describeContent
     * @function
     * @memberof GreenView.prototype
     */
    this.describeContent = function(content) {
      __opacity = undefined;
      __opacityManual = undefined;
      __opacityEco = undefined;
      __content = content;
    };

    /**
     * Will apply a fullscreen layer with the provided opacity percentage.
     *
     * Use carefully as it will override the default opacity value.
     * @param opacity {number} A percentage number value.
     * @name setOpacity
     * @function
     * @memberof GreenView.prototype
     */
    this.setOpacity = function(opacity) {
      __opacityManual = opacity / 100;
      __internal_setOpacity();
    };

    this.getOpacity = function() {
      return __opacity * 100;
    };

    if (player && player.__proto__) {
      var proto = player.__proto__;
      if (proto.version && proto.getConfig && proto.getPlayerType) {
        __playerType = PLAYER_BITMOVIN;
      } else if (player.initialize && player.attachView && player.setProtectionData) {
        __playerType = PLAYER_DASH;
      }
    }
  }

  window.GreenView = GreenView;
})();