Source: plugin.js

/**
 * @module plugin
 */
import videojs from 'video.js';

// vjs 5/6 cross compatibility.
const registerPlugin = videojs.registerPlugin || videojs.plugin;

/* eslint func-style: 0 */

const defaults = {
  cancel: true,
  sensitivity: 10,
  wait: 500,
  disabled: false
};

const EVENT_NAME = 'vjs-contextmenu';

/**
 * Abstracts a DOM standard event into a vjs-contextmenu event.
 *
 * @private
 * @param  {Player} player
 * @param  {Event} event
 *         A triggering, native event.
 * @return {Player}
 */
function sendAbstractedEvent(player, event) {
  if (player.contextmenu.options.disabled) {
    // videojs-contextmenu is disabled
    return player;
  }
  const abstracted = {
    target: player,
    type: EVENT_NAME
  };

  [
    'clientX',
    'clientY',
    'pageX',
    'pageY',
    'screenX',
    'screenY'
  ].forEach(k => {
    abstracted[k] = event[k];
  });

  return player.trigger(abstracted);
}

/**
 * Handles both touchcancel and touchend events.
 *
 * @private
 * @param  {Event} e
 */
function handleTouchEnd(e) {
  const current = this.contextmenu.current;

  if (!current) {
    return;
  }

  const wait = this.contextmenu.options.wait;

  if (e.type === 'touchend' && Number(new Date()) - current.time >= wait) {
    sendAbstractedEvent(this, e);
  }

  this.contextmenu.current = null;
}

/**
 * Handles touchmove events.
 *
 * @private
 * @param  {Event} e
 */
function handleTouchMove(e) {
  const current = this.contextmenu.current;

  if (!current) {
    return;
  }

  const touch = e.touches[0];
  const sensitivity = this.contextmenu.options.sensitivity;

  // Cancel the current touch if the pointer has moved in either direction
  // more than the sensitivity number of pixels.
  if (
    touch.screenX - current.screenX > sensitivity ||
    touch.screenY - current.screenY > sensitivity
  ) {
    this.contextmenu.current = null;
  }
}

/**
 * Handles touchstart events.
 *
 * @private
 * @param  {Event} e
 */
function handleTouchStart(e) {

  // We only care about the first touch point.
  if (this.contextmenu.current) {
    return;
  }

  const touch = e.touches[0];

  this.contextmenu.current = {
    screenX: touch.screenX,
    screenY: touch.screenY,
    time: Number(new Date())
  };
}

/**
 * Handles contextmenu events.
 *
 * @private
 * @param  {Event} e
 */
function handleContextMenu(e) {
  if (this.contextmenu.options.cancel && !this.contextmenu.options.disabled) {
    e.preventDefault();
  }

  sendAbstractedEvent(this, e);

  // If we get a "contextmenu" event, we can rely on that going forward
  // because this client supports it; so, we can stop listening for
  // touch events.
  this.off(['touchcancel', 'touchend'], handleTouchEnd);
  this.off('touchmove', handleTouchMove);
  this.off('touchstart', handleTouchStart);
}

/**
 * A cross-device context menu implementation for video.js players.
 *
 * @param    {Object}  [options={}]
 * @param    {Boolean} [cancel=true]
 *           Whether or not to cancel the native "contextmenu" event when
 *           it is seen.
 *
 * @param    {Number} [sensitivity=10]
 *           The maximum number of pixels a finger can move because a touch
 *           is no longer considered to be "held".
 *
 * @param    {Number} [wait=500]
 *           The minimum number of milliseconds a touch must be "held" before
 *           it registers.
 */
function contextmenu(options) {
  this.contextmenu.options = videojs.mergeOptions(defaults, options);
  this.contextmenu.VERSION = '__VERSION__';

  this.on('contextmenu', handleContextMenu);
  this.on(['touchcancel', 'touchend'], handleTouchEnd);
  this.on('touchmove', handleTouchMove);
  this.on('touchstart', handleTouchStart);

  this.ready(() => this.addClass(EVENT_NAME));
}

registerPlugin('contextmenu', contextmenu);
contextmenu.VERSION = '__VERSION__';

export default contextmenu;