cloudinary-video-player
Version:
Cloudinary Video Player
573 lines (554 loc) • 20 kB
JavaScript
import { _ as _vjs } from './_videojs-proxy.js';
import { P as PLAYER_EVENT, f as find, I as ImageSource } from './index2.js';
import { t as throttle } from './throttle.js';
// Convert time string i.e. '2:40' to seconds number (160)
// Also allows h:m:s format and mm:ss, m:s etc.
const parseTime = function (hms) {
const [seconds, minutes, hours] = hms.split(':').reverse();
let sum = null;
if (!isNaN(seconds)) {
sum = (+hours || 0) * 60 * 60 + (+minutes || 0) * 60 + +seconds;
}
return sum;
};
const SHOPPABLE_WIDGET_OPTIONS_DEFAULTS = {
location: 'right',
toggleIcon: '',
width: '20%',
startState: 'openOnPlay',
autoClose: 2,
transformation: {
quality: 'auto',
width: 'auto',
fetch_format: 'auto',
crop: 'scale'
},
products: [],
showPostPlayOverlay: false
};
const SHOPPABLE_CLICK_ACTIONS = {
GO_TO: 'goto',
SEEk: 'seek'
};
const SHOPPABLE_HOVER_ACTIONS = {
OVERLAY: 'overlay'
};
const SHOPPABLE_PANEL_VISIBLE_CLASS = 'shoppable-panel-visible';
const SHOPPABLE_PANEL_HIDDEN_CLASS = 'shoppable-panel-hidden';
const SHOPPABLE_PRODUCTS_OVERLAY_CLASS = 'shoppable-products-overlay';
const CLD_SPBL_PANEL_CLASS = 'cld-spbl-panel';
const CLD_SPBL_TOGGLE_CLASS = 'cld-spbl-toggle';
const CLD_SPBL_TOGGLE_ICON_CLASS = 'cld-spbl-toggle-icon';
const CLD_SPBL_INNER_BAR = 'cld-spbl-bar-inner';
const CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS = 'cld-spbl-toggle-custom-icon';
const ICON_CART_CLASS = 'vjs-icon-cart';
const CLOSE_ICON_CLASS = 'vjs-icon-close';
const SHOPPABLE_ANIMATION_CLASS = 'animate';
const CLD_SPBL_ITEM = 'cld-spbl-item';
const CLD_SPBL_IMAGE = 'cld-spbl-img';
const dom$4 = _vjs.dom || _vjs;
const Component$2 = _vjs.getComponent('Component');
class ShoppableProductsOverlay extends Component$2 {
constructor(player) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
super(player, options);
this.options_ = options;
this.player_ = player;
this.player_.on(PLAYER_EVENT.SHOW_PRODUCTS_OVERLAY, this.renderProducts);
this.dispose = () => {
this.layout_.dispose();
};
}
renderProducts = () => {
// Close products side-panel
this.player_.removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
this.player_.addClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
this.player_.addClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS);
this.layout_.innerHTML = '';
// Filter products with appearance on currentTime
const currentTime = this.player_.currentTime();
const currentProducts = this.options_.products.filter(product => product.hotspots && product.hotspots.some(a => parseTime(a.time) === currentTime));
currentProducts.forEach(product => {
const hotspot = find(product.hotspots, hs => parseTime(hs.time) === currentTime);
const productName = dom$4.createEl('div', {
className: 'cld-spbl-product-hotspot-name'
}, {}, product.productName);
const productTooltip = dom$4.createEl('div', {
className: 'cld-spbl-product-tooltip cld-spbl-product-tooltip-' + hotspot.tooltipPosition
}, {}, productName);
const productHotSpot = dom$4.createEl('a', {
className: 'cld-spbl-product-hotspot accent-color-text',
href: hotspot.clickUrl,
target: '_blank'
}, {
style: 'left:' + hotspot.x + '; top:' + hotspot.y + ';'
}, productTooltip);
this.layout_.appendChild(productHotSpot);
});
// Remove
this.player_.one(PLAYER_EVENT.SEEKING, this.clearLayout);
this.player_.one(PLAYER_EVENT.PLAY, this.clearLayout);
};
clearLayout = () => {
this.layout_.innerHTML = '';
this.player_.removeClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS);
};
createEl() {
const dimensions = this.player_.currentDimensions();
this.layout_ = dom$4.createEl('div', {
className: 'cld-spbl-products-overlay',
style: `padding-top: ${dimensions.height / dimensions.width * 100}%;`
});
return this.layout_;
}
}
_vjs.registerComponent('ShoppableProductsOverlay', ShoppableProductsOverlay);
const dom$3 = _vjs.dom || _vjs;
const ClickableComponent$1 = _vjs.getComponent('ClickableComponent');
class ShoppablePanelToggle extends ClickableComponent$1 {
constructor(player, options) {
super(player, options);
this.options_ = options;
}
handleClick(event) {
event.preventDefault();
event.stopPropagation();
this.options_.clickHandler();
}
createEl() {
let iconProps = {};
let iconAttrs = {};
if (this.options_.toggleIcon) {
iconProps = {
className: `${CLD_SPBL_TOGGLE_ICON_CLASS} ${CLD_SPBL_TOGGLE_CUSTOM_ICON_CLASS} ${CLOSE_ICON_CLASS}`
};
iconAttrs = {
style: `background-image: url(${this.options_.toggleIcon})`
};
} else {
iconProps = {
className: `${CLD_SPBL_TOGGLE_ICON_CLASS} ${ICON_CART_CLASS}`
};
}
const icon = dom$3.createEl('span', iconProps, iconAttrs);
const el = super.createEl('a', {
className: `${CLD_SPBL_TOGGLE_CLASS} base-color-bg`
});
el.appendChild(icon);
this.player_.on(PLAYER_EVENT.PRODUCT_BAR_MIN, () => {
setTimeout(() => {
icon.classList.add(SHOPPABLE_ANIMATION_CLASS);
setTimeout(() => {
icon.classList.remove(SHOPPABLE_ANIMATION_CLASS);
}, 1000);
}, 500);
});
return el;
}
}
_vjs.registerComponent('shoppablePanelToggle', ShoppablePanelToggle);
const dom$2 = _vjs.dom || _vjs;
const Component$1 = _vjs.getComponent('Component');
class ShoppableBarLayout extends Component$1 {
constructor(player, options) {
super(player, options);
this.player_ = player;
this.player().addClass('cld-shoppable-panel');
this.player().addClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
this.contentWrpEl_ = dom$2.createEl('div', {
className: 'cld-spbl-bar'
});
this.contentBannerEl_ = dom$2.createEl('div', {
className: 'cld-spbl-banner-msg base-color-text'
}, {}, this.options_.bannerMsg || 'Shop the Video');
this.contentWrpEl_.appendChild(this.contentBannerEl_);
const productsOverlay = new ShoppableProductsOverlay(this.player_, this.options_);
this.contentWrpEl_.appendChild(productsOverlay.el_);
this.contentEl_ = dom$2.createEl('div', {
className: CLD_SPBL_INNER_BAR
});
this.contentWrpEl_.appendChild(this.contentEl_);
this.player().el().appendChild(this.contentWrpEl_);
this.addChild(new ShoppablePanelToggle(this.player_, {
toggleIcon: this.options_.toggleIcon,
clickHandler: () => {
this.togglePanel();
}
}));
this.addChild('ShoppablePanel', this.options_);
this.dispose = () => {
this.removeLayout();
super.dispose();
};
this.togglePanel = open => {
if (open === true) {
// Open
this.player().removeClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
this.player().addClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
} else if (open === false) {
// Close
this.player().removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
this.player().addClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
} else {
// Toggle
this.player().toggleClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
this.player().toggleClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
}
let eventName = this.player().hasClass(SHOPPABLE_PANEL_VISIBLE_CLASS) ? 'productBarMax' : 'productBarMin';
this.player().trigger(eventName);
};
// Open shoppable
if (this.options_.startState === 'open') {
this.togglePanel(true);
}
// On play start
this.player_.on(PLAYER_EVENT.PLAY, () => {
if (this.player_.currentTime() < 0.01) {
// Open shoppable on-play
if (this.options_.startState === 'openOnPlay') {
this.togglePanel(true, this.options_.autoClose);
}
// Auto-close shoppable
if (this.options_.autoClose && this.options_.startState.indexOf('open') !== -1) {
setTimeout(() => {
// Keep it open while hovered
if (!this.contentEl_.matches(':hover')) {
this.togglePanel(false);
} else {
this.contentEl_.addEventListener('mouseleave', () => {
this.togglePanel(false);
}, {
once: true
});
}
}, this.options_.autoClose * 1000);
}
}
});
}
createEl() {
const el = super.createEl('div');
return el;
}
}
_vjs.registerComponent('shoppableBarLayout', ShoppableBarLayout);
const ClickableComponent = _vjs.getComponent('ClickableComponent');
const dom$1 = _vjs.dom || _vjs;
const widthTransformation = {
width: 132
};
class ShoppablePanelItem extends ClickableComponent {
constructor(player, initOptions) {
super(player, initOptions);
this.options_ = initOptions;
this.isDragged = false;
}
handleClick(event) {
event.preventDefault();
event.stopPropagation();
if (!this.el_.matches(`.dragged .${CLD_SPBL_ITEM}`)) {
// Prevent click event if dragged
this.options_.clickHandler(event);
}
this.isDragged = false;
}
getTitle() {
return this.options_.conf.title;
}
createEl() {
const el = super.createEl('a', {
className: `${CLD_SPBL_ITEM} base-color-bg accent-color-text`,
href: '#'
});
el.setAttribute('data-product-id', this.options_.conf.productId || '');
el.setAttribute('data-product-name', this.options_.conf.productName || '');
if (this.options_.conf.onHover) {
addOnHover(el, this.options_.conf.onHover, this.options_.item.cloudinaryConfig());
}
if (this.options_.conf.onClick) {
addOnClick(el, this.options_.conf.onClick);
}
const img = super.createEl('img', {
className: CLD_SPBL_IMAGE
}, {
src: this.options_.item.url(widthTransformation)
});
el.appendChild(img);
if (this.getTitle()) {
const info = dom$1.createEl('div', {
className: 'cld-spbl-item-info base-color-semi-bg text-color-text'
});
const title = dom$1.createEl('span', {
className: 'cld-spbl-item-title'
}, {}, this.getTitle());
info.appendChild(title);
el.appendChild(info);
}
return el;
}
}
const addOnHover = (el, conf, cldConf) => {
el.setAttribute('data-hover-action', conf.action);
if (conf.action === SHOPPABLE_HOVER_ACTIONS.OVERLAY) {
const overlayText = dom$1.createEl('span', {
className: 'cld-spbl-overlay-text base-color-text'
}, {}, conf.args);
const overlay = dom$1.createEl('span', {
className: 'cld-spbl-overlay text-color-semi-bg base-color-text'
}, {
title: conf.args
}, overlayText);
el.appendChild(overlay);
} else {
const switchImgSource = new ImageSource(conf.args.publicId, {
cloudinaryConfig: cldConf,
transformation: conf.args.transformation
});
const hoverImg = dom$1.createEl('img', {
className: `${CLD_SPBL_IMAGE} cld-spbl-hover-img`
}, {
src: switchImgSource.url(widthTransformation)
});
el.appendChild(hoverImg);
}
};
const addOnClick = (el, conf) => {
el.setAttribute('data-click-action', conf.action);
el.setAttribute('data-pause', conf.pause);
if (conf.action === SHOPPABLE_CLICK_ACTIONS.SEEk) {
el.setAttribute('data-seek', conf.args.time);
} else if (conf.action === SHOPPABLE_CLICK_ACTIONS.GO_TO) {
el.setAttribute('data-goto-url', conf.args.url);
}
};
_vjs.registerComponent('shoppablePanelItem', ShoppablePanelItem);
const Component = _vjs.getComponent('Component');
class ShoppablePanel extends Component {
constructor(player) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
super(player, options);
this.options = options;
const itemChangeHandler = () => {
this.render();
};
player.on(PLAYER_EVENT.SHOPPABLE_ITEM_CHANGED, itemChangeHandler);
this.render();
this.dispose = () => {
super.dispose();
player.off(PLAYER_EVENT.SHOPPABLE_ITEM_CHANGED, itemChangeHandler);
};
}
createEl() {
const el = super.createEl();
[CLD_SPBL_PANEL_CLASS, 'base-color-bg'].map(cls => el.classList.add(cls));
return el;
}
removeAll() {
const childrens = this.children();
for (let i = childrens.length - 1; i >= 0; --i) {
this.removeChild(childrens[i]);
}
}
getItems() {
const cloudinaryConfig = this.player_.cloudinary.cloudinaryConfig();
return this.options.products.map(product => {
if (product.onHover && typeof product.onHover.args === 'object') {
product.onHover.args.transformation = Object.assign({}, this.options.transformation, product.onHover.args.transformation);
}
const conf = {
productId: product.productId,
productName: product.productName,
title: product.title,
onHover: product.onHover,
onClick: product.onClick,
startTime: product.startTime,
endTime: product.endTime
};
const imageSource = new ImageSource(product.publicId, {
cloudinaryConfig: cloudinaryConfig,
transformation: Object.assign({}, this.options.transformation, product.transformation)
});
return {
imageSrc: imageSource,
conf: conf
};
});
}
scrollToActiveItem() {
const activeItems = this.el_.getElementsByClassName('active');
if (activeItems.length > 0) {
const toScroll = activeItems[0].offsetTop - 12;
// Test for native scrollTo support (IE will fail)
if ('scrollBehavior' in document.documentElement.style) {
this.el_.scrollTo({
top: toScroll,
behavior: 'smooth'
});
} else {
this.el_.scrollTop = toScroll;
}
}
}
render() {
this.removeAll();
const items = this.getItems();
const throttledScrollToActiveItem = throttle(() => this.scrollToActiveItem(), 1000);
items.forEach((item, index) => {
const shoppablePanelItem = new ShoppablePanelItem(this.player(), {
item: item.imageSrc,
conf: item.conf,
next: index === 1,
current: index === 0,
clickHandler: e => {
let target = e.currentTarget || e.target;
let evName = this.player_.ended() ? 'productClickPost' : 'productClick';
this.player_.trigger(evName, {
productId: target.dataset.productId,
productName: target.dataset.productName
});
// Go to URL, or seek video (set currentTime)
if (target.dataset.clickAction === SHOPPABLE_CLICK_ACTIONS.GO_TO) {
window.open(target.dataset.gotoUrl, '_blank');
} else if (target.dataset.clickAction === SHOPPABLE_CLICK_ACTIONS.SEEk) {
const gotoSecs = parseTime(target.dataset.seek);
if (gotoSecs !== null) {
this.player_.addClass('vjs-has-started'); // Hide the poster image
if (this.player_.postModal) {
this.player_.postModal.close();
}
this.player_.currentTime(gotoSecs);
// Close products side-panel
this.player_.removeClass(SHOPPABLE_PANEL_VISIBLE_CLASS);
this.player_.addClass(SHOPPABLE_PANEL_HIDDEN_CLASS);
this.player_.addClass(SHOPPABLE_PRODUCTS_OVERLAY_CLASS);
// Wait for the time update and show the tooltips
this.player_.one('seeked', () => this.player_.trigger('showProductsOverlay'));
}
}
// pause - true (default), false, or number of seconds
if (target.dataset.pause !== 'false') {
this.player_.pause();
if (parseTime(target.dataset.pause)) {
setTimeout(() => {
this.player_.play();
}, parseTime(target.dataset.pause) * 1000);
}
}
}
});
shoppablePanelItem.on('mouseover', e => {
let target = e.currentTarget || e.target;
let evName = this.player_.ended() ? 'productHoverPost' : 'productHover';
this.player_.trigger(evName, {
productId: target.dataset.productId,
productName: target.dataset.productName
});
});
if (typeof item.conf.startTime !== 'undefined' && typeof item.conf.endTime !== 'undefined') {
this.player_.on(PLAYER_EVENT.TIME_UPDATE, () => {
const time = this.player_.currentTime();
if (time >= item.conf.startTime && time < item.conf.endTime) {
shoppablePanelItem.el_.classList.add('active');
throttledScrollToActiveItem();
} else if (shoppablePanelItem.el_.classList.contains('active')) {
shoppablePanelItem.el_.classList.remove('active');
}
});
}
this.addChild(shoppablePanelItem);
});
}
}
_vjs.registerComponent('shoppablePanel', ShoppablePanel);
const dom = _vjs.dom || _vjs;
class ShoppablePostWidget {
constructor(player) {
let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
this.options_ = {
...options,
postPlay: true
};
this.player_ = player;
this.render();
// Handle drag-to-scroll
this.handleDragToScroll();
this.dispose = () => {
this.layout_.dispose();
};
}
handleDragToScroll() {
const postModal = this.player_.postModal.el_;
const slider = postModal.querySelector(`.${CLD_SPBL_PANEL_CLASS}`);
let isDown = false;
let startX = 0;
let scrollLeft = 0;
slider.addEventListener('mousedown', e => {
isDown = true;
startX = e.pageX - slider.offsetLeft;
scrollLeft = slider.scrollLeft;
});
document.addEventListener('mouseup', e => {
isDown = false;
setTimeout(() => {
slider.classList.remove('dragged');
}, 300);
const x = e.pageX - slider.offsetLeft;
const walk = x - startX;
if (Math.abs(walk) > 5) {
e.preventDefault();
}
});
document.addEventListener('mousemove', e => {
if (!isDown) {
return;
}
e.preventDefault();
const x = e.pageX - slider.offsetLeft;
const walk = x - startX;
slider.scrollLeft = scrollLeft - walk;
if (Math.abs(walk) > 5 && !slider.classList.contains('dragged')) {
slider.classList.add('dragged');
}
});
}
render() {
this.player_.postModal = null;
const el = dom.createEl('div', {
className: 'cld-spbl-post-play'
});
const panel = new ShoppablePanel(this.player_, this.options_);
const title = dom.createEl('div', {
className: 'cld-spbl-post-title base-color-text'
}, {}, this.options_.bannerMsg || 'Shop the Video');
// Background - poster + blur effect
const bgSrc = this.player_.cloudinary.currentPoster();
bgSrc.transformation([bgSrc.transformation().toOptions ? bgSrc.transformation().toOptions() : {}, {
effect: 'blur:3000'
}]);
const panelBg = dom.createEl('div', {
className: 'cld-spbl-post-play-bg',
style: `background-image: url("${bgSrc.url()}")`
});
const replayBtn = dom.createEl('button', {
className: 'cld-spbl-replay-btn base-color-bg vjs-icon-replay',
onclick: () => {
this.player_.trigger('replay');
this.player_.postModal.close();
this.player_.play();
}
}, {}, 'Replay');
el.appendChild(panelBg);
el.appendChild(title);
el.appendChild(panel.el());
el.appendChild(replayBtn);
this.player_.postModal = this.player_.createModal(el, {
name: 'postModal',
uncloseable: true
});
this.player_.addClass('cld-spbl-post-modal');
this.player_.postModal.on('beforemodalclose', () => {
this.player_.removeClass('cld-spbl-post-modal');
});
}
}
export { CLD_SPBL_INNER_BAR as C, SHOPPABLE_WIDGET_OPTIONS_DEFAULTS as S, ShoppablePostWidget as a, SHOPPABLE_PANEL_VISIBLE_CLASS as b, CLD_SPBL_TOGGLE_CLASS as c, CLD_SPBL_PANEL_CLASS as d, ShoppableBarLayout as e };