cloudinary-video-player
Version:
Cloudinary Video Player
472 lines (462 loc) • 17.6 kB
JavaScript
import { _ as _vjs } from './_videojs-proxy.js';
import { a as get, P as PLAYER_EVENT } from './index2.js';
import { t as throttle } from './throttle.js';
import { n as noop } from './noop.js';
import { I as INTERACTION_AREA_HAND_ICON, a as INTERACTION_AREAS_PREFIX, b as INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME, c as INTERACTION_AREAS_CONTAINER_CLASS_NAME, d as INTERACTION_AREAS_THEME, C as CLOSE_INTERACTION_AREA_LAYOUT_DELAY, T as TEMPLATE_INTERACTION_AREAS_VTT, D as DEFAULT_INTERACTION_ARE_TRANSITION } from './interaction-areas.const.js';
import { e as elementsCreator, s as styleElement, b as addEventListener, d as addMetadataTrack, f as createElement } from './video-player.js';
import { getDefaultPlayerColor } from './colors.js';
import './_commonjsHelpers.js';
import './video-player.const.js';
import './cloudinary-config-from-options.js';
import './cloudinary-url-prefix.js';
import './toNumber.js';
import './validators-functions.js';
const themedButton = _ref => {
let {
text,
onClick,
theme = '',
loadingDelay = 0
} = _ref;
return elementsCreator({
tag: 'button',
attr: {
class: `vp-theme-button theme-${theme}`
},
onClick,
children: [{
tag: 'div',
attr: {
class: 'vp-loading-bar'
},
style: {
'animation-duration': `${loadingDelay}ms`
}
}, {
tag: 'div',
attr: {
class: 'content'
},
children: text
}]
});
};
const BUTTON_THEME = {
TRANSPARENT_WHITE: 'transparent-white'
};
const getInteractionAreaItemId = (item, index) => item.id || item.type || `id_${index}`;
const getInteractionAreaItem = (_ref, item, index, durationTime, onClick) => {
let {
playerOptions,
videojsOptions
} = _ref;
const defaultColor = getDefaultPlayerColor(videojsOptions);
const accentColor = playerOptions && playerOptions.colors ? playerOptions.colors.accent : defaultColor.accent;
// theme = 'pulsing' / 'shadowed'
const theme = get(videojsOptions, 'interactionDisplay.theme.template', INTERACTION_AREAS_THEME.PULSING);
return elementsCreator({
tag: 'div',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-item theme-${theme}`,
'data-id': getInteractionAreaItemId(item, index)
},
style: {
left: `${item.left}%`,
top: `${item.top}%`,
width: `${item.width}%`,
height: `${item.height}%`,
transitionDuration: `${durationTime}ms`
},
event: {
name: 'click',
callback: onClick
},
children: [{
tag: 'div',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-area-marker`
},
children: [{
tag: 'div',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-marker-shadow`
},
style: {
[theme === INTERACTION_AREAS_THEME.SHADOWED ? 'backgroundColor' : 'borderColor']: accentColor
}
}, {
tag: 'div',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-marker-main`
},
style: {
borderColor: accentColor
}
}]
}]
});
};
const percentageToFixedValue = (outOf, value) => outOf / (100 / +value);
const getZoomTransformation = (videoElement, interactionAreaItem) => {
const {
videoHeight,
videoWidth
} = videoElement;
const itemX = percentageToFixedValue(videoWidth, interactionAreaItem.left);
const itemY = percentageToFixedValue(videoHeight, interactionAreaItem.top);
const itemWidth = percentageToFixedValue(videoWidth, interactionAreaItem.width);
const itemHeight = percentageToFixedValue(videoHeight, interactionAreaItem.height);
const videoAspectRatio = videoWidth / videoHeight;
const itemAspectRatio = itemWidth / itemHeight;
const width = Math.round(itemAspectRatio > 1 || videoAspectRatio > 1 ? itemHeight * itemAspectRatio : itemWidth);
const height = Math.round(width / videoAspectRatio);
const x = Math.round(itemX - (width - itemWidth) / 2);
const y = Math.round(itemY - (height - itemHeight) / 2);
return {
width,
height,
x: Math.min(Math.max(x, 0), videoWidth - width),
y: Math.min(Math.max(y, 0), videoHeight - height),
crop: 'crop'
};
};
const setInteractionAreasContainer = (videojs, newInteractionAreasContainer) => {
const currentInteractionAreasContainer = getInteractionAreasContainer(videojs);
if (currentInteractionAreasContainer) {
currentInteractionAreasContainer.replaceWith(newInteractionAreasContainer);
} else {
// do not use element.append for ie11 support
videojs.el().appendChild(newInteractionAreasContainer);
}
};
const getInteractionAreaElementById = (interactionAreasContainer, item, index) => interactionAreasContainer.querySelector(`[data-id=${getInteractionAreaItemId(item, index)}]`);
const updateInteractionAreasItem = (videojs, configs, interactionAreasData, previousInteractionAreasData, durationTime, onClick) => {
const interactionAreasContainer = getInteractionAreasContainer(videojs);
interactionAreasData.forEach((item, index) => {
const itemElement = getInteractionAreaElementById(interactionAreasContainer, item, index);
const itemId = getInteractionAreaItemId(item);
const isExistItem = previousInteractionAreasData.some(i => getInteractionAreaItemId(i) === itemId);
// in case the element of the item is in the dom and exist in the previous data , it update the element position
if (isExistItem && itemElement) {
styleElement(itemElement, {
left: `${item.left}%`,
top: `${item.top}%`,
width: `${item.width}%`,
height: `${item.height}%`,
transitionDuration: `${durationTime}ms`
});
// if the element did not exist before , not in the dom and not in the previous data , it add a new element
} else if (!isExistItem && !itemElement) {
// do not use element.append for ie11 support
interactionAreasContainer.appendChild(getInteractionAreaItem(configs, item, index, durationTime, event => {
onClick({
event,
item,
index
});
}));
}
});
// checking the previous data for element that should be removed if not exist in the new data object.
previousInteractionAreasData.forEach((item, index) => {
const itemElement = getInteractionAreaElementById(interactionAreasContainer, item, index);
const itemId = getInteractionAreaItemId(item);
const shouldBeRemoved = !interactionAreasData.some(i => getInteractionAreaItemId(i) === itemId);
if (itemElement && shouldBeRemoved) {
// do not use element.remove for ie11 support
itemElement.parentNode.removeChild(itemElement);
}
});
};
const shouldShowAreaLayoutMessage = interactionAreasConfig => {
const isLayoutEnabled = get(interactionAreasConfig, 'layout.enable', true);
return isLayoutEnabled && localStorage.getItem(INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME) !== 'true';
};
const onClickInteractionAreaLayoutClick = function (checked) {
let onClick = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : noop;
localStorage.setItem(INTERACTION_AREA_LAYOUT_LOCAL_STORAGE_NAME, JSON.parse(checked));
onClick();
};
const createInteractionAreaLayoutMessage = function (videojs, onClick) {
let showItAgainCheckbox = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
let checked = false;
const id = `checkbox_${Math.round(Math.random() * 10000)}`;
const tracksContainer = elementsCreator({
tag: 'div',
attr: {
class: `${INTERACTION_AREAS_CONTAINER_CLASS_NAME} ${INTERACTION_AREAS_PREFIX}-layout-message ${showItAgainCheckbox ? '' : 'clickable'}`
},
onClick: !showItAgainCheckbox ? () => onClickInteractionAreaLayoutClick(checked, onClick) : null,
children: [{
tag: 'img',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-layout-icon`,
src: INTERACTION_AREA_HAND_ICON
}
}, {
tag: 'h3',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-layout-message-title`
},
children: 'Tap on dots to zoom for a product.'
}, themedButton({
text: 'Got it',
theme: BUTTON_THEME.TRANSPARENT_WHITE,
loadingDelay: showItAgainCheckbox ? 0 : CLOSE_INTERACTION_AREA_LAYOUT_DELAY,
onClick: showItAgainCheckbox ? () => onClickInteractionAreaLayoutClick(checked, onClick) : null
}), showItAgainCheckbox && {
tag: 'div',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-layout-message-do-not-show`
},
children: [{
tag: 'input',
attr: {
type: 'checkbox',
class: `${INTERACTION_AREAS_PREFIX}-layout-message-checkbox`,
id
},
event: {
name: 'input',
callback: event => {
checked = event.target.checked;
}
}
}, {
tag: 'label',
attr: {
class: `${INTERACTION_AREAS_PREFIX}-layout-message-checkbox-title`,
for: id
},
children: 'Don׳t show it again'
}]
}].filter(i => i)
});
setInteractionAreasContainer(videojs, tracksContainer);
};
const getInteractionAreasContainer = videojs => videojs.el().querySelector(`.${INTERACTION_AREAS_CONTAINER_CLASS_NAME}`);
const removeInteractionAreasContainer = videojs => {
const interactionAreasContainer = getInteractionAreasContainer(videojs);
// do not use element.remove for ie11 support
interactionAreasContainer && interactionAreasContainer.parentNode.removeChild(interactionAreasContainer);
};
const setInteractionAreasContainerSize = (videojs, videoElement) => {
const interactionAreasContainer = getInteractionAreasContainer(videojs);
if (!interactionAreasContainer) {
return;
}
const {
videoHeight,
videoWidth
} = videoElement;
const videoAspectRatio = videoWidth / videoHeight;
const width = videoAspectRatio * videoElement.clientHeight;
interactionAreasContainer.style.width = `${videoElement.clientWidth < width ? '100%' : width}px`;
interactionAreasContainer.style.height = videoElement.clientWidth < width ? `${videoElement.clientWidth / videoAspectRatio}px` : '100%';
};
const interactionAreasService = (player, playerOptions, videojsOptions) => {
let isZoomed = false;
let currentSource = null;
let currentTrack = null;
let unZoom = noop;
const shouldLayoutMessage = () => shouldShowAreaLayoutMessage(videojsOptions.interactionDisplay);
const getIsSyncOffsetTime = () => {
const interactionAreasConfig = getInteractionAreasConfig();
return interactionAreasConfig && interactionAreasConfig.syncOffsetTime !== undefined ? interactionAreasConfig.syncOffsetTime : false;
};
function isInteractionAreasEnabled() {
let enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
const interactionAreasConfig = getInteractionAreasConfig();
return enabled || interactionAreasConfig && interactionAreasConfig.enable;
}
function setAreasPositionListener() {
currentTrack && player.videojs.removeRemoteTextTrack(currentTrack);
const isEnabled = isInteractionAreasEnabled();
const interactionAreasConfig = getInteractionAreasConfig();
if (!isEnabled || isZoomed) {
return null;
}
if (Array.isArray(interactionAreasConfig.template)) {
addInteractionAreasItems(interactionAreasConfig.template);
setContainerSize();
} else {
const vttUrl = interactionAreasConfig.vttUrl || TEMPLATE_INTERACTION_AREAS_VTT[interactionAreasConfig.template];
if (vttUrl) {
currentTrack = addMetadataTrack(player.videojs, vttUrl);
addCueListener(currentTrack);
}
}
}
function setGoBackButton() {
const button = createElement('div', {
'class': 'go-back-button'
});
button.addEventListener('click', () => {
unZoom();
}, false);
const tracksContainer = createElement('div', {
'class': INTERACTION_AREAS_CONTAINER_CLASS_NAME
}, button);
setInteractionAreasContainer(player.videojs, tracksContainer);
}
function getInteractionAreasConfig() {
const {
cldSrc
} = currentSource;
return cldSrc && cldSrc.interactionAreas();
}
function removeLayoutMessage() {
removeInteractionAreasContainer(player.videojs);
setAreasPositionListener();
player.play();
}
function setLayoutMessage() {
if (!isInteractionAreasEnabled()) {
return;
}
if (shouldLayoutMessage()) {
let layoutMessageTimout = null;
const showItAgainCheckbox = get(videojsOptions, 'interactionDisplay.layout.showAgain', false);
player.pause();
createInteractionAreaLayoutMessage(player.videojs, () => {
clearTimeout(layoutMessageTimout);
removeLayoutMessage();
}, showItAgainCheckbox);
if (!showItAgainCheckbox) {
layoutMessageTimout = setTimeout(removeLayoutMessage, CLOSE_INTERACTION_AREA_LAYOUT_DELAY);
}
} else {
removeLayoutMessage();
}
}
function handleAds() {
player.on('adsready', () => {
player.videojs.ima.addEventListener(window.google.ima.AdEvent.Type.ALL_ADS_COMPLETED, setLayoutMessage);
});
}
function init() {
currentSource = currentSource || player.videojs.currentSource();
if (isInteractionAreasEnabled()) {
player.videojs.el().classList.add('interaction-areas');
player.videojs.one(PLAYER_EVENT.PLAY, () => {
if (typeof player.videojs.ima === 'object') {
handleAds();
} else {
setLayoutMessage();
}
});
const setInteractionAreasContainerSize = throttle(setContainerSize, 100);
player.videojs.on(PLAYER_EVENT.FULL_SCREEN_CHANGE, () => {
// waiting for fullscreen will end
setTimeout(setInteractionAreasContainerSize, 100);
});
const resizeDestroy = addEventListener(window, 'resize', setContainerSize);
player.videojs.on(PLAYER_EVENT.DISPOSE, resizeDestroy);
}
player.videojs.on(PLAYER_EVENT.ENDED, () => {
unZoom();
});
player.videojs.on(PLAYER_EVENT.ERROR, () => {
player.pause();
});
}
function onZoom(src, newOption, item) {
const originalCurrentTime = player.currentTime();
const isSyncOffsetTime = getIsSyncOffsetTime();
const {
cldSrc
} = currentSource;
const currentSrcOptions = cldSrc.getInitOptions();
const option = newOption || {
transformation: currentSrcOptions.transformation
};
const transformation = !src && getZoomTransformation(player.videoElement, item);
const sourceOptions = transformation ? _vjs.obj.merge({
transformation
}, option) : option;
const newSource = cldSrc.isRawUrl ? currentSource.src : {
publicId: cldSrc.publicId()
};
player.source(transformation ? {
publicId: cldSrc.publicId()
} : src, sourceOptions).play();
isSyncOffsetTime && player.currentTime(originalCurrentTime);
isZoomed = true;
setGoBackButton();
unZoom = () => {
if (isZoomed) {
isZoomed = false;
const currentZoomedTime = player.currentTime();
const duration = player.duration();
player.source(newSource, currentSrcOptions).play();
isSyncOffsetTime && currentZoomedTime < duration && player.currentTime(currentZoomedTime);
setAreasPositionListener();
}
};
}
function onInteractionAreasClick(_ref) {
let {
event,
item,
index
} = _ref;
const interactionAreasConfig = getInteractionAreasConfig();
interactionAreasConfig.onClick && interactionAreasConfig.onClick({
item,
index,
event,
zoom: (source, option) => {
onZoom(source, option, item);
}
});
}
function addInteractionAreasItems(interactionAreasData, previousInteractionAreasData) {
let durationTime = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
const configs = {
playerOptions: playerOptions,
videojsOptions: videojsOptions
};
if (previousInteractionAreasData) {
updateInteractionAreasItem(player.videojs, configs, interactionAreasData, previousInteractionAreasData, durationTime, onInteractionAreasClick);
} else {
const interactionAreasItems = interactionAreasData.map((item, index) => {
return getInteractionAreaItem(configs, item, index, durationTime, event => {
onInteractionAreasClick({
event,
item,
index
});
});
});
setInteractionAreasContainer(player.videojs, createElement('div', {
'class': INTERACTION_AREAS_CONTAINER_CLASS_NAME
}, interactionAreasItems));
}
}
function setContainerSize() {
if (isInteractionAreasEnabled()) {
setInteractionAreasContainerSize(player.videojs, player.videoElement);
}
}
function addCueListener(track) {
if (!track) {
return;
}
let previousTracksData = null;
track.addEventListener('cuechange', () => {
const activeCue = track.activeCues && track.activeCues[0];
if (activeCue) {
const durationTime = Math.max(Math.floor((activeCue.endTime - activeCue.startTime) * 1000), DEFAULT_INTERACTION_ARE_TRANSITION);
const tracksData = JSON.parse(activeCue.text);
addInteractionAreasItems(tracksData, previousTracksData, durationTime);
!previousTracksData && setContainerSize();
previousTracksData = tracksData;
} else {
removeInteractionAreasContainer(player.videojs);
previousTracksData = null;
}
});
}
init();
};
export { interactionAreasService };