UNPKG

cloudinary-video-player

Version:
640 lines (626 loc) 19.9 kB
import { i as isPlainObject, b as camelCase, P as PLAYER_EVENT, s as sliceProperties, g as getCloudinaryUrl } from './index2.js'; import { V as VideoSource } from './video-player.js'; import { i as isInteger, P as PlaylistLayout } from './playlist-panel.js'; import { _ as _vjs } from './_videojs-proxy.js'; import './_commonjsHelpers.js'; import './video-player.const.js'; import './cloudinary-config-from-options.js'; import './cloudinary-url-prefix.js'; import './validators-functions.js'; import './toNumber.js'; // https://github.com/csnover/js-iso8601/blob/master/iso8601.js const numericKeys = [1, 4, 5, 6, 7, 10, 11]; const parseISO8601 = function (date) { let timestamp = 0; let struct = 0; let minutesOffset = 0; // ES5 §15.9.4.2 states that the string should attempt to be parsed as a Date Time String Format string // before falling back to any implementation-specific date parsing, so that’s what we do, even if native // implementations could be faster // 1 YYYY 2 MM 3 DD 4 HH 5 mm 6 ss 7 msec 8 Z 9 ± 10 tzHH 11 tzmm if (struct = /^(\d{4}|[+\-]\d{6})(?:-(\d{2})(?:-(\d{2}))?)?(?:T(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{3}))?)?(?:(Z)|([+\-])(\d{2})(?::(\d{2}))?)?)?$/.exec(date)) { // avoid NaN timestamps caused by “undefined” values being passed to Date.UTC for (let i = 0, k; k = numericKeys[i]; ++i) { struct[k] = +struct[k] || 0; } // allow undefined days and months struct[2] = (+struct[2] || 1) - 1; struct[3] = +struct[3] || 1; if (struct[8] !== 'Z' && struct[9] !== undefined) { minutesOffset = struct[10] * 60 + struct[11]; if (struct[9] === '+') { minutesOffset = 0 - minutesOffset; } } timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); } else { timestamp = NaN; } return timestamp; }; const TIME_FIELDS = ['created_at', 'updated_at']; const normalizeJsonResponse = obj => { const agg = {}; if (isPlainObject(obj)) { Object.keys(obj).reduce((agg, key) => { const newKey = camelCase(key); if (TIME_FIELDS.indexOf(key) !== -1) { agg[newKey] = new Date(parseISO8601(obj[key])); } else { agg[newKey] = normalizeJsonResponse(obj[key]); } return agg; }, agg); return agg; } else if (Array.isArray(obj)) { return obj.map(item => normalizeJsonResponse(item)); } else { return obj; } }; const DEFAULT_AUTO_ADVANCE = 0; const DEFAULT_PRESENT_UPCOMING = 10; const UPCOMING_VIDEO_TRANSITION = 1; const PLAYLIST_DEFAULTS_OPTIONS = { fluid: false, show: true, direction: 'vertical', total: 4, selector: false, renderTo: [] }; class Playlist { constructor(context) { let sources = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; let { repeat = false, autoAdvance = false, presentUpcoming = false } = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; this._context = context; this._sources = []; this._defaultRecResolverCache = {}; this._currentIndex = null; this._recommendationsHandler = null; this._autoAdvance = null; this._presentUpcoming = null; this.addUiComponents(); this.resetState = () => { this.repeat(repeat); this.autoAdvance(autoAdvance); this.presentUpcoming(presentUpcoming); }; sources.forEach(source => this.enqueue(source)); this.resetState(); } list() { return this._sources; } player() { return this._context.player; } addUiComponents() { const controlBar = this.player().getChild('ControlBar'); const children = controlBar.children(); controlBar.addChild('playlistPreviousButton', {}, children.findIndex(c => c.name_ === 'PlayToggle')); controlBar.addChild('playlistNextButton', {}, children.findIndex(c => c.name_ === 'PlayToggle') + 1); this.player().addChild('upcomingVideoOverlay'); } presentUpcoming(delay) { this._presentUpcoming = this._presentUpcoming || {}; if (delay === undefined) { return this._presentUpcoming.delay; } if (delay === true) { delay = DEFAULT_PRESENT_UPCOMING; } else if (delay === false) { delay = false; } else if (!isInteger(delay) || delay < 0) { throw new Error('presentUpcoming \'delay\' must be either a boolean or a positive integer.'); } this._presentUpcoming.delay = delay; this._setupPresentUpcoming(); return this._presentUpcoming.delay; } autoAdvance(delay) { this._autoAdvance = this._autoAdvance || {}; if (delay === undefined) { return this._autoAdvance.delay; } if (delay === true) { delay = DEFAULT_AUTO_ADVANCE; } else if (delay === false) { delay = false; } else if (!isInteger(delay) || delay < 0) { throw new Error('Auto advance \'delay\' must be either a boolean or a positive integer.'); } this._autoAdvance.delay = delay; this._setupAutoAdvance(); return this._autoAdvance.delay; } _setupAutoAdvance() { this._resetAutoAdvance(); const delay = this._autoAdvance.delay; if (delay === false) { return; } const trigger = () => { if (this.player().ended()) { this._autoAdvance.timeout = setTimeout(() => { this.playNext(); }, delay * 1000); } }; this._autoAdvance = { delay, trigger }; this._context.on('ended', this._autoAdvance.trigger); } dispose() { this._resetAutoAdvance(); this._resetPresentUpcoming(); this._resetRecommendations(); } _resetPresentUpcoming() { this.player().trigger('upcomingvideohide'); if (!this._presentUpcoming) { this._presentUpcoming = {}; } if (this._presentUpcoming.trigger) { this._context.off('timeupdate', this._presentUpcoming.trigger); } this._presentUpcoming.trigger = null; this._presentUpcoming.showTriggered = false; } _setupPresentUpcoming() { this._resetPresentUpcoming(); const delay = this._presentUpcoming.delay; if (delay === false) { return; } this._presentUpcoming.trigger = () => { const currentTime = this.player().currentTime(); const duration = this.player().duration(); const remainingTime = duration - currentTime; if (remainingTime < UPCOMING_VIDEO_TRANSITION + 0.5) { if (this._presentUpcoming.showTriggered) { this.player().trigger('upcomingvideohide'); this._presentUpcoming.showTriggered = false; } } else if (remainingTime <= this._presentUpcoming.delay && !this._presentUpcoming.showTriggered && !this.player().loop()) { this.player().trigger('upcomingvideoshow'); this._presentUpcoming.showTriggered = true; } else if (this._presentUpcoming.showTriggered && (remainingTime > this._presentUpcoming.delay || this.player().loop())) { this.player().trigger('upcomingvideohide'); this._presentUpcoming.showTriggered = false; } }; this._context.on('timeupdate', this._presentUpcoming.trigger); } _resetAutoAdvance() { if (!this._autoAdvance) { this._autoAdvance = {}; } if (this._autoAdvance.timeout) { clearTimeout(this._autoAdvance.timeout); } if (this._autoAdvance.trigger) { this._context.off('ended', this._autoAdvance.trigger); } this._autoAdvance.timeout = null; this._autoAdvance.trigger = null; } _resetRecommendations() { if (this._recommendationsHandler) { this._context.off('ended', this._recommendationsHandler); } } _refreshRecommendations() { this._resetRecommendations(); this._recommendationsHandler = () => { if (this.autoAdvance() === false && this._context.autoShowRecommendations()) { this.player().trigger('recommendationsshow'); } }; this._context.on('ended', this._recommendationsHandler); } _refreshTextTracks() { this.player().trigger('refreshTextTracks', this.currentSource().textTracks()); } _recommendationItemBuilder(source) { const defaultResolver = this._defaultRecResolverCache[source.objectId]; if (source.recommendations() && (!defaultResolver || source.recommendations() !== defaultResolver)) { return; } return source => ({ source, action: () => this.playItem(source) }); } currentIndex(index) { if (index === undefined) { return this._currentIndex; } if (index >= this.length() || index < 0) { throw new Error('Invalid playlist index.'); } this._currentIndex = index; const current = this.currentSource(); const itemBuilder = this._recommendationItemBuilder(current); if (!current.recommendations()) { current.recommendations(this._defaultRecommendationsResolver(current)); } // Extract source options for analytics const sourceOptions = current.getInitOptions ? current.getInitOptions() : {}; this._context.source(current, { ...sourceOptions, recommendationOptions: { disableAutoShow: true, itemBuilder } }); const eventData = { playlist: this, current, next: this.next() }; this.player().trigger('playlistitemchanged', eventData); this._refreshRecommendations(); this._refreshTextTracks(); return current; } _defaultRecommendationsResolver(source) { const defaultResolver = this._defaultRecResolverCache[source.objectId]; if (defaultResolver) { return defaultResolver; } this._defaultRecResolverCache[source.objectId] = () => { let index = this.list().indexOf(source); const items = []; const numOfItems = Math.min(4, this.length() - 1); while (items.length < numOfItems) { index = this.nextIndex(index); if (index === -1) { break; } const source = this.list()[index]; items.push(source); } return items; }; return this._defaultRecResolverCache[source.objectId]; } buildSource(source) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return this._context.buildSource(source, options); } enqueue(source) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const src = source instanceof VideoSource ? source : this.buildSource(source, options); this._sources.push(src); return src; } playItem(item) { let index = this.list().indexOf(item); if (index === -1) { throw new Error('Invalid playlist item.'); } this.playAtIndex(index); } playAtIndex(index) { this.currentIndex(index); this.player().play(); return this.currentSource(); } currentSource() { return this.list()[this.currentIndex()]; } removeAt(index) { if (index >= this.length() || index < 0) { throw new Error('Invalid playlist index.'); } this._sources.splice(index, 1); return this; } repeat(repeat) { if (repeat === undefined) { return this._repeat; } this._repeat = !!repeat; return this._repeat; } first() { return this.list()[0]; } last() { return this.list()[this.length() - 1]; } next() { const nextIndex = this.nextIndex(); if (nextIndex === -1) { return null; } return this.list()[nextIndex]; } nextIndex(index) { index = index !== undefined ? index : this.currentIndex(); if (index >= this.length() || index < 0) { throw new Error('Invalid playlist index.'); } const isLast = index === this.length() - 1; let nextIndex = index + 1; if (isLast) { if (this.repeat()) { nextIndex = 0; } else { return -1; } } return nextIndex; } previousIndex() { if (this.isFirst()) { return -1; } return this.currentIndex() - 1; } playFirst() { return this.playAtIndex(0); } playLast() { const lastIndex = this.list().length - 1; return this.playAtIndex(lastIndex); } isLast() { return this.currentIndex() >= this.length() - 1; } isFirst() { return this.currentIndex() === 0; } length() { return this.list().length; } playNext() { let nextIndex = this.nextIndex(); if (nextIndex === -1) { return null; } return this.playAtIndex(nextIndex); } playPrevious() { let previousIndex = this.previousIndex(); if (previousIndex === -1) { return null; } return this.playAtIndex(previousIndex); } playlistByTag = (() => { var _this = this; return function (tag) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; return _this.player().sourcesByTag(tag, options).then(sources => _this.player().playlist(sources, options)); }; })(); } class PlaylistLayoutHorizontal extends PlaylistLayout { constructor(player, options) { options.wrap = true; super(player, options); } getCls() { const cls = super.getCls(); cls.push('cld-plw-horizontal'); return cls; } } class PlaylistLayoutVertical extends PlaylistLayout { constructor(player, options) { options.wrap = true; super(player, options); } getCls() { const cls = super.getCls(); cls.push('cld-plw-vertical'); return cls; } } class PlaylistLayoutCustom extends PlaylistLayout { getCls() { let cls = super.getCls(); cls.push('cld-plw-custom'); return cls; } createEl() { const el = super.createEl(); this.options_.renderTo.appendChild(el); return el; } } const modifyOptions = (player, opt) => { const options = { ...PLAYLIST_DEFAULTS_OPTIONS, ...opt }; if (options.show && typeof options.selector === 'string') { options.useDefaultLayout = false; options.useCustomLayout = true; options.renderTo = document.querySelector(options.selector); options.showAll = true; if (!options.renderTo.length === 0) { throw new Error(`Couldn't find element(s) by selector '${options.selector}' for playlist`); } } if (options.show && !options.selector) { options.useDefaultLayout = true; options.useCustomLayout = false; } options.direction = options.direction.toLowerCase() === 'horizontal' ? 'horizontal' : 'vertical'; options.skin = player.options_.skin; return options; }; class PlaylistWidget { constructor(player) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; options = modifyOptions(player, options); this.options_ = options; this.player_ = player; this.render(); const fluidHandler = (e, fluid) => { this.options_.fluid = fluid; }; player.on(PLAYER_EVENT.FLUID, fluidHandler); this.options = options => { if (!options) { return this.options_; } this.options_ = _vjs.obj.merge(this.options_, options); player.trigger('playlistwidgetoption', this.options_.playlistWidget); return this.options_; }; this.dispose = () => { this.layout_.dispose(); player.off(PLAYER_EVENT.FLUID, fluidHandler); }; } render() { if (this.options_.useDefaultLayout) { if (this.options_.direction === 'horizontal') { this.layout_ = new PlaylistLayoutHorizontal(this.player_, this.options_); } else { this.layout_ = new PlaylistLayoutVertical(this.player_, this.options_); } } if (this.options_.useCustomLayout) { this.layout_ = new PlaylistLayoutCustom(this.player_, this.options_); } } getLayout() { return this.layout_; } update(optionName, optionValue) { this.options(optionValue); if (optionName === 'direction') { this.layout_.removeLayout(); this.layout_.dispose(); this.render(); } else { this.layout_.update(optionName, this.options_); } } setSkin() { this.layout_.setCls(); } total() { let totalNumber = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PLAYLIST_DEFAULTS_OPTIONS.total; const total = parseInt(totalNumber, 10); if (total !== this.options_.total && typeof total === 'number' && total > 0) { this.update('total', { total: total }); } return this; } direction() { let direction = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PLAYLIST_DEFAULTS_OPTIONS.direction; if (direction === 'horizontal' || direction === 'vertical') { this.update('direction', { direction: direction }); } return this; } } const LIST_BY_TAG_PARAMS = { format: 'json', resource_type: 'video', type: 'list' }; const playlist = function (player) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const chainTarget = sliceProperties(options, 'chainTarget').chainTarget; let playlistInstance = null; let playlistDisposer = null; let playlistWidget = null; const initPlaylistWidget = () => { player.on(PLAYER_EVENT.PLAYLIST_CREATED, () => { if (playlistWidget) { playlistWidget.dispose(); } if (options.playlistWidget?.show != false) { if (player.fluid_) { options.playlistWidget.fluid = true; } if (player.cloudinary.fontFace) { options.playlistWidget.fontFace = player.cloudinary.fontFace; } playlistWidget = new PlaylistWidget(player, options.playlistWidget); } }); }; const disposePlaylist = () => { player.removeClass('vjs-playlist'); playlistInstance = undefined; player.playlist().dispose(); player.off('cldsourcechanged', playlistDisposer); }; const addPlaylistDisposer = () => { const disposer = () => { if (playlistInstance && !playlistInstance.currentSource().contains(player.currentSource())) { player.disposePlaylist(); } }; player.on('cldsourcechanged', disposer); return disposer; }; const createPlaylist = (sources, options) => { if (sources instanceof Playlist) { playlistInstance = sources; playlistInstance.resetState(); playlistInstance.currentIndex(playlistInstance.currentIndex()); } else { playlistInstance = new Playlist(player.cloudinary, sources, options); playlistInstance.currentIndex(0); } initPlaylistWidget(); playlistDisposer = addPlaylistDisposer(); player.addClass('vjs-playlist'); }; player.cloudinary.sourcesByTag = async function (tag) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const url = getCloudinaryUrl(tag, Object.assign({}, player.cloudinary.cloudinaryConfig(), LIST_BY_TAG_PARAMS)); const result = await fetch(url); const json = await result.json(); const resources = normalizeJsonResponse(json.resources); if (options.sorter) { resources.sort(options.sorter); } const sources = resources.map(resource => { let sourceParams = options.sourceParams || {}; if (typeof sourceParams === 'function') { sourceParams = sourceParams(resource); } const info = resource.context && resource.context.custom || {}; const source = Object.assign({ info }, sourceParams, { publicId: resource.publicId }); return player.cloudinary.buildSource(source); }); return sources; }; return function (sources) { let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; if (sources === undefined) { return playlistInstance; } if (playlistInstance) { disposePlaylist(); } createPlaylist(sources, options); player.trigger('playlistcreated'); return chainTarget; }; }; export { playlist as default };