cloudinary-video-player
Version:
Cloudinary Video Player
133 lines (106 loc) • 4.69 kB
JavaScript
import videojs from 'video.js';
import './context-menu.scss';
import 'videojs-contextmenu';
import ContextMenu from './components/context-menu';
import { getPointerPosition } from 'utils/positioning';
import { sliceProperties } from 'utils/slicing';
import assign from 'utils/assign';
const defaults = {
showNativeOnRecurringEvent: false
};
class ContextMenuPlugin {
constructor(player, opts) {
if (!Array.isArray(opts.content) && typeof opts.content !== 'function') {
throw new Error('"content" required');
}
opts = assign({}, defaults, opts);
this.player = player;
const _options = sliceProperties(opts, 'content', 'showNativeOnRecurringEvent');
this.init = () => {
// If we are not already providing "vjs-contextmenu" events, do so.
this.player.contextmenu();
this.player.on('vjs-contextmenu', onContextMenu);
this.player.ready(() => this.player.addClass('vjs-context-menu'));
};
const getMenuPosition = (e) => {
// Calc menu size
const menuEl = this.menu.el();
// Must append to element to get bounding rect
menuEl.style.visibility = 'hidden';
this.player.el().appendChild(menuEl);
const menuSize = menuEl.getBoundingClientRect();
this.player.el().removeChild(menuEl);
menuEl.style.visibility = 'visible';
const ptrPosition = getPointerPosition(this.player.el(), e);
const playerSize = this.player.el().getBoundingClientRect();
let ptrTop = playerSize.height - (playerSize.height * ptrPosition.y) + 1;
let ptrLeft = Math.round(playerSize.width * ptrPosition.x) + 1;
let top = ptrTop;
let left = ptrLeft;
// Correct top when menu can't fit fully height-wise when pointer is at it's top left corner
if (ptrTop + menuSize.height > playerSize.height) {
let difference = ptrTop + menuSize.height - playerSize.height;
top = difference > menuSize.height / 2 ? ptrTop - menuSize.height - 1 : playerSize.height - menuSize.height;
}
// Correct left where menu can't fit fully width-wise when pointer is at it's top left corner
if (ptrLeft + menuSize.width > playerSize.width) {
let difference = ptrLeft + menuSize.width - playerSize.width;
left = difference > menuSize.width / 2 ? ptrLeft - menuSize.width - 1 : playerSize.width - menuSize.width;
}
// Correct top and left in cases that menu is positioned on the pointer
if (top < ptrTop && left < ptrLeft) {
top = ptrTop - menuSize.height - 1;
left = ptrLeft - menuSize.width - 1;
}
// Make sure that we're still in bounds after the corrections.
top = Math.max(0, top);
left = Math.max(0, left);
return { left, top };
};
const onContextMenu = (e) => {
if (this.menu) {
this.menu.dispose();
if (_options.showNativeOnRecurringEvent) {
// If this event happens while the custom menu is open, close it and do
// nothing else. This will cause native contextmenu events to be intercepted
// once again; so, the next time a contextmenu event is encountered, we'll
// open the custom menu.
return;
}
}
// Stop canceling the native contextmenu event until further notice.
if (_options.showNativeOnRecurringEvent) {
this.player.contextmenu.options.cancel = false;
}
e.preventDefault();
// Allow dynamically setting the menu labels based on player
let content = _options.content;
if (typeof content === 'function') {
content = content(this.player);
}
this.menu = new ContextMenu(this.player, { content });
const { left, top } = getMenuPosition(e);
this.menu.setPosition(left, top);
// This is to handle a bug where firefox triggers both 'contextmenu' and 'click'
// events on rightclick, causing menu to open and then immediately close.
const clickHandler = (_e) => {
if (!(_e.type === 'click' && (_e.which === 3 || _e.button === 2))) {
this.menu.dispose();
}
};
this.menu.on('dispose', () => {
// Begin canceling contextmenu events again, so subsequent events will
// cause the custom menu to be displayed again.
this.player.contextmenu.options.cancel = true;
this.player.removeChild(this.menu);
videojs.off(document, ['click', 'tap'], clickHandler);
delete this.menu;
});
this.player.addChild(this.menu);
videojs.on(document, ['click', 'tap'], clickHandler);
};
}
}
export default function(opts = {}) {
new ContextMenuPlugin(this, opts).init();
}