cloudinary-video-player
Version:
Cloudinary Video Player
640 lines (626 loc) • 19.9 kB
JavaScript
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 };