Source: plugin.js

import videojs from 'video.js';
import elementResizeDetectorMaker from 'element-resize-detector';
import {version as VERSION} from '../package.json';

// Default options for the plugin.
const defaults = {
  sizes: {
    mini: 450,
    mobile: 600,
  },
  controls: {
    currentTimeDisplay: {
      mini: false,
    },
    timeDivider: {
      mini: false,
    },
    durationDisplay: {
      mini: false,
    },
    remainingTimeDisplay: {
      mini: false,
      mobile: false,
    },
    captionsButton: {
      mini: false,
    },
  }
};

// // Cross-compatibility for Video.js 5 and 6.
const registerPlugin = videojs.registerPlugin || videojs.plugin;
// // const dom = videojs.dom || videojs;

/**
 * Ascending sorting for breakpoints
 *
 * @param      {Array}  previousSize  Previous breakpoint to sort
 * @param      {Array}  nextSize      Next breakpoint to sort
 * @return     {Array}  Breakpoints array sorted in ascending order
 */
const ascending = (previousSize, nextSize) => previousSize[1] - nextSize[1];

/**
 * Gets all active breakpoints that may be applied to current video player size.
 *
 * @param      {number}    playerSize  The player size.
 * @param      {Object}    sizes       Object containing all possible breakpoints.
 * @return     {Function}  The active breakpoints.
 */
const getActiveBreakpoints = (playerSize, sizes) => (
  Object.entries(sizes)
    .sort(ascending)
    .filter(size => size[1] >= playerSize)
)

/**
 * Gets the breakpoint name for the current player's size
 *
 * @param      {Player}   player  Video.js player object.
 * @param      {Object}   sizes   Object containing all possible breakpoints.
 * @return     {String}   Breakpoint name.
 */
const getPlayerSize = (player, sizes) => {
  const playerSize = player.el().clientWidth;
  const breakpoints = getActiveBreakpoints(playerSize, sizes);

  return breakpoints[0] ? breakpoints[0][0] : 'default';
}


/**
 * Controls should be visible by default so this function checks if
 * desired state was specified and returns either desired state or true.
 *
 * @param      {boolean}  setting  Desired state.
 * @return     {boolean}  Desired state or true.
 */
const trueByDefault = setting => typeof setting !== 'undefined' ? !!setting : true;

/**
 * Determines if given control is a native Video.js plugin by searching for it
 * in player.controlBar.
 *
 * @param      {Player}   player   Video.js player object.
 * @param      {String}   control  Name of the plugin.
 * @return     {boolean}  True if native, False otherwise.
 */
const isNative = (player, control) => typeof player.controlBar[control] === 'object';

/**
 * Uses Video.js API to hide or show Video.js plugin.
 *
 * @param      {Player}   player   Video.js player object.
 * @param      {Object}   control  The element to trigger.
 * @param      {boolean}  show     Desired state.
 * @return     {Function}
 */
const setNative = (player, control, show) => {
  const target = player.controlBar[control];
  return show ? target.show() : target.hide();
}

/**
 * Uses CSS to hide or show custom element. Useful for legacy plugins that
 * do not follow official plugin structure.
 *
 * @param      {Player}     player      Video.js player object.
 * @param      {String}     className   Class of the element to trigger.
 * @param      {boolean}    show        Desired state.
 * @return     {(DOMElement|undefined)}
 */
const setCustom = (player, className, show) => {
  const target = player.el().querySelectorAll(`.${className}`);
  const hiddenClass = 'vjs-hidden';

  if (target.length === 0) {
      return undefined;
  }

  return show ? target[0].classList.remove(hiddenClass) :
                target[0].classList.add(hiddenClass);
}

/**
 * Applies specified settingf to the control element based on video player's size
 *
 * @param      {Player}         player   Video.js player object.
 * @param      {Object|String}  control  Video.js plugin instance or class name.
 * @param      {boolean}        setting  Indites if the control element should be visible
 *                                       on given breakpoint.
 * @return     {Function}
 */
const set = (player, control, setting) => {
  const target = trueByDefault(setting);
  const native = isNative(player, control);

  return native ? setNative(player, control, target) :
                  setCustom(player, control, target);
}

/**
 * Applies specified settings to the video player based on it's size
 *
 * @param      {Player}  player         Video.js player object.
 * @param      {Object}  arg2           Settings object
 * @param      {Object}  arg2.sizes     Breakpoints on which the controls will update.
 * @param      {Object}  arg2.controls  Specifies states of all the controls for different breakpoints.
 * @return     {undefined}
 */
const setup = (player, { sizes, controls }) => {
  const size = getPlayerSize(player, sizes);

  for (const control in controls) {
    const setting = controls[control][size];
    set(player, control, setting);
  }
}

/**
 * Initializes new instance of resize detector
 *
 * @return     {ElementResizeDetector} Detector instance
 */
const newDetector = () => {
  return elementResizeDetectorMaker({ strategy: "scroll" });
}

/**
 * Overwrites default breakpoints with ones specified by the developer.
 *
 * @param      {Object}  settings  Settings passed on initialisation.
 * @param      {Object}  defaults  Default settings for plugin.
 * @return     {Object}  Settings to be applied.
 */
const getMediaQueries = (settings, defaults) => (
  settings && settings.sizes ? settings.sizes : defaults.sizes
);

/**
 * Uses deep-merge to merge default controls settings with ones
 * specified by the developer.
 *
 * @param      {Object}  settings  Settings paased on initialisation.
 * @param      {Object}  defaults  Default settings for plugin.
 * @return     {Object}  Settings to be applied.
 */
const mergeUserSettings = (settings, defaults) => (
  settings && settings.controls ?
  videojs.mergeOptions(defaults.controls, settings.controls) :
  defaults.controls
)

/**
 * A video.js plugin.
 *
 * In the plugin function, the value of `this` is a video.js `Player`
 * instance. You cannot rely on the player being in a "ready" state here,
 * depending on how the plugin is invoked. This may or may not be important
 * to you; if not, remove the wait for "ready"!
 *
 * @function responsiveControls
 * @param    {Object} [options={}]
 *           An object of options left to the plugin author to define.
 */
const responsiveControls = function(userSettings) {
  const settings = {
    sizes: getMediaQueries(userSettings, defaults),
    controls: mergeUserSettings(userSettings, defaults),
  };

  this.addClass('vjs-responsive-controls');
  window.resizeDetector = window.resizeDetector || newDetector();
  window.resizeDetector.listenTo(this.el(), () => setup(this, settings));
};

// Register the plugin with video.js.
registerPlugin('responsiveControls', responsiveControls);

// Include the version number.
responsiveControls.VERSION = VERSION;

export default responsiveControls;