UNPKG

videojs-sprite-thumbnails

Version:

Plugin to display thumbnails when hovering over the progress bar.

220 lines (187 loc) 6.82 kB
import videojs from 'video.js'; import window from 'global/window'; /** * 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.dom; const obj = videojs.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'); }; export default spriteThumbs;