UNPKG

videojs-overlay

Version:

A plugin to display simple overlays during video playback.

391 lines (309 loc) 12.3 kB
/*! @name videojs-overlay @version 2.1.4 @license Apache-2.0 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js'), require('global/window')) : typeof define === 'function' && define.amd ? define(['video.js', 'global/window'], factory) : (global.videojsOverlay = factory(global.videojs,global.window)); }(this, (function (videojs,window) { 'use strict'; videojs = videojs && videojs.hasOwnProperty('default') ? videojs['default'] : videojs; window = window && window.hasOwnProperty('default') ? window['default'] : window; function _inheritsLoose(subClass, superClass) { subClass.prototype = Object.create(superClass.prototype); subClass.prototype.constructor = subClass; subClass.__proto__ = superClass; } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } var version = "2.1.4"; var defaults = { align: 'top-left', class: '', content: 'This overlay will show up while the video is playing', debug: false, showBackground: true, attachToControlBar: false, overlays: [{ start: 'playing', end: 'paused' }] }; var Component = videojs.getComponent('Component'); var dom = videojs.dom || videojs; var registerPlugin = videojs.registerPlugin || videojs.plugin; /** * Whether the value is a `Number`. * * Both `Infinity` and `-Infinity` are accepted, but `NaN` is not. * * @param {Number} n * @return {Boolean} */ /* eslint-disable no-self-compare */ var isNumber = function isNumber(n) { return typeof n === 'number' && n === n; }; /* eslint-enable no-self-compare */ /** * Whether a value is a string with no whitespace. * * @param {String} s * @return {Boolean} */ var hasNoWhitespace = function hasNoWhitespace(s) { return typeof s === 'string' && /^\S+$/.test(s); }; /** * Overlay component. * * @class Overlay * @extends {videojs.Component} */ var Overlay = /*#__PURE__*/ function (_Component) { _inheritsLoose(Overlay, _Component); function Overlay(player, options) { var _this; _this = _Component.call(this, player, options) || this; ['start', 'end'].forEach(function (key) { var value = _this.options_[key]; if (isNumber(value)) { _this[key + 'Event_'] = 'timeupdate'; } else if (hasNoWhitespace(value)) { _this[key + 'Event_'] = value; // An overlay MUST have a start option. Otherwise, it's pointless. } else if (key === 'start') { throw new Error('invalid "start" option; expected number or string'); } }); // video.js does not like components with multiple instances binding // events to the player because it tracks them at the player level, // not at the level of the object doing the binding. This could also be // solved with Function.prototype.bind (but not videojs.bind because of // its GUID magic), but the anonymous function approach avoids any issues // caused by crappy libraries clobbering Function.prototype.bind. // - https://github.com/videojs/video.js/issues/3097 ['endListener_', 'rewindListener_', 'startListener_'].forEach(function (name$$1) { _this[name$$1] = function (e) { return Overlay.prototype[name$$1].call(_assertThisInitialized(_assertThisInitialized(_this)), e); }; }); // If the start event is a timeupdate, we need to watch for rewinds (i.e., // when the user seeks backward). if (_this.startEvent_ === 'timeupdate') { _this.on(player, 'timeupdate', _this.rewindListener_); } _this.debug("created, listening to \"" + _this.startEvent_ + "\" for \"start\" and \"" + (_this.endEvent_ || 'nothing') + "\" for \"end\""); _this.hide(); return _this; } var _proto = Overlay.prototype; _proto.createEl = function createEl() { var options = this.options_; var content = options.content; var background = options.showBackground ? 'vjs-overlay-background' : 'vjs-overlay-no-background'; var el = dom.createEl('div', { className: "\n vjs-overlay\n vjs-overlay-" + options.align + "\n " + options.class + "\n " + background + "\n vjs-hidden\n " }); if (typeof content === 'string') { el.innerHTML = content; } else if (content instanceof window.DocumentFragment) { el.appendChild(content); } else { dom.appendContent(el, content); } return el; }; /** * Logs debug errors * @param {...[type]} args [description] * @return {[type]} [description] */ _proto.debug = function debug() { if (!this.options_.debug) { return; } var log = videojs.log; var fn = log; // Support `videojs.log.foo` calls. for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } if (log.hasOwnProperty(args[0]) && typeof log[args[0]] === 'function') { fn = log[args.shift()]; } fn.apply(void 0, ["overlay#" + this.id() + ": "].concat(args)); }; /** * Overrides the inherited method to perform some event binding * * @return {Overlay} */ _proto.hide = function hide() { _Component.prototype.hide.call(this); this.debug('hidden'); this.debug("bound `startListener_` to \"" + this.startEvent_ + "\""); // Overlays without an "end" are valid. if (this.endEvent_) { this.debug("unbound `endListener_` from \"" + this.endEvent_ + "\""); this.off(this.player(), this.endEvent_, this.endListener_); } this.on(this.player(), this.startEvent_, this.startListener_); return this; }; /** * Determine whether or not the overlay should hide. * * @param {Number} time * The current time reported by the player. * @param {String} type * An event type. * @return {Boolean} */ _proto.shouldHide_ = function shouldHide_(time, type) { var end = this.options_.end; return isNumber(end) ? time >= end : end === type; }; /** * Overrides the inherited method to perform some event binding * * @return {Overlay} */ _proto.show = function show() { _Component.prototype.show.call(this); this.off(this.player(), this.startEvent_, this.startListener_); this.debug('shown'); this.debug("unbound `startListener_` from \"" + this.startEvent_ + "\""); // Overlays without an "end" are valid. if (this.endEvent_) { this.debug("bound `endListener_` to \"" + this.endEvent_ + "\""); this.on(this.player(), this.endEvent_, this.endListener_); } return this; }; /** * Determine whether or not the overlay should show. * * @param {Number} time * The current time reported by the player. * @param {String} type * An event type. * @return {Boolean} */ _proto.shouldShow_ = function shouldShow_(time, type) { var start = this.options_.start; var end = this.options_.end; if (isNumber(start)) { if (isNumber(end)) { return time >= start && time < end; // In this case, the start is a number and the end is a string. We need // to check whether or not the overlay has shown since the last seek. } else if (!this.hasShownSinceSeek_) { this.hasShownSinceSeek_ = true; return time >= start; } // In this case, the start is a number and the end is a string, but // the overlay has shown since the last seek. This means that we need // to be sure we aren't re-showing it at a later time than it is // scheduled to appear. return Math.floor(time) === start; } return start === type; }; /** * Event listener that can trigger the overlay to show. * * @param {Event} e */ _proto.startListener_ = function startListener_(e) { var time = this.player().currentTime(); if (this.shouldShow_(time, e.type)) { this.show(); } }; /** * Event listener that can trigger the overlay to show. * * @param {Event} e */ _proto.endListener_ = function endListener_(e) { var time = this.player().currentTime(); if (this.shouldHide_(time, e.type)) { this.hide(); } }; /** * Event listener that can looks for rewinds - that is, backward seeks * and may hide the overlay as needed. * * @param {Event} e */ _proto.rewindListener_ = function rewindListener_(e) { var time = this.player().currentTime(); var previous = this.previousTime_; var start = this.options_.start; var end = this.options_.end; // Did we seek backward? if (time < previous) { this.debug('rewind detected'); // The overlay remains visible if two conditions are met: the end value // MUST be an integer and the the current time indicates that the // overlay should NOT be visible. if (isNumber(end) && !this.shouldShow_(time)) { this.debug("hiding; " + end + " is an integer and overlay should not show at this time"); this.hasShownSinceSeek_ = false; this.hide(); // If the end value is an event name, we cannot reliably decide if the // overlay should still be displayed based solely on time; so, we can // only queue it up for showing if the seek took us to a point before // the start time. } else if (hasNoWhitespace(end) && time < start) { this.debug("hiding; show point (" + start + ") is before now (" + time + ") and end point (" + end + ") is an event"); this.hasShownSinceSeek_ = false; this.hide(); } } this.previousTime_ = time; }; return Overlay; }(Component); videojs.registerComponent('Overlay', Overlay); /** * Initialize the plugin. * * @function plugin * @param {Object} [options={}] */ var plugin = function plugin(options) { var _this2 = this; var settings = videojs.mergeOptions(defaults, options); // De-initialize the plugin if it already has an array of overlays. if (Array.isArray(this.overlays_)) { this.overlays_.forEach(function (overlay) { _this2.removeChild(overlay); if (_this2.controlBar) { _this2.controlBar.removeChild(overlay); } overlay.dispose(); }); } var overlays = settings.overlays; // We don't want to keep the original array of overlay options around // because it doesn't make sense to pass it to each Overlay component. delete settings.overlays; this.overlays_ = overlays.map(function (o) { var mergeOptions = videojs.mergeOptions(settings, o); var attachToControlBar = typeof mergeOptions.attachToControlBar === 'string' || mergeOptions.attachToControlBar === true; if (!_this2.controls() || !_this2.controlBar) { return _this2.addChild('overlay', mergeOptions); } if (attachToControlBar && mergeOptions.align.indexOf('bottom') !== -1) { var referenceChild = _this2.controlBar.children()[0]; if (_this2.controlBar.getChild(mergeOptions.attachToControlBar) !== undefined) { referenceChild = _this2.controlBar.getChild(mergeOptions.attachToControlBar); } if (referenceChild) { var controlBarChild = _this2.controlBar.addChild('overlay', mergeOptions); _this2.controlBar.el().insertBefore(controlBarChild.el(), referenceChild.el()); return controlBarChild; } } var playerChild = _this2.addChild('overlay', mergeOptions); _this2.el().insertBefore(playerChild.el(), _this2.controlBar.el()); return playerChild; }); }; plugin.VERSION = version; registerPlugin('overlay', plugin); return plugin; })));