UNPKG

videojs-sprite-thumbnails

Version:

Plugin to display thumbnails when hovering over the progress bar.

277 lines (259 loc) 10.5 kB
/*! @name videojs-sprite-thumbnails @version 2.2.3 @license MIT */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) : typeof define === 'function' && define.amd ? define(['video.js'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsSpriteThumbnails = factory(global.videojs)); })(this, (function (videojs) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs); /** * Set up sprite thumbnails for a player. * * @function spriteThumbs * @param {Player} player * The current player instance. * @param {Plugin} plugin * The current spriteThumbnails plugin instance. * @param {Object} options * Plugin configuration options. */ const spriteThumbs = (player, plugin, options) => { const navigator = window.navigator; const dom = videojs__default["default"].dom; const obj = videojs__default["default"].obj; const log = plugin.log; const debug = log.debug; const defaultState = { ...plugin.state }; const setDefaultState = () => { plugin.setState(defaultState); }; // default control bar component tree is expected // https://docs.videojs.com/tutorial-components.html#default-component-tree const descendants = ['ControlBar', 'ProgressControl', 'SeekBar', 'MouseTimeDisplay', 'TimeTooltip']; const [_controlBar, _progressControl, _seekBar, // no need to assign MouseTimeDisplay , _timeTooltip] = descendants; const playerDescendant = componentName => { const idx = descendants.indexOf(componentName); const component = player.getDescendant(descendants.slice(0, idx + 1)); if (!component) { setDefaultState(); debug(`component tree ${descendants.join(' > ')} required`); } return component; }; let tooltipEl; let tooltipStyleOrig; const getUrl = idx => { const urlArray = options.urlArray; return urlArray.length ? urlArray[idx] : options.url.replace('{index}', options.idxTag(idx)); }; const hijackMouseTooltip = evt => { if (!playerDescendant(_timeTooltip)) { return; } const seekBarEl = playerDescendant(_seekBar).el(); const controlsTop = dom.findPosition(playerDescendant(_controlBar).el()).top; const playerWidth = player.currentWidth(); const duration = player.duration(); const interval = options.interval; const columns = options.columns; const responsive = options.responsive; const rowDuration = interval * columns; // spriteDuration is needed to calculate idx const spriteDuration = rowDuration * (options.rows || Math.ceil(duration / rowDuration)); let position = dom.getPointerPosition(seekBarEl, evt).x * duration; // for single sprites idx is always 0 const idx = Math.floor(position / spriteDuration); // if (idx == 0) position /= interval position = (position - spriteDuration * idx) / interval; const scaleFactor = responsive && playerWidth < responsive ? playerWidth / responsive : 1; const scaledWidth = options.width * scaleFactor; const scaledHeight = options.height * scaleFactor; const cleft = Math.floor(position % columns) * -scaledWidth; const ctop = Math.floor(position / columns) * -scaledHeight; const seekBarTop = dom.findPosition(seekBarEl).top; // top of seekBar is 0 position const topOffset = -scaledHeight - Math.max(0, seekBarTop - controlsTop); const tooltipStyle = { backgroundImage: `url("${getUrl(idx)}")`, backgroundRepeat: 'no-repeat', backgroundPosition: `${cleft}px ${ctop}px`, backgroundSize: `${scaledWidth * columns}px auto`, top: `${topOffset}px`, color: '#fff', textShadow: '1px 1px #000', // box-sizing: border-box inherited from .video-js border: '1px solid #000', // border should not overlay thumbnail area width: `${scaledWidth + 2}px`, height: `${scaledHeight + 2}px` }; obj.each(tooltipStyle, (value, key) => { tooltipEl.style[key] = value; }); }; const intCheck = opt => { const val = options[opt]; const min = opt !== 'rows' ? 1 : 0; const check = parseInt(val, 10) === val && val >= min; if (!check) { log.warn(`${opt} must be an integer greater than ${min - 1}`); } return check; }; const downlinkCheck = () => { const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection; const dl = 'downlink'; const check = !connection || connection[dl] >= options[dl]; if (!check) { log(`connection.${dl} < ${options[dl]}`); } return check; }; const handleStateChanged = evt => { const pstate = plugin.state; const spriteEvents = ['mousemove', 'touchmove']; const progress = playerDescendant(_progressControl); if (pstate.ready) { debug('ready to show thumbnails'); progress.on(spriteEvents, hijackMouseTooltip); } else { if (!options.url && !options.urlArray.length) { debug('no urls given, resetting'); } if (progress) { progress.off(spriteEvents, hijackMouseTooltip); tooltipEl.style = tooltipStyleOrig; } } player.toggleClass('vjs-thumbnails-ready', pstate.ready); }; const init = evt => { // `loadstart` callback is only needed when all of the following apply: // - player is set up to load an initial video via `src` or `loadMedia` // specifying a `spriteThumbnails` config object // - the player is told e.g. by user action to load a different video `src` // or `loadMedia` before metadata of the initial video is loaded and its // `spriteThumbnails` options cannot be merged // Thereafter the `loadstart` callback is redundant. player.off('loadstart', init); // clean slate setDefaultState(); // if present, merge source config with current config const plugName = plugin.name; const thumbSource = player.currentSources().find(source => { return source.hasOwnProperty(plugName); }); let srcOpts = thumbSource && thumbSource[plugName]; if (srcOpts) { // empty config unsets url and urlArray // force urlArray or url according to precedence const urlArray = srcOpts.urlArray; if (!Object.keys(srcOpts).length) { srcOpts = { url: '', urlArray: [] }; } else if (urlArray && urlArray.length) { srcOpts.url = ''; } else if (srcOpts.url) { srcOpts.urlArray = []; } plugin.options = options = obj.merge(options, srcOpts); } const mouseTimeTooltip = playerDescendant(_timeTooltip); if (!mouseTimeTooltip || evt.type === 'loadstart') { return; } tooltipEl = mouseTimeTooltip.el(); tooltipStyleOrig = tooltipEl.style; plugin.setState({ ready: !!((options.urlArray.length || options.url) && intCheck('width') && intCheck('height') && intCheck('columns') && intCheck('rows') && downlinkCheck()) }); }; plugin.on('statechanged', handleStateChanged); player.on(['loadstart', 'loadedmetadata'], init); player.addClass('vjs-sprite-thumbnails'); }; var version = "2.2.3"; const Plugin = videojs__default["default"].getPlugin('plugin'); /** * Default plugin options * * @param {String} url * Location of image(s). Must be set by user. For multiple images the * filename must contain the template {index} which is replaced by the * zero based index number of the image in the sequence. Default: ''. * @param {Array} urlArray * Array of image locations. Default: []. * @param {Integer} width * Width of a thumbnail in pixels. Must be set by user. Default: 0. * @param {Integer} height * Height of a thumbnail in pixels. Must be set by user. Default: 0. * @param {Integer} columns * Number of thumbnail columns per image. Must be set by user. * @param {Integer} rows * Number of thumbnail rows per image. If set to greater than 0, the * plugin will expect a sequence of images. Default: 0. * @param {Number} interval * Interval between thumbnail frames in seconds. Default: 1. * @param {Function} idxTag * Function determining the substitiuton of the {index} template in the * current url. Default: returns index as is. * @param {Integer} responsive * Width of player below which thumbnails are reponsive. Default: 600. * @param {Number} downlink * Minimum of NetworkInformation downlink where supported. Default: 1.5. * https://developer.mozilla.org/docs/Web/API/NetworkInformation/downlink */ const defaults = { url: '', idxTag(i) { return i; }, urlArray: [], width: 0, height: 0, columns: 0, rows: 0, interval: 1, responsive: 600, downlink: 1.5 }; /** * An advanced Video.js plugin. For more information on the API * * See: https://blog.videojs.com/feature-spotlight-advanced-plugins/ */ class SpriteThumbnails extends Plugin { /** * Create a SpriteThumbnails plugin instance. * * @param {Player} player * A Video.js Player instance. * * @param {Object} [options] * An optional options object. */ constructor(player, options) { // the parent class will add player under this.player super(player, options); this.options = videojs__default["default"].obj.merge(defaults, options); this.player.ready(() => { spriteThumbs(this.player, this, this.options); }); } } // Define default values for the plugin's `state` object here. SpriteThumbnails.defaultState = { ready: false }; // Include the version number. SpriteThumbnails.VERSION = version; // Register the plugin with video.js. videojs__default["default"].registerPlugin('spriteThumbnails', SpriteThumbnails); return SpriteThumbnails; }));