1?a.options.decimal+r[1]:"",a.options.useGrouping){e="";for(var l=3,h=0,u=0,p=n.length;uwindow.scrollY&&t.paused?(t.paused=!1,setTimeout((function(){return t.start()}),t.options.scrollSpyDelay),t.options.scrollSpyOnce&&(t.once=!0)):(window.scrollY>a||s>i)&&!t.paused&&t.reset()}},i.prototype.determineDirectionAndSmartEasing=function(){var t=this.finalEndVal?this.finalEndVal:this.endVal;this.countDown=this.startVal>t;var i=t-this.startVal;if(Math.abs(i)>this.options.smartEasingThreshold&&this.options.useEasing){this.finalEndVal=t;var n=this.countDown?1:-1;this.endVal=t+n*this.options.smartEasingAmount,this.duration=this.duration/2}else this.endVal=t,this.finalEndVal=null;null!==this.finalEndVal?this.useEasing=!1:this.useEasing=this.options.useEasing},i.prototype.start=function(t){this.error||(this.options.onStartCallback&&this.options.onStartCallback(),t&&(this.options.onCompleteCallback=t),this.duration>0?(this.determineDirectionAndSmartEasing(),this.paused=!1,this.rAF=requestAnimationFrame(this.count)):this.printValue(this.endVal))},i.prototype.pauseResume=function(){this.paused?(this.startTime=null,this.duration=this.remaining,this.startVal=this.frameVal,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count)):cancelAnimationFrame(this.rAF),this.paused=!this.paused},i.prototype.reset=function(){cancelAnimationFrame(this.rAF),this.paused=!0,this.resetDuration(),this.startVal=this.validateValue(this.options.startVal),this.frameVal=this.startVal,this.printValue(this.startVal)},i.prototype.update=function(t){cancelAnimationFrame(this.rAF),this.startTime=null,this.endVal=this.validateValue(t),this.endVal!==this.frameVal&&(this.startVal=this.frameVal,null==this.finalEndVal&&this.resetDuration(),this.finalEndVal=null,this.determineDirectionAndSmartEasing(),this.rAF=requestAnimationFrame(this.count))},i.prototype.printValue=function(t){var i;if(this.el){var n=this.formattingFn(t);if(null===(i=this.options.plugin)||void 0===i?void 0:i.render)this.options.plugin.render(this.el,n);else if("INPUT"===this.el.tagName)this.el.value=n;else"text"===this.el.tagName||"tspan"===this.el.tagName?this.el.textContent=n:this.el.innerHTML=n}},i.prototype.ensureNumber=function(t){return"number"==typeof t&&!isNaN(t)},i.prototype.validateValue=function(t){var i=Number(t);return this.ensureNumber(i)?i:(this.error="[CountUp] invalid start or end value: ".concat(t),null)},i.prototype.resetDuration=function(){this.startTime=null,this.duration=1e3*Number(this.options.duration),this.remaining=this.duration},i.prototype.parse=function(t){var i=function(t){return t.replace(/([.,' ])/g,"\\$1")},n=i(this.options.separator),s=i(this.options.decimal),a=t.replace(new RegExp(n,"g"),"").replace(new RegExp(s,"g"),".");return parseFloat(a)},i}();
// EXTERNAL MODULE: ./node_modules/aos/dist/aos.js
var aos = __webpack_require__(42);
var aos_default = /*#__PURE__*/__webpack_require__.n(aos);
;// ./src/js/inc/global.js
/* provided dependency */ var jQuery = __webpack_require__(692);
// import { Fancybox } from "@fancyapps/ui/dist/fancybox/";
jQuery($ => {
let $body = $('body'),
$window = $(window),
$header = $('#header');
// scroll to hash
let scrollTo = () => {
let hash = window.location.hash,
$elm = $(hash);
if ($elm.length > 0) {
setTimeout(() => {
$('html,body').animate({
scrollTop: $elm.offset().top - $header.outerHeight()
}, 600);
}, 800);
}
};
scrollTo();
aos_default().init({
// duration: 300,
once: true
});
// Fancybox.bind('[data-fancybox], .format a[href*="youtube"]:not(.noplay)', {})
$('.countup').each((index, t) => {
let $t = $(t),
val = $t.data('val');
let countUp = new i(t, val, {
enableScrollSpy: true,
separator: ''
});
});
$('.format a[href*="youtube"]').each((index, a) => {
let $a = $(a);
if ($a.hasClass('noplay')) return;
if ($('img', $a).length > 0) {
$a.addClass('has-play');
}
});
// let updateHeader = () => {
// let headerH = $header.height()
// if ($window.scrollTop() >= headerH) {
// $body.addClass('header-collapsed')
// }
// else {
// $body.removeClass('header-collapsed')
// }
// }
// updateHeader()
// $window.on('scroll', updateHeader)
$body.on('click', '.toggle-nav', e => {
e.preventDefault();
let $a = $(e.currentTarget);
$('span', $a).toggleClass('icon-menu icon-close');
$body.toggleClass('menu-open');
});
// .on('click', '#header .hs-item-has-children', e => {
// let $li = $(e.currentTarget), $ul = $li.parent()
// $('li', $ul).not($li).removeClass('active')
// $li.toggleClass('active')
// })
// .on('click', '.caps-menu h4 a', e => {
// e.preventDefault()
// let $t = $(e.currentTarget), $cm = $t.parents('.caps-menu')
// $('ul', $cm).slideToggle()
// $cm.toggleClass('caps-menu-reveal')
// })
});
// EXTERNAL MODULE: ./src/js/inc/faqs.js
var faqs = __webpack_require__(517);
// EXTERNAL MODULE: ./src/js/inc/accordion-items.js
var accordion_items = __webpack_require__(493);
;// ./node_modules/swiper/shared/ssr-window.esm.mjs
/**
* SSR Window 5.0.1
* Better handling for window object in SSR environment
* https://github.com/nolimits4web/ssr-window
*
* Copyright 2025, Vladimir Kharlampidi
*
* Licensed under MIT
*
* Released on: June 27, 2025
*/
/* eslint-disable no-param-reassign */
function ssr_window_esm_isObject(obj) {
return obj !== null && typeof obj === 'object' && 'constructor' in obj && obj.constructor === Object;
}
function extend(target, src) {
if (target === void 0) {
target = {};
}
if (src === void 0) {
src = {};
}
const noExtend = ['__proto__', 'constructor', 'prototype'];
Object.keys(src).filter(key => noExtend.indexOf(key) < 0).forEach(key => {
if (typeof target[key] === 'undefined') target[key] = src[key];else if (ssr_window_esm_isObject(src[key]) && ssr_window_esm_isObject(target[key]) && Object.keys(src[key]).length > 0) {
extend(target[key], src[key]);
}
});
}
const ssrDocument = {
body: {},
addEventListener() {},
removeEventListener() {},
activeElement: {
blur() {},
nodeName: ''
},
querySelector() {
return null;
},
querySelectorAll() {
return [];
},
getElementById() {
return null;
},
createEvent() {
return {
initEvent() {}
};
},
createElement() {
return {
children: [],
childNodes: [],
style: {},
setAttribute() {},
getElementsByTagName() {
return [];
}
};
},
createElementNS() {
return {};
},
importNode() {
return null;
},
location: {
hash: '',
host: '',
hostname: '',
href: '',
origin: '',
pathname: '',
protocol: '',
search: ''
}
};
function ssr_window_esm_getDocument() {
const doc = typeof document !== 'undefined' ? document : {};
extend(doc, ssrDocument);
return doc;
}
const ssrWindow = {
document: ssrDocument,
navigator: {
userAgent: ''
},
location: {
hash: '',
host: '',
hostname: '',
href: '',
origin: '',
pathname: '',
protocol: '',
search: ''
},
history: {
replaceState() {},
pushState() {},
go() {},
back() {}
},
CustomEvent: function CustomEvent() {
return this;
},
addEventListener() {},
removeEventListener() {},
getComputedStyle() {
return {
getPropertyValue() {
return '';
}
};
},
Image() {},
Date() {},
screen: {},
setTimeout() {},
clearTimeout() {},
matchMedia() {
return {};
},
requestAnimationFrame(callback) {
if (typeof setTimeout === 'undefined') {
callback();
return null;
}
return setTimeout(callback, 0);
},
cancelAnimationFrame(id) {
if (typeof setTimeout === 'undefined') {
return;
}
clearTimeout(id);
}
};
function ssr_window_esm_getWindow() {
const win = typeof window !== 'undefined' ? window : {};
extend(win, ssrWindow);
return win;
}
;// ./node_modules/swiper/shared/utils.mjs
function utils_classesToTokens(classes) {
if (classes === void 0) {
classes = '';
}
return classes.trim().split(' ').filter(c => !!c.trim());
}
function deleteProps(obj) {
const object = obj;
Object.keys(object).forEach(key => {
try {
object[key] = null;
} catch (e) {
// no getter for object
}
try {
delete object[key];
} catch (e) {
// something got wrong
}
});
}
function utils_nextTick(callback, delay) {
if (delay === void 0) {
delay = 0;
}
return setTimeout(callback, delay);
}
function utils_now() {
return Date.now();
}
function utils_getComputedStyle(el) {
const window = ssr_window_esm_getWindow();
let style;
if (window.getComputedStyle) {
style = window.getComputedStyle(el, null);
}
if (!style && el.currentStyle) {
style = el.currentStyle;
}
if (!style) {
style = el.style;
}
return style;
}
function utils_getTranslate(el, axis) {
if (axis === void 0) {
axis = 'x';
}
const window = ssr_window_esm_getWindow();
let matrix;
let curTransform;
let transformMatrix;
const curStyle = utils_getComputedStyle(el);
if (window.WebKitCSSMatrix) {
curTransform = curStyle.transform || curStyle.webkitTransform;
if (curTransform.split(',').length > 6) {
curTransform = curTransform.split(', ').map(a => a.replace(',', '.')).join(', ');
}
// Some old versions of Webkit choke when 'none' is passed; pass
// empty string instead in this case
transformMatrix = new window.WebKitCSSMatrix(curTransform === 'none' ? '' : curTransform);
} else {
transformMatrix = curStyle.MozTransform || curStyle.OTransform || curStyle.MsTransform || curStyle.msTransform || curStyle.transform || curStyle.getPropertyValue('transform').replace('translate(', 'matrix(1, 0, 0, 1,');
matrix = transformMatrix.toString().split(',');
}
if (axis === 'x') {
// Latest Chrome and webkits Fix
if (window.WebKitCSSMatrix) curTransform = transformMatrix.m41;
// Crazy IE10 Matrix
else if (matrix.length === 16) curTransform = parseFloat(matrix[12]);
// Normal Browsers
else curTransform = parseFloat(matrix[4]);
}
if (axis === 'y') {
// Latest Chrome and webkits Fix
if (window.WebKitCSSMatrix) curTransform = transformMatrix.m42;
// Crazy IE10 Matrix
else if (matrix.length === 16) curTransform = parseFloat(matrix[13]);
// Normal Browsers
else curTransform = parseFloat(matrix[5]);
}
return curTransform || 0;
}
function utils_isObject(o) {
return typeof o === 'object' && o !== null && o.constructor && Object.prototype.toString.call(o).slice(8, -1) === 'Object';
}
function isNode(node) {
// eslint-disable-next-line
if (typeof window !== 'undefined' && typeof window.HTMLElement !== 'undefined') {
return node instanceof HTMLElement;
}
return node && (node.nodeType === 1 || node.nodeType === 11);
}
function utils_extend() {
const to = Object(arguments.length <= 0 ? undefined : arguments[0]);
const noExtend = ['__proto__', 'constructor', 'prototype'];
for (let i = 1; i < arguments.length; i += 1) {
const nextSource = i < 0 || arguments.length <= i ? undefined : arguments[i];
if (nextSource !== undefined && nextSource !== null && !isNode(nextSource)) {
const keysArray = Object.keys(Object(nextSource)).filter(key => noExtend.indexOf(key) < 0);
for (let nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex += 1) {
const nextKey = keysArray[nextIndex];
const desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
if (utils_isObject(to[nextKey]) && utils_isObject(nextSource[nextKey])) {
if (nextSource[nextKey].__swiper__) {
to[nextKey] = nextSource[nextKey];
} else {
utils_extend(to[nextKey], nextSource[nextKey]);
}
} else if (!utils_isObject(to[nextKey]) && utils_isObject(nextSource[nextKey])) {
to[nextKey] = {};
if (nextSource[nextKey].__swiper__) {
to[nextKey] = nextSource[nextKey];
} else {
utils_extend(to[nextKey], nextSource[nextKey]);
}
} else {
to[nextKey] = nextSource[nextKey];
}
}
}
}
}
return to;
}
function utils_setCSSProperty(el, varName, varValue) {
el.style.setProperty(varName, varValue);
}
function animateCSSModeScroll(_ref) {
let {
swiper,
targetPosition,
side
} = _ref;
const window = ssr_window_esm_getWindow();
const startPosition = -swiper.translate;
let startTime = null;
let time;
const duration = swiper.params.speed;
swiper.wrapperEl.style.scrollSnapType = 'none';
window.cancelAnimationFrame(swiper.cssModeFrameID);
const dir = targetPosition > startPosition ? 'next' : 'prev';
const isOutOfBound = (current, target) => {
return dir === 'next' && current >= target || dir === 'prev' && current <= target;
};
const animate = () => {
time = new Date().getTime();
if (startTime === null) {
startTime = time;
}
const progress = Math.max(Math.min((time - startTime) / duration, 1), 0);
const easeProgress = 0.5 - Math.cos(progress * Math.PI) / 2;
let currentPosition = startPosition + easeProgress * (targetPosition - startPosition);
if (isOutOfBound(currentPosition, targetPosition)) {
currentPosition = targetPosition;
}
swiper.wrapperEl.scrollTo({
[side]: currentPosition
});
if (isOutOfBound(currentPosition, targetPosition)) {
swiper.wrapperEl.style.overflow = 'hidden';
swiper.wrapperEl.style.scrollSnapType = '';
setTimeout(() => {
swiper.wrapperEl.style.overflow = '';
swiper.wrapperEl.scrollTo({
[side]: currentPosition
});
});
window.cancelAnimationFrame(swiper.cssModeFrameID);
return;
}
swiper.cssModeFrameID = window.requestAnimationFrame(animate);
};
animate();
}
function utils_getSlideTransformEl(slideEl) {
return slideEl.querySelector('.swiper-slide-transform') || slideEl.shadowRoot && slideEl.shadowRoot.querySelector('.swiper-slide-transform') || slideEl;
}
function utils_elementChildren(element, selector) {
if (selector === void 0) {
selector = '';
}
const window = ssr_window_esm_getWindow();
const children = [...element.children];
if (window.HTMLSlotElement && element instanceof HTMLSlotElement) {
children.push(...element.assignedElements());
}
if (!selector) {
return children;
}
return children.filter(el => el.matches(selector));
}
function elementIsChildOfSlot(el, slot) {
// Breadth-first search through all parent's children and assigned elements
const elementsQueue = [slot];
while (elementsQueue.length > 0) {
const elementToCheck = elementsQueue.shift();
if (el === elementToCheck) {
return true;
}
elementsQueue.push(...elementToCheck.children, ...(elementToCheck.shadowRoot ? elementToCheck.shadowRoot.children : []), ...(elementToCheck.assignedElements ? elementToCheck.assignedElements() : []));
}
}
function elementIsChildOf(el, parent) {
const window = ssr_window_esm_getWindow();
let isChild = parent.contains(el);
if (!isChild && window.HTMLSlotElement && parent instanceof HTMLSlotElement) {
const children = [...parent.assignedElements()];
isChild = children.includes(el);
if (!isChild) {
isChild = elementIsChildOfSlot(el, parent);
}
}
return isChild;
}
function showWarning(text) {
try {
console.warn(text);
return;
} catch (err) {
// err
}
}
function utils_createElement(tag, classes) {
if (classes === void 0) {
classes = [];
}
const el = document.createElement(tag);
el.classList.add(...(Array.isArray(classes) ? classes : utils_classesToTokens(classes)));
return el;
}
function utils_elementOffset(el) {
const window = getWindow();
const document = getDocument();
const box = el.getBoundingClientRect();
const body = document.body;
const clientTop = el.clientTop || body.clientTop || 0;
const clientLeft = el.clientLeft || body.clientLeft || 0;
const scrollTop = el === window ? window.scrollY : el.scrollTop;
const scrollLeft = el === window ? window.scrollX : el.scrollLeft;
return {
top: box.top + scrollTop - clientTop,
left: box.left + scrollLeft - clientLeft
};
}
function elementPrevAll(el, selector) {
const prevEls = [];
while (el.previousElementSibling) {
const prev = el.previousElementSibling; // eslint-disable-line
if (selector) {
if (prev.matches(selector)) prevEls.push(prev);
} else prevEls.push(prev);
el = prev;
}
return prevEls;
}
function elementNextAll(el, selector) {
const nextEls = [];
while (el.nextElementSibling) {
const next = el.nextElementSibling; // eslint-disable-line
if (selector) {
if (next.matches(selector)) nextEls.push(next);
} else nextEls.push(next);
el = next;
}
return nextEls;
}
function elementStyle(el, prop) {
const window = ssr_window_esm_getWindow();
return window.getComputedStyle(el, null).getPropertyValue(prop);
}
function utils_elementIndex(el) {
let child = el;
let i;
if (child) {
i = 0;
// eslint-disable-next-line
while ((child = child.previousSibling) !== null) {
if (child.nodeType === 1) i += 1;
}
return i;
}
return undefined;
}
function utils_elementParents(el, selector) {
const parents = []; // eslint-disable-line
let parent = el.parentElement; // eslint-disable-line
while (parent) {
if (selector) {
if (parent.matches(selector)) parents.push(parent);
} else {
parents.push(parent);
}
parent = parent.parentElement;
}
return parents;
}
function utils_elementTransitionEnd(el, callback) {
function fireCallBack(e) {
if (e.target !== el) return;
callback.call(el, e);
el.removeEventListener('transitionend', fireCallBack);
}
if (callback) {
el.addEventListener('transitionend', fireCallBack);
}
}
function elementOuterSize(el, size, includeMargins) {
const window = ssr_window_esm_getWindow();
if (includeMargins) {
return el[size === 'width' ? 'offsetWidth' : 'offsetHeight'] + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-right' : 'margin-top')) + parseFloat(window.getComputedStyle(el, null).getPropertyValue(size === 'width' ? 'margin-left' : 'margin-bottom'));
}
return el.offsetWidth;
}
function utils_makeElementsArray(el) {
return (Array.isArray(el) ? el : [el]).filter(e => !!e);
}
function utils_getRotateFix(swiper) {
return v => {
if (Math.abs(v) > 0 && swiper.browser && swiper.browser.need3dFix && Math.abs(v) % 90 === 0) {
return v + 0.001;
}
return v;
};
}
function utils_setInnerHTML(el, html) {
if (html === void 0) {
html = '';
}
if (typeof trustedTypes !== 'undefined') {
el.innerHTML = trustedTypes.createPolicy('html', {
createHTML: s => s
}).createHTML(html);
} else {
el.innerHTML = html;
}
}
;// ./node_modules/swiper/shared/swiper-core.mjs
let support;
function calcSupport() {
const window = ssr_window_esm_getWindow();
const document = ssr_window_esm_getDocument();
return {
smoothScroll: document.documentElement && document.documentElement.style && 'scrollBehavior' in document.documentElement.style,
touch: !!('ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch)
};
}
function getSupport() {
if (!support) {
support = calcSupport();
}
return support;
}
let deviceCached;
function calcDevice(_temp) {
let {
userAgent
} = _temp === void 0 ? {} : _temp;
const support = getSupport();
const window = ssr_window_esm_getWindow();
const platform = window.navigator.platform;
const ua = userAgent || window.navigator.userAgent;
const device = {
ios: false,
android: false
};
const screenWidth = window.screen.width;
const screenHeight = window.screen.height;
const android = ua.match(/(Android);?[\s\/]+([\d.]+)?/); // eslint-disable-line
let ipad = ua.match(/(iPad).*OS\s([\d_]+)/);
const ipod = ua.match(/(iPod)(.*OS\s([\d_]+))?/);
const iphone = !ipad && ua.match(/(iPhone\sOS|iOS)\s([\d_]+)/);
const windows = platform === 'Win32';
let macos = platform === 'MacIntel';
// iPadOs 13 fix
const iPadScreens = ['1024x1366', '1366x1024', '834x1194', '1194x834', '834x1112', '1112x834', '768x1024', '1024x768', '820x1180', '1180x820', '810x1080', '1080x810'];
if (!ipad && macos && support.touch && iPadScreens.indexOf(`${screenWidth}x${screenHeight}`) >= 0) {
ipad = ua.match(/(Version)\/([\d.]+)/);
if (!ipad) ipad = [0, 1, '13_0_0'];
macos = false;
}
// Android
if (android && !windows) {
device.os = 'android';
device.android = true;
}
if (ipad || iphone || ipod) {
device.os = 'ios';
device.ios = true;
}
// Export object
return device;
}
function getDevice(overrides) {
if (overrides === void 0) {
overrides = {};
}
if (!deviceCached) {
deviceCached = calcDevice(overrides);
}
return deviceCached;
}
let browser;
function calcBrowser() {
const window = ssr_window_esm_getWindow();
const device = getDevice();
let needPerspectiveFix = false;
function isSafari() {
const ua = window.navigator.userAgent.toLowerCase();
return ua.indexOf('safari') >= 0 && ua.indexOf('chrome') < 0 && ua.indexOf('android') < 0;
}
if (isSafari()) {
const ua = String(window.navigator.userAgent);
if (ua.includes('Version/')) {
const [major, minor] = ua.split('Version/')[1].split(' ')[0].split('.').map(num => Number(num));
needPerspectiveFix = major < 16 || major === 16 && minor < 2;
}
}
const isWebView = /(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)/i.test(window.navigator.userAgent);
const isSafariBrowser = isSafari();
const need3dFix = isSafariBrowser || isWebView && device.ios;
return {
isSafari: needPerspectiveFix || isSafariBrowser,
needPerspectiveFix,
need3dFix,
isWebView
};
}
function getBrowser() {
if (!browser) {
browser = calcBrowser();
}
return browser;
}
function Resize(_ref) {
let {
swiper,
on,
emit
} = _ref;
const window = ssr_window_esm_getWindow();
let observer = null;
let animationFrame = null;
const resizeHandler = () => {
if (!swiper || swiper.destroyed || !swiper.initialized) return;
emit('beforeResize');
emit('resize');
};
const createObserver = () => {
if (!swiper || swiper.destroyed || !swiper.initialized) return;
observer = new ResizeObserver(entries => {
animationFrame = window.requestAnimationFrame(() => {
const {
width,
height
} = swiper;
let newWidth = width;
let newHeight = height;
entries.forEach(_ref2 => {
let {
contentBoxSize,
contentRect,
target
} = _ref2;
if (target && target !== swiper.el) return;
newWidth = contentRect ? contentRect.width : (contentBoxSize[0] || contentBoxSize).inlineSize;
newHeight = contentRect ? contentRect.height : (contentBoxSize[0] || contentBoxSize).blockSize;
});
if (newWidth !== width || newHeight !== height) {
resizeHandler();
}
});
});
observer.observe(swiper.el);
};
const removeObserver = () => {
if (animationFrame) {
window.cancelAnimationFrame(animationFrame);
}
if (observer && observer.unobserve && swiper.el) {
observer.unobserve(swiper.el);
observer = null;
}
};
const orientationChangeHandler = () => {
if (!swiper || swiper.destroyed || !swiper.initialized) return;
emit('orientationchange');
};
on('init', () => {
if (swiper.params.resizeObserver && typeof window.ResizeObserver !== 'undefined') {
createObserver();
return;
}
window.addEventListener('resize', resizeHandler);
window.addEventListener('orientationchange', orientationChangeHandler);
});
on('destroy', () => {
removeObserver();
window.removeEventListener('resize', resizeHandler);
window.removeEventListener('orientationchange', orientationChangeHandler);
});
}
function Observer(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
const observers = [];
const window = ssr_window_esm_getWindow();
const attach = function (target, options) {
if (options === void 0) {
options = {};
}
const ObserverFunc = window.MutationObserver || window.WebkitMutationObserver;
const observer = new ObserverFunc(mutations => {
// The observerUpdate event should only be triggered
// once despite the number of mutations. Additional
// triggers are redundant and are very costly
if (swiper.__preventObserver__) return;
if (mutations.length === 1) {
emit('observerUpdate', mutations[0]);
return;
}
const observerUpdate = function observerUpdate() {
emit('observerUpdate', mutations[0]);
};
if (window.requestAnimationFrame) {
window.requestAnimationFrame(observerUpdate);
} else {
window.setTimeout(observerUpdate, 0);
}
});
observer.observe(target, {
attributes: typeof options.attributes === 'undefined' ? true : options.attributes,
childList: swiper.isElement || (typeof options.childList === 'undefined' ? true : options).childList,
characterData: typeof options.characterData === 'undefined' ? true : options.characterData
});
observers.push(observer);
};
const init = () => {
if (!swiper.params.observer) return;
if (swiper.params.observeParents) {
const containerParents = utils_elementParents(swiper.hostEl);
for (let i = 0; i < containerParents.length; i += 1) {
attach(containerParents[i]);
}
}
// Observe container
attach(swiper.hostEl, {
childList: swiper.params.observeSlideChildren
});
// Observe wrapper
attach(swiper.wrapperEl, {
attributes: false
});
};
const destroy = () => {
observers.forEach(observer => {
observer.disconnect();
});
observers.splice(0, observers.length);
};
extendParams({
observer: false,
observeParents: false,
observeSlideChildren: false
});
on('init', init);
on('destroy', destroy);
}
/* eslint-disable no-underscore-dangle */
var eventsEmitter = {
on(events, handler, priority) {
const self = this;
if (!self.eventsListeners || self.destroyed) return self;
if (typeof handler !== 'function') return self;
const method = priority ? 'unshift' : 'push';
events.split(' ').forEach(event => {
if (!self.eventsListeners[event]) self.eventsListeners[event] = [];
self.eventsListeners[event][method](handler);
});
return self;
},
once(events, handler, priority) {
const self = this;
if (!self.eventsListeners || self.destroyed) return self;
if (typeof handler !== 'function') return self;
function onceHandler() {
self.off(events, onceHandler);
if (onceHandler.__emitterProxy) {
delete onceHandler.__emitterProxy;
}
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
handler.apply(self, args);
}
onceHandler.__emitterProxy = handler;
return self.on(events, onceHandler, priority);
},
onAny(handler, priority) {
const self = this;
if (!self.eventsListeners || self.destroyed) return self;
if (typeof handler !== 'function') return self;
const method = priority ? 'unshift' : 'push';
if (self.eventsAnyListeners.indexOf(handler) < 0) {
self.eventsAnyListeners[method](handler);
}
return self;
},
offAny(handler) {
const self = this;
if (!self.eventsListeners || self.destroyed) return self;
if (!self.eventsAnyListeners) return self;
const index = self.eventsAnyListeners.indexOf(handler);
if (index >= 0) {
self.eventsAnyListeners.splice(index, 1);
}
return self;
},
off(events, handler) {
const self = this;
if (!self.eventsListeners || self.destroyed) return self;
if (!self.eventsListeners) return self;
events.split(' ').forEach(event => {
if (typeof handler === 'undefined') {
self.eventsListeners[event] = [];
} else if (self.eventsListeners[event]) {
self.eventsListeners[event].forEach((eventHandler, index) => {
if (eventHandler === handler || eventHandler.__emitterProxy && eventHandler.__emitterProxy === handler) {
self.eventsListeners[event].splice(index, 1);
}
});
}
});
return self;
},
emit() {
const self = this;
if (!self.eventsListeners || self.destroyed) return self;
if (!self.eventsListeners) return self;
let events;
let data;
let context;
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
if (typeof args[0] === 'string' || Array.isArray(args[0])) {
events = args[0];
data = args.slice(1, args.length);
context = self;
} else {
events = args[0].events;
data = args[0].data;
context = args[0].context || self;
}
data.unshift(context);
const eventsArray = Array.isArray(events) ? events : events.split(' ');
eventsArray.forEach(event => {
if (self.eventsAnyListeners && self.eventsAnyListeners.length) {
self.eventsAnyListeners.forEach(eventHandler => {
eventHandler.apply(context, [event, ...data]);
});
}
if (self.eventsListeners && self.eventsListeners[event]) {
self.eventsListeners[event].forEach(eventHandler => {
eventHandler.apply(context, data);
});
}
});
return self;
}
};
function updateSize() {
const swiper = this;
let width;
let height;
const el = swiper.el;
if (typeof swiper.params.width !== 'undefined' && swiper.params.width !== null) {
width = swiper.params.width;
} else {
width = el.clientWidth;
}
if (typeof swiper.params.height !== 'undefined' && swiper.params.height !== null) {
height = swiper.params.height;
} else {
height = el.clientHeight;
}
if (width === 0 && swiper.isHorizontal() || height === 0 && swiper.isVertical()) {
return;
}
// Subtract paddings
width = width - parseInt(elementStyle(el, 'padding-left') || 0, 10) - parseInt(elementStyle(el, 'padding-right') || 0, 10);
height = height - parseInt(elementStyle(el, 'padding-top') || 0, 10) - parseInt(elementStyle(el, 'padding-bottom') || 0, 10);
if (Number.isNaN(width)) width = 0;
if (Number.isNaN(height)) height = 0;
Object.assign(swiper, {
width,
height,
size: swiper.isHorizontal() ? width : height
});
}
function updateSlides() {
const swiper = this;
function getDirectionPropertyValue(node, label) {
return parseFloat(node.getPropertyValue(swiper.getDirectionLabel(label)) || 0);
}
const params = swiper.params;
const {
wrapperEl,
slidesEl,
size: swiperSize,
rtlTranslate: rtl,
wrongRTL
} = swiper;
const isVirtual = swiper.virtual && params.virtual.enabled;
const previousSlidesLength = isVirtual ? swiper.virtual.slides.length : swiper.slides.length;
const slides = utils_elementChildren(slidesEl, `.${swiper.params.slideClass}, swiper-slide`);
const slidesLength = isVirtual ? swiper.virtual.slides.length : slides.length;
let snapGrid = [];
const slidesGrid = [];
const slidesSizesGrid = [];
let offsetBefore = params.slidesOffsetBefore;
if (typeof offsetBefore === 'function') {
offsetBefore = params.slidesOffsetBefore.call(swiper);
}
let offsetAfter = params.slidesOffsetAfter;
if (typeof offsetAfter === 'function') {
offsetAfter = params.slidesOffsetAfter.call(swiper);
}
const previousSnapGridLength = swiper.snapGrid.length;
const previousSlidesGridLength = swiper.slidesGrid.length;
let spaceBetween = params.spaceBetween;
let slidePosition = -offsetBefore;
let prevSlideSize = 0;
let index = 0;
if (typeof swiperSize === 'undefined') {
return;
}
if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * swiperSize;
} else if (typeof spaceBetween === 'string') {
spaceBetween = parseFloat(spaceBetween);
}
swiper.virtualSize = -spaceBetween;
// reset margins
slides.forEach(slideEl => {
if (rtl) {
slideEl.style.marginLeft = '';
} else {
slideEl.style.marginRight = '';
}
slideEl.style.marginBottom = '';
slideEl.style.marginTop = '';
});
// reset cssMode offsets
if (params.centeredSlides && params.cssMode) {
utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-before', '');
utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-after', '');
}
const gridEnabled = params.grid && params.grid.rows > 1 && swiper.grid;
if (gridEnabled) {
swiper.grid.initSlides(slides);
} else if (swiper.grid) {
swiper.grid.unsetSlides();
}
// Calc slides
let slideSize;
const shouldResetSlideSize = params.slidesPerView === 'auto' && params.breakpoints && Object.keys(params.breakpoints).filter(key => {
return typeof params.breakpoints[key].slidesPerView !== 'undefined';
}).length > 0;
for (let i = 0; i < slidesLength; i += 1) {
slideSize = 0;
let slide;
if (slides[i]) slide = slides[i];
if (gridEnabled) {
swiper.grid.updateSlide(i, slide, slides);
}
if (slides[i] && elementStyle(slide, 'display') === 'none') continue; // eslint-disable-line
if (params.slidesPerView === 'auto') {
if (shouldResetSlideSize) {
slides[i].style[swiper.getDirectionLabel('width')] = ``;
}
const slideStyles = getComputedStyle(slide);
const currentTransform = slide.style.transform;
const currentWebKitTransform = slide.style.webkitTransform;
if (currentTransform) {
slide.style.transform = 'none';
}
if (currentWebKitTransform) {
slide.style.webkitTransform = 'none';
}
if (params.roundLengths) {
slideSize = swiper.isHorizontal() ? elementOuterSize(slide, 'width', true) : elementOuterSize(slide, 'height', true);
} else {
// eslint-disable-next-line
const width = getDirectionPropertyValue(slideStyles, 'width');
const paddingLeft = getDirectionPropertyValue(slideStyles, 'padding-left');
const paddingRight = getDirectionPropertyValue(slideStyles, 'padding-right');
const marginLeft = getDirectionPropertyValue(slideStyles, 'margin-left');
const marginRight = getDirectionPropertyValue(slideStyles, 'margin-right');
const boxSizing = slideStyles.getPropertyValue('box-sizing');
if (boxSizing && boxSizing === 'border-box') {
slideSize = width + marginLeft + marginRight;
} else {
const {
clientWidth,
offsetWidth
} = slide;
slideSize = width + paddingLeft + paddingRight + marginLeft + marginRight + (offsetWidth - clientWidth);
}
}
if (currentTransform) {
slide.style.transform = currentTransform;
}
if (currentWebKitTransform) {
slide.style.webkitTransform = currentWebKitTransform;
}
if (params.roundLengths) slideSize = Math.floor(slideSize);
} else {
slideSize = (swiperSize - (params.slidesPerView - 1) * spaceBetween) / params.slidesPerView;
if (params.roundLengths) slideSize = Math.floor(slideSize);
if (slides[i]) {
slides[i].style[swiper.getDirectionLabel('width')] = `${slideSize}px`;
}
}
if (slides[i]) {
slides[i].swiperSlideSize = slideSize;
}
slidesSizesGrid.push(slideSize);
if (params.centeredSlides) {
slidePosition = slidePosition + slideSize / 2 + prevSlideSize / 2 + spaceBetween;
if (prevSlideSize === 0 && i !== 0) slidePosition = slidePosition - swiperSize / 2 - spaceBetween;
if (i === 0) slidePosition = slidePosition - swiperSize / 2 - spaceBetween;
if (Math.abs(slidePosition) < 1 / 1000) slidePosition = 0;
if (params.roundLengths) slidePosition = Math.floor(slidePosition);
if (index % params.slidesPerGroup === 0) snapGrid.push(slidePosition);
slidesGrid.push(slidePosition);
} else {
if (params.roundLengths) slidePosition = Math.floor(slidePosition);
if ((index - Math.min(swiper.params.slidesPerGroupSkip, index)) % swiper.params.slidesPerGroup === 0) snapGrid.push(slidePosition);
slidesGrid.push(slidePosition);
slidePosition = slidePosition + slideSize + spaceBetween;
}
swiper.virtualSize += slideSize + spaceBetween;
prevSlideSize = slideSize;
index += 1;
}
swiper.virtualSize = Math.max(swiper.virtualSize, swiperSize) + offsetAfter;
if (rtl && wrongRTL && (params.effect === 'slide' || params.effect === 'coverflow')) {
wrapperEl.style.width = `${swiper.virtualSize + spaceBetween}px`;
}
if (params.setWrapperSize) {
wrapperEl.style[swiper.getDirectionLabel('width')] = `${swiper.virtualSize + spaceBetween}px`;
}
if (gridEnabled) {
swiper.grid.updateWrapperSize(slideSize, snapGrid);
}
// Remove last grid elements depending on width
if (!params.centeredSlides) {
const newSlidesGrid = [];
for (let i = 0; i < snapGrid.length; i += 1) {
let slidesGridItem = snapGrid[i];
if (params.roundLengths) slidesGridItem = Math.floor(slidesGridItem);
if (snapGrid[i] <= swiper.virtualSize - swiperSize) {
newSlidesGrid.push(slidesGridItem);
}
}
snapGrid = newSlidesGrid;
if (Math.floor(swiper.virtualSize - swiperSize) - Math.floor(snapGrid[snapGrid.length - 1]) > 1) {
snapGrid.push(swiper.virtualSize - swiperSize);
}
}
if (isVirtual && params.loop) {
const size = slidesSizesGrid[0] + spaceBetween;
if (params.slidesPerGroup > 1) {
const groups = Math.ceil((swiper.virtual.slidesBefore + swiper.virtual.slidesAfter) / params.slidesPerGroup);
const groupSize = size * params.slidesPerGroup;
for (let i = 0; i < groups; i += 1) {
snapGrid.push(snapGrid[snapGrid.length - 1] + groupSize);
}
}
for (let i = 0; i < swiper.virtual.slidesBefore + swiper.virtual.slidesAfter; i += 1) {
if (params.slidesPerGroup === 1) {
snapGrid.push(snapGrid[snapGrid.length - 1] + size);
}
slidesGrid.push(slidesGrid[slidesGrid.length - 1] + size);
swiper.virtualSize += size;
}
}
if (snapGrid.length === 0) snapGrid = [0];
if (spaceBetween !== 0) {
const key = swiper.isHorizontal() && rtl ? 'marginLeft' : swiper.getDirectionLabel('marginRight');
slides.filter((_, slideIndex) => {
if (!params.cssMode || params.loop) return true;
if (slideIndex === slides.length - 1) {
return false;
}
return true;
}).forEach(slideEl => {
slideEl.style[key] = `${spaceBetween}px`;
});
}
if (params.centeredSlides && params.centeredSlidesBounds) {
let allSlidesSize = 0;
slidesSizesGrid.forEach(slideSizeValue => {
allSlidesSize += slideSizeValue + (spaceBetween || 0);
});
allSlidesSize -= spaceBetween;
const maxSnap = allSlidesSize > swiperSize ? allSlidesSize - swiperSize : 0;
snapGrid = snapGrid.map(snap => {
if (snap <= 0) return -offsetBefore;
if (snap > maxSnap) return maxSnap + offsetAfter;
return snap;
});
}
if (params.centerInsufficientSlides) {
let allSlidesSize = 0;
slidesSizesGrid.forEach(slideSizeValue => {
allSlidesSize += slideSizeValue + (spaceBetween || 0);
});
allSlidesSize -= spaceBetween;
const offsetSize = (params.slidesOffsetBefore || 0) + (params.slidesOffsetAfter || 0);
if (allSlidesSize + offsetSize < swiperSize) {
const allSlidesOffset = (swiperSize - allSlidesSize - offsetSize) / 2;
snapGrid.forEach((snap, snapIndex) => {
snapGrid[snapIndex] = snap - allSlidesOffset;
});
slidesGrid.forEach((snap, snapIndex) => {
slidesGrid[snapIndex] = snap + allSlidesOffset;
});
}
}
Object.assign(swiper, {
slides,
snapGrid,
slidesGrid,
slidesSizesGrid
});
if (params.centeredSlides && params.cssMode && !params.centeredSlidesBounds) {
utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-before', `${-snapGrid[0]}px`);
utils_setCSSProperty(wrapperEl, '--swiper-centered-offset-after', `${swiper.size / 2 - slidesSizesGrid[slidesSizesGrid.length - 1] / 2}px`);
const addToSnapGrid = -swiper.snapGrid[0];
const addToSlidesGrid = -swiper.slidesGrid[0];
swiper.snapGrid = swiper.snapGrid.map(v => v + addToSnapGrid);
swiper.slidesGrid = swiper.slidesGrid.map(v => v + addToSlidesGrid);
}
if (slidesLength !== previousSlidesLength) {
swiper.emit('slidesLengthChange');
}
if (snapGrid.length !== previousSnapGridLength) {
if (swiper.params.watchOverflow) swiper.checkOverflow();
swiper.emit('snapGridLengthChange');
}
if (slidesGrid.length !== previousSlidesGridLength) {
swiper.emit('slidesGridLengthChange');
}
if (params.watchSlidesProgress) {
swiper.updateSlidesOffset();
}
swiper.emit('slidesUpdated');
if (!isVirtual && !params.cssMode && (params.effect === 'slide' || params.effect === 'fade')) {
const backFaceHiddenClass = `${params.containerModifierClass}backface-hidden`;
const hasClassBackfaceClassAdded = swiper.el.classList.contains(backFaceHiddenClass);
if (slidesLength <= params.maxBackfaceHiddenSlides) {
if (!hasClassBackfaceClassAdded) swiper.el.classList.add(backFaceHiddenClass);
} else if (hasClassBackfaceClassAdded) {
swiper.el.classList.remove(backFaceHiddenClass);
}
}
}
function updateAutoHeight(speed) {
const swiper = this;
const activeSlides = [];
const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
let newHeight = 0;
let i;
if (typeof speed === 'number') {
swiper.setTransition(speed);
} else if (speed === true) {
swiper.setTransition(swiper.params.speed);
}
const getSlideByIndex = index => {
if (isVirtual) {
return swiper.slides[swiper.getSlideIndexByData(index)];
}
return swiper.slides[index];
};
// Find slides currently in view
if (swiper.params.slidesPerView !== 'auto' && swiper.params.slidesPerView > 1) {
if (swiper.params.centeredSlides) {
(swiper.visibleSlides || []).forEach(slide => {
activeSlides.push(slide);
});
} else {
for (i = 0; i < Math.ceil(swiper.params.slidesPerView); i += 1) {
const index = swiper.activeIndex + i;
if (index > swiper.slides.length && !isVirtual) break;
activeSlides.push(getSlideByIndex(index));
}
}
} else {
activeSlides.push(getSlideByIndex(swiper.activeIndex));
}
// Find new height from highest slide in view
for (i = 0; i < activeSlides.length; i += 1) {
if (typeof activeSlides[i] !== 'undefined') {
const height = activeSlides[i].offsetHeight;
newHeight = height > newHeight ? height : newHeight;
}
}
// Update Height
if (newHeight || newHeight === 0) swiper.wrapperEl.style.height = `${newHeight}px`;
}
function updateSlidesOffset() {
const swiper = this;
const slides = swiper.slides;
// eslint-disable-next-line
const minusOffset = swiper.isElement ? swiper.isHorizontal() ? swiper.wrapperEl.offsetLeft : swiper.wrapperEl.offsetTop : 0;
for (let i = 0; i < slides.length; i += 1) {
slides[i].swiperSlideOffset = (swiper.isHorizontal() ? slides[i].offsetLeft : slides[i].offsetTop) - minusOffset - swiper.cssOverflowAdjustment();
}
}
const toggleSlideClasses$1 = (slideEl, condition, className) => {
if (condition && !slideEl.classList.contains(className)) {
slideEl.classList.add(className);
} else if (!condition && slideEl.classList.contains(className)) {
slideEl.classList.remove(className);
}
};
function updateSlidesProgress(translate) {
if (translate === void 0) {
translate = this && this.translate || 0;
}
const swiper = this;
const params = swiper.params;
const {
slides,
rtlTranslate: rtl,
snapGrid
} = swiper;
if (slides.length === 0) return;
if (typeof slides[0].swiperSlideOffset === 'undefined') swiper.updateSlidesOffset();
let offsetCenter = -translate;
if (rtl) offsetCenter = translate;
swiper.visibleSlidesIndexes = [];
swiper.visibleSlides = [];
let spaceBetween = params.spaceBetween;
if (typeof spaceBetween === 'string' && spaceBetween.indexOf('%') >= 0) {
spaceBetween = parseFloat(spaceBetween.replace('%', '')) / 100 * swiper.size;
} else if (typeof spaceBetween === 'string') {
spaceBetween = parseFloat(spaceBetween);
}
for (let i = 0; i < slides.length; i += 1) {
const slide = slides[i];
let slideOffset = slide.swiperSlideOffset;
if (params.cssMode && params.centeredSlides) {
slideOffset -= slides[0].swiperSlideOffset;
}
const slideProgress = (offsetCenter + (params.centeredSlides ? swiper.minTranslate() : 0) - slideOffset) / (slide.swiperSlideSize + spaceBetween);
const originalSlideProgress = (offsetCenter - snapGrid[0] + (params.centeredSlides ? swiper.minTranslate() : 0) - slideOffset) / (slide.swiperSlideSize + spaceBetween);
const slideBefore = -(offsetCenter - slideOffset);
const slideAfter = slideBefore + swiper.slidesSizesGrid[i];
const isFullyVisible = slideBefore >= 0 && slideBefore <= swiper.size - swiper.slidesSizesGrid[i];
const isVisible = slideBefore >= 0 && slideBefore < swiper.size - 1 || slideAfter > 1 && slideAfter <= swiper.size || slideBefore <= 0 && slideAfter >= swiper.size;
if (isVisible) {
swiper.visibleSlides.push(slide);
swiper.visibleSlidesIndexes.push(i);
}
toggleSlideClasses$1(slide, isVisible, params.slideVisibleClass);
toggleSlideClasses$1(slide, isFullyVisible, params.slideFullyVisibleClass);
slide.progress = rtl ? -slideProgress : slideProgress;
slide.originalProgress = rtl ? -originalSlideProgress : originalSlideProgress;
}
}
function updateProgress(translate) {
const swiper = this;
if (typeof translate === 'undefined') {
const multiplier = swiper.rtlTranslate ? -1 : 1;
// eslint-disable-next-line
translate = swiper && swiper.translate && swiper.translate * multiplier || 0;
}
const params = swiper.params;
const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
let {
progress,
isBeginning,
isEnd,
progressLoop
} = swiper;
const wasBeginning = isBeginning;
const wasEnd = isEnd;
if (translatesDiff === 0) {
progress = 0;
isBeginning = true;
isEnd = true;
} else {
progress = (translate - swiper.minTranslate()) / translatesDiff;
const isBeginningRounded = Math.abs(translate - swiper.minTranslate()) < 1;
const isEndRounded = Math.abs(translate - swiper.maxTranslate()) < 1;
isBeginning = isBeginningRounded || progress <= 0;
isEnd = isEndRounded || progress >= 1;
if (isBeginningRounded) progress = 0;
if (isEndRounded) progress = 1;
}
if (params.loop) {
const firstSlideIndex = swiper.getSlideIndexByData(0);
const lastSlideIndex = swiper.getSlideIndexByData(swiper.slides.length - 1);
const firstSlideTranslate = swiper.slidesGrid[firstSlideIndex];
const lastSlideTranslate = swiper.slidesGrid[lastSlideIndex];
const translateMax = swiper.slidesGrid[swiper.slidesGrid.length - 1];
const translateAbs = Math.abs(translate);
if (translateAbs >= firstSlideTranslate) {
progressLoop = (translateAbs - firstSlideTranslate) / translateMax;
} else {
progressLoop = (translateAbs + translateMax - lastSlideTranslate) / translateMax;
}
if (progressLoop > 1) progressLoop -= 1;
}
Object.assign(swiper, {
progress,
progressLoop,
isBeginning,
isEnd
});
if (params.watchSlidesProgress || params.centeredSlides && params.autoHeight) swiper.updateSlidesProgress(translate);
if (isBeginning && !wasBeginning) {
swiper.emit('reachBeginning toEdge');
}
if (isEnd && !wasEnd) {
swiper.emit('reachEnd toEdge');
}
if (wasBeginning && !isBeginning || wasEnd && !isEnd) {
swiper.emit('fromEdge');
}
swiper.emit('progress', progress);
}
const toggleSlideClasses = (slideEl, condition, className) => {
if (condition && !slideEl.classList.contains(className)) {
slideEl.classList.add(className);
} else if (!condition && slideEl.classList.contains(className)) {
slideEl.classList.remove(className);
}
};
function updateSlidesClasses() {
const swiper = this;
const {
slides,
params,
slidesEl,
activeIndex
} = swiper;
const isVirtual = swiper.virtual && params.virtual.enabled;
const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
const getFilteredSlide = selector => {
return utils_elementChildren(slidesEl, `.${params.slideClass}${selector}, swiper-slide${selector}`)[0];
};
let activeSlide;
let prevSlide;
let nextSlide;
if (isVirtual) {
if (params.loop) {
let slideIndex = activeIndex - swiper.virtual.slidesBefore;
if (slideIndex < 0) slideIndex = swiper.virtual.slides.length + slideIndex;
if (slideIndex >= swiper.virtual.slides.length) slideIndex -= swiper.virtual.slides.length;
activeSlide = getFilteredSlide(`[data-swiper-slide-index="${slideIndex}"]`);
} else {
activeSlide = getFilteredSlide(`[data-swiper-slide-index="${activeIndex}"]`);
}
} else {
if (gridEnabled) {
activeSlide = slides.find(slideEl => slideEl.column === activeIndex);
nextSlide = slides.find(slideEl => slideEl.column === activeIndex + 1);
prevSlide = slides.find(slideEl => slideEl.column === activeIndex - 1);
} else {
activeSlide = slides[activeIndex];
}
}
if (activeSlide) {
if (!gridEnabled) {
// Next Slide
nextSlide = elementNextAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0];
if (params.loop && !nextSlide) {
nextSlide = slides[0];
}
// Prev Slide
prevSlide = elementPrevAll(activeSlide, `.${params.slideClass}, swiper-slide`)[0];
if (params.loop && !prevSlide === 0) {
prevSlide = slides[slides.length - 1];
}
}
}
slides.forEach(slideEl => {
toggleSlideClasses(slideEl, slideEl === activeSlide, params.slideActiveClass);
toggleSlideClasses(slideEl, slideEl === nextSlide, params.slideNextClass);
toggleSlideClasses(slideEl, slideEl === prevSlide, params.slidePrevClass);
});
swiper.emitSlidesClasses();
}
const processLazyPreloader = (swiper, imageEl) => {
if (!swiper || swiper.destroyed || !swiper.params) return;
const slideSelector = () => swiper.isElement ? `swiper-slide` : `.${swiper.params.slideClass}`;
const slideEl = imageEl.closest(slideSelector());
if (slideEl) {
let lazyEl = slideEl.querySelector(`.${swiper.params.lazyPreloaderClass}`);
if (!lazyEl && swiper.isElement) {
if (slideEl.shadowRoot) {
lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`);
} else {
// init later
requestAnimationFrame(() => {
if (slideEl.shadowRoot) {
lazyEl = slideEl.shadowRoot.querySelector(`.${swiper.params.lazyPreloaderClass}`);
if (lazyEl) lazyEl.remove();
}
});
}
}
if (lazyEl) lazyEl.remove();
}
};
const unlazy = (swiper, index) => {
if (!swiper.slides[index]) return;
const imageEl = swiper.slides[index].querySelector('[loading="lazy"]');
if (imageEl) imageEl.removeAttribute('loading');
};
const preload = swiper => {
if (!swiper || swiper.destroyed || !swiper.params) return;
let amount = swiper.params.lazyPreloadPrevNext;
const len = swiper.slides.length;
if (!len || !amount || amount < 0) return;
amount = Math.min(amount, len);
const slidesPerView = swiper.params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : Math.ceil(swiper.params.slidesPerView);
const activeIndex = swiper.activeIndex;
if (swiper.params.grid && swiper.params.grid.rows > 1) {
const activeColumn = activeIndex;
const preloadColumns = [activeColumn - amount];
preloadColumns.push(...Array.from({
length: amount
}).map((_, i) => {
return activeColumn + slidesPerView + i;
}));
swiper.slides.forEach((slideEl, i) => {
if (preloadColumns.includes(slideEl.column)) unlazy(swiper, i);
});
return;
}
const slideIndexLastInView = activeIndex + slidesPerView - 1;
if (swiper.params.rewind || swiper.params.loop) {
for (let i = activeIndex - amount; i <= slideIndexLastInView + amount; i += 1) {
const realIndex = (i % len + len) % len;
if (realIndex < activeIndex || realIndex > slideIndexLastInView) unlazy(swiper, realIndex);
}
} else {
for (let i = Math.max(activeIndex - amount, 0); i <= Math.min(slideIndexLastInView + amount, len - 1); i += 1) {
if (i !== activeIndex && (i > slideIndexLastInView || i < activeIndex)) {
unlazy(swiper, i);
}
}
}
};
function getActiveIndexByTranslate(swiper) {
const {
slidesGrid,
params
} = swiper;
const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate;
let activeIndex;
for (let i = 0; i < slidesGrid.length; i += 1) {
if (typeof slidesGrid[i + 1] !== 'undefined') {
if (translate >= slidesGrid[i] && translate < slidesGrid[i + 1] - (slidesGrid[i + 1] - slidesGrid[i]) / 2) {
activeIndex = i;
} else if (translate >= slidesGrid[i] && translate < slidesGrid[i + 1]) {
activeIndex = i + 1;
}
} else if (translate >= slidesGrid[i]) {
activeIndex = i;
}
}
// Normalize slideIndex
if (params.normalizeSlideIndex) {
if (activeIndex < 0 || typeof activeIndex === 'undefined') activeIndex = 0;
}
return activeIndex;
}
function updateActiveIndex(newActiveIndex) {
const swiper = this;
const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate;
const {
snapGrid,
params,
activeIndex: previousIndex,
realIndex: previousRealIndex,
snapIndex: previousSnapIndex
} = swiper;
let activeIndex = newActiveIndex;
let snapIndex;
const getVirtualRealIndex = aIndex => {
let realIndex = aIndex - swiper.virtual.slidesBefore;
if (realIndex < 0) {
realIndex = swiper.virtual.slides.length + realIndex;
}
if (realIndex >= swiper.virtual.slides.length) {
realIndex -= swiper.virtual.slides.length;
}
return realIndex;
};
if (typeof activeIndex === 'undefined') {
activeIndex = getActiveIndexByTranslate(swiper);
}
if (snapGrid.indexOf(translate) >= 0) {
snapIndex = snapGrid.indexOf(translate);
} else {
const skip = Math.min(params.slidesPerGroupSkip, activeIndex);
snapIndex = skip + Math.floor((activeIndex - skip) / params.slidesPerGroup);
}
if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1;
if (activeIndex === previousIndex && !swiper.params.loop) {
if (snapIndex !== previousSnapIndex) {
swiper.snapIndex = snapIndex;
swiper.emit('snapIndexChange');
}
return;
}
if (activeIndex === previousIndex && swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) {
swiper.realIndex = getVirtualRealIndex(activeIndex);
return;
}
const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
// Get real index
let realIndex;
if (swiper.virtual && params.virtual.enabled && params.loop) {
realIndex = getVirtualRealIndex(activeIndex);
} else if (gridEnabled) {
const firstSlideInColumn = swiper.slides.find(slideEl => slideEl.column === activeIndex);
let activeSlideIndex = parseInt(firstSlideInColumn.getAttribute('data-swiper-slide-index'), 10);
if (Number.isNaN(activeSlideIndex)) {
activeSlideIndex = Math.max(swiper.slides.indexOf(firstSlideInColumn), 0);
}
realIndex = Math.floor(activeSlideIndex / params.grid.rows);
} else if (swiper.slides[activeIndex]) {
const slideIndex = swiper.slides[activeIndex].getAttribute('data-swiper-slide-index');
if (slideIndex) {
realIndex = parseInt(slideIndex, 10);
} else {
realIndex = activeIndex;
}
} else {
realIndex = activeIndex;
}
Object.assign(swiper, {
previousSnapIndex,
snapIndex,
previousRealIndex,
realIndex,
previousIndex,
activeIndex
});
if (swiper.initialized) {
preload(swiper);
}
swiper.emit('activeIndexChange');
swiper.emit('snapIndexChange');
if (swiper.initialized || swiper.params.runCallbacksOnInit) {
if (previousRealIndex !== realIndex) {
swiper.emit('realIndexChange');
}
swiper.emit('slideChange');
}
}
function updateClickedSlide(el, path) {
const swiper = this;
const params = swiper.params;
let slide = el.closest(`.${params.slideClass}, swiper-slide`);
if (!slide && swiper.isElement && path && path.length > 1 && path.includes(el)) {
[...path.slice(path.indexOf(el) + 1, path.length)].forEach(pathEl => {
if (!slide && pathEl.matches && pathEl.matches(`.${params.slideClass}, swiper-slide`)) {
slide = pathEl;
}
});
}
let slideFound = false;
let slideIndex;
if (slide) {
for (let i = 0; i < swiper.slides.length; i += 1) {
if (swiper.slides[i] === slide) {
slideFound = true;
slideIndex = i;
break;
}
}
}
if (slide && slideFound) {
swiper.clickedSlide = slide;
if (swiper.virtual && swiper.params.virtual.enabled) {
swiper.clickedIndex = parseInt(slide.getAttribute('data-swiper-slide-index'), 10);
} else {
swiper.clickedIndex = slideIndex;
}
} else {
swiper.clickedSlide = undefined;
swiper.clickedIndex = undefined;
return;
}
if (params.slideToClickedSlide && swiper.clickedIndex !== undefined && swiper.clickedIndex !== swiper.activeIndex) {
swiper.slideToClickedSlide();
}
}
var update = {
updateSize,
updateSlides,
updateAutoHeight,
updateSlidesOffset,
updateSlidesProgress,
updateProgress,
updateSlidesClasses,
updateActiveIndex,
updateClickedSlide
};
function getSwiperTranslate(axis) {
if (axis === void 0) {
axis = this.isHorizontal() ? 'x' : 'y';
}
const swiper = this;
const {
params,
rtlTranslate: rtl,
translate,
wrapperEl
} = swiper;
if (params.virtualTranslate) {
return rtl ? -translate : translate;
}
if (params.cssMode) {
return translate;
}
let currentTranslate = utils_getTranslate(wrapperEl, axis);
currentTranslate += swiper.cssOverflowAdjustment();
if (rtl) currentTranslate = -currentTranslate;
return currentTranslate || 0;
}
function setTranslate(translate, byController) {
const swiper = this;
const {
rtlTranslate: rtl,
params,
wrapperEl,
progress
} = swiper;
let x = 0;
let y = 0;
const z = 0;
if (swiper.isHorizontal()) {
x = rtl ? -translate : translate;
} else {
y = translate;
}
if (params.roundLengths) {
x = Math.floor(x);
y = Math.floor(y);
}
swiper.previousTranslate = swiper.translate;
swiper.translate = swiper.isHorizontal() ? x : y;
if (params.cssMode) {
wrapperEl[swiper.isHorizontal() ? 'scrollLeft' : 'scrollTop'] = swiper.isHorizontal() ? -x : -y;
} else if (!params.virtualTranslate) {
if (swiper.isHorizontal()) {
x -= swiper.cssOverflowAdjustment();
} else {
y -= swiper.cssOverflowAdjustment();
}
wrapperEl.style.transform = `translate3d(${x}px, ${y}px, ${z}px)`;
}
// Check if we need to update progress
let newProgress;
const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
if (translatesDiff === 0) {
newProgress = 0;
} else {
newProgress = (translate - swiper.minTranslate()) / translatesDiff;
}
if (newProgress !== progress) {
swiper.updateProgress(translate);
}
swiper.emit('setTranslate', swiper.translate, byController);
}
function minTranslate() {
return -this.snapGrid[0];
}
function maxTranslate() {
return -this.snapGrid[this.snapGrid.length - 1];
}
function translateTo(translate, speed, runCallbacks, translateBounds, internal) {
if (translate === void 0) {
translate = 0;
}
if (speed === void 0) {
speed = this.params.speed;
}
if (runCallbacks === void 0) {
runCallbacks = true;
}
if (translateBounds === void 0) {
translateBounds = true;
}
const swiper = this;
const {
params,
wrapperEl
} = swiper;
if (swiper.animating && params.preventInteractionOnTransition) {
return false;
}
const minTranslate = swiper.minTranslate();
const maxTranslate = swiper.maxTranslate();
let newTranslate;
if (translateBounds && translate > minTranslate) newTranslate = minTranslate;else if (translateBounds && translate < maxTranslate) newTranslate = maxTranslate;else newTranslate = translate;
// Update progress
swiper.updateProgress(newTranslate);
if (params.cssMode) {
const isH = swiper.isHorizontal();
if (speed === 0) {
wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = -newTranslate;
} else {
if (!swiper.support.smoothScroll) {
animateCSSModeScroll({
swiper,
targetPosition: -newTranslate,
side: isH ? 'left' : 'top'
});
return true;
}
wrapperEl.scrollTo({
[isH ? 'left' : 'top']: -newTranslate,
behavior: 'smooth'
});
}
return true;
}
if (speed === 0) {
swiper.setTransition(0);
swiper.setTranslate(newTranslate);
if (runCallbacks) {
swiper.emit('beforeTransitionStart', speed, internal);
swiper.emit('transitionEnd');
}
} else {
swiper.setTransition(speed);
swiper.setTranslate(newTranslate);
if (runCallbacks) {
swiper.emit('beforeTransitionStart', speed, internal);
swiper.emit('transitionStart');
}
if (!swiper.animating) {
swiper.animating = true;
if (!swiper.onTranslateToWrapperTransitionEnd) {
swiper.onTranslateToWrapperTransitionEnd = function transitionEnd(e) {
if (!swiper || swiper.destroyed) return;
if (e.target !== this) return;
swiper.wrapperEl.removeEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
swiper.onTranslateToWrapperTransitionEnd = null;
delete swiper.onTranslateToWrapperTransitionEnd;
swiper.animating = false;
if (runCallbacks) {
swiper.emit('transitionEnd');
}
};
}
swiper.wrapperEl.addEventListener('transitionend', swiper.onTranslateToWrapperTransitionEnd);
}
}
return true;
}
var translate = {
getTranslate: getSwiperTranslate,
setTranslate,
minTranslate,
maxTranslate,
translateTo
};
function setTransition(duration, byController) {
const swiper = this;
if (!swiper.params.cssMode) {
swiper.wrapperEl.style.transitionDuration = `${duration}ms`;
swiper.wrapperEl.style.transitionDelay = duration === 0 ? `0ms` : '';
}
swiper.emit('setTransition', duration, byController);
}
function transitionEmit(_ref) {
let {
swiper,
runCallbacks,
direction,
step
} = _ref;
const {
activeIndex,
previousIndex
} = swiper;
let dir = direction;
if (!dir) {
if (activeIndex > previousIndex) dir = 'next';else if (activeIndex < previousIndex) dir = 'prev';else dir = 'reset';
}
swiper.emit(`transition${step}`);
if (runCallbacks && dir === 'reset') {
swiper.emit(`slideResetTransition${step}`);
} else if (runCallbacks && activeIndex !== previousIndex) {
swiper.emit(`slideChangeTransition${step}`);
if (dir === 'next') {
swiper.emit(`slideNextTransition${step}`);
} else {
swiper.emit(`slidePrevTransition${step}`);
}
}
}
function transitionStart(runCallbacks, direction) {
if (runCallbacks === void 0) {
runCallbacks = true;
}
const swiper = this;
const {
params
} = swiper;
if (params.cssMode) return;
if (params.autoHeight) {
swiper.updateAutoHeight();
}
transitionEmit({
swiper,
runCallbacks,
direction,
step: 'Start'
});
}
function transitionEnd(runCallbacks, direction) {
if (runCallbacks === void 0) {
runCallbacks = true;
}
const swiper = this;
const {
params
} = swiper;
swiper.animating = false;
if (params.cssMode) return;
swiper.setTransition(0);
transitionEmit({
swiper,
runCallbacks,
direction,
step: 'End'
});
}
var transition = {
setTransition,
transitionStart,
transitionEnd
};
function slideTo(index, speed, runCallbacks, internal, initial) {
if (index === void 0) {
index = 0;
}
if (runCallbacks === void 0) {
runCallbacks = true;
}
if (typeof index === 'string') {
index = parseInt(index, 10);
}
const swiper = this;
let slideIndex = index;
if (slideIndex < 0) slideIndex = 0;
const {
params,
snapGrid,
slidesGrid,
previousIndex,
activeIndex,
rtlTranslate: rtl,
wrapperEl,
enabled
} = swiper;
if (!enabled && !internal && !initial || swiper.destroyed || swiper.animating && params.preventInteractionOnTransition) {
return false;
}
if (typeof speed === 'undefined') {
speed = swiper.params.speed;
}
const skip = Math.min(swiper.params.slidesPerGroupSkip, slideIndex);
let snapIndex = skip + Math.floor((slideIndex - skip) / swiper.params.slidesPerGroup);
if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1;
const translate = -snapGrid[snapIndex];
// Normalize slideIndex
if (params.normalizeSlideIndex) {
for (let i = 0; i < slidesGrid.length; i += 1) {
const normalizedTranslate = -Math.floor(translate * 100);
const normalizedGrid = Math.floor(slidesGrid[i] * 100);
const normalizedGridNext = Math.floor(slidesGrid[i + 1] * 100);
if (typeof slidesGrid[i + 1] !== 'undefined') {
if (normalizedTranslate >= normalizedGrid && normalizedTranslate < normalizedGridNext - (normalizedGridNext - normalizedGrid) / 2) {
slideIndex = i;
} else if (normalizedTranslate >= normalizedGrid && normalizedTranslate < normalizedGridNext) {
slideIndex = i + 1;
}
} else if (normalizedTranslate >= normalizedGrid) {
slideIndex = i;
}
}
}
// Directions locks
if (swiper.initialized && slideIndex !== activeIndex) {
if (!swiper.allowSlideNext && (rtl ? translate > swiper.translate && translate > swiper.minTranslate() : translate < swiper.translate && translate < swiper.minTranslate())) {
return false;
}
if (!swiper.allowSlidePrev && translate > swiper.translate && translate > swiper.maxTranslate()) {
if ((activeIndex || 0) !== slideIndex) {
return false;
}
}
}
if (slideIndex !== (previousIndex || 0) && runCallbacks) {
swiper.emit('beforeSlideChangeStart');
}
// Update progress
swiper.updateProgress(translate);
let direction;
if (slideIndex > activeIndex) direction = 'next';else if (slideIndex < activeIndex) direction = 'prev';else direction = 'reset';
// initial virtual
const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
const isInitialVirtual = isVirtual && initial;
// Update Index
if (!isInitialVirtual && (rtl && -translate === swiper.translate || !rtl && translate === swiper.translate)) {
swiper.updateActiveIndex(slideIndex);
// Update Height
if (params.autoHeight) {
swiper.updateAutoHeight();
}
swiper.updateSlidesClasses();
if (params.effect !== 'slide') {
swiper.setTranslate(translate);
}
if (direction !== 'reset') {
swiper.transitionStart(runCallbacks, direction);
swiper.transitionEnd(runCallbacks, direction);
}
return false;
}
if (params.cssMode) {
const isH = swiper.isHorizontal();
const t = rtl ? translate : -translate;
if (speed === 0) {
if (isVirtual) {
swiper.wrapperEl.style.scrollSnapType = 'none';
swiper._immediateVirtual = true;
}
if (isVirtual && !swiper._cssModeVirtualInitialSet && swiper.params.initialSlide > 0) {
swiper._cssModeVirtualInitialSet = true;
requestAnimationFrame(() => {
wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = t;
});
} else {
wrapperEl[isH ? 'scrollLeft' : 'scrollTop'] = t;
}
if (isVirtual) {
requestAnimationFrame(() => {
swiper.wrapperEl.style.scrollSnapType = '';
swiper._immediateVirtual = false;
});
}
} else {
if (!swiper.support.smoothScroll) {
animateCSSModeScroll({
swiper,
targetPosition: t,
side: isH ? 'left' : 'top'
});
return true;
}
wrapperEl.scrollTo({
[isH ? 'left' : 'top']: t,
behavior: 'smooth'
});
}
return true;
}
const browser = getBrowser();
const isSafari = browser.isSafari;
if (isVirtual && !initial && isSafari && swiper.isElement) {
swiper.virtual.update(false, false, slideIndex);
}
swiper.setTransition(speed);
swiper.setTranslate(translate);
swiper.updateActiveIndex(slideIndex);
swiper.updateSlidesClasses();
swiper.emit('beforeTransitionStart', speed, internal);
swiper.transitionStart(runCallbacks, direction);
if (speed === 0) {
swiper.transitionEnd(runCallbacks, direction);
} else if (!swiper.animating) {
swiper.animating = true;
if (!swiper.onSlideToWrapperTransitionEnd) {
swiper.onSlideToWrapperTransitionEnd = function transitionEnd(e) {
if (!swiper || swiper.destroyed) return;
if (e.target !== this) return;
swiper.wrapperEl.removeEventListener('transitionend', swiper.onSlideToWrapperTransitionEnd);
swiper.onSlideToWrapperTransitionEnd = null;
delete swiper.onSlideToWrapperTransitionEnd;
swiper.transitionEnd(runCallbacks, direction);
};
}
swiper.wrapperEl.addEventListener('transitionend', swiper.onSlideToWrapperTransitionEnd);
}
return true;
}
function slideToLoop(index, speed, runCallbacks, internal) {
if (index === void 0) {
index = 0;
}
if (runCallbacks === void 0) {
runCallbacks = true;
}
if (typeof index === 'string') {
const indexAsNumber = parseInt(index, 10);
index = indexAsNumber;
}
const swiper = this;
if (swiper.destroyed) return;
if (typeof speed === 'undefined') {
speed = swiper.params.speed;
}
const gridEnabled = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1;
let newIndex = index;
if (swiper.params.loop) {
if (swiper.virtual && swiper.params.virtual.enabled) {
// eslint-disable-next-line
newIndex = newIndex + swiper.virtual.slidesBefore;
} else {
let targetSlideIndex;
if (gridEnabled) {
const slideIndex = newIndex * swiper.params.grid.rows;
targetSlideIndex = swiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex).column;
} else {
targetSlideIndex = swiper.getSlideIndexByData(newIndex);
}
const cols = gridEnabled ? Math.ceil(swiper.slides.length / swiper.params.grid.rows) : swiper.slides.length;
const {
centeredSlides
} = swiper.params;
let slidesPerView = swiper.params.slidesPerView;
if (slidesPerView === 'auto') {
slidesPerView = swiper.slidesPerViewDynamic();
} else {
slidesPerView = Math.ceil(parseFloat(swiper.params.slidesPerView, 10));
if (centeredSlides && slidesPerView % 2 === 0) {
slidesPerView = slidesPerView + 1;
}
}
let needLoopFix = cols - targetSlideIndex < slidesPerView;
if (centeredSlides) {
needLoopFix = needLoopFix || targetSlideIndex < Math.ceil(slidesPerView / 2);
}
if (internal && centeredSlides && swiper.params.slidesPerView !== 'auto' && !gridEnabled) {
needLoopFix = false;
}
if (needLoopFix) {
const direction = centeredSlides ? targetSlideIndex < swiper.activeIndex ? 'prev' : 'next' : targetSlideIndex - swiper.activeIndex - 1 < swiper.params.slidesPerView ? 'next' : 'prev';
swiper.loopFix({
direction,
slideTo: true,
activeSlideIndex: direction === 'next' ? targetSlideIndex + 1 : targetSlideIndex - cols + 1,
slideRealIndex: direction === 'next' ? swiper.realIndex : undefined
});
}
if (gridEnabled) {
const slideIndex = newIndex * swiper.params.grid.rows;
newIndex = swiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === slideIndex).column;
} else {
newIndex = swiper.getSlideIndexByData(newIndex);
}
}
}
requestAnimationFrame(() => {
swiper.slideTo(newIndex, speed, runCallbacks, internal);
});
return swiper;
}
/* eslint no-unused-vars: "off" */
function slideNext(speed, runCallbacks, internal) {
if (runCallbacks === void 0) {
runCallbacks = true;
}
const swiper = this;
const {
enabled,
params,
animating
} = swiper;
if (!enabled || swiper.destroyed) return swiper;
if (typeof speed === 'undefined') {
speed = swiper.params.speed;
}
let perGroup = params.slidesPerGroup;
if (params.slidesPerView === 'auto' && params.slidesPerGroup === 1 && params.slidesPerGroupAuto) {
perGroup = Math.max(swiper.slidesPerViewDynamic('current', true), 1);
}
const increment = swiper.activeIndex < params.slidesPerGroupSkip ? 1 : perGroup;
const isVirtual = swiper.virtual && params.virtual.enabled;
if (params.loop) {
if (animating && !isVirtual && params.loopPreventsSliding) return false;
swiper.loopFix({
direction: 'next'
});
// eslint-disable-next-line
swiper._clientLeft = swiper.wrapperEl.clientLeft;
if (swiper.activeIndex === swiper.slides.length - 1 && params.cssMode) {
requestAnimationFrame(() => {
swiper.slideTo(swiper.activeIndex + increment, speed, runCallbacks, internal);
});
return true;
}
}
if (params.rewind && swiper.isEnd) {
return swiper.slideTo(0, speed, runCallbacks, internal);
}
return swiper.slideTo(swiper.activeIndex + increment, speed, runCallbacks, internal);
}
/* eslint no-unused-vars: "off" */
function slidePrev(speed, runCallbacks, internal) {
if (runCallbacks === void 0) {
runCallbacks = true;
}
const swiper = this;
const {
params,
snapGrid,
slidesGrid,
rtlTranslate,
enabled,
animating
} = swiper;
if (!enabled || swiper.destroyed) return swiper;
if (typeof speed === 'undefined') {
speed = swiper.params.speed;
}
const isVirtual = swiper.virtual && params.virtual.enabled;
if (params.loop) {
if (animating && !isVirtual && params.loopPreventsSliding) return false;
swiper.loopFix({
direction: 'prev'
});
// eslint-disable-next-line
swiper._clientLeft = swiper.wrapperEl.clientLeft;
}
const translate = rtlTranslate ? swiper.translate : -swiper.translate;
function normalize(val) {
if (val < 0) return -Math.floor(Math.abs(val));
return Math.floor(val);
}
const normalizedTranslate = normalize(translate);
const normalizedSnapGrid = snapGrid.map(val => normalize(val));
const isFreeMode = params.freeMode && params.freeMode.enabled;
let prevSnap = snapGrid[normalizedSnapGrid.indexOf(normalizedTranslate) - 1];
if (typeof prevSnap === 'undefined' && (params.cssMode || isFreeMode)) {
let prevSnapIndex;
snapGrid.forEach((snap, snapIndex) => {
if (normalizedTranslate >= snap) {
// prevSnap = snap;
prevSnapIndex = snapIndex;
}
});
if (typeof prevSnapIndex !== 'undefined') {
prevSnap = isFreeMode ? snapGrid[prevSnapIndex] : snapGrid[prevSnapIndex > 0 ? prevSnapIndex - 1 : prevSnapIndex];
}
}
let prevIndex = 0;
if (typeof prevSnap !== 'undefined') {
prevIndex = slidesGrid.indexOf(prevSnap);
if (prevIndex < 0) prevIndex = swiper.activeIndex - 1;
if (params.slidesPerView === 'auto' && params.slidesPerGroup === 1 && params.slidesPerGroupAuto) {
prevIndex = prevIndex - swiper.slidesPerViewDynamic('previous', true) + 1;
prevIndex = Math.max(prevIndex, 0);
}
}
if (params.rewind && swiper.isBeginning) {
const lastIndex = swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual ? swiper.virtual.slides.length - 1 : swiper.slides.length - 1;
return swiper.slideTo(lastIndex, speed, runCallbacks, internal);
} else if (params.loop && swiper.activeIndex === 0 && params.cssMode) {
requestAnimationFrame(() => {
swiper.slideTo(prevIndex, speed, runCallbacks, internal);
});
return true;
}
return swiper.slideTo(prevIndex, speed, runCallbacks, internal);
}
/* eslint no-unused-vars: "off" */
function slideReset(speed, runCallbacks, internal) {
if (runCallbacks === void 0) {
runCallbacks = true;
}
const swiper = this;
if (swiper.destroyed) return;
if (typeof speed === 'undefined') {
speed = swiper.params.speed;
}
return swiper.slideTo(swiper.activeIndex, speed, runCallbacks, internal);
}
/* eslint no-unused-vars: "off" */
function slideToClosest(speed, runCallbacks, internal, threshold) {
if (runCallbacks === void 0) {
runCallbacks = true;
}
if (threshold === void 0) {
threshold = 0.5;
}
const swiper = this;
if (swiper.destroyed) return;
if (typeof speed === 'undefined') {
speed = swiper.params.speed;
}
let index = swiper.activeIndex;
const skip = Math.min(swiper.params.slidesPerGroupSkip, index);
const snapIndex = skip + Math.floor((index - skip) / swiper.params.slidesPerGroup);
const translate = swiper.rtlTranslate ? swiper.translate : -swiper.translate;
if (translate >= swiper.snapGrid[snapIndex]) {
// The current translate is on or after the current snap index, so the choice
// is between the current index and the one after it.
const currentSnap = swiper.snapGrid[snapIndex];
const nextSnap = swiper.snapGrid[snapIndex + 1];
if (translate - currentSnap > (nextSnap - currentSnap) * threshold) {
index += swiper.params.slidesPerGroup;
}
} else {
// The current translate is before the current snap index, so the choice
// is between the current index and the one before it.
const prevSnap = swiper.snapGrid[snapIndex - 1];
const currentSnap = swiper.snapGrid[snapIndex];
if (translate - prevSnap <= (currentSnap - prevSnap) * threshold) {
index -= swiper.params.slidesPerGroup;
}
}
index = Math.max(index, 0);
index = Math.min(index, swiper.slidesGrid.length - 1);
return swiper.slideTo(index, speed, runCallbacks, internal);
}
function slideToClickedSlide() {
const swiper = this;
if (swiper.destroyed) return;
const {
params,
slidesEl
} = swiper;
const slidesPerView = params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : params.slidesPerView;
let slideToIndex = swiper.getSlideIndexWhenGrid(swiper.clickedIndex);
let realIndex;
const slideSelector = swiper.isElement ? `swiper-slide` : `.${params.slideClass}`;
const isGrid = swiper.grid && swiper.params.grid && swiper.params.grid.rows > 1;
if (params.loop) {
if (swiper.animating) return;
realIndex = parseInt(swiper.clickedSlide.getAttribute('data-swiper-slide-index'), 10);
if (params.centeredSlides) {
swiper.slideToLoop(realIndex);
} else if (slideToIndex > (isGrid ? (swiper.slides.length - slidesPerView) / 2 - (swiper.params.grid.rows - 1) : swiper.slides.length - slidesPerView)) {
swiper.loopFix();
slideToIndex = swiper.getSlideIndex(utils_elementChildren(slidesEl, `${slideSelector}[data-swiper-slide-index="${realIndex}"]`)[0]);
utils_nextTick(() => {
swiper.slideTo(slideToIndex);
});
} else {
swiper.slideTo(slideToIndex);
}
} else {
swiper.slideTo(slideToIndex);
}
}
var slide = {
slideTo,
slideToLoop,
slideNext,
slidePrev,
slideReset,
slideToClosest,
slideToClickedSlide
};
function loopCreate(slideRealIndex, initial) {
const swiper = this;
const {
params,
slidesEl
} = swiper;
if (!params.loop || swiper.virtual && swiper.params.virtual.enabled) return;
const initSlides = () => {
const slides = utils_elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
slides.forEach((el, index) => {
el.setAttribute('data-swiper-slide-index', index);
});
};
const clearBlankSlides = () => {
const slides = utils_elementChildren(slidesEl, `.${params.slideBlankClass}`);
slides.forEach(el => {
el.remove();
});
if (slides.length > 0) {
swiper.recalcSlides();
swiper.updateSlides();
}
};
const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
if (params.loopAddBlankSlides && (params.slidesPerGroup > 1 || gridEnabled)) {
clearBlankSlides();
}
const slidesPerGroup = params.slidesPerGroup * (gridEnabled ? params.grid.rows : 1);
const shouldFillGroup = swiper.slides.length % slidesPerGroup !== 0;
const shouldFillGrid = gridEnabled && swiper.slides.length % params.grid.rows !== 0;
const addBlankSlides = amountOfSlides => {
for (let i = 0; i < amountOfSlides; i += 1) {
const slideEl = swiper.isElement ? utils_createElement('swiper-slide', [params.slideBlankClass]) : utils_createElement('div', [params.slideClass, params.slideBlankClass]);
swiper.slidesEl.append(slideEl);
}
};
if (shouldFillGroup) {
if (params.loopAddBlankSlides) {
const slidesToAdd = slidesPerGroup - swiper.slides.length % slidesPerGroup;
addBlankSlides(slidesToAdd);
swiper.recalcSlides();
swiper.updateSlides();
} else {
showWarning('Swiper Loop Warning: The number of slides is not even to slidesPerGroup, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)');
}
initSlides();
} else if (shouldFillGrid) {
if (params.loopAddBlankSlides) {
const slidesToAdd = params.grid.rows - swiper.slides.length % params.grid.rows;
addBlankSlides(slidesToAdd);
swiper.recalcSlides();
swiper.updateSlides();
} else {
showWarning('Swiper Loop Warning: The number of slides is not even to grid.rows, loop mode may not function properly. You need to add more slides (or make duplicates, or empty slides)');
}
initSlides();
} else {
initSlides();
}
swiper.loopFix({
slideRealIndex,
direction: params.centeredSlides ? undefined : 'next',
initial
});
}
function loopFix(_temp) {
let {
slideRealIndex,
slideTo = true,
direction,
setTranslate,
activeSlideIndex,
initial,
byController,
byMousewheel
} = _temp === void 0 ? {} : _temp;
const swiper = this;
if (!swiper.params.loop) return;
swiper.emit('beforeLoopFix');
const {
slides,
allowSlidePrev,
allowSlideNext,
slidesEl,
params
} = swiper;
const {
centeredSlides,
initialSlide
} = params;
swiper.allowSlidePrev = true;
swiper.allowSlideNext = true;
if (swiper.virtual && params.virtual.enabled) {
if (slideTo) {
if (!params.centeredSlides && swiper.snapIndex === 0) {
swiper.slideTo(swiper.virtual.slides.length, 0, false, true);
} else if (params.centeredSlides && swiper.snapIndex < params.slidesPerView) {
swiper.slideTo(swiper.virtual.slides.length + swiper.snapIndex, 0, false, true);
} else if (swiper.snapIndex === swiper.snapGrid.length - 1) {
swiper.slideTo(swiper.virtual.slidesBefore, 0, false, true);
}
}
swiper.allowSlidePrev = allowSlidePrev;
swiper.allowSlideNext = allowSlideNext;
swiper.emit('loopFix');
return;
}
let slidesPerView = params.slidesPerView;
if (slidesPerView === 'auto') {
slidesPerView = swiper.slidesPerViewDynamic();
} else {
slidesPerView = Math.ceil(parseFloat(params.slidesPerView, 10));
if (centeredSlides && slidesPerView % 2 === 0) {
slidesPerView = slidesPerView + 1;
}
}
const slidesPerGroup = params.slidesPerGroupAuto ? slidesPerView : params.slidesPerGroup;
let loopedSlides = centeredSlides ? Math.max(slidesPerGroup, Math.ceil(slidesPerView / 2)) : slidesPerGroup;
if (loopedSlides % slidesPerGroup !== 0) {
loopedSlides += slidesPerGroup - loopedSlides % slidesPerGroup;
}
loopedSlides += params.loopAdditionalSlides;
swiper.loopedSlides = loopedSlides;
const gridEnabled = swiper.grid && params.grid && params.grid.rows > 1;
if (slides.length < slidesPerView + loopedSlides || swiper.params.effect === 'cards' && slides.length < slidesPerView + loopedSlides * 2) {
showWarning('Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled or not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters');
} else if (gridEnabled && params.grid.fill === 'row') {
showWarning('Swiper Loop Warning: Loop mode is not compatible with grid.fill = `row`');
}
const prependSlidesIndexes = [];
const appendSlidesIndexes = [];
const cols = gridEnabled ? Math.ceil(slides.length / params.grid.rows) : slides.length;
const isInitialOverflow = initial && cols - initialSlide < slidesPerView && !centeredSlides;
let activeIndex = isInitialOverflow ? initialSlide : swiper.activeIndex;
if (typeof activeSlideIndex === 'undefined') {
activeSlideIndex = swiper.getSlideIndex(slides.find(el => el.classList.contains(params.slideActiveClass)));
} else {
activeIndex = activeSlideIndex;
}
const isNext = direction === 'next' || !direction;
const isPrev = direction === 'prev' || !direction;
let slidesPrepended = 0;
let slidesAppended = 0;
const activeColIndex = gridEnabled ? slides[activeSlideIndex].column : activeSlideIndex;
const activeColIndexWithShift = activeColIndex + (centeredSlides && typeof setTranslate === 'undefined' ? -slidesPerView / 2 + 0.5 : 0);
// prepend last slides before start
if (activeColIndexWithShift < loopedSlides) {
slidesPrepended = Math.max(loopedSlides - activeColIndexWithShift, slidesPerGroup);
for (let i = 0; i < loopedSlides - activeColIndexWithShift; i += 1) {
const index = i - Math.floor(i / cols) * cols;
if (gridEnabled) {
const colIndexToPrepend = cols - index - 1;
for (let i = slides.length - 1; i >= 0; i -= 1) {
if (slides[i].column === colIndexToPrepend) prependSlidesIndexes.push(i);
}
// slides.forEach((slide, slideIndex) => {
// if (slide.column === colIndexToPrepend) prependSlidesIndexes.push(slideIndex);
// });
} else {
prependSlidesIndexes.push(cols - index - 1);
}
}
} else if (activeColIndexWithShift + slidesPerView > cols - loopedSlides) {
slidesAppended = Math.max(activeColIndexWithShift - (cols - loopedSlides * 2), slidesPerGroup);
if (isInitialOverflow) {
slidesAppended = Math.max(slidesAppended, slidesPerView - cols + initialSlide + 1);
}
for (let i = 0; i < slidesAppended; i += 1) {
const index = i - Math.floor(i / cols) * cols;
if (gridEnabled) {
slides.forEach((slide, slideIndex) => {
if (slide.column === index) appendSlidesIndexes.push(slideIndex);
});
} else {
appendSlidesIndexes.push(index);
}
}
}
swiper.__preventObserver__ = true;
requestAnimationFrame(() => {
swiper.__preventObserver__ = false;
});
if (swiper.params.effect === 'cards' && slides.length < slidesPerView + loopedSlides * 2) {
if (appendSlidesIndexes.includes(activeSlideIndex)) {
appendSlidesIndexes.splice(appendSlidesIndexes.indexOf(activeSlideIndex), 1);
}
if (prependSlidesIndexes.includes(activeSlideIndex)) {
prependSlidesIndexes.splice(prependSlidesIndexes.indexOf(activeSlideIndex), 1);
}
}
if (isPrev) {
prependSlidesIndexes.forEach(index => {
slides[index].swiperLoopMoveDOM = true;
slidesEl.prepend(slides[index]);
slides[index].swiperLoopMoveDOM = false;
});
}
if (isNext) {
appendSlidesIndexes.forEach(index => {
slides[index].swiperLoopMoveDOM = true;
slidesEl.append(slides[index]);
slides[index].swiperLoopMoveDOM = false;
});
}
swiper.recalcSlides();
if (params.slidesPerView === 'auto') {
swiper.updateSlides();
} else if (gridEnabled && (prependSlidesIndexes.length > 0 && isPrev || appendSlidesIndexes.length > 0 && isNext)) {
swiper.slides.forEach((slide, slideIndex) => {
swiper.grid.updateSlide(slideIndex, slide, swiper.slides);
});
}
if (params.watchSlidesProgress) {
swiper.updateSlidesOffset();
}
if (slideTo) {
if (prependSlidesIndexes.length > 0 && isPrev) {
if (typeof slideRealIndex === 'undefined') {
const currentSlideTranslate = swiper.slidesGrid[activeIndex];
const newSlideTranslate = swiper.slidesGrid[activeIndex + slidesPrepended];
const diff = newSlideTranslate - currentSlideTranslate;
if (byMousewheel) {
swiper.setTranslate(swiper.translate - diff);
} else {
swiper.slideTo(activeIndex + Math.ceil(slidesPrepended), 0, false, true);
if (setTranslate) {
swiper.touchEventsData.startTranslate = swiper.touchEventsData.startTranslate - diff;
swiper.touchEventsData.currentTranslate = swiper.touchEventsData.currentTranslate - diff;
}
}
} else {
if (setTranslate) {
const shift = gridEnabled ? prependSlidesIndexes.length / params.grid.rows : prependSlidesIndexes.length;
swiper.slideTo(swiper.activeIndex + shift, 0, false, true);
swiper.touchEventsData.currentTranslate = swiper.translate;
}
}
} else if (appendSlidesIndexes.length > 0 && isNext) {
if (typeof slideRealIndex === 'undefined') {
const currentSlideTranslate = swiper.slidesGrid[activeIndex];
const newSlideTranslate = swiper.slidesGrid[activeIndex - slidesAppended];
const diff = newSlideTranslate - currentSlideTranslate;
if (byMousewheel) {
swiper.setTranslate(swiper.translate - diff);
} else {
swiper.slideTo(activeIndex - slidesAppended, 0, false, true);
if (setTranslate) {
swiper.touchEventsData.startTranslate = swiper.touchEventsData.startTranslate - diff;
swiper.touchEventsData.currentTranslate = swiper.touchEventsData.currentTranslate - diff;
}
}
} else {
const shift = gridEnabled ? appendSlidesIndexes.length / params.grid.rows : appendSlidesIndexes.length;
swiper.slideTo(swiper.activeIndex - shift, 0, false, true);
}
}
}
swiper.allowSlidePrev = allowSlidePrev;
swiper.allowSlideNext = allowSlideNext;
if (swiper.controller && swiper.controller.control && !byController) {
const loopParams = {
slideRealIndex,
direction,
setTranslate,
activeSlideIndex,
byController: true
};
if (Array.isArray(swiper.controller.control)) {
swiper.controller.control.forEach(c => {
if (!c.destroyed && c.params.loop) c.loopFix({
...loopParams,
slideTo: c.params.slidesPerView === params.slidesPerView ? slideTo : false
});
});
} else if (swiper.controller.control instanceof swiper.constructor && swiper.controller.control.params.loop) {
swiper.controller.control.loopFix({
...loopParams,
slideTo: swiper.controller.control.params.slidesPerView === params.slidesPerView ? slideTo : false
});
}
}
swiper.emit('loopFix');
}
function loopDestroy() {
const swiper = this;
const {
params,
slidesEl
} = swiper;
if (!params.loop || !slidesEl || swiper.virtual && swiper.params.virtual.enabled) return;
swiper.recalcSlides();
const newSlidesOrder = [];
swiper.slides.forEach(slideEl => {
const index = typeof slideEl.swiperSlideIndex === 'undefined' ? slideEl.getAttribute('data-swiper-slide-index') * 1 : slideEl.swiperSlideIndex;
newSlidesOrder[index] = slideEl;
});
swiper.slides.forEach(slideEl => {
slideEl.removeAttribute('data-swiper-slide-index');
});
newSlidesOrder.forEach(slideEl => {
slidesEl.append(slideEl);
});
swiper.recalcSlides();
swiper.slideTo(swiper.realIndex, 0);
}
var loop = {
loopCreate,
loopFix,
loopDestroy
};
function setGrabCursor(moving) {
const swiper = this;
if (!swiper.params.simulateTouch || swiper.params.watchOverflow && swiper.isLocked || swiper.params.cssMode) return;
const el = swiper.params.touchEventsTarget === 'container' ? swiper.el : swiper.wrapperEl;
if (swiper.isElement) {
swiper.__preventObserver__ = true;
}
el.style.cursor = 'move';
el.style.cursor = moving ? 'grabbing' : 'grab';
if (swiper.isElement) {
requestAnimationFrame(() => {
swiper.__preventObserver__ = false;
});
}
}
function unsetGrabCursor() {
const swiper = this;
if (swiper.params.watchOverflow && swiper.isLocked || swiper.params.cssMode) {
return;
}
if (swiper.isElement) {
swiper.__preventObserver__ = true;
}
swiper[swiper.params.touchEventsTarget === 'container' ? 'el' : 'wrapperEl'].style.cursor = '';
if (swiper.isElement) {
requestAnimationFrame(() => {
swiper.__preventObserver__ = false;
});
}
}
var grabCursor = {
setGrabCursor,
unsetGrabCursor
};
// Modified from https://stackoverflow.com/questions/54520554/custom-element-getrootnode-closest-function-crossing-multiple-parent-shadowd
function closestElement(selector, base) {
if (base === void 0) {
base = this;
}
function __closestFrom(el) {
if (!el || el === ssr_window_esm_getDocument() || el === ssr_window_esm_getWindow()) return null;
if (el.assignedSlot) el = el.assignedSlot;
const found = el.closest(selector);
if (!found && !el.getRootNode) {
return null;
}
return found || __closestFrom(el.getRootNode().host);
}
return __closestFrom(base);
}
function preventEdgeSwipe(swiper, event, startX) {
const window = ssr_window_esm_getWindow();
const {
params
} = swiper;
const edgeSwipeDetection = params.edgeSwipeDetection;
const edgeSwipeThreshold = params.edgeSwipeThreshold;
if (edgeSwipeDetection && (startX <= edgeSwipeThreshold || startX >= window.innerWidth - edgeSwipeThreshold)) {
if (edgeSwipeDetection === 'prevent') {
event.preventDefault();
return true;
}
return false;
}
return true;
}
function onTouchStart(event) {
const swiper = this;
const document = ssr_window_esm_getDocument();
let e = event;
if (e.originalEvent) e = e.originalEvent;
const data = swiper.touchEventsData;
if (e.type === 'pointerdown') {
if (data.pointerId !== null && data.pointerId !== e.pointerId) {
return;
}
data.pointerId = e.pointerId;
} else if (e.type === 'touchstart' && e.targetTouches.length === 1) {
data.touchId = e.targetTouches[0].identifier;
}
if (e.type === 'touchstart') {
// don't proceed touch event
preventEdgeSwipe(swiper, e, e.targetTouches[0].pageX);
return;
}
const {
params,
touches,
enabled
} = swiper;
if (!enabled) return;
if (!params.simulateTouch && e.pointerType === 'mouse') return;
if (swiper.animating && params.preventInteractionOnTransition) {
return;
}
if (!swiper.animating && params.cssMode && params.loop) {
swiper.loopFix();
}
let targetEl = e.target;
if (params.touchEventsTarget === 'wrapper') {
if (!elementIsChildOf(targetEl, swiper.wrapperEl)) return;
}
if ('which' in e && e.which === 3) return;
if ('button' in e && e.button > 0) return;
if (data.isTouched && data.isMoved) return;
// change target el for shadow root component
const swipingClassHasValue = !!params.noSwipingClass && params.noSwipingClass !== '';
// eslint-disable-next-line
const eventPath = e.composedPath ? e.composedPath() : e.path;
if (swipingClassHasValue && e.target && e.target.shadowRoot && eventPath) {
targetEl = eventPath[0];
}
const noSwipingSelector = params.noSwipingSelector ? params.noSwipingSelector : `.${params.noSwipingClass}`;
const isTargetShadow = !!(e.target && e.target.shadowRoot);
// use closestElement for shadow root element to get the actual closest for nested shadow root element
if (params.noSwiping && (isTargetShadow ? closestElement(noSwipingSelector, targetEl) : targetEl.closest(noSwipingSelector))) {
swiper.allowClick = true;
return;
}
if (params.swipeHandler) {
if (!targetEl.closest(params.swipeHandler)) return;
}
touches.currentX = e.pageX;
touches.currentY = e.pageY;
const startX = touches.currentX;
const startY = touches.currentY;
// Do NOT start if iOS edge swipe is detected. Otherwise iOS app cannot swipe-to-go-back anymore
if (!preventEdgeSwipe(swiper, e, startX)) {
return;
}
Object.assign(data, {
isTouched: true,
isMoved: false,
allowTouchCallbacks: true,
isScrolling: undefined,
startMoving: undefined
});
touches.startX = startX;
touches.startY = startY;
data.touchStartTime = utils_now();
swiper.allowClick = true;
swiper.updateSize();
swiper.swipeDirection = undefined;
if (params.threshold > 0) data.allowThresholdMove = false;
let preventDefault = true;
if (targetEl.matches(data.focusableElements)) {
preventDefault = false;
if (targetEl.nodeName === 'SELECT') {
data.isTouched = false;
}
}
if (document.activeElement && document.activeElement.matches(data.focusableElements) && document.activeElement !== targetEl && (e.pointerType === 'mouse' || e.pointerType !== 'mouse' && !targetEl.matches(data.focusableElements))) {
document.activeElement.blur();
}
const shouldPreventDefault = preventDefault && swiper.allowTouchMove && params.touchStartPreventDefault;
if ((params.touchStartForcePreventDefault || shouldPreventDefault) && !targetEl.isContentEditable) {
e.preventDefault();
}
if (params.freeMode && params.freeMode.enabled && swiper.freeMode && swiper.animating && !params.cssMode) {
swiper.freeMode.onTouchStart();
}
swiper.emit('touchStart', e);
}
function onTouchMove(event) {
const document = ssr_window_esm_getDocument();
const swiper = this;
const data = swiper.touchEventsData;
const {
params,
touches,
rtlTranslate: rtl,
enabled
} = swiper;
if (!enabled) return;
if (!params.simulateTouch && event.pointerType === 'mouse') return;
let e = event;
if (e.originalEvent) e = e.originalEvent;
if (e.type === 'pointermove') {
if (data.touchId !== null) return; // return from pointer if we use touch
const id = e.pointerId;
if (id !== data.pointerId) return;
}
let targetTouch;
if (e.type === 'touchmove') {
targetTouch = [...e.changedTouches].find(t => t.identifier === data.touchId);
if (!targetTouch || targetTouch.identifier !== data.touchId) return;
} else {
targetTouch = e;
}
if (!data.isTouched) {
if (data.startMoving && data.isScrolling) {
swiper.emit('touchMoveOpposite', e);
}
return;
}
const pageX = targetTouch.pageX;
const pageY = targetTouch.pageY;
if (e.preventedByNestedSwiper) {
touches.startX = pageX;
touches.startY = pageY;
return;
}
if (!swiper.allowTouchMove) {
if (!e.target.matches(data.focusableElements)) {
swiper.allowClick = false;
}
if (data.isTouched) {
Object.assign(touches, {
startX: pageX,
startY: pageY,
currentX: pageX,
currentY: pageY
});
data.touchStartTime = utils_now();
}
return;
}
if (params.touchReleaseOnEdges && !params.loop) {
if (swiper.isVertical()) {
// Vertical
if (pageY < touches.startY && swiper.translate <= swiper.maxTranslate() || pageY > touches.startY && swiper.translate >= swiper.minTranslate()) {
data.isTouched = false;
data.isMoved = false;
return;
}
} else if (rtl && (pageX > touches.startX && -swiper.translate <= swiper.maxTranslate() || pageX < touches.startX && -swiper.translate >= swiper.minTranslate())) {
return;
} else if (!rtl && (pageX < touches.startX && swiper.translate <= swiper.maxTranslate() || pageX > touches.startX && swiper.translate >= swiper.minTranslate())) {
return;
}
}
if (document.activeElement && document.activeElement.matches(data.focusableElements) && document.activeElement !== e.target && e.pointerType !== 'mouse') {
document.activeElement.blur();
}
if (document.activeElement) {
if (e.target === document.activeElement && e.target.matches(data.focusableElements)) {
data.isMoved = true;
swiper.allowClick = false;
return;
}
}
if (data.allowTouchCallbacks) {
swiper.emit('touchMove', e);
}
touches.previousX = touches.currentX;
touches.previousY = touches.currentY;
touches.currentX = pageX;
touches.currentY = pageY;
const diffX = touches.currentX - touches.startX;
const diffY = touches.currentY - touches.startY;
if (swiper.params.threshold && Math.sqrt(diffX ** 2 + diffY ** 2) < swiper.params.threshold) return;
if (typeof data.isScrolling === 'undefined') {
let touchAngle;
if (swiper.isHorizontal() && touches.currentY === touches.startY || swiper.isVertical() && touches.currentX === touches.startX) {
data.isScrolling = false;
} else {
// eslint-disable-next-line
if (diffX * diffX + diffY * diffY >= 25) {
touchAngle = Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180 / Math.PI;
data.isScrolling = swiper.isHorizontal() ? touchAngle > params.touchAngle : 90 - touchAngle > params.touchAngle;
}
}
}
if (data.isScrolling) {
swiper.emit('touchMoveOpposite', e);
}
if (typeof data.startMoving === 'undefined') {
if (touches.currentX !== touches.startX || touches.currentY !== touches.startY) {
data.startMoving = true;
}
}
if (data.isScrolling || e.type === 'touchmove' && data.preventTouchMoveFromPointerMove) {
data.isTouched = false;
return;
}
if (!data.startMoving) {
return;
}
swiper.allowClick = false;
if (!params.cssMode && e.cancelable) {
e.preventDefault();
}
if (params.touchMoveStopPropagation && !params.nested) {
e.stopPropagation();
}
let diff = swiper.isHorizontal() ? diffX : diffY;
let touchesDiff = swiper.isHorizontal() ? touches.currentX - touches.previousX : touches.currentY - touches.previousY;
if (params.oneWayMovement) {
diff = Math.abs(diff) * (rtl ? 1 : -1);
touchesDiff = Math.abs(touchesDiff) * (rtl ? 1 : -1);
}
touches.diff = diff;
diff *= params.touchRatio;
if (rtl) {
diff = -diff;
touchesDiff = -touchesDiff;
}
const prevTouchesDirection = swiper.touchesDirection;
swiper.swipeDirection = diff > 0 ? 'prev' : 'next';
swiper.touchesDirection = touchesDiff > 0 ? 'prev' : 'next';
const isLoop = swiper.params.loop && !params.cssMode;
const allowLoopFix = swiper.touchesDirection === 'next' && swiper.allowSlideNext || swiper.touchesDirection === 'prev' && swiper.allowSlidePrev;
if (!data.isMoved) {
if (isLoop && allowLoopFix) {
swiper.loopFix({
direction: swiper.swipeDirection
});
}
data.startTranslate = swiper.getTranslate();
swiper.setTransition(0);
if (swiper.animating) {
const evt = new window.CustomEvent('transitionend', {
bubbles: true,
cancelable: true,
detail: {
bySwiperTouchMove: true
}
});
swiper.wrapperEl.dispatchEvent(evt);
}
data.allowMomentumBounce = false;
// Grab Cursor
if (params.grabCursor && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) {
swiper.setGrabCursor(true);
}
swiper.emit('sliderFirstMove', e);
}
let loopFixed;
new Date().getTime();
if (params._loopSwapReset !== false && data.isMoved && data.allowThresholdMove && prevTouchesDirection !== swiper.touchesDirection && isLoop && allowLoopFix && Math.abs(diff) >= 1) {
Object.assign(touches, {
startX: pageX,
startY: pageY,
currentX: pageX,
currentY: pageY,
startTranslate: data.currentTranslate
});
data.loopSwapReset = true;
data.startTranslate = data.currentTranslate;
return;
}
swiper.emit('sliderMove', e);
data.isMoved = true;
data.currentTranslate = diff + data.startTranslate;
let disableParentSwiper = true;
let resistanceRatio = params.resistanceRatio;
if (params.touchReleaseOnEdges) {
resistanceRatio = 0;
}
if (diff > 0) {
if (isLoop && allowLoopFix && !loopFixed && data.allowThresholdMove && data.currentTranslate > (params.centeredSlides ? swiper.minTranslate() - swiper.slidesSizesGrid[swiper.activeIndex + 1] - (params.slidesPerView !== 'auto' && swiper.slides.length - params.slidesPerView >= 2 ? swiper.slidesSizesGrid[swiper.activeIndex + 1] + swiper.params.spaceBetween : 0) - swiper.params.spaceBetween : swiper.minTranslate())) {
swiper.loopFix({
direction: 'prev',
setTranslate: true,
activeSlideIndex: 0
});
}
if (data.currentTranslate > swiper.minTranslate()) {
disableParentSwiper = false;
if (params.resistance) {
data.currentTranslate = swiper.minTranslate() - 1 + (-swiper.minTranslate() + data.startTranslate + diff) ** resistanceRatio;
}
}
} else if (diff < 0) {
if (isLoop && allowLoopFix && !loopFixed && data.allowThresholdMove && data.currentTranslate < (params.centeredSlides ? swiper.maxTranslate() + swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] + swiper.params.spaceBetween + (params.slidesPerView !== 'auto' && swiper.slides.length - params.slidesPerView >= 2 ? swiper.slidesSizesGrid[swiper.slidesSizesGrid.length - 1] + swiper.params.spaceBetween : 0) : swiper.maxTranslate())) {
swiper.loopFix({
direction: 'next',
setTranslate: true,
activeSlideIndex: swiper.slides.length - (params.slidesPerView === 'auto' ? swiper.slidesPerViewDynamic() : Math.ceil(parseFloat(params.slidesPerView, 10)))
});
}
if (data.currentTranslate < swiper.maxTranslate()) {
disableParentSwiper = false;
if (params.resistance) {
data.currentTranslate = swiper.maxTranslate() + 1 - (swiper.maxTranslate() - data.startTranslate - diff) ** resistanceRatio;
}
}
}
if (disableParentSwiper) {
e.preventedByNestedSwiper = true;
}
// Directions locks
if (!swiper.allowSlideNext && swiper.swipeDirection === 'next' && data.currentTranslate < data.startTranslate) {
data.currentTranslate = data.startTranslate;
}
if (!swiper.allowSlidePrev && swiper.swipeDirection === 'prev' && data.currentTranslate > data.startTranslate) {
data.currentTranslate = data.startTranslate;
}
if (!swiper.allowSlidePrev && !swiper.allowSlideNext) {
data.currentTranslate = data.startTranslate;
}
// Threshold
if (params.threshold > 0) {
if (Math.abs(diff) > params.threshold || data.allowThresholdMove) {
if (!data.allowThresholdMove) {
data.allowThresholdMove = true;
touches.startX = touches.currentX;
touches.startY = touches.currentY;
data.currentTranslate = data.startTranslate;
touches.diff = swiper.isHorizontal() ? touches.currentX - touches.startX : touches.currentY - touches.startY;
return;
}
} else {
data.currentTranslate = data.startTranslate;
return;
}
}
if (!params.followFinger || params.cssMode) return;
// Update active index in free mode
if (params.freeMode && params.freeMode.enabled && swiper.freeMode || params.watchSlidesProgress) {
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
}
if (params.freeMode && params.freeMode.enabled && swiper.freeMode) {
swiper.freeMode.onTouchMove();
}
// Update progress
swiper.updateProgress(data.currentTranslate);
// Update translate
swiper.setTranslate(data.currentTranslate);
}
function onTouchEnd(event) {
const swiper = this;
const data = swiper.touchEventsData;
let e = event;
if (e.originalEvent) e = e.originalEvent;
let targetTouch;
const isTouchEvent = e.type === 'touchend' || e.type === 'touchcancel';
if (!isTouchEvent) {
if (data.touchId !== null) return; // return from pointer if we use touch
if (e.pointerId !== data.pointerId) return;
targetTouch = e;
} else {
targetTouch = [...e.changedTouches].find(t => t.identifier === data.touchId);
if (!targetTouch || targetTouch.identifier !== data.touchId) return;
}
if (['pointercancel', 'pointerout', 'pointerleave', 'contextmenu'].includes(e.type)) {
const proceed = ['pointercancel', 'contextmenu'].includes(e.type) && (swiper.browser.isSafari || swiper.browser.isWebView);
if (!proceed) {
return;
}
}
data.pointerId = null;
data.touchId = null;
const {
params,
touches,
rtlTranslate: rtl,
slidesGrid,
enabled
} = swiper;
if (!enabled) return;
if (!params.simulateTouch && e.pointerType === 'mouse') return;
if (data.allowTouchCallbacks) {
swiper.emit('touchEnd', e);
}
data.allowTouchCallbacks = false;
if (!data.isTouched) {
if (data.isMoved && params.grabCursor) {
swiper.setGrabCursor(false);
}
data.isMoved = false;
data.startMoving = false;
return;
}
// Return Grab Cursor
if (params.grabCursor && data.isMoved && data.isTouched && (swiper.allowSlideNext === true || swiper.allowSlidePrev === true)) {
swiper.setGrabCursor(false);
}
// Time diff
const touchEndTime = utils_now();
const timeDiff = touchEndTime - data.touchStartTime;
// Tap, doubleTap, Click
if (swiper.allowClick) {
const pathTree = e.path || e.composedPath && e.composedPath();
swiper.updateClickedSlide(pathTree && pathTree[0] || e.target, pathTree);
swiper.emit('tap click', e);
if (timeDiff < 300 && touchEndTime - data.lastClickTime < 300) {
swiper.emit('doubleTap doubleClick', e);
}
}
data.lastClickTime = utils_now();
utils_nextTick(() => {
if (!swiper.destroyed) swiper.allowClick = true;
});
if (!data.isTouched || !data.isMoved || !swiper.swipeDirection || touches.diff === 0 && !data.loopSwapReset || data.currentTranslate === data.startTranslate && !data.loopSwapReset) {
data.isTouched = false;
data.isMoved = false;
data.startMoving = false;
return;
}
data.isTouched = false;
data.isMoved = false;
data.startMoving = false;
let currentPos;
if (params.followFinger) {
currentPos = rtl ? swiper.translate : -swiper.translate;
} else {
currentPos = -data.currentTranslate;
}
if (params.cssMode) {
return;
}
if (params.freeMode && params.freeMode.enabled) {
swiper.freeMode.onTouchEnd({
currentPos
});
return;
}
// Find current slide
const swipeToLast = currentPos >= -swiper.maxTranslate() && !swiper.params.loop;
let stopIndex = 0;
let groupSize = swiper.slidesSizesGrid[0];
for (let i = 0; i < slidesGrid.length; i += i < params.slidesPerGroupSkip ? 1 : params.slidesPerGroup) {
const increment = i < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup;
if (typeof slidesGrid[i + increment] !== 'undefined') {
if (swipeToLast || currentPos >= slidesGrid[i] && currentPos < slidesGrid[i + increment]) {
stopIndex = i;
groupSize = slidesGrid[i + increment] - slidesGrid[i];
}
} else if (swipeToLast || currentPos >= slidesGrid[i]) {
stopIndex = i;
groupSize = slidesGrid[slidesGrid.length - 1] - slidesGrid[slidesGrid.length - 2];
}
}
let rewindFirstIndex = null;
let rewindLastIndex = null;
if (params.rewind) {
if (swiper.isBeginning) {
rewindLastIndex = params.virtual && params.virtual.enabled && swiper.virtual ? swiper.virtual.slides.length - 1 : swiper.slides.length - 1;
} else if (swiper.isEnd) {
rewindFirstIndex = 0;
}
}
// Find current slide size
const ratio = (currentPos - slidesGrid[stopIndex]) / groupSize;
const increment = stopIndex < params.slidesPerGroupSkip - 1 ? 1 : params.slidesPerGroup;
if (timeDiff > params.longSwipesMs) {
// Long touches
if (!params.longSwipes) {
swiper.slideTo(swiper.activeIndex);
return;
}
if (swiper.swipeDirection === 'next') {
if (ratio >= params.longSwipesRatio) swiper.slideTo(params.rewind && swiper.isEnd ? rewindFirstIndex : stopIndex + increment);else swiper.slideTo(stopIndex);
}
if (swiper.swipeDirection === 'prev') {
if (ratio > 1 - params.longSwipesRatio) {
swiper.slideTo(stopIndex + increment);
} else if (rewindLastIndex !== null && ratio < 0 && Math.abs(ratio) > params.longSwipesRatio) {
swiper.slideTo(rewindLastIndex);
} else {
swiper.slideTo(stopIndex);
}
}
} else {
// Short swipes
if (!params.shortSwipes) {
swiper.slideTo(swiper.activeIndex);
return;
}
const isNavButtonTarget = swiper.navigation && (e.target === swiper.navigation.nextEl || e.target === swiper.navigation.prevEl);
if (!isNavButtonTarget) {
if (swiper.swipeDirection === 'next') {
swiper.slideTo(rewindFirstIndex !== null ? rewindFirstIndex : stopIndex + increment);
}
if (swiper.swipeDirection === 'prev') {
swiper.slideTo(rewindLastIndex !== null ? rewindLastIndex : stopIndex);
}
} else if (e.target === swiper.navigation.nextEl) {
swiper.slideTo(stopIndex + increment);
} else {
swiper.slideTo(stopIndex);
}
}
}
function onResize() {
const swiper = this;
const {
params,
el
} = swiper;
if (el && el.offsetWidth === 0) return;
// Breakpoints
if (params.breakpoints) {
swiper.setBreakpoint();
}
// Save locks
const {
allowSlideNext,
allowSlidePrev,
snapGrid
} = swiper;
const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
// Disable locks on resize
swiper.allowSlideNext = true;
swiper.allowSlidePrev = true;
swiper.updateSize();
swiper.updateSlides();
swiper.updateSlidesClasses();
const isVirtualLoop = isVirtual && params.loop;
if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !swiper.isBeginning && !swiper.params.centeredSlides && !isVirtualLoop) {
swiper.slideTo(swiper.slides.length - 1, 0, false, true);
} else {
if (swiper.params.loop && !isVirtual) {
swiper.slideToLoop(swiper.realIndex, 0, false, true);
} else {
swiper.slideTo(swiper.activeIndex, 0, false, true);
}
}
if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) {
clearTimeout(swiper.autoplay.resizeTimeout);
swiper.autoplay.resizeTimeout = setTimeout(() => {
if (swiper.autoplay && swiper.autoplay.running && swiper.autoplay.paused) {
swiper.autoplay.resume();
}
}, 500);
}
// Return locks after resize
swiper.allowSlidePrev = allowSlidePrev;
swiper.allowSlideNext = allowSlideNext;
if (swiper.params.watchOverflow && snapGrid !== swiper.snapGrid) {
swiper.checkOverflow();
}
}
function onClick(e) {
const swiper = this;
if (!swiper.enabled) return;
if (!swiper.allowClick) {
if (swiper.params.preventClicks) e.preventDefault();
if (swiper.params.preventClicksPropagation && swiper.animating) {
e.stopPropagation();
e.stopImmediatePropagation();
}
}
}
function onScroll() {
const swiper = this;
const {
wrapperEl,
rtlTranslate,
enabled
} = swiper;
if (!enabled) return;
swiper.previousTranslate = swiper.translate;
if (swiper.isHorizontal()) {
swiper.translate = -wrapperEl.scrollLeft;
} else {
swiper.translate = -wrapperEl.scrollTop;
}
// eslint-disable-next-line
if (swiper.translate === 0) swiper.translate = 0;
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
let newProgress;
const translatesDiff = swiper.maxTranslate() - swiper.minTranslate();
if (translatesDiff === 0) {
newProgress = 0;
} else {
newProgress = (swiper.translate - swiper.minTranslate()) / translatesDiff;
}
if (newProgress !== swiper.progress) {
swiper.updateProgress(rtlTranslate ? -swiper.translate : swiper.translate);
}
swiper.emit('setTranslate', swiper.translate, false);
}
function onLoad(e) {
const swiper = this;
processLazyPreloader(swiper, e.target);
if (swiper.params.cssMode || swiper.params.slidesPerView !== 'auto' && !swiper.params.autoHeight) {
return;
}
swiper.update();
}
function onDocumentTouchStart() {
const swiper = this;
if (swiper.documentTouchHandlerProceeded) return;
swiper.documentTouchHandlerProceeded = true;
if (swiper.params.touchReleaseOnEdges) {
swiper.el.style.touchAction = 'auto';
}
}
const events = (swiper, method) => {
const document = ssr_window_esm_getDocument();
const {
params,
el,
wrapperEl,
device
} = swiper;
const capture = !!params.nested;
const domMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
const swiperMethod = method;
if (!el || typeof el === 'string') return;
// Touch Events
document[domMethod]('touchstart', swiper.onDocumentTouchStart, {
passive: false,
capture
});
el[domMethod]('touchstart', swiper.onTouchStart, {
passive: false
});
el[domMethod]('pointerdown', swiper.onTouchStart, {
passive: false
});
document[domMethod]('touchmove', swiper.onTouchMove, {
passive: false,
capture
});
document[domMethod]('pointermove', swiper.onTouchMove, {
passive: false,
capture
});
document[domMethod]('touchend', swiper.onTouchEnd, {
passive: true
});
document[domMethod]('pointerup', swiper.onTouchEnd, {
passive: true
});
document[domMethod]('pointercancel', swiper.onTouchEnd, {
passive: true
});
document[domMethod]('touchcancel', swiper.onTouchEnd, {
passive: true
});
document[domMethod]('pointerout', swiper.onTouchEnd, {
passive: true
});
document[domMethod]('pointerleave', swiper.onTouchEnd, {
passive: true
});
document[domMethod]('contextmenu', swiper.onTouchEnd, {
passive: true
});
// Prevent Links Clicks
if (params.preventClicks || params.preventClicksPropagation) {
el[domMethod]('click', swiper.onClick, true);
}
if (params.cssMode) {
wrapperEl[domMethod]('scroll', swiper.onScroll);
}
// Resize handler
if (params.updateOnWindowResize) {
swiper[swiperMethod](device.ios || device.android ? 'resize orientationchange observerUpdate' : 'resize observerUpdate', onResize, true);
} else {
swiper[swiperMethod]('observerUpdate', onResize, true);
}
// Images loader
el[domMethod]('load', swiper.onLoad, {
capture: true
});
};
function attachEvents() {
const swiper = this;
const {
params
} = swiper;
swiper.onTouchStart = onTouchStart.bind(swiper);
swiper.onTouchMove = onTouchMove.bind(swiper);
swiper.onTouchEnd = onTouchEnd.bind(swiper);
swiper.onDocumentTouchStart = onDocumentTouchStart.bind(swiper);
if (params.cssMode) {
swiper.onScroll = onScroll.bind(swiper);
}
swiper.onClick = onClick.bind(swiper);
swiper.onLoad = onLoad.bind(swiper);
events(swiper, 'on');
}
function detachEvents() {
const swiper = this;
events(swiper, 'off');
}
var events$1 = {
attachEvents,
detachEvents
};
const isGridEnabled = (swiper, params) => {
return swiper.grid && params.grid && params.grid.rows > 1;
};
function setBreakpoint() {
const swiper = this;
const {
realIndex,
initialized,
params,
el
} = swiper;
const breakpoints = params.breakpoints;
if (!breakpoints || breakpoints && Object.keys(breakpoints).length === 0) return;
const document = ssr_window_esm_getDocument();
// Get breakpoint for window/container width and update parameters
const breakpointsBase = params.breakpointsBase === 'window' || !params.breakpointsBase ? params.breakpointsBase : 'container';
const breakpointContainer = ['window', 'container'].includes(params.breakpointsBase) || !params.breakpointsBase ? swiper.el : document.querySelector(params.breakpointsBase);
const breakpoint = swiper.getBreakpoint(breakpoints, breakpointsBase, breakpointContainer);
if (!breakpoint || swiper.currentBreakpoint === breakpoint) return;
const breakpointOnlyParams = breakpoint in breakpoints ? breakpoints[breakpoint] : undefined;
const breakpointParams = breakpointOnlyParams || swiper.originalParams;
const wasMultiRow = isGridEnabled(swiper, params);
const isMultiRow = isGridEnabled(swiper, breakpointParams);
const wasGrabCursor = swiper.params.grabCursor;
const isGrabCursor = breakpointParams.grabCursor;
const wasEnabled = params.enabled;
if (wasMultiRow && !isMultiRow) {
el.classList.remove(`${params.containerModifierClass}grid`, `${params.containerModifierClass}grid-column`);
swiper.emitContainerClasses();
} else if (!wasMultiRow && isMultiRow) {
el.classList.add(`${params.containerModifierClass}grid`);
if (breakpointParams.grid.fill && breakpointParams.grid.fill === 'column' || !breakpointParams.grid.fill && params.grid.fill === 'column') {
el.classList.add(`${params.containerModifierClass}grid-column`);
}
swiper.emitContainerClasses();
}
if (wasGrabCursor && !isGrabCursor) {
swiper.unsetGrabCursor();
} else if (!wasGrabCursor && isGrabCursor) {
swiper.setGrabCursor();
}
// Toggle navigation, pagination, scrollbar
['navigation', 'pagination', 'scrollbar'].forEach(prop => {
if (typeof breakpointParams[prop] === 'undefined') return;
const wasModuleEnabled = params[prop] && params[prop].enabled;
const isModuleEnabled = breakpointParams[prop] && breakpointParams[prop].enabled;
if (wasModuleEnabled && !isModuleEnabled) {
swiper[prop].disable();
}
if (!wasModuleEnabled && isModuleEnabled) {
swiper[prop].enable();
}
});
const directionChanged = breakpointParams.direction && breakpointParams.direction !== params.direction;
const needsReLoop = params.loop && (breakpointParams.slidesPerView !== params.slidesPerView || directionChanged);
const wasLoop = params.loop;
if (directionChanged && initialized) {
swiper.changeDirection();
}
utils_extend(swiper.params, breakpointParams);
const isEnabled = swiper.params.enabled;
const hasLoop = swiper.params.loop;
Object.assign(swiper, {
allowTouchMove: swiper.params.allowTouchMove,
allowSlideNext: swiper.params.allowSlideNext,
allowSlidePrev: swiper.params.allowSlidePrev
});
if (wasEnabled && !isEnabled) {
swiper.disable();
} else if (!wasEnabled && isEnabled) {
swiper.enable();
}
swiper.currentBreakpoint = breakpoint;
swiper.emit('_beforeBreakpoint', breakpointParams);
if (initialized) {
if (needsReLoop) {
swiper.loopDestroy();
swiper.loopCreate(realIndex);
swiper.updateSlides();
} else if (!wasLoop && hasLoop) {
swiper.loopCreate(realIndex);
swiper.updateSlides();
} else if (wasLoop && !hasLoop) {
swiper.loopDestroy();
}
}
swiper.emit('breakpoint', breakpointParams);
}
function getBreakpoint(breakpoints, base, containerEl) {
if (base === void 0) {
base = 'window';
}
if (!breakpoints || base === 'container' && !containerEl) return undefined;
let breakpoint = false;
const window = ssr_window_esm_getWindow();
const currentHeight = base === 'window' ? window.innerHeight : containerEl.clientHeight;
const points = Object.keys(breakpoints).map(point => {
if (typeof point === 'string' && point.indexOf('@') === 0) {
const minRatio = parseFloat(point.substr(1));
const value = currentHeight * minRatio;
return {
value,
point
};
}
return {
value: point,
point
};
});
points.sort((a, b) => parseInt(a.value, 10) - parseInt(b.value, 10));
for (let i = 0; i < points.length; i += 1) {
const {
point,
value
} = points[i];
if (base === 'window') {
if (window.matchMedia(`(min-width: ${value}px)`).matches) {
breakpoint = point;
}
} else if (value <= containerEl.clientWidth) {
breakpoint = point;
}
}
return breakpoint || 'max';
}
var breakpoints = {
setBreakpoint,
getBreakpoint
};
function prepareClasses(entries, prefix) {
const resultClasses = [];
entries.forEach(item => {
if (typeof item === 'object') {
Object.keys(item).forEach(classNames => {
if (item[classNames]) {
resultClasses.push(prefix + classNames);
}
});
} else if (typeof item === 'string') {
resultClasses.push(prefix + item);
}
});
return resultClasses;
}
function addClasses() {
const swiper = this;
const {
classNames,
params,
rtl,
el,
device
} = swiper;
// prettier-ignore
const suffixes = prepareClasses(['initialized', params.direction, {
'free-mode': swiper.params.freeMode && params.freeMode.enabled
}, {
'autoheight': params.autoHeight
}, {
'rtl': rtl
}, {
'grid': params.grid && params.grid.rows > 1
}, {
'grid-column': params.grid && params.grid.rows > 1 && params.grid.fill === 'column'
}, {
'android': device.android
}, {
'ios': device.ios
}, {
'css-mode': params.cssMode
}, {
'centered': params.cssMode && params.centeredSlides
}, {
'watch-progress': params.watchSlidesProgress
}], params.containerModifierClass);
classNames.push(...suffixes);
el.classList.add(...classNames);
swiper.emitContainerClasses();
}
function removeClasses() {
const swiper = this;
const {
el,
classNames
} = swiper;
if (!el || typeof el === 'string') return;
el.classList.remove(...classNames);
swiper.emitContainerClasses();
}
var classes = {
addClasses,
removeClasses
};
function checkOverflow() {
const swiper = this;
const {
isLocked: wasLocked,
params
} = swiper;
const {
slidesOffsetBefore
} = params;
if (slidesOffsetBefore) {
const lastSlideIndex = swiper.slides.length - 1;
const lastSlideRightEdge = swiper.slidesGrid[lastSlideIndex] + swiper.slidesSizesGrid[lastSlideIndex] + slidesOffsetBefore * 2;
swiper.isLocked = swiper.size > lastSlideRightEdge;
} else {
swiper.isLocked = swiper.snapGrid.length === 1;
}
if (params.allowSlideNext === true) {
swiper.allowSlideNext = !swiper.isLocked;
}
if (params.allowSlidePrev === true) {
swiper.allowSlidePrev = !swiper.isLocked;
}
if (wasLocked && wasLocked !== swiper.isLocked) {
swiper.isEnd = false;
}
if (wasLocked !== swiper.isLocked) {
swiper.emit(swiper.isLocked ? 'lock' : 'unlock');
}
}
var checkOverflow$1 = {
checkOverflow
};
var defaults = {
init: true,
direction: 'horizontal',
oneWayMovement: false,
swiperElementNodeName: 'SWIPER-CONTAINER',
touchEventsTarget: 'wrapper',
initialSlide: 0,
speed: 300,
cssMode: false,
updateOnWindowResize: true,
resizeObserver: true,
nested: false,
createElements: false,
eventsPrefix: 'swiper',
enabled: true,
focusableElements: 'input, select, option, textarea, button, video, label',
// Overrides
width: null,
height: null,
//
preventInteractionOnTransition: false,
// ssr
userAgent: null,
url: null,
// To support iOS's swipe-to-go-back gesture (when being used in-app).
edgeSwipeDetection: false,
edgeSwipeThreshold: 20,
// Autoheight
autoHeight: false,
// Set wrapper width
setWrapperSize: false,
// Virtual Translate
virtualTranslate: false,
// Effects
effect: 'slide',
// 'slide' or 'fade' or 'cube' or 'coverflow' or 'flip'
// Breakpoints
breakpoints: undefined,
breakpointsBase: 'window',
// Slides grid
spaceBetween: 0,
slidesPerView: 1,
slidesPerGroup: 1,
slidesPerGroupSkip: 0,
slidesPerGroupAuto: false,
centeredSlides: false,
centeredSlidesBounds: false,
slidesOffsetBefore: 0,
// in px
slidesOffsetAfter: 0,
// in px
normalizeSlideIndex: true,
centerInsufficientSlides: false,
// Disable swiper and hide navigation when container not overflow
watchOverflow: true,
// Round length
roundLengths: false,
// Touches
touchRatio: 1,
touchAngle: 45,
simulateTouch: true,
shortSwipes: true,
longSwipes: true,
longSwipesRatio: 0.5,
longSwipesMs: 300,
followFinger: true,
allowTouchMove: true,
threshold: 5,
touchMoveStopPropagation: false,
touchStartPreventDefault: true,
touchStartForcePreventDefault: false,
touchReleaseOnEdges: false,
// Unique Navigation Elements
uniqueNavElements: true,
// Resistance
resistance: true,
resistanceRatio: 0.85,
// Progress
watchSlidesProgress: false,
// Cursor
grabCursor: false,
// Clicks
preventClicks: true,
preventClicksPropagation: true,
slideToClickedSlide: false,
// loop
loop: false,
loopAddBlankSlides: true,
loopAdditionalSlides: 0,
loopPreventsSliding: true,
// rewind
rewind: false,
// Swiping/no swiping
allowSlidePrev: true,
allowSlideNext: true,
swipeHandler: null,
// '.swipe-handler',
noSwiping: true,
noSwipingClass: 'swiper-no-swiping',
noSwipingSelector: null,
// Passive Listeners
passiveListeners: true,
maxBackfaceHiddenSlides: 10,
// NS
containerModifierClass: 'swiper-',
// NEW
slideClass: 'swiper-slide',
slideBlankClass: 'swiper-slide-blank',
slideActiveClass: 'swiper-slide-active',
slideVisibleClass: 'swiper-slide-visible',
slideFullyVisibleClass: 'swiper-slide-fully-visible',
slideNextClass: 'swiper-slide-next',
slidePrevClass: 'swiper-slide-prev',
wrapperClass: 'swiper-wrapper',
lazyPreloaderClass: 'swiper-lazy-preloader',
lazyPreloadPrevNext: 0,
// Callbacks
runCallbacksOnInit: true,
// Internals
_emitClasses: false
};
function moduleExtendParams(params, allModulesParams) {
return function extendParams(obj) {
if (obj === void 0) {
obj = {};
}
const moduleParamName = Object.keys(obj)[0];
const moduleParams = obj[moduleParamName];
if (typeof moduleParams !== 'object' || moduleParams === null) {
utils_extend(allModulesParams, obj);
return;
}
if (params[moduleParamName] === true) {
params[moduleParamName] = {
enabled: true
};
}
if (moduleParamName === 'navigation' && params[moduleParamName] && params[moduleParamName].enabled && !params[moduleParamName].prevEl && !params[moduleParamName].nextEl) {
params[moduleParamName].auto = true;
}
if (['pagination', 'scrollbar'].indexOf(moduleParamName) >= 0 && params[moduleParamName] && params[moduleParamName].enabled && !params[moduleParamName].el) {
params[moduleParamName].auto = true;
}
if (!(moduleParamName in params && 'enabled' in moduleParams)) {
utils_extend(allModulesParams, obj);
return;
}
if (typeof params[moduleParamName] === 'object' && !('enabled' in params[moduleParamName])) {
params[moduleParamName].enabled = true;
}
if (!params[moduleParamName]) params[moduleParamName] = {
enabled: false
};
utils_extend(allModulesParams, obj);
};
}
/* eslint no-param-reassign: "off" */
const prototypes = {
eventsEmitter,
update,
translate,
transition,
slide,
loop,
grabCursor,
events: events$1,
breakpoints,
checkOverflow: checkOverflow$1,
classes
};
const extendedDefaults = {};
class Swiper {
constructor() {
let el;
let params;
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
if (args.length === 1 && args[0].constructor && Object.prototype.toString.call(args[0]).slice(8, -1) === 'Object') {
params = args[0];
} else {
[el, params] = args;
}
if (!params) params = {};
params = utils_extend({}, params);
if (el && !params.el) params.el = el;
const document = ssr_window_esm_getDocument();
if (params.el && typeof params.el === 'string' && document.querySelectorAll(params.el).length > 1) {
const swipers = [];
document.querySelectorAll(params.el).forEach(containerEl => {
const newParams = utils_extend({}, params, {
el: containerEl
});
swipers.push(new Swiper(newParams));
});
// eslint-disable-next-line no-constructor-return
return swipers;
}
// Swiper Instance
const swiper = this;
swiper.__swiper__ = true;
swiper.support = getSupport();
swiper.device = getDevice({
userAgent: params.userAgent
});
swiper.browser = getBrowser();
swiper.eventsListeners = {};
swiper.eventsAnyListeners = [];
swiper.modules = [...swiper.__modules__];
if (params.modules && Array.isArray(params.modules)) {
swiper.modules.push(...params.modules);
}
const allModulesParams = {};
swiper.modules.forEach(mod => {
mod({
params,
swiper,
extendParams: moduleExtendParams(params, allModulesParams),
on: swiper.on.bind(swiper),
once: swiper.once.bind(swiper),
off: swiper.off.bind(swiper),
emit: swiper.emit.bind(swiper)
});
});
// Extend defaults with modules params
const swiperParams = utils_extend({}, defaults, allModulesParams);
// Extend defaults with passed params
swiper.params = utils_extend({}, swiperParams, extendedDefaults, params);
swiper.originalParams = utils_extend({}, swiper.params);
swiper.passedParams = utils_extend({}, params);
// add event listeners
if (swiper.params && swiper.params.on) {
Object.keys(swiper.params.on).forEach(eventName => {
swiper.on(eventName, swiper.params.on[eventName]);
});
}
if (swiper.params && swiper.params.onAny) {
swiper.onAny(swiper.params.onAny);
}
// Extend Swiper
Object.assign(swiper, {
enabled: swiper.params.enabled,
el,
// Classes
classNames: [],
// Slides
slides: [],
slidesGrid: [],
snapGrid: [],
slidesSizesGrid: [],
// isDirection
isHorizontal() {
return swiper.params.direction === 'horizontal';
},
isVertical() {
return swiper.params.direction === 'vertical';
},
// Indexes
activeIndex: 0,
realIndex: 0,
//
isBeginning: true,
isEnd: false,
// Props
translate: 0,
previousTranslate: 0,
progress: 0,
velocity: 0,
animating: false,
cssOverflowAdjustment() {
// Returns 0 unless `translate` is > 2**23
// Should be subtracted from css values to prevent overflow
return Math.trunc(this.translate / 2 ** 23) * 2 ** 23;
},
// Locks
allowSlideNext: swiper.params.allowSlideNext,
allowSlidePrev: swiper.params.allowSlidePrev,
// Touch Events
touchEventsData: {
isTouched: undefined,
isMoved: undefined,
allowTouchCallbacks: undefined,
touchStartTime: undefined,
isScrolling: undefined,
currentTranslate: undefined,
startTranslate: undefined,
allowThresholdMove: undefined,
// Form elements to match
focusableElements: swiper.params.focusableElements,
// Last click time
lastClickTime: 0,
clickTimeout: undefined,
// Velocities
velocities: [],
allowMomentumBounce: undefined,
startMoving: undefined,
pointerId: null,
touchId: null
},
// Clicks
allowClick: true,
// Touches
allowTouchMove: swiper.params.allowTouchMove,
touches: {
startX: 0,
startY: 0,
currentX: 0,
currentY: 0,
diff: 0
},
// Images
imagesToLoad: [],
imagesLoaded: 0
});
swiper.emit('_swiper');
// Init
if (swiper.params.init) {
swiper.init();
}
// Return app instance
// eslint-disable-next-line no-constructor-return
return swiper;
}
getDirectionLabel(property) {
if (this.isHorizontal()) {
return property;
}
// prettier-ignore
return {
'width': 'height',
'margin-top': 'margin-left',
'margin-bottom ': 'margin-right',
'margin-left': 'margin-top',
'margin-right': 'margin-bottom',
'padding-left': 'padding-top',
'padding-right': 'padding-bottom',
'marginRight': 'marginBottom'
}[property];
}
getSlideIndex(slideEl) {
const {
slidesEl,
params
} = this;
const slides = utils_elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
const firstSlideIndex = utils_elementIndex(slides[0]);
return utils_elementIndex(slideEl) - firstSlideIndex;
}
getSlideIndexByData(index) {
return this.getSlideIndex(this.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') * 1 === index));
}
getSlideIndexWhenGrid(index) {
if (this.grid && this.params.grid && this.params.grid.rows > 1) {
if (this.params.grid.fill === 'column') {
index = Math.floor(index / this.params.grid.rows);
} else if (this.params.grid.fill === 'row') {
index = index % Math.ceil(this.slides.length / this.params.grid.rows);
}
}
return index;
}
recalcSlides() {
const swiper = this;
const {
slidesEl,
params
} = swiper;
swiper.slides = utils_elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
}
enable() {
const swiper = this;
if (swiper.enabled) return;
swiper.enabled = true;
if (swiper.params.grabCursor) {
swiper.setGrabCursor();
}
swiper.emit('enable');
}
disable() {
const swiper = this;
if (!swiper.enabled) return;
swiper.enabled = false;
if (swiper.params.grabCursor) {
swiper.unsetGrabCursor();
}
swiper.emit('disable');
}
setProgress(progress, speed) {
const swiper = this;
progress = Math.min(Math.max(progress, 0), 1);
const min = swiper.minTranslate();
const max = swiper.maxTranslate();
const current = (max - min) * progress + min;
swiper.translateTo(current, typeof speed === 'undefined' ? 0 : speed);
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
}
emitContainerClasses() {
const swiper = this;
if (!swiper.params._emitClasses || !swiper.el) return;
const cls = swiper.el.className.split(' ').filter(className => {
return className.indexOf('swiper') === 0 || className.indexOf(swiper.params.containerModifierClass) === 0;
});
swiper.emit('_containerClasses', cls.join(' '));
}
getSlideClasses(slideEl) {
const swiper = this;
if (swiper.destroyed) return '';
return slideEl.className.split(' ').filter(className => {
return className.indexOf('swiper-slide') === 0 || className.indexOf(swiper.params.slideClass) === 0;
}).join(' ');
}
emitSlidesClasses() {
const swiper = this;
if (!swiper.params._emitClasses || !swiper.el) return;
const updates = [];
swiper.slides.forEach(slideEl => {
const classNames = swiper.getSlideClasses(slideEl);
updates.push({
slideEl,
classNames
});
swiper.emit('_slideClass', slideEl, classNames);
});
swiper.emit('_slideClasses', updates);
}
slidesPerViewDynamic(view, exact) {
if (view === void 0) {
view = 'current';
}
if (exact === void 0) {
exact = false;
}
const swiper = this;
const {
params,
slides,
slidesGrid,
slidesSizesGrid,
size: swiperSize,
activeIndex
} = swiper;
let spv = 1;
if (typeof params.slidesPerView === 'number') return params.slidesPerView;
if (params.centeredSlides) {
let slideSize = slides[activeIndex] ? Math.ceil(slides[activeIndex].swiperSlideSize) : 0;
let breakLoop;
for (let i = activeIndex + 1; i < slides.length; i += 1) {
if (slides[i] && !breakLoop) {
slideSize += Math.ceil(slides[i].swiperSlideSize);
spv += 1;
if (slideSize > swiperSize) breakLoop = true;
}
}
for (let i = activeIndex - 1; i >= 0; i -= 1) {
if (slides[i] && !breakLoop) {
slideSize += slides[i].swiperSlideSize;
spv += 1;
if (slideSize > swiperSize) breakLoop = true;
}
}
} else {
// eslint-disable-next-line
if (view === 'current') {
for (let i = activeIndex + 1; i < slides.length; i += 1) {
const slideInView = exact ? slidesGrid[i] + slidesSizesGrid[i] - slidesGrid[activeIndex] < swiperSize : slidesGrid[i] - slidesGrid[activeIndex] < swiperSize;
if (slideInView) {
spv += 1;
}
}
} else {
// previous
for (let i = activeIndex - 1; i >= 0; i -= 1) {
const slideInView = slidesGrid[activeIndex] - slidesGrid[i] < swiperSize;
if (slideInView) {
spv += 1;
}
}
}
}
return spv;
}
update() {
const swiper = this;
if (!swiper || swiper.destroyed) return;
const {
snapGrid,
params
} = swiper;
// Breakpoints
if (params.breakpoints) {
swiper.setBreakpoint();
}
[...swiper.el.querySelectorAll('[loading="lazy"]')].forEach(imageEl => {
if (imageEl.complete) {
processLazyPreloader(swiper, imageEl);
}
});
swiper.updateSize();
swiper.updateSlides();
swiper.updateProgress();
swiper.updateSlidesClasses();
function setTranslate() {
const translateValue = swiper.rtlTranslate ? swiper.translate * -1 : swiper.translate;
const newTranslate = Math.min(Math.max(translateValue, swiper.maxTranslate()), swiper.minTranslate());
swiper.setTranslate(newTranslate);
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
}
let translated;
if (params.freeMode && params.freeMode.enabled && !params.cssMode) {
setTranslate();
if (params.autoHeight) {
swiper.updateAutoHeight();
}
} else {
if ((params.slidesPerView === 'auto' || params.slidesPerView > 1) && swiper.isEnd && !params.centeredSlides) {
const slides = swiper.virtual && params.virtual.enabled ? swiper.virtual.slides : swiper.slides;
translated = swiper.slideTo(slides.length - 1, 0, false, true);
} else {
translated = swiper.slideTo(swiper.activeIndex, 0, false, true);
}
if (!translated) {
setTranslate();
}
}
if (params.watchOverflow && snapGrid !== swiper.snapGrid) {
swiper.checkOverflow();
}
swiper.emit('update');
}
changeDirection(newDirection, needUpdate) {
if (needUpdate === void 0) {
needUpdate = true;
}
const swiper = this;
const currentDirection = swiper.params.direction;
if (!newDirection) {
// eslint-disable-next-line
newDirection = currentDirection === 'horizontal' ? 'vertical' : 'horizontal';
}
if (newDirection === currentDirection || newDirection !== 'horizontal' && newDirection !== 'vertical') {
return swiper;
}
swiper.el.classList.remove(`${swiper.params.containerModifierClass}${currentDirection}`);
swiper.el.classList.add(`${swiper.params.containerModifierClass}${newDirection}`);
swiper.emitContainerClasses();
swiper.params.direction = newDirection;
swiper.slides.forEach(slideEl => {
if (newDirection === 'vertical') {
slideEl.style.width = '';
} else {
slideEl.style.height = '';
}
});
swiper.emit('changeDirection');
if (needUpdate) swiper.update();
return swiper;
}
changeLanguageDirection(direction) {
const swiper = this;
if (swiper.rtl && direction === 'rtl' || !swiper.rtl && direction === 'ltr') return;
swiper.rtl = direction === 'rtl';
swiper.rtlTranslate = swiper.params.direction === 'horizontal' && swiper.rtl;
if (swiper.rtl) {
swiper.el.classList.add(`${swiper.params.containerModifierClass}rtl`);
swiper.el.dir = 'rtl';
} else {
swiper.el.classList.remove(`${swiper.params.containerModifierClass}rtl`);
swiper.el.dir = 'ltr';
}
swiper.update();
}
mount(element) {
const swiper = this;
if (swiper.mounted) return true;
// Find el
let el = element || swiper.params.el;
if (typeof el === 'string') {
el = document.querySelector(el);
}
if (!el) {
return false;
}
el.swiper = swiper;
if (el.parentNode && el.parentNode.host && el.parentNode.host.nodeName === swiper.params.swiperElementNodeName.toUpperCase()) {
swiper.isElement = true;
}
const getWrapperSelector = () => {
return `.${(swiper.params.wrapperClass || '').trim().split(' ').join('.')}`;
};
const getWrapper = () => {
if (el && el.shadowRoot && el.shadowRoot.querySelector) {
const res = el.shadowRoot.querySelector(getWrapperSelector());
// Children needs to return slot items
return res;
}
return utils_elementChildren(el, getWrapperSelector())[0];
};
// Find Wrapper
let wrapperEl = getWrapper();
if (!wrapperEl && swiper.params.createElements) {
wrapperEl = utils_createElement('div', swiper.params.wrapperClass);
el.append(wrapperEl);
utils_elementChildren(el, `.${swiper.params.slideClass}`).forEach(slideEl => {
wrapperEl.append(slideEl);
});
}
Object.assign(swiper, {
el,
wrapperEl,
slidesEl: swiper.isElement && !el.parentNode.host.slideSlots ? el.parentNode.host : wrapperEl,
hostEl: swiper.isElement ? el.parentNode.host : el,
mounted: true,
// RTL
rtl: el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl',
rtlTranslate: swiper.params.direction === 'horizontal' && (el.dir.toLowerCase() === 'rtl' || elementStyle(el, 'direction') === 'rtl'),
wrongRTL: elementStyle(wrapperEl, 'display') === '-webkit-box'
});
return true;
}
init(el) {
const swiper = this;
if (swiper.initialized) return swiper;
const mounted = swiper.mount(el);
if (mounted === false) return swiper;
swiper.emit('beforeInit');
// Set breakpoint
if (swiper.params.breakpoints) {
swiper.setBreakpoint();
}
// Add Classes
swiper.addClasses();
// Update size
swiper.updateSize();
// Update slides
swiper.updateSlides();
if (swiper.params.watchOverflow) {
swiper.checkOverflow();
}
// Set Grab Cursor
if (swiper.params.grabCursor && swiper.enabled) {
swiper.setGrabCursor();
}
// Slide To Initial Slide
if (swiper.params.loop && swiper.virtual && swiper.params.virtual.enabled) {
swiper.slideTo(swiper.params.initialSlide + swiper.virtual.slidesBefore, 0, swiper.params.runCallbacksOnInit, false, true);
} else {
swiper.slideTo(swiper.params.initialSlide, 0, swiper.params.runCallbacksOnInit, false, true);
}
// Create loop
if (swiper.params.loop) {
swiper.loopCreate(undefined, true);
}
// Attach events
swiper.attachEvents();
const lazyElements = [...swiper.el.querySelectorAll('[loading="lazy"]')];
if (swiper.isElement) {
lazyElements.push(...swiper.hostEl.querySelectorAll('[loading="lazy"]'));
}
lazyElements.forEach(imageEl => {
if (imageEl.complete) {
processLazyPreloader(swiper, imageEl);
} else {
imageEl.addEventListener('load', e => {
processLazyPreloader(swiper, e.target);
});
}
});
preload(swiper);
// Init Flag
swiper.initialized = true;
preload(swiper);
// Emit
swiper.emit('init');
swiper.emit('afterInit');
return swiper;
}
destroy(deleteInstance, cleanStyles) {
if (deleteInstance === void 0) {
deleteInstance = true;
}
if (cleanStyles === void 0) {
cleanStyles = true;
}
const swiper = this;
const {
params,
el,
wrapperEl,
slides
} = swiper;
if (typeof swiper.params === 'undefined' || swiper.destroyed) {
return null;
}
swiper.emit('beforeDestroy');
// Init Flag
swiper.initialized = false;
// Detach events
swiper.detachEvents();
// Destroy loop
if (params.loop) {
swiper.loopDestroy();
}
// Cleanup styles
if (cleanStyles) {
swiper.removeClasses();
if (el && typeof el !== 'string') {
el.removeAttribute('style');
}
if (wrapperEl) {
wrapperEl.removeAttribute('style');
}
if (slides && slides.length) {
slides.forEach(slideEl => {
slideEl.classList.remove(params.slideVisibleClass, params.slideFullyVisibleClass, params.slideActiveClass, params.slideNextClass, params.slidePrevClass);
slideEl.removeAttribute('style');
slideEl.removeAttribute('data-swiper-slide-index');
});
}
}
swiper.emit('destroy');
// Detach emitter events
Object.keys(swiper.eventsListeners).forEach(eventName => {
swiper.off(eventName);
});
if (deleteInstance !== false) {
if (swiper.el && typeof swiper.el !== 'string') {
swiper.el.swiper = null;
}
deleteProps(swiper);
}
swiper.destroyed = true;
return null;
}
static extendDefaults(newDefaults) {
utils_extend(extendedDefaults, newDefaults);
}
static get extendedDefaults() {
return extendedDefaults;
}
static get defaults() {
return defaults;
}
static installModule(mod) {
if (!Swiper.prototype.__modules__) Swiper.prototype.__modules__ = [];
const modules = Swiper.prototype.__modules__;
if (typeof mod === 'function' && modules.indexOf(mod) < 0) {
modules.push(mod);
}
}
static use(module) {
if (Array.isArray(module)) {
module.forEach(m => Swiper.installModule(m));
return Swiper;
}
Swiper.installModule(module);
return Swiper;
}
}
Object.keys(prototypes).forEach(prototypeGroup => {
Object.keys(prototypes[prototypeGroup]).forEach(protoMethod => {
Swiper.prototype[protoMethod] = prototypes[prototypeGroup][protoMethod];
});
});
Swiper.use([Resize, Observer]);
;// ./node_modules/swiper/swiper.mjs
/**
* Swiper 11.2.10
* Most modern mobile touch slider and framework with hardware accelerated transitions
* https://swiperjs.com
*
* Copyright 2014-2025 Vladimir Kharlampidi
*
* Released under the MIT License
*
* Released on: June 28, 2025
*/
;// ./node_modules/swiper/modules/virtual.mjs
function Virtual(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
extendParams({
virtual: {
enabled: false,
slides: [],
cache: true,
renderSlide: null,
renderExternal: null,
renderExternalUpdate: true,
addSlidesBefore: 0,
addSlidesAfter: 0
}
});
let cssModeTimeout;
const document = getDocument();
swiper.virtual = {
cache: {},
from: undefined,
to: undefined,
slides: [],
offset: 0,
slidesGrid: []
};
const tempDOM = document.createElement('div');
function renderSlide(slide, index) {
const params = swiper.params.virtual;
if (params.cache && swiper.virtual.cache[index]) {
return swiper.virtual.cache[index];
}
// eslint-disable-next-line
let slideEl;
if (params.renderSlide) {
slideEl = params.renderSlide.call(swiper, slide, index);
if (typeof slideEl === 'string') {
setInnerHTML(tempDOM, slideEl);
slideEl = tempDOM.children[0];
}
} else if (swiper.isElement) {
slideEl = createElement('swiper-slide');
} else {
slideEl = createElement('div', swiper.params.slideClass);
}
slideEl.setAttribute('data-swiper-slide-index', index);
if (!params.renderSlide) {
setInnerHTML(slideEl, slide);
}
if (params.cache) {
swiper.virtual.cache[index] = slideEl;
}
return slideEl;
}
function update(force, beforeInit, forceActiveIndex) {
const {
slidesPerView,
slidesPerGroup,
centeredSlides,
loop: isLoop,
initialSlide
} = swiper.params;
if (beforeInit && !isLoop && initialSlide > 0) {
return;
}
const {
addSlidesBefore,
addSlidesAfter
} = swiper.params.virtual;
const {
from: previousFrom,
to: previousTo,
slides,
slidesGrid: previousSlidesGrid,
offset: previousOffset
} = swiper.virtual;
if (!swiper.params.cssMode) {
swiper.updateActiveIndex();
}
const activeIndex = typeof forceActiveIndex === 'undefined' ? swiper.activeIndex || 0 : forceActiveIndex;
let offsetProp;
if (swiper.rtlTranslate) offsetProp = 'right';else offsetProp = swiper.isHorizontal() ? 'left' : 'top';
let slidesAfter;
let slidesBefore;
if (centeredSlides) {
slidesAfter = Math.floor(slidesPerView / 2) + slidesPerGroup + addSlidesAfter;
slidesBefore = Math.floor(slidesPerView / 2) + slidesPerGroup + addSlidesBefore;
} else {
slidesAfter = slidesPerView + (slidesPerGroup - 1) + addSlidesAfter;
slidesBefore = (isLoop ? slidesPerView : slidesPerGroup) + addSlidesBefore;
}
let from = activeIndex - slidesBefore;
let to = activeIndex + slidesAfter;
if (!isLoop) {
from = Math.max(from, 0);
to = Math.min(to, slides.length - 1);
}
let offset = (swiper.slidesGrid[from] || 0) - (swiper.slidesGrid[0] || 0);
if (isLoop && activeIndex >= slidesBefore) {
from -= slidesBefore;
if (!centeredSlides) offset += swiper.slidesGrid[0];
} else if (isLoop && activeIndex < slidesBefore) {
from = -slidesBefore;
if (centeredSlides) offset += swiper.slidesGrid[0];
}
Object.assign(swiper.virtual, {
from,
to,
offset,
slidesGrid: swiper.slidesGrid,
slidesBefore,
slidesAfter
});
function onRendered() {
swiper.updateSlides();
swiper.updateProgress();
swiper.updateSlidesClasses();
emit('virtualUpdate');
}
if (previousFrom === from && previousTo === to && !force) {
if (swiper.slidesGrid !== previousSlidesGrid && offset !== previousOffset) {
swiper.slides.forEach(slideEl => {
slideEl.style[offsetProp] = `${offset - Math.abs(swiper.cssOverflowAdjustment())}px`;
});
}
swiper.updateProgress();
emit('virtualUpdate');
return;
}
if (swiper.params.virtual.renderExternal) {
swiper.params.virtual.renderExternal.call(swiper, {
offset,
from,
to,
slides: function getSlides() {
const slidesToRender = [];
for (let i = from; i <= to; i += 1) {
slidesToRender.push(slides[i]);
}
return slidesToRender;
}()
});
if (swiper.params.virtual.renderExternalUpdate) {
onRendered();
} else {
emit('virtualUpdate');
}
return;
}
const prependIndexes = [];
const appendIndexes = [];
const getSlideIndex = index => {
let slideIndex = index;
if (index < 0) {
slideIndex = slides.length + index;
} else if (slideIndex >= slides.length) {
// eslint-disable-next-line
slideIndex = slideIndex - slides.length;
}
return slideIndex;
};
if (force) {
swiper.slides.filter(el => el.matches(`.${swiper.params.slideClass}, swiper-slide`)).forEach(slideEl => {
slideEl.remove();
});
} else {
for (let i = previousFrom; i <= previousTo; i += 1) {
if (i < from || i > to) {
const slideIndex = getSlideIndex(i);
swiper.slides.filter(el => el.matches(`.${swiper.params.slideClass}[data-swiper-slide-index="${slideIndex}"], swiper-slide[data-swiper-slide-index="${slideIndex}"]`)).forEach(slideEl => {
slideEl.remove();
});
}
}
}
const loopFrom = isLoop ? -slides.length : 0;
const loopTo = isLoop ? slides.length * 2 : slides.length;
for (let i = loopFrom; i < loopTo; i += 1) {
if (i >= from && i <= to) {
const slideIndex = getSlideIndex(i);
if (typeof previousTo === 'undefined' || force) {
appendIndexes.push(slideIndex);
} else {
if (i > previousTo) appendIndexes.push(slideIndex);
if (i < previousFrom) prependIndexes.push(slideIndex);
}
}
}
appendIndexes.forEach(index => {
swiper.slidesEl.append(renderSlide(slides[index], index));
});
if (isLoop) {
for (let i = prependIndexes.length - 1; i >= 0; i -= 1) {
const index = prependIndexes[i];
swiper.slidesEl.prepend(renderSlide(slides[index], index));
}
} else {
prependIndexes.sort((a, b) => b - a);
prependIndexes.forEach(index => {
swiper.slidesEl.prepend(renderSlide(slides[index], index));
});
}
elementChildren(swiper.slidesEl, '.swiper-slide, swiper-slide').forEach(slideEl => {
slideEl.style[offsetProp] = `${offset - Math.abs(swiper.cssOverflowAdjustment())}px`;
});
onRendered();
}
function appendSlide(slides) {
if (typeof slides === 'object' && 'length' in slides) {
for (let i = 0; i < slides.length; i += 1) {
if (slides[i]) swiper.virtual.slides.push(slides[i]);
}
} else {
swiper.virtual.slides.push(slides);
}
update(true);
}
function prependSlide(slides) {
const activeIndex = swiper.activeIndex;
let newActiveIndex = activeIndex + 1;
let numberOfNewSlides = 1;
if (Array.isArray(slides)) {
for (let i = 0; i < slides.length; i += 1) {
if (slides[i]) swiper.virtual.slides.unshift(slides[i]);
}
newActiveIndex = activeIndex + slides.length;
numberOfNewSlides = slides.length;
} else {
swiper.virtual.slides.unshift(slides);
}
if (swiper.params.virtual.cache) {
const cache = swiper.virtual.cache;
const newCache = {};
Object.keys(cache).forEach(cachedIndex => {
const cachedEl = cache[cachedIndex];
const cachedElIndex = cachedEl.getAttribute('data-swiper-slide-index');
if (cachedElIndex) {
cachedEl.setAttribute('data-swiper-slide-index', parseInt(cachedElIndex, 10) + numberOfNewSlides);
}
newCache[parseInt(cachedIndex, 10) + numberOfNewSlides] = cachedEl;
});
swiper.virtual.cache = newCache;
}
update(true);
swiper.slideTo(newActiveIndex, 0);
}
function removeSlide(slidesIndexes) {
if (typeof slidesIndexes === 'undefined' || slidesIndexes === null) return;
let activeIndex = swiper.activeIndex;
if (Array.isArray(slidesIndexes)) {
for (let i = slidesIndexes.length - 1; i >= 0; i -= 1) {
if (swiper.params.virtual.cache) {
delete swiper.virtual.cache[slidesIndexes[i]];
// shift cache indexes
Object.keys(swiper.virtual.cache).forEach(key => {
if (key > slidesIndexes) {
swiper.virtual.cache[key - 1] = swiper.virtual.cache[key];
swiper.virtual.cache[key - 1].setAttribute('data-swiper-slide-index', key - 1);
delete swiper.virtual.cache[key];
}
});
}
swiper.virtual.slides.splice(slidesIndexes[i], 1);
if (slidesIndexes[i] < activeIndex) activeIndex -= 1;
activeIndex = Math.max(activeIndex, 0);
}
} else {
if (swiper.params.virtual.cache) {
delete swiper.virtual.cache[slidesIndexes];
// shift cache indexes
Object.keys(swiper.virtual.cache).forEach(key => {
if (key > slidesIndexes) {
swiper.virtual.cache[key - 1] = swiper.virtual.cache[key];
swiper.virtual.cache[key - 1].setAttribute('data-swiper-slide-index', key - 1);
delete swiper.virtual.cache[key];
}
});
}
swiper.virtual.slides.splice(slidesIndexes, 1);
if (slidesIndexes < activeIndex) activeIndex -= 1;
activeIndex = Math.max(activeIndex, 0);
}
update(true);
swiper.slideTo(activeIndex, 0);
}
function removeAllSlides() {
swiper.virtual.slides = [];
if (swiper.params.virtual.cache) {
swiper.virtual.cache = {};
}
update(true);
swiper.slideTo(0, 0);
}
on('beforeInit', () => {
if (!swiper.params.virtual.enabled) return;
let domSlidesAssigned;
if (typeof swiper.passedParams.virtual.slides === 'undefined') {
const slides = [...swiper.slidesEl.children].filter(el => el.matches(`.${swiper.params.slideClass}, swiper-slide`));
if (slides && slides.length) {
swiper.virtual.slides = [...slides];
domSlidesAssigned = true;
slides.forEach((slideEl, slideIndex) => {
slideEl.setAttribute('data-swiper-slide-index', slideIndex);
swiper.virtual.cache[slideIndex] = slideEl;
slideEl.remove();
});
}
}
if (!domSlidesAssigned) {
swiper.virtual.slides = swiper.params.virtual.slides;
}
swiper.classNames.push(`${swiper.params.containerModifierClass}virtual`);
swiper.params.watchSlidesProgress = true;
swiper.originalParams.watchSlidesProgress = true;
update(false, true);
});
on('setTranslate', () => {
if (!swiper.params.virtual.enabled) return;
if (swiper.params.cssMode && !swiper._immediateVirtual) {
clearTimeout(cssModeTimeout);
cssModeTimeout = setTimeout(() => {
update();
}, 100);
} else {
update();
}
});
on('init update resize', () => {
if (!swiper.params.virtual.enabled) return;
if (swiper.params.cssMode) {
setCSSProperty(swiper.wrapperEl, '--swiper-virtual-size', `${swiper.virtualSize}px`);
}
});
Object.assign(swiper.virtual, {
appendSlide,
prependSlide,
removeSlide,
removeAllSlides,
update
});
}
;// ./node_modules/swiper/modules/keyboard.mjs
/* eslint-disable consistent-return */
function Keyboard(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
const document = getDocument();
const window = getWindow();
swiper.keyboard = {
enabled: false
};
extendParams({
keyboard: {
enabled: false,
onlyInViewport: true,
pageUpDown: true
}
});
function handle(event) {
if (!swiper.enabled) return;
const {
rtlTranslate: rtl
} = swiper;
let e = event;
if (e.originalEvent) e = e.originalEvent; // jquery fix
const kc = e.keyCode || e.charCode;
const pageUpDown = swiper.params.keyboard.pageUpDown;
const isPageUp = pageUpDown && kc === 33;
const isPageDown = pageUpDown && kc === 34;
const isArrowLeft = kc === 37;
const isArrowRight = kc === 39;
const isArrowUp = kc === 38;
const isArrowDown = kc === 40;
// Directions locks
if (!swiper.allowSlideNext && (swiper.isHorizontal() && isArrowRight || swiper.isVertical() && isArrowDown || isPageDown)) {
return false;
}
if (!swiper.allowSlidePrev && (swiper.isHorizontal() && isArrowLeft || swiper.isVertical() && isArrowUp || isPageUp)) {
return false;
}
if (e.shiftKey || e.altKey || e.ctrlKey || e.metaKey) {
return undefined;
}
if (document.activeElement && (document.activeElement.isContentEditable || document.activeElement.nodeName && (document.activeElement.nodeName.toLowerCase() === 'input' || document.activeElement.nodeName.toLowerCase() === 'textarea'))) {
return undefined;
}
if (swiper.params.keyboard.onlyInViewport && (isPageUp || isPageDown || isArrowLeft || isArrowRight || isArrowUp || isArrowDown)) {
let inView = false;
// Check that swiper should be inside of visible area of window
if (elementParents(swiper.el, `.${swiper.params.slideClass}, swiper-slide`).length > 0 && elementParents(swiper.el, `.${swiper.params.slideActiveClass}`).length === 0) {
return undefined;
}
const el = swiper.el;
const swiperWidth = el.clientWidth;
const swiperHeight = el.clientHeight;
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
const swiperOffset = elementOffset(el);
if (rtl) swiperOffset.left -= el.scrollLeft;
const swiperCoord = [[swiperOffset.left, swiperOffset.top], [swiperOffset.left + swiperWidth, swiperOffset.top], [swiperOffset.left, swiperOffset.top + swiperHeight], [swiperOffset.left + swiperWidth, swiperOffset.top + swiperHeight]];
for (let i = 0; i < swiperCoord.length; i += 1) {
const point = swiperCoord[i];
if (point[0] >= 0 && point[0] <= windowWidth && point[1] >= 0 && point[1] <= windowHeight) {
if (point[0] === 0 && point[1] === 0) continue; // eslint-disable-line
inView = true;
}
}
if (!inView) return undefined;
}
if (swiper.isHorizontal()) {
if (isPageUp || isPageDown || isArrowLeft || isArrowRight) {
if (e.preventDefault) e.preventDefault();else e.returnValue = false;
}
if ((isPageDown || isArrowRight) && !rtl || (isPageUp || isArrowLeft) && rtl) swiper.slideNext();
if ((isPageUp || isArrowLeft) && !rtl || (isPageDown || isArrowRight) && rtl) swiper.slidePrev();
} else {
if (isPageUp || isPageDown || isArrowUp || isArrowDown) {
if (e.preventDefault) e.preventDefault();else e.returnValue = false;
}
if (isPageDown || isArrowDown) swiper.slideNext();
if (isPageUp || isArrowUp) swiper.slidePrev();
}
emit('keyPress', kc);
return undefined;
}
function enable() {
if (swiper.keyboard.enabled) return;
document.addEventListener('keydown', handle);
swiper.keyboard.enabled = true;
}
function disable() {
if (!swiper.keyboard.enabled) return;
document.removeEventListener('keydown', handle);
swiper.keyboard.enabled = false;
}
on('init', () => {
if (swiper.params.keyboard.enabled) {
enable();
}
});
on('destroy', () => {
if (swiper.keyboard.enabled) {
disable();
}
});
Object.assign(swiper.keyboard, {
enable,
disable
});
}
;// ./node_modules/swiper/modules/mousewheel.mjs
/* eslint-disable consistent-return */
function Mousewheel(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
const window = getWindow();
extendParams({
mousewheel: {
enabled: false,
releaseOnEdges: false,
invert: false,
forceToAxis: false,
sensitivity: 1,
eventsTarget: 'container',
thresholdDelta: null,
thresholdTime: null,
noMousewheelClass: 'swiper-no-mousewheel'
}
});
swiper.mousewheel = {
enabled: false
};
let timeout;
let lastScrollTime = now();
let lastEventBeforeSnap;
const recentWheelEvents = [];
function normalize(e) {
// Reasonable defaults
const PIXEL_STEP = 10;
const LINE_HEIGHT = 40;
const PAGE_HEIGHT = 800;
let sX = 0;
let sY = 0; // spinX, spinY
let pX = 0;
let pY = 0; // pixelX, pixelY
// Legacy
if ('detail' in e) {
sY = e.detail;
}
if ('wheelDelta' in e) {
sY = -e.wheelDelta / 120;
}
if ('wheelDeltaY' in e) {
sY = -e.wheelDeltaY / 120;
}
if ('wheelDeltaX' in e) {
sX = -e.wheelDeltaX / 120;
}
// side scrolling on FF with DOMMouseScroll
if ('axis' in e && e.axis === e.HORIZONTAL_AXIS) {
sX = sY;
sY = 0;
}
pX = sX * PIXEL_STEP;
pY = sY * PIXEL_STEP;
if ('deltaY' in e) {
pY = e.deltaY;
}
if ('deltaX' in e) {
pX = e.deltaX;
}
if (e.shiftKey && !pX) {
// if user scrolls with shift he wants horizontal scroll
pX = pY;
pY = 0;
}
if ((pX || pY) && e.deltaMode) {
if (e.deltaMode === 1) {
// delta in LINE units
pX *= LINE_HEIGHT;
pY *= LINE_HEIGHT;
} else {
// delta in PAGE units
pX *= PAGE_HEIGHT;
pY *= PAGE_HEIGHT;
}
}
// Fall-back if spin cannot be determined
if (pX && !sX) {
sX = pX < 1 ? -1 : 1;
}
if (pY && !sY) {
sY = pY < 1 ? -1 : 1;
}
return {
spinX: sX,
spinY: sY,
pixelX: pX,
pixelY: pY
};
}
function handleMouseEnter() {
if (!swiper.enabled) return;
swiper.mouseEntered = true;
}
function handleMouseLeave() {
if (!swiper.enabled) return;
swiper.mouseEntered = false;
}
function animateSlider(newEvent) {
if (swiper.params.mousewheel.thresholdDelta && newEvent.delta < swiper.params.mousewheel.thresholdDelta) {
// Prevent if delta of wheel scroll delta is below configured threshold
return false;
}
if (swiper.params.mousewheel.thresholdTime && now() - lastScrollTime < swiper.params.mousewheel.thresholdTime) {
// Prevent if time between scrolls is below configured threshold
return false;
}
// If the movement is NOT big enough and
// if the last time the user scrolled was too close to the current one (avoid continuously triggering the slider):
// Don't go any further (avoid insignificant scroll movement).
if (newEvent.delta >= 6 && now() - lastScrollTime < 60) {
// Return false as a default
return true;
}
// If user is scrolling towards the end:
// If the slider hasn't hit the latest slide or
// if the slider is a loop and
// if the slider isn't moving right now:
// Go to next slide and
// emit a scroll event.
// Else (the user is scrolling towards the beginning) and
// if the slider hasn't hit the first slide or
// if the slider is a loop and
// if the slider isn't moving right now:
// Go to prev slide and
// emit a scroll event.
if (newEvent.direction < 0) {
if ((!swiper.isEnd || swiper.params.loop) && !swiper.animating) {
swiper.slideNext();
emit('scroll', newEvent.raw);
}
} else if ((!swiper.isBeginning || swiper.params.loop) && !swiper.animating) {
swiper.slidePrev();
emit('scroll', newEvent.raw);
}
// If you got here is because an animation has been triggered so store the current time
lastScrollTime = new window.Date().getTime();
// Return false as a default
return false;
}
function releaseScroll(newEvent) {
const params = swiper.params.mousewheel;
if (newEvent.direction < 0) {
if (swiper.isEnd && !swiper.params.loop && params.releaseOnEdges) {
// Return true to animate scroll on edges
return true;
}
} else if (swiper.isBeginning && !swiper.params.loop && params.releaseOnEdges) {
// Return true to animate scroll on edges
return true;
}
return false;
}
function handle(event) {
let e = event;
let disableParentSwiper = true;
if (!swiper.enabled) return;
// Ignore event if the target or its parents have the swiper-no-mousewheel class
if (event.target.closest(`.${swiper.params.mousewheel.noMousewheelClass}`)) return;
const params = swiper.params.mousewheel;
if (swiper.params.cssMode) {
e.preventDefault();
}
let targetEl = swiper.el;
if (swiper.params.mousewheel.eventsTarget !== 'container') {
targetEl = document.querySelector(swiper.params.mousewheel.eventsTarget);
}
const targetElContainsTarget = targetEl && targetEl.contains(e.target);
if (!swiper.mouseEntered && !targetElContainsTarget && !params.releaseOnEdges) return true;
if (e.originalEvent) e = e.originalEvent; // jquery fix
let delta = 0;
const rtlFactor = swiper.rtlTranslate ? -1 : 1;
const data = normalize(e);
if (params.forceToAxis) {
if (swiper.isHorizontal()) {
if (Math.abs(data.pixelX) > Math.abs(data.pixelY)) delta = -data.pixelX * rtlFactor;else return true;
} else if (Math.abs(data.pixelY) > Math.abs(data.pixelX)) delta = -data.pixelY;else return true;
} else {
delta = Math.abs(data.pixelX) > Math.abs(data.pixelY) ? -data.pixelX * rtlFactor : -data.pixelY;
}
if (delta === 0) return true;
if (params.invert) delta = -delta;
// Get the scroll positions
let positions = swiper.getTranslate() + delta * params.sensitivity;
if (positions >= swiper.minTranslate()) positions = swiper.minTranslate();
if (positions <= swiper.maxTranslate()) positions = swiper.maxTranslate();
// When loop is true:
// the disableParentSwiper will be true.
// When loop is false:
// if the scroll positions is not on edge,
// then the disableParentSwiper will be true.
// if the scroll on edge positions,
// then the disableParentSwiper will be false.
disableParentSwiper = swiper.params.loop ? true : !(positions === swiper.minTranslate() || positions === swiper.maxTranslate());
if (disableParentSwiper && swiper.params.nested) e.stopPropagation();
if (!swiper.params.freeMode || !swiper.params.freeMode.enabled) {
// Register the new event in a variable which stores the relevant data
const newEvent = {
time: now(),
delta: Math.abs(delta),
direction: Math.sign(delta),
raw: event
};
// Keep the most recent events
if (recentWheelEvents.length >= 2) {
recentWheelEvents.shift(); // only store the last N events
}
const prevEvent = recentWheelEvents.length ? recentWheelEvents[recentWheelEvents.length - 1] : undefined;
recentWheelEvents.push(newEvent);
// If there is at least one previous recorded event:
// If direction has changed or
// if the scroll is quicker than the previous one:
// Animate the slider.
// Else (this is the first time the wheel is moved):
// Animate the slider.
if (prevEvent) {
if (newEvent.direction !== prevEvent.direction || newEvent.delta > prevEvent.delta || newEvent.time > prevEvent.time + 150) {
animateSlider(newEvent);
}
} else {
animateSlider(newEvent);
}
// If it's time to release the scroll:
// Return now so you don't hit the preventDefault.
if (releaseScroll(newEvent)) {
return true;
}
} else {
// Freemode or scrollContainer:
// If we recently snapped after a momentum scroll, then ignore wheel events
// to give time for the deceleration to finish. Stop ignoring after 500 msecs
// or if it's a new scroll (larger delta or inverse sign as last event before
// an end-of-momentum snap).
const newEvent = {
time: now(),
delta: Math.abs(delta),
direction: Math.sign(delta)
};
const ignoreWheelEvents = lastEventBeforeSnap && newEvent.time < lastEventBeforeSnap.time + 500 && newEvent.delta <= lastEventBeforeSnap.delta && newEvent.direction === lastEventBeforeSnap.direction;
if (!ignoreWheelEvents) {
lastEventBeforeSnap = undefined;
let position = swiper.getTranslate() + delta * params.sensitivity;
const wasBeginning = swiper.isBeginning;
const wasEnd = swiper.isEnd;
if (position >= swiper.minTranslate()) position = swiper.minTranslate();
if (position <= swiper.maxTranslate()) position = swiper.maxTranslate();
swiper.setTransition(0);
swiper.setTranslate(position);
swiper.updateProgress();
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
if (!wasBeginning && swiper.isBeginning || !wasEnd && swiper.isEnd) {
swiper.updateSlidesClasses();
}
if (swiper.params.loop) {
swiper.loopFix({
direction: newEvent.direction < 0 ? 'next' : 'prev',
byMousewheel: true
});
}
if (swiper.params.freeMode.sticky) {
// When wheel scrolling starts with sticky (aka snap) enabled, then detect
// the end of a momentum scroll by storing recent (N=15?) wheel events.
// 1. do all N events have decreasing or same (absolute value) delta?
// 2. did all N events arrive in the last M (M=500?) msecs?
// 3. does the earliest event have an (absolute value) delta that's
// at least P (P=1?) larger than the most recent event's delta?
// 4. does the latest event have a delta that's smaller than Q (Q=6?) pixels?
// If 1-4 are "yes" then we're near the end of a momentum scroll deceleration.
// Snap immediately and ignore remaining wheel events in this scroll.
// See comment above for "remaining wheel events in this scroll" determination.
// If 1-4 aren't satisfied, then wait to snap until 500ms after the last event.
clearTimeout(timeout);
timeout = undefined;
if (recentWheelEvents.length >= 15) {
recentWheelEvents.shift(); // only store the last N events
}
const prevEvent = recentWheelEvents.length ? recentWheelEvents[recentWheelEvents.length - 1] : undefined;
const firstEvent = recentWheelEvents[0];
recentWheelEvents.push(newEvent);
if (prevEvent && (newEvent.delta > prevEvent.delta || newEvent.direction !== prevEvent.direction)) {
// Increasing or reverse-sign delta means the user started scrolling again. Clear the wheel event log.
recentWheelEvents.splice(0);
} else if (recentWheelEvents.length >= 15 && newEvent.time - firstEvent.time < 500 && firstEvent.delta - newEvent.delta >= 1 && newEvent.delta <= 6) {
// We're at the end of the deceleration of a momentum scroll, so there's no need
// to wait for more events. Snap ASAP on the next tick.
// Also, because there's some remaining momentum we'll bias the snap in the
// direction of the ongoing scroll because it's better UX for the scroll to snap
// in the same direction as the scroll instead of reversing to snap. Therefore,
// if it's already scrolled more than 20% in the current direction, keep going.
const snapToThreshold = delta > 0 ? 0.8 : 0.2;
lastEventBeforeSnap = newEvent;
recentWheelEvents.splice(0);
timeout = nextTick(() => {
if (swiper.destroyed || !swiper.params) return;
swiper.slideToClosest(swiper.params.speed, true, undefined, snapToThreshold);
}, 0); // no delay; move on next tick
}
if (!timeout) {
// if we get here, then we haven't detected the end of a momentum scroll, so
// we'll consider a scroll "complete" when there haven't been any wheel events
// for 500ms.
timeout = nextTick(() => {
if (swiper.destroyed || !swiper.params) return;
const snapToThreshold = 0.5;
lastEventBeforeSnap = newEvent;
recentWheelEvents.splice(0);
swiper.slideToClosest(swiper.params.speed, true, undefined, snapToThreshold);
}, 500);
}
}
// Emit event
if (!ignoreWheelEvents) emit('scroll', e);
// Stop autoplay
if (swiper.params.autoplay && swiper.params.autoplay.disableOnInteraction) swiper.autoplay.stop();
// Return page scroll on edge positions
if (params.releaseOnEdges && (position === swiper.minTranslate() || position === swiper.maxTranslate())) {
return true;
}
}
}
if (e.preventDefault) e.preventDefault();else e.returnValue = false;
return false;
}
function events(method) {
let targetEl = swiper.el;
if (swiper.params.mousewheel.eventsTarget !== 'container') {
targetEl = document.querySelector(swiper.params.mousewheel.eventsTarget);
}
targetEl[method]('mouseenter', handleMouseEnter);
targetEl[method]('mouseleave', handleMouseLeave);
targetEl[method]('wheel', handle);
}
function enable() {
if (swiper.params.cssMode) {
swiper.wrapperEl.removeEventListener('wheel', handle);
return true;
}
if (swiper.mousewheel.enabled) return false;
events('addEventListener');
swiper.mousewheel.enabled = true;
return true;
}
function disable() {
if (swiper.params.cssMode) {
swiper.wrapperEl.addEventListener(event, handle);
return true;
}
if (!swiper.mousewheel.enabled) return false;
events('removeEventListener');
swiper.mousewheel.enabled = false;
return true;
}
on('init', () => {
if (!swiper.params.mousewheel.enabled && swiper.params.cssMode) {
disable();
}
if (swiper.params.mousewheel.enabled) enable();
});
on('destroy', () => {
if (swiper.params.cssMode) {
enable();
}
if (swiper.mousewheel.enabled) disable();
});
Object.assign(swiper.mousewheel, {
enable,
disable
});
}
;// ./node_modules/swiper/shared/create-element-if-not-defined.mjs
function create_element_if_not_defined_createElementIfNotDefined(swiper, originalParams, params, checkProps) {
if (swiper.params.createElements) {
Object.keys(checkProps).forEach(key => {
if (!params[key] && params.auto === true) {
let element = utils_elementChildren(swiper.el, `.${checkProps[key]}`)[0];
if (!element) {
element = utils_createElement('div', checkProps[key]);
element.className = checkProps[key];
swiper.el.append(element);
}
params[key] = element;
originalParams[key] = element;
}
});
}
return params;
}
;// ./node_modules/swiper/modules/navigation.mjs
function Navigation(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
extendParams({
navigation: {
nextEl: null,
prevEl: null,
hideOnClick: false,
disabledClass: 'swiper-button-disabled',
hiddenClass: 'swiper-button-hidden',
lockClass: 'swiper-button-lock',
navigationDisabledClass: 'swiper-navigation-disabled'
}
});
swiper.navigation = {
nextEl: null,
prevEl: null
};
function getEl(el) {
let res;
if (el && typeof el === 'string' && swiper.isElement) {
res = swiper.el.querySelector(el) || swiper.hostEl.querySelector(el);
if (res) return res;
}
if (el) {
if (typeof el === 'string') res = [...document.querySelectorAll(el)];
if (swiper.params.uniqueNavElements && typeof el === 'string' && res && res.length > 1 && swiper.el.querySelectorAll(el).length === 1) {
res = swiper.el.querySelector(el);
} else if (res && res.length === 1) {
res = res[0];
}
}
if (el && !res) return el;
// if (Array.isArray(res) && res.length === 1) res = res[0];
return res;
}
function toggleEl(el, disabled) {
const params = swiper.params.navigation;
el = utils_makeElementsArray(el);
el.forEach(subEl => {
if (subEl) {
subEl.classList[disabled ? 'add' : 'remove'](...params.disabledClass.split(' '));
if (subEl.tagName === 'BUTTON') subEl.disabled = disabled;
if (swiper.params.watchOverflow && swiper.enabled) {
subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass);
}
}
});
}
function update() {
// Update Navigation Buttons
const {
nextEl,
prevEl
} = swiper.navigation;
if (swiper.params.loop) {
toggleEl(prevEl, false);
toggleEl(nextEl, false);
return;
}
toggleEl(prevEl, swiper.isBeginning && !swiper.params.rewind);
toggleEl(nextEl, swiper.isEnd && !swiper.params.rewind);
}
function onPrevClick(e) {
e.preventDefault();
if (swiper.isBeginning && !swiper.params.loop && !swiper.params.rewind) return;
swiper.slidePrev();
emit('navigationPrev');
}
function onNextClick(e) {
e.preventDefault();
if (swiper.isEnd && !swiper.params.loop && !swiper.params.rewind) return;
swiper.slideNext();
emit('navigationNext');
}
function init() {
const params = swiper.params.navigation;
swiper.params.navigation = create_element_if_not_defined_createElementIfNotDefined(swiper, swiper.originalParams.navigation, swiper.params.navigation, {
nextEl: 'swiper-button-next',
prevEl: 'swiper-button-prev'
});
if (!(params.nextEl || params.prevEl)) return;
let nextEl = getEl(params.nextEl);
let prevEl = getEl(params.prevEl);
Object.assign(swiper.navigation, {
nextEl,
prevEl
});
nextEl = utils_makeElementsArray(nextEl);
prevEl = utils_makeElementsArray(prevEl);
const initButton = (el, dir) => {
if (el) {
el.addEventListener('click', dir === 'next' ? onNextClick : onPrevClick);
}
if (!swiper.enabled && el) {
el.classList.add(...params.lockClass.split(' '));
}
};
nextEl.forEach(el => initButton(el, 'next'));
prevEl.forEach(el => initButton(el, 'prev'));
}
function destroy() {
let {
nextEl,
prevEl
} = swiper.navigation;
nextEl = utils_makeElementsArray(nextEl);
prevEl = utils_makeElementsArray(prevEl);
const destroyButton = (el, dir) => {
el.removeEventListener('click', dir === 'next' ? onNextClick : onPrevClick);
el.classList.remove(...swiper.params.navigation.disabledClass.split(' '));
};
nextEl.forEach(el => destroyButton(el, 'next'));
prevEl.forEach(el => destroyButton(el, 'prev'));
}
on('init', () => {
if (swiper.params.navigation.enabled === false) {
// eslint-disable-next-line
disable();
} else {
init();
update();
}
});
on('toEdge fromEdge lock unlock', () => {
update();
});
on('destroy', () => {
destroy();
});
on('enable disable', () => {
let {
nextEl,
prevEl
} = swiper.navigation;
nextEl = utils_makeElementsArray(nextEl);
prevEl = utils_makeElementsArray(prevEl);
if (swiper.enabled) {
update();
return;
}
[...nextEl, ...prevEl].filter(el => !!el).forEach(el => el.classList.add(swiper.params.navigation.lockClass));
});
on('click', (_s, e) => {
let {
nextEl,
prevEl
} = swiper.navigation;
nextEl = utils_makeElementsArray(nextEl);
prevEl = utils_makeElementsArray(prevEl);
const targetEl = e.target;
let targetIsButton = prevEl.includes(targetEl) || nextEl.includes(targetEl);
if (swiper.isElement && !targetIsButton) {
const path = e.path || e.composedPath && e.composedPath();
if (path) {
targetIsButton = path.find(pathEl => nextEl.includes(pathEl) || prevEl.includes(pathEl));
}
}
if (swiper.params.navigation.hideOnClick && !targetIsButton) {
if (swiper.pagination && swiper.params.pagination && swiper.params.pagination.clickable && (swiper.pagination.el === targetEl || swiper.pagination.el.contains(targetEl))) return;
let isHidden;
if (nextEl.length) {
isHidden = nextEl[0].classList.contains(swiper.params.navigation.hiddenClass);
} else if (prevEl.length) {
isHidden = prevEl[0].classList.contains(swiper.params.navigation.hiddenClass);
}
if (isHidden === true) {
emit('navigationShow');
} else {
emit('navigationHide');
}
[...nextEl, ...prevEl].filter(el => !!el).forEach(el => el.classList.toggle(swiper.params.navigation.hiddenClass));
}
});
const enable = () => {
swiper.el.classList.remove(...swiper.params.navigation.navigationDisabledClass.split(' '));
init();
update();
};
const disable = () => {
swiper.el.classList.add(...swiper.params.navigation.navigationDisabledClass.split(' '));
destroy();
};
Object.assign(swiper.navigation, {
enable,
disable,
update,
init,
destroy
});
}
;// ./node_modules/swiper/shared/classes-to-selector.mjs
function classes_to_selector_classesToSelector(classes) {
if (classes === void 0) {
classes = '';
}
return `.${classes.trim().replace(/([\.:!+\/()[\]])/g, '\\$1') // eslint-disable-line
.replace(/ /g, '.')}`;
}
;// ./node_modules/swiper/modules/pagination.mjs
function Pagination(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
const pfx = 'swiper-pagination';
extendParams({
pagination: {
el: null,
bulletElement: 'span',
clickable: false,
hideOnClick: false,
renderBullet: null,
renderProgressbar: null,
renderFraction: null,
renderCustom: null,
progressbarOpposite: false,
type: 'bullets',
// 'bullets' or 'progressbar' or 'fraction' or 'custom'
dynamicBullets: false,
dynamicMainBullets: 1,
formatFractionCurrent: number => number,
formatFractionTotal: number => number,
bulletClass: `${pfx}-bullet`,
bulletActiveClass: `${pfx}-bullet-active`,
modifierClass: `${pfx}-`,
currentClass: `${pfx}-current`,
totalClass: `${pfx}-total`,
hiddenClass: `${pfx}-hidden`,
progressbarFillClass: `${pfx}-progressbar-fill`,
progressbarOppositeClass: `${pfx}-progressbar-opposite`,
clickableClass: `${pfx}-clickable`,
lockClass: `${pfx}-lock`,
horizontalClass: `${pfx}-horizontal`,
verticalClass: `${pfx}-vertical`,
paginationDisabledClass: `${pfx}-disabled`
}
});
swiper.pagination = {
el: null,
bullets: []
};
let bulletSize;
let dynamicBulletIndex = 0;
function isPaginationDisabled() {
return !swiper.params.pagination.el || !swiper.pagination.el || Array.isArray(swiper.pagination.el) && swiper.pagination.el.length === 0;
}
function setSideBullets(bulletEl, position) {
const {
bulletActiveClass
} = swiper.params.pagination;
if (!bulletEl) return;
bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`];
if (bulletEl) {
bulletEl.classList.add(`${bulletActiveClass}-${position}`);
bulletEl = bulletEl[`${position === 'prev' ? 'previous' : 'next'}ElementSibling`];
if (bulletEl) {
bulletEl.classList.add(`${bulletActiveClass}-${position}-${position}`);
}
}
}
function getMoveDirection(prevIndex, nextIndex, length) {
prevIndex = prevIndex % length;
nextIndex = nextIndex % length;
if (nextIndex === prevIndex + 1) {
return 'next';
} else if (nextIndex === prevIndex - 1) {
return 'previous';
}
return;
}
function onBulletClick(e) {
const bulletEl = e.target.closest(classes_to_selector_classesToSelector(swiper.params.pagination.bulletClass));
if (!bulletEl) {
return;
}
e.preventDefault();
const index = utils_elementIndex(bulletEl) * swiper.params.slidesPerGroup;
if (swiper.params.loop) {
if (swiper.realIndex === index) return;
const moveDirection = getMoveDirection(swiper.realIndex, index, swiper.slides.length);
if (moveDirection === 'next') {
swiper.slideNext();
} else if (moveDirection === 'previous') {
swiper.slidePrev();
} else {
swiper.slideToLoop(index);
}
} else {
swiper.slideTo(index);
}
}
function update() {
// Render || Update Pagination bullets/items
const rtl = swiper.rtl;
const params = swiper.params.pagination;
if (isPaginationDisabled()) return;
let el = swiper.pagination.el;
el = utils_makeElementsArray(el);
// Current/Total
let current;
let previousIndex;
const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.slides.length;
const total = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length;
if (swiper.params.loop) {
previousIndex = swiper.previousRealIndex || 0;
current = swiper.params.slidesPerGroup > 1 ? Math.floor(swiper.realIndex / swiper.params.slidesPerGroup) : swiper.realIndex;
} else if (typeof swiper.snapIndex !== 'undefined') {
current = swiper.snapIndex;
previousIndex = swiper.previousSnapIndex;
} else {
previousIndex = swiper.previousIndex || 0;
current = swiper.activeIndex || 0;
}
// Types
if (params.type === 'bullets' && swiper.pagination.bullets && swiper.pagination.bullets.length > 0) {
const bullets = swiper.pagination.bullets;
let firstIndex;
let lastIndex;
let midIndex;
if (params.dynamicBullets) {
bulletSize = elementOuterSize(bullets[0], swiper.isHorizontal() ? 'width' : 'height', true);
el.forEach(subEl => {
subEl.style[swiper.isHorizontal() ? 'width' : 'height'] = `${bulletSize * (params.dynamicMainBullets + 4)}px`;
});
if (params.dynamicMainBullets > 1 && previousIndex !== undefined) {
dynamicBulletIndex += current - (previousIndex || 0);
if (dynamicBulletIndex > params.dynamicMainBullets - 1) {
dynamicBulletIndex = params.dynamicMainBullets - 1;
} else if (dynamicBulletIndex < 0) {
dynamicBulletIndex = 0;
}
}
firstIndex = Math.max(current - dynamicBulletIndex, 0);
lastIndex = firstIndex + (Math.min(bullets.length, params.dynamicMainBullets) - 1);
midIndex = (lastIndex + firstIndex) / 2;
}
bullets.forEach(bulletEl => {
const classesToRemove = [...['', '-next', '-next-next', '-prev', '-prev-prev', '-main'].map(suffix => `${params.bulletActiveClass}${suffix}`)].map(s => typeof s === 'string' && s.includes(' ') ? s.split(' ') : s).flat();
bulletEl.classList.remove(...classesToRemove);
});
if (el.length > 1) {
bullets.forEach(bullet => {
const bulletIndex = utils_elementIndex(bullet);
if (bulletIndex === current) {
bullet.classList.add(...params.bulletActiveClass.split(' '));
} else if (swiper.isElement) {
bullet.setAttribute('part', 'bullet');
}
if (params.dynamicBullets) {
if (bulletIndex >= firstIndex && bulletIndex <= lastIndex) {
bullet.classList.add(...`${params.bulletActiveClass}-main`.split(' '));
}
if (bulletIndex === firstIndex) {
setSideBullets(bullet, 'prev');
}
if (bulletIndex === lastIndex) {
setSideBullets(bullet, 'next');
}
}
});
} else {
const bullet = bullets[current];
if (bullet) {
bullet.classList.add(...params.bulletActiveClass.split(' '));
}
if (swiper.isElement) {
bullets.forEach((bulletEl, bulletIndex) => {
bulletEl.setAttribute('part', bulletIndex === current ? 'bullet-active' : 'bullet');
});
}
if (params.dynamicBullets) {
const firstDisplayedBullet = bullets[firstIndex];
const lastDisplayedBullet = bullets[lastIndex];
for (let i = firstIndex; i <= lastIndex; i += 1) {
if (bullets[i]) {
bullets[i].classList.add(...`${params.bulletActiveClass}-main`.split(' '));
}
}
setSideBullets(firstDisplayedBullet, 'prev');
setSideBullets(lastDisplayedBullet, 'next');
}
}
if (params.dynamicBullets) {
const dynamicBulletsLength = Math.min(bullets.length, params.dynamicMainBullets + 4);
const bulletsOffset = (bulletSize * dynamicBulletsLength - bulletSize) / 2 - midIndex * bulletSize;
const offsetProp = rtl ? 'right' : 'left';
bullets.forEach(bullet => {
bullet.style[swiper.isHorizontal() ? offsetProp : 'top'] = `${bulletsOffset}px`;
});
}
}
el.forEach((subEl, subElIndex) => {
if (params.type === 'fraction') {
subEl.querySelectorAll(classes_to_selector_classesToSelector(params.currentClass)).forEach(fractionEl => {
fractionEl.textContent = params.formatFractionCurrent(current + 1);
});
subEl.querySelectorAll(classes_to_selector_classesToSelector(params.totalClass)).forEach(totalEl => {
totalEl.textContent = params.formatFractionTotal(total);
});
}
if (params.type === 'progressbar') {
let progressbarDirection;
if (params.progressbarOpposite) {
progressbarDirection = swiper.isHorizontal() ? 'vertical' : 'horizontal';
} else {
progressbarDirection = swiper.isHorizontal() ? 'horizontal' : 'vertical';
}
const scale = (current + 1) / total;
let scaleX = 1;
let scaleY = 1;
if (progressbarDirection === 'horizontal') {
scaleX = scale;
} else {
scaleY = scale;
}
subEl.querySelectorAll(classes_to_selector_classesToSelector(params.progressbarFillClass)).forEach(progressEl => {
progressEl.style.transform = `translate3d(0,0,0) scaleX(${scaleX}) scaleY(${scaleY})`;
progressEl.style.transitionDuration = `${swiper.params.speed}ms`;
});
}
if (params.type === 'custom' && params.renderCustom) {
utils_setInnerHTML(subEl, params.renderCustom(swiper, current + 1, total));
if (subElIndex === 0) emit('paginationRender', subEl);
} else {
if (subElIndex === 0) emit('paginationRender', subEl);
emit('paginationUpdate', subEl);
}
if (swiper.params.watchOverflow && swiper.enabled) {
subEl.classList[swiper.isLocked ? 'add' : 'remove'](params.lockClass);
}
});
}
function render() {
// Render Container
const params = swiper.params.pagination;
if (isPaginationDisabled()) return;
const slidesLength = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.slides.length : swiper.grid && swiper.params.grid.rows > 1 ? swiper.slides.length / Math.ceil(swiper.params.grid.rows) : swiper.slides.length;
let el = swiper.pagination.el;
el = utils_makeElementsArray(el);
let paginationHTML = '';
if (params.type === 'bullets') {
let numberOfBullets = swiper.params.loop ? Math.ceil(slidesLength / swiper.params.slidesPerGroup) : swiper.snapGrid.length;
if (swiper.params.freeMode && swiper.params.freeMode.enabled && numberOfBullets > slidesLength) {
numberOfBullets = slidesLength;
}
for (let i = 0; i < numberOfBullets; i += 1) {
if (params.renderBullet) {
paginationHTML += params.renderBullet.call(swiper, i, params.bulletClass);
} else {
// prettier-ignore
paginationHTML += `<${params.bulletElement} ${swiper.isElement ? 'part="bullet"' : ''} class="${params.bulletClass}">${params.bulletElement}>`;
}
}
}
if (params.type === 'fraction') {
if (params.renderFraction) {
paginationHTML = params.renderFraction.call(swiper, params.currentClass, params.totalClass);
} else {
paginationHTML = `` + ' / ' + ``;
}
}
if (params.type === 'progressbar') {
if (params.renderProgressbar) {
paginationHTML = params.renderProgressbar.call(swiper, params.progressbarFillClass);
} else {
paginationHTML = ``;
}
}
swiper.pagination.bullets = [];
el.forEach(subEl => {
if (params.type !== 'custom') {
utils_setInnerHTML(subEl, paginationHTML || '');
}
if (params.type === 'bullets') {
swiper.pagination.bullets.push(...subEl.querySelectorAll(classes_to_selector_classesToSelector(params.bulletClass)));
}
});
if (params.type !== 'custom') {
emit('paginationRender', el[0]);
}
}
function init() {
swiper.params.pagination = create_element_if_not_defined_createElementIfNotDefined(swiper, swiper.originalParams.pagination, swiper.params.pagination, {
el: 'swiper-pagination'
});
const params = swiper.params.pagination;
if (!params.el) return;
let el;
if (typeof params.el === 'string' && swiper.isElement) {
el = swiper.el.querySelector(params.el);
}
if (!el && typeof params.el === 'string') {
el = [...document.querySelectorAll(params.el)];
}
if (!el) {
el = params.el;
}
if (!el || el.length === 0) return;
if (swiper.params.uniqueNavElements && typeof params.el === 'string' && Array.isArray(el) && el.length > 1) {
el = [...swiper.el.querySelectorAll(params.el)];
// check if it belongs to another nested Swiper
if (el.length > 1) {
el = el.find(subEl => {
if (utils_elementParents(subEl, '.swiper')[0] !== swiper.el) return false;
return true;
});
}
}
if (Array.isArray(el) && el.length === 1) el = el[0];
Object.assign(swiper.pagination, {
el
});
el = utils_makeElementsArray(el);
el.forEach(subEl => {
if (params.type === 'bullets' && params.clickable) {
subEl.classList.add(...(params.clickableClass || '').split(' '));
}
subEl.classList.add(params.modifierClass + params.type);
subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
if (params.type === 'bullets' && params.dynamicBullets) {
subEl.classList.add(`${params.modifierClass}${params.type}-dynamic`);
dynamicBulletIndex = 0;
if (params.dynamicMainBullets < 1) {
params.dynamicMainBullets = 1;
}
}
if (params.type === 'progressbar' && params.progressbarOpposite) {
subEl.classList.add(params.progressbarOppositeClass);
}
if (params.clickable) {
subEl.addEventListener('click', onBulletClick);
}
if (!swiper.enabled) {
subEl.classList.add(params.lockClass);
}
});
}
function destroy() {
const params = swiper.params.pagination;
if (isPaginationDisabled()) return;
let el = swiper.pagination.el;
if (el) {
el = utils_makeElementsArray(el);
el.forEach(subEl => {
subEl.classList.remove(params.hiddenClass);
subEl.classList.remove(params.modifierClass + params.type);
subEl.classList.remove(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
if (params.clickable) {
subEl.classList.remove(...(params.clickableClass || '').split(' '));
subEl.removeEventListener('click', onBulletClick);
}
});
}
if (swiper.pagination.bullets) swiper.pagination.bullets.forEach(subEl => subEl.classList.remove(...params.bulletActiveClass.split(' ')));
}
on('changeDirection', () => {
if (!swiper.pagination || !swiper.pagination.el) return;
const params = swiper.params.pagination;
let {
el
} = swiper.pagination;
el = utils_makeElementsArray(el);
el.forEach(subEl => {
subEl.classList.remove(params.horizontalClass, params.verticalClass);
subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
});
});
on('init', () => {
if (swiper.params.pagination.enabled === false) {
// eslint-disable-next-line
disable();
} else {
init();
render();
update();
}
});
on('activeIndexChange', () => {
if (typeof swiper.snapIndex === 'undefined') {
update();
}
});
on('snapIndexChange', () => {
update();
});
on('snapGridLengthChange', () => {
render();
update();
});
on('destroy', () => {
destroy();
});
on('enable disable', () => {
let {
el
} = swiper.pagination;
if (el) {
el = utils_makeElementsArray(el);
el.forEach(subEl => subEl.classList[swiper.enabled ? 'remove' : 'add'](swiper.params.pagination.lockClass));
}
});
on('lock unlock', () => {
update();
});
on('click', (_s, e) => {
const targetEl = e.target;
const el = utils_makeElementsArray(swiper.pagination.el);
if (swiper.params.pagination.el && swiper.params.pagination.hideOnClick && el && el.length > 0 && !targetEl.classList.contains(swiper.params.pagination.bulletClass)) {
if (swiper.navigation && (swiper.navigation.nextEl && targetEl === swiper.navigation.nextEl || swiper.navigation.prevEl && targetEl === swiper.navigation.prevEl)) return;
const isHidden = el[0].classList.contains(swiper.params.pagination.hiddenClass);
if (isHidden === true) {
emit('paginationShow');
} else {
emit('paginationHide');
}
el.forEach(subEl => subEl.classList.toggle(swiper.params.pagination.hiddenClass));
}
});
const enable = () => {
swiper.el.classList.remove(swiper.params.pagination.paginationDisabledClass);
let {
el
} = swiper.pagination;
if (el) {
el = utils_makeElementsArray(el);
el.forEach(subEl => subEl.classList.remove(swiper.params.pagination.paginationDisabledClass));
}
init();
render();
update();
};
const disable = () => {
swiper.el.classList.add(swiper.params.pagination.paginationDisabledClass);
let {
el
} = swiper.pagination;
if (el) {
el = utils_makeElementsArray(el);
el.forEach(subEl => subEl.classList.add(swiper.params.pagination.paginationDisabledClass));
}
destroy();
};
Object.assign(swiper.pagination, {
enable,
disable,
render,
update,
init,
destroy
});
}
;// ./node_modules/swiper/modules/scrollbar.mjs
function Scrollbar(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
const document = getDocument();
let isTouched = false;
let timeout = null;
let dragTimeout = null;
let dragStartPos;
let dragSize;
let trackSize;
let divider;
extendParams({
scrollbar: {
el: null,
dragSize: 'auto',
hide: false,
draggable: false,
snapOnRelease: true,
lockClass: 'swiper-scrollbar-lock',
dragClass: 'swiper-scrollbar-drag',
scrollbarDisabledClass: 'swiper-scrollbar-disabled',
horizontalClass: `swiper-scrollbar-horizontal`,
verticalClass: `swiper-scrollbar-vertical`
}
});
swiper.scrollbar = {
el: null,
dragEl: null
};
function setTranslate() {
if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
const {
scrollbar,
rtlTranslate: rtl
} = swiper;
const {
dragEl,
el
} = scrollbar;
const params = swiper.params.scrollbar;
const progress = swiper.params.loop ? swiper.progressLoop : swiper.progress;
let newSize = dragSize;
let newPos = (trackSize - dragSize) * progress;
if (rtl) {
newPos = -newPos;
if (newPos > 0) {
newSize = dragSize - newPos;
newPos = 0;
} else if (-newPos + dragSize > trackSize) {
newSize = trackSize + newPos;
}
} else if (newPos < 0) {
newSize = dragSize + newPos;
newPos = 0;
} else if (newPos + dragSize > trackSize) {
newSize = trackSize - newPos;
}
if (swiper.isHorizontal()) {
dragEl.style.transform = `translate3d(${newPos}px, 0, 0)`;
dragEl.style.width = `${newSize}px`;
} else {
dragEl.style.transform = `translate3d(0px, ${newPos}px, 0)`;
dragEl.style.height = `${newSize}px`;
}
if (params.hide) {
clearTimeout(timeout);
el.style.opacity = 1;
timeout = setTimeout(() => {
el.style.opacity = 0;
el.style.transitionDuration = '400ms';
}, 1000);
}
}
function setTransition(duration) {
if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
swiper.scrollbar.dragEl.style.transitionDuration = `${duration}ms`;
}
function updateSize() {
if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
const {
scrollbar
} = swiper;
const {
dragEl,
el
} = scrollbar;
dragEl.style.width = '';
dragEl.style.height = '';
trackSize = swiper.isHorizontal() ? el.offsetWidth : el.offsetHeight;
divider = swiper.size / (swiper.virtualSize + swiper.params.slidesOffsetBefore - (swiper.params.centeredSlides ? swiper.snapGrid[0] : 0));
if (swiper.params.scrollbar.dragSize === 'auto') {
dragSize = trackSize * divider;
} else {
dragSize = parseInt(swiper.params.scrollbar.dragSize, 10);
}
if (swiper.isHorizontal()) {
dragEl.style.width = `${dragSize}px`;
} else {
dragEl.style.height = `${dragSize}px`;
}
if (divider >= 1) {
el.style.display = 'none';
} else {
el.style.display = '';
}
if (swiper.params.scrollbar.hide) {
el.style.opacity = 0;
}
if (swiper.params.watchOverflow && swiper.enabled) {
scrollbar.el.classList[swiper.isLocked ? 'add' : 'remove'](swiper.params.scrollbar.lockClass);
}
}
function getPointerPosition(e) {
return swiper.isHorizontal() ? e.clientX : e.clientY;
}
function setDragPosition(e) {
const {
scrollbar,
rtlTranslate: rtl
} = swiper;
const {
el
} = scrollbar;
let positionRatio;
positionRatio = (getPointerPosition(e) - elementOffset(el)[swiper.isHorizontal() ? 'left' : 'top'] - (dragStartPos !== null ? dragStartPos : dragSize / 2)) / (trackSize - dragSize);
positionRatio = Math.max(Math.min(positionRatio, 1), 0);
if (rtl) {
positionRatio = 1 - positionRatio;
}
const position = swiper.minTranslate() + (swiper.maxTranslate() - swiper.minTranslate()) * positionRatio;
swiper.updateProgress(position);
swiper.setTranslate(position);
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
}
function onDragStart(e) {
const params = swiper.params.scrollbar;
const {
scrollbar,
wrapperEl
} = swiper;
const {
el,
dragEl
} = scrollbar;
isTouched = true;
dragStartPos = e.target === dragEl ? getPointerPosition(e) - e.target.getBoundingClientRect()[swiper.isHorizontal() ? 'left' : 'top'] : null;
e.preventDefault();
e.stopPropagation();
wrapperEl.style.transitionDuration = '100ms';
dragEl.style.transitionDuration = '100ms';
setDragPosition(e);
clearTimeout(dragTimeout);
el.style.transitionDuration = '0ms';
if (params.hide) {
el.style.opacity = 1;
}
if (swiper.params.cssMode) {
swiper.wrapperEl.style['scroll-snap-type'] = 'none';
}
emit('scrollbarDragStart', e);
}
function onDragMove(e) {
const {
scrollbar,
wrapperEl
} = swiper;
const {
el,
dragEl
} = scrollbar;
if (!isTouched) return;
if (e.preventDefault && e.cancelable) e.preventDefault();else e.returnValue = false;
setDragPosition(e);
wrapperEl.style.transitionDuration = '0ms';
el.style.transitionDuration = '0ms';
dragEl.style.transitionDuration = '0ms';
emit('scrollbarDragMove', e);
}
function onDragEnd(e) {
const params = swiper.params.scrollbar;
const {
scrollbar,
wrapperEl
} = swiper;
const {
el
} = scrollbar;
if (!isTouched) return;
isTouched = false;
if (swiper.params.cssMode) {
swiper.wrapperEl.style['scroll-snap-type'] = '';
wrapperEl.style.transitionDuration = '';
}
if (params.hide) {
clearTimeout(dragTimeout);
dragTimeout = nextTick(() => {
el.style.opacity = 0;
el.style.transitionDuration = '400ms';
}, 1000);
}
emit('scrollbarDragEnd', e);
if (params.snapOnRelease) {
swiper.slideToClosest();
}
}
function events(method) {
const {
scrollbar,
params
} = swiper;
const el = scrollbar.el;
if (!el) return;
const target = el;
const activeListener = params.passiveListeners ? {
passive: false,
capture: false
} : false;
const passiveListener = params.passiveListeners ? {
passive: true,
capture: false
} : false;
if (!target) return;
const eventMethod = method === 'on' ? 'addEventListener' : 'removeEventListener';
target[eventMethod]('pointerdown', onDragStart, activeListener);
document[eventMethod]('pointermove', onDragMove, activeListener);
document[eventMethod]('pointerup', onDragEnd, passiveListener);
}
function enableDraggable() {
if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
events('on');
}
function disableDraggable() {
if (!swiper.params.scrollbar.el || !swiper.scrollbar.el) return;
events('off');
}
function init() {
const {
scrollbar,
el: swiperEl
} = swiper;
swiper.params.scrollbar = createElementIfNotDefined(swiper, swiper.originalParams.scrollbar, swiper.params.scrollbar, {
el: 'swiper-scrollbar'
});
const params = swiper.params.scrollbar;
if (!params.el) return;
let el;
if (typeof params.el === 'string' && swiper.isElement) {
el = swiper.el.querySelector(params.el);
}
if (!el && typeof params.el === 'string') {
el = document.querySelectorAll(params.el);
if (!el.length) return;
} else if (!el) {
el = params.el;
}
if (swiper.params.uniqueNavElements && typeof params.el === 'string' && el.length > 1 && swiperEl.querySelectorAll(params.el).length === 1) {
el = swiperEl.querySelector(params.el);
}
if (el.length > 0) el = el[0];
el.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
let dragEl;
if (el) {
dragEl = el.querySelector(classesToSelector(swiper.params.scrollbar.dragClass));
if (!dragEl) {
dragEl = createElement('div', swiper.params.scrollbar.dragClass);
el.append(dragEl);
}
}
Object.assign(scrollbar, {
el,
dragEl
});
if (params.draggable) {
enableDraggable();
}
if (el) {
el.classList[swiper.enabled ? 'remove' : 'add'](...classesToTokens(swiper.params.scrollbar.lockClass));
}
}
function destroy() {
const params = swiper.params.scrollbar;
const el = swiper.scrollbar.el;
if (el) {
el.classList.remove(...classesToTokens(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass));
}
disableDraggable();
}
on('changeDirection', () => {
if (!swiper.scrollbar || !swiper.scrollbar.el) return;
const params = swiper.params.scrollbar;
let {
el
} = swiper.scrollbar;
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.classList.remove(params.horizontalClass, params.verticalClass);
subEl.classList.add(swiper.isHorizontal() ? params.horizontalClass : params.verticalClass);
});
});
on('init', () => {
if (swiper.params.scrollbar.enabled === false) {
// eslint-disable-next-line
disable();
} else {
init();
updateSize();
setTranslate();
}
});
on('update resize observerUpdate lock unlock changeDirection', () => {
updateSize();
});
on('setTranslate', () => {
setTranslate();
});
on('setTransition', (_s, duration) => {
setTransition(duration);
});
on('enable disable', () => {
const {
el
} = swiper.scrollbar;
if (el) {
el.classList[swiper.enabled ? 'remove' : 'add'](...classesToTokens(swiper.params.scrollbar.lockClass));
}
});
on('destroy', () => {
destroy();
});
const enable = () => {
swiper.el.classList.remove(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
if (swiper.scrollbar.el) {
swiper.scrollbar.el.classList.remove(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
}
init();
updateSize();
setTranslate();
};
const disable = () => {
swiper.el.classList.add(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
if (swiper.scrollbar.el) {
swiper.scrollbar.el.classList.add(...classesToTokens(swiper.params.scrollbar.scrollbarDisabledClass));
}
destroy();
};
Object.assign(swiper.scrollbar, {
enable,
disable,
updateSize,
setTranslate,
init,
destroy
});
}
;// ./node_modules/swiper/modules/parallax.mjs
function Parallax(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
parallax: {
enabled: false
}
});
const elementsSelector = '[data-swiper-parallax], [data-swiper-parallax-x], [data-swiper-parallax-y], [data-swiper-parallax-opacity], [data-swiper-parallax-scale]';
const setTransform = (el, progress) => {
const {
rtl
} = swiper;
const rtlFactor = rtl ? -1 : 1;
const p = el.getAttribute('data-swiper-parallax') || '0';
let x = el.getAttribute('data-swiper-parallax-x');
let y = el.getAttribute('data-swiper-parallax-y');
const scale = el.getAttribute('data-swiper-parallax-scale');
const opacity = el.getAttribute('data-swiper-parallax-opacity');
const rotate = el.getAttribute('data-swiper-parallax-rotate');
if (x || y) {
x = x || '0';
y = y || '0';
} else if (swiper.isHorizontal()) {
x = p;
y = '0';
} else {
y = p;
x = '0';
}
if (x.indexOf('%') >= 0) {
x = `${parseInt(x, 10) * progress * rtlFactor}%`;
} else {
x = `${x * progress * rtlFactor}px`;
}
if (y.indexOf('%') >= 0) {
y = `${parseInt(y, 10) * progress}%`;
} else {
y = `${y * progress}px`;
}
if (typeof opacity !== 'undefined' && opacity !== null) {
const currentOpacity = opacity - (opacity - 1) * (1 - Math.abs(progress));
el.style.opacity = currentOpacity;
}
let transform = `translate3d(${x}, ${y}, 0px)`;
if (typeof scale !== 'undefined' && scale !== null) {
const currentScale = scale - (scale - 1) * (1 - Math.abs(progress));
transform += ` scale(${currentScale})`;
}
if (rotate && typeof rotate !== 'undefined' && rotate !== null) {
const currentRotate = rotate * progress * -1;
transform += ` rotate(${currentRotate}deg)`;
}
el.style.transform = transform;
};
const setTranslate = () => {
const {
el,
slides,
progress,
snapGrid,
isElement
} = swiper;
const elements = elementChildren(el, elementsSelector);
if (swiper.isElement) {
elements.push(...elementChildren(swiper.hostEl, elementsSelector));
}
elements.forEach(subEl => {
setTransform(subEl, progress);
});
slides.forEach((slideEl, slideIndex) => {
let slideProgress = slideEl.progress;
if (swiper.params.slidesPerGroup > 1 && swiper.params.slidesPerView !== 'auto') {
slideProgress += Math.ceil(slideIndex / 2) - progress * (snapGrid.length - 1);
}
slideProgress = Math.min(Math.max(slideProgress, -1), 1);
slideEl.querySelectorAll(`${elementsSelector}, [data-swiper-parallax-rotate]`).forEach(subEl => {
setTransform(subEl, slideProgress);
});
});
};
const setTransition = function (duration) {
if (duration === void 0) {
duration = swiper.params.speed;
}
const {
el,
hostEl
} = swiper;
const elements = [...el.querySelectorAll(elementsSelector)];
if (swiper.isElement) {
elements.push(...hostEl.querySelectorAll(elementsSelector));
}
elements.forEach(parallaxEl => {
let parallaxDuration = parseInt(parallaxEl.getAttribute('data-swiper-parallax-duration'), 10) || duration;
if (duration === 0) parallaxDuration = 0;
parallaxEl.style.transitionDuration = `${parallaxDuration}ms`;
});
};
on('beforeInit', () => {
if (!swiper.params.parallax.enabled) return;
swiper.params.watchSlidesProgress = true;
swiper.originalParams.watchSlidesProgress = true;
});
on('init', () => {
if (!swiper.params.parallax.enabled) return;
setTranslate();
});
on('setTranslate', () => {
if (!swiper.params.parallax.enabled) return;
setTranslate();
});
on('setTransition', (_swiper, duration) => {
if (!swiper.params.parallax.enabled) return;
setTransition(duration);
});
}
;// ./node_modules/swiper/modules/zoom.mjs
function Zoom(_ref) {
let {
swiper,
extendParams,
on,
emit
} = _ref;
const window = getWindow();
extendParams({
zoom: {
enabled: false,
limitToOriginalSize: false,
maxRatio: 3,
minRatio: 1,
panOnMouseMove: false,
toggle: true,
containerClass: 'swiper-zoom-container',
zoomedSlideClass: 'swiper-slide-zoomed'
}
});
swiper.zoom = {
enabled: false
};
let currentScale = 1;
let isScaling = false;
let isPanningWithMouse = false;
let mousePanStart = {
x: 0,
y: 0
};
const mousePanSensitivity = -3; // Negative to invert pan direction
let fakeGestureTouched;
let fakeGestureMoved;
const evCache = [];
const gesture = {
originX: 0,
originY: 0,
slideEl: undefined,
slideWidth: undefined,
slideHeight: undefined,
imageEl: undefined,
imageWrapEl: undefined,
maxRatio: 3
};
const image = {
isTouched: undefined,
isMoved: undefined,
currentX: undefined,
currentY: undefined,
minX: undefined,
minY: undefined,
maxX: undefined,
maxY: undefined,
width: undefined,
height: undefined,
startX: undefined,
startY: undefined,
touchesStart: {},
touchesCurrent: {}
};
const velocity = {
x: undefined,
y: undefined,
prevPositionX: undefined,
prevPositionY: undefined,
prevTime: undefined
};
let scale = 1;
Object.defineProperty(swiper.zoom, 'scale', {
get() {
return scale;
},
set(value) {
if (scale !== value) {
const imageEl = gesture.imageEl;
const slideEl = gesture.slideEl;
emit('zoomChange', value, imageEl, slideEl);
}
scale = value;
}
});
function getDistanceBetweenTouches() {
if (evCache.length < 2) return 1;
const x1 = evCache[0].pageX;
const y1 = evCache[0].pageY;
const x2 = evCache[1].pageX;
const y2 = evCache[1].pageY;
const distance = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
return distance;
}
function getMaxRatio() {
const params = swiper.params.zoom;
const maxRatio = gesture.imageWrapEl.getAttribute('data-swiper-zoom') || params.maxRatio;
if (params.limitToOriginalSize && gesture.imageEl && gesture.imageEl.naturalWidth) {
const imageMaxRatio = gesture.imageEl.naturalWidth / gesture.imageEl.offsetWidth;
return Math.min(imageMaxRatio, maxRatio);
}
return maxRatio;
}
function getScaleOrigin() {
if (evCache.length < 2) return {
x: null,
y: null
};
const box = gesture.imageEl.getBoundingClientRect();
return [(evCache[0].pageX + (evCache[1].pageX - evCache[0].pageX) / 2 - box.x - window.scrollX) / currentScale, (evCache[0].pageY + (evCache[1].pageY - evCache[0].pageY) / 2 - box.y - window.scrollY) / currentScale];
}
function getSlideSelector() {
return swiper.isElement ? `swiper-slide` : `.${swiper.params.slideClass}`;
}
function eventWithinSlide(e) {
const slideSelector = getSlideSelector();
if (e.target.matches(slideSelector)) return true;
if (swiper.slides.filter(slideEl => slideEl.contains(e.target)).length > 0) return true;
return false;
}
function eventWithinZoomContainer(e) {
const selector = `.${swiper.params.zoom.containerClass}`;
if (e.target.matches(selector)) return true;
if ([...swiper.hostEl.querySelectorAll(selector)].filter(containerEl => containerEl.contains(e.target)).length > 0) return true;
return false;
}
// Events
function onGestureStart(e) {
if (e.pointerType === 'mouse') {
evCache.splice(0, evCache.length);
}
if (!eventWithinSlide(e)) return;
const params = swiper.params.zoom;
fakeGestureTouched = false;
fakeGestureMoved = false;
evCache.push(e);
if (evCache.length < 2) {
return;
}
fakeGestureTouched = true;
gesture.scaleStart = getDistanceBetweenTouches();
if (!gesture.slideEl) {
gesture.slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`);
if (!gesture.slideEl) gesture.slideEl = swiper.slides[swiper.activeIndex];
let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`);
if (imageEl) {
imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0];
}
gesture.imageEl = imageEl;
if (imageEl) {
gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0];
} else {
gesture.imageWrapEl = undefined;
}
if (!gesture.imageWrapEl) {
gesture.imageEl = undefined;
return;
}
gesture.maxRatio = getMaxRatio();
}
if (gesture.imageEl) {
const [originX, originY] = getScaleOrigin();
gesture.originX = originX;
gesture.originY = originY;
gesture.imageEl.style.transitionDuration = '0ms';
}
isScaling = true;
}
function onGestureChange(e) {
if (!eventWithinSlide(e)) return;
const params = swiper.params.zoom;
const zoom = swiper.zoom;
const pointerIndex = evCache.findIndex(cachedEv => cachedEv.pointerId === e.pointerId);
if (pointerIndex >= 0) evCache[pointerIndex] = e;
if (evCache.length < 2) {
return;
}
fakeGestureMoved = true;
gesture.scaleMove = getDistanceBetweenTouches();
if (!gesture.imageEl) {
return;
}
zoom.scale = gesture.scaleMove / gesture.scaleStart * currentScale;
if (zoom.scale > gesture.maxRatio) {
zoom.scale = gesture.maxRatio - 1 + (zoom.scale - gesture.maxRatio + 1) ** 0.5;
}
if (zoom.scale < params.minRatio) {
zoom.scale = params.minRatio + 1 - (params.minRatio - zoom.scale + 1) ** 0.5;
}
gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`;
}
function onGestureEnd(e) {
if (!eventWithinSlide(e)) return;
if (e.pointerType === 'mouse' && e.type === 'pointerout') return;
const params = swiper.params.zoom;
const zoom = swiper.zoom;
const pointerIndex = evCache.findIndex(cachedEv => cachedEv.pointerId === e.pointerId);
if (pointerIndex >= 0) evCache.splice(pointerIndex, 1);
if (!fakeGestureTouched || !fakeGestureMoved) {
return;
}
fakeGestureTouched = false;
fakeGestureMoved = false;
if (!gesture.imageEl) return;
zoom.scale = Math.max(Math.min(zoom.scale, gesture.maxRatio), params.minRatio);
gesture.imageEl.style.transitionDuration = `${swiper.params.speed}ms`;
gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`;
currentScale = zoom.scale;
isScaling = false;
if (zoom.scale > 1 && gesture.slideEl) {
gesture.slideEl.classList.add(`${params.zoomedSlideClass}`);
} else if (zoom.scale <= 1 && gesture.slideEl) {
gesture.slideEl.classList.remove(`${params.zoomedSlideClass}`);
}
if (zoom.scale === 1) {
gesture.originX = 0;
gesture.originY = 0;
gesture.slideEl = undefined;
}
}
let allowTouchMoveTimeout;
function allowTouchMove() {
swiper.touchEventsData.preventTouchMoveFromPointerMove = false;
}
function preventTouchMove() {
clearTimeout(allowTouchMoveTimeout);
swiper.touchEventsData.preventTouchMoveFromPointerMove = true;
allowTouchMoveTimeout = setTimeout(() => {
if (swiper.destroyed) return;
allowTouchMove();
});
}
function onTouchStart(e) {
const device = swiper.device;
if (!gesture.imageEl) return;
if (image.isTouched) return;
if (device.android && e.cancelable) e.preventDefault();
image.isTouched = true;
const event = evCache.length > 0 ? evCache[0] : e;
image.touchesStart.x = event.pageX;
image.touchesStart.y = event.pageY;
}
function onTouchMove(e) {
const isMouseEvent = e.pointerType === 'mouse';
const isMousePan = isMouseEvent && swiper.params.zoom.panOnMouseMove;
if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) {
return;
}
const zoom = swiper.zoom;
if (!gesture.imageEl) {
return;
}
if (!image.isTouched || !gesture.slideEl) {
if (isMousePan) onMouseMove(e);
return;
}
if (isMousePan) {
onMouseMove(e);
return;
}
if (!image.isMoved) {
image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth;
image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight;
image.startX = getTranslate(gesture.imageWrapEl, 'x') || 0;
image.startY = getTranslate(gesture.imageWrapEl, 'y') || 0;
gesture.slideWidth = gesture.slideEl.offsetWidth;
gesture.slideHeight = gesture.slideEl.offsetHeight;
gesture.imageWrapEl.style.transitionDuration = '0ms';
}
// Define if we need image drag
const scaledWidth = image.width * zoom.scale;
const scaledHeight = image.height * zoom.scale;
image.minX = Math.min(gesture.slideWidth / 2 - scaledWidth / 2, 0);
image.maxX = -image.minX;
image.minY = Math.min(gesture.slideHeight / 2 - scaledHeight / 2, 0);
image.maxY = -image.minY;
image.touchesCurrent.x = evCache.length > 0 ? evCache[0].pageX : e.pageX;
image.touchesCurrent.y = evCache.length > 0 ? evCache[0].pageY : e.pageY;
const touchesDiff = Math.max(Math.abs(image.touchesCurrent.x - image.touchesStart.x), Math.abs(image.touchesCurrent.y - image.touchesStart.y));
if (touchesDiff > 5) {
swiper.allowClick = false;
}
if (!image.isMoved && !isScaling) {
if (swiper.isHorizontal() && (Math.floor(image.minX) === Math.floor(image.startX) && image.touchesCurrent.x < image.touchesStart.x || Math.floor(image.maxX) === Math.floor(image.startX) && image.touchesCurrent.x > image.touchesStart.x)) {
image.isTouched = false;
allowTouchMove();
return;
}
if (!swiper.isHorizontal() && (Math.floor(image.minY) === Math.floor(image.startY) && image.touchesCurrent.y < image.touchesStart.y || Math.floor(image.maxY) === Math.floor(image.startY) && image.touchesCurrent.y > image.touchesStart.y)) {
image.isTouched = false;
allowTouchMove();
return;
}
}
if (e.cancelable) {
e.preventDefault();
}
e.stopPropagation();
preventTouchMove();
image.isMoved = true;
const scaleRatio = (zoom.scale - currentScale) / (gesture.maxRatio - swiper.params.zoom.minRatio);
const {
originX,
originY
} = gesture;
image.currentX = image.touchesCurrent.x - image.touchesStart.x + image.startX + scaleRatio * (image.width - originX * 2);
image.currentY = image.touchesCurrent.y - image.touchesStart.y + image.startY + scaleRatio * (image.height - originY * 2);
if (image.currentX < image.minX) {
image.currentX = image.minX + 1 - (image.minX - image.currentX + 1) ** 0.8;
}
if (image.currentX > image.maxX) {
image.currentX = image.maxX - 1 + (image.currentX - image.maxX + 1) ** 0.8;
}
if (image.currentY < image.minY) {
image.currentY = image.minY + 1 - (image.minY - image.currentY + 1) ** 0.8;
}
if (image.currentY > image.maxY) {
image.currentY = image.maxY - 1 + (image.currentY - image.maxY + 1) ** 0.8;
}
// Velocity
if (!velocity.prevPositionX) velocity.prevPositionX = image.touchesCurrent.x;
if (!velocity.prevPositionY) velocity.prevPositionY = image.touchesCurrent.y;
if (!velocity.prevTime) velocity.prevTime = Date.now();
velocity.x = (image.touchesCurrent.x - velocity.prevPositionX) / (Date.now() - velocity.prevTime) / 2;
velocity.y = (image.touchesCurrent.y - velocity.prevPositionY) / (Date.now() - velocity.prevTime) / 2;
if (Math.abs(image.touchesCurrent.x - velocity.prevPositionX) < 2) velocity.x = 0;
if (Math.abs(image.touchesCurrent.y - velocity.prevPositionY) < 2) velocity.y = 0;
velocity.prevPositionX = image.touchesCurrent.x;
velocity.prevPositionY = image.touchesCurrent.y;
velocity.prevTime = Date.now();
gesture.imageWrapEl.style.transform = `translate3d(${image.currentX}px, ${image.currentY}px,0)`;
}
function onTouchEnd() {
const zoom = swiper.zoom;
evCache.length = 0;
if (!gesture.imageEl) return;
if (!image.isTouched || !image.isMoved) {
image.isTouched = false;
image.isMoved = false;
return;
}
image.isTouched = false;
image.isMoved = false;
let momentumDurationX = 300;
let momentumDurationY = 300;
const momentumDistanceX = velocity.x * momentumDurationX;
const newPositionX = image.currentX + momentumDistanceX;
const momentumDistanceY = velocity.y * momentumDurationY;
const newPositionY = image.currentY + momentumDistanceY;
// Fix duration
if (velocity.x !== 0) momentumDurationX = Math.abs((newPositionX - image.currentX) / velocity.x);
if (velocity.y !== 0) momentumDurationY = Math.abs((newPositionY - image.currentY) / velocity.y);
const momentumDuration = Math.max(momentumDurationX, momentumDurationY);
image.currentX = newPositionX;
image.currentY = newPositionY;
// Define if we need image drag
const scaledWidth = image.width * zoom.scale;
const scaledHeight = image.height * zoom.scale;
image.minX = Math.min(gesture.slideWidth / 2 - scaledWidth / 2, 0);
image.maxX = -image.minX;
image.minY = Math.min(gesture.slideHeight / 2 - scaledHeight / 2, 0);
image.maxY = -image.minY;
image.currentX = Math.max(Math.min(image.currentX, image.maxX), image.minX);
image.currentY = Math.max(Math.min(image.currentY, image.maxY), image.minY);
gesture.imageWrapEl.style.transitionDuration = `${momentumDuration}ms`;
gesture.imageWrapEl.style.transform = `translate3d(${image.currentX}px, ${image.currentY}px,0)`;
}
function onTransitionEnd() {
const zoom = swiper.zoom;
if (gesture.slideEl && swiper.activeIndex !== swiper.slides.indexOf(gesture.slideEl)) {
if (gesture.imageEl) {
gesture.imageEl.style.transform = 'translate3d(0,0,0) scale(1)';
}
if (gesture.imageWrapEl) {
gesture.imageWrapEl.style.transform = 'translate3d(0,0,0)';
}
gesture.slideEl.classList.remove(`${swiper.params.zoom.zoomedSlideClass}`);
zoom.scale = 1;
currentScale = 1;
gesture.slideEl = undefined;
gesture.imageEl = undefined;
gesture.imageWrapEl = undefined;
gesture.originX = 0;
gesture.originY = 0;
}
}
function onMouseMove(e) {
// Only pan if zoomed in and mouse panning is enabled
if (currentScale <= 1 || !gesture.imageWrapEl) return;
if (!eventWithinSlide(e) || !eventWithinZoomContainer(e)) return;
const currentTransform = window.getComputedStyle(gesture.imageWrapEl).transform;
const matrix = new window.DOMMatrix(currentTransform);
if (!isPanningWithMouse) {
isPanningWithMouse = true;
mousePanStart.x = e.clientX;
mousePanStart.y = e.clientY;
image.startX = matrix.e;
image.startY = matrix.f;
image.width = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth;
image.height = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight;
gesture.slideWidth = gesture.slideEl.offsetWidth;
gesture.slideHeight = gesture.slideEl.offsetHeight;
return;
}
const deltaX = (e.clientX - mousePanStart.x) * mousePanSensitivity;
const deltaY = (e.clientY - mousePanStart.y) * mousePanSensitivity;
const scaledWidth = image.width * currentScale;
const scaledHeight = image.height * currentScale;
const slideWidth = gesture.slideWidth;
const slideHeight = gesture.slideHeight;
const minX = Math.min(slideWidth / 2 - scaledWidth / 2, 0);
const maxX = -minX;
const minY = Math.min(slideHeight / 2 - scaledHeight / 2, 0);
const maxY = -minY;
const newX = Math.max(Math.min(image.startX + deltaX, maxX), minX);
const newY = Math.max(Math.min(image.startY + deltaY, maxY), minY);
gesture.imageWrapEl.style.transitionDuration = '0ms';
gesture.imageWrapEl.style.transform = `translate3d(${newX}px, ${newY}px, 0)`;
mousePanStart.x = e.clientX;
mousePanStart.y = e.clientY;
image.startX = newX;
image.startY = newY;
image.currentX = newX;
image.currentY = newY;
}
function zoomIn(e) {
const zoom = swiper.zoom;
const params = swiper.params.zoom;
if (!gesture.slideEl) {
if (e && e.target) {
gesture.slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`);
}
if (!gesture.slideEl) {
if (swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual) {
gesture.slideEl = elementChildren(swiper.slidesEl, `.${swiper.params.slideActiveClass}`)[0];
} else {
gesture.slideEl = swiper.slides[swiper.activeIndex];
}
}
let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`);
if (imageEl) {
imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0];
}
gesture.imageEl = imageEl;
if (imageEl) {
gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0];
} else {
gesture.imageWrapEl = undefined;
}
}
if (!gesture.imageEl || !gesture.imageWrapEl) return;
if (swiper.params.cssMode) {
swiper.wrapperEl.style.overflow = 'hidden';
swiper.wrapperEl.style.touchAction = 'none';
}
gesture.slideEl.classList.add(`${params.zoomedSlideClass}`);
let touchX;
let touchY;
let offsetX;
let offsetY;
let diffX;
let diffY;
let translateX;
let translateY;
let imageWidth;
let imageHeight;
let scaledWidth;
let scaledHeight;
let translateMinX;
let translateMinY;
let translateMaxX;
let translateMaxY;
let slideWidth;
let slideHeight;
if (typeof image.touchesStart.x === 'undefined' && e) {
touchX = e.pageX;
touchY = e.pageY;
} else {
touchX = image.touchesStart.x;
touchY = image.touchesStart.y;
}
const prevScale = currentScale;
const forceZoomRatio = typeof e === 'number' ? e : null;
if (currentScale === 1 && forceZoomRatio) {
touchX = undefined;
touchY = undefined;
image.touchesStart.x = undefined;
image.touchesStart.y = undefined;
}
const maxRatio = getMaxRatio();
zoom.scale = forceZoomRatio || maxRatio;
currentScale = forceZoomRatio || maxRatio;
if (e && !(currentScale === 1 && forceZoomRatio)) {
slideWidth = gesture.slideEl.offsetWidth;
slideHeight = gesture.slideEl.offsetHeight;
offsetX = elementOffset(gesture.slideEl).left + window.scrollX;
offsetY = elementOffset(gesture.slideEl).top + window.scrollY;
diffX = offsetX + slideWidth / 2 - touchX;
diffY = offsetY + slideHeight / 2 - touchY;
imageWidth = gesture.imageEl.offsetWidth || gesture.imageEl.clientWidth;
imageHeight = gesture.imageEl.offsetHeight || gesture.imageEl.clientHeight;
scaledWidth = imageWidth * zoom.scale;
scaledHeight = imageHeight * zoom.scale;
translateMinX = Math.min(slideWidth / 2 - scaledWidth / 2, 0);
translateMinY = Math.min(slideHeight / 2 - scaledHeight / 2, 0);
translateMaxX = -translateMinX;
translateMaxY = -translateMinY;
if (prevScale > 0 && forceZoomRatio && typeof image.currentX === 'number' && typeof image.currentY === 'number') {
translateX = image.currentX * zoom.scale / prevScale;
translateY = image.currentY * zoom.scale / prevScale;
} else {
translateX = diffX * zoom.scale;
translateY = diffY * zoom.scale;
}
if (translateX < translateMinX) {
translateX = translateMinX;
}
if (translateX > translateMaxX) {
translateX = translateMaxX;
}
if (translateY < translateMinY) {
translateY = translateMinY;
}
if (translateY > translateMaxY) {
translateY = translateMaxY;
}
} else {
translateX = 0;
translateY = 0;
}
if (forceZoomRatio && zoom.scale === 1) {
gesture.originX = 0;
gesture.originY = 0;
}
image.currentX = translateX;
image.currentY = translateY;
gesture.imageWrapEl.style.transitionDuration = '300ms';
gesture.imageWrapEl.style.transform = `translate3d(${translateX}px, ${translateY}px,0)`;
gesture.imageEl.style.transitionDuration = '300ms';
gesture.imageEl.style.transform = `translate3d(0,0,0) scale(${zoom.scale})`;
}
function zoomOut() {
const zoom = swiper.zoom;
const params = swiper.params.zoom;
if (!gesture.slideEl) {
if (swiper.params.virtual && swiper.params.virtual.enabled && swiper.virtual) {
gesture.slideEl = elementChildren(swiper.slidesEl, `.${swiper.params.slideActiveClass}`)[0];
} else {
gesture.slideEl = swiper.slides[swiper.activeIndex];
}
let imageEl = gesture.slideEl.querySelector(`.${params.containerClass}`);
if (imageEl) {
imageEl = imageEl.querySelectorAll('picture, img, svg, canvas, .swiper-zoom-target')[0];
}
gesture.imageEl = imageEl;
if (imageEl) {
gesture.imageWrapEl = elementParents(gesture.imageEl, `.${params.containerClass}`)[0];
} else {
gesture.imageWrapEl = undefined;
}
}
if (!gesture.imageEl || !gesture.imageWrapEl) return;
if (swiper.params.cssMode) {
swiper.wrapperEl.style.overflow = '';
swiper.wrapperEl.style.touchAction = '';
}
zoom.scale = 1;
currentScale = 1;
image.currentX = undefined;
image.currentY = undefined;
image.touchesStart.x = undefined;
image.touchesStart.y = undefined;
gesture.imageWrapEl.style.transitionDuration = '300ms';
gesture.imageWrapEl.style.transform = 'translate3d(0,0,0)';
gesture.imageEl.style.transitionDuration = '300ms';
gesture.imageEl.style.transform = 'translate3d(0,0,0) scale(1)';
gesture.slideEl.classList.remove(`${params.zoomedSlideClass}`);
gesture.slideEl = undefined;
gesture.originX = 0;
gesture.originY = 0;
if (swiper.params.zoom.panOnMouseMove) {
mousePanStart = {
x: 0,
y: 0
};
if (isPanningWithMouse) {
isPanningWithMouse = false;
image.startX = 0;
image.startY = 0;
}
}
}
// Toggle Zoom
function zoomToggle(e) {
const zoom = swiper.zoom;
if (zoom.scale && zoom.scale !== 1) {
// Zoom Out
zoomOut();
} else {
// Zoom In
zoomIn(e);
}
}
function getListeners() {
const passiveListener = swiper.params.passiveListeners ? {
passive: true,
capture: false
} : false;
const activeListenerWithCapture = swiper.params.passiveListeners ? {
passive: false,
capture: true
} : true;
return {
passiveListener,
activeListenerWithCapture
};
}
// Attach/Detach Events
function enable() {
const zoom = swiper.zoom;
if (zoom.enabled) return;
zoom.enabled = true;
const {
passiveListener,
activeListenerWithCapture
} = getListeners();
// Scale image
swiper.wrapperEl.addEventListener('pointerdown', onGestureStart, passiveListener);
swiper.wrapperEl.addEventListener('pointermove', onGestureChange, activeListenerWithCapture);
['pointerup', 'pointercancel', 'pointerout'].forEach(eventName => {
swiper.wrapperEl.addEventListener(eventName, onGestureEnd, passiveListener);
});
// Move image
swiper.wrapperEl.addEventListener('pointermove', onTouchMove, activeListenerWithCapture);
}
function disable() {
const zoom = swiper.zoom;
if (!zoom.enabled) return;
zoom.enabled = false;
const {
passiveListener,
activeListenerWithCapture
} = getListeners();
// Scale image
swiper.wrapperEl.removeEventListener('pointerdown', onGestureStart, passiveListener);
swiper.wrapperEl.removeEventListener('pointermove', onGestureChange, activeListenerWithCapture);
['pointerup', 'pointercancel', 'pointerout'].forEach(eventName => {
swiper.wrapperEl.removeEventListener(eventName, onGestureEnd, passiveListener);
});
// Move image
swiper.wrapperEl.removeEventListener('pointermove', onTouchMove, activeListenerWithCapture);
}
on('init', () => {
if (swiper.params.zoom.enabled) {
enable();
}
});
on('destroy', () => {
disable();
});
on('touchStart', (_s, e) => {
if (!swiper.zoom.enabled) return;
onTouchStart(e);
});
on('touchEnd', (_s, e) => {
if (!swiper.zoom.enabled) return;
onTouchEnd();
});
on('doubleTap', (_s, e) => {
if (!swiper.animating && swiper.params.zoom.enabled && swiper.zoom.enabled && swiper.params.zoom.toggle) {
zoomToggle(e);
}
});
on('transitionEnd', () => {
if (swiper.zoom.enabled && swiper.params.zoom.enabled) {
onTransitionEnd();
}
});
on('slideChange', () => {
if (swiper.zoom.enabled && swiper.params.zoom.enabled && swiper.params.cssMode) {
onTransitionEnd();
}
});
Object.assign(swiper.zoom, {
enable,
disable,
in: zoomIn,
out: zoomOut,
toggle: zoomToggle
});
}
;// ./node_modules/swiper/modules/controller.mjs
/* eslint no-bitwise: ["error", { "allow": [">>"] }] */
function Controller(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
controller: {
control: undefined,
inverse: false,
by: 'slide' // or 'container'
}
});
swiper.controller = {
control: undefined
};
function LinearSpline(x, y) {
const binarySearch = function search() {
let maxIndex;
let minIndex;
let guess;
return (array, val) => {
minIndex = -1;
maxIndex = array.length;
while (maxIndex - minIndex > 1) {
guess = maxIndex + minIndex >> 1;
if (array[guess] <= val) {
minIndex = guess;
} else {
maxIndex = guess;
}
}
return maxIndex;
};
}();
this.x = x;
this.y = y;
this.lastIndex = x.length - 1;
// Given an x value (x2), return the expected y2 value:
// (x1,y1) is the known point before given value,
// (x3,y3) is the known point after given value.
let i1;
let i3;
this.interpolate = function interpolate(x2) {
if (!x2) return 0;
// Get the indexes of x1 and x3 (the array indexes before and after given x2):
i3 = binarySearch(this.x, x2);
i1 = i3 - 1;
// We have our indexes i1 & i3, so we can calculate already:
// y2 := ((x2−x1) × (y3−y1)) ÷ (x3−x1) + y1
return (x2 - this.x[i1]) * (this.y[i3] - this.y[i1]) / (this.x[i3] - this.x[i1]) + this.y[i1];
};
return this;
}
function getInterpolateFunction(c) {
swiper.controller.spline = swiper.params.loop ? new LinearSpline(swiper.slidesGrid, c.slidesGrid) : new LinearSpline(swiper.snapGrid, c.snapGrid);
}
function setTranslate(_t, byController) {
const controlled = swiper.controller.control;
let multiplier;
let controlledTranslate;
const Swiper = swiper.constructor;
function setControlledTranslate(c) {
if (c.destroyed) return;
// this will create an Interpolate function based on the snapGrids
// x is the Grid of the scrolled scroller and y will be the controlled scroller
// it makes sense to create this only once and recall it for the interpolation
// the function does a lot of value caching for performance
const translate = swiper.rtlTranslate ? -swiper.translate : swiper.translate;
if (swiper.params.controller.by === 'slide') {
getInterpolateFunction(c);
// i am not sure why the values have to be multiplicated this way, tried to invert the snapGrid
// but it did not work out
controlledTranslate = -swiper.controller.spline.interpolate(-translate);
}
if (!controlledTranslate || swiper.params.controller.by === 'container') {
multiplier = (c.maxTranslate() - c.minTranslate()) / (swiper.maxTranslate() - swiper.minTranslate());
if (Number.isNaN(multiplier) || !Number.isFinite(multiplier)) {
multiplier = 1;
}
controlledTranslate = (translate - swiper.minTranslate()) * multiplier + c.minTranslate();
}
if (swiper.params.controller.inverse) {
controlledTranslate = c.maxTranslate() - controlledTranslate;
}
c.updateProgress(controlledTranslate);
c.setTranslate(controlledTranslate, swiper);
c.updateActiveIndex();
c.updateSlidesClasses();
}
if (Array.isArray(controlled)) {
for (let i = 0; i < controlled.length; i += 1) {
if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
setControlledTranslate(controlled[i]);
}
}
} else if (controlled instanceof Swiper && byController !== controlled) {
setControlledTranslate(controlled);
}
}
function setTransition(duration, byController) {
const Swiper = swiper.constructor;
const controlled = swiper.controller.control;
let i;
function setControlledTransition(c) {
if (c.destroyed) return;
c.setTransition(duration, swiper);
if (duration !== 0) {
c.transitionStart();
if (c.params.autoHeight) {
utils_nextTick(() => {
c.updateAutoHeight();
});
}
utils_elementTransitionEnd(c.wrapperEl, () => {
if (!controlled) return;
c.transitionEnd();
});
}
}
if (Array.isArray(controlled)) {
for (i = 0; i < controlled.length; i += 1) {
if (controlled[i] !== byController && controlled[i] instanceof Swiper) {
setControlledTransition(controlled[i]);
}
}
} else if (controlled instanceof Swiper && byController !== controlled) {
setControlledTransition(controlled);
}
}
function removeSpline() {
if (!swiper.controller.control) return;
if (swiper.controller.spline) {
swiper.controller.spline = undefined;
delete swiper.controller.spline;
}
}
on('beforeInit', () => {
if (typeof window !== 'undefined' && (
// eslint-disable-line
typeof swiper.params.controller.control === 'string' || swiper.params.controller.control instanceof HTMLElement)) {
const controlElements = typeof swiper.params.controller.control === 'string' ? [...document.querySelectorAll(swiper.params.controller.control)] : [swiper.params.controller.control];
controlElements.forEach(controlElement => {
if (!swiper.controller.control) swiper.controller.control = [];
if (controlElement && controlElement.swiper) {
swiper.controller.control.push(controlElement.swiper);
} else if (controlElement) {
const eventName = `${swiper.params.eventsPrefix}init`;
const onControllerSwiper = e => {
swiper.controller.control.push(e.detail[0]);
swiper.update();
controlElement.removeEventListener(eventName, onControllerSwiper);
};
controlElement.addEventListener(eventName, onControllerSwiper);
}
});
return;
}
swiper.controller.control = swiper.params.controller.control;
});
on('update', () => {
removeSpline();
});
on('resize', () => {
removeSpline();
});
on('observerUpdate', () => {
removeSpline();
});
on('setTranslate', (_s, translate, byController) => {
if (!swiper.controller.control || swiper.controller.control.destroyed) return;
swiper.controller.setTranslate(translate, byController);
});
on('setTransition', (_s, duration, byController) => {
if (!swiper.controller.control || swiper.controller.control.destroyed) return;
swiper.controller.setTransition(duration, byController);
});
Object.assign(swiper.controller, {
setTranslate,
setTransition
});
}
;// ./node_modules/swiper/modules/a11y.mjs
function A11y(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
a11y: {
enabled: true,
notificationClass: 'swiper-notification',
prevSlideMessage: 'Previous slide',
nextSlideMessage: 'Next slide',
firstSlideMessage: 'This is the first slide',
lastSlideMessage: 'This is the last slide',
paginationBulletMessage: 'Go to slide {{index}}',
slideLabelMessage: '{{index}} / {{slidesLength}}',
containerMessage: null,
containerRoleDescriptionMessage: null,
containerRole: null,
itemRoleDescriptionMessage: null,
slideRole: 'group',
id: null,
scrollOnFocus: true
}
});
swiper.a11y = {
clicked: false
};
let liveRegion = null;
let preventFocusHandler;
let focusTargetSlideEl;
let visibilityChangedTimestamp = new Date().getTime();
function notify(message) {
const notification = liveRegion;
if (notification.length === 0) return;
setInnerHTML(notification, message);
}
function getRandomNumber(size) {
if (size === void 0) {
size = 16;
}
const randomChar = () => Math.round(16 * Math.random()).toString(16);
return 'x'.repeat(size).replace(/x/g, randomChar);
}
function makeElFocusable(el) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('tabIndex', '0');
});
}
function makeElNotFocusable(el) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('tabIndex', '-1');
});
}
function addElRole(el, role) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('role', role);
});
}
function addElRoleDescription(el, description) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('aria-roledescription', description);
});
}
function addElControls(el, controls) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('aria-controls', controls);
});
}
function addElLabel(el, label) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('aria-label', label);
});
}
function addElId(el, id) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('id', id);
});
}
function addElLive(el, live) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('aria-live', live);
});
}
function disableEl(el) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('aria-disabled', true);
});
}
function enableEl(el) {
el = makeElementsArray(el);
el.forEach(subEl => {
subEl.setAttribute('aria-disabled', false);
});
}
function onEnterOrSpaceKey(e) {
if (e.keyCode !== 13 && e.keyCode !== 32) return;
const params = swiper.params.a11y;
const targetEl = e.target;
if (swiper.pagination && swiper.pagination.el && (targetEl === swiper.pagination.el || swiper.pagination.el.contains(e.target))) {
if (!e.target.matches(classesToSelector(swiper.params.pagination.bulletClass))) return;
}
if (swiper.navigation && swiper.navigation.prevEl && swiper.navigation.nextEl) {
const prevEls = makeElementsArray(swiper.navigation.prevEl);
const nextEls = makeElementsArray(swiper.navigation.nextEl);
if (nextEls.includes(targetEl)) {
if (!(swiper.isEnd && !swiper.params.loop)) {
swiper.slideNext();
}
if (swiper.isEnd) {
notify(params.lastSlideMessage);
} else {
notify(params.nextSlideMessage);
}
}
if (prevEls.includes(targetEl)) {
if (!(swiper.isBeginning && !swiper.params.loop)) {
swiper.slidePrev();
}
if (swiper.isBeginning) {
notify(params.firstSlideMessage);
} else {
notify(params.prevSlideMessage);
}
}
}
if (swiper.pagination && targetEl.matches(classesToSelector(swiper.params.pagination.bulletClass))) {
targetEl.click();
}
}
function updateNavigation() {
if (swiper.params.loop || swiper.params.rewind || !swiper.navigation) return;
const {
nextEl,
prevEl
} = swiper.navigation;
if (prevEl) {
if (swiper.isBeginning) {
disableEl(prevEl);
makeElNotFocusable(prevEl);
} else {
enableEl(prevEl);
makeElFocusable(prevEl);
}
}
if (nextEl) {
if (swiper.isEnd) {
disableEl(nextEl);
makeElNotFocusable(nextEl);
} else {
enableEl(nextEl);
makeElFocusable(nextEl);
}
}
}
function hasPagination() {
return swiper.pagination && swiper.pagination.bullets && swiper.pagination.bullets.length;
}
function hasClickablePagination() {
return hasPagination() && swiper.params.pagination.clickable;
}
function updatePagination() {
const params = swiper.params.a11y;
if (!hasPagination()) return;
swiper.pagination.bullets.forEach(bulletEl => {
if (swiper.params.pagination.clickable) {
makeElFocusable(bulletEl);
if (!swiper.params.pagination.renderBullet) {
addElRole(bulletEl, 'button');
addElLabel(bulletEl, params.paginationBulletMessage.replace(/\{\{index\}\}/, elementIndex(bulletEl) + 1));
}
}
if (bulletEl.matches(classesToSelector(swiper.params.pagination.bulletActiveClass))) {
bulletEl.setAttribute('aria-current', 'true');
} else {
bulletEl.removeAttribute('aria-current');
}
});
}
const initNavEl = (el, wrapperId, message) => {
makeElFocusable(el);
if (el.tagName !== 'BUTTON') {
addElRole(el, 'button');
el.addEventListener('keydown', onEnterOrSpaceKey);
}
addElLabel(el, message);
addElControls(el, wrapperId);
};
const handlePointerDown = e => {
if (focusTargetSlideEl && focusTargetSlideEl !== e.target && !focusTargetSlideEl.contains(e.target)) {
preventFocusHandler = true;
}
swiper.a11y.clicked = true;
};
const handlePointerUp = () => {
preventFocusHandler = false;
requestAnimationFrame(() => {
requestAnimationFrame(() => {
if (!swiper.destroyed) {
swiper.a11y.clicked = false;
}
});
});
};
const onVisibilityChange = e => {
visibilityChangedTimestamp = new Date().getTime();
};
const handleFocus = e => {
if (swiper.a11y.clicked || !swiper.params.a11y.scrollOnFocus) return;
if (new Date().getTime() - visibilityChangedTimestamp < 100) return;
const slideEl = e.target.closest(`.${swiper.params.slideClass}, swiper-slide`);
if (!slideEl || !swiper.slides.includes(slideEl)) return;
focusTargetSlideEl = slideEl;
const isActive = swiper.slides.indexOf(slideEl) === swiper.activeIndex;
const isVisible = swiper.params.watchSlidesProgress && swiper.visibleSlides && swiper.visibleSlides.includes(slideEl);
if (isActive || isVisible) return;
if (e.sourceCapabilities && e.sourceCapabilities.firesTouchEvents) return;
if (swiper.isHorizontal()) {
swiper.el.scrollLeft = 0;
} else {
swiper.el.scrollTop = 0;
}
requestAnimationFrame(() => {
if (preventFocusHandler) return;
if (swiper.params.loop) {
swiper.slideToLoop(swiper.getSlideIndexWhenGrid(parseInt(slideEl.getAttribute('data-swiper-slide-index'))), 0);
} else {
swiper.slideTo(swiper.getSlideIndexWhenGrid(swiper.slides.indexOf(slideEl)), 0);
}
preventFocusHandler = false;
});
};
const initSlides = () => {
const params = swiper.params.a11y;
if (params.itemRoleDescriptionMessage) {
addElRoleDescription(swiper.slides, params.itemRoleDescriptionMessage);
}
if (params.slideRole) {
addElRole(swiper.slides, params.slideRole);
}
const slidesLength = swiper.slides.length;
if (params.slideLabelMessage) {
swiper.slides.forEach((slideEl, index) => {
const slideIndex = swiper.params.loop ? parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10) : index;
const ariaLabelMessage = params.slideLabelMessage.replace(/\{\{index\}\}/, slideIndex + 1).replace(/\{\{slidesLength\}\}/, slidesLength);
addElLabel(slideEl, ariaLabelMessage);
});
}
};
const init = () => {
const params = swiper.params.a11y;
swiper.el.append(liveRegion);
// Container
const containerEl = swiper.el;
if (params.containerRoleDescriptionMessage) {
addElRoleDescription(containerEl, params.containerRoleDescriptionMessage);
}
if (params.containerMessage) {
addElLabel(containerEl, params.containerMessage);
}
if (params.containerRole) {
addElRole(containerEl, params.containerRole);
}
// Wrapper
const wrapperEl = swiper.wrapperEl;
const wrapperId = params.id || wrapperEl.getAttribute('id') || `swiper-wrapper-${getRandomNumber(16)}`;
const live = swiper.params.autoplay && swiper.params.autoplay.enabled ? 'off' : 'polite';
addElId(wrapperEl, wrapperId);
addElLive(wrapperEl, live);
// Slide
initSlides();
// Navigation
let {
nextEl,
prevEl
} = swiper.navigation ? swiper.navigation : {};
nextEl = makeElementsArray(nextEl);
prevEl = makeElementsArray(prevEl);
if (nextEl) {
nextEl.forEach(el => initNavEl(el, wrapperId, params.nextSlideMessage));
}
if (prevEl) {
prevEl.forEach(el => initNavEl(el, wrapperId, params.prevSlideMessage));
}
// Pagination
if (hasClickablePagination()) {
const paginationEl = makeElementsArray(swiper.pagination.el);
paginationEl.forEach(el => {
el.addEventListener('keydown', onEnterOrSpaceKey);
});
}
// Tab focus
const document = getDocument();
document.addEventListener('visibilitychange', onVisibilityChange);
swiper.el.addEventListener('focus', handleFocus, true);
swiper.el.addEventListener('focus', handleFocus, true);
swiper.el.addEventListener('pointerdown', handlePointerDown, true);
swiper.el.addEventListener('pointerup', handlePointerUp, true);
};
function destroy() {
if (liveRegion) liveRegion.remove();
let {
nextEl,
prevEl
} = swiper.navigation ? swiper.navigation : {};
nextEl = makeElementsArray(nextEl);
prevEl = makeElementsArray(prevEl);
if (nextEl) {
nextEl.forEach(el => el.removeEventListener('keydown', onEnterOrSpaceKey));
}
if (prevEl) {
prevEl.forEach(el => el.removeEventListener('keydown', onEnterOrSpaceKey));
}
// Pagination
if (hasClickablePagination()) {
const paginationEl = makeElementsArray(swiper.pagination.el);
paginationEl.forEach(el => {
el.removeEventListener('keydown', onEnterOrSpaceKey);
});
}
const document = getDocument();
document.removeEventListener('visibilitychange', onVisibilityChange);
// Tab focus
if (swiper.el && typeof swiper.el !== 'string') {
swiper.el.removeEventListener('focus', handleFocus, true);
swiper.el.removeEventListener('pointerdown', handlePointerDown, true);
swiper.el.removeEventListener('pointerup', handlePointerUp, true);
}
}
on('beforeInit', () => {
liveRegion = createElement('span', swiper.params.a11y.notificationClass);
liveRegion.setAttribute('aria-live', 'assertive');
liveRegion.setAttribute('aria-atomic', 'true');
});
on('afterInit', () => {
if (!swiper.params.a11y.enabled) return;
init();
});
on('slidesLengthChange snapGridLengthChange slidesGridLengthChange', () => {
if (!swiper.params.a11y.enabled) return;
initSlides();
});
on('fromEdge toEdge afterInit lock unlock', () => {
if (!swiper.params.a11y.enabled) return;
updateNavigation();
});
on('paginationUpdate', () => {
if (!swiper.params.a11y.enabled) return;
updatePagination();
});
on('destroy', () => {
if (!swiper.params.a11y.enabled) return;
destroy();
});
}
;// ./node_modules/swiper/modules/history.mjs
function History(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
history: {
enabled: false,
root: '',
replaceState: false,
key: 'slides',
keepQuery: false
}
});
let initialized = false;
let paths = {};
const slugify = text => {
return text.toString().replace(/\s+/g, '-').replace(/[^\w-]+/g, '').replace(/--+/g, '-').replace(/^-+/, '').replace(/-+$/, '');
};
const getPathValues = urlOverride => {
const window = getWindow();
let location;
if (urlOverride) {
location = new URL(urlOverride);
} else {
location = window.location;
}
const pathArray = location.pathname.slice(1).split('/').filter(part => part !== '');
const total = pathArray.length;
const key = pathArray[total - 2];
const value = pathArray[total - 1];
return {
key,
value
};
};
const setHistory = (key, index) => {
const window = getWindow();
if (!initialized || !swiper.params.history.enabled) return;
let location;
if (swiper.params.url) {
location = new URL(swiper.params.url);
} else {
location = window.location;
}
const slide = swiper.virtual && swiper.params.virtual.enabled ? swiper.slidesEl.querySelector(`[data-swiper-slide-index="${index}"]`) : swiper.slides[index];
let value = slugify(slide.getAttribute('data-history'));
if (swiper.params.history.root.length > 0) {
let root = swiper.params.history.root;
if (root[root.length - 1] === '/') root = root.slice(0, root.length - 1);
value = `${root}/${key ? `${key}/` : ''}${value}`;
} else if (!location.pathname.includes(key)) {
value = `${key ? `${key}/` : ''}${value}`;
}
if (swiper.params.history.keepQuery) {
value += location.search;
}
const currentState = window.history.state;
if (currentState && currentState.value === value) {
return;
}
if (swiper.params.history.replaceState) {
window.history.replaceState({
value
}, null, value);
} else {
window.history.pushState({
value
}, null, value);
}
};
const scrollToSlide = (speed, value, runCallbacks) => {
if (value) {
for (let i = 0, length = swiper.slides.length; i < length; i += 1) {
const slide = swiper.slides[i];
const slideHistory = slugify(slide.getAttribute('data-history'));
if (slideHistory === value) {
const index = swiper.getSlideIndex(slide);
swiper.slideTo(index, speed, runCallbacks);
}
}
} else {
swiper.slideTo(0, speed, runCallbacks);
}
};
const setHistoryPopState = () => {
paths = getPathValues(swiper.params.url);
scrollToSlide(swiper.params.speed, paths.value, false);
};
const init = () => {
const window = getWindow();
if (!swiper.params.history) return;
if (!window.history || !window.history.pushState) {
swiper.params.history.enabled = false;
swiper.params.hashNavigation.enabled = true;
return;
}
initialized = true;
paths = getPathValues(swiper.params.url);
if (!paths.key && !paths.value) {
if (!swiper.params.history.replaceState) {
window.addEventListener('popstate', setHistoryPopState);
}
return;
}
scrollToSlide(0, paths.value, swiper.params.runCallbacksOnInit);
if (!swiper.params.history.replaceState) {
window.addEventListener('popstate', setHistoryPopState);
}
};
const destroy = () => {
const window = getWindow();
if (!swiper.params.history.replaceState) {
window.removeEventListener('popstate', setHistoryPopState);
}
};
on('init', () => {
if (swiper.params.history.enabled) {
init();
}
});
on('destroy', () => {
if (swiper.params.history.enabled) {
destroy();
}
});
on('transitionEnd _freeModeNoMomentumRelease', () => {
if (initialized) {
setHistory(swiper.params.history.key, swiper.activeIndex);
}
});
on('slideChange', () => {
if (initialized && swiper.params.cssMode) {
setHistory(swiper.params.history.key, swiper.activeIndex);
}
});
}
;// ./node_modules/swiper/modules/hash-navigation.mjs
function HashNavigation(_ref) {
let {
swiper,
extendParams,
emit,
on
} = _ref;
let initialized = false;
const document = getDocument();
const window = getWindow();
extendParams({
hashNavigation: {
enabled: false,
replaceState: false,
watchState: false,
getSlideIndex(_s, hash) {
if (swiper.virtual && swiper.params.virtual.enabled) {
const slideWithHash = swiper.slides.find(slideEl => slideEl.getAttribute('data-hash') === hash);
if (!slideWithHash) return 0;
const index = parseInt(slideWithHash.getAttribute('data-swiper-slide-index'), 10);
return index;
}
return swiper.getSlideIndex(elementChildren(swiper.slidesEl, `.${swiper.params.slideClass}[data-hash="${hash}"], swiper-slide[data-hash="${hash}"]`)[0]);
}
}
});
const onHashChange = () => {
emit('hashChange');
const newHash = document.location.hash.replace('#', '');
const activeSlideEl = swiper.virtual && swiper.params.virtual.enabled ? swiper.slidesEl.querySelector(`[data-swiper-slide-index="${swiper.activeIndex}"]`) : swiper.slides[swiper.activeIndex];
const activeSlideHash = activeSlideEl ? activeSlideEl.getAttribute('data-hash') : '';
if (newHash !== activeSlideHash) {
const newIndex = swiper.params.hashNavigation.getSlideIndex(swiper, newHash);
if (typeof newIndex === 'undefined' || Number.isNaN(newIndex)) return;
swiper.slideTo(newIndex);
}
};
const setHash = () => {
if (!initialized || !swiper.params.hashNavigation.enabled) return;
const activeSlideEl = swiper.virtual && swiper.params.virtual.enabled ? swiper.slidesEl.querySelector(`[data-swiper-slide-index="${swiper.activeIndex}"]`) : swiper.slides[swiper.activeIndex];
const activeSlideHash = activeSlideEl ? activeSlideEl.getAttribute('data-hash') || activeSlideEl.getAttribute('data-history') : '';
if (swiper.params.hashNavigation.replaceState && window.history && window.history.replaceState) {
window.history.replaceState(null, null, `#${activeSlideHash}` || '');
emit('hashSet');
} else {
document.location.hash = activeSlideHash || '';
emit('hashSet');
}
};
const init = () => {
if (!swiper.params.hashNavigation.enabled || swiper.params.history && swiper.params.history.enabled) return;
initialized = true;
const hash = document.location.hash.replace('#', '');
if (hash) {
const speed = 0;
const index = swiper.params.hashNavigation.getSlideIndex(swiper, hash);
swiper.slideTo(index || 0, speed, swiper.params.runCallbacksOnInit, true);
}
if (swiper.params.hashNavigation.watchState) {
window.addEventListener('hashchange', onHashChange);
}
};
const destroy = () => {
if (swiper.params.hashNavigation.watchState) {
window.removeEventListener('hashchange', onHashChange);
}
};
on('init', () => {
if (swiper.params.hashNavigation.enabled) {
init();
}
});
on('destroy', () => {
if (swiper.params.hashNavigation.enabled) {
destroy();
}
});
on('transitionEnd _freeModeNoMomentumRelease', () => {
if (initialized) {
setHash();
}
});
on('slideChange', () => {
if (initialized && swiper.params.cssMode) {
setHash();
}
});
}
;// ./node_modules/swiper/modules/autoplay.mjs
/* eslint no-underscore-dangle: "off" */
/* eslint no-use-before-define: "off" */
function Autoplay(_ref) {
let {
swiper,
extendParams,
on,
emit,
params
} = _ref;
swiper.autoplay = {
running: false,
paused: false,
timeLeft: 0
};
extendParams({
autoplay: {
enabled: false,
delay: 3000,
waitForTransition: true,
disableOnInteraction: false,
stopOnLastSlide: false,
reverseDirection: false,
pauseOnMouseEnter: false
}
});
let timeout;
let raf;
let autoplayDelayTotal = params && params.autoplay ? params.autoplay.delay : 3000;
let autoplayDelayCurrent = params && params.autoplay ? params.autoplay.delay : 3000;
let autoplayTimeLeft;
let autoplayStartTime = new Date().getTime();
let wasPaused;
let isTouched;
let pausedByTouch;
let touchStartTimeout;
let slideChanged;
let pausedByInteraction;
let pausedByPointerEnter;
function onTransitionEnd(e) {
if (!swiper || swiper.destroyed || !swiper.wrapperEl) return;
if (e.target !== swiper.wrapperEl) return;
swiper.wrapperEl.removeEventListener('transitionend', onTransitionEnd);
if (pausedByPointerEnter || e.detail && e.detail.bySwiperTouchMove) {
return;
}
resume();
}
const calcTimeLeft = () => {
if (swiper.destroyed || !swiper.autoplay.running) return;
if (swiper.autoplay.paused) {
wasPaused = true;
} else if (wasPaused) {
autoplayDelayCurrent = autoplayTimeLeft;
wasPaused = false;
}
const timeLeft = swiper.autoplay.paused ? autoplayTimeLeft : autoplayStartTime + autoplayDelayCurrent - new Date().getTime();
swiper.autoplay.timeLeft = timeLeft;
emit('autoplayTimeLeft', timeLeft, timeLeft / autoplayDelayTotal);
raf = requestAnimationFrame(() => {
calcTimeLeft();
});
};
const getSlideDelay = () => {
let activeSlideEl;
if (swiper.virtual && swiper.params.virtual.enabled) {
activeSlideEl = swiper.slides.find(slideEl => slideEl.classList.contains('swiper-slide-active'));
} else {
activeSlideEl = swiper.slides[swiper.activeIndex];
}
if (!activeSlideEl) return undefined;
const currentSlideDelay = parseInt(activeSlideEl.getAttribute('data-swiper-autoplay'), 10);
return currentSlideDelay;
};
const run = delayForce => {
if (swiper.destroyed || !swiper.autoplay.running) return;
cancelAnimationFrame(raf);
calcTimeLeft();
let delay = typeof delayForce === 'undefined' ? swiper.params.autoplay.delay : delayForce;
autoplayDelayTotal = swiper.params.autoplay.delay;
autoplayDelayCurrent = swiper.params.autoplay.delay;
const currentSlideDelay = getSlideDelay();
if (!Number.isNaN(currentSlideDelay) && currentSlideDelay > 0 && typeof delayForce === 'undefined') {
delay = currentSlideDelay;
autoplayDelayTotal = currentSlideDelay;
autoplayDelayCurrent = currentSlideDelay;
}
autoplayTimeLeft = delay;
const speed = swiper.params.speed;
const proceed = () => {
if (!swiper || swiper.destroyed) return;
if (swiper.params.autoplay.reverseDirection) {
if (!swiper.isBeginning || swiper.params.loop || swiper.params.rewind) {
swiper.slidePrev(speed, true, true);
emit('autoplay');
} else if (!swiper.params.autoplay.stopOnLastSlide) {
swiper.slideTo(swiper.slides.length - 1, speed, true, true);
emit('autoplay');
}
} else {
if (!swiper.isEnd || swiper.params.loop || swiper.params.rewind) {
swiper.slideNext(speed, true, true);
emit('autoplay');
} else if (!swiper.params.autoplay.stopOnLastSlide) {
swiper.slideTo(0, speed, true, true);
emit('autoplay');
}
}
if (swiper.params.cssMode) {
autoplayStartTime = new Date().getTime();
requestAnimationFrame(() => {
run();
});
}
};
if (delay > 0) {
clearTimeout(timeout);
timeout = setTimeout(() => {
proceed();
}, delay);
} else {
requestAnimationFrame(() => {
proceed();
});
}
// eslint-disable-next-line
return delay;
};
const start = () => {
autoplayStartTime = new Date().getTime();
swiper.autoplay.running = true;
run();
emit('autoplayStart');
};
const stop = () => {
swiper.autoplay.running = false;
clearTimeout(timeout);
cancelAnimationFrame(raf);
emit('autoplayStop');
};
const pause = (internal, reset) => {
if (swiper.destroyed || !swiper.autoplay.running) return;
clearTimeout(timeout);
if (!internal) {
pausedByInteraction = true;
}
const proceed = () => {
emit('autoplayPause');
if (swiper.params.autoplay.waitForTransition) {
swiper.wrapperEl.addEventListener('transitionend', onTransitionEnd);
} else {
resume();
}
};
swiper.autoplay.paused = true;
if (reset) {
if (slideChanged) {
autoplayTimeLeft = swiper.params.autoplay.delay;
}
slideChanged = false;
proceed();
return;
}
const delay = autoplayTimeLeft || swiper.params.autoplay.delay;
autoplayTimeLeft = delay - (new Date().getTime() - autoplayStartTime);
if (swiper.isEnd && autoplayTimeLeft < 0 && !swiper.params.loop) return;
if (autoplayTimeLeft < 0) autoplayTimeLeft = 0;
proceed();
};
const resume = () => {
if (swiper.isEnd && autoplayTimeLeft < 0 && !swiper.params.loop || swiper.destroyed || !swiper.autoplay.running) return;
autoplayStartTime = new Date().getTime();
if (pausedByInteraction) {
pausedByInteraction = false;
run(autoplayTimeLeft);
} else {
run();
}
swiper.autoplay.paused = false;
emit('autoplayResume');
};
const onVisibilityChange = () => {
if (swiper.destroyed || !swiper.autoplay.running) return;
const document = ssr_window_esm_getDocument();
if (document.visibilityState === 'hidden') {
pausedByInteraction = true;
pause(true);
}
if (document.visibilityState === 'visible') {
resume();
}
};
const onPointerEnter = e => {
if (e.pointerType !== 'mouse') return;
pausedByInteraction = true;
pausedByPointerEnter = true;
if (swiper.animating || swiper.autoplay.paused) return;
pause(true);
};
const onPointerLeave = e => {
if (e.pointerType !== 'mouse') return;
pausedByPointerEnter = false;
if (swiper.autoplay.paused) {
resume();
}
};
const attachMouseEvents = () => {
if (swiper.params.autoplay.pauseOnMouseEnter) {
swiper.el.addEventListener('pointerenter', onPointerEnter);
swiper.el.addEventListener('pointerleave', onPointerLeave);
}
};
const detachMouseEvents = () => {
if (swiper.el && typeof swiper.el !== 'string') {
swiper.el.removeEventListener('pointerenter', onPointerEnter);
swiper.el.removeEventListener('pointerleave', onPointerLeave);
}
};
const attachDocumentEvents = () => {
const document = ssr_window_esm_getDocument();
document.addEventListener('visibilitychange', onVisibilityChange);
};
const detachDocumentEvents = () => {
const document = ssr_window_esm_getDocument();
document.removeEventListener('visibilitychange', onVisibilityChange);
};
on('init', () => {
if (swiper.params.autoplay.enabled) {
attachMouseEvents();
attachDocumentEvents();
start();
}
});
on('destroy', () => {
detachMouseEvents();
detachDocumentEvents();
if (swiper.autoplay.running) {
stop();
}
});
on('_freeModeStaticRelease', () => {
if (pausedByTouch || pausedByInteraction) {
resume();
}
});
on('_freeModeNoMomentumRelease', () => {
if (!swiper.params.autoplay.disableOnInteraction) {
pause(true, true);
} else {
stop();
}
});
on('beforeTransitionStart', (_s, speed, internal) => {
if (swiper.destroyed || !swiper.autoplay.running) return;
if (internal || !swiper.params.autoplay.disableOnInteraction) {
pause(true, true);
} else {
stop();
}
});
on('sliderFirstMove', () => {
if (swiper.destroyed || !swiper.autoplay.running) return;
if (swiper.params.autoplay.disableOnInteraction) {
stop();
return;
}
isTouched = true;
pausedByTouch = false;
pausedByInteraction = false;
touchStartTimeout = setTimeout(() => {
pausedByInteraction = true;
pausedByTouch = true;
pause(true);
}, 200);
});
on('touchEnd', () => {
if (swiper.destroyed || !swiper.autoplay.running || !isTouched) return;
clearTimeout(touchStartTimeout);
clearTimeout(timeout);
if (swiper.params.autoplay.disableOnInteraction) {
pausedByTouch = false;
isTouched = false;
return;
}
if (pausedByTouch && swiper.params.cssMode) resume();
pausedByTouch = false;
isTouched = false;
});
on('slideChange', () => {
if (swiper.destroyed || !swiper.autoplay.running) return;
slideChanged = true;
});
Object.assign(swiper.autoplay, {
start,
stop,
pause,
resume
});
}
;// ./node_modules/swiper/modules/thumbs.mjs
function Thumb(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
thumbs: {
swiper: null,
multipleActiveThumbs: true,
autoScrollOffset: 0,
slideThumbActiveClass: 'swiper-slide-thumb-active',
thumbsContainerClass: 'swiper-thumbs'
}
});
let initialized = false;
let swiperCreated = false;
swiper.thumbs = {
swiper: null
};
function onThumbClick() {
const thumbsSwiper = swiper.thumbs.swiper;
if (!thumbsSwiper || thumbsSwiper.destroyed) return;
const clickedIndex = thumbsSwiper.clickedIndex;
const clickedSlide = thumbsSwiper.clickedSlide;
if (clickedSlide && clickedSlide.classList.contains(swiper.params.thumbs.slideThumbActiveClass)) return;
if (typeof clickedIndex === 'undefined' || clickedIndex === null) return;
let slideToIndex;
if (thumbsSwiper.params.loop) {
slideToIndex = parseInt(thumbsSwiper.clickedSlide.getAttribute('data-swiper-slide-index'), 10);
} else {
slideToIndex = clickedIndex;
}
if (swiper.params.loop) {
swiper.slideToLoop(slideToIndex);
} else {
swiper.slideTo(slideToIndex);
}
}
function init() {
const {
thumbs: thumbsParams
} = swiper.params;
if (initialized) return false;
initialized = true;
const SwiperClass = swiper.constructor;
if (thumbsParams.swiper instanceof SwiperClass) {
if (thumbsParams.swiper.destroyed) {
initialized = false;
return false;
}
swiper.thumbs.swiper = thumbsParams.swiper;
Object.assign(swiper.thumbs.swiper.originalParams, {
watchSlidesProgress: true,
slideToClickedSlide: false
});
Object.assign(swiper.thumbs.swiper.params, {
watchSlidesProgress: true,
slideToClickedSlide: false
});
swiper.thumbs.swiper.update();
} else if (isObject(thumbsParams.swiper)) {
const thumbsSwiperParams = Object.assign({}, thumbsParams.swiper);
Object.assign(thumbsSwiperParams, {
watchSlidesProgress: true,
slideToClickedSlide: false
});
swiper.thumbs.swiper = new SwiperClass(thumbsSwiperParams);
swiperCreated = true;
}
swiper.thumbs.swiper.el.classList.add(swiper.params.thumbs.thumbsContainerClass);
swiper.thumbs.swiper.on('tap', onThumbClick);
return true;
}
function update(initial) {
const thumbsSwiper = swiper.thumbs.swiper;
if (!thumbsSwiper || thumbsSwiper.destroyed) return;
const slidesPerView = thumbsSwiper.params.slidesPerView === 'auto' ? thumbsSwiper.slidesPerViewDynamic() : thumbsSwiper.params.slidesPerView;
// Activate thumbs
let thumbsToActivate = 1;
const thumbActiveClass = swiper.params.thumbs.slideThumbActiveClass;
if (swiper.params.slidesPerView > 1 && !swiper.params.centeredSlides) {
thumbsToActivate = swiper.params.slidesPerView;
}
if (!swiper.params.thumbs.multipleActiveThumbs) {
thumbsToActivate = 1;
}
thumbsToActivate = Math.floor(thumbsToActivate);
thumbsSwiper.slides.forEach(slideEl => slideEl.classList.remove(thumbActiveClass));
if (thumbsSwiper.params.loop || thumbsSwiper.params.virtual && thumbsSwiper.params.virtual.enabled) {
for (let i = 0; i < thumbsToActivate; i += 1) {
elementChildren(thumbsSwiper.slidesEl, `[data-swiper-slide-index="${swiper.realIndex + i}"]`).forEach(slideEl => {
slideEl.classList.add(thumbActiveClass);
});
}
} else {
for (let i = 0; i < thumbsToActivate; i += 1) {
if (thumbsSwiper.slides[swiper.realIndex + i]) {
thumbsSwiper.slides[swiper.realIndex + i].classList.add(thumbActiveClass);
}
}
}
const autoScrollOffset = swiper.params.thumbs.autoScrollOffset;
const useOffset = autoScrollOffset && !thumbsSwiper.params.loop;
if (swiper.realIndex !== thumbsSwiper.realIndex || useOffset) {
const currentThumbsIndex = thumbsSwiper.activeIndex;
let newThumbsIndex;
let direction;
if (thumbsSwiper.params.loop) {
const newThumbsSlide = thumbsSwiper.slides.find(slideEl => slideEl.getAttribute('data-swiper-slide-index') === `${swiper.realIndex}`);
newThumbsIndex = thumbsSwiper.slides.indexOf(newThumbsSlide);
direction = swiper.activeIndex > swiper.previousIndex ? 'next' : 'prev';
} else {
newThumbsIndex = swiper.realIndex;
direction = newThumbsIndex > swiper.previousIndex ? 'next' : 'prev';
}
if (useOffset) {
newThumbsIndex += direction === 'next' ? autoScrollOffset : -1 * autoScrollOffset;
}
if (thumbsSwiper.visibleSlidesIndexes && thumbsSwiper.visibleSlidesIndexes.indexOf(newThumbsIndex) < 0) {
if (thumbsSwiper.params.centeredSlides) {
if (newThumbsIndex > currentThumbsIndex) {
newThumbsIndex = newThumbsIndex - Math.floor(slidesPerView / 2) + 1;
} else {
newThumbsIndex = newThumbsIndex + Math.floor(slidesPerView / 2) - 1;
}
} else if (newThumbsIndex > currentThumbsIndex && thumbsSwiper.params.slidesPerGroup === 1) ;
thumbsSwiper.slideTo(newThumbsIndex, initial ? 0 : undefined);
}
}
}
on('beforeInit', () => {
const {
thumbs
} = swiper.params;
if (!thumbs || !thumbs.swiper) return;
if (typeof thumbs.swiper === 'string' || thumbs.swiper instanceof HTMLElement) {
const document = getDocument();
const getThumbsElementAndInit = () => {
const thumbsElement = typeof thumbs.swiper === 'string' ? document.querySelector(thumbs.swiper) : thumbs.swiper;
if (thumbsElement && thumbsElement.swiper) {
thumbs.swiper = thumbsElement.swiper;
init();
update(true);
} else if (thumbsElement) {
const eventName = `${swiper.params.eventsPrefix}init`;
const onThumbsSwiper = e => {
thumbs.swiper = e.detail[0];
thumbsElement.removeEventListener(eventName, onThumbsSwiper);
init();
update(true);
thumbs.swiper.update();
swiper.update();
};
thumbsElement.addEventListener(eventName, onThumbsSwiper);
}
return thumbsElement;
};
const watchForThumbsToAppear = () => {
if (swiper.destroyed) return;
const thumbsElement = getThumbsElementAndInit();
if (!thumbsElement) {
requestAnimationFrame(watchForThumbsToAppear);
}
};
requestAnimationFrame(watchForThumbsToAppear);
} else {
init();
update(true);
}
});
on('slideChange update resize observerUpdate', () => {
update();
});
on('setTransition', (_s, duration) => {
const thumbsSwiper = swiper.thumbs.swiper;
if (!thumbsSwiper || thumbsSwiper.destroyed) return;
thumbsSwiper.setTransition(duration);
});
on('beforeDestroy', () => {
const thumbsSwiper = swiper.thumbs.swiper;
if (!thumbsSwiper || thumbsSwiper.destroyed) return;
if (swiperCreated) {
thumbsSwiper.destroy();
}
});
Object.assign(swiper.thumbs, {
init,
update
});
}
;// ./node_modules/swiper/modules/free-mode.mjs
function freeMode(_ref) {
let {
swiper,
extendParams,
emit,
once
} = _ref;
extendParams({
freeMode: {
enabled: false,
momentum: true,
momentumRatio: 1,
momentumBounce: true,
momentumBounceRatio: 1,
momentumVelocityRatio: 1,
sticky: false,
minimumVelocity: 0.02
}
});
function onTouchStart() {
if (swiper.params.cssMode) return;
const translate = swiper.getTranslate();
swiper.setTranslate(translate);
swiper.setTransition(0);
swiper.touchEventsData.velocities.length = 0;
swiper.freeMode.onTouchEnd({
currentPos: swiper.rtl ? swiper.translate : -swiper.translate
});
}
function onTouchMove() {
if (swiper.params.cssMode) return;
const {
touchEventsData: data,
touches
} = swiper;
// Velocity
if (data.velocities.length === 0) {
data.velocities.push({
position: touches[swiper.isHorizontal() ? 'startX' : 'startY'],
time: data.touchStartTime
});
}
data.velocities.push({
position: touches[swiper.isHorizontal() ? 'currentX' : 'currentY'],
time: now()
});
}
function onTouchEnd(_ref2) {
let {
currentPos
} = _ref2;
if (swiper.params.cssMode) return;
const {
params,
wrapperEl,
rtlTranslate: rtl,
snapGrid,
touchEventsData: data
} = swiper;
// Time diff
const touchEndTime = now();
const timeDiff = touchEndTime - data.touchStartTime;
if (currentPos < -swiper.minTranslate()) {
swiper.slideTo(swiper.activeIndex);
return;
}
if (currentPos > -swiper.maxTranslate()) {
if (swiper.slides.length < snapGrid.length) {
swiper.slideTo(snapGrid.length - 1);
} else {
swiper.slideTo(swiper.slides.length - 1);
}
return;
}
if (params.freeMode.momentum) {
if (data.velocities.length > 1) {
const lastMoveEvent = data.velocities.pop();
const velocityEvent = data.velocities.pop();
const distance = lastMoveEvent.position - velocityEvent.position;
const time = lastMoveEvent.time - velocityEvent.time;
swiper.velocity = distance / time;
swiper.velocity /= 2;
if (Math.abs(swiper.velocity) < params.freeMode.minimumVelocity) {
swiper.velocity = 0;
}
// this implies that the user stopped moving a finger then released.
// There would be no events with distance zero, so the last event is stale.
if (time > 150 || now() - lastMoveEvent.time > 300) {
swiper.velocity = 0;
}
} else {
swiper.velocity = 0;
}
swiper.velocity *= params.freeMode.momentumVelocityRatio;
data.velocities.length = 0;
let momentumDuration = 1000 * params.freeMode.momentumRatio;
const momentumDistance = swiper.velocity * momentumDuration;
let newPosition = swiper.translate + momentumDistance;
if (rtl) newPosition = -newPosition;
let doBounce = false;
let afterBouncePosition;
const bounceAmount = Math.abs(swiper.velocity) * 20 * params.freeMode.momentumBounceRatio;
let needsLoopFix;
if (newPosition < swiper.maxTranslate()) {
if (params.freeMode.momentumBounce) {
if (newPosition + swiper.maxTranslate() < -bounceAmount) {
newPosition = swiper.maxTranslate() - bounceAmount;
}
afterBouncePosition = swiper.maxTranslate();
doBounce = true;
data.allowMomentumBounce = true;
} else {
newPosition = swiper.maxTranslate();
}
if (params.loop && params.centeredSlides) needsLoopFix = true;
} else if (newPosition > swiper.minTranslate()) {
if (params.freeMode.momentumBounce) {
if (newPosition - swiper.minTranslate() > bounceAmount) {
newPosition = swiper.minTranslate() + bounceAmount;
}
afterBouncePosition = swiper.minTranslate();
doBounce = true;
data.allowMomentumBounce = true;
} else {
newPosition = swiper.minTranslate();
}
if (params.loop && params.centeredSlides) needsLoopFix = true;
} else if (params.freeMode.sticky) {
let nextSlide;
for (let j = 0; j < snapGrid.length; j += 1) {
if (snapGrid[j] > -newPosition) {
nextSlide = j;
break;
}
}
if (Math.abs(snapGrid[nextSlide] - newPosition) < Math.abs(snapGrid[nextSlide - 1] - newPosition) || swiper.swipeDirection === 'next') {
newPosition = snapGrid[nextSlide];
} else {
newPosition = snapGrid[nextSlide - 1];
}
newPosition = -newPosition;
}
if (needsLoopFix) {
once('transitionEnd', () => {
swiper.loopFix();
});
}
// Fix duration
if (swiper.velocity !== 0) {
if (rtl) {
momentumDuration = Math.abs((-newPosition - swiper.translate) / swiper.velocity);
} else {
momentumDuration = Math.abs((newPosition - swiper.translate) / swiper.velocity);
}
if (params.freeMode.sticky) {
// If freeMode.sticky is active and the user ends a swipe with a slow-velocity
// event, then durations can be 20+ seconds to slide one (or zero!) slides.
// It's easy to see this when simulating touch with mouse events. To fix this,
// limit single-slide swipes to the default slide duration. This also has the
// nice side effect of matching slide speed if the user stopped moving before
// lifting finger or mouse vs. moving slowly before lifting the finger/mouse.
// For faster swipes, also apply limits (albeit higher ones).
const moveDistance = Math.abs((rtl ? -newPosition : newPosition) - swiper.translate);
const currentSlideSize = swiper.slidesSizesGrid[swiper.activeIndex];
if (moveDistance < currentSlideSize) {
momentumDuration = params.speed;
} else if (moveDistance < 2 * currentSlideSize) {
momentumDuration = params.speed * 1.5;
} else {
momentumDuration = params.speed * 2.5;
}
}
} else if (params.freeMode.sticky) {
swiper.slideToClosest();
return;
}
if (params.freeMode.momentumBounce && doBounce) {
swiper.updateProgress(afterBouncePosition);
swiper.setTransition(momentumDuration);
swiper.setTranslate(newPosition);
swiper.transitionStart(true, swiper.swipeDirection);
swiper.animating = true;
elementTransitionEnd(wrapperEl, () => {
if (!swiper || swiper.destroyed || !data.allowMomentumBounce) return;
emit('momentumBounce');
swiper.setTransition(params.speed);
setTimeout(() => {
swiper.setTranslate(afterBouncePosition);
elementTransitionEnd(wrapperEl, () => {
if (!swiper || swiper.destroyed) return;
swiper.transitionEnd();
});
}, 0);
});
} else if (swiper.velocity) {
emit('_freeModeNoMomentumRelease');
swiper.updateProgress(newPosition);
swiper.setTransition(momentumDuration);
swiper.setTranslate(newPosition);
swiper.transitionStart(true, swiper.swipeDirection);
if (!swiper.animating) {
swiper.animating = true;
elementTransitionEnd(wrapperEl, () => {
if (!swiper || swiper.destroyed) return;
swiper.transitionEnd();
});
}
} else {
swiper.updateProgress(newPosition);
}
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
} else if (params.freeMode.sticky) {
swiper.slideToClosest();
return;
} else if (params.freeMode) {
emit('_freeModeNoMomentumRelease');
}
if (!params.freeMode.momentum || timeDiff >= params.longSwipesMs) {
emit('_freeModeStaticRelease');
swiper.updateProgress();
swiper.updateActiveIndex();
swiper.updateSlidesClasses();
}
}
Object.assign(swiper, {
freeMode: {
onTouchStart,
onTouchMove,
onTouchEnd
}
});
}
;// ./node_modules/swiper/modules/manipulation.mjs
function appendSlide(slides) {
const swiper = this;
const {
params,
slidesEl
} = swiper;
if (params.loop) {
swiper.loopDestroy();
}
const appendElement = slideEl => {
if (typeof slideEl === 'string') {
const tempDOM = document.createElement('div');
setInnerHTML(tempDOM, slideEl);
slidesEl.append(tempDOM.children[0]);
setInnerHTML(tempDOM, '');
} else {
slidesEl.append(slideEl);
}
};
if (typeof slides === 'object' && 'length' in slides) {
for (let i = 0; i < slides.length; i += 1) {
if (slides[i]) appendElement(slides[i]);
}
} else {
appendElement(slides);
}
swiper.recalcSlides();
if (params.loop) {
swiper.loopCreate();
}
if (!params.observer || swiper.isElement) {
swiper.update();
}
}
function prependSlide(slides) {
const swiper = this;
const {
params,
activeIndex,
slidesEl
} = swiper;
if (params.loop) {
swiper.loopDestroy();
}
let newActiveIndex = activeIndex + 1;
const prependElement = slideEl => {
if (typeof slideEl === 'string') {
const tempDOM = document.createElement('div');
setInnerHTML(tempDOM, slideEl);
slidesEl.prepend(tempDOM.children[0]);
setInnerHTML(tempDOM, '');
} else {
slidesEl.prepend(slideEl);
}
};
if (typeof slides === 'object' && 'length' in slides) {
for (let i = 0; i < slides.length; i += 1) {
if (slides[i]) prependElement(slides[i]);
}
newActiveIndex = activeIndex + slides.length;
} else {
prependElement(slides);
}
swiper.recalcSlides();
if (params.loop) {
swiper.loopCreate();
}
if (!params.observer || swiper.isElement) {
swiper.update();
}
swiper.slideTo(newActiveIndex, 0, false);
}
function addSlide(index, slides) {
const swiper = this;
const {
params,
activeIndex,
slidesEl
} = swiper;
let activeIndexBuffer = activeIndex;
if (params.loop) {
activeIndexBuffer -= swiper.loopedSlides;
swiper.loopDestroy();
swiper.recalcSlides();
}
const baseLength = swiper.slides.length;
if (index <= 0) {
swiper.prependSlide(slides);
return;
}
if (index >= baseLength) {
swiper.appendSlide(slides);
return;
}
let newActiveIndex = activeIndexBuffer > index ? activeIndexBuffer + 1 : activeIndexBuffer;
const slidesBuffer = [];
for (let i = baseLength - 1; i >= index; i -= 1) {
const currentSlide = swiper.slides[i];
currentSlide.remove();
slidesBuffer.unshift(currentSlide);
}
if (typeof slides === 'object' && 'length' in slides) {
for (let i = 0; i < slides.length; i += 1) {
if (slides[i]) slidesEl.append(slides[i]);
}
newActiveIndex = activeIndexBuffer > index ? activeIndexBuffer + slides.length : activeIndexBuffer;
} else {
slidesEl.append(slides);
}
for (let i = 0; i < slidesBuffer.length; i += 1) {
slidesEl.append(slidesBuffer[i]);
}
swiper.recalcSlides();
if (params.loop) {
swiper.loopCreate();
}
if (!params.observer || swiper.isElement) {
swiper.update();
}
if (params.loop) {
swiper.slideTo(newActiveIndex + swiper.loopedSlides, 0, false);
} else {
swiper.slideTo(newActiveIndex, 0, false);
}
}
function removeSlide(slidesIndexes) {
const swiper = this;
const {
params,
activeIndex
} = swiper;
let activeIndexBuffer = activeIndex;
if (params.loop) {
activeIndexBuffer -= swiper.loopedSlides;
swiper.loopDestroy();
}
let newActiveIndex = activeIndexBuffer;
let indexToRemove;
if (typeof slidesIndexes === 'object' && 'length' in slidesIndexes) {
for (let i = 0; i < slidesIndexes.length; i += 1) {
indexToRemove = slidesIndexes[i];
if (swiper.slides[indexToRemove]) swiper.slides[indexToRemove].remove();
if (indexToRemove < newActiveIndex) newActiveIndex -= 1;
}
newActiveIndex = Math.max(newActiveIndex, 0);
} else {
indexToRemove = slidesIndexes;
if (swiper.slides[indexToRemove]) swiper.slides[indexToRemove].remove();
if (indexToRemove < newActiveIndex) newActiveIndex -= 1;
newActiveIndex = Math.max(newActiveIndex, 0);
}
swiper.recalcSlides();
if (params.loop) {
swiper.loopCreate();
}
if (!params.observer || swiper.isElement) {
swiper.update();
}
if (params.loop) {
swiper.slideTo(newActiveIndex + swiper.loopedSlides, 0, false);
} else {
swiper.slideTo(newActiveIndex, 0, false);
}
}
function removeAllSlides() {
const swiper = this;
const slidesIndexes = [];
for (let i = 0; i < swiper.slides.length; i += 1) {
slidesIndexes.push(i);
}
swiper.removeSlide(slidesIndexes);
}
function Manipulation(_ref) {
let {
swiper
} = _ref;
Object.assign(swiper, {
appendSlide: appendSlide.bind(swiper),
prependSlide: prependSlide.bind(swiper),
addSlide: addSlide.bind(swiper),
removeSlide: removeSlide.bind(swiper),
removeAllSlides: removeAllSlides.bind(swiper)
});
}
;// ./node_modules/swiper/shared/effect-init.mjs
function effect_init_effectInit(params) {
const {
effect,
swiper,
on,
setTranslate,
setTransition,
overwriteParams,
perspective,
recreateShadows,
getEffectParams
} = params;
on('beforeInit', () => {
if (swiper.params.effect !== effect) return;
swiper.classNames.push(`${swiper.params.containerModifierClass}${effect}`);
if (perspective && perspective()) {
swiper.classNames.push(`${swiper.params.containerModifierClass}3d`);
}
const overwriteParamsResult = overwriteParams ? overwriteParams() : {};
Object.assign(swiper.params, overwriteParamsResult);
Object.assign(swiper.originalParams, overwriteParamsResult);
});
on('setTranslate _virtualUpdated', () => {
if (swiper.params.effect !== effect) return;
setTranslate();
});
on('setTransition', (_s, duration) => {
if (swiper.params.effect !== effect) return;
setTransition(duration);
});
on('transitionEnd', () => {
if (swiper.params.effect !== effect) return;
if (recreateShadows) {
if (!getEffectParams || !getEffectParams().slideShadows) return;
// remove shadows
swiper.slides.forEach(slideEl => {
slideEl.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(shadowEl => shadowEl.remove());
});
// create new one
recreateShadows();
}
});
let requireUpdateOnVirtual;
on('virtualUpdate', () => {
if (swiper.params.effect !== effect) return;
if (!swiper.slides.length) {
requireUpdateOnVirtual = true;
}
requestAnimationFrame(() => {
if (requireUpdateOnVirtual && swiper.slides && swiper.slides.length) {
setTranslate();
requireUpdateOnVirtual = false;
}
});
});
}
;// ./node_modules/swiper/shared/effect-target.mjs
function effect_target_effectTarget(effectParams, slideEl) {
const transformEl = utils_getSlideTransformEl(slideEl);
if (transformEl !== slideEl) {
transformEl.style.backfaceVisibility = 'hidden';
transformEl.style['-webkit-backface-visibility'] = 'hidden';
}
return transformEl;
}
;// ./node_modules/swiper/shared/effect-virtual-transition-end.mjs
function effect_virtual_transition_end_effectVirtualTransitionEnd(_ref) {
let {
swiper,
duration,
transformElements,
allSlides
} = _ref;
const {
activeIndex
} = swiper;
const getSlide = el => {
if (!el.parentElement) {
// assume shadow root
const slide = swiper.slides.find(slideEl => slideEl.shadowRoot && slideEl.shadowRoot === el.parentNode);
return slide;
}
return el.parentElement;
};
if (swiper.params.virtualTranslate && duration !== 0) {
let eventTriggered = false;
let transitionEndTarget;
if (allSlides) {
transitionEndTarget = transformElements;
} else {
transitionEndTarget = transformElements.filter(transformEl => {
const el = transformEl.classList.contains('swiper-slide-transform') ? getSlide(transformEl) : transformEl;
return swiper.getSlideIndex(el) === activeIndex;
});
}
transitionEndTarget.forEach(el => {
utils_elementTransitionEnd(el, () => {
if (eventTriggered) return;
if (!swiper || swiper.destroyed) return;
eventTriggered = true;
swiper.animating = false;
const evt = new window.CustomEvent('transitionend', {
bubbles: true,
cancelable: true
});
swiper.wrapperEl.dispatchEvent(evt);
});
});
}
}
;// ./node_modules/swiper/modules/effect-fade.mjs
function EffectFade(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
fadeEffect: {
crossFade: false
}
});
const setTranslate = () => {
const {
slides
} = swiper;
const params = swiper.params.fadeEffect;
for (let i = 0; i < slides.length; i += 1) {
const slideEl = swiper.slides[i];
const offset = slideEl.swiperSlideOffset;
let tx = -offset;
if (!swiper.params.virtualTranslate) tx -= swiper.translate;
let ty = 0;
if (!swiper.isHorizontal()) {
ty = tx;
tx = 0;
}
const slideOpacity = swiper.params.fadeEffect.crossFade ? Math.max(1 - Math.abs(slideEl.progress), 0) : 1 + Math.min(Math.max(slideEl.progress, -1), 0);
const targetEl = effect_target_effectTarget(params, slideEl);
targetEl.style.opacity = slideOpacity;
targetEl.style.transform = `translate3d(${tx}px, ${ty}px, 0px)`;
}
};
const setTransition = duration => {
const transformElements = swiper.slides.map(slideEl => utils_getSlideTransformEl(slideEl));
transformElements.forEach(el => {
el.style.transitionDuration = `${duration}ms`;
});
effect_virtual_transition_end_effectVirtualTransitionEnd({
swiper,
duration,
transformElements,
allSlides: true
});
};
effect_init_effectInit({
effect: 'fade',
swiper,
on,
setTranslate,
setTransition,
overwriteParams: () => ({
slidesPerView: 1,
slidesPerGroup: 1,
watchSlidesProgress: true,
spaceBetween: 0,
virtualTranslate: !swiper.params.cssMode
})
});
}
;// ./node_modules/swiper/modules/effect-cube.mjs
function EffectCube(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
cubeEffect: {
slideShadows: true,
shadow: true,
shadowOffset: 20,
shadowScale: 0.94
}
});
const createSlideShadows = (slideEl, progress, isHorizontal) => {
let shadowBefore = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-left') : slideEl.querySelector('.swiper-slide-shadow-top');
let shadowAfter = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-right') : slideEl.querySelector('.swiper-slide-shadow-bottom');
if (!shadowBefore) {
shadowBefore = createElement('div', `swiper-slide-shadow-cube swiper-slide-shadow-${isHorizontal ? 'left' : 'top'}`.split(' '));
slideEl.append(shadowBefore);
}
if (!shadowAfter) {
shadowAfter = createElement('div', `swiper-slide-shadow-cube swiper-slide-shadow-${isHorizontal ? 'right' : 'bottom'}`.split(' '));
slideEl.append(shadowAfter);
}
if (shadowBefore) shadowBefore.style.opacity = Math.max(-progress, 0);
if (shadowAfter) shadowAfter.style.opacity = Math.max(progress, 0);
};
const recreateShadows = () => {
// create new ones
const isHorizontal = swiper.isHorizontal();
swiper.slides.forEach(slideEl => {
const progress = Math.max(Math.min(slideEl.progress, 1), -1);
createSlideShadows(slideEl, progress, isHorizontal);
});
};
const setTranslate = () => {
const {
el,
wrapperEl,
slides,
width: swiperWidth,
height: swiperHeight,
rtlTranslate: rtl,
size: swiperSize,
browser
} = swiper;
const r = getRotateFix(swiper);
const params = swiper.params.cubeEffect;
const isHorizontal = swiper.isHorizontal();
const isVirtual = swiper.virtual && swiper.params.virtual.enabled;
let wrapperRotate = 0;
let cubeShadowEl;
if (params.shadow) {
if (isHorizontal) {
cubeShadowEl = swiper.wrapperEl.querySelector('.swiper-cube-shadow');
if (!cubeShadowEl) {
cubeShadowEl = createElement('div', 'swiper-cube-shadow');
swiper.wrapperEl.append(cubeShadowEl);
}
cubeShadowEl.style.height = `${swiperWidth}px`;
} else {
cubeShadowEl = el.querySelector('.swiper-cube-shadow');
if (!cubeShadowEl) {
cubeShadowEl = createElement('div', 'swiper-cube-shadow');
el.append(cubeShadowEl);
}
}
}
for (let i = 0; i < slides.length; i += 1) {
const slideEl = slides[i];
let slideIndex = i;
if (isVirtual) {
slideIndex = parseInt(slideEl.getAttribute('data-swiper-slide-index'), 10);
}
let slideAngle = slideIndex * 90;
let round = Math.floor(slideAngle / 360);
if (rtl) {
slideAngle = -slideAngle;
round = Math.floor(-slideAngle / 360);
}
const progress = Math.max(Math.min(slideEl.progress, 1), -1);
let tx = 0;
let ty = 0;
let tz = 0;
if (slideIndex % 4 === 0) {
tx = -round * 4 * swiperSize;
tz = 0;
} else if ((slideIndex - 1) % 4 === 0) {
tx = 0;
tz = -round * 4 * swiperSize;
} else if ((slideIndex - 2) % 4 === 0) {
tx = swiperSize + round * 4 * swiperSize;
tz = swiperSize;
} else if ((slideIndex - 3) % 4 === 0) {
tx = -swiperSize;
tz = 3 * swiperSize + swiperSize * 4 * round;
}
if (rtl) {
tx = -tx;
}
if (!isHorizontal) {
ty = tx;
tx = 0;
}
const transform = `rotateX(${r(isHorizontal ? 0 : -slideAngle)}deg) rotateY(${r(isHorizontal ? slideAngle : 0)}deg) translate3d(${tx}px, ${ty}px, ${tz}px)`;
if (progress <= 1 && progress > -1) {
wrapperRotate = slideIndex * 90 + progress * 90;
if (rtl) wrapperRotate = -slideIndex * 90 - progress * 90;
}
slideEl.style.transform = transform;
if (params.slideShadows) {
createSlideShadows(slideEl, progress, isHorizontal);
}
}
wrapperEl.style.transformOrigin = `50% 50% -${swiperSize / 2}px`;
wrapperEl.style['-webkit-transform-origin'] = `50% 50% -${swiperSize / 2}px`;
if (params.shadow) {
if (isHorizontal) {
cubeShadowEl.style.transform = `translate3d(0px, ${swiperWidth / 2 + params.shadowOffset}px, ${-swiperWidth / 2}px) rotateX(89.99deg) rotateZ(0deg) scale(${params.shadowScale})`;
} else {
const shadowAngle = Math.abs(wrapperRotate) - Math.floor(Math.abs(wrapperRotate) / 90) * 90;
const multiplier = 1.5 - (Math.sin(shadowAngle * 2 * Math.PI / 360) / 2 + Math.cos(shadowAngle * 2 * Math.PI / 360) / 2);
const scale1 = params.shadowScale;
const scale2 = params.shadowScale / multiplier;
const offset = params.shadowOffset;
cubeShadowEl.style.transform = `scale3d(${scale1}, 1, ${scale2}) translate3d(0px, ${swiperHeight / 2 + offset}px, ${-swiperHeight / 2 / scale2}px) rotateX(-89.99deg)`;
}
}
const zFactor = (browser.isSafari || browser.isWebView) && browser.needPerspectiveFix ? -swiperSize / 2 : 0;
wrapperEl.style.transform = `translate3d(0px,0,${zFactor}px) rotateX(${r(swiper.isHorizontal() ? 0 : wrapperRotate)}deg) rotateY(${r(swiper.isHorizontal() ? -wrapperRotate : 0)}deg)`;
wrapperEl.style.setProperty('--swiper-cube-translate-z', `${zFactor}px`);
};
const setTransition = duration => {
const {
el,
slides
} = swiper;
slides.forEach(slideEl => {
slideEl.style.transitionDuration = `${duration}ms`;
slideEl.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(subEl => {
subEl.style.transitionDuration = `${duration}ms`;
});
});
if (swiper.params.cubeEffect.shadow && !swiper.isHorizontal()) {
const shadowEl = el.querySelector('.swiper-cube-shadow');
if (shadowEl) shadowEl.style.transitionDuration = `${duration}ms`;
}
};
effectInit({
effect: 'cube',
swiper,
on,
setTranslate,
setTransition,
recreateShadows,
getEffectParams: () => swiper.params.cubeEffect,
perspective: () => true,
overwriteParams: () => ({
slidesPerView: 1,
slidesPerGroup: 1,
watchSlidesProgress: true,
resistanceRatio: 0,
spaceBetween: 0,
centeredSlides: false,
virtualTranslate: true
})
});
}
;// ./node_modules/swiper/shared/create-shadow.mjs
function create_shadow_createShadow(suffix, slideEl, side) {
const shadowClass = `swiper-slide-shadow${side ? `-${side}` : ''}${suffix ? ` swiper-slide-shadow-${suffix}` : ''}`;
const shadowContainer = getSlideTransformEl(slideEl);
let shadowEl = shadowContainer.querySelector(`.${shadowClass.split(' ').join('.')}`);
if (!shadowEl) {
shadowEl = createElement('div', shadowClass.split(' '));
shadowContainer.append(shadowEl);
}
return shadowEl;
}
;// ./node_modules/swiper/modules/effect-flip.mjs
function EffectFlip(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
flipEffect: {
slideShadows: true,
limitRotation: true
}
});
const createSlideShadows = (slideEl, progress) => {
let shadowBefore = swiper.isHorizontal() ? slideEl.querySelector('.swiper-slide-shadow-left') : slideEl.querySelector('.swiper-slide-shadow-top');
let shadowAfter = swiper.isHorizontal() ? slideEl.querySelector('.swiper-slide-shadow-right') : slideEl.querySelector('.swiper-slide-shadow-bottom');
if (!shadowBefore) {
shadowBefore = createShadow('flip', slideEl, swiper.isHorizontal() ? 'left' : 'top');
}
if (!shadowAfter) {
shadowAfter = createShadow('flip', slideEl, swiper.isHorizontal() ? 'right' : 'bottom');
}
if (shadowBefore) shadowBefore.style.opacity = Math.max(-progress, 0);
if (shadowAfter) shadowAfter.style.opacity = Math.max(progress, 0);
};
const recreateShadows = () => {
// Set shadows
swiper.params.flipEffect;
swiper.slides.forEach(slideEl => {
let progress = slideEl.progress;
if (swiper.params.flipEffect.limitRotation) {
progress = Math.max(Math.min(slideEl.progress, 1), -1);
}
createSlideShadows(slideEl, progress);
});
};
const setTranslate = () => {
const {
slides,
rtlTranslate: rtl
} = swiper;
const params = swiper.params.flipEffect;
const rotateFix = getRotateFix(swiper);
for (let i = 0; i < slides.length; i += 1) {
const slideEl = slides[i];
let progress = slideEl.progress;
if (swiper.params.flipEffect.limitRotation) {
progress = Math.max(Math.min(slideEl.progress, 1), -1);
}
const offset = slideEl.swiperSlideOffset;
const rotate = -180 * progress;
let rotateY = rotate;
let rotateX = 0;
let tx = swiper.params.cssMode ? -offset - swiper.translate : -offset;
let ty = 0;
if (!swiper.isHorizontal()) {
ty = tx;
tx = 0;
rotateX = -rotateY;
rotateY = 0;
} else if (rtl) {
rotateY = -rotateY;
}
slideEl.style.zIndex = -Math.abs(Math.round(progress)) + slides.length;
if (params.slideShadows) {
createSlideShadows(slideEl, progress);
}
const transform = `translate3d(${tx}px, ${ty}px, 0px) rotateX(${rotateFix(rotateX)}deg) rotateY(${rotateFix(rotateY)}deg)`;
const targetEl = effectTarget(params, slideEl);
targetEl.style.transform = transform;
}
};
const setTransition = duration => {
const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl));
transformElements.forEach(el => {
el.style.transitionDuration = `${duration}ms`;
el.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(shadowEl => {
shadowEl.style.transitionDuration = `${duration}ms`;
});
});
effectVirtualTransitionEnd({
swiper,
duration,
transformElements
});
};
effectInit({
effect: 'flip',
swiper,
on,
setTranslate,
setTransition,
recreateShadows,
getEffectParams: () => swiper.params.flipEffect,
perspective: () => true,
overwriteParams: () => ({
slidesPerView: 1,
slidesPerGroup: 1,
watchSlidesProgress: true,
spaceBetween: 0,
virtualTranslate: !swiper.params.cssMode
})
});
}
;// ./node_modules/swiper/modules/effect-coverflow.mjs
function EffectCoverflow(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
coverflowEffect: {
rotate: 50,
stretch: 0,
depth: 100,
scale: 1,
modifier: 1,
slideShadows: true
}
});
const setTranslate = () => {
const {
width: swiperWidth,
height: swiperHeight,
slides,
slidesSizesGrid
} = swiper;
const params = swiper.params.coverflowEffect;
const isHorizontal = swiper.isHorizontal();
const transform = swiper.translate;
const center = isHorizontal ? -transform + swiperWidth / 2 : -transform + swiperHeight / 2;
const rotate = isHorizontal ? params.rotate : -params.rotate;
const translate = params.depth;
const r = getRotateFix(swiper);
// Each slide offset from center
for (let i = 0, length = slides.length; i < length; i += 1) {
const slideEl = slides[i];
const slideSize = slidesSizesGrid[i];
const slideOffset = slideEl.swiperSlideOffset;
const centerOffset = (center - slideOffset - slideSize / 2) / slideSize;
const offsetMultiplier = typeof params.modifier === 'function' ? params.modifier(centerOffset) : centerOffset * params.modifier;
let rotateY = isHorizontal ? rotate * offsetMultiplier : 0;
let rotateX = isHorizontal ? 0 : rotate * offsetMultiplier;
// var rotateZ = 0
let translateZ = -translate * Math.abs(offsetMultiplier);
let stretch = params.stretch;
// Allow percentage to make a relative stretch for responsive sliders
if (typeof stretch === 'string' && stretch.indexOf('%') !== -1) {
stretch = parseFloat(params.stretch) / 100 * slideSize;
}
let translateY = isHorizontal ? 0 : stretch * offsetMultiplier;
let translateX = isHorizontal ? stretch * offsetMultiplier : 0;
let scale = 1 - (1 - params.scale) * Math.abs(offsetMultiplier);
// Fix for ultra small values
if (Math.abs(translateX) < 0.001) translateX = 0;
if (Math.abs(translateY) < 0.001) translateY = 0;
if (Math.abs(translateZ) < 0.001) translateZ = 0;
if (Math.abs(rotateY) < 0.001) rotateY = 0;
if (Math.abs(rotateX) < 0.001) rotateX = 0;
if (Math.abs(scale) < 0.001) scale = 0;
const slideTransform = `translate3d(${translateX}px,${translateY}px,${translateZ}px) rotateX(${r(rotateX)}deg) rotateY(${r(rotateY)}deg) scale(${scale})`;
const targetEl = effectTarget(params, slideEl);
targetEl.style.transform = slideTransform;
slideEl.style.zIndex = -Math.abs(Math.round(offsetMultiplier)) + 1;
if (params.slideShadows) {
// Set shadows
let shadowBeforeEl = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-left') : slideEl.querySelector('.swiper-slide-shadow-top');
let shadowAfterEl = isHorizontal ? slideEl.querySelector('.swiper-slide-shadow-right') : slideEl.querySelector('.swiper-slide-shadow-bottom');
if (!shadowBeforeEl) {
shadowBeforeEl = createShadow('coverflow', slideEl, isHorizontal ? 'left' : 'top');
}
if (!shadowAfterEl) {
shadowAfterEl = createShadow('coverflow', slideEl, isHorizontal ? 'right' : 'bottom');
}
if (shadowBeforeEl) shadowBeforeEl.style.opacity = offsetMultiplier > 0 ? offsetMultiplier : 0;
if (shadowAfterEl) shadowAfterEl.style.opacity = -offsetMultiplier > 0 ? -offsetMultiplier : 0;
}
}
};
const setTransition = duration => {
const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl));
transformElements.forEach(el => {
el.style.transitionDuration = `${duration}ms`;
el.querySelectorAll('.swiper-slide-shadow-top, .swiper-slide-shadow-right, .swiper-slide-shadow-bottom, .swiper-slide-shadow-left').forEach(shadowEl => {
shadowEl.style.transitionDuration = `${duration}ms`;
});
});
};
effectInit({
effect: 'coverflow',
swiper,
on,
setTranslate,
setTransition,
perspective: () => true,
overwriteParams: () => ({
watchSlidesProgress: true
})
});
}
;// ./node_modules/swiper/modules/effect-creative.mjs
function EffectCreative(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
creativeEffect: {
limitProgress: 1,
shadowPerProgress: false,
progressMultiplier: 1,
perspective: true,
prev: {
translate: [0, 0, 0],
rotate: [0, 0, 0],
opacity: 1,
scale: 1
},
next: {
translate: [0, 0, 0],
rotate: [0, 0, 0],
opacity: 1,
scale: 1
}
}
});
const getTranslateValue = value => {
if (typeof value === 'string') return value;
return `${value}px`;
};
const setTranslate = () => {
const {
slides,
wrapperEl,
slidesSizesGrid
} = swiper;
const params = swiper.params.creativeEffect;
const {
progressMultiplier: multiplier
} = params;
const isCenteredSlides = swiper.params.centeredSlides;
const rotateFix = getRotateFix(swiper);
if (isCenteredSlides) {
const margin = slidesSizesGrid[0] / 2 - swiper.params.slidesOffsetBefore || 0;
wrapperEl.style.transform = `translateX(calc(50% - ${margin}px))`;
}
for (let i = 0; i < slides.length; i += 1) {
const slideEl = slides[i];
const slideProgress = slideEl.progress;
const progress = Math.min(Math.max(slideEl.progress, -params.limitProgress), params.limitProgress);
let originalProgress = progress;
if (!isCenteredSlides) {
originalProgress = Math.min(Math.max(slideEl.originalProgress, -params.limitProgress), params.limitProgress);
}
const offset = slideEl.swiperSlideOffset;
const t = [swiper.params.cssMode ? -offset - swiper.translate : -offset, 0, 0];
const r = [0, 0, 0];
let custom = false;
if (!swiper.isHorizontal()) {
t[1] = t[0];
t[0] = 0;
}
let data = {
translate: [0, 0, 0],
rotate: [0, 0, 0],
scale: 1,
opacity: 1
};
if (progress < 0) {
data = params.next;
custom = true;
} else if (progress > 0) {
data = params.prev;
custom = true;
}
// set translate
t.forEach((value, index) => {
t[index] = `calc(${value}px + (${getTranslateValue(data.translate[index])} * ${Math.abs(progress * multiplier)}))`;
});
// set rotates
r.forEach((value, index) => {
let val = data.rotate[index] * Math.abs(progress * multiplier);
r[index] = val;
});
slideEl.style.zIndex = -Math.abs(Math.round(slideProgress)) + slides.length;
const translateString = t.join(', ');
const rotateString = `rotateX(${rotateFix(r[0])}deg) rotateY(${rotateFix(r[1])}deg) rotateZ(${rotateFix(r[2])}deg)`;
const scaleString = originalProgress < 0 ? `scale(${1 + (1 - data.scale) * originalProgress * multiplier})` : `scale(${1 - (1 - data.scale) * originalProgress * multiplier})`;
const opacityString = originalProgress < 0 ? 1 + (1 - data.opacity) * originalProgress * multiplier : 1 - (1 - data.opacity) * originalProgress * multiplier;
const transform = `translate3d(${translateString}) ${rotateString} ${scaleString}`;
// Set shadows
if (custom && data.shadow || !custom) {
let shadowEl = slideEl.querySelector('.swiper-slide-shadow');
if (!shadowEl && data.shadow) {
shadowEl = createShadow('creative', slideEl);
}
if (shadowEl) {
const shadowOpacity = params.shadowPerProgress ? progress * (1 / params.limitProgress) : progress;
shadowEl.style.opacity = Math.min(Math.max(Math.abs(shadowOpacity), 0), 1);
}
}
const targetEl = effectTarget(params, slideEl);
targetEl.style.transform = transform;
targetEl.style.opacity = opacityString;
if (data.origin) {
targetEl.style.transformOrigin = data.origin;
}
}
};
const setTransition = duration => {
const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl));
transformElements.forEach(el => {
el.style.transitionDuration = `${duration}ms`;
el.querySelectorAll('.swiper-slide-shadow').forEach(shadowEl => {
shadowEl.style.transitionDuration = `${duration}ms`;
});
});
effectVirtualTransitionEnd({
swiper,
duration,
transformElements,
allSlides: true
});
};
effectInit({
effect: 'creative',
swiper,
on,
setTranslate,
setTransition,
perspective: () => swiper.params.creativeEffect.perspective,
overwriteParams: () => ({
watchSlidesProgress: true,
virtualTranslate: !swiper.params.cssMode
})
});
}
;// ./node_modules/swiper/modules/effect-cards.mjs
function EffectCards(_ref) {
let {
swiper,
extendParams,
on
} = _ref;
extendParams({
cardsEffect: {
slideShadows: true,
rotate: true,
perSlideRotate: 2,
perSlideOffset: 8
}
});
const setTranslate = () => {
const {
slides,
activeIndex,
rtlTranslate: rtl
} = swiper;
const params = swiper.params.cardsEffect;
const {
startTranslate,
isTouched
} = swiper.touchEventsData;
const currentTranslate = rtl ? -swiper.translate : swiper.translate;
for (let i = 0; i < slides.length; i += 1) {
const slideEl = slides[i];
const slideProgress = slideEl.progress;
const progress = Math.min(Math.max(slideProgress, -4), 4);
let offset = slideEl.swiperSlideOffset;
if (swiper.params.centeredSlides && !swiper.params.cssMode) {
swiper.wrapperEl.style.transform = `translateX(${swiper.minTranslate()}px)`;
}
if (swiper.params.centeredSlides && swiper.params.cssMode) {
offset -= slides[0].swiperSlideOffset;
}
let tX = swiper.params.cssMode ? -offset - swiper.translate : -offset;
let tY = 0;
const tZ = -100 * Math.abs(progress);
let scale = 1;
let rotate = -params.perSlideRotate * progress;
let tXAdd = params.perSlideOffset - Math.abs(progress) * 0.75;
const slideIndex = swiper.virtual && swiper.params.virtual.enabled ? swiper.virtual.from + i : i;
const isSwipeToNext = (slideIndex === activeIndex || slideIndex === activeIndex - 1) && progress > 0 && progress < 1 && (isTouched || swiper.params.cssMode) && currentTranslate < startTranslate;
const isSwipeToPrev = (slideIndex === activeIndex || slideIndex === activeIndex + 1) && progress < 0 && progress > -1 && (isTouched || swiper.params.cssMode) && currentTranslate > startTranslate;
if (isSwipeToNext || isSwipeToPrev) {
const subProgress = (1 - Math.abs((Math.abs(progress) - 0.5) / 0.5)) ** 0.5;
rotate += -28 * progress * subProgress;
scale += -0.5 * subProgress;
tXAdd += 96 * subProgress;
tY = `${-25 * subProgress * Math.abs(progress)}%`;
}
if (progress < 0) {
// next
tX = `calc(${tX}px ${rtl ? '-' : '+'} (${tXAdd * Math.abs(progress)}%))`;
} else if (progress > 0) {
// prev
tX = `calc(${tX}px ${rtl ? '-' : '+'} (-${tXAdd * Math.abs(progress)}%))`;
} else {
tX = `${tX}px`;
}
if (!swiper.isHorizontal()) {
const prevY = tY;
tY = tX;
tX = prevY;
}
const scaleString = progress < 0 ? `${1 + (1 - scale) * progress}` : `${1 - (1 - scale) * progress}`;
/* eslint-disable */
const transform = `
translate3d(${tX}, ${tY}, ${tZ}px)
rotateZ(${params.rotate ? rtl ? -rotate : rotate : 0}deg)
scale(${scaleString})
`;
/* eslint-enable */
if (params.slideShadows) {
// Set shadows
let shadowEl = slideEl.querySelector('.swiper-slide-shadow');
if (!shadowEl) {
shadowEl = createShadow('cards', slideEl);
}
if (shadowEl) shadowEl.style.opacity = Math.min(Math.max((Math.abs(progress) - 0.5) / 0.5, 0), 1);
}
slideEl.style.zIndex = -Math.abs(Math.round(slideProgress)) + slides.length;
const targetEl = effectTarget(params, slideEl);
targetEl.style.transform = transform;
}
};
const setTransition = duration => {
const transformElements = swiper.slides.map(slideEl => getSlideTransformEl(slideEl));
transformElements.forEach(el => {
el.style.transitionDuration = `${duration}ms`;
el.querySelectorAll('.swiper-slide-shadow').forEach(shadowEl => {
shadowEl.style.transitionDuration = `${duration}ms`;
});
});
effectVirtualTransitionEnd({
swiper,
duration,
transformElements
});
};
effectInit({
effect: 'cards',
swiper,
on,
setTranslate,
setTransition,
perspective: () => true,
overwriteParams: () => ({
_loopSwapReset: false,
watchSlidesProgress: true,
loopAdditionalSlides: swiper.params.cardsEffect.rotate ? 3 : 2,
centeredSlides: true,
virtualTranslate: !swiper.params.cssMode
})
});
}
;// ./node_modules/swiper/modules/index.mjs
;// ./src/js/inc/content-carousel.js
/* provided dependency */ var content_carousel_jQuery = __webpack_require__(692);
content_carousel_jQuery($ => {
$('.st-ct-carousel').each((index, st) => {
let $st = $(st),
swiper;
swiper = new Swiper('.ct-swiper', {
modules: [Autoplay, Navigation, Pagination, EffectFade],
// effect: 'fade',
loop: true,
slidesPerView: 1.001,
spaceBetween: 1,
autoHeight: false,
// autoplay: {
// delay: 4000,
// pauseOnMouseEnter: true,
// },
navigation: {
nextEl: '.st-ct-carousel .swiper-next',
prevEl: '.st-ct-carousel .swiper-prev'
},
pagination: {
el: '.st-ct-carousel .swiper-pagination',
type: 'bullets',
clickable: true
}
// breakpoints: {
// 768: {
// slidesPerView: "auto",
// }
// }
});
});
});
;// ./src/js/inc/logos.js
/* provided dependency */ var logos_jQuery = __webpack_require__(692);
logos_jQuery($ => {
$('.st-logos').each((index, st) => {
let $st = $(st),
swiper;
swiper = new Swiper('.logos-swiper', {
modules: [Autoplay],
slidesPerView: 'auto',
spaceBetween: 70,
loop: true,
speed: 6000,
allowTouchMove: false,
autoplay: {
delay: 1,
disableOnInteraction: false
}
});
});
});
;// ./src/js/inc/services-carousel.js
/* provided dependency */ var services_carousel_jQuery = __webpack_require__(692);
services_carousel_jQuery($ => {
$('.st-sv-carousel').each((index, st) => {
let $st = $(st),
swiper1,
swiper2;
swiper2 = new Swiper('.sv-desc-swiper', {
modules: [EffectFade],
effect: 'fade',
speed: 1000,
loop: true,
slidesPerView: 1,
spaceBetween: 0,
allowTouchMove: false,
autoHeight: true
});
swiper1 = new Swiper('.sv-image-swiper', {
modules: [Navigation, Pagination, Controller],
loop: true,
speed: 1000,
slidesPerView: 'auto',
spaceBetween: 0,
allowTouchMove: false,
autoHeight: false,
navigation: {
nextEl: '.st-sv-carousel .swiper-next',
prevEl: '.st-sv-carousel .swiper-prev'
},
pagination: {
el: '.st-sv-carousel .swiper-pagination',
type: 'bullets',
clickable: true
},
controller: {
control: swiper2
},
on: {
beforeSlideChangeStart(swiper) {
console.log(swiper);
}
}
});
});
});
// EXTERNAL MODULE: ./src/js/inc/services-carousel-slick.js
var services_carousel_slick = __webpack_require__(430);
// EXTERNAL MODULE: ./node_modules/global/window.js
var global_window = __webpack_require__(840);
var window_default = /*#__PURE__*/__webpack_require__.n(global_window);
// EXTERNAL MODULE: ./node_modules/global/document.js
var global_document = __webpack_require__(697);
var document_default = /*#__PURE__*/__webpack_require__.n(global_document);
// EXTERNAL MODULE: ./node_modules/@videojs/xhr/lib/index.js
var lib = __webpack_require__(36);
var lib_default = /*#__PURE__*/__webpack_require__.n(lib);
// EXTERNAL MODULE: ./node_modules/videojs-vtt.js/lib/browser-index.js
var browser_index = __webpack_require__(784);
var browser_index_default = /*#__PURE__*/__webpack_require__.n(browser_index);
;// ./node_modules/@babel/runtime/helpers/esm/extends.js
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
;// ./node_modules/@videojs/vhs-utils/es/resolve-url.js
var DEFAULT_LOCATION = 'https://example.com';
var resolveUrl = function resolveUrl(baseUrl, relativeUrl) {
// return early if we don't need to resolve
if (/^[a-z]+:/i.test(relativeUrl)) {
return relativeUrl;
} // if baseUrl is a data URI, ignore it and resolve everything relative to window.location
if (/^data:/.test(baseUrl)) {
baseUrl = (window_default()).location && (window_default()).location.href || '';
}
var protocolLess = /^\/\//.test(baseUrl); // remove location if window.location isn't available (i.e. we're in node)
// and if baseUrl isn't an absolute url
var removeLocation = !(window_default()).location && !/\/\//i.test(baseUrl); // if the base URL is relative then combine with the current location
baseUrl = new (window_default()).URL(baseUrl, (window_default()).location || DEFAULT_LOCATION);
var newUrl = new URL(relativeUrl, baseUrl); // if we're a protocol-less url, remove the protocol
// and if we're location-less, remove the location
// otherwise, return the url unmodified
if (removeLocation) {
return newUrl.href.slice(DEFAULT_LOCATION.length);
} else if (protocolLess) {
return newUrl.href.slice(newUrl.protocol.length);
}
return newUrl.href;
};
/* harmony default export */ const resolve_url = (resolveUrl);
;// ./node_modules/@videojs/vhs-utils/es/stream.js
/**
* @file stream.js
*/
/**
* A lightweight readable stream implemention that handles event dispatching.
*
* @class Stream
*/
var Stream = /*#__PURE__*/function () {
function Stream() {
this.listeners = {};
}
/**
* Add a listener for a specified event type.
*
* @param {string} type the event name
* @param {Function} listener the callback to be invoked when an event of
* the specified type occurs
*/
var _proto = Stream.prototype;
_proto.on = function on(type, listener) {
if (!this.listeners[type]) {
this.listeners[type] = [];
}
this.listeners[type].push(listener);
}
/**
* Remove a listener for a specified event type.
*
* @param {string} type the event name
* @param {Function} listener a function previously registered for this
* type of event through `on`
* @return {boolean} if we could turn it off or not
*/
;
_proto.off = function off(type, listener) {
if (!this.listeners[type]) {
return false;
}
var index = this.listeners[type].indexOf(listener); // TODO: which is better?
// In Video.js we slice listener functions
// on trigger so that it does not mess up the order
// while we loop through.
//
// Here we slice on off so that the loop in trigger
// can continue using it's old reference to loop without
// messing up the order.
this.listeners[type] = this.listeners[type].slice(0);
this.listeners[type].splice(index, 1);
return index > -1;
}
/**
* Trigger an event of the specified type on this stream. Any additional
* arguments to this function are passed as parameters to event listeners.
*
* @param {string} type the event name
*/
;
_proto.trigger = function trigger(type) {
var callbacks = this.listeners[type];
if (!callbacks) {
return;
} // Slicing the arguments on every invocation of this method
// can add a significant amount of overhead. Avoid the
// intermediate object creation for the common case of a
// single callback argument
if (arguments.length === 2) {
var length = callbacks.length;
for (var i = 0; i < length; ++i) {
callbacks[i].call(this, arguments[1]);
}
} else {
var args = Array.prototype.slice.call(arguments, 1);
var _length = callbacks.length;
for (var _i = 0; _i < _length; ++_i) {
callbacks[_i].apply(this, args);
}
}
}
/**
* Destroys the stream and cleans up.
*/
;
_proto.dispose = function dispose() {
this.listeners = {};
}
/**
* Forwards all `data` events on this stream to the destination stream. The
* destination stream should provide a method `push` to receive the data
* events as they arrive.
*
* @param {Stream} destination the stream that will receive all `data` events
* @see http://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
*/
;
_proto.pipe = function pipe(destination) {
this.on('data', function (data) {
destination.push(data);
});
};
return Stream;
}();
;// ./node_modules/@videojs/vhs-utils/es/decode-b64-to-uint8-array.js
var atob = function atob(s) {
return (window_default()).atob ? window_default().atob(s) : Buffer.from(s, 'base64').toString('binary');
};
function decodeB64ToUint8Array(b64Text) {
var decodedString = atob(b64Text);
var array = new Uint8Array(decodedString.length);
for (var i = 0; i < decodedString.length; i++) {
array[i] = decodedString.charCodeAt(i);
}
return array;
}
;// ./node_modules/m3u8-parser/dist/m3u8-parser.es.js
/*! @name m3u8-parser @version 7.2.0 @license Apache-2.0 */
/**
* @file m3u8/line-stream.js
*/
/**
* A stream that buffers string input and generates a `data` event for each
* line.
*
* @class LineStream
* @extends Stream
*/
class LineStream extends Stream {
constructor() {
super();
this.buffer = '';
}
/**
* Add new data to be parsed.
*
* @param {string} data the text to process
*/
push(data) {
let nextNewline;
this.buffer += data;
nextNewline = this.buffer.indexOf('\n');
for (; nextNewline > -1; nextNewline = this.buffer.indexOf('\n')) {
this.trigger('data', this.buffer.substring(0, nextNewline));
this.buffer = this.buffer.substring(nextNewline + 1);
}
}
}
const TAB = String.fromCharCode(0x09);
const parseByterange = function (byterangeString) {
// optionally match and capture 0+ digits before `@`
// optionally match and capture 0+ digits after `@`
const match = /([0-9.]*)?@?([0-9.]*)?/.exec(byterangeString || '');
const result = {};
if (match[1]) {
result.length = parseInt(match[1], 10);
}
if (match[2]) {
result.offset = parseInt(match[2], 10);
}
return result;
};
/**
* "forgiving" attribute list psuedo-grammar:
* attributes -> keyvalue (',' keyvalue)*
* keyvalue -> key '=' value
* key -> [^=]*
* value -> '"' [^"]* '"' | [^,]*
*/
const attributeSeparator = function () {
const key = '[^=]*';
const value = '"[^"]*"|[^,]*';
const keyvalue = '(?:' + key + ')=(?:' + value + ')';
return new RegExp('(?:^|,)(' + keyvalue + ')');
};
/**
* Parse attributes from a line given the separator
*
* @param {string} attributes the attribute line to parse
*/
const parseAttributes = function (attributes) {
const result = {};
if (!attributes) {
return result;
} // split the string using attributes as the separator
const attrs = attributes.split(attributeSeparator());
let i = attrs.length;
let attr;
while (i--) {
// filter out unmatched portions of the string
if (attrs[i] === '') {
continue;
} // split the key and value
attr = /([^=]*)=(.*)/.exec(attrs[i]).slice(1); // trim whitespace and remove optional quotes around the value
attr[0] = attr[0].replace(/^\s+|\s+$/g, '');
attr[1] = attr[1].replace(/^\s+|\s+$/g, '');
attr[1] = attr[1].replace(/^['"](.*)['"]$/g, '$1');
result[attr[0]] = attr[1];
}
return result;
};
/**
* Converts a string into a resolution object
*
* @param {string} resolution a string such as 3840x2160
*
* @return {Object} An object representing the resolution
*
*/
const parseResolution = resolution => {
const split = resolution.split('x');
const result = {};
if (split[0]) {
result.width = parseInt(split[0], 10);
}
if (split[1]) {
result.height = parseInt(split[1], 10);
}
return result;
};
/**
* A line-level M3U8 parser event stream. It expects to receive input one
* line at a time and performs a context-free parse of its contents. A stream
* interpretation of a manifest can be useful if the manifest is expected to
* be too large to fit comfortably into memory or the entirety of the input
* is not immediately available. Otherwise, it's probably much easier to work
* with a regular `Parser` object.
*
* Produces `data` events with an object that captures the parser's
* interpretation of the input. That object has a property `tag` that is one
* of `uri`, `comment`, or `tag`. URIs only have a single additional
* property, `line`, which captures the entirety of the input without
* interpretation. Comments similarly have a single additional property
* `text` which is the input without the leading `#`.
*
* Tags always have a property `tagType` which is the lower-cased version of
* the M3U8 directive without the `#EXT` or `#EXT-X-` prefix. For instance,
* `#EXT-X-MEDIA-SEQUENCE` becomes `media-sequence` when parsed. Unrecognized
* tags are given the tag type `unknown` and a single additional property
* `data` with the remainder of the input.
*
* @class ParseStream
* @extends Stream
*/
class ParseStream extends Stream {
constructor() {
super();
this.customParsers = [];
this.tagMappers = [];
}
/**
* Parses an additional line of input.
*
* @param {string} line a single line of an M3U8 file to parse
*/
push(line) {
let match;
let event; // strip whitespace
line = line.trim();
if (line.length === 0) {
// ignore empty lines
return;
} // URIs
if (line[0] !== '#') {
this.trigger('data', {
type: 'uri',
uri: line
});
return;
} // map tags
const newLines = this.tagMappers.reduce((acc, mapper) => {
const mappedLine = mapper(line); // skip if unchanged
if (mappedLine === line) {
return acc;
}
return acc.concat([mappedLine]);
}, [line]);
newLines.forEach(newLine => {
for (let i = 0; i < this.customParsers.length; i++) {
if (this.customParsers[i].call(this, newLine)) {
return;
}
} // Comments
if (newLine.indexOf('#EXT') !== 0) {
this.trigger('data', {
type: 'comment',
text: newLine.slice(1)
});
return;
} // strip off any carriage returns here so the regex matching
// doesn't have to account for them.
newLine = newLine.replace('\r', ''); // Tags
match = /^#EXTM3U/.exec(newLine);
if (match) {
this.trigger('data', {
type: 'tag',
tagType: 'm3u'
});
return;
}
match = /^#EXTINF:([0-9\.]*)?,?(.*)?$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'inf'
};
if (match[1]) {
event.duration = parseFloat(match[1]);
}
if (match[2]) {
event.title = match[2];
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-TARGETDURATION:([0-9.]*)?/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'targetduration'
};
if (match[1]) {
event.duration = parseInt(match[1], 10);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-VERSION:([0-9.]*)?/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'version'
};
if (match[1]) {
event.version = parseInt(match[1], 10);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-MEDIA-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'media-sequence'
};
if (match[1]) {
event.number = parseInt(match[1], 10);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-DISCONTINUITY-SEQUENCE:(\-?[0-9.]*)?/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'discontinuity-sequence'
};
if (match[1]) {
event.number = parseInt(match[1], 10);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-PLAYLIST-TYPE:(.*)?$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'playlist-type'
};
if (match[1]) {
event.playlistType = match[1];
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-BYTERANGE:(.*)?$/.exec(newLine);
if (match) {
event = _extends(parseByterange(match[1]), {
type: 'tag',
tagType: 'byterange'
});
this.trigger('data', event);
return;
}
match = /^#EXT-X-ALLOW-CACHE:(YES|NO)?/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'allow-cache'
};
if (match[1]) {
event.allowed = !/NO/.test(match[1]);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-MAP:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'map'
};
if (match[1]) {
const attributes = parseAttributes(match[1]);
if (attributes.URI) {
event.uri = attributes.URI;
}
if (attributes.BYTERANGE) {
event.byterange = parseByterange(attributes.BYTERANGE);
}
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-STREAM-INF:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'stream-inf'
};
if (match[1]) {
event.attributes = parseAttributes(match[1]);
if (event.attributes.RESOLUTION) {
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
}
if (event.attributes.BANDWIDTH) {
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
}
if (event.attributes['FRAME-RATE']) {
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
}
if (event.attributes['PROGRAM-ID']) {
event.attributes['PROGRAM-ID'] = parseInt(event.attributes['PROGRAM-ID'], 10);
}
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-MEDIA:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'media'
};
if (match[1]) {
event.attributes = parseAttributes(match[1]);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-ENDLIST/.exec(newLine);
if (match) {
this.trigger('data', {
type: 'tag',
tagType: 'endlist'
});
return;
}
match = /^#EXT-X-DISCONTINUITY/.exec(newLine);
if (match) {
this.trigger('data', {
type: 'tag',
tagType: 'discontinuity'
});
return;
}
match = /^#EXT-X-PROGRAM-DATE-TIME:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'program-date-time'
};
if (match[1]) {
event.dateTimeString = match[1];
event.dateTimeObject = new Date(match[1]);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-KEY:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'key'
};
if (match[1]) {
event.attributes = parseAttributes(match[1]); // parse the IV string into a Uint32Array
if (event.attributes.IV) {
if (event.attributes.IV.substring(0, 2).toLowerCase() === '0x') {
event.attributes.IV = event.attributes.IV.substring(2);
}
event.attributes.IV = event.attributes.IV.match(/.{8}/g);
event.attributes.IV[0] = parseInt(event.attributes.IV[0], 16);
event.attributes.IV[1] = parseInt(event.attributes.IV[1], 16);
event.attributes.IV[2] = parseInt(event.attributes.IV[2], 16);
event.attributes.IV[3] = parseInt(event.attributes.IV[3], 16);
event.attributes.IV = new Uint32Array(event.attributes.IV);
}
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-START:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'start'
};
if (match[1]) {
event.attributes = parseAttributes(match[1]);
event.attributes['TIME-OFFSET'] = parseFloat(event.attributes['TIME-OFFSET']);
event.attributes.PRECISE = /YES/.test(event.attributes.PRECISE);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-CUE-OUT-CONT:(.*)?$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'cue-out-cont'
};
if (match[1]) {
event.data = match[1];
} else {
event.data = '';
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-CUE-OUT:(.*)?$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'cue-out'
};
if (match[1]) {
event.data = match[1];
} else {
event.data = '';
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-CUE-IN:?(.*)?$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'cue-in'
};
if (match[1]) {
event.data = match[1];
} else {
event.data = '';
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-SKIP:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'skip'
};
event.attributes = parseAttributes(match[1]);
if (event.attributes.hasOwnProperty('SKIPPED-SEGMENTS')) {
event.attributes['SKIPPED-SEGMENTS'] = parseInt(event.attributes['SKIPPED-SEGMENTS'], 10);
}
if (event.attributes.hasOwnProperty('RECENTLY-REMOVED-DATERANGES')) {
event.attributes['RECENTLY-REMOVED-DATERANGES'] = event.attributes['RECENTLY-REMOVED-DATERANGES'].split(TAB);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-PART:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'part'
};
event.attributes = parseAttributes(match[1]);
['DURATION'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
['INDEPENDENT', 'GAP'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = /YES/.test(event.attributes[key]);
}
});
if (event.attributes.hasOwnProperty('BYTERANGE')) {
event.attributes.byterange = parseByterange(event.attributes.BYTERANGE);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-SERVER-CONTROL:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'server-control'
};
event.attributes = parseAttributes(match[1]);
['CAN-SKIP-UNTIL', 'PART-HOLD-BACK', 'HOLD-BACK'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
['CAN-SKIP-DATERANGES', 'CAN-BLOCK-RELOAD'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = /YES/.test(event.attributes[key]);
}
});
this.trigger('data', event);
return;
}
match = /^#EXT-X-PART-INF:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'part-inf'
};
event.attributes = parseAttributes(match[1]);
['PART-TARGET'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
this.trigger('data', event);
return;
}
match = /^#EXT-X-PRELOAD-HINT:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'preload-hint'
};
event.attributes = parseAttributes(match[1]);
['BYTERANGE-START', 'BYTERANGE-LENGTH'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseInt(event.attributes[key], 10);
const subkey = key === 'BYTERANGE-LENGTH' ? 'length' : 'offset';
event.attributes.byterange = event.attributes.byterange || {};
event.attributes.byterange[subkey] = event.attributes[key]; // only keep the parsed byterange object.
delete event.attributes[key];
}
});
this.trigger('data', event);
return;
}
match = /^#EXT-X-RENDITION-REPORT:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'rendition-report'
};
event.attributes = parseAttributes(match[1]);
['LAST-MSN', 'LAST-PART'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseInt(event.attributes[key], 10);
}
});
this.trigger('data', event);
return;
}
match = /^#EXT-X-DATERANGE:(.*)$/.exec(newLine);
if (match && match[1]) {
event = {
type: 'tag',
tagType: 'daterange'
};
event.attributes = parseAttributes(match[1]);
['ID', 'CLASS'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = String(event.attributes[key]);
}
});
['START-DATE', 'END-DATE'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = new Date(event.attributes[key]);
}
});
['DURATION', 'PLANNED-DURATION'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = parseFloat(event.attributes[key]);
}
});
['END-ON-NEXT'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = /YES/i.test(event.attributes[key]);
}
});
['SCTE35-CMD', ' SCTE35-OUT', 'SCTE35-IN'].forEach(function (key) {
if (event.attributes.hasOwnProperty(key)) {
event.attributes[key] = event.attributes[key].toString(16);
}
});
const clientAttributePattern = /^X-([A-Z]+-)+[A-Z]+$/;
for (const key in event.attributes) {
if (!clientAttributePattern.test(key)) {
continue;
}
const isHexaDecimal = /[0-9A-Fa-f]{6}/g.test(event.attributes[key]);
const isDecimalFloating = /^\d+(\.\d+)?$/.test(event.attributes[key]);
event.attributes[key] = isHexaDecimal ? event.attributes[key].toString(16) : isDecimalFloating ? parseFloat(event.attributes[key]) : String(event.attributes[key]);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-INDEPENDENT-SEGMENTS/.exec(newLine);
if (match) {
this.trigger('data', {
type: 'tag',
tagType: 'independent-segments'
});
return;
}
match = /^#EXT-X-I-FRAMES-ONLY/.exec(newLine);
if (match) {
this.trigger('data', {
type: 'tag',
tagType: 'i-frames-only'
});
return;
}
match = /^#EXT-X-CONTENT-STEERING:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'content-steering'
};
event.attributes = parseAttributes(match[1]);
this.trigger('data', event);
return;
}
match = /^#EXT-X-I-FRAME-STREAM-INF:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'i-frame-playlist'
};
event.attributes = parseAttributes(match[1]);
if (event.attributes.URI) {
event.uri = event.attributes.URI;
}
if (event.attributes.BANDWIDTH) {
event.attributes.BANDWIDTH = parseInt(event.attributes.BANDWIDTH, 10);
}
if (event.attributes.RESOLUTION) {
event.attributes.RESOLUTION = parseResolution(event.attributes.RESOLUTION);
}
if (event.attributes['AVERAGE-BANDWIDTH']) {
event.attributes['AVERAGE-BANDWIDTH'] = parseInt(event.attributes['AVERAGE-BANDWIDTH'], 10);
}
if (event.attributes['FRAME-RATE']) {
event.attributes['FRAME-RATE'] = parseFloat(event.attributes['FRAME-RATE']);
}
this.trigger('data', event);
return;
}
match = /^#EXT-X-DEFINE:(.*)$/.exec(newLine);
if (match) {
event = {
type: 'tag',
tagType: 'define'
};
event.attributes = parseAttributes(match[1]);
this.trigger('data', event);
return;
} // unknown tag type
this.trigger('data', {
type: 'tag',
data: newLine.slice(4)
});
});
}
/**
* Add a parser for custom headers
*
* @param {Object} options a map of options for the added parser
* @param {RegExp} options.expression a regular expression to match the custom header
* @param {string} options.customType the custom type to register to the output
* @param {Function} [options.dataParser] function to parse the line into an object
* @param {boolean} [options.segment] should tag data be attached to the segment object
*/
addParser({
expression,
customType,
dataParser,
segment
}) {
if (typeof dataParser !== 'function') {
dataParser = line => line;
}
this.customParsers.push(line => {
const match = expression.exec(line);
if (match) {
this.trigger('data', {
type: 'custom',
data: dataParser(line),
customType,
segment
});
return true;
}
});
}
/**
* Add a custom header mapper
*
* @param {Object} options
* @param {RegExp} options.expression a regular expression to match the custom header
* @param {Function} options.map function to translate tag into a different tag
*/
addTagMapper({
expression,
map
}) {
const mapFn = line => {
if (expression.test(line)) {
return map(line);
}
return line;
};
this.tagMappers.push(mapFn);
}
}
const camelCase = str => str.toLowerCase().replace(/-(\w)/g, a => a[1].toUpperCase());
const camelCaseKeys = function (attributes) {
const result = {};
Object.keys(attributes).forEach(function (key) {
result[camelCase(key)] = attributes[key];
});
return result;
}; // set SERVER-CONTROL hold back based upon targetDuration and partTargetDuration
// we need this helper because defaults are based upon targetDuration and
// partTargetDuration being set, but they may not be if SERVER-CONTROL appears before
// target durations are set.
const setHoldBack = function (manifest) {
const {
serverControl,
targetDuration,
partTargetDuration
} = manifest;
if (!serverControl) {
return;
}
const tag = '#EXT-X-SERVER-CONTROL';
const hb = 'holdBack';
const phb = 'partHoldBack';
const minTargetDuration = targetDuration && targetDuration * 3;
const minPartDuration = partTargetDuration && partTargetDuration * 2;
if (targetDuration && !serverControl.hasOwnProperty(hb)) {
serverControl[hb] = minTargetDuration;
this.trigger('info', {
message: `${tag} defaulting HOLD-BACK to targetDuration * 3 (${minTargetDuration}).`
});
}
if (minTargetDuration && serverControl[hb] < minTargetDuration) {
this.trigger('warn', {
message: `${tag} clamping HOLD-BACK (${serverControl[hb]}) to targetDuration * 3 (${minTargetDuration})`
});
serverControl[hb] = minTargetDuration;
} // default no part hold back to part target duration * 3
if (partTargetDuration && !serverControl.hasOwnProperty(phb)) {
serverControl[phb] = partTargetDuration * 3;
this.trigger('info', {
message: `${tag} defaulting PART-HOLD-BACK to partTargetDuration * 3 (${serverControl[phb]}).`
});
} // if part hold back is too small default it to part target duration * 2
if (partTargetDuration && serverControl[phb] < minPartDuration) {
this.trigger('warn', {
message: `${tag} clamping PART-HOLD-BACK (${serverControl[phb]}) to partTargetDuration * 2 (${minPartDuration}).`
});
serverControl[phb] = minPartDuration;
}
};
/**
* A parser for M3U8 files. The current interpretation of the input is
* exposed as a property `manifest` on parser objects. It's just two lines to
* create and parse a manifest once you have the contents available as a string:
*
* ```js
* var parser = new m3u8.Parser();
* parser.push(xhr.responseText);
* ```
*
* New input can later be applied to update the manifest object by calling
* `push` again.
*
* The parser attempts to create a usable manifest object even if the
* underlying input is somewhat nonsensical. It emits `info` and `warning`
* events during the parse if it encounters input that seems invalid or
* requires some property of the manifest object to be defaulted.
*
* @class Parser
* @param {Object} [opts] Options for the constructor, needed for substitutions
* @param {string} [opts.uri] URL to check for query params
* @param {Object} [opts.mainDefinitions] Definitions on main playlist that can be imported
* @extends Stream
*/
class Parser extends Stream {
constructor(opts = {}) {
super();
this.lineStream = new LineStream();
this.parseStream = new ParseStream();
this.lineStream.pipe(this.parseStream);
this.mainDefinitions = opts.mainDefinitions || {};
this.params = new URL(opts.uri, 'https://a.com').searchParams;
this.lastProgramDateTime = null;
/* eslint-disable consistent-this */
const self = this;
/* eslint-enable consistent-this */
const uris = [];
let currentUri = {}; // if specified, the active EXT-X-MAP definition
let currentMap; // if specified, the active decryption key
let key;
let hasParts = false;
const noop = function () {};
const defaultMediaGroups = {
'AUDIO': {},
'VIDEO': {},
'CLOSED-CAPTIONS': {},
'SUBTITLES': {}
}; // This is the Widevine UUID from DASH IF IOP. The same exact string is
// used in MPDs with Widevine encrypted streams.
const widevineUuid = 'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed'; // group segments into numbered timelines delineated by discontinuities
let currentTimeline = 0; // the manifest is empty until the parse stream begins delivering data
this.manifest = {
allowCache: true,
discontinuityStarts: [],
dateRanges: [],
iFramePlaylists: [],
segments: []
}; // keep track of the last seen segment's byte range end, as segments are not required
// to provide the offset, in which case it defaults to the next byte after the
// previous segment
let lastByterangeEnd = 0; // keep track of the last seen part's byte range end.
let lastPartByterangeEnd = 0;
const dateRangeTags = {};
this.on('end', () => {
// only add preloadSegment if we don't yet have a uri for it.
// and we actually have parts/preloadHints
if (currentUri.uri || !currentUri.parts && !currentUri.preloadHints) {
return;
}
if (!currentUri.map && currentMap) {
currentUri.map = currentMap;
}
if (!currentUri.key && key) {
currentUri.key = key;
}
if (!currentUri.timeline && typeof currentTimeline === 'number') {
currentUri.timeline = currentTimeline;
}
this.manifest.preloadSegment = currentUri;
}); // update the manifest with the m3u8 entry from the parse stream
this.parseStream.on('data', function (entry) {
let mediaGroup;
let rendition; // Replace variables in uris and attributes as defined in #EXT-X-DEFINE tags
if (self.manifest.definitions) {
for (const def in self.manifest.definitions) {
if (entry.uri) {
entry.uri = entry.uri.replace(`{$${def}}`, self.manifest.definitions[def]);
}
if (entry.attributes) {
for (const attr in entry.attributes) {
if (typeof entry.attributes[attr] === 'string') {
entry.attributes[attr] = entry.attributes[attr].replace(`{$${def}}`, self.manifest.definitions[def]);
}
}
}
}
}
({
tag() {
// switch based on the tag type
(({
version() {
if (entry.version) {
this.manifest.version = entry.version;
}
},
'allow-cache'() {
this.manifest.allowCache = entry.allowed;
if (!('allowed' in entry)) {
this.trigger('info', {
message: 'defaulting allowCache to YES'
});
this.manifest.allowCache = true;
}
},
byterange() {
const byterange = {};
if ('length' in entry) {
currentUri.byterange = byterange;
byterange.length = entry.length;
if (!('offset' in entry)) {
/*
* From the latest spec (as of this writing):
* https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-4.3.2.2
*
* Same text since EXT-X-BYTERANGE's introduction in draft 7:
* https://tools.ietf.org/html/draft-pantos-http-live-streaming-07#section-3.3.1)
*
* "If o [offset] is not present, the sub-range begins at the next byte
* following the sub-range of the previous media segment."
*/
entry.offset = lastByterangeEnd;
}
}
if ('offset' in entry) {
currentUri.byterange = byterange;
byterange.offset = entry.offset;
}
lastByterangeEnd = byterange.offset + byterange.length;
},
endlist() {
this.manifest.endList = true;
},
inf() {
if (!('mediaSequence' in this.manifest)) {
this.manifest.mediaSequence = 0;
this.trigger('info', {
message: 'defaulting media sequence to zero'
});
}
if (!('discontinuitySequence' in this.manifest)) {
this.manifest.discontinuitySequence = 0;
this.trigger('info', {
message: 'defaulting discontinuity sequence to zero'
});
}
if (entry.title) {
currentUri.title = entry.title;
}
if (entry.duration > 0) {
currentUri.duration = entry.duration;
}
if (entry.duration === 0) {
currentUri.duration = 0.01;
this.trigger('info', {
message: 'updating zero segment duration to a small value'
});
}
this.manifest.segments = uris;
},
key() {
if (!entry.attributes) {
this.trigger('warn', {
message: 'ignoring key declaration without attribute list'
});
return;
} // clear the active encryption key
if (entry.attributes.METHOD === 'NONE') {
key = null;
return;
}
if (!entry.attributes.URI) {
this.trigger('warn', {
message: 'ignoring key declaration without URI'
});
return;
}
if (entry.attributes.KEYFORMAT === 'com.apple.streamingkeydelivery') {
this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
this.manifest.contentProtection['com.apple.fps.1_0'] = {
attributes: entry.attributes
};
return;
}
if (entry.attributes.KEYFORMAT === 'com.microsoft.playready') {
this.manifest.contentProtection = this.manifest.contentProtection || {}; // TODO: add full support for this.
this.manifest.contentProtection['com.microsoft.playready'] = {
uri: entry.attributes.URI
};
return;
} // check if the content is encrypted for Widevine
// Widevine/HLS spec: https://storage.googleapis.com/wvdocs/Widevine_DRM_HLS.pdf
if (entry.attributes.KEYFORMAT === widevineUuid) {
const VALID_METHODS = ['SAMPLE-AES', 'SAMPLE-AES-CTR', 'SAMPLE-AES-CENC'];
if (VALID_METHODS.indexOf(entry.attributes.METHOD) === -1) {
this.trigger('warn', {
message: 'invalid key method provided for Widevine'
});
return;
}
if (entry.attributes.METHOD === 'SAMPLE-AES-CENC') {
this.trigger('warn', {
message: 'SAMPLE-AES-CENC is deprecated, please use SAMPLE-AES-CTR instead'
});
}
if (entry.attributes.URI.substring(0, 23) !== 'data:text/plain;base64,') {
this.trigger('warn', {
message: 'invalid key URI provided for Widevine'
});
return;
}
if (!(entry.attributes.KEYID && entry.attributes.KEYID.substring(0, 2) === '0x')) {
this.trigger('warn', {
message: 'invalid key ID provided for Widevine'
});
return;
} // if Widevine key attributes are valid, store them as `contentProtection`
// on the manifest to emulate Widevine tag structure in a DASH mpd
this.manifest.contentProtection = this.manifest.contentProtection || {};
this.manifest.contentProtection['com.widevine.alpha'] = {
attributes: {
schemeIdUri: entry.attributes.KEYFORMAT,
// remove '0x' from the key id string
keyId: entry.attributes.KEYID.substring(2)
},
// decode the base64-encoded PSSH box
pssh: decodeB64ToUint8Array(entry.attributes.URI.split(',')[1])
};
return;
}
if (!entry.attributes.METHOD) {
this.trigger('warn', {
message: 'defaulting key method to AES-128'
});
} // setup an encryption key for upcoming segments
key = {
method: entry.attributes.METHOD || 'AES-128',
uri: entry.attributes.URI
};
if (typeof entry.attributes.IV !== 'undefined') {
key.iv = entry.attributes.IV;
}
},
'media-sequence'() {
if (!isFinite(entry.number)) {
this.trigger('warn', {
message: 'ignoring invalid media sequence: ' + entry.number
});
return;
}
this.manifest.mediaSequence = entry.number;
},
'discontinuity-sequence'() {
if (!isFinite(entry.number)) {
this.trigger('warn', {
message: 'ignoring invalid discontinuity sequence: ' + entry.number
});
return;
}
this.manifest.discontinuitySequence = entry.number;
currentTimeline = entry.number;
},
'playlist-type'() {
if (!/VOD|EVENT/.test(entry.playlistType)) {
this.trigger('warn', {
message: 'ignoring unknown playlist type: ' + entry.playlist
});
return;
}
this.manifest.playlistType = entry.playlistType;
},
map() {
currentMap = {};
if (entry.uri) {
currentMap.uri = entry.uri;
}
if (entry.byterange) {
currentMap.byterange = entry.byterange;
}
if (key) {
currentMap.key = key;
}
},
'stream-inf'() {
this.manifest.playlists = uris;
this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
if (!entry.attributes) {
this.trigger('warn', {
message: 'ignoring empty stream-inf attributes'
});
return;
}
if (!currentUri.attributes) {
currentUri.attributes = {};
}
_extends(currentUri.attributes, entry.attributes);
},
media() {
this.manifest.mediaGroups = this.manifest.mediaGroups || defaultMediaGroups;
if (!(entry.attributes && entry.attributes.TYPE && entry.attributes['GROUP-ID'] && entry.attributes.NAME)) {
this.trigger('warn', {
message: 'ignoring incomplete or missing media group'
});
return;
} // find the media group, creating defaults as necessary
const mediaGroupType = this.manifest.mediaGroups[entry.attributes.TYPE];
mediaGroupType[entry.attributes['GROUP-ID']] = mediaGroupType[entry.attributes['GROUP-ID']] || {};
mediaGroup = mediaGroupType[entry.attributes['GROUP-ID']]; // collect the rendition metadata
rendition = {
default: /yes/i.test(entry.attributes.DEFAULT)
};
if (rendition.default) {
rendition.autoselect = true;
} else {
rendition.autoselect = /yes/i.test(entry.attributes.AUTOSELECT);
}
if (entry.attributes.LANGUAGE) {
rendition.language = entry.attributes.LANGUAGE;
}
if (entry.attributes.URI) {
rendition.uri = entry.attributes.URI;
}
if (entry.attributes['INSTREAM-ID']) {
rendition.instreamId = entry.attributes['INSTREAM-ID'];
}
if (entry.attributes.CHARACTERISTICS) {
rendition.characteristics = entry.attributes.CHARACTERISTICS;
}
if (entry.attributes.FORCED) {
rendition.forced = /yes/i.test(entry.attributes.FORCED);
} // insert the new rendition
mediaGroup[entry.attributes.NAME] = rendition;
},
discontinuity() {
currentTimeline += 1;
currentUri.discontinuity = true;
this.manifest.discontinuityStarts.push(uris.length);
},
'program-date-time'() {
if (typeof this.manifest.dateTimeString === 'undefined') {
// PROGRAM-DATE-TIME is a media-segment tag, but for backwards
// compatibility, we add the first occurence of the PROGRAM-DATE-TIME tag
// to the manifest object
// TODO: Consider removing this in future major version
this.manifest.dateTimeString = entry.dateTimeString;
this.manifest.dateTimeObject = entry.dateTimeObject;
}
currentUri.dateTimeString = entry.dateTimeString;
currentUri.dateTimeObject = entry.dateTimeObject;
const {
lastProgramDateTime
} = this;
this.lastProgramDateTime = new Date(entry.dateTimeString).getTime(); // We should extrapolate Program Date Time backward only during first program date time occurrence.
// Once we have at least one program date time point, we can always extrapolate it forward using lastProgramDateTime reference.
if (lastProgramDateTime === null) {
// Extrapolate Program Date Time backward
// Since it is first program date time occurrence we're assuming that
// all this.manifest.segments have no program date time info
this.manifest.segments.reduceRight((programDateTime, segment) => {
segment.programDateTime = programDateTime - segment.duration * 1000;
return segment.programDateTime;
}, this.lastProgramDateTime);
}
},
targetduration() {
if (!isFinite(entry.duration) || entry.duration < 0) {
this.trigger('warn', {
message: 'ignoring invalid target duration: ' + entry.duration
});
return;
}
this.manifest.targetDuration = entry.duration;
setHoldBack.call(this, this.manifest);
},
start() {
if (!entry.attributes || isNaN(entry.attributes['TIME-OFFSET'])) {
this.trigger('warn', {
message: 'ignoring start declaration without appropriate attribute list'
});
return;
}
this.manifest.start = {
timeOffset: entry.attributes['TIME-OFFSET'],
precise: entry.attributes.PRECISE
};
},
'cue-out'() {
currentUri.cueOut = entry.data;
},
'cue-out-cont'() {
currentUri.cueOutCont = entry.data;
},
'cue-in'() {
currentUri.cueIn = entry.data;
},
'skip'() {
this.manifest.skip = camelCaseKeys(entry.attributes);
this.warnOnMissingAttributes_('#EXT-X-SKIP', entry.attributes, ['SKIPPED-SEGMENTS']);
},
'part'() {
hasParts = true; // parts are always specifed before a segment
const segmentIndex = this.manifest.segments.length;
const part = camelCaseKeys(entry.attributes);
currentUri.parts = currentUri.parts || [];
currentUri.parts.push(part);
if (part.byterange) {
if (!part.byterange.hasOwnProperty('offset')) {
part.byterange.offset = lastPartByterangeEnd;
}
lastPartByterangeEnd = part.byterange.offset + part.byterange.length;
}
const partIndex = currentUri.parts.length - 1;
this.warnOnMissingAttributes_(`#EXT-X-PART #${partIndex} for segment #${segmentIndex}`, entry.attributes, ['URI', 'DURATION']);
if (this.manifest.renditionReports) {
this.manifest.renditionReports.forEach((r, i) => {
if (!r.hasOwnProperty('lastPart')) {
this.trigger('warn', {
message: `#EXT-X-RENDITION-REPORT #${i} lacks required attribute(s): LAST-PART`
});
}
});
}
},
'server-control'() {
const attrs = this.manifest.serverControl = camelCaseKeys(entry.attributes);
if (!attrs.hasOwnProperty('canBlockReload')) {
attrs.canBlockReload = false;
this.trigger('info', {
message: '#EXT-X-SERVER-CONTROL defaulting CAN-BLOCK-RELOAD to false'
});
}
setHoldBack.call(this, this.manifest);
if (attrs.canSkipDateranges && !attrs.hasOwnProperty('canSkipUntil')) {
this.trigger('warn', {
message: '#EXT-X-SERVER-CONTROL lacks required attribute CAN-SKIP-UNTIL which is required when CAN-SKIP-DATERANGES is set'
});
}
},
'preload-hint'() {
// parts are always specifed before a segment
const segmentIndex = this.manifest.segments.length;
const hint = camelCaseKeys(entry.attributes);
const isPart = hint.type && hint.type === 'PART';
currentUri.preloadHints = currentUri.preloadHints || [];
currentUri.preloadHints.push(hint);
if (hint.byterange) {
if (!hint.byterange.hasOwnProperty('offset')) {
// use last part byterange end or zero if not a part.
hint.byterange.offset = isPart ? lastPartByterangeEnd : 0;
if (isPart) {
lastPartByterangeEnd = hint.byterange.offset + hint.byterange.length;
}
}
}
const index = currentUri.preloadHints.length - 1;
this.warnOnMissingAttributes_(`#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex}`, entry.attributes, ['TYPE', 'URI']);
if (!hint.type) {
return;
} // search through all preload hints except for the current one for
// a duplicate type.
for (let i = 0; i < currentUri.preloadHints.length - 1; i++) {
const otherHint = currentUri.preloadHints[i];
if (!otherHint.type) {
continue;
}
if (otherHint.type === hint.type) {
this.trigger('warn', {
message: `#EXT-X-PRELOAD-HINT #${index} for segment #${segmentIndex} has the same TYPE ${hint.type} as preload hint #${i}`
});
}
}
},
'rendition-report'() {
const report = camelCaseKeys(entry.attributes);
this.manifest.renditionReports = this.manifest.renditionReports || [];
this.manifest.renditionReports.push(report);
const index = this.manifest.renditionReports.length - 1;
const required = ['LAST-MSN', 'URI'];
if (hasParts) {
required.push('LAST-PART');
}
this.warnOnMissingAttributes_(`#EXT-X-RENDITION-REPORT #${index}`, entry.attributes, required);
},
'part-inf'() {
this.manifest.partInf = camelCaseKeys(entry.attributes);
this.warnOnMissingAttributes_('#EXT-X-PART-INF', entry.attributes, ['PART-TARGET']);
if (this.manifest.partInf.partTarget) {
this.manifest.partTargetDuration = this.manifest.partInf.partTarget;
}
setHoldBack.call(this, this.manifest);
},
'daterange'() {
this.manifest.dateRanges.push(camelCaseKeys(entry.attributes));
const index = this.manifest.dateRanges.length - 1;
this.warnOnMissingAttributes_(`#EXT-X-DATERANGE #${index}`, entry.attributes, ['ID', 'START-DATE']);
const dateRange = this.manifest.dateRanges[index];
if (dateRange.endDate && dateRange.startDate && new Date(dateRange.endDate) < new Date(dateRange.startDate)) {
this.trigger('warn', {
message: 'EXT-X-DATERANGE END-DATE must be equal to or later than the value of the START-DATE'
});
}
if (dateRange.duration && dateRange.duration < 0) {
this.trigger('warn', {
message: 'EXT-X-DATERANGE DURATION must not be negative'
});
}
if (dateRange.plannedDuration && dateRange.plannedDuration < 0) {
this.trigger('warn', {
message: 'EXT-X-DATERANGE PLANNED-DURATION must not be negative'
});
}
const endOnNextYes = !!dateRange.endOnNext;
if (endOnNextYes && !dateRange.class) {
this.trigger('warn', {
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must have a CLASS attribute'
});
}
if (endOnNextYes && (dateRange.duration || dateRange.endDate)) {
this.trigger('warn', {
message: 'EXT-X-DATERANGE with an END-ON-NEXT=YES attribute must not contain DURATION or END-DATE attributes'
});
}
if (dateRange.duration && dateRange.endDate) {
const startDate = dateRange.startDate;
const newDateInSeconds = startDate.getTime() + dateRange.duration * 1000;
this.manifest.dateRanges[index].endDate = new Date(newDateInSeconds);
}
if (!dateRangeTags[dateRange.id]) {
dateRangeTags[dateRange.id] = dateRange;
} else {
for (const attribute in dateRangeTags[dateRange.id]) {
if (!!dateRange[attribute] && JSON.stringify(dateRangeTags[dateRange.id][attribute]) !== JSON.stringify(dateRange[attribute])) {
this.trigger('warn', {
message: 'EXT-X-DATERANGE tags with the same ID in a playlist must have the same attributes values'
});
break;
}
} // if tags with the same ID do not have conflicting attributes, merge them
const dateRangeWithSameId = this.manifest.dateRanges.findIndex(dateRangeToFind => dateRangeToFind.id === dateRange.id);
this.manifest.dateRanges[dateRangeWithSameId] = _extends(this.manifest.dateRanges[dateRangeWithSameId], dateRange);
dateRangeTags[dateRange.id] = _extends(dateRangeTags[dateRange.id], dateRange); // after merging, delete the duplicate dateRange that was added last
this.manifest.dateRanges.pop();
}
},
'independent-segments'() {
this.manifest.independentSegments = true;
},
'i-frames-only'() {
this.manifest.iFramesOnly = true;
this.requiredCompatibilityversion(this.manifest.version, 4);
},
'content-steering'() {
this.manifest.contentSteering = camelCaseKeys(entry.attributes);
this.warnOnMissingAttributes_('#EXT-X-CONTENT-STEERING', entry.attributes, ['SERVER-URI']);
},
/** @this {Parser} */
define() {
this.manifest.definitions = this.manifest.definitions || {};
const addDef = (n, v) => {
if (n in this.manifest.definitions) {
// An EXT-X-DEFINE tag MUST NOT specify the same Variable Name as any other
// EXT-X-DEFINE tag in the same Playlist. Parsers that encounter duplicate
// Variable Name declarations MUST fail to parse the Playlist.
this.trigger('error', {
message: `EXT-X-DEFINE: Duplicate name ${n}`
});
return;
}
this.manifest.definitions[n] = v;
};
if ('QUERYPARAM' in entry.attributes) {
if ('NAME' in entry.attributes || 'IMPORT' in entry.attributes) {
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
// QUERYPARAM attribute, but only one of the three. Otherwise, the
// client MUST fail to parse the Playlist.
this.trigger('error', {
message: 'EXT-X-DEFINE: Invalid attributes'
});
return;
}
const val = this.params.get(entry.attributes.QUERYPARAM);
if (!val) {
// If the QUERYPARAM attribute value does not match any query parameter in
// the URI or the matching parameter has no associated value, the parser
// MUST fail to parse the Playlist. If more than one parameter matches,
// any of the associated values MAY be used.
this.trigger('error', {
message: `EXT-X-DEFINE: No query param ${entry.attributes.QUERYPARAM}`
});
return;
}
addDef(entry.attributes.QUERYPARAM, decodeURIComponent(val));
return;
}
if ('NAME' in entry.attributes) {
if ('IMPORT' in entry.attributes) {
// An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a
// QUERYPARAM attribute, but only one of the three. Otherwise, the
// client MUST fail to parse the Playlist.
this.trigger('error', {
message: 'EXT-X-DEFINE: Invalid attributes'
});
return;
}
if (!('VALUE' in entry.attributes) || typeof entry.attributes.VALUE !== 'string') {
// This attribute is REQUIRED if the EXT-X-DEFINE tag has a NAME attribute.
// The quoted-string MAY be empty.
this.trigger('error', {
message: `EXT-X-DEFINE: No value for ${entry.attributes.NAME}`
});
return;
}
addDef(entry.attributes.NAME, entry.attributes.VALUE);
return;
}
if ('IMPORT' in entry.attributes) {
if (!this.mainDefinitions[entry.attributes.IMPORT]) {
// Covers two conditions, as mainDefinitions will always be empty on main
//
// EXT-X-DEFINE tags containing the IMPORT attribute MUST NOT occur in
// Multivariant Playlists; they are only allowed in Media Playlists.
//
// If the IMPORT attribute value does not match any Variable Name in the
// Multivariant Playlist, or if the Media Playlist loaded from a
// Multivariant Playlist, the parser MUST fail the Playlist.
this.trigger('error', {
message: `EXT-X-DEFINE: No value ${entry.attributes.IMPORT} to import, or IMPORT used on main playlist`
});
return;
}
addDef(entry.attributes.IMPORT, this.mainDefinitions[entry.attributes.IMPORT]);
return;
} // An EXT-X-DEFINE tag MUST contain either a NAME, an IMPORT, or a QUERYPARAM
// attribute, but only one of the three. Otherwise, the client MUST fail to
// parse the Playlist.
this.trigger('error', {
message: 'EXT-X-DEFINE: No attribute'
});
},
'i-frame-playlist'() {
this.manifest.iFramePlaylists.push({
attributes: entry.attributes,
uri: entry.uri,
timeline: currentTimeline
});
this.warnOnMissingAttributes_('#EXT-X-I-FRAME-STREAM-INF', entry.attributes, ['BANDWIDTH', 'URI']);
}
})[entry.tagType] || noop).call(self);
},
uri() {
currentUri.uri = entry.uri;
uris.push(currentUri); // if no explicit duration was declared, use the target duration
if (this.manifest.targetDuration && !('duration' in currentUri)) {
this.trigger('warn', {
message: 'defaulting segment duration to the target duration'
});
currentUri.duration = this.manifest.targetDuration;
} // annotate with encryption information, if necessary
if (key) {
currentUri.key = key;
}
currentUri.timeline = currentTimeline; // annotate with initialization segment information, if necessary
if (currentMap) {
currentUri.map = currentMap;
} // reset the last byterange end as it needs to be 0 between parts
lastPartByterangeEnd = 0; // Once we have at least one program date time we can always extrapolate it forward
if (this.lastProgramDateTime !== null) {
currentUri.programDateTime = this.lastProgramDateTime;
this.lastProgramDateTime += currentUri.duration * 1000;
} // prepare for the next URI
currentUri = {};
},
comment() {// comments are not important for playback
},
custom() {
// if this is segment-level data attach the output to the segment
if (entry.segment) {
currentUri.custom = currentUri.custom || {};
currentUri.custom[entry.customType] = entry.data; // if this is manifest-level data attach to the top level manifest object
} else {
this.manifest.custom = this.manifest.custom || {};
this.manifest.custom[entry.customType] = entry.data;
}
}
})[entry.type].call(self);
});
}
requiredCompatibilityversion(currentVersion, targetVersion) {
if (currentVersion < targetVersion || !currentVersion) {
this.trigger('warn', {
message: `manifest must be at least version ${targetVersion}`
});
}
}
warnOnMissingAttributes_(identifier, attributes, required) {
const missing = [];
required.forEach(function (key) {
if (!attributes.hasOwnProperty(key)) {
missing.push(key);
}
});
if (missing.length) {
this.trigger('warn', {
message: `${identifier} lacks required attribute(s): ${missing.join(', ')}`
});
}
}
/**
* Parse the input string and update the manifest object.
*
* @param {string} chunk a potentially incomplete portion of the manifest
*/
push(chunk) {
this.lineStream.push(chunk);
}
/**
* Flush any remaining input. This can be handy if the last line of an M3U8
* manifest did not contain a trailing newline but the file has been
* completely received.
*/
end() {
// flush any buffered input
this.lineStream.push('\n');
if (this.manifest.dateRanges.length && this.lastProgramDateTime === null) {
this.trigger('warn', {
message: 'A playlist with EXT-X-DATERANGE tag must contain atleast one EXT-X-PROGRAM-DATE-TIME tag'
});
}
this.lastProgramDateTime = null;
this.trigger('end');
}
/**
* Add an additional parser for non-standard tags
*
* @param {Object} options a map of options for the added parser
* @param {RegExp} options.expression a regular expression to match the custom header
* @param {string} options.customType the custom type to register to the output
* @param {Function} [options.dataParser] function to parse the line into an object
* @param {boolean} [options.segment] should tag data be attached to the segment object
*/
addParser(options) {
this.parseStream.addParser(options);
}
/**
* Add a custom header mapper
*
* @param {Object} options
* @param {RegExp} options.expression a regular expression to match the custom header
* @param {Function} options.map function to translate tag into a different tag
*/
addTagMapper(options) {
this.parseStream.addTagMapper(options);
}
}
;// ./node_modules/@videojs/vhs-utils/es/codecs.js
var regexs = {
// to determine mime types
mp4: /^(av0?1|avc0?[1234]|vp0?9|flac|opus|mp3|mp4a|mp4v|stpp.ttml.im1t)/,
webm: /^(vp0?[89]|av0?1|opus|vorbis)/,
ogg: /^(vp0?[89]|theora|flac|opus|vorbis)/,
// to determine if a codec is audio or video
video: /^(av0?1|avc0?[1234]|vp0?[89]|hvc1|hev1|theora|mp4v)/,
audio: /^(mp4a|flac|vorbis|opus|ac-[34]|ec-3|alac|mp3|speex|aac)/,
text: /^(stpp.ttml.im1t)/,
// mux.js support regex
muxerVideo: /^(avc0?1)/,
muxerAudio: /^(mp4a)/,
// match nothing as muxer does not support text right now.
// there cannot never be a character before the start of a string
// so this matches nothing.
muxerText: /a^/
};
var mediaTypes = ['video', 'audio', 'text'];
var upperMediaTypes = ['Video', 'Audio', 'Text'];
/**
* Replace the old apple-style `avc1.
.` codec string with the standard
* `avc1.`
*
* @param {string} codec
* Codec string to translate
* @return {string}
* The translated codec string
*/
var translateLegacyCodec = function translateLegacyCodec(codec) {
if (!codec) {
return codec;
}
return codec.replace(/avc1\.(\d+)\.(\d+)/i, function (orig, profile, avcLevel) {
var profileHex = ('00' + Number(profile).toString(16)).slice(-2);
var avcLevelHex = ('00' + Number(avcLevel).toString(16)).slice(-2);
return 'avc1.' + profileHex + '00' + avcLevelHex;
});
};
/**
* Replace the old apple-style `avc1..` codec strings with the standard
* `avc1.`
*
* @param {string[]} codecs
* An array of codec strings to translate
* @return {string[]}
* The translated array of codec strings
*/
var translateLegacyCodecs = function translateLegacyCodecs(codecs) {
return codecs.map(translateLegacyCodec);
};
/**
* Replace codecs in the codec string with the old apple-style `avc1..` to the
* standard `avc1.`.
*
* @param {string} codecString
* The codec string
* @return {string}
* The codec string with old apple-style codecs replaced
*
* @private
*/
var mapLegacyAvcCodecs = function mapLegacyAvcCodecs(codecString) {
return codecString.replace(/avc1\.(\d+)\.(\d+)/i, function (match) {
return translateLegacyCodecs([match])[0];
});
};
/**
* @typedef {Object} ParsedCodecInfo
* @property {number} codecCount
* Number of codecs parsed
* @property {string} [videoCodec]
* Parsed video codec (if found)
* @property {string} [videoObjectTypeIndicator]
* Video object type indicator (if found)
* @property {string|null} audioProfile
* Audio profile
*/
/**
* Parses a codec string to retrieve the number of codecs specified, the video codec and
* object type indicator, and the audio profile.
*
* @param {string} [codecString]
* The codec string to parse
* @return {ParsedCodecInfo}
* Parsed codec info
*/
var parseCodecs = function parseCodecs(codecString) {
if (codecString === void 0) {
codecString = '';
}
var codecs = codecString.split(',');
var result = [];
codecs.forEach(function (codec) {
codec = codec.trim();
var codecType;
mediaTypes.forEach(function (name) {
var match = regexs[name].exec(codec.toLowerCase());
if (!match || match.length <= 1) {
return;
}
codecType = name; // maintain codec case
var type = codec.substring(0, match[1].length);
var details = codec.replace(type, '');
result.push({
type: type,
details: details,
mediaType: name
});
});
if (!codecType) {
result.push({
type: codec,
details: '',
mediaType: 'unknown'
});
}
});
return result;
};
/**
* Returns a ParsedCodecInfo object for the default alternate audio playlist if there is
* a default alternate audio playlist for the provided audio group.
*
* @param {Object} master
* The master playlist
* @param {string} audioGroupId
* ID of the audio group for which to find the default codec info
* @return {ParsedCodecInfo}
* Parsed codec info
*/
var codecsFromDefault = function codecsFromDefault(master, audioGroupId) {
if (!master.mediaGroups.AUDIO || !audioGroupId) {
return null;
}
var audioGroup = master.mediaGroups.AUDIO[audioGroupId];
if (!audioGroup) {
return null;
}
for (var name in audioGroup) {
var audioType = audioGroup[name];
if (audioType.default && audioType.playlists) {
// codec should be the same for all playlists within the audio type
return parseCodecs(audioType.playlists[0].attributes.CODECS);
}
}
return null;
};
var isVideoCodec = function isVideoCodec(codec) {
if (codec === void 0) {
codec = '';
}
return regexs.video.test(codec.trim().toLowerCase());
};
var isAudioCodec = function isAudioCodec(codec) {
if (codec === void 0) {
codec = '';
}
return regexs.audio.test(codec.trim().toLowerCase());
};
var isTextCodec = function isTextCodec(codec) {
if (codec === void 0) {
codec = '';
}
return regexs.text.test(codec.trim().toLowerCase());
};
var getMimeForCodec = function getMimeForCodec(codecString) {
if (!codecString || typeof codecString !== 'string') {
return;
}
var codecs = codecString.toLowerCase().split(',').map(function (c) {
return translateLegacyCodec(c.trim());
}); // default to video type
var type = 'video'; // only change to audio type if the only codec we have is
// audio
if (codecs.length === 1 && isAudioCodec(codecs[0])) {
type = 'audio';
} else if (codecs.length === 1 && isTextCodec(codecs[0])) {
// text uses application/ for now
type = 'application';
} // default the container to mp4
var container = 'mp4'; // every codec must be able to go into the container
// for that container to be the correct one
if (codecs.every(function (c) {
return regexs.mp4.test(c);
})) {
container = 'mp4';
} else if (codecs.every(function (c) {
return regexs.webm.test(c);
})) {
container = 'webm';
} else if (codecs.every(function (c) {
return regexs.ogg.test(c);
})) {
container = 'ogg';
}
return type + "/" + container + ";codecs=\"" + codecString + "\"";
};
/**
* Tests whether the codec is supported by MediaSource. Optionally also tests ManagedMediaSource.
*
* @param {string} codecString
* Codec to test
* @param {boolean} [withMMS]
* Whether to check if ManagedMediaSource supports it
* @return {boolean}
* Codec is supported
*/
var browserSupportsCodec = function browserSupportsCodec(codecString, withMMS) {
if (codecString === void 0) {
codecString = '';
}
if (withMMS === void 0) {
withMMS = false;
}
return (window_default()).MediaSource && (window_default()).MediaSource.isTypeSupported && window_default().MediaSource.isTypeSupported(getMimeForCodec(codecString)) || withMMS && (window_default()).ManagedMediaSource && (window_default()).ManagedMediaSource.isTypeSupported && window_default().ManagedMediaSource.isTypeSupported(getMimeForCodec(codecString)) || false;
};
var muxerSupportsCodec = function muxerSupportsCodec(codecString) {
if (codecString === void 0) {
codecString = '';
}
return codecString.toLowerCase().split(',').every(function (codec) {
codec = codec.trim(); // any match is supported.
for (var i = 0; i < upperMediaTypes.length; i++) {
var type = upperMediaTypes[i];
if (regexs["muxer" + type].test(codec)) {
return true;
}
}
return false;
});
};
var DEFAULT_AUDIO_CODEC = 'mp4a.40.2';
var DEFAULT_VIDEO_CODEC = 'avc1.4d400d';
;// ./node_modules/@videojs/vhs-utils/es/media-types.js
var MPEGURL_REGEX = /^(audio|video|application)\/(x-|vnd\.apple\.)?mpegurl/i;
var DASH_REGEX = /^application\/dash\+xml/i;
/**
* Returns a string that describes the type of source based on a video source object's
* media type.
*
* @see {@link https://dev.w3.org/html5/pf-summary/video.html#dom-source-type|Source Type}
*
* @param {string} type
* Video source object media type
* @return {('hls'|'dash'|'vhs-json'|null)}
* VHS source type string
*/
var simpleTypeFromSourceType = function simpleTypeFromSourceType(type) {
if (MPEGURL_REGEX.test(type)) {
return 'hls';
}
if (DASH_REGEX.test(type)) {
return 'dash';
} // Denotes the special case of a manifest object passed to http-streaming instead of a
// source URL.
//
// See https://en.wikipedia.org/wiki/Media_type for details on specifying media types.
//
// In this case, vnd stands for vendor, video.js for the organization, VHS for this
// project, and the +json suffix identifies the structure of the media type.
if (type === 'application/vnd.videojs.vhs+json') {
return 'vhs-json';
}
return null;
};
;// ./node_modules/@videojs/vhs-utils/es/byte-helpers.js
// const log2 = Math.log2 ? Math.log2 : (x) => (Math.log(x) / Math.log(2));
var repeat = function repeat(str, len) {
var acc = '';
while (len--) {
acc += str;
}
return acc;
}; // count the number of bits it would take to represent a number
// we used to do this with log2 but BigInt does not support builtin math
// Math.ceil(log2(x));
var countBits = function countBits(x) {
return x.toString(2).length;
}; // count the number of whole bytes it would take to represent a number
var countBytes = function countBytes(x) {
return Math.ceil(countBits(x) / 8);
};
var byte_helpers_padStart = function padStart(b, len, str) {
if (str === void 0) {
str = ' ';
}
return (repeat(str, len) + b.toString()).slice(-len);
};
var isArrayBufferView = function isArrayBufferView(obj) {
if (ArrayBuffer.isView === 'function') {
return ArrayBuffer.isView(obj);
}
return obj && obj.buffer instanceof ArrayBuffer;
};
var isTypedArray = function isTypedArray(obj) {
return isArrayBufferView(obj);
};
var byte_helpers_toUint8 = function toUint8(bytes) {
if (bytes instanceof Uint8Array) {
return bytes;
}
if (!Array.isArray(bytes) && !isTypedArray(bytes) && !(bytes instanceof ArrayBuffer)) {
// any non-number or NaN leads to empty uint8array
// eslint-disable-next-line
if (typeof bytes !== 'number' || typeof bytes === 'number' && bytes !== bytes) {
bytes = 0;
} else {
bytes = [bytes];
}
}
return new Uint8Array(bytes && bytes.buffer || bytes, bytes && bytes.byteOffset || 0, bytes && bytes.byteLength || 0);
};
var byte_helpers_toHexString = function toHexString(bytes) {
bytes = byte_helpers_toUint8(bytes);
var str = '';
for (var i = 0; i < bytes.length; i++) {
str += byte_helpers_padStart(bytes[i].toString(16), 2, '0');
}
return str;
};
var byte_helpers_toBinaryString = function toBinaryString(bytes) {
bytes = byte_helpers_toUint8(bytes);
var str = '';
for (var i = 0; i < bytes.length; i++) {
str += byte_helpers_padStart(bytes[i].toString(2), 8, '0');
}
return str;
};
var BigInt = (window_default()).BigInt || Number;
var BYTE_TABLE = [BigInt('0x1'), BigInt('0x100'), BigInt('0x10000'), BigInt('0x1000000'), BigInt('0x100000000'), BigInt('0x10000000000'), BigInt('0x1000000000000'), BigInt('0x100000000000000'), BigInt('0x10000000000000000')];
var ENDIANNESS = function () {
var a = new Uint16Array([0xFFCC]);
var b = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
if (b[0] === 0xFF) {
return 'big';
}
if (b[0] === 0xCC) {
return 'little';
}
return 'unknown';
}();
var IS_BIG_ENDIAN = ENDIANNESS === 'big';
var IS_LITTLE_ENDIAN = ENDIANNESS === 'little';
var byte_helpers_bytesToNumber = function bytesToNumber(bytes, _temp) {
var _ref = _temp === void 0 ? {} : _temp,
_ref$signed = _ref.signed,
signed = _ref$signed === void 0 ? false : _ref$signed,
_ref$le = _ref.le,
le = _ref$le === void 0 ? false : _ref$le;
bytes = byte_helpers_toUint8(bytes);
var fn = le ? 'reduce' : 'reduceRight';
var obj = bytes[fn] ? bytes[fn] : Array.prototype[fn];
var number = obj.call(bytes, function (total, byte, i) {
var exponent = le ? i : Math.abs(i + 1 - bytes.length);
return total + BigInt(byte) * BYTE_TABLE[exponent];
}, BigInt(0));
if (signed) {
var max = BYTE_TABLE[bytes.length] / BigInt(2) - BigInt(1);
number = BigInt(number);
if (number > max) {
number -= max;
number -= max;
number -= BigInt(2);
}
}
return Number(number);
};
var numberToBytes = function numberToBytes(number, _temp2) {
var _ref2 = _temp2 === void 0 ? {} : _temp2,
_ref2$le = _ref2.le,
le = _ref2$le === void 0 ? false : _ref2$le;
// eslint-disable-next-line
if (typeof number !== 'bigint' && typeof number !== 'number' || typeof number === 'number' && number !== number) {
number = 0;
}
number = BigInt(number);
var byteCount = countBytes(number);
var bytes = new Uint8Array(new ArrayBuffer(byteCount));
for (var i = 0; i < byteCount; i++) {
var byteIndex = le ? i : Math.abs(i + 1 - bytes.length);
bytes[byteIndex] = Number(number / BYTE_TABLE[i] & BigInt(0xFF));
if (number < 0) {
bytes[byteIndex] = Math.abs(~bytes[byteIndex]);
bytes[byteIndex] -= i === 0 ? 1 : 2;
}
}
return bytes;
};
var byte_helpers_bytesToString = function bytesToString(bytes) {
if (!bytes) {
return '';
} // TODO: should toUint8 handle cases where we only have 8 bytes
// but report more since this is a Uint16+ Array?
bytes = Array.prototype.slice.call(bytes);
var string = String.fromCharCode.apply(null, byte_helpers_toUint8(bytes));
try {
return decodeURIComponent(escape(string));
} catch (e) {// if decodeURIComponent/escape fails, we are dealing with partial
// or full non string data. Just return the potentially garbled string.
}
return string;
};
var stringToBytes = function stringToBytes(string, stringIsBytes) {
if (typeof string !== 'string' && string && typeof string.toString === 'function') {
string = string.toString();
}
if (typeof string !== 'string') {
return new Uint8Array();
} // If the string already is bytes, we don't have to do this
// otherwise we do this so that we split multi length characters
// into individual bytes
if (!stringIsBytes) {
string = unescape(encodeURIComponent(string));
}
var view = new Uint8Array(string.length);
for (var i = 0; i < string.length; i++) {
view[i] = string.charCodeAt(i);
}
return view;
};
var concatTypedArrays = function concatTypedArrays() {
for (var _len = arguments.length, buffers = new Array(_len), _key = 0; _key < _len; _key++) {
buffers[_key] = arguments[_key];
}
buffers = buffers.filter(function (b) {
return b && (b.byteLength || b.length) && typeof b !== 'string';
});
if (buffers.length <= 1) {
// for 0 length we will return empty uint8
// for 1 length we return the first uint8
return byte_helpers_toUint8(buffers[0]);
}
var totalLen = buffers.reduce(function (total, buf, i) {
return total + (buf.byteLength || buf.length);
}, 0);
var tempBuffer = new Uint8Array(totalLen);
var offset = 0;
buffers.forEach(function (buf) {
buf = byte_helpers_toUint8(buf);
tempBuffer.set(buf, offset);
offset += buf.byteLength;
});
return tempBuffer;
};
/**
* Check if the bytes "b" are contained within bytes "a".
*
* @param {Uint8Array|Array} a
* Bytes to check in
*
* @param {Uint8Array|Array} b
* Bytes to check for
*
* @param {Object} options
* options
*
* @param {Array|Uint8Array} [offset=0]
* offset to use when looking at bytes in a
*
* @param {Array|Uint8Array} [mask=[]]
* mask to use on bytes before comparison.
*
* @return {boolean}
* If all bytes in b are inside of a, taking into account
* bit masks.
*/
var byte_helpers_bytesMatch = function bytesMatch(a, b, _temp3) {
var _ref3 = _temp3 === void 0 ? {} : _temp3,
_ref3$offset = _ref3.offset,
offset = _ref3$offset === void 0 ? 0 : _ref3$offset,
_ref3$mask = _ref3.mask,
mask = _ref3$mask === void 0 ? [] : _ref3$mask;
a = byte_helpers_toUint8(a);
b = byte_helpers_toUint8(b); // ie 11 does not support uint8 every
var fn = b.every ? b.every : Array.prototype.every;
return b.length && a.length - offset >= b.length && // ie 11 doesn't support every on uin8
fn.call(b, function (bByte, i) {
var aByte = mask[i] ? mask[i] & a[offset + i] : a[offset + i];
return bByte === aByte;
});
};
var sliceBytes = function sliceBytes(src, start, end) {
if (Uint8Array.prototype.slice) {
return Uint8Array.prototype.slice.call(src, start, end);
}
return new Uint8Array(Array.prototype.slice.call(src, start, end));
};
var reverseBytes = function reverseBytes(src) {
if (src.reverse) {
return src.reverse();
}
return Array.prototype.reverse.call(src);
};
;// ./node_modules/@videojs/vhs-utils/es/media-groups.js
/**
* Loops through all supported media groups in master and calls the provided
* callback for each group
*
* @param {Object} master
* The parsed master manifest object
* @param {string[]} groups
* The media groups to call the callback for
* @param {Function} callback
* Callback to call for each media group
*/
var forEachMediaGroup = function forEachMediaGroup(master, groups, callback) {
groups.forEach(function (mediaType) {
for (var groupKey in master.mediaGroups[mediaType]) {
for (var labelKey in master.mediaGroups[mediaType][groupKey]) {
var mediaProperties = master.mediaGroups[mediaType][groupKey][labelKey];
callback(mediaProperties, mediaType, groupKey, labelKey);
}
}
});
};
// EXTERNAL MODULE: ./node_modules/@xmldom/xmldom/lib/index.js
var xmldom_lib = __webpack_require__(978);
;// ./node_modules/mpd-parser/dist/mpd-parser.es.js
/*! @name mpd-parser @version 1.3.1 @license Apache-2.0 */
var version = "1.3.1";
const mpd_parser_es_isObject = obj => {
return !!obj && typeof obj === 'object';
};
const merge = (...objects) => {
return objects.reduce((result, source) => {
if (typeof source !== 'object') {
return result;
}
Object.keys(source).forEach(key => {
if (Array.isArray(result[key]) && Array.isArray(source[key])) {
result[key] = result[key].concat(source[key]);
} else if (mpd_parser_es_isObject(result[key]) && mpd_parser_es_isObject(source[key])) {
result[key] = merge(result[key], source[key]);
} else {
result[key] = source[key];
}
});
return result;
}, {});
};
const values = o => Object.keys(o).map(k => o[k]);
const range = (start, end) => {
const result = [];
for (let i = start; i < end; i++) {
result.push(i);
}
return result;
};
const flatten = lists => lists.reduce((x, y) => x.concat(y), []);
const from = list => {
if (!list.length) {
return [];
}
const result = [];
for (let i = 0; i < list.length; i++) {
result.push(list[i]);
}
return result;
};
const findIndexes = (l, key) => l.reduce((a, e, i) => {
if (e[key]) {
a.push(i);
}
return a;
}, []);
/**
* Returns a union of the included lists provided each element can be identified by a key.
*
* @param {Array} list - list of lists to get the union of
* @param {Function} keyFunction - the function to use as a key for each element
*
* @return {Array} the union of the arrays
*/
const union = (lists, keyFunction) => {
return values(lists.reduce((acc, list) => {
list.forEach(el => {
acc[keyFunction(el)] = el;
});
return acc;
}, {}));
};
var errors = {
INVALID_NUMBER_OF_PERIOD: 'INVALID_NUMBER_OF_PERIOD',
INVALID_NUMBER_OF_CONTENT_STEERING: 'INVALID_NUMBER_OF_CONTENT_STEERING',
DASH_EMPTY_MANIFEST: 'DASH_EMPTY_MANIFEST',
DASH_INVALID_XML: 'DASH_INVALID_XML',
NO_BASE_URL: 'NO_BASE_URL',
MISSING_SEGMENT_INFORMATION: 'MISSING_SEGMENT_INFORMATION',
SEGMENT_TIME_UNSPECIFIED: 'SEGMENT_TIME_UNSPECIFIED',
UNSUPPORTED_UTC_TIMING_SCHEME: 'UNSUPPORTED_UTC_TIMING_SCHEME'
};
/**
* @typedef {Object} SingleUri
* @property {string} uri - relative location of segment
* @property {string} resolvedUri - resolved location of segment
* @property {Object} byterange - Object containing information on how to make byte range
* requests following byte-range-spec per RFC2616.
* @property {String} byterange.length - length of range request
* @property {String} byterange.offset - byte offset of range request
*
* @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.1
*/
/**
* Converts a URLType node (5.3.9.2.3 Table 13) to a segment object
* that conforms to how m3u8-parser is structured
*
* @see https://github.com/videojs/m3u8-parser
*
* @param {string} baseUrl - baseUrl provided by nodes
* @param {string} source - source url for segment
* @param {string} range - optional range used for range calls,
* follows RFC 2616, Clause 14.35.1
* @return {SingleUri} full segment information transformed into a format similar
* to m3u8-parser
*/
const urlTypeToSegment = ({
baseUrl = '',
source = '',
range = '',
indexRange = ''
}) => {
const segment = {
uri: source,
resolvedUri: resolve_url(baseUrl || '', source)
};
if (range || indexRange) {
const rangeStr = range ? range : indexRange;
const ranges = rangeStr.split('-'); // default to parsing this as a BigInt if possible
let startRange = (window_default()).BigInt ? window_default().BigInt(ranges[0]) : parseInt(ranges[0], 10);
let endRange = (window_default()).BigInt ? window_default().BigInt(ranges[1]) : parseInt(ranges[1], 10); // convert back to a number if less than MAX_SAFE_INTEGER
if (startRange < Number.MAX_SAFE_INTEGER && typeof startRange === 'bigint') {
startRange = Number(startRange);
}
if (endRange < Number.MAX_SAFE_INTEGER && typeof endRange === 'bigint') {
endRange = Number(endRange);
}
let length;
if (typeof endRange === 'bigint' || typeof startRange === 'bigint') {
length = window_default().BigInt(endRange) - window_default().BigInt(startRange) + window_default().BigInt(1);
} else {
length = endRange - startRange + 1;
}
if (typeof length === 'bigint' && length < Number.MAX_SAFE_INTEGER) {
length = Number(length);
} // byterange should be inclusive according to
// RFC 2616, Clause 14.35.1
segment.byterange = {
length,
offset: startRange
};
}
return segment;
};
const byteRangeToString = byterange => {
// `endRange` is one less than `offset + length` because the HTTP range
// header uses inclusive ranges
let endRange;
if (typeof byterange.offset === 'bigint' || typeof byterange.length === 'bigint') {
endRange = window_default().BigInt(byterange.offset) + window_default().BigInt(byterange.length) - window_default().BigInt(1);
} else {
endRange = byterange.offset + byterange.length - 1;
}
return `${byterange.offset}-${endRange}`;
};
/**
* parse the end number attribue that can be a string
* number, or undefined.
*
* @param {string|number|undefined} endNumber
* The end number attribute.
*
* @return {number|null}
* The result of parsing the end number.
*/
const parseEndNumber = endNumber => {
if (endNumber && typeof endNumber !== 'number') {
endNumber = parseInt(endNumber, 10);
}
if (isNaN(endNumber)) {
return null;
}
return endNumber;
};
/**
* Functions for calculating the range of available segments in static and dynamic
* manifests.
*/
const segmentRange = {
/**
* Returns the entire range of available segments for a static MPD
*
* @param {Object} attributes
* Inheritied MPD attributes
* @return {{ start: number, end: number }}
* The start and end numbers for available segments
*/
static(attributes) {
const {
duration,
timescale = 1,
sourceDuration,
periodDuration
} = attributes;
const endNumber = parseEndNumber(attributes.endNumber);
const segmentDuration = duration / timescale;
if (typeof endNumber === 'number') {
return {
start: 0,
end: endNumber
};
}
if (typeof periodDuration === 'number') {
return {
start: 0,
end: periodDuration / segmentDuration
};
}
return {
start: 0,
end: sourceDuration / segmentDuration
};
},
/**
* Returns the current live window range of available segments for a dynamic MPD
*
* @param {Object} attributes
* Inheritied MPD attributes
* @return {{ start: number, end: number }}
* The start and end numbers for available segments
*/
dynamic(attributes) {
const {
NOW,
clientOffset,
availabilityStartTime,
timescale = 1,
duration,
periodStart = 0,
minimumUpdatePeriod = 0,
timeShiftBufferDepth = Infinity
} = attributes;
const endNumber = parseEndNumber(attributes.endNumber); // clientOffset is passed in at the top level of mpd-parser and is an offset calculated
// after retrieving UTC server time.
const now = (NOW + clientOffset) / 1000; // WC stands for Wall Clock.
// Convert the period start time to EPOCH.
const periodStartWC = availabilityStartTime + periodStart; // Period end in EPOCH is manifest's retrieval time + time until next update.
const periodEndWC = now + minimumUpdatePeriod;
const periodDuration = periodEndWC - periodStartWC;
const segmentCount = Math.ceil(periodDuration * timescale / duration);
const availableStart = Math.floor((now - periodStartWC - timeShiftBufferDepth) * timescale / duration);
const availableEnd = Math.floor((now - periodStartWC) * timescale / duration);
return {
start: Math.max(0, availableStart),
end: typeof endNumber === 'number' ? endNumber : Math.min(segmentCount, availableEnd)
};
}
};
/**
* Maps a range of numbers to objects with information needed to build the corresponding
* segment list
*
* @name toSegmentsCallback
* @function
* @param {number} number
* Number of the segment
* @param {number} index
* Index of the number in the range list
* @return {{ number: Number, duration: Number, timeline: Number, time: Number }}
* Object with segment timing and duration info
*/
/**
* Returns a callback for Array.prototype.map for mapping a range of numbers to
* information needed to build the segment list.
*
* @param {Object} attributes
* Inherited MPD attributes
* @return {toSegmentsCallback}
* Callback map function
*/
const toSegments = attributes => number => {
const {
duration,
timescale = 1,
periodStart,
startNumber = 1
} = attributes;
return {
number: startNumber + number,
duration: duration / timescale,
timeline: periodStart,
time: number * duration
};
};
/**
* Returns a list of objects containing segment timing and duration info used for
* building the list of segments. This uses the @duration attribute specified
* in the MPD manifest to derive the range of segments.
*
* @param {Object} attributes
* Inherited MPD attributes
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
const parseByDuration = attributes => {
const {
type,
duration,
timescale = 1,
periodDuration,
sourceDuration
} = attributes;
const {
start,
end
} = segmentRange[type](attributes);
const segments = range(start, end).map(toSegments(attributes));
if (type === 'static') {
const index = segments.length - 1; // section is either a period or the full source
const sectionDuration = typeof periodDuration === 'number' ? periodDuration : sourceDuration; // final segment may be less than full segment duration
segments[index].duration = sectionDuration - duration / timescale * index;
}
return segments;
};
/**
* Translates SegmentBase into a set of segments.
* (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each
* node should be translated into segment.
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @return {Object.} list of segments
*/
const segmentsFromBase = attributes => {
const {
baseUrl,
initialization = {},
sourceDuration,
indexRange = '',
periodStart,
presentationTime,
number = 0,
duration
} = attributes; // base url is required for SegmentBase to work, per spec (Section 5.3.9.2.1)
if (!baseUrl) {
throw new Error(errors.NO_BASE_URL);
}
const initSegment = urlTypeToSegment({
baseUrl,
source: initialization.sourceURL,
range: initialization.range
});
const segment = urlTypeToSegment({
baseUrl,
source: baseUrl,
indexRange
});
segment.map = initSegment; // If there is a duration, use it, otherwise use the given duration of the source
// (since SegmentBase is only for one total segment)
if (duration) {
const segmentTimeInfo = parseByDuration(attributes);
if (segmentTimeInfo.length) {
segment.duration = segmentTimeInfo[0].duration;
segment.timeline = segmentTimeInfo[0].timeline;
}
} else if (sourceDuration) {
segment.duration = sourceDuration;
segment.timeline = periodStart;
} // If presentation time is provided, these segments are being generated by SIDX
// references, and should use the time provided. For the general case of SegmentBase,
// there should only be one segment in the period, so its presentation time is the same
// as its period start.
segment.presentationTime = presentationTime || periodStart;
segment.number = number;
return [segment];
};
/**
* Given a playlist, a sidx box, and a baseUrl, update the segment list of the playlist
* according to the sidx information given.
*
* playlist.sidx has metadadata about the sidx where-as the sidx param
* is the parsed sidx box itself.
*
* @param {Object} playlist the playlist to update the sidx information for
* @param {Object} sidx the parsed sidx box
* @return {Object} the playlist object with the updated sidx information
*/
const addSidxSegmentsToPlaylist$1 = (playlist, sidx, baseUrl) => {
// Retain init segment information
const initSegment = playlist.sidx.map ? playlist.sidx.map : null; // Retain source duration from initial main manifest parsing
const sourceDuration = playlist.sidx.duration; // Retain source timeline
const timeline = playlist.timeline || 0;
const sidxByteRange = playlist.sidx.byterange;
const sidxEnd = sidxByteRange.offset + sidxByteRange.length; // Retain timescale of the parsed sidx
const timescale = sidx.timescale; // referenceType 1 refers to other sidx boxes
const mediaReferences = sidx.references.filter(r => r.referenceType !== 1);
const segments = [];
const type = playlist.endList ? 'static' : 'dynamic';
const periodStart = playlist.sidx.timeline;
let presentationTime = periodStart;
let number = playlist.mediaSequence || 0; // firstOffset is the offset from the end of the sidx box
let startIndex; // eslint-disable-next-line
if (typeof sidx.firstOffset === 'bigint') {
startIndex = window_default().BigInt(sidxEnd) + sidx.firstOffset;
} else {
startIndex = sidxEnd + sidx.firstOffset;
}
for (let i = 0; i < mediaReferences.length; i++) {
const reference = sidx.references[i]; // size of the referenced (sub)segment
const size = reference.referencedSize; // duration of the referenced (sub)segment, in the timescale
// this will be converted to seconds when generating segments
const duration = reference.subsegmentDuration; // should be an inclusive range
let endIndex; // eslint-disable-next-line
if (typeof startIndex === 'bigint') {
endIndex = startIndex + window_default().BigInt(size) - window_default().BigInt(1);
} else {
endIndex = startIndex + size - 1;
}
const indexRange = `${startIndex}-${endIndex}`;
const attributes = {
baseUrl,
timescale,
timeline,
periodStart,
presentationTime,
number,
duration,
sourceDuration,
indexRange,
type
};
const segment = segmentsFromBase(attributes)[0];
if (initSegment) {
segment.map = initSegment;
}
segments.push(segment);
if (typeof startIndex === 'bigint') {
startIndex += window_default().BigInt(size);
} else {
startIndex += size;
}
presentationTime += duration / timescale;
number++;
}
playlist.segments = segments;
return playlist;
};
const SUPPORTED_MEDIA_TYPES = ['AUDIO', 'SUBTITLES']; // allow one 60fps frame as leniency (arbitrarily chosen)
const TIME_FUDGE = 1 / 60;
/**
* Given a list of timelineStarts, combines, dedupes, and sorts them.
*
* @param {TimelineStart[]} timelineStarts - list of timeline starts
*
* @return {TimelineStart[]} the combined and deduped timeline starts
*/
const getUniqueTimelineStarts = timelineStarts => {
return union(timelineStarts, ({
timeline
}) => timeline).sort((a, b) => a.timeline > b.timeline ? 1 : -1);
};
/**
* Finds the playlist with the matching NAME attribute.
*
* @param {Array} playlists - playlists to search through
* @param {string} name - the NAME attribute to search for
*
* @return {Object|null} the matching playlist object, or null
*/
const findPlaylistWithName = (playlists, name) => {
for (let i = 0; i < playlists.length; i++) {
if (playlists[i].attributes.NAME === name) {
return playlists[i];
}
}
return null;
};
/**
* Gets a flattened array of media group playlists.
*
* @param {Object} manifest - the main manifest object
*
* @return {Array} the media group playlists
*/
const getMediaGroupPlaylists = manifest => {
let mediaGroupPlaylists = [];
forEachMediaGroup(manifest, SUPPORTED_MEDIA_TYPES, (properties, type, group, label) => {
mediaGroupPlaylists = mediaGroupPlaylists.concat(properties.playlists || []);
});
return mediaGroupPlaylists;
};
/**
* Updates the playlist's media sequence numbers.
*
* @param {Object} config - options object
* @param {Object} config.playlist - the playlist to update
* @param {number} config.mediaSequence - the mediaSequence number to start with
*/
const updateMediaSequenceForPlaylist = ({
playlist,
mediaSequence
}) => {
playlist.mediaSequence = mediaSequence;
playlist.segments.forEach((segment, index) => {
segment.number = playlist.mediaSequence + index;
});
};
/**
* Updates the media and discontinuity sequence numbers of newPlaylists given oldPlaylists
* and a complete list of timeline starts.
*
* If no matching playlist is found, only the discontinuity sequence number of the playlist
* will be updated.
*
* Since early available timelines are not supported, at least one segment must be present.
*
* @param {Object} config - options object
* @param {Object[]} oldPlaylists - the old playlists to use as a reference
* @param {Object[]} newPlaylists - the new playlists to update
* @param {Object} timelineStarts - all timelineStarts seen in the stream to this point
*/
const updateSequenceNumbers = ({
oldPlaylists,
newPlaylists,
timelineStarts
}) => {
newPlaylists.forEach(playlist => {
playlist.discontinuitySequence = timelineStarts.findIndex(function ({
timeline
}) {
return timeline === playlist.timeline;
}); // Playlists NAMEs come from DASH Representation IDs, which are mandatory
// (see ISO_23009-1-2012 5.3.5.2).
//
// If the same Representation existed in a prior Period, it will retain the same NAME.
const oldPlaylist = findPlaylistWithName(oldPlaylists, playlist.attributes.NAME);
if (!oldPlaylist) {
// Since this is a new playlist, the media sequence values can start from 0 without
// consequence.
return;
} // TODO better support for live SIDX
//
// As of this writing, mpd-parser does not support multiperiod SIDX (in live or VOD).
// This is evident by a playlist only having a single SIDX reference. In a multiperiod
// playlist there would need to be multiple SIDX references. In addition, live SIDX is
// not supported when the SIDX properties change on refreshes.
//
// In the future, if support needs to be added, the merging logic here can be called
// after SIDX references are resolved. For now, exit early to prevent exceptions being
// thrown due to undefined references.
if (playlist.sidx) {
return;
} // Since we don't yet support early available timelines, we don't need to support
// playlists with no segments.
const firstNewSegment = playlist.segments[0];
const oldMatchingSegmentIndex = oldPlaylist.segments.findIndex(function (oldSegment) {
return Math.abs(oldSegment.presentationTime - firstNewSegment.presentationTime) < TIME_FUDGE;
}); // No matching segment from the old playlist means the entire playlist was refreshed.
// In this case the media sequence should account for this update, and the new segments
// should be marked as discontinuous from the prior content, since the last prior
// timeline was removed.
if (oldMatchingSegmentIndex === -1) {
updateMediaSequenceForPlaylist({
playlist,
mediaSequence: oldPlaylist.mediaSequence + oldPlaylist.segments.length
});
playlist.segments[0].discontinuity = true;
playlist.discontinuityStarts.unshift(0); // No matching segment does not necessarily mean there's missing content.
//
// If the new playlist's timeline is the same as the last seen segment's timeline,
// then a discontinuity can be added to identify that there's potentially missing
// content. If there's no missing content, the discontinuity should still be rather
// harmless. It's possible that if segment durations are accurate enough, that the
// existence of a gap can be determined using the presentation times and durations,
// but if the segment timing info is off, it may introduce more problems than simply
// adding the discontinuity.
//
// If the new playlist's timeline is different from the last seen segment's timeline,
// then a discontinuity can be added to identify that this is the first seen segment
// of a new timeline. However, the logic at the start of this function that
// determined the disconinuity sequence by timeline index is now off by one (the
// discontinuity of the newest timeline hasn't yet fallen off the manifest...since
// we added it), so the disconinuity sequence must be decremented.
//
// A period may also have a duration of zero, so the case of no segments is handled
// here even though we don't yet support early available periods.
if (!oldPlaylist.segments.length && playlist.timeline > oldPlaylist.timeline || oldPlaylist.segments.length && playlist.timeline > oldPlaylist.segments[oldPlaylist.segments.length - 1].timeline) {
playlist.discontinuitySequence--;
}
return;
} // If the first segment matched with a prior segment on a discontinuity (it's matching
// on the first segment of a period), then the discontinuitySequence shouldn't be the
// timeline's matching one, but instead should be the one prior, and the first segment
// of the new manifest should be marked with a discontinuity.
//
// The reason for this special case is that discontinuity sequence shows how many
// discontinuities have fallen off of the playlist, and discontinuities are marked on
// the first segment of a new "timeline." Because of this, while DASH will retain that
// Period while the "timeline" exists, HLS keeps track of it via the discontinuity
// sequence, and that first segment is an indicator, but can be removed before that
// timeline is gone.
const oldMatchingSegment = oldPlaylist.segments[oldMatchingSegmentIndex];
if (oldMatchingSegment.discontinuity && !firstNewSegment.discontinuity) {
firstNewSegment.discontinuity = true;
playlist.discontinuityStarts.unshift(0);
playlist.discontinuitySequence--;
}
updateMediaSequenceForPlaylist({
playlist,
mediaSequence: oldPlaylist.segments[oldMatchingSegmentIndex].number
});
});
};
/**
* Given an old parsed manifest object and a new parsed manifest object, updates the
* sequence and timing values within the new manifest to ensure that it lines up with the
* old.
*
* @param {Array} oldManifest - the old main manifest object
* @param {Array} newManifest - the new main manifest object
*
* @return {Object} the updated new manifest object
*/
const positionManifestOnTimeline = ({
oldManifest,
newManifest
}) => {
// Starting from v4.1.2 of the IOP, section 4.4.3.3 states:
//
// "MPD@availabilityStartTime and Period@start shall not be changed over MPD updates."
//
// This was added from https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/160
//
// Because of this change, and the difficulty of supporting periods with changing start
// times, periods with changing start times are not supported. This makes the logic much
// simpler, since periods with the same start time can be considerred the same period
// across refreshes.
//
// To give an example as to the difficulty of handling periods where the start time may
// change, if a single period manifest is refreshed with another manifest with a single
// period, and both the start and end times are increased, then the only way to determine
// if it's a new period or an old one that has changed is to look through the segments of
// each playlist and determine the presentation time bounds to find a match. In addition,
// if the period start changed to exceed the old period end, then there would be no
// match, and it would not be possible to determine whether the refreshed period is a new
// one or the old one.
const oldPlaylists = oldManifest.playlists.concat(getMediaGroupPlaylists(oldManifest));
const newPlaylists = newManifest.playlists.concat(getMediaGroupPlaylists(newManifest)); // Save all seen timelineStarts to the new manifest. Although this potentially means that
// there's a "memory leak" in that it will never stop growing, in reality, only a couple
// of properties are saved for each seen Period. Even long running live streams won't
// generate too many Periods, unless the stream is watched for decades. In the future,
// this can be optimized by mapping to discontinuity sequence numbers for each timeline,
// but it may not become an issue, and the additional info can be useful for debugging.
newManifest.timelineStarts = getUniqueTimelineStarts([oldManifest.timelineStarts, newManifest.timelineStarts]);
updateSequenceNumbers({
oldPlaylists,
newPlaylists,
timelineStarts: newManifest.timelineStarts
});
return newManifest;
};
const generateSidxKey = sidx => sidx && sidx.uri + '-' + byteRangeToString(sidx.byterange);
const mergeDiscontiguousPlaylists = playlists => {
// Break out playlists into groups based on their baseUrl
const playlistsByBaseUrl = playlists.reduce(function (acc, cur) {
if (!acc[cur.attributes.baseUrl]) {
acc[cur.attributes.baseUrl] = [];
}
acc[cur.attributes.baseUrl].push(cur);
return acc;
}, {});
let allPlaylists = [];
Object.values(playlistsByBaseUrl).forEach(playlistGroup => {
const mergedPlaylists = values(playlistGroup.reduce((acc, playlist) => {
// assuming playlist IDs are the same across periods
// TODO: handle multiperiod where representation sets are not the same
// across periods
const name = playlist.attributes.id + (playlist.attributes.lang || '');
if (!acc[name]) {
// First Period
acc[name] = playlist;
acc[name].attributes.timelineStarts = [];
} else {
// Subsequent Periods
if (playlist.segments) {
// first segment of subsequent periods signal a discontinuity
if (playlist.segments[0]) {
playlist.segments[0].discontinuity = true;
}
acc[name].segments.push(...playlist.segments);
} // bubble up contentProtection, this assumes all DRM content
// has the same contentProtection
if (playlist.attributes.contentProtection) {
acc[name].attributes.contentProtection = playlist.attributes.contentProtection;
}
}
acc[name].attributes.timelineStarts.push({
// Although they represent the same number, it's important to have both to make it
// compatible with HLS potentially having a similar attribute.
start: playlist.attributes.periodStart,
timeline: playlist.attributes.periodStart
});
return acc;
}, {}));
allPlaylists = allPlaylists.concat(mergedPlaylists);
});
return allPlaylists.map(playlist => {
playlist.discontinuityStarts = findIndexes(playlist.segments || [], 'discontinuity');
return playlist;
});
};
const addSidxSegmentsToPlaylist = (playlist, sidxMapping) => {
const sidxKey = generateSidxKey(playlist.sidx);
const sidxMatch = sidxKey && sidxMapping[sidxKey] && sidxMapping[sidxKey].sidx;
if (sidxMatch) {
addSidxSegmentsToPlaylist$1(playlist, sidxMatch, playlist.sidx.resolvedUri);
}
return playlist;
};
const addSidxSegmentsToPlaylists = (playlists, sidxMapping = {}) => {
if (!Object.keys(sidxMapping).length) {
return playlists;
}
for (const i in playlists) {
playlists[i] = addSidxSegmentsToPlaylist(playlists[i], sidxMapping);
}
return playlists;
};
const formatAudioPlaylist = ({
attributes,
segments,
sidx,
mediaSequence,
discontinuitySequence,
discontinuityStarts
}, isAudioOnly) => {
const playlist = {
attributes: {
NAME: attributes.id,
BANDWIDTH: attributes.bandwidth,
CODECS: attributes.codecs,
['PROGRAM-ID']: 1
},
uri: '',
endList: attributes.type === 'static',
timeline: attributes.periodStart,
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
discontinuitySequence,
discontinuityStarts,
timelineStarts: attributes.timelineStarts,
mediaSequence,
segments
};
if (attributes.contentProtection) {
playlist.contentProtection = attributes.contentProtection;
}
if (attributes.serviceLocation) {
playlist.attributes.serviceLocation = attributes.serviceLocation;
}
if (sidx) {
playlist.sidx = sidx;
}
if (isAudioOnly) {
playlist.attributes.AUDIO = 'audio';
playlist.attributes.SUBTITLES = 'subs';
}
return playlist;
};
const formatVttPlaylist = ({
attributes,
segments,
mediaSequence,
discontinuityStarts,
discontinuitySequence
}) => {
if (typeof segments === 'undefined') {
// vtt tracks may use single file in BaseURL
segments = [{
uri: attributes.baseUrl,
timeline: attributes.periodStart,
resolvedUri: attributes.baseUrl || '',
duration: attributes.sourceDuration,
number: 0
}]; // targetDuration should be the same duration as the only segment
attributes.duration = attributes.sourceDuration;
}
const m3u8Attributes = {
NAME: attributes.id,
BANDWIDTH: attributes.bandwidth,
['PROGRAM-ID']: 1
};
if (attributes.codecs) {
m3u8Attributes.CODECS = attributes.codecs;
}
const vttPlaylist = {
attributes: m3u8Attributes,
uri: '',
endList: attributes.type === 'static',
timeline: attributes.periodStart,
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
timelineStarts: attributes.timelineStarts,
discontinuityStarts,
discontinuitySequence,
mediaSequence,
segments
};
if (attributes.serviceLocation) {
vttPlaylist.attributes.serviceLocation = attributes.serviceLocation;
}
return vttPlaylist;
};
const organizeAudioPlaylists = (playlists, sidxMapping = {}, isAudioOnly = false) => {
let mainPlaylist;
const formattedPlaylists = playlists.reduce((a, playlist) => {
const role = playlist.attributes.role && playlist.attributes.role.value || '';
const language = playlist.attributes.lang || '';
let label = playlist.attributes.label || 'main';
if (language && !playlist.attributes.label) {
const roleLabel = role ? ` (${role})` : '';
label = `${playlist.attributes.lang}${roleLabel}`;
}
if (!a[label]) {
a[label] = {
language,
autoselect: true,
default: role === 'main',
playlists: [],
uri: ''
};
}
const formatted = addSidxSegmentsToPlaylist(formatAudioPlaylist(playlist, isAudioOnly), sidxMapping);
a[label].playlists.push(formatted);
if (typeof mainPlaylist === 'undefined' && role === 'main') {
mainPlaylist = playlist;
mainPlaylist.default = true;
}
return a;
}, {}); // if no playlists have role "main", mark the first as main
if (!mainPlaylist) {
const firstLabel = Object.keys(formattedPlaylists)[0];
formattedPlaylists[firstLabel].default = true;
}
return formattedPlaylists;
};
const organizeVttPlaylists = (playlists, sidxMapping = {}) => {
return playlists.reduce((a, playlist) => {
const label = playlist.attributes.label || playlist.attributes.lang || 'text';
const language = playlist.attributes.lang || 'und';
if (!a[label]) {
a[label] = {
language,
default: false,
autoselect: false,
playlists: [],
uri: ''
};
}
a[label].playlists.push(addSidxSegmentsToPlaylist(formatVttPlaylist(playlist), sidxMapping));
return a;
}, {});
};
const organizeCaptionServices = captionServices => captionServices.reduce((svcObj, svc) => {
if (!svc) {
return svcObj;
}
svc.forEach(service => {
const {
channel,
language
} = service;
svcObj[language] = {
autoselect: false,
default: false,
instreamId: channel,
language
};
if (service.hasOwnProperty('aspectRatio')) {
svcObj[language].aspectRatio = service.aspectRatio;
}
if (service.hasOwnProperty('easyReader')) {
svcObj[language].easyReader = service.easyReader;
}
if (service.hasOwnProperty('3D')) {
svcObj[language]['3D'] = service['3D'];
}
});
return svcObj;
}, {});
const formatVideoPlaylist = ({
attributes,
segments,
sidx,
discontinuityStarts
}) => {
const playlist = {
attributes: {
NAME: attributes.id,
AUDIO: 'audio',
SUBTITLES: 'subs',
RESOLUTION: {
width: attributes.width,
height: attributes.height
},
CODECS: attributes.codecs,
BANDWIDTH: attributes.bandwidth,
['PROGRAM-ID']: 1
},
uri: '',
endList: attributes.type === 'static',
timeline: attributes.periodStart,
resolvedUri: attributes.baseUrl || '',
targetDuration: attributes.duration,
discontinuityStarts,
timelineStarts: attributes.timelineStarts,
segments
};
if (attributes.frameRate) {
playlist.attributes['FRAME-RATE'] = attributes.frameRate;
}
if (attributes.contentProtection) {
playlist.contentProtection = attributes.contentProtection;
}
if (attributes.serviceLocation) {
playlist.attributes.serviceLocation = attributes.serviceLocation;
}
if (sidx) {
playlist.sidx = sidx;
}
return playlist;
};
const videoOnly = ({
attributes
}) => attributes.mimeType === 'video/mp4' || attributes.mimeType === 'video/webm' || attributes.contentType === 'video';
const audioOnly = ({
attributes
}) => attributes.mimeType === 'audio/mp4' || attributes.mimeType === 'audio/webm' || attributes.contentType === 'audio';
const vttOnly = ({
attributes
}) => attributes.mimeType === 'text/vtt' || attributes.contentType === 'text';
/**
* Contains start and timeline properties denoting a timeline start. For DASH, these will
* be the same number.
*
* @typedef {Object} TimelineStart
* @property {number} start - the start time of the timeline
* @property {number} timeline - the timeline number
*/
/**
* Adds appropriate media and discontinuity sequence values to the segments and playlists.
*
* Throughout mpd-parser, the `number` attribute is used in relation to `startNumber`, a
* DASH specific attribute used in constructing segment URI's from templates. However, from
* an HLS perspective, the `number` attribute on a segment would be its `mediaSequence`
* value, which should start at the original media sequence value (or 0) and increment by 1
* for each segment thereafter. Since DASH's `startNumber` values are independent per
* period, it doesn't make sense to use it for `number`. Instead, assume everything starts
* from a 0 mediaSequence value and increment from there.
*
* Note that VHS currently doesn't use the `number` property, but it can be helpful for
* debugging and making sense of the manifest.
*
* For live playlists, to account for values increasing in manifests when periods are
* removed on refreshes, merging logic should be used to update the numbers to their
* appropriate values (to ensure they're sequential and increasing).
*
* @param {Object[]} playlists - the playlists to update
* @param {TimelineStart[]} timelineStarts - the timeline starts for the manifest
*/
const addMediaSequenceValues = (playlists, timelineStarts) => {
// increment all segments sequentially
playlists.forEach(playlist => {
playlist.mediaSequence = 0;
playlist.discontinuitySequence = timelineStarts.findIndex(function ({
timeline
}) {
return timeline === playlist.timeline;
});
if (!playlist.segments) {
return;
}
playlist.segments.forEach((segment, index) => {
segment.number = index;
});
});
};
/**
* Given a media group object, flattens all playlists within the media group into a single
* array.
*
* @param {Object} mediaGroupObject - the media group object
*
* @return {Object[]}
* The media group playlists
*/
const flattenMediaGroupPlaylists = mediaGroupObject => {
if (!mediaGroupObject) {
return [];
}
return Object.keys(mediaGroupObject).reduce((acc, label) => {
const labelContents = mediaGroupObject[label];
return acc.concat(labelContents.playlists);
}, []);
};
const toM3u8 = ({
dashPlaylists,
locations,
contentSteering,
sidxMapping = {},
previousManifest,
eventStream
}) => {
if (!dashPlaylists.length) {
return {};
} // grab all main manifest attributes
const {
sourceDuration: duration,
type,
suggestedPresentationDelay,
minimumUpdatePeriod
} = dashPlaylists[0].attributes;
const videoPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(videoOnly)).map(formatVideoPlaylist);
const audioPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(audioOnly));
const vttPlaylists = mergeDiscontiguousPlaylists(dashPlaylists.filter(vttOnly));
const captions = dashPlaylists.map(playlist => playlist.attributes.captionServices).filter(Boolean);
const manifest = {
allowCache: true,
discontinuityStarts: [],
segments: [],
endList: true,
mediaGroups: {
AUDIO: {},
VIDEO: {},
['CLOSED-CAPTIONS']: {},
SUBTITLES: {}
},
uri: '',
duration,
playlists: addSidxSegmentsToPlaylists(videoPlaylists, sidxMapping)
};
if (minimumUpdatePeriod >= 0) {
manifest.minimumUpdatePeriod = minimumUpdatePeriod * 1000;
}
if (locations) {
manifest.locations = locations;
}
if (contentSteering) {
manifest.contentSteering = contentSteering;
}
if (type === 'dynamic') {
manifest.suggestedPresentationDelay = suggestedPresentationDelay;
}
if (eventStream && eventStream.length > 0) {
manifest.eventStream = eventStream;
}
const isAudioOnly = manifest.playlists.length === 0;
const organizedAudioGroup = audioPlaylists.length ? organizeAudioPlaylists(audioPlaylists, sidxMapping, isAudioOnly) : null;
const organizedVttGroup = vttPlaylists.length ? organizeVttPlaylists(vttPlaylists, sidxMapping) : null;
const formattedPlaylists = videoPlaylists.concat(flattenMediaGroupPlaylists(organizedAudioGroup), flattenMediaGroupPlaylists(organizedVttGroup));
const playlistTimelineStarts = formattedPlaylists.map(({
timelineStarts
}) => timelineStarts);
manifest.timelineStarts = getUniqueTimelineStarts(playlistTimelineStarts);
addMediaSequenceValues(formattedPlaylists, manifest.timelineStarts);
if (organizedAudioGroup) {
manifest.mediaGroups.AUDIO.audio = organizedAudioGroup;
}
if (organizedVttGroup) {
manifest.mediaGroups.SUBTITLES.subs = organizedVttGroup;
}
if (captions.length) {
manifest.mediaGroups['CLOSED-CAPTIONS'].cc = organizeCaptionServices(captions);
}
if (previousManifest) {
return positionManifestOnTimeline({
oldManifest: previousManifest,
newManifest: manifest
});
}
return manifest;
};
/**
* Calculates the R (repetition) value for a live stream (for the final segment
* in a manifest where the r value is negative 1)
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {number} time
* current time (typically the total time up until the final segment)
* @param {number} duration
* duration property for the given
*
* @return {number}
* R value to reach the end of the given period
*/
const getLiveRValue = (attributes, time, duration) => {
const {
NOW,
clientOffset,
availabilityStartTime,
timescale = 1,
periodStart = 0,
minimumUpdatePeriod = 0
} = attributes;
const now = (NOW + clientOffset) / 1000;
const periodStartWC = availabilityStartTime + periodStart;
const periodEndWC = now + minimumUpdatePeriod;
const periodDuration = periodEndWC - periodStartWC;
return Math.ceil((periodDuration * timescale - time) / duration);
};
/**
* Uses information provided by SegmentTemplate.SegmentTimeline to determine segment
* timing and duration
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]} segmentTimeline
* List of objects representing the attributes of each S element contained within
*
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
const parseByTimeline = (attributes, segmentTimeline) => {
const {
type,
minimumUpdatePeriod = 0,
media = '',
sourceDuration,
timescale = 1,
startNumber = 1,
periodStart: timeline
} = attributes;
const segments = [];
let time = -1;
for (let sIndex = 0; sIndex < segmentTimeline.length; sIndex++) {
const S = segmentTimeline[sIndex];
const duration = S.d;
const repeat = S.r || 0;
const segmentTime = S.t || 0;
if (time < 0) {
// first segment
time = segmentTime;
}
if (segmentTime && segmentTime > time) {
// discontinuity
// TODO: How to handle this type of discontinuity
// timeline++ here would treat it like HLS discontuity and content would
// get appended without gap
// E.G.
//
//
//
//
// would have $Time$ values of [0, 1, 2, 5]
// should this be appened at time positions [0, 1, 2, 3],(#EXT-X-DISCONTINUITY)
// or [0, 1, 2, gap, gap, 5]? (#EXT-X-GAP)
// does the value of sourceDuration consider this when calculating arbitrary
// negative @r repeat value?
// E.G. Same elements as above with this added at the end
//
// with a sourceDuration of 10
// Would the 2 gaps be included in the time duration calculations resulting in
// 8 segments with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9] or 10 segments
// with $Time$ values of [0, 1, 2, 5, 6, 7, 8, 9, 10, 11] ?
time = segmentTime;
}
let count;
if (repeat < 0) {
const nextS = sIndex + 1;
if (nextS === segmentTimeline.length) {
// last segment
if (type === 'dynamic' && minimumUpdatePeriod > 0 && media.indexOf('$Number$') > 0) {
count = getLiveRValue(attributes, time, duration);
} else {
// TODO: This may be incorrect depending on conclusion of TODO above
count = (sourceDuration * timescale - time) / duration;
}
} else {
count = (segmentTimeline[nextS].t - time) / duration;
}
} else {
count = repeat + 1;
}
const end = startNumber + segments.length + count;
let number = startNumber + segments.length;
while (number < end) {
segments.push({
number,
duration: duration / timescale,
time,
timeline
});
time += duration;
number++;
}
}
return segments;
};
const identifierPattern = /\$([A-z]*)(?:(%0)([0-9]+)d)?\$/g;
/**
* Replaces template identifiers with corresponding values. To be used as the callback
* for String.prototype.replace
*
* @name replaceCallback
* @function
* @param {string} match
* Entire match of identifier
* @param {string} identifier
* Name of matched identifier
* @param {string} format
* Format tag string. Its presence indicates that padding is expected
* @param {string} width
* Desired length of the replaced value. Values less than this width shall be left
* zero padded
* @return {string}
* Replacement for the matched identifier
*/
/**
* Returns a function to be used as a callback for String.prototype.replace to replace
* template identifiers
*
* @param {Obect} values
* Object containing values that shall be used to replace known identifiers
* @param {number} values.RepresentationID
* Value of the Representation@id attribute
* @param {number} values.Number
* Number of the corresponding segment
* @param {number} values.Bandwidth
* Value of the Representation@bandwidth attribute.
* @param {number} values.Time
* Timestamp value of the corresponding segment
* @return {replaceCallback}
* Callback to be used with String.prototype.replace to replace identifiers
*/
const identifierReplacement = values => (match, identifier, format, width) => {
if (match === '$$') {
// escape sequence
return '$';
}
if (typeof values[identifier] === 'undefined') {
return match;
}
const value = '' + values[identifier];
if (identifier === 'RepresentationID') {
// Format tag shall not be present with RepresentationID
return value;
}
if (!format) {
width = 1;
} else {
width = parseInt(width, 10);
}
if (value.length >= width) {
return value;
}
return `${new Array(width - value.length + 1).join('0')}${value}`;
};
/**
* Constructs a segment url from a template string
*
* @param {string} url
* Template string to construct url from
* @param {Obect} values
* Object containing values that shall be used to replace known identifiers
* @param {number} values.RepresentationID
* Value of the Representation@id attribute
* @param {number} values.Number
* Number of the corresponding segment
* @param {number} values.Bandwidth
* Value of the Representation@bandwidth attribute.
* @param {number} values.Time
* Timestamp value of the corresponding segment
* @return {string}
* Segment url with identifiers replaced
*/
const constructTemplateUrl = (url, values) => url.replace(identifierPattern, identifierReplacement(values));
/**
* Generates a list of objects containing timing and duration information about each
* segment needed to generate segment uris and the complete segment object
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {{number: number, duration: number, time: number, timeline: number}[]}
* List of Objects with segment timing and duration info
*/
const parseTemplateInfo = (attributes, segmentTimeline) => {
if (!attributes.duration && !segmentTimeline) {
// if neither @duration or SegmentTimeline are present, then there shall be exactly
// one media segment
return [{
number: attributes.startNumber || 1,
duration: attributes.sourceDuration,
time: 0,
timeline: attributes.periodStart
}];
}
if (attributes.duration) {
return parseByDuration(attributes);
}
return parseByTimeline(attributes, segmentTimeline);
};
/**
* Generates a list of segments using information provided by the SegmentTemplate element
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {Object[]}
* List of segment objects
*/
const segmentsFromTemplate = (attributes, segmentTimeline) => {
const templateValues = {
RepresentationID: attributes.id,
Bandwidth: attributes.bandwidth || 0
};
const {
initialization = {
sourceURL: '',
range: ''
}
} = attributes;
const mapSegment = urlTypeToSegment({
baseUrl: attributes.baseUrl,
source: constructTemplateUrl(initialization.sourceURL, templateValues),
range: initialization.range
});
const segments = parseTemplateInfo(attributes, segmentTimeline);
return segments.map(segment => {
templateValues.Number = segment.number;
templateValues.Time = segment.time;
const uri = constructTemplateUrl(attributes.media || '', templateValues); // See DASH spec section 5.3.9.2.2
// - if timescale isn't present on any level, default to 1.
const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
const presentationTimeOffset = attributes.presentationTimeOffset || 0;
const presentationTime = // Even if the @t attribute is not specified for the segment, segment.time is
// calculated in mpd-parser prior to this, so it's assumed to be available.
attributes.periodStart + (segment.time - presentationTimeOffset) / timescale;
const map = {
uri,
timeline: segment.timeline,
duration: segment.duration,
resolvedUri: resolve_url(attributes.baseUrl || '', uri),
map: mapSegment,
number: segment.number,
presentationTime
};
return map;
});
};
/**
* Converts a (of type URLType from the DASH spec 5.3.9.2 Table 14)
* to an object that matches the output of a segment in videojs/mpd-parser
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object} segmentUrl
* node to translate into a segment object
* @return {Object} translated segment object
*/
const SegmentURLToSegmentObject = (attributes, segmentUrl) => {
const {
baseUrl,
initialization = {}
} = attributes;
const initSegment = urlTypeToSegment({
baseUrl,
source: initialization.sourceURL,
range: initialization.range
});
const segment = urlTypeToSegment({
baseUrl,
source: segmentUrl.media,
range: segmentUrl.mediaRange
});
segment.map = initSegment;
return segment;
};
/**
* Generates a list of segments using information provided by the SegmentList element
* SegmentList (DASH SPEC Section 5.3.9.3.2) contains a set of nodes. Each
* node should be translated into segment.
*
* @param {Object} attributes
* Object containing all inherited attributes from parent elements with attribute
* names as keys
* @param {Object[]|undefined} segmentTimeline
* List of objects representing the attributes of each S element contained within
* the SegmentTimeline element
* @return {Object.} list of segments
*/
const segmentsFromList = (attributes, segmentTimeline) => {
const {
duration,
segmentUrls = [],
periodStart
} = attributes; // Per spec (5.3.9.2.1) no way to determine segment duration OR
// if both SegmentTimeline and @duration are defined, it is outside of spec.
if (!duration && !segmentTimeline || duration && segmentTimeline) {
throw new Error(errors.SEGMENT_TIME_UNSPECIFIED);
}
const segmentUrlMap = segmentUrls.map(segmentUrlObject => SegmentURLToSegmentObject(attributes, segmentUrlObject));
let segmentTimeInfo;
if (duration) {
segmentTimeInfo = parseByDuration(attributes);
}
if (segmentTimeline) {
segmentTimeInfo = parseByTimeline(attributes, segmentTimeline);
}
const segments = segmentTimeInfo.map((segmentTime, index) => {
if (segmentUrlMap[index]) {
const segment = segmentUrlMap[index]; // See DASH spec section 5.3.9.2.2
// - if timescale isn't present on any level, default to 1.
const timescale = attributes.timescale || 1; // - if presentationTimeOffset isn't present on any level, default to 0
const presentationTimeOffset = attributes.presentationTimeOffset || 0;
segment.timeline = segmentTime.timeline;
segment.duration = segmentTime.duration;
segment.number = segmentTime.number;
segment.presentationTime = periodStart + (segmentTime.time - presentationTimeOffset) / timescale;
return segment;
} // Since we're mapping we should get rid of any blank segments (in case
// the given SegmentTimeline is handling for more elements than we have
// SegmentURLs for).
}).filter(segment => segment);
return segments;
};
const generateSegments = ({
attributes,
segmentInfo
}) => {
let segmentAttributes;
let segmentsFn;
if (segmentInfo.template) {
segmentsFn = segmentsFromTemplate;
segmentAttributes = merge(attributes, segmentInfo.template);
} else if (segmentInfo.base) {
segmentsFn = segmentsFromBase;
segmentAttributes = merge(attributes, segmentInfo.base);
} else if (segmentInfo.list) {
segmentsFn = segmentsFromList;
segmentAttributes = merge(attributes, segmentInfo.list);
}
const segmentsInfo = {
attributes
};
if (!segmentsFn) {
return segmentsInfo;
}
const segments = segmentsFn(segmentAttributes, segmentInfo.segmentTimeline); // The @duration attribute will be used to determin the playlist's targetDuration which
// must be in seconds. Since we've generated the segment list, we no longer need
// @duration to be in @timescale units, so we can convert it here.
if (segmentAttributes.duration) {
const {
duration,
timescale = 1
} = segmentAttributes;
segmentAttributes.duration = duration / timescale;
} else if (segments.length) {
// if there is no @duration attribute, use the largest segment duration as
// as target duration
segmentAttributes.duration = segments.reduce((max, segment) => {
return Math.max(max, Math.ceil(segment.duration));
}, 0);
} else {
segmentAttributes.duration = 0;
}
segmentsInfo.attributes = segmentAttributes;
segmentsInfo.segments = segments; // This is a sidx box without actual segment information
if (segmentInfo.base && segmentAttributes.indexRange) {
segmentsInfo.sidx = segments[0];
segmentsInfo.segments = [];
}
return segmentsInfo;
};
const toPlaylists = representations => representations.map(generateSegments);
const findChildren = (element, name) => from(element.childNodes).filter(({
tagName
}) => tagName === name);
const getContent = element => element.textContent.trim();
/**
* Converts the provided string that may contain a division operation to a number.
*
* @param {string} value - the provided string value
*
* @return {number} the parsed string value
*/
const parseDivisionValue = value => {
return parseFloat(value.split('/').reduce((prev, current) => prev / current));
};
const parseDuration = str => {
const SECONDS_IN_YEAR = 365 * 24 * 60 * 60;
const SECONDS_IN_MONTH = 30 * 24 * 60 * 60;
const SECONDS_IN_DAY = 24 * 60 * 60;
const SECONDS_IN_HOUR = 60 * 60;
const SECONDS_IN_MIN = 60; // P10Y10M10DT10H10M10.1S
const durationRegex = /P(?:(\d*)Y)?(?:(\d*)M)?(?:(\d*)D)?(?:T(?:(\d*)H)?(?:(\d*)M)?(?:([\d.]*)S)?)?/;
const match = durationRegex.exec(str);
if (!match) {
return 0;
}
const [year, month, day, hour, minute, second] = match.slice(1);
return parseFloat(year || 0) * SECONDS_IN_YEAR + parseFloat(month || 0) * SECONDS_IN_MONTH + parseFloat(day || 0) * SECONDS_IN_DAY + parseFloat(hour || 0) * SECONDS_IN_HOUR + parseFloat(minute || 0) * SECONDS_IN_MIN + parseFloat(second || 0);
};
const parseDate = str => {
// Date format without timezone according to ISO 8601
// YYY-MM-DDThh:mm:ss.ssssss
const dateRegex = /^\d+-\d+-\d+T\d+:\d+:\d+(\.\d+)?$/; // If the date string does not specifiy a timezone, we must specifiy UTC. This is
// expressed by ending with 'Z'
if (dateRegex.test(str)) {
str += 'Z';
}
return Date.parse(str);
};
const parsers = {
/**
* Specifies the duration of the entire Media Presentation. Format is a duration string
* as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
mediaPresentationDuration(value) {
return parseDuration(value);
},
/**
* Specifies the Segment availability start time for all Segments referred to in this
* MPD. For a dynamic manifest, it specifies the anchor for the earliest availability
* time. Format is a date string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The date as seconds from unix epoch
*/
availabilityStartTime(value) {
return parseDate(value) / 1000;
},
/**
* Specifies the smallest period between potential changes to the MPD. Format is a
* duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
minimumUpdatePeriod(value) {
return parseDuration(value);
},
/**
* Specifies the suggested presentation delay. Format is a
* duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
suggestedPresentationDelay(value) {
return parseDuration(value);
},
/**
* specifices the type of mpd. Can be either "static" or "dynamic"
*
* @param {string} value
* value of attribute as a string
*
* @return {string}
* The type as a string
*/
type(value) {
return value;
},
/**
* Specifies the duration of the smallest time shifting buffer for any Representation
* in the MPD. Format is a duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
timeShiftBufferDepth(value) {
return parseDuration(value);
},
/**
* Specifies the PeriodStart time of the Period relative to the availabilityStarttime.
* Format is a duration string as specified in ISO 8601
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The duration in seconds
*/
start(value) {
return parseDuration(value);
},
/**
* Specifies the width of the visual presentation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed width
*/
width(value) {
return parseInt(value, 10);
},
/**
* Specifies the height of the visual presentation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed height
*/
height(value) {
return parseInt(value, 10);
},
/**
* Specifies the bitrate of the representation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed bandwidth
*/
bandwidth(value) {
return parseInt(value, 10);
},
/**
* Specifies the frame rate of the representation
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed frame rate
*/
frameRate(value) {
return parseDivisionValue(value);
},
/**
* Specifies the number of the first Media Segment in this Representation in the Period
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed number
*/
startNumber(value) {
return parseInt(value, 10);
},
/**
* Specifies the timescale in units per seconds
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed timescale
*/
timescale(value) {
return parseInt(value, 10);
},
/**
* Specifies the presentationTimeOffset.
*
* @param {string} value
* value of the attribute as a string
*
* @return {number}
* The parsed presentationTimeOffset
*/
presentationTimeOffset(value) {
return parseInt(value, 10);
},
/**
* Specifies the constant approximate Segment duration
* NOTE: The element also contains an @duration attribute. This duration
* specifies the duration of the Period. This attribute is currently not
* supported by the rest of the parser, however we still check for it to prevent
* errors.
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed duration
*/
duration(value) {
const parsedValue = parseInt(value, 10);
if (isNaN(parsedValue)) {
return parseDuration(value);
}
return parsedValue;
},
/**
* Specifies the Segment duration, in units of the value of the @timescale.
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed duration
*/
d(value) {
return parseInt(value, 10);
},
/**
* Specifies the MPD start time, in @timescale units, the first Segment in the series
* starts relative to the beginning of the Period
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed time
*/
t(value) {
return parseInt(value, 10);
},
/**
* Specifies the repeat count of the number of following contiguous Segments with the
* same duration expressed by the value of @d
*
* @param {string} value
* value of attribute as a string
* @return {number}
* The parsed number
*/
r(value) {
return parseInt(value, 10);
},
/**
* Specifies the presentationTime.
*
* @param {string} value
* value of the attribute as a string
*
* @return {number}
* The parsed presentationTime
*/
presentationTime(value) {
return parseInt(value, 10);
},
/**
* Default parser for all other attributes. Acts as a no-op and just returns the value
* as a string
*
* @param {string} value
* value of attribute as a string
* @return {string}
* Unparsed value
*/
DEFAULT(value) {
return value;
}
};
/**
* Gets all the attributes and values of the provided node, parses attributes with known
* types, and returns an object with attribute names mapped to values.
*
* @param {Node} el
* The node to parse attributes from
* @return {Object}
* Object with all attributes of el parsed
*/
const mpd_parser_es_parseAttributes = el => {
if (!(el && el.attributes)) {
return {};
}
return from(el.attributes).reduce((a, e) => {
const parseFn = parsers[e.name] || parsers.DEFAULT;
a[e.name] = parseFn(e.value);
return a;
}, {});
};
const keySystemsMap = {
'urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b': 'org.w3.clearkey',
'urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed': 'com.widevine.alpha',
'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready',
'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb': 'com.adobe.primetime',
// ISO_IEC 23009-1_2022 5.8.5.2.2 The mp4 Protection Scheme
'urn:mpeg:dash:mp4protection:2011': 'mp4protection'
};
/**
* Builds a list of urls that is the product of the reference urls and BaseURL values
*
* @param {Object[]} references
* List of objects containing the reference URL as well as its attributes
* @param {Node[]} baseUrlElements
* List of BaseURL nodes from the mpd
* @return {Object[]}
* List of objects with resolved urls and attributes
*/
const buildBaseUrls = (references, baseUrlElements) => {
if (!baseUrlElements.length) {
return references;
}
return flatten(references.map(function (reference) {
return baseUrlElements.map(function (baseUrlElement) {
const initialBaseUrl = getContent(baseUrlElement);
const resolvedBaseUrl = resolve_url(reference.baseUrl, initialBaseUrl);
const finalBaseUrl = merge(mpd_parser_es_parseAttributes(baseUrlElement), {
baseUrl: resolvedBaseUrl
}); // If the URL is resolved, we want to get the serviceLocation from the reference
// assuming there is no serviceLocation on the initialBaseUrl
if (resolvedBaseUrl !== initialBaseUrl && !finalBaseUrl.serviceLocation && reference.serviceLocation) {
finalBaseUrl.serviceLocation = reference.serviceLocation;
}
return finalBaseUrl;
});
}));
};
/**
* Contains all Segment information for its containing AdaptationSet
*
* @typedef {Object} SegmentInformation
* @property {Object|undefined} template
* Contains the attributes for the SegmentTemplate node
* @property {Object[]|undefined} segmentTimeline
* Contains a list of atrributes for each S node within the SegmentTimeline node
* @property {Object|undefined} list
* Contains the attributes for the SegmentList node
* @property {Object|undefined} base
* Contains the attributes for the SegmentBase node
*/
/**
* Returns all available Segment information contained within the AdaptationSet node
*
* @param {Node} adaptationSet
* The AdaptationSet node to get Segment information from
* @return {SegmentInformation}
* The Segment information contained within the provided AdaptationSet
*/
const getSegmentInformation = adaptationSet => {
const segmentTemplate = findChildren(adaptationSet, 'SegmentTemplate')[0];
const segmentList = findChildren(adaptationSet, 'SegmentList')[0];
const segmentUrls = segmentList && findChildren(segmentList, 'SegmentURL').map(s => merge({
tag: 'SegmentURL'
}, mpd_parser_es_parseAttributes(s)));
const segmentBase = findChildren(adaptationSet, 'SegmentBase')[0];
const segmentTimelineParentNode = segmentList || segmentTemplate;
const segmentTimeline = segmentTimelineParentNode && findChildren(segmentTimelineParentNode, 'SegmentTimeline')[0];
const segmentInitializationParentNode = segmentList || segmentBase || segmentTemplate;
const segmentInitialization = segmentInitializationParentNode && findChildren(segmentInitializationParentNode, 'Initialization')[0]; // SegmentTemplate is handled slightly differently, since it can have both
// @initialization and an node. @initialization can be templated,
// while the node can have a url and range specified. If the has
// both @initialization and an subelement we opt to override with
// the node, as this interaction is not defined in the spec.
const template = segmentTemplate && mpd_parser_es_parseAttributes(segmentTemplate);
if (template && segmentInitialization) {
template.initialization = segmentInitialization && mpd_parser_es_parseAttributes(segmentInitialization);
} else if (template && template.initialization) {
// If it is @initialization we convert it to an object since this is the format that
// later functions will rely on for the initialization segment. This is only valid
// for
template.initialization = {
sourceURL: template.initialization
};
}
const segmentInfo = {
template,
segmentTimeline: segmentTimeline && findChildren(segmentTimeline, 'S').map(s => mpd_parser_es_parseAttributes(s)),
list: segmentList && merge(mpd_parser_es_parseAttributes(segmentList), {
segmentUrls,
initialization: mpd_parser_es_parseAttributes(segmentInitialization)
}),
base: segmentBase && merge(mpd_parser_es_parseAttributes(segmentBase), {
initialization: mpd_parser_es_parseAttributes(segmentInitialization)
})
};
Object.keys(segmentInfo).forEach(key => {
if (!segmentInfo[key]) {
delete segmentInfo[key];
}
});
return segmentInfo;
};
/**
* Contains Segment information and attributes needed to construct a Playlist object
* from a Representation
*
* @typedef {Object} RepresentationInformation
* @property {SegmentInformation} segmentInfo
* Segment information for this Representation
* @property {Object} attributes
* Inherited attributes for this Representation
*/
/**
* Maps a Representation node to an object containing Segment information and attributes
*
* @name inheritBaseUrlsCallback
* @function
* @param {Node} representation
* Representation node from the mpd
* @return {RepresentationInformation}
* Representation information needed to construct a Playlist object
*/
/**
* Returns a callback for Array.prototype.map for mapping Representation nodes to
* Segment information and attributes using inherited BaseURL nodes.
*
* @param {Object} adaptationSetAttributes
* Contains attributes inherited by the AdaptationSet
* @param {Object[]} adaptationSetBaseUrls
* List of objects containing resolved base URLs and attributes
* inherited by the AdaptationSet
* @param {SegmentInformation} adaptationSetSegmentInfo
* Contains Segment information for the AdaptationSet
* @return {inheritBaseUrlsCallback}
* Callback map function
*/
const inheritBaseUrls = (adaptationSetAttributes, adaptationSetBaseUrls, adaptationSetSegmentInfo) => representation => {
const repBaseUrlElements = findChildren(representation, 'BaseURL');
const repBaseUrls = buildBaseUrls(adaptationSetBaseUrls, repBaseUrlElements);
const attributes = merge(adaptationSetAttributes, mpd_parser_es_parseAttributes(representation));
const representationSegmentInfo = getSegmentInformation(representation);
return repBaseUrls.map(baseUrl => {
return {
segmentInfo: merge(adaptationSetSegmentInfo, representationSegmentInfo),
attributes: merge(attributes, baseUrl)
};
});
};
/**
* Tranforms a series of content protection nodes to
* an object containing pssh data by key system
*
* @param {Node[]} contentProtectionNodes
* Content protection nodes
* @return {Object}
* Object containing pssh data by key system
*/
const generateKeySystemInformation = contentProtectionNodes => {
return contentProtectionNodes.reduce((acc, node) => {
const attributes = mpd_parser_es_parseAttributes(node); // Although it could be argued that according to the UUID RFC spec the UUID string (a-f chars) should be generated
// as a lowercase string it also mentions it should be treated as case-insensitive on input. Since the key system
// UUIDs in the keySystemsMap are hardcoded as lowercase in the codebase there isn't any reason not to do
// .toLowerCase() on the input UUID string from the manifest (at least I could not think of one).
if (attributes.schemeIdUri) {
attributes.schemeIdUri = attributes.schemeIdUri.toLowerCase();
}
const keySystem = keySystemsMap[attributes.schemeIdUri];
if (keySystem) {
acc[keySystem] = {
attributes
};
const psshNode = findChildren(node, 'cenc:pssh')[0];
if (psshNode) {
const pssh = getContent(psshNode);
acc[keySystem].pssh = pssh && decodeB64ToUint8Array(pssh);
}
}
return acc;
}, {});
}; // defined in ANSI_SCTE 214-1 2016
const parseCaptionServiceMetadata = service => {
// 608 captions
if (service.schemeIdUri === 'urn:scte:dash:cc:cea-608:2015') {
const values = typeof service.value !== 'string' ? [] : service.value.split(';');
return values.map(value => {
let channel;
let language; // default language to value
language = value;
if (/^CC\d=/.test(value)) {
[channel, language] = value.split('=');
} else if (/^CC\d$/.test(value)) {
channel = value;
}
return {
channel,
language
};
});
} else if (service.schemeIdUri === 'urn:scte:dash:cc:cea-708:2015') {
const values = typeof service.value !== 'string' ? [] : service.value.split(';');
return values.map(value => {
const flags = {
// service or channel number 1-63
'channel': undefined,
// language is a 3ALPHA per ISO 639.2/B
// field is required
'language': undefined,
// BIT 1/0 or ?
// default value is 1, meaning 16:9 aspect ratio, 0 is 4:3, ? is unknown
'aspectRatio': 1,
// BIT 1/0
// easy reader flag indicated the text is tailed to the needs of beginning readers
// default 0, or off
'easyReader': 0,
// BIT 1/0
// If 3d metadata is present (CEA-708.1) then 1
// default 0
'3D': 0
};
if (/=/.test(value)) {
const [channel, opts = ''] = value.split('=');
flags.channel = channel;
flags.language = value;
opts.split(',').forEach(opt => {
const [name, val] = opt.split(':');
if (name === 'lang') {
flags.language = val; // er for easyReadery
} else if (name === 'er') {
flags.easyReader = Number(val); // war for wide aspect ratio
} else if (name === 'war') {
flags.aspectRatio = Number(val);
} else if (name === '3D') {
flags['3D'] = Number(val);
}
});
} else {
flags.language = value;
}
if (flags.channel) {
flags.channel = 'SERVICE' + flags.channel;
}
return flags;
});
}
};
/**
* A map callback that will parse all event stream data for a collection of periods
* DASH ISO_IEC_23009 5.10.2.2
* https://dashif-documents.azurewebsites.net/Events/master/event.html#mpd-event-timing
*
* @param {PeriodInformation} period object containing necessary period information
* @return a collection of parsed eventstream event objects
*/
const toEventStream = period => {
// get and flatten all EventStreams tags and parse attributes and children
return flatten(findChildren(period.node, 'EventStream').map(eventStream => {
const eventStreamAttributes = mpd_parser_es_parseAttributes(eventStream);
const schemeIdUri = eventStreamAttributes.schemeIdUri; // find all Events per EventStream tag and map to return objects
return findChildren(eventStream, 'Event').map(event => {
const eventAttributes = mpd_parser_es_parseAttributes(event);
const presentationTime = eventAttributes.presentationTime || 0;
const timescale = eventStreamAttributes.timescale || 1;
const duration = eventAttributes.duration || 0;
const start = presentationTime / timescale + period.attributes.start;
return {
schemeIdUri,
value: eventStreamAttributes.value,
id: eventAttributes.id,
start,
end: start + duration / timescale,
messageData: getContent(event) || eventAttributes.messageData,
contentEncoding: eventStreamAttributes.contentEncoding,
presentationTimeOffset: eventStreamAttributes.presentationTimeOffset || 0
};
});
}));
};
/**
* Maps an AdaptationSet node to a list of Representation information objects
*
* @name toRepresentationsCallback
* @function
* @param {Node} adaptationSet
* AdaptationSet node from the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/
/**
* Returns a callback for Array.prototype.map for mapping AdaptationSet nodes to a list of
* Representation information objects
*
* @param {Object} periodAttributes
* Contains attributes inherited by the Period
* @param {Object[]} periodBaseUrls
* Contains list of objects with resolved base urls and attributes
* inherited by the Period
* @param {string[]} periodSegmentInfo
* Contains Segment Information at the period level
* @return {toRepresentationsCallback}
* Callback map function
*/
const toRepresentations = (periodAttributes, periodBaseUrls, periodSegmentInfo) => adaptationSet => {
const adaptationSetAttributes = mpd_parser_es_parseAttributes(adaptationSet);
const adaptationSetBaseUrls = buildBaseUrls(periodBaseUrls, findChildren(adaptationSet, 'BaseURL'));
const role = findChildren(adaptationSet, 'Role')[0];
const roleAttributes = {
role: mpd_parser_es_parseAttributes(role)
};
let attrs = merge(periodAttributes, adaptationSetAttributes, roleAttributes);
const accessibility = findChildren(adaptationSet, 'Accessibility')[0];
const captionServices = parseCaptionServiceMetadata(mpd_parser_es_parseAttributes(accessibility));
if (captionServices) {
attrs = merge(attrs, {
captionServices
});
}
const label = findChildren(adaptationSet, 'Label')[0];
if (label && label.childNodes.length) {
const labelVal = label.childNodes[0].nodeValue.trim();
attrs = merge(attrs, {
label: labelVal
});
}
const contentProtection = generateKeySystemInformation(findChildren(adaptationSet, 'ContentProtection'));
if (Object.keys(contentProtection).length) {
attrs = merge(attrs, {
contentProtection
});
}
const segmentInfo = getSegmentInformation(adaptationSet);
const representations = findChildren(adaptationSet, 'Representation');
const adaptationSetSegmentInfo = merge(periodSegmentInfo, segmentInfo);
return flatten(representations.map(inheritBaseUrls(attrs, adaptationSetBaseUrls, adaptationSetSegmentInfo)));
};
/**
* Contains all period information for mapping nodes onto adaptation sets.
*
* @typedef {Object} PeriodInformation
* @property {Node} period.node
* Period node from the mpd
* @property {Object} period.attributes
* Parsed period attributes from node plus any added
*/
/**
* Maps a PeriodInformation object to a list of Representation information objects for all
* AdaptationSet nodes contained within the Period.
*
* @name toAdaptationSetsCallback
* @function
* @param {PeriodInformation} period
* Period object containing necessary period information
* @param {number} periodStart
* Start time of the Period within the mpd
* @return {RepresentationInformation[]}
* List of objects containing Representaion information
*/
/**
* Returns a callback for Array.prototype.map for mapping Period nodes to a list of
* Representation information objects
*
* @param {Object} mpdAttributes
* Contains attributes inherited by the mpd
* @param {Object[]} mpdBaseUrls
* Contains list of objects with resolved base urls and attributes
* inherited by the mpd
* @return {toAdaptationSetsCallback}
* Callback map function
*/
const toAdaptationSets = (mpdAttributes, mpdBaseUrls) => (period, index) => {
const periodBaseUrls = buildBaseUrls(mpdBaseUrls, findChildren(period.node, 'BaseURL'));
const periodAttributes = merge(mpdAttributes, {
periodStart: period.attributes.start
});
if (typeof period.attributes.duration === 'number') {
periodAttributes.periodDuration = period.attributes.duration;
}
const adaptationSets = findChildren(period.node, 'AdaptationSet');
const periodSegmentInfo = getSegmentInformation(period.node);
return flatten(adaptationSets.map(toRepresentations(periodAttributes, periodBaseUrls, periodSegmentInfo)));
};
/**
* Tranforms an array of content steering nodes into an object
* containing CDN content steering information from the MPD manifest.
*
* For more information on the DASH spec for Content Steering parsing, see:
* https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
*
* @param {Node[]} contentSteeringNodes
* Content steering nodes
* @param {Function} eventHandler
* The event handler passed into the parser options to handle warnings
* @return {Object}
* Object containing content steering data
*/
const generateContentSteeringInformation = (contentSteeringNodes, eventHandler) => {
// If there are more than one ContentSteering tags, throw an error
if (contentSteeringNodes.length > 1) {
eventHandler({
type: 'warn',
message: 'The MPD manifest should contain no more than one ContentSteering tag'
});
} // Return a null value if there are no ContentSteering tags
if (!contentSteeringNodes.length) {
return null;
}
const infoFromContentSteeringTag = merge({
serverURL: getContent(contentSteeringNodes[0])
}, mpd_parser_es_parseAttributes(contentSteeringNodes[0])); // Converts `queryBeforeStart` to a boolean, as well as setting the default value
// to `false` if it doesn't exist
infoFromContentSteeringTag.queryBeforeStart = infoFromContentSteeringTag.queryBeforeStart === 'true';
return infoFromContentSteeringTag;
};
/**
* Gets Period@start property for a given period.
*
* @param {Object} options
* Options object
* @param {Object} options.attributes
* Period attributes
* @param {Object} [options.priorPeriodAttributes]
* Prior period attributes (if prior period is available)
* @param {string} options.mpdType
* The MPD@type these periods came from
* @return {number|null}
* The period start, or null if it's an early available period or error
*/
const getPeriodStart = ({
attributes,
priorPeriodAttributes,
mpdType
}) => {
// Summary of period start time calculation from DASH spec section 5.3.2.1
//
// A period's start is the first period's start + time elapsed after playing all
// prior periods to this one. Periods continue one after the other in time (without
// gaps) until the end of the presentation.
//
// The value of Period@start should be:
// 1. if Period@start is present: value of Period@start
// 2. if previous period exists and it has @duration: previous Period@start +
// previous Period@duration
// 3. if this is first period and MPD@type is 'static': 0
// 4. in all other cases, consider the period an "early available period" (note: not
// currently supported)
// (1)
if (typeof attributes.start === 'number') {
return attributes.start;
} // (2)
if (priorPeriodAttributes && typeof priorPeriodAttributes.start === 'number' && typeof priorPeriodAttributes.duration === 'number') {
return priorPeriodAttributes.start + priorPeriodAttributes.duration;
} // (3)
if (!priorPeriodAttributes && mpdType === 'static') {
return 0;
} // (4)
// There is currently no logic for calculating the Period@start value if there is
// no Period@start or prior Period@start and Period@duration available. This is not made
// explicit by the DASH interop guidelines or the DASH spec, however, since there's
// nothing about any other resolution strategies, it's implied. Thus, this case should
// be considered an early available period, or error, and null should suffice for both
// of those cases.
return null;
};
/**
* Traverses the mpd xml tree to generate a list of Representation information objects
* that have inherited attributes from parent nodes
*
* @param {Node} mpd
* The root node of the mpd
* @param {Object} options
* Available options for inheritAttributes
* @param {string} options.manifestUri
* The uri source of the mpd
* @param {number} options.NOW
* Current time per DASH IOP. Default is current time in ms since epoch
* @param {number} options.clientOffset
* Client time difference from NOW (in milliseconds)
* @return {RepresentationInformation[]}
* List of objects containing Representation information
*/
const inheritAttributes = (mpd, options = {}) => {
const {
manifestUri = '',
NOW = Date.now(),
clientOffset = 0,
// TODO: For now, we are expecting an eventHandler callback function
// to be passed into the mpd parser as an option.
// In the future, we should enable stream parsing by using the Stream class from vhs-utils.
// This will support new features including a standardized event handler.
// See the m3u8 parser for examples of how stream parsing is currently used for HLS parsing.
// https://github.com/videojs/vhs-utils/blob/88d6e10c631e57a5af02c5a62bc7376cd456b4f5/src/stream.js#L9
eventHandler = function () {}
} = options;
const periodNodes = findChildren(mpd, 'Period');
if (!periodNodes.length) {
throw new Error(errors.INVALID_NUMBER_OF_PERIOD);
}
const locations = findChildren(mpd, 'Location');
const mpdAttributes = mpd_parser_es_parseAttributes(mpd);
const mpdBaseUrls = buildBaseUrls([{
baseUrl: manifestUri
}], findChildren(mpd, 'BaseURL'));
const contentSteeringNodes = findChildren(mpd, 'ContentSteering'); // See DASH spec section 5.3.1.2, Semantics of MPD element. Default type to 'static'.
mpdAttributes.type = mpdAttributes.type || 'static';
mpdAttributes.sourceDuration = mpdAttributes.mediaPresentationDuration || 0;
mpdAttributes.NOW = NOW;
mpdAttributes.clientOffset = clientOffset;
if (locations.length) {
mpdAttributes.locations = locations.map(getContent);
}
const periods = []; // Since toAdaptationSets acts on individual periods right now, the simplest approach to
// adding properties that require looking at prior periods is to parse attributes and add
// missing ones before toAdaptationSets is called. If more such properties are added, it
// may be better to refactor toAdaptationSets.
periodNodes.forEach((node, index) => {
const attributes = mpd_parser_es_parseAttributes(node); // Use the last modified prior period, as it may contain added information necessary
// for this period.
const priorPeriod = periods[index - 1];
attributes.start = getPeriodStart({
attributes,
priorPeriodAttributes: priorPeriod ? priorPeriod.attributes : null,
mpdType: mpdAttributes.type
});
periods.push({
node,
attributes
});
});
return {
locations: mpdAttributes.locations,
contentSteeringInfo: generateContentSteeringInformation(contentSteeringNodes, eventHandler),
// TODO: There are occurences where this `representationInfo` array contains undesired
// duplicates. This generally occurs when there are multiple BaseURL nodes that are
// direct children of the MPD node. When we attempt to resolve URLs from a combination of the
// parent BaseURL and a child BaseURL, and the value does not resolve,
// we end up returning the child BaseURL multiple times.
// We need to determine a way to remove these duplicates in a safe way.
// See: https://github.com/videojs/mpd-parser/pull/17#discussion_r162750527
representationInfo: flatten(periods.map(toAdaptationSets(mpdAttributes, mpdBaseUrls))),
eventStream: flatten(periods.map(toEventStream))
};
};
const stringToMpdXml = manifestString => {
if (manifestString === '') {
throw new Error(errors.DASH_EMPTY_MANIFEST);
}
const parser = new xmldom_lib.DOMParser();
let xml;
let mpd;
try {
xml = parser.parseFromString(manifestString, 'application/xml');
mpd = xml && xml.documentElement.tagName === 'MPD' ? xml.documentElement : null;
} catch (e) {// ie 11 throws on invalid xml
}
if (!mpd || mpd && mpd.getElementsByTagName('parsererror').length > 0) {
throw new Error(errors.DASH_INVALID_XML);
}
return mpd;
};
/**
* Parses the manifest for a UTCTiming node, returning the nodes attributes if found
*
* @param {string} mpd
* XML string of the MPD manifest
* @return {Object|null}
* Attributes of UTCTiming node specified in the manifest. Null if none found
*/
const parseUTCTimingScheme = mpd => {
const UTCTimingNode = findChildren(mpd, 'UTCTiming')[0];
if (!UTCTimingNode) {
return null;
}
const attributes = mpd_parser_es_parseAttributes(UTCTimingNode);
switch (attributes.schemeIdUri) {
case 'urn:mpeg:dash:utc:http-head:2014':
case 'urn:mpeg:dash:utc:http-head:2012':
attributes.method = 'HEAD';
break;
case 'urn:mpeg:dash:utc:http-xsdate:2014':
case 'urn:mpeg:dash:utc:http-iso:2014':
case 'urn:mpeg:dash:utc:http-xsdate:2012':
case 'urn:mpeg:dash:utc:http-iso:2012':
attributes.method = 'GET';
break;
case 'urn:mpeg:dash:utc:direct:2014':
case 'urn:mpeg:dash:utc:direct:2012':
attributes.method = 'DIRECT';
attributes.value = Date.parse(attributes.value);
break;
case 'urn:mpeg:dash:utc:http-ntp:2014':
case 'urn:mpeg:dash:utc:ntp:2014':
case 'urn:mpeg:dash:utc:sntp:2014':
default:
throw new Error(errors.UNSUPPORTED_UTC_TIMING_SCHEME);
}
return attributes;
};
const VERSION = (/* unused pure expression or super */ null && (version));
/*
* Given a DASH manifest string and options, parses the DASH manifest into an object in the
* form outputed by m3u8-parser and accepted by videojs/http-streaming.
*
* For live DASH manifests, if `previousManifest` is provided in options, then the newly
* parsed DASH manifest will have its media sequence and discontinuity sequence values
* updated to reflect its position relative to the prior manifest.
*
* @param {string} manifestString - the DASH manifest as a string
* @param {options} [options] - any options
*
* @return {Object} the manifest object
*/
const parse = (manifestString, options = {}) => {
const parsedManifestInfo = inheritAttributes(stringToMpdXml(manifestString), options);
const playlists = toPlaylists(parsedManifestInfo.representationInfo);
return toM3u8({
dashPlaylists: playlists,
locations: parsedManifestInfo.locations,
contentSteering: parsedManifestInfo.contentSteeringInfo,
sidxMapping: options.sidxMapping,
previousManifest: options.previousManifest,
eventStream: parsedManifestInfo.eventStream
});
};
/**
* Parses the manifest for a UTCTiming node, returning the nodes attributes if found
*
* @param {string} manifestString
* XML string of the MPD manifest
* @return {Object|null}
* Attributes of UTCTiming node specified in the manifest. Null if none found
*/
const parseUTCTiming = manifestString => parseUTCTimingScheme(stringToMpdXml(manifestString));
// EXTERNAL MODULE: ./node_modules/mux.js/lib/tools/parse-sidx.js
var parse_sidx = __webpack_require__(962);
var parse_sidx_default = /*#__PURE__*/__webpack_require__.n(parse_sidx);
;// ./node_modules/@videojs/vhs-utils/es/id3-helpers.js
var ID3 = byte_helpers_toUint8([0x49, 0x44, 0x33]);
var getId3Size = function getId3Size(bytes, offset) {
if (offset === void 0) {
offset = 0;
}
bytes = byte_helpers_toUint8(bytes);
var flags = bytes[offset + 5];
var returnSize = bytes[offset + 6] << 21 | bytes[offset + 7] << 14 | bytes[offset + 8] << 7 | bytes[offset + 9];
var footerPresent = (flags & 16) >> 4;
if (footerPresent) {
return returnSize + 20;
}
return returnSize + 10;
};
var getId3Offset = function getId3Offset(bytes, offset) {
if (offset === void 0) {
offset = 0;
}
bytes = byte_helpers_toUint8(bytes);
if (bytes.length - offset < 10 || !byte_helpers_bytesMatch(bytes, ID3, {
offset: offset
})) {
return offset;
}
offset += getId3Size(bytes, offset); // recursive check for id3 tags as some files
// have multiple ID3 tag sections even though
// they should not.
return getId3Offset(bytes, offset);
};
;// ./node_modules/@videojs/vhs-utils/es/codec-helpers.js
// https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax
// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/codecs_parameter#AV1
var codec_helpers_getAv1Codec = function getAv1Codec(bytes) {
var codec = '';
var profile = bytes[1] >>> 3;
var level = bytes[1] & 0x1F;
var tier = bytes[2] >>> 7;
var highBitDepth = (bytes[2] & 0x40) >> 6;
var twelveBit = (bytes[2] & 0x20) >> 5;
var monochrome = (bytes[2] & 0x10) >> 4;
var chromaSubsamplingX = (bytes[2] & 0x08) >> 3;
var chromaSubsamplingY = (bytes[2] & 0x04) >> 2;
var chromaSamplePosition = bytes[2] & 0x03;
codec += profile + "." + padStart(level, 2, '0');
if (tier === 0) {
codec += 'M';
} else if (tier === 1) {
codec += 'H';
}
var bitDepth;
if (profile === 2 && highBitDepth) {
bitDepth = twelveBit ? 12 : 10;
} else {
bitDepth = highBitDepth ? 10 : 8;
}
codec += "." + padStart(bitDepth, 2, '0'); // TODO: can we parse color range??
codec += "." + monochrome;
codec += "." + chromaSubsamplingX + chromaSubsamplingY + chromaSamplePosition;
return codec;
};
var codec_helpers_getAvcCodec = function getAvcCodec(bytes) {
var profileId = toHexString(bytes[1]);
var constraintFlags = toHexString(bytes[2] & 0xFC);
var levelId = toHexString(bytes[3]);
return "" + profileId + constraintFlags + levelId;
};
var codec_helpers_getHvcCodec = function getHvcCodec(bytes) {
var codec = '';
var profileSpace = bytes[1] >> 6;
var profileId = bytes[1] & 0x1F;
var tierFlag = (bytes[1] & 0x20) >> 5;
var profileCompat = bytes.subarray(2, 6);
var constraintIds = bytes.subarray(6, 12);
var levelId = bytes[12];
if (profileSpace === 1) {
codec += 'A';
} else if (profileSpace === 2) {
codec += 'B';
} else if (profileSpace === 3) {
codec += 'C';
}
codec += profileId + "."; // ffmpeg does this in big endian
var profileCompatVal = parseInt(toBinaryString(profileCompat).split('').reverse().join(''), 2); // apple does this in little endian...
if (profileCompatVal > 255) {
profileCompatVal = parseInt(toBinaryString(profileCompat), 2);
}
codec += profileCompatVal.toString(16) + ".";
if (tierFlag === 0) {
codec += 'L';
} else {
codec += 'H';
}
codec += levelId;
var constraints = '';
for (var i = 0; i < constraintIds.length; i++) {
var v = constraintIds[i];
if (v) {
if (constraints) {
constraints += '.';
}
constraints += v.toString(16);
}
}
if (constraints) {
codec += "." + constraints;
}
return codec;
};
;// ./node_modules/@videojs/vhs-utils/es/opus-helpers.js
var OPUS_HEAD = new Uint8Array([// O, p, u, s
0x4f, 0x70, 0x75, 0x73, // H, e, a, d
0x48, 0x65, 0x61, 0x64]); // https://wiki.xiph.org/OggOpus
// https://vfrmaniac.fushizen.eu/contents/opus_in_isobmff.html
// https://opus-codec.org/docs/opusfile_api-0.7/structOpusHead.html
var opus_helpers_parseOpusHead = function parseOpusHead(bytes) {
var view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
var version = view.getUint8(0); // version 0, from mp4, does not use littleEndian.
var littleEndian = version !== 0;
var config = {
version: version,
channels: view.getUint8(1),
preSkip: view.getUint16(2, littleEndian),
sampleRate: view.getUint32(4, littleEndian),
outputGain: view.getUint16(8, littleEndian),
channelMappingFamily: view.getUint8(10)
};
if (config.channelMappingFamily > 0 && bytes.length > 10) {
config.streamCount = view.getUint8(11);
config.twoChannelStreamCount = view.getUint8(12);
config.channelMapping = [];
for (var c = 0; c < config.channels; c++) {
config.channelMapping.push(view.getUint8(13 + c));
}
}
return config;
};
var setOpusHead = function setOpusHead(config) {
var size = config.channelMappingFamily <= 0 ? 11 : 12 + config.channels;
var view = new DataView(new ArrayBuffer(size));
var littleEndian = config.version !== 0;
view.setUint8(0, config.version);
view.setUint8(1, config.channels);
view.setUint16(2, config.preSkip, littleEndian);
view.setUint32(4, config.sampleRate, littleEndian);
view.setUint16(8, config.outputGain, littleEndian);
view.setUint8(10, config.channelMappingFamily);
if (config.channelMappingFamily > 0) {
view.setUint8(11, config.streamCount);
config.channelMapping.foreach(function (cm, i) {
view.setUint8(12 + i, cm);
});
}
return new Uint8Array(view.buffer);
};
;// ./node_modules/@videojs/vhs-utils/es/mp4-helpers.js
var normalizePath = function normalizePath(path) {
if (typeof path === 'string') {
return stringToBytes(path);
}
if (typeof path === 'number') {
return path;
}
return path;
};
var normalizePaths = function normalizePaths(paths) {
if (!Array.isArray(paths)) {
return [normalizePath(paths)];
}
return paths.map(function (p) {
return normalizePath(p);
});
};
var DESCRIPTORS;
var parseDescriptors = function parseDescriptors(bytes) {
bytes = byte_helpers_toUint8(bytes);
var results = [];
var i = 0;
while (bytes.length > i) {
var tag = bytes[i];
var size = 0;
var headerSize = 0; // tag
headerSize++;
var byte = bytes[headerSize]; // first byte
headerSize++;
while (byte & 0x80) {
size = (byte & 0x7F) << 7;
byte = bytes[headerSize];
headerSize++;
}
size += byte & 0x7F;
for (var z = 0; z < DESCRIPTORS.length; z++) {
var _DESCRIPTORS$z = DESCRIPTORS[z],
id = _DESCRIPTORS$z.id,
parser = _DESCRIPTORS$z.parser;
if (tag === id) {
results.push(parser(bytes.subarray(headerSize, headerSize + size)));
break;
}
}
i += size + headerSize;
}
return results;
};
DESCRIPTORS = [{
id: 0x03,
parser: function parser(bytes) {
var desc = {
tag: 0x03,
id: bytes[0] << 8 | bytes[1],
flags: bytes[2],
size: 3,
dependsOnEsId: 0,
ocrEsId: 0,
descriptors: [],
url: ''
}; // depends on es id
if (desc.flags & 0x80) {
desc.dependsOnEsId = bytes[desc.size] << 8 | bytes[desc.size + 1];
desc.size += 2;
} // url
if (desc.flags & 0x40) {
var len = bytes[desc.size];
desc.url = byte_helpers_bytesToString(bytes.subarray(desc.size + 1, desc.size + 1 + len));
desc.size += len;
} // ocr es id
if (desc.flags & 0x20) {
desc.ocrEsId = bytes[desc.size] << 8 | bytes[desc.size + 1];
desc.size += 2;
}
desc.descriptors = parseDescriptors(bytes.subarray(desc.size)) || [];
return desc;
}
}, {
id: 0x04,
parser: function parser(bytes) {
// DecoderConfigDescriptor
var desc = {
tag: 0x04,
oti: bytes[0],
streamType: bytes[1],
bufferSize: bytes[2] << 16 | bytes[3] << 8 | bytes[4],
maxBitrate: bytes[5] << 24 | bytes[6] << 16 | bytes[7] << 8 | bytes[8],
avgBitrate: bytes[9] << 24 | bytes[10] << 16 | bytes[11] << 8 | bytes[12],
descriptors: parseDescriptors(bytes.subarray(13))
};
return desc;
}
}, {
id: 0x05,
parser: function parser(bytes) {
// DecoderSpecificInfo
return {
tag: 0x05,
bytes: bytes
};
}
}, {
id: 0x06,
parser: function parser(bytes) {
// SLConfigDescriptor
return {
tag: 0x06,
bytes: bytes
};
}
}];
/**
* find any number of boxes by name given a path to it in an iso bmff
* such as mp4.
*
* @param {TypedArray} bytes
* bytes for the iso bmff to search for boxes in
*
* @param {Uint8Array[]|string[]|string|Uint8Array} name
* An array of paths or a single path representing the name
* of boxes to search through in bytes. Paths may be
* uint8 (character codes) or strings.
*
* @param {boolean} [complete=false]
* Should we search only for complete boxes on the final path.
* This is very useful when you do not want to get back partial boxes
* in the case of streaming files.
*
* @return {Uint8Array[]}
* An array of the end paths that we found.
*/
var findBox = function findBox(bytes, paths, complete) {
if (complete === void 0) {
complete = false;
}
paths = normalizePaths(paths);
bytes = byte_helpers_toUint8(bytes);
var results = [];
if (!paths.length) {
// short-circuit the search for empty paths
return results;
}
var i = 0;
while (i < bytes.length) {
var size = (bytes[i] << 24 | bytes[i + 1] << 16 | bytes[i + 2] << 8 | bytes[i + 3]) >>> 0;
var type = bytes.subarray(i + 4, i + 8); // invalid box format.
if (size === 0) {
break;
}
var end = i + size;
if (end > bytes.length) {
// this box is bigger than the number of bytes we have
// and complete is set, we cannot find any more boxes.
if (complete) {
break;
}
end = bytes.length;
}
var data = bytes.subarray(i + 8, end);
if (byte_helpers_bytesMatch(type, paths[0])) {
if (paths.length === 1) {
// this is the end of the path and we've found the box we were
// looking for
results.push(data);
} else {
// recursively search for the next box along the path
results.push.apply(results, findBox(data, paths.slice(1), complete));
}
}
i = end;
} // we've finished searching all of bytes
return results;
};
/**
* Search for a single matching box by name in an iso bmff format like
* mp4. This function is useful for finding codec boxes which
* can be placed arbitrarily in sample descriptions depending
* on the version of the file or file type.
*
* @param {TypedArray} bytes
* bytes for the iso bmff to search for boxes in
*
* @param {string|Uint8Array} name
* The name of the box to find.
*
* @return {Uint8Array[]}
* a subarray of bytes representing the name boxed we found.
*/
var findNamedBox = function findNamedBox(bytes, name) {
name = normalizePath(name);
if (!name.length) {
// short-circuit the search for empty paths
return bytes.subarray(bytes.length);
}
var i = 0;
while (i < bytes.length) {
if (bytesMatch(bytes.subarray(i, i + name.length), name)) {
var size = (bytes[i - 4] << 24 | bytes[i - 3] << 16 | bytes[i - 2] << 8 | bytes[i - 1]) >>> 0;
var end = size > 1 ? i + size : bytes.byteLength;
return bytes.subarray(i + 4, end);
}
i++;
} // we've finished searching all of bytes
return bytes.subarray(bytes.length);
};
var parseSamples = function parseSamples(data, entrySize, parseEntry) {
if (entrySize === void 0) {
entrySize = 4;
}
if (parseEntry === void 0) {
parseEntry = function parseEntry(d) {
return bytesToNumber(d);
};
}
var entries = [];
if (!data || !data.length) {
return entries;
}
var entryCount = bytesToNumber(data.subarray(4, 8));
for (var i = 8; entryCount; i += entrySize, entryCount--) {
entries.push(parseEntry(data.subarray(i, i + entrySize)));
}
return entries;
};
var buildFrameTable = function buildFrameTable(stbl, timescale) {
var keySamples = parseSamples(findBox(stbl, ['stss'])[0]);
var chunkOffsets = parseSamples(findBox(stbl, ['stco'])[0]);
var timeToSamples = parseSamples(findBox(stbl, ['stts'])[0], 8, function (entry) {
return {
sampleCount: bytesToNumber(entry.subarray(0, 4)),
sampleDelta: bytesToNumber(entry.subarray(4, 8))
};
});
var samplesToChunks = parseSamples(findBox(stbl, ['stsc'])[0], 12, function (entry) {
return {
firstChunk: bytesToNumber(entry.subarray(0, 4)),
samplesPerChunk: bytesToNumber(entry.subarray(4, 8)),
sampleDescriptionIndex: bytesToNumber(entry.subarray(8, 12))
};
});
var stsz = findBox(stbl, ['stsz'])[0]; // stsz starts with a 4 byte sampleSize which we don't need
var sampleSizes = parseSamples(stsz && stsz.length && stsz.subarray(4) || null);
var frames = [];
for (var chunkIndex = 0; chunkIndex < chunkOffsets.length; chunkIndex++) {
var samplesInChunk = void 0;
for (var i = 0; i < samplesToChunks.length; i++) {
var sampleToChunk = samplesToChunks[i];
var isThisOne = chunkIndex + 1 >= sampleToChunk.firstChunk && (i + 1 >= samplesToChunks.length || chunkIndex + 1 < samplesToChunks[i + 1].firstChunk);
if (isThisOne) {
samplesInChunk = sampleToChunk.samplesPerChunk;
break;
}
}
var chunkOffset = chunkOffsets[chunkIndex];
for (var _i = 0; _i < samplesInChunk; _i++) {
var frameEnd = sampleSizes[frames.length]; // if we don't have key samples every frame is a keyframe
var keyframe = !keySamples.length;
if (keySamples.length && keySamples.indexOf(frames.length + 1) !== -1) {
keyframe = true;
}
var frame = {
keyframe: keyframe,
start: chunkOffset,
end: chunkOffset + frameEnd
};
for (var k = 0; k < timeToSamples.length; k++) {
var _timeToSamples$k = timeToSamples[k],
sampleCount = _timeToSamples$k.sampleCount,
sampleDelta = _timeToSamples$k.sampleDelta;
if (frames.length <= sampleCount) {
// ms to ns
var lastTimestamp = frames.length ? frames[frames.length - 1].timestamp : 0;
frame.timestamp = lastTimestamp + sampleDelta / timescale * 1000;
frame.duration = sampleDelta;
break;
}
}
frames.push(frame);
chunkOffset += frameEnd;
}
}
return frames;
};
var addSampleDescription = function addSampleDescription(track, bytes) {
var codec = bytesToString(bytes.subarray(0, 4));
if (track.type === 'video') {
track.info = track.info || {};
track.info.width = bytes[28] << 8 | bytes[29];
track.info.height = bytes[30] << 8 | bytes[31];
} else if (track.type === 'audio') {
track.info = track.info || {};
track.info.channels = bytes[20] << 8 | bytes[21];
track.info.bitDepth = bytes[22] << 8 | bytes[23];
track.info.sampleRate = bytes[28] << 8 | bytes[29];
}
if (codec === 'avc1') {
var avcC = findNamedBox(bytes, 'avcC'); // AVCDecoderConfigurationRecord
codec += "." + getAvcCodec(avcC);
track.info.avcC = avcC; // TODO: do we need to parse all this?
/* {
configurationVersion: avcC[0],
profile: avcC[1],
profileCompatibility: avcC[2],
level: avcC[3],
lengthSizeMinusOne: avcC[4] & 0x3
};
let spsNalUnitCount = avcC[5] & 0x1F;
const spsNalUnits = track.info.avc.spsNalUnits = [];
// past spsNalUnitCount
let offset = 6;
while (spsNalUnitCount--) {
const nalLen = avcC[offset] << 8 | avcC[offset + 1];
spsNalUnits.push(avcC.subarray(offset + 2, offset + 2 + nalLen));
offset += nalLen + 2;
}
let ppsNalUnitCount = avcC[offset];
const ppsNalUnits = track.info.avc.ppsNalUnits = [];
// past ppsNalUnitCount
offset += 1;
while (ppsNalUnitCount--) {
const nalLen = avcC[offset] << 8 | avcC[offset + 1];
ppsNalUnits.push(avcC.subarray(offset + 2, offset + 2 + nalLen));
offset += nalLen + 2;
}*/
// HEVCDecoderConfigurationRecord
} else if (codec === 'hvc1' || codec === 'hev1') {
codec += "." + getHvcCodec(findNamedBox(bytes, 'hvcC'));
} else if (codec === 'mp4a' || codec === 'mp4v') {
var esds = findNamedBox(bytes, 'esds');
var esDescriptor = parseDescriptors(esds.subarray(4))[0];
var decoderConfig = esDescriptor && esDescriptor.descriptors.filter(function (_ref) {
var tag = _ref.tag;
return tag === 0x04;
})[0];
if (decoderConfig) {
// most codecs do not have a further '.'
// such as 0xa5 for ac-3 and 0xa6 for e-ac-3
codec += '.' + toHexString(decoderConfig.oti);
if (decoderConfig.oti === 0x40) {
codec += '.' + (decoderConfig.descriptors[0].bytes[0] >> 3).toString();
} else if (decoderConfig.oti === 0x20) {
codec += '.' + decoderConfig.descriptors[0].bytes[4].toString();
} else if (decoderConfig.oti === 0xdd) {
codec = 'vorbis';
}
} else if (track.type === 'audio') {
codec += '.40.2';
} else {
codec += '.20.9';
}
} else if (codec === 'av01') {
// AV1DecoderConfigurationRecord
codec += "." + getAv1Codec(findNamedBox(bytes, 'av1C'));
} else if (codec === 'vp09') {
// VPCodecConfigurationRecord
var vpcC = findNamedBox(bytes, 'vpcC'); // https://www.webmproject.org/vp9/mp4/
var profile = vpcC[0];
var level = vpcC[1];
var bitDepth = vpcC[2] >> 4;
var chromaSubsampling = (vpcC[2] & 0x0F) >> 1;
var videoFullRangeFlag = (vpcC[2] & 0x0F) >> 3;
var colourPrimaries = vpcC[3];
var transferCharacteristics = vpcC[4];
var matrixCoefficients = vpcC[5];
codec += "." + padStart(profile, 2, '0');
codec += "." + padStart(level, 2, '0');
codec += "." + padStart(bitDepth, 2, '0');
codec += "." + padStart(chromaSubsampling, 2, '0');
codec += "." + padStart(colourPrimaries, 2, '0');
codec += "." + padStart(transferCharacteristics, 2, '0');
codec += "." + padStart(matrixCoefficients, 2, '0');
codec += "." + padStart(videoFullRangeFlag, 2, '0');
} else if (codec === 'theo') {
codec = 'theora';
} else if (codec === 'spex') {
codec = 'speex';
} else if (codec === '.mp3') {
codec = 'mp4a.40.34';
} else if (codec === 'msVo') {
codec = 'vorbis';
} else if (codec === 'Opus') {
codec = 'opus';
var dOps = findNamedBox(bytes, 'dOps');
track.info.opus = parseOpusHead(dOps); // TODO: should this go into the webm code??
// Firefox requires a codecDelay for opus playback
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1276238
track.info.codecDelay = 6500000;
} else {
codec = codec.toLowerCase();
}
/* eslint-enable */
// flac, ac-3, ec-3, opus
track.codec = codec;
};
var parseTracks = function parseTracks(bytes, frameTable) {
if (frameTable === void 0) {
frameTable = true;
}
bytes = toUint8(bytes);
var traks = findBox(bytes, ['moov', 'trak'], true);
var tracks = [];
traks.forEach(function (trak) {
var track = {
bytes: trak
};
var mdia = findBox(trak, ['mdia'])[0];
var hdlr = findBox(mdia, ['hdlr'])[0];
var trakType = bytesToString(hdlr.subarray(8, 12));
if (trakType === 'soun') {
track.type = 'audio';
} else if (trakType === 'vide') {
track.type = 'video';
} else {
track.type = trakType;
}
var tkhd = findBox(trak, ['tkhd'])[0];
if (tkhd) {
var view = new DataView(tkhd.buffer, tkhd.byteOffset, tkhd.byteLength);
var tkhdVersion = view.getUint8(0);
track.number = tkhdVersion === 0 ? view.getUint32(12) : view.getUint32(20);
}
var mdhd = findBox(mdia, ['mdhd'])[0];
if (mdhd) {
// mdhd is a FullBox, meaning it will have its own version as the first byte
var version = mdhd[0];
var index = version === 0 ? 12 : 20;
track.timescale = (mdhd[index] << 24 | mdhd[index + 1] << 16 | mdhd[index + 2] << 8 | mdhd[index + 3]) >>> 0;
}
var stbl = findBox(mdia, ['minf', 'stbl'])[0];
var stsd = findBox(stbl, ['stsd'])[0];
var descriptionCount = bytesToNumber(stsd.subarray(4, 8));
var offset = 8; // add codec and codec info
while (descriptionCount--) {
var len = bytesToNumber(stsd.subarray(offset, offset + 4));
var sampleDescriptor = stsd.subarray(offset + 4, offset + 4 + len);
addSampleDescription(track, sampleDescriptor);
offset += 4 + len;
}
if (frameTable) {
track.frameTable = buildFrameTable(stbl, track.timescale);
} // codec has no sub parameters
tracks.push(track);
});
return tracks;
};
var parseMediaInfo = function parseMediaInfo(bytes) {
var mvhd = findBox(bytes, ['moov', 'mvhd'], true)[0];
if (!mvhd || !mvhd.length) {
return;
}
var info = {}; // ms to ns
// mvhd v1 has 8 byte duration and other fields too
if (mvhd[0] === 1) {
info.timestampScale = bytesToNumber(mvhd.subarray(20, 24));
info.duration = bytesToNumber(mvhd.subarray(24, 32));
} else {
info.timestampScale = bytesToNumber(mvhd.subarray(12, 16));
info.duration = bytesToNumber(mvhd.subarray(16, 20));
}
info.bytes = mvhd;
return info;
};
;// ./node_modules/@videojs/vhs-utils/es/ebml-helpers.js
// relevant specs for this parser:
// https://matroska-org.github.io/libebml/specs.html
// https://www.matroska.org/technical/elements.html
// https://www.webmproject.org/docs/container/
var EBML_TAGS = {
EBML: byte_helpers_toUint8([0x1A, 0x45, 0xDF, 0xA3]),
DocType: byte_helpers_toUint8([0x42, 0x82]),
Segment: byte_helpers_toUint8([0x18, 0x53, 0x80, 0x67]),
SegmentInfo: byte_helpers_toUint8([0x15, 0x49, 0xA9, 0x66]),
Tracks: byte_helpers_toUint8([0x16, 0x54, 0xAE, 0x6B]),
Track: byte_helpers_toUint8([0xAE]),
TrackNumber: byte_helpers_toUint8([0xd7]),
DefaultDuration: byte_helpers_toUint8([0x23, 0xe3, 0x83]),
TrackEntry: byte_helpers_toUint8([0xAE]),
TrackType: byte_helpers_toUint8([0x83]),
FlagDefault: byte_helpers_toUint8([0x88]),
CodecID: byte_helpers_toUint8([0x86]),
CodecPrivate: byte_helpers_toUint8([0x63, 0xA2]),
VideoTrack: byte_helpers_toUint8([0xe0]),
AudioTrack: byte_helpers_toUint8([0xe1]),
// Not used yet, but will be used for live webm/mkv
// see https://www.matroska.org/technical/basics.html#block-structure
// see https://www.matroska.org/technical/basics.html#simpleblock-structure
Cluster: byte_helpers_toUint8([0x1F, 0x43, 0xB6, 0x75]),
Timestamp: byte_helpers_toUint8([0xE7]),
TimestampScale: byte_helpers_toUint8([0x2A, 0xD7, 0xB1]),
BlockGroup: byte_helpers_toUint8([0xA0]),
BlockDuration: byte_helpers_toUint8([0x9B]),
Block: byte_helpers_toUint8([0xA1]),
SimpleBlock: byte_helpers_toUint8([0xA3])
};
/**
* This is a simple table to determine the length
* of things in ebml. The length is one based (starts at 1,
* rather than zero) and for every zero bit before a one bit
* we add one to length. We also need this table because in some
* case we have to xor all the length bits from another value.
*/
var LENGTH_TABLE = [128, 64, 32, 16, 8, 4, 2, 1];
var getLength = function getLength(byte) {
var len = 1;
for (var i = 0; i < LENGTH_TABLE.length; i++) {
if (byte & LENGTH_TABLE[i]) {
break;
}
len++;
}
return len;
}; // length in ebml is stored in the first 4 to 8 bits
// of the first byte. 4 for the id length and 8 for the
// data size length. Length is measured by converting the number to binary
// then 1 + the number of zeros before a 1 is encountered starting
// from the left.
var getvint = function getvint(bytes, offset, removeLength, signed) {
if (removeLength === void 0) {
removeLength = true;
}
if (signed === void 0) {
signed = false;
}
var length = getLength(bytes[offset]);
var valueBytes = bytes.subarray(offset, offset + length); // NOTE that we do **not** subarray here because we need to copy these bytes
// as they will be modified below to remove the dataSizeLen bits and we do not
// want to modify the original data. normally we could just call slice on
// uint8array but ie 11 does not support that...
if (removeLength) {
valueBytes = Array.prototype.slice.call(bytes, offset, offset + length);
valueBytes[0] ^= LENGTH_TABLE[length - 1];
}
return {
length: length,
value: byte_helpers_bytesToNumber(valueBytes, {
signed: signed
}),
bytes: valueBytes
};
};
var ebml_helpers_normalizePath = function normalizePath(path) {
if (typeof path === 'string') {
return path.match(/.{1,2}/g).map(function (p) {
return normalizePath(p);
});
}
if (typeof path === 'number') {
return numberToBytes(path);
}
return path;
};
var ebml_helpers_normalizePaths = function normalizePaths(paths) {
if (!Array.isArray(paths)) {
return [ebml_helpers_normalizePath(paths)];
}
return paths.map(function (p) {
return ebml_helpers_normalizePath(p);
});
};
var getInfinityDataSize = function getInfinityDataSize(id, bytes, offset) {
if (offset >= bytes.length) {
return bytes.length;
}
var innerid = getvint(bytes, offset, false);
if (byte_helpers_bytesMatch(id.bytes, innerid.bytes)) {
return offset;
}
var dataHeader = getvint(bytes, offset + innerid.length);
return getInfinityDataSize(id, bytes, offset + dataHeader.length + dataHeader.value + innerid.length);
};
/**
* Notes on the EBLM format.
*
* EBLM uses "vints" tags. Every vint tag contains
* two parts
*
* 1. The length from the first byte. You get this by
* converting the byte to binary and counting the zeros
* before a 1. Then you add 1 to that. Examples
* 00011111 = length 4 because there are 3 zeros before a 1.
* 00100000 = length 3 because there are 2 zeros before a 1.
* 00000011 = length 7 because there are 6 zeros before a 1.
*
* 2. The bits used for length are removed from the first byte
* Then all the bytes are merged into a value. NOTE: this
* is not the case for id ebml tags as there id includes
* length bits.
*
*/
var findEbml = function findEbml(bytes, paths) {
paths = ebml_helpers_normalizePaths(paths);
bytes = byte_helpers_toUint8(bytes);
var results = [];
if (!paths.length) {
return results;
}
var i = 0;
while (i < bytes.length) {
var id = getvint(bytes, i, false);
var dataHeader = getvint(bytes, i + id.length);
var dataStart = i + id.length + dataHeader.length; // dataSize is unknown or this is a live stream
if (dataHeader.value === 0x7f) {
dataHeader.value = getInfinityDataSize(id, bytes, dataStart);
if (dataHeader.value !== bytes.length) {
dataHeader.value -= dataStart;
}
}
var dataEnd = dataStart + dataHeader.value > bytes.length ? bytes.length : dataStart + dataHeader.value;
var data = bytes.subarray(dataStart, dataEnd);
if (byte_helpers_bytesMatch(paths[0], id.bytes)) {
if (paths.length === 1) {
// this is the end of the paths and we've found the tag we were
// looking for
results.push(data);
} else {
// recursively search for the next tag inside of the data
// of this one
results = results.concat(findEbml(data, paths.slice(1)));
}
}
var totalLength = id.length + dataHeader.length + data.length; // move past this tag entirely, we are not looking for it
i += totalLength;
}
return results;
}; // see https://www.matroska.org/technical/basics.html#block-structure
var decodeBlock = function decodeBlock(block, type, timestampScale, clusterTimestamp) {
var duration;
if (type === 'group') {
duration = findEbml(block, [EBML_TAGS.BlockDuration])[0];
if (duration) {
duration = bytesToNumber(duration);
duration = 1 / timestampScale * duration * timestampScale / 1000;
}
block = findEbml(block, [EBML_TAGS.Block])[0];
type = 'block'; // treat data as a block after this point
}
var dv = new DataView(block.buffer, block.byteOffset, block.byteLength);
var trackNumber = getvint(block, 0);
var timestamp = dv.getInt16(trackNumber.length, false);
var flags = block[trackNumber.length + 2];
var data = block.subarray(trackNumber.length + 3); // pts/dts in seconds
var ptsdts = 1 / timestampScale * (clusterTimestamp + timestamp) * timestampScale / 1000; // return the frame
var parsed = {
duration: duration,
trackNumber: trackNumber.value,
keyframe: type === 'simple' && flags >> 7 === 1,
invisible: (flags & 0x08) >> 3 === 1,
lacing: (flags & 0x06) >> 1,
discardable: type === 'simple' && (flags & 0x01) === 1,
frames: [],
pts: ptsdts,
dts: ptsdts,
timestamp: timestamp
};
if (!parsed.lacing) {
parsed.frames.push(data);
return parsed;
}
var numberOfFrames = data[0] + 1;
var frameSizes = [];
var offset = 1; // Fixed
if (parsed.lacing === 2) {
var sizeOfFrame = (data.length - offset) / numberOfFrames;
for (var i = 0; i < numberOfFrames; i++) {
frameSizes.push(sizeOfFrame);
}
} // xiph
if (parsed.lacing === 1) {
for (var _i = 0; _i < numberOfFrames - 1; _i++) {
var size = 0;
do {
size += data[offset];
offset++;
} while (data[offset - 1] === 0xFF);
frameSizes.push(size);
}
} // ebml
if (parsed.lacing === 3) {
// first vint is unsinged
// after that vints are singed and
// based on a compounding size
var _size = 0;
for (var _i2 = 0; _i2 < numberOfFrames - 1; _i2++) {
var vint = _i2 === 0 ? getvint(data, offset) : getvint(data, offset, true, true);
_size += vint.value;
frameSizes.push(_size);
offset += vint.length;
}
}
frameSizes.forEach(function (size) {
parsed.frames.push(data.subarray(offset, offset + size));
offset += size;
});
return parsed;
}; // VP9 Codec Feature Metadata (CodecPrivate)
// https://www.webmproject.org/docs/container/
var parseVp9Private = function parseVp9Private(bytes) {
var i = 0;
var params = {};
while (i < bytes.length) {
var id = bytes[i] & 0x7f;
var len = bytes[i + 1];
var val = void 0;
if (len === 1) {
val = bytes[i + 2];
} else {
val = bytes.subarray(i + 2, i + 2 + len);
}
if (id === 1) {
params.profile = val;
} else if (id === 2) {
params.level = val;
} else if (id === 3) {
params.bitDepth = val;
} else if (id === 4) {
params.chromaSubsampling = val;
} else {
params[id] = val;
}
i += 2 + len;
}
return params;
};
var ebml_helpers_parseTracks = function parseTracks(bytes) {
bytes = toUint8(bytes);
var decodedTracks = [];
var tracks = findEbml(bytes, [EBML_TAGS.Segment, EBML_TAGS.Tracks, EBML_TAGS.Track]);
if (!tracks.length) {
tracks = findEbml(bytes, [EBML_TAGS.Tracks, EBML_TAGS.Track]);
}
if (!tracks.length) {
tracks = findEbml(bytes, [EBML_TAGS.Track]);
}
if (!tracks.length) {
return decodedTracks;
}
tracks.forEach(function (track) {
var trackType = findEbml(track, EBML_TAGS.TrackType)[0];
if (!trackType || !trackType.length) {
return;
} // 1 is video, 2 is audio, 17 is subtitle
// other values are unimportant in this context
if (trackType[0] === 1) {
trackType = 'video';
} else if (trackType[0] === 2) {
trackType = 'audio';
} else if (trackType[0] === 17) {
trackType = 'subtitle';
} else {
return;
} // todo parse language
var decodedTrack = {
rawCodec: bytesToString(findEbml(track, [EBML_TAGS.CodecID])[0]),
type: trackType,
codecPrivate: findEbml(track, [EBML_TAGS.CodecPrivate])[0],
number: bytesToNumber(findEbml(track, [EBML_TAGS.TrackNumber])[0]),
defaultDuration: bytesToNumber(findEbml(track, [EBML_TAGS.DefaultDuration])[0]),
default: findEbml(track, [EBML_TAGS.FlagDefault])[0],
rawData: track
};
var codec = '';
if (/V_MPEG4\/ISO\/AVC/.test(decodedTrack.rawCodec)) {
codec = "avc1." + getAvcCodec(decodedTrack.codecPrivate);
} else if (/V_MPEGH\/ISO\/HEVC/.test(decodedTrack.rawCodec)) {
codec = "hev1." + getHvcCodec(decodedTrack.codecPrivate);
} else if (/V_MPEG4\/ISO\/ASP/.test(decodedTrack.rawCodec)) {
if (decodedTrack.codecPrivate) {
codec = 'mp4v.20.' + decodedTrack.codecPrivate[4].toString();
} else {
codec = 'mp4v.20.9';
}
} else if (/^V_THEORA/.test(decodedTrack.rawCodec)) {
codec = 'theora';
} else if (/^V_VP8/.test(decodedTrack.rawCodec)) {
codec = 'vp8';
} else if (/^V_VP9/.test(decodedTrack.rawCodec)) {
if (decodedTrack.codecPrivate) {
var _parseVp9Private = parseVp9Private(decodedTrack.codecPrivate),
profile = _parseVp9Private.profile,
level = _parseVp9Private.level,
bitDepth = _parseVp9Private.bitDepth,
chromaSubsampling = _parseVp9Private.chromaSubsampling;
codec = 'vp09.';
codec += padStart(profile, 2, '0') + ".";
codec += padStart(level, 2, '0') + ".";
codec += padStart(bitDepth, 2, '0') + ".";
codec += "" + padStart(chromaSubsampling, 2, '0'); // Video -> Colour -> Ebml name
var matrixCoefficients = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB1]])[0] || [];
var videoFullRangeFlag = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xB9]])[0] || [];
var transferCharacteristics = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBA]])[0] || [];
var colourPrimaries = findEbml(track, [0xE0, [0x55, 0xB0], [0x55, 0xBB]])[0] || []; // if we find any optional codec parameter specify them all.
if (matrixCoefficients.length || videoFullRangeFlag.length || transferCharacteristics.length || colourPrimaries.length) {
codec += "." + padStart(colourPrimaries[0], 2, '0');
codec += "." + padStart(transferCharacteristics[0], 2, '0');
codec += "." + padStart(matrixCoefficients[0], 2, '0');
codec += "." + padStart(videoFullRangeFlag[0], 2, '0');
}
} else {
codec = 'vp9';
}
} else if (/^V_AV1/.test(decodedTrack.rawCodec)) {
codec = "av01." + getAv1Codec(decodedTrack.codecPrivate);
} else if (/A_ALAC/.test(decodedTrack.rawCodec)) {
codec = 'alac';
} else if (/A_MPEG\/L2/.test(decodedTrack.rawCodec)) {
codec = 'mp2';
} else if (/A_MPEG\/L3/.test(decodedTrack.rawCodec)) {
codec = 'mp3';
} else if (/^A_AAC/.test(decodedTrack.rawCodec)) {
if (decodedTrack.codecPrivate) {
codec = 'mp4a.40.' + (decodedTrack.codecPrivate[0] >>> 3).toString();
} else {
codec = 'mp4a.40.2';
}
} else if (/^A_AC3/.test(decodedTrack.rawCodec)) {
codec = 'ac-3';
} else if (/^A_PCM/.test(decodedTrack.rawCodec)) {
codec = 'pcm';
} else if (/^A_MS\/ACM/.test(decodedTrack.rawCodec)) {
codec = 'speex';
} else if (/^A_EAC3/.test(decodedTrack.rawCodec)) {
codec = 'ec-3';
} else if (/^A_VORBIS/.test(decodedTrack.rawCodec)) {
codec = 'vorbis';
} else if (/^A_FLAC/.test(decodedTrack.rawCodec)) {
codec = 'flac';
} else if (/^A_OPUS/.test(decodedTrack.rawCodec)) {
codec = 'opus';
}
decodedTrack.codec = codec;
decodedTracks.push(decodedTrack);
});
return decodedTracks.sort(function (a, b) {
return a.number - b.number;
});
};
var parseData = function parseData(data, tracks) {
var allBlocks = [];
var segment = findEbml(data, [EBML_TAGS.Segment])[0];
var timestampScale = findEbml(segment, [EBML_TAGS.SegmentInfo, EBML_TAGS.TimestampScale])[0]; // in nanoseconds, defaults to 1ms
if (timestampScale && timestampScale.length) {
timestampScale = bytesToNumber(timestampScale);
} else {
timestampScale = 1000000;
}
var clusters = findEbml(segment, [EBML_TAGS.Cluster]);
if (!tracks) {
tracks = ebml_helpers_parseTracks(segment);
}
clusters.forEach(function (cluster, ci) {
var simpleBlocks = findEbml(cluster, [EBML_TAGS.SimpleBlock]).map(function (b) {
return {
type: 'simple',
data: b
};
});
var blockGroups = findEbml(cluster, [EBML_TAGS.BlockGroup]).map(function (b) {
return {
type: 'group',
data: b
};
});
var timestamp = findEbml(cluster, [EBML_TAGS.Timestamp])[0] || 0;
if (timestamp && timestamp.length) {
timestamp = bytesToNumber(timestamp);
} // get all blocks then sort them into the correct order
var blocks = simpleBlocks.concat(blockGroups).sort(function (a, b) {
return a.data.byteOffset - b.data.byteOffset;
});
blocks.forEach(function (block, bi) {
var decoded = decodeBlock(block.data, block.type, timestampScale, timestamp);
allBlocks.push(decoded);
});
});
return {
tracks: tracks,
blocks: allBlocks
};
};
;// ./node_modules/@videojs/vhs-utils/es/nal-helpers.js
var NAL_TYPE_ONE = byte_helpers_toUint8([0x00, 0x00, 0x00, 0x01]);
var NAL_TYPE_TWO = byte_helpers_toUint8([0x00, 0x00, 0x01]);
var EMULATION_PREVENTION = byte_helpers_toUint8([0x00, 0x00, 0x03]);
/**
* Expunge any "Emulation Prevention" bytes from a "Raw Byte
* Sequence Payload"
*
* @param data {Uint8Array} the bytes of a RBSP from a NAL
* unit
* @return {Uint8Array} the RBSP without any Emulation
* Prevention Bytes
*/
var discardEmulationPreventionBytes = function discardEmulationPreventionBytes(bytes) {
var positions = [];
var i = 1; // Find all `Emulation Prevention Bytes`
while (i < bytes.length - 2) {
if (byte_helpers_bytesMatch(bytes.subarray(i, i + 3), EMULATION_PREVENTION)) {
positions.push(i + 2);
i++;
}
i++;
} // If no Emulation Prevention Bytes were found just return the original
// array
if (positions.length === 0) {
return bytes;
} // Create a new array to hold the NAL unit data
var newLength = bytes.length - positions.length;
var newData = new Uint8Array(newLength);
var sourceIndex = 0;
for (i = 0; i < newLength; sourceIndex++, i++) {
if (sourceIndex === positions[0]) {
// Skip this byte
sourceIndex++; // Remove this position index
positions.shift();
}
newData[i] = bytes[sourceIndex];
}
return newData;
};
var findNal = function findNal(bytes, dataType, types, nalLimit) {
if (nalLimit === void 0) {
nalLimit = Infinity;
}
bytes = byte_helpers_toUint8(bytes);
types = [].concat(types);
var i = 0;
var nalStart;
var nalsFound = 0; // keep searching until:
// we reach the end of bytes
// we reach the maximum number of nals they want to seach
// NOTE: that we disregard nalLimit when we have found the start
// of the nal we want so that we can find the end of the nal we want.
while (i < bytes.length && (nalsFound < nalLimit || nalStart)) {
var nalOffset = void 0;
if (byte_helpers_bytesMatch(bytes.subarray(i), NAL_TYPE_ONE)) {
nalOffset = 4;
} else if (byte_helpers_bytesMatch(bytes.subarray(i), NAL_TYPE_TWO)) {
nalOffset = 3;
} // we are unsynced,
// find the next nal unit
if (!nalOffset) {
i++;
continue;
}
nalsFound++;
if (nalStart) {
return discardEmulationPreventionBytes(bytes.subarray(nalStart, i));
}
var nalType = void 0;
if (dataType === 'h264') {
nalType = bytes[i + nalOffset] & 0x1f;
} else if (dataType === 'h265') {
nalType = bytes[i + nalOffset] >> 1 & 0x3f;
}
if (types.indexOf(nalType) !== -1) {
nalStart = i + nalOffset;
} // nal header is 1 length for h264, and 2 for h265
i += nalOffset + (dataType === 'h264' ? 1 : 2);
}
return bytes.subarray(0, 0);
};
var findH264Nal = function findH264Nal(bytes, type, nalLimit) {
return findNal(bytes, 'h264', type, nalLimit);
};
var findH265Nal = function findH265Nal(bytes, type, nalLimit) {
return findNal(bytes, 'h265', type, nalLimit);
};
;// ./node_modules/@videojs/vhs-utils/es/containers.js
var CONSTANTS = {
// "webm" string literal in hex
'webm': byte_helpers_toUint8([0x77, 0x65, 0x62, 0x6d]),
// "matroska" string literal in hex
'matroska': byte_helpers_toUint8([0x6d, 0x61, 0x74, 0x72, 0x6f, 0x73, 0x6b, 0x61]),
// "fLaC" string literal in hex
'flac': byte_helpers_toUint8([0x66, 0x4c, 0x61, 0x43]),
// "OggS" string literal in hex
'ogg': byte_helpers_toUint8([0x4f, 0x67, 0x67, 0x53]),
// ac-3 sync byte, also works for ec-3 as that is simply a codec
// of ac-3
'ac3': byte_helpers_toUint8([0x0b, 0x77]),
// "RIFF" string literal in hex used for wav and avi
'riff': byte_helpers_toUint8([0x52, 0x49, 0x46, 0x46]),
// "AVI" string literal in hex
'avi': byte_helpers_toUint8([0x41, 0x56, 0x49]),
// "WAVE" string literal in hex
'wav': byte_helpers_toUint8([0x57, 0x41, 0x56, 0x45]),
// "ftyp3g" string literal in hex
'3gp': byte_helpers_toUint8([0x66, 0x74, 0x79, 0x70, 0x33, 0x67]),
// "ftyp" string literal in hex
'mp4': byte_helpers_toUint8([0x66, 0x74, 0x79, 0x70]),
// "styp" string literal in hex
'fmp4': byte_helpers_toUint8([0x73, 0x74, 0x79, 0x70]),
// "ftypqt" string literal in hex
'mov': byte_helpers_toUint8([0x66, 0x74, 0x79, 0x70, 0x71, 0x74]),
// moov string literal in hex
'moov': byte_helpers_toUint8([0x6D, 0x6F, 0x6F, 0x76]),
// moof string literal in hex
'moof': byte_helpers_toUint8([0x6D, 0x6F, 0x6F, 0x66])
};
var _isLikely = {
aac: function aac(bytes) {
var offset = getId3Offset(bytes);
return byte_helpers_bytesMatch(bytes, [0xFF, 0x10], {
offset: offset,
mask: [0xFF, 0x16]
});
},
mp3: function mp3(bytes) {
var offset = getId3Offset(bytes);
return byte_helpers_bytesMatch(bytes, [0xFF, 0x02], {
offset: offset,
mask: [0xFF, 0x06]
});
},
webm: function webm(bytes) {
var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is webm
return byte_helpers_bytesMatch(docType, CONSTANTS.webm);
},
mkv: function mkv(bytes) {
var docType = findEbml(bytes, [EBML_TAGS.EBML, EBML_TAGS.DocType])[0]; // check if DocType EBML tag is matroska
return byte_helpers_bytesMatch(docType, CONSTANTS.matroska);
},
mp4: function mp4(bytes) {
// if this file is another base media file format, it is not mp4
if (_isLikely['3gp'](bytes) || _isLikely.mov(bytes)) {
return false;
} // if this file starts with a ftyp or styp box its mp4
if (byte_helpers_bytesMatch(bytes, CONSTANTS.mp4, {
offset: 4
}) || byte_helpers_bytesMatch(bytes, CONSTANTS.fmp4, {
offset: 4
})) {
return true;
} // if this file starts with a moof/moov box its mp4
if (byte_helpers_bytesMatch(bytes, CONSTANTS.moof, {
offset: 4
}) || byte_helpers_bytesMatch(bytes, CONSTANTS.moov, {
offset: 4
})) {
return true;
}
},
mov: function mov(bytes) {
return byte_helpers_bytesMatch(bytes, CONSTANTS.mov, {
offset: 4
});
},
'3gp': function gp(bytes) {
return byte_helpers_bytesMatch(bytes, CONSTANTS['3gp'], {
offset: 4
});
},
ac3: function ac3(bytes) {
var offset = getId3Offset(bytes);
return byte_helpers_bytesMatch(bytes, CONSTANTS.ac3, {
offset: offset
});
},
ts: function ts(bytes) {
if (bytes.length < 189 && bytes.length >= 1) {
return bytes[0] === 0x47;
}
var i = 0; // check the first 376 bytes for two matching sync bytes
while (i + 188 < bytes.length && i < 188) {
if (bytes[i] === 0x47 && bytes[i + 188] === 0x47) {
return true;
}
i += 1;
}
return false;
},
flac: function flac(bytes) {
var offset = getId3Offset(bytes);
return byte_helpers_bytesMatch(bytes, CONSTANTS.flac, {
offset: offset
});
},
ogg: function ogg(bytes) {
return byte_helpers_bytesMatch(bytes, CONSTANTS.ogg);
},
avi: function avi(bytes) {
return byte_helpers_bytesMatch(bytes, CONSTANTS.riff) && byte_helpers_bytesMatch(bytes, CONSTANTS.avi, {
offset: 8
});
},
wav: function wav(bytes) {
return byte_helpers_bytesMatch(bytes, CONSTANTS.riff) && byte_helpers_bytesMatch(bytes, CONSTANTS.wav, {
offset: 8
});
},
'h264': function h264(bytes) {
// find seq_parameter_set_rbsp
return findH264Nal(bytes, 7, 3).length;
},
'h265': function h265(bytes) {
// find video_parameter_set_rbsp or seq_parameter_set_rbsp
return findH265Nal(bytes, [32, 33], 3).length;
}
}; // get all the isLikely functions
// but make sure 'ts' is above h264 and h265
// but below everything else as it is the least specific
var isLikelyTypes = Object.keys(_isLikely) // remove ts, h264, h265
.filter(function (t) {
return t !== 'ts' && t !== 'h264' && t !== 'h265';
}) // add it back to the bottom
.concat(['ts', 'h264', 'h265']); // make sure we are dealing with uint8 data.
isLikelyTypes.forEach(function (type) {
var isLikelyFn = _isLikely[type];
_isLikely[type] = function (bytes) {
return isLikelyFn(byte_helpers_toUint8(bytes));
};
}); // export after wrapping
var isLikely = _isLikely; // A useful list of file signatures can be found here
// https://en.wikipedia.org/wiki/List_of_file_signatures
var detectContainerForBytes = function detectContainerForBytes(bytes) {
bytes = byte_helpers_toUint8(bytes);
for (var i = 0; i < isLikelyTypes.length; i++) {
var type = isLikelyTypes[i];
if (isLikely[type](bytes)) {
return type;
}
}
return '';
}; // fmp4 is not a container
var isLikelyFmp4MediaSegment = function isLikelyFmp4MediaSegment(bytes) {
return findBox(bytes, ['moof']).length > 0;
};
// EXTERNAL MODULE: ./node_modules/mux.js/lib/utils/clock.js
var clock = __webpack_require__(622);
;// ./node_modules/video.js/dist/video.es.js
/**
* @license
* Video.js 8.23.7
* Copyright 2010-present Video.js contributors
* Available under Apache License Version 2.0
*
*
* Includes vtt.js
* Available under Apache License Version 2.0
*
*/
var version$6 = "8.23.7";
/**
* An Object that contains lifecycle hooks as keys which point to an array
* of functions that are run when a lifecycle is triggered
*
* @private
*/
const hooks_ = {};
/**
* Get a list of hooks for a specific lifecycle
*
* @param {string} type
* the lifecycle to get hooks from
*
* @param {Function|Function[]} [fn]
* Optionally add a hook (or hooks) to the lifecycle that your are getting.
*
* @return {Array}
* an array of hooks, or an empty array if there are none.
*/
const hooks = function (type, fn) {
hooks_[type] = hooks_[type] || [];
if (fn) {
hooks_[type] = hooks_[type].concat(fn);
}
return hooks_[type];
};
/**
* Add a function hook to a specific videojs lifecycle.
*
* @param {string} type
* the lifecycle to hook the function to.
*
* @param {Function|Function[]}
* The function or array of functions to attach.
*/
const hook = function (type, fn) {
hooks(type, fn);
};
/**
* Remove a hook from a specific videojs lifecycle.
*
* @param {string} type
* the lifecycle that the function hooked to
*
* @param {Function} fn
* The hooked function to remove
*
* @return {boolean}
* The function that was removed or undef
*/
const removeHook = function (type, fn) {
const index = hooks(type).indexOf(fn);
if (index <= -1) {
return false;
}
hooks_[type] = hooks_[type].slice();
hooks_[type].splice(index, 1);
return true;
};
/**
* Add a function hook that will only run once to a specific videojs lifecycle.
*
* @param {string} type
* the lifecycle to hook the function to.
*
* @param {Function|Function[]}
* The function or array of functions to attach.
*/
const hookOnce = function (type, fn) {
hooks(type, [].concat(fn).map(original => {
const wrapper = (...args) => {
removeHook(type, wrapper);
return original(...args);
};
return wrapper;
}));
};
/**
* @file fullscreen-api.js
* @module fullscreen-api
*/
/**
* Store the browser-specific methods for the fullscreen API.
*
* @type {Object}
* @see [Specification]{@link https://fullscreen.spec.whatwg.org}
* @see [Map Approach From Screenfull.js]{@link https://github.com/sindresorhus/screenfull.js}
*/
const FullscreenApi = {
prefixed: true
};
// browser API methods
const apiMap = [['requestFullscreen', 'exitFullscreen', 'fullscreenElement', 'fullscreenEnabled', 'fullscreenchange', 'fullscreenerror', 'fullscreen'],
// WebKit
['webkitRequestFullscreen', 'webkitExitFullscreen', 'webkitFullscreenElement', 'webkitFullscreenEnabled', 'webkitfullscreenchange', 'webkitfullscreenerror', '-webkit-full-screen']];
const specApi = apiMap[0];
let browserApi;
// determine the supported set of functions
for (let i = 0; i < apiMap.length; i++) {
// check for exitFullscreen function
if (apiMap[i][1] in (document_default())) {
browserApi = apiMap[i];
break;
}
}
// map the browser API names to the spec API names
if (browserApi) {
for (let i = 0; i < browserApi.length; i++) {
FullscreenApi[specApi[i]] = browserApi[i];
}
FullscreenApi.prefixed = browserApi[0] !== specApi[0];
}
/**
* @file create-logger.js
* @module create-logger
*/
// This is the private tracking variable for the logging history.
let video_es_history = [];
/**
* Log messages to the console and history based on the type of message
*
* @private
* @param {string} name
* The name of the console method to use.
*
* @param {Object} log
* The arguments to be passed to the matching console method.
*
* @param {string} [styles]
* styles for name
*/
const LogByTypeFactory = (name, log, styles) => (type, level, args) => {
const lvl = log.levels[level];
const lvlRegExp = new RegExp(`^(${lvl})$`);
let resultName = name;
if (type !== 'log') {
// Add the type to the front of the message when it's not "log".
args.unshift(type.toUpperCase() + ':');
}
if (styles) {
resultName = `%c${name}`;
args.unshift(styles);
}
// Add console prefix after adding to history.
args.unshift(resultName + ':');
// Add a clone of the args at this point to history.
if (video_es_history) {
video_es_history.push([].concat(args));
// only store 1000 history entries
const splice = video_es_history.length - 1000;
video_es_history.splice(0, splice > 0 ? splice : 0);
}
// If there's no console then don't try to output messages, but they will
// still be stored in history.
if (!(window_default()).console) {
return;
}
// Was setting these once outside of this function, but containing them
// in the function makes it easier to test cases where console doesn't exist
// when the module is executed.
let fn = (window_default()).console[type];
if (!fn && type === 'debug') {
// Certain browsers don't have support for console.debug. For those, we
// should default to the closest comparable log.
fn = (window_default()).console.info || (window_default()).console.log;
}
// Bail out if there's no console or if this type is not allowed by the
// current logging level.
if (!fn || !lvl || !lvlRegExp.test(type)) {
return;
}
fn[Array.isArray(args) ? 'apply' : 'call']((window_default()).console, args);
};
function createLogger$1(name, delimiter = ':', styles = '') {
// This is the private tracking variable for logging level.
let level = 'info';
// the curried logByType bound to the specific log and history
let logByType;
/**
* Logs plain debug messages. Similar to `console.log`.
*
* Due to [limitations](https://github.com/jsdoc3/jsdoc/issues/955#issuecomment-313829149)
* of our JSDoc template, we cannot properly document this as both a function
* and a namespace, so its function signature is documented here.
*
* #### Arguments
* ##### *args
* *[]
*
* Any combination of values that could be passed to `console.log()`.
*
* #### Return Value
*
* `undefined`
*
* @namespace
* @param {...*} args
* One or more messages or objects that should be logged.
*/
function log(...args) {
logByType('log', level, args);
}
// This is the logByType helper that the logging methods below use
logByType = LogByTypeFactory(name, log, styles);
/**
* Create a new subLogger which chains the old name to the new name.
*
* For example, doing `mylogger = videojs.log.createLogger('player')` and then using that logger will log the following:
* ```js
* mylogger('foo');
* // > VIDEOJS: player: foo
* ```
*
* @param {string} subName
* The name to add call the new logger
* @param {string} [subDelimiter]
* Optional delimiter
* @param {string} [subStyles]
* Optional styles
* @return {Object}
*/
log.createLogger = (subName, subDelimiter, subStyles) => {
const resultDelimiter = subDelimiter !== undefined ? subDelimiter : delimiter;
const resultStyles = subStyles !== undefined ? subStyles : styles;
const resultName = `${name} ${resultDelimiter} ${subName}`;
return createLogger$1(resultName, resultDelimiter, resultStyles);
};
/**
* Create a new logger.
*
* @param {string} newName
* The name for the new logger
* @param {string} [newDelimiter]
* Optional delimiter
* @param {string} [newStyles]
* Optional styles
* @return {Object}
*/
log.createNewLogger = (newName, newDelimiter, newStyles) => {
return createLogger$1(newName, newDelimiter, newStyles);
};
/**
* Enumeration of available logging levels, where the keys are the level names
* and the values are `|`-separated strings containing logging methods allowed
* in that logging level. These strings are used to create a regular expression
* matching the function name being called.
*
* Levels provided by Video.js are:
*
* - `off`: Matches no calls. Any value that can be cast to `false` will have
* this effect. The most restrictive.
* - `all`: Matches only Video.js-provided functions (`debug`, `log`,
* `log.warn`, and `log.error`).
* - `debug`: Matches `log.debug`, `log`, `log.warn`, and `log.error` calls.
* - `info` (default): Matches `log`, `log.warn`, and `log.error` calls.
* - `warn`: Matches `log.warn` and `log.error` calls.
* - `error`: Matches only `log.error` calls.
*
* @type {Object}
*/
log.levels = {
all: 'debug|log|warn|error',
off: '',
debug: 'debug|log|warn|error',
info: 'log|warn|error',
warn: 'warn|error',
error: 'error',
DEFAULT: level
};
/**
* Get or set the current logging level.
*
* If a string matching a key from {@link module:log.levels} is provided, acts
* as a setter.
*
* @param {'all'|'debug'|'info'|'warn'|'error'|'off'} [lvl]
* Pass a valid level to set a new logging level.
*
* @return {string}
* The current logging level.
*/
log.level = lvl => {
if (typeof lvl === 'string') {
if (!log.levels.hasOwnProperty(lvl)) {
throw new Error(`"${lvl}" in not a valid log level`);
}
level = lvl;
}
return level;
};
/**
* Returns an array containing everything that has been logged to the history.
*
* This array is a shallow clone of the internal history record. However, its
* contents are _not_ cloned; so, mutating objects inside this array will
* mutate them in history.
*
* @return {Array}
*/
log.history = () => video_es_history ? [].concat(video_es_history) : [];
/**
* Allows you to filter the history by the given logger name
*
* @param {string} fname
* The name to filter by
*
* @return {Array}
* The filtered list to return
*/
log.history.filter = fname => {
return (video_es_history || []).filter(historyItem => {
// if the first item in each historyItem includes `fname`, then it's a match
return new RegExp(`.*${fname}.*`).test(historyItem[0]);
});
};
/**
* Clears the internal history tracking, but does not prevent further history
* tracking.
*/
log.history.clear = () => {
if (video_es_history) {
video_es_history.length = 0;
}
};
/**
* Disable history tracking if it is currently enabled.
*/
log.history.disable = () => {
if (video_es_history !== null) {
video_es_history.length = 0;
video_es_history = null;
}
};
/**
* Enable history tracking if it is currently disabled.
*/
log.history.enable = () => {
if (video_es_history === null) {
video_es_history = [];
}
};
/**
* Logs error messages. Similar to `console.error`.
*
* @param {...*} args
* One or more messages or objects that should be logged as an error
*/
log.error = (...args) => logByType('error', level, args);
/**
* Logs warning messages. Similar to `console.warn`.
*
* @param {...*} args
* One or more messages or objects that should be logged as a warning.
*/
log.warn = (...args) => logByType('warn', level, args);
/**
* Logs debug messages. Similar to `console.debug`, but may also act as a comparable
* log if `console.debug` is not available
*
* @param {...*} args
* One or more messages or objects that should be logged as debug.
*/
log.debug = (...args) => logByType('debug', level, args);
return log;
}
/**
* @file log.js
* @module log
*/
const log$1 = createLogger$1('VIDEOJS');
const createLogger = log$1.createLogger;
/**
* @file obj.js
* @module obj
*/
/**
* @callback obj:EachCallback
*
* @param {*} value
* The current key for the object that is being iterated over.
*
* @param {string} key
* The current key-value for object that is being iterated over
*/
/**
* @callback obj:ReduceCallback
*
* @param {*} accum
* The value that is accumulating over the reduce loop.
*
* @param {*} value
* The current key for the object that is being iterated over.
*
* @param {string} key
* The current key-value for object that is being iterated over
*
* @return {*}
* The new accumulated value.
*/
const video_es_toString = Object.prototype.toString;
/**
* Get the keys of an Object
*
* @param {Object}
* The Object to get the keys from
*
* @return {string[]}
* An array of the keys from the object. Returns an empty array if the
* object passed in was invalid or had no keys.
*
* @private
*/
const keys = function (object) {
return video_es_isObject(object) ? Object.keys(object) : [];
};
/**
* Array-like iteration for objects.
*
* @param {Object} object
* The object to iterate over
*
* @param {obj:EachCallback} fn
* The callback function which is called for each key in the object.
*/
function each(object, fn) {
keys(object).forEach(key => fn(object[key], key));
}
/**
* Array-like reduce for objects.
*
* @param {Object} object
* The Object that you want to reduce.
*
* @param {Function} fn
* A callback function which is called for each key in the object. It
* receives the accumulated value and the per-iteration value and key
* as arguments.
*
* @param {*} [initial = 0]
* Starting value
*
* @return {*}
* The final accumulated value.
*/
function reduce(object, fn, initial = 0) {
return keys(object).reduce((accum, key) => fn(accum, object[key], key), initial);
}
/**
* Returns whether a value is an object of any kind - including DOM nodes,
* arrays, regular expressions, etc. Not functions, though.
*
* This avoids the gotcha where using `typeof` on a `null` value
* results in `'object'`.
*
* @param {Object} value
* @return {boolean}
*/
function video_es_isObject(value) {
return !!value && typeof value === 'object';
}
/**
* Returns whether an object appears to be a "plain" object - that is, a
* direct instance of `Object`.
*
* @param {Object} value
* @return {boolean}
*/
function isPlain(value) {
return video_es_isObject(value) && video_es_toString.call(value) === '[object Object]' && value.constructor === Object;
}
/**
* Merge two objects recursively.
*
* Performs a deep merge like
* {@link https://lodash.com/docs/4.17.10#merge|lodash.merge}, but only merges
* plain objects (not arrays, elements, or anything else).
*
* Non-plain object values will be copied directly from the right-most
* argument.
*
* @param {Object[]} sources
* One or more objects to merge into a new object.
*
* @return {Object}
* A new object that is the merged result of all sources.
*/
function merge$1(...sources) {
const result = {};
sources.forEach(source => {
if (!source) {
return;
}
each(source, (value, key) => {
if (!isPlain(value)) {
result[key] = value;
return;
}
if (!isPlain(result[key])) {
result[key] = {};
}
result[key] = merge$1(result[key], value);
});
});
return result;
}
/**
* Returns an array of values for a given object
*
* @param {Object} source - target object
* @return {Array} - object values
*/
function video_es_values(source = {}) {
const result = [];
for (const key in source) {
if (source.hasOwnProperty(key)) {
const value = source[key];
result.push(value);
}
}
return result;
}
/**
* Object.defineProperty but "lazy", which means that the value is only set after
* it is retrieved the first time, rather than being set right away.
*
* @param {Object} obj the object to set the property on
* @param {string} key the key for the property to set
* @param {Function} getValue the function used to get the value when it is needed.
* @param {boolean} setter whether a setter should be allowed or not
*/
function defineLazyProperty(obj, key, getValue, setter = true) {
const set = value => Object.defineProperty(obj, key, {
value,
enumerable: true,
writable: true
});
const options = {
configurable: true,
enumerable: true,
get() {
const value = getValue();
set(value);
return value;
}
};
if (setter) {
options.set = set;
}
return Object.defineProperty(obj, key, options);
}
var Obj = /*#__PURE__*/Object.freeze({
__proto__: null,
each: each,
reduce: reduce,
isObject: video_es_isObject,
isPlain: isPlain,
merge: merge$1,
values: video_es_values,
defineLazyProperty: defineLazyProperty
});
/**
* @file browser.js
* @module browser
*/
/**
* Whether or not this device is an iPod.
*
* @static
* @type {Boolean}
*/
let IS_IPOD = false;
/**
* The detected iOS version - or `null`.
*
* @static
* @type {string|null}
*/
let IOS_VERSION = null;
/**
* Whether or not this is an Android device.
*
* @static
* @type {Boolean}
*/
let IS_ANDROID = false;
/**
* The detected Android version - or `null` if not Android or indeterminable.
*
* @static
* @type {number|string|null}
*/
let ANDROID_VERSION;
/**
* Whether or not this is Mozilla Firefox.
*
* @static
* @type {Boolean}
*/
let IS_FIREFOX = false;
/**
* Whether or not this is Microsoft Edge.
*
* @static
* @type {Boolean}
*/
let IS_EDGE = false;
/**
* Whether or not this is any Chromium Browser
*
* @static
* @type {Boolean}
*/
let IS_CHROMIUM = false;
/**
* Whether or not this is any Chromium browser that is not Edge.
*
* This will also be `true` for Chrome on iOS, which will have different support
* as it is actually Safari under the hood.
*
* Deprecated, as the behaviour to not match Edge was to prevent Legacy Edge's UA matching.
* IS_CHROMIUM should be used instead.
* "Chromium but not Edge" could be explicitly tested with IS_CHROMIUM && !IS_EDGE
*
* @static
* @deprecated
* @type {Boolean}
*/
let IS_CHROME = false;
/**
* The detected Chromium version - or `null`.
*
* @static
* @type {number|null}
*/
let CHROMIUM_VERSION = null;
/**
* The detected Google Chrome version - or `null`.
* This has always been the _Chromium_ version, i.e. would return on Chromium Edge.
* Deprecated, use CHROMIUM_VERSION instead.
*
* @static
* @deprecated
* @type {number|null}
*/
let CHROME_VERSION = null;
/**
* Whether or not this is a Chromecast receiver application.
*
* @static
* @type {Boolean}
*/
const IS_CHROMECAST_RECEIVER = Boolean((window_default()).cast && (window_default()).cast.framework && (window_default()).cast.framework.CastReceiverContext);
/**
* The detected Internet Explorer version - or `null`.
*
* @static
* @deprecated
* @type {number|null}
*/
let IE_VERSION = null;
/**
* Whether or not this is desktop Safari.
*
* @static
* @type {Boolean}
*/
let IS_SAFARI = false;
/**
* Whether or not this is a Windows machine.
*
* @static
* @type {Boolean}
*/
let IS_WINDOWS = false;
/**
* Whether or not this device is an iPad.
*
* @static
* @type {Boolean}
*/
let IS_IPAD = false;
/**
* Whether or not this device is an iPhone.
*
* @static
* @type {Boolean}
*/
// The Facebook app's UIWebView identifies as both an iPhone and iPad, so
// to identify iPhones, we need to exclude iPads.
// http://artsy.github.io/blog/2012/10/18/the-perils-of-ios-user-agent-sniffing/
let IS_IPHONE = false;
/**
* Whether or not this is a Tizen device.
*
* @static
* @type {Boolean}
*/
let IS_TIZEN = false;
/**
* Whether or not this is a WebOS device.
*
* @static
* @type {Boolean}
*/
let IS_WEBOS = false;
/**
* Whether or not this is a Smart TV (Tizen or WebOS) device.
*
* @static
* @type {Boolean}
*/
let IS_SMART_TV = false;
/**
* Whether or not this device is touch-enabled.
*
* @static
* @const
* @type {Boolean}
*/
const TOUCH_ENABLED = Boolean(isReal() && ("ontouchstart" in (window_default()) || (window_default()).navigator.maxTouchPoints || (window_default()).DocumentTouch && (window_default()).document instanceof (window_default()).DocumentTouch));
const UAD = (window_default()).navigator && (window_default()).navigator.userAgentData;
if (UAD && UAD.platform && UAD.brands) {
// If userAgentData is present, use it instead of userAgent to avoid warnings
// Currently only implemented on Chromium
// userAgentData does not expose Android version, so ANDROID_VERSION remains `null`
IS_ANDROID = UAD.platform === 'Android';
IS_EDGE = Boolean(UAD.brands.find(b => b.brand === 'Microsoft Edge'));
IS_CHROMIUM = Boolean(UAD.brands.find(b => b.brand === 'Chromium'));
IS_CHROME = !IS_EDGE && IS_CHROMIUM;
CHROMIUM_VERSION = CHROME_VERSION = (UAD.brands.find(b => b.brand === 'Chromium') || {}).version || null;
IS_WINDOWS = UAD.platform === 'Windows';
}
// If the browser is not Chromium, either userAgentData is not present which could be an old Chromium browser,
// or it's a browser that has added userAgentData since that we don't have tests for yet. In either case,
// the checks need to be made agiainst the regular userAgent string.
if (!IS_CHROMIUM) {
const USER_AGENT = (window_default()).navigator && (window_default()).navigator.userAgent || '';
IS_IPOD = /iPod/i.test(USER_AGENT);
IOS_VERSION = function () {
const match = USER_AGENT.match(/OS (\d+)_/i);
if (match && match[1]) {
return match[1];
}
return null;
}();
IS_ANDROID = /Android/i.test(USER_AGENT);
ANDROID_VERSION = function () {
// This matches Android Major.Minor.Patch versions
// ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned
const match = USER_AGENT.match(/Android (\d+)(?:\.(\d+))?(?:\.(\d+))*/i);
if (!match) {
return null;
}
const major = match[1] && parseFloat(match[1]);
const minor = match[2] && parseFloat(match[2]);
if (major && minor) {
return parseFloat(match[1] + '.' + match[2]);
} else if (major) {
return major;
}
return null;
}();
IS_FIREFOX = /Firefox/i.test(USER_AGENT);
IS_EDGE = /Edg/i.test(USER_AGENT);
IS_CHROMIUM = /Chrome/i.test(USER_AGENT) || /CriOS/i.test(USER_AGENT);
IS_CHROME = !IS_EDGE && IS_CHROMIUM;
CHROMIUM_VERSION = CHROME_VERSION = function () {
const match = USER_AGENT.match(/(Chrome|CriOS)\/(\d+)/);
if (match && match[2]) {
return parseFloat(match[2]);
}
return null;
}();
IE_VERSION = function () {
const result = /MSIE\s(\d+)\.\d/.exec(USER_AGENT);
let version = result && parseFloat(result[1]);
if (!version && /Trident\/7.0/i.test(USER_AGENT) && /rv:11.0/.test(USER_AGENT)) {
// IE 11 has a different user agent string than other IE versions
version = 11.0;
}
return version;
}();
IS_TIZEN = /Tizen/i.test(USER_AGENT);
IS_WEBOS = /Web0S/i.test(USER_AGENT);
IS_SMART_TV = IS_TIZEN || IS_WEBOS;
IS_SAFARI = /Safari/i.test(USER_AGENT) && !IS_CHROME && !IS_ANDROID && !IS_EDGE && !IS_SMART_TV;
IS_WINDOWS = /Windows/i.test(USER_AGENT);
IS_IPAD = /iPad/i.test(USER_AGENT) || IS_SAFARI && TOUCH_ENABLED && !/iPhone/i.test(USER_AGENT);
IS_IPHONE = /iPhone/i.test(USER_AGENT) && !IS_IPAD;
}
/**
* Whether or not this is an iOS device.
*
* @static
* @const
* @type {Boolean}
*/
const IS_IOS = IS_IPHONE || IS_IPAD || IS_IPOD;
/**
* Whether or not this is any flavor of Safari - including iOS.
*
* @static
* @const
* @type {Boolean}
*/
const IS_ANY_SAFARI = (IS_SAFARI || IS_IOS) && !IS_CHROME;
var video_es_browser = /*#__PURE__*/Object.freeze({
__proto__: null,
get IS_IPOD () { return IS_IPOD; },
get IOS_VERSION () { return IOS_VERSION; },
get IS_ANDROID () { return IS_ANDROID; },
get ANDROID_VERSION () { return ANDROID_VERSION; },
get IS_FIREFOX () { return IS_FIREFOX; },
get IS_EDGE () { return IS_EDGE; },
get IS_CHROMIUM () { return IS_CHROMIUM; },
get IS_CHROME () { return IS_CHROME; },
get CHROMIUM_VERSION () { return CHROMIUM_VERSION; },
get CHROME_VERSION () { return CHROME_VERSION; },
IS_CHROMECAST_RECEIVER: IS_CHROMECAST_RECEIVER,
get IE_VERSION () { return IE_VERSION; },
get IS_SAFARI () { return IS_SAFARI; },
get IS_WINDOWS () { return IS_WINDOWS; },
get IS_IPAD () { return IS_IPAD; },
get IS_IPHONE () { return IS_IPHONE; },
get IS_TIZEN () { return IS_TIZEN; },
get IS_WEBOS () { return IS_WEBOS; },
get IS_SMART_TV () { return IS_SMART_TV; },
TOUCH_ENABLED: TOUCH_ENABLED,
IS_IOS: IS_IOS,
IS_ANY_SAFARI: IS_ANY_SAFARI
});
/**
* @file dom.js
* @module dom
*/
/**
* Detect if a value is a string with any non-whitespace characters.
*
* @private
* @param {string} str
* The string to check
*
* @return {boolean}
* Will be `true` if the string is non-blank, `false` otherwise.
*
*/
function isNonBlankString(str) {
// we use str.trim as it will trim any whitespace characters
// from the front or back of non-whitespace characters. aka
// Any string that contains non-whitespace characters will
// still contain them after `trim` but whitespace only strings
// will have a length of 0, failing this check.
return typeof str === 'string' && Boolean(str.trim());
}
/**
* Throws an error if the passed string has whitespace. This is used by
* class methods to be relatively consistent with the classList API.
*
* @private
* @param {string} str
* The string to check for whitespace.
*
* @throws {Error}
* Throws an error if there is whitespace in the string.
*/
function throwIfWhitespace(str) {
// str.indexOf instead of regex because str.indexOf is faster performance wise.
if (str.indexOf(' ') >= 0) {
throw new Error('class has illegal whitespace characters');
}
}
/**
* Whether the current DOM interface appears to be real (i.e. not simulated).
*
* @return {boolean}
* Will be `true` if the DOM appears to be real, `false` otherwise.
*/
function isReal() {
// Both document and window will never be undefined thanks to `global`.
return (document_default()) === (window_default()).document;
}
/**
* Determines, via duck typing, whether or not a value is a DOM element.
*
* @param {*} value
* The value to check.
*
* @return {boolean}
* Will be `true` if the value is a DOM element, `false` otherwise.
*/
function isEl(value) {
return video_es_isObject(value) && value.nodeType === 1;
}
/**
* Determines if the current DOM is embedded in an iframe.
*
* @return {boolean}
* Will be `true` if the DOM is embedded in an iframe, `false`
* otherwise.
*/
function isInFrame() {
// We need a try/catch here because Safari will throw errors when attempting
// to get either `parent` or `self`
try {
return (window_default()).parent !== (window_default()).self;
} catch (x) {
return true;
}
}
/**
* Creates functions to query the DOM using a given method.
*
* @private
* @param {string} method
* The method to create the query with.
*
* @return {Function}
* The query method
*/
function createQuerier(method) {
return function (selector, context) {
if (!isNonBlankString(selector)) {
return (document_default())[method](null);
}
if (isNonBlankString(context)) {
context = document_default().querySelector(context);
}
const ctx = isEl(context) ? context : (document_default());
return ctx[method] && ctx[method](selector);
};
}
/**
* Creates an element and applies properties, attributes, and inserts content.
*
* @param {string} [tagName='div']
* Name of tag to be created.
*
* @param {Object} [properties={}]
* Element properties to be applied.
*
* @param {Object} [attributes={}]
* Element attributes to be applied.
*
* @param {ContentDescriptor} [content]
* A content descriptor object.
*
* @return {Element}
* The element that was created.
*/
function createEl(tagName = 'div', properties = {}, attributes = {}, content) {
const el = document_default().createElement(tagName);
Object.getOwnPropertyNames(properties).forEach(function (propName) {
const val = properties[propName];
// Handle textContent since it's not supported everywhere and we have a
// method for it.
if (propName === 'textContent') {
textContent(el, val);
} else if (el[propName] !== val || propName === 'tabIndex') {
el[propName] = val;
}
});
Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
el.setAttribute(attrName, attributes[attrName]);
});
if (content) {
appendContent(el, content);
}
return el;
}
/**
* Injects text into an element, replacing any existing contents entirely.
*
* @param {HTMLElement} el
* The element to add text content into
*
* @param {string} text
* The text content to add.
*
* @return {Element}
* The element with added text content.
*/
function textContent(el, text) {
if (typeof el.textContent === 'undefined') {
el.innerText = text;
} else {
el.textContent = text;
}
return el;
}
/**
* Insert an element as the first child node of another
*
* @param {Element} child
* Element to insert
*
* @param {Element} parent
* Element to insert child into
*/
function prependTo(child, parent) {
if (parent.firstChild) {
parent.insertBefore(child, parent.firstChild);
} else {
parent.appendChild(child);
}
}
/**
* Check if an element has a class name.
*
* @param {Element} element
* Element to check
*
* @param {string} classToCheck
* Class name to check for
*
* @return {boolean}
* Will be `true` if the element has a class, `false` otherwise.
*
* @throws {Error}
* Throws an error if `classToCheck` has white space.
*/
function hasClass(element, classToCheck) {
throwIfWhitespace(classToCheck);
return element.classList.contains(classToCheck);
}
/**
* Add a class name to an element.
*
* @param {Element} element
* Element to add class name to.
*
* @param {...string} classesToAdd
* One or more class name to add.
*
* @return {Element}
* The DOM element with the added class name.
*/
function addClass(element, ...classesToAdd) {
element.classList.add(...classesToAdd.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
return element;
}
/**
* Remove a class name from an element.
*
* @param {Element} element
* Element to remove a class name from.
*
* @param {...string} classesToRemove
* One or more class name to remove.
*
* @return {Element}
* The DOM element with class name removed.
*/
function removeClass(element, ...classesToRemove) {
// Protect in case the player gets disposed
if (!element) {
log$1.warn("removeClass was called with an element that doesn't exist");
return null;
}
element.classList.remove(...classesToRemove.reduce((prev, current) => prev.concat(current.split(/\s+/)), []));
return element;
}
/**
* The callback definition for toggleClass.
*
* @callback PredicateCallback
* @param {Element} element
* The DOM element of the Component.
*
* @param {string} classToToggle
* The `className` that wants to be toggled
*
* @return {boolean|undefined}
* If `true` is returned, the `classToToggle` will be added to the
* `element`, but not removed. If `false`, the `classToToggle` will be removed from
* the `element`, but not added. If `undefined`, the callback will be ignored.
*
*/
/**
* Adds or removes a class name to/from an element depending on an optional
* condition or the presence/absence of the class name.
*
* @param {Element} element
* The element to toggle a class name on.
*
* @param {string} classToToggle
* The class that should be toggled.
*
* @param {boolean|PredicateCallback} [predicate]
* See the return value for {@link module:dom~PredicateCallback}
*
* @return {Element}
* The element with a class that has been toggled.
*/
function toggleClass(element, classToToggle, predicate) {
if (typeof predicate === 'function') {
predicate = predicate(element, classToToggle);
}
if (typeof predicate !== 'boolean') {
predicate = undefined;
}
classToToggle.split(/\s+/).forEach(className => element.classList.toggle(className, predicate));
return element;
}
/**
* Apply attributes to an HTML element.
*
* @param {Element} el
* Element to add attributes to.
*
* @param {Object} [attributes]
* Attributes to be applied.
*/
function setAttributes(el, attributes) {
Object.getOwnPropertyNames(attributes).forEach(function (attrName) {
const attrValue = attributes[attrName];
if (attrValue === null || typeof attrValue === 'undefined' || attrValue === false) {
el.removeAttribute(attrName);
} else {
el.setAttribute(attrName, attrValue === true ? '' : attrValue);
}
});
}
/**
* Get an element's attribute values, as defined on the HTML tag.
*
* Attributes are not the same as properties. They're defined on the tag
* or with setAttribute.
*
* @param {Element} tag
* Element from which to get tag attributes.
*
* @return {Object}
* All attributes of the element. Boolean attributes will be `true` or
* `false`, others will be strings.
*/
function getAttributes(tag) {
const obj = {};
// known boolean attributes
// we can check for matching boolean properties, but not all browsers
// and not all tags know about these attributes, so, we still want to check them manually
const knownBooleans = ['autoplay', 'controls', 'playsinline', 'loop', 'muted', 'default', 'defaultMuted'];
if (tag && tag.attributes && tag.attributes.length > 0) {
const attrs = tag.attributes;
for (let i = attrs.length - 1; i >= 0; i--) {
const attrName = attrs[i].name;
/** @type {boolean|string} */
let attrVal = attrs[i].value;
// check for known booleans
// the matching element property will return a value for typeof
if (knownBooleans.includes(attrName)) {
// the value of an included boolean attribute is typically an empty
// string ('') which would equal false if we just check for a false value.
// we also don't want support bad code like autoplay='false'
attrVal = attrVal !== null ? true : false;
}
obj[attrName] = attrVal;
}
}
return obj;
}
/**
* Get the value of an element's attribute.
*
* @param {Element} el
* A DOM element.
*
* @param {string} attribute
* Attribute to get the value of.
*
* @return {string}
* The value of the attribute.
*/
function getAttribute(el, attribute) {
return el.getAttribute(attribute);
}
/**
* Set the value of an element's attribute.
*
* @param {Element} el
* A DOM element.
*
* @param {string} attribute
* Attribute to set.
*
* @param {string} value
* Value to set the attribute to.
*/
function setAttribute(el, attribute, value) {
el.setAttribute(attribute, value);
}
/**
* Remove an element's attribute.
*
* @param {Element} el
* A DOM element.
*
* @param {string} attribute
* Attribute to remove.
*/
function removeAttribute(el, attribute) {
el.removeAttribute(attribute);
}
/**
* Attempt to block the ability to select text.
*/
function blockTextSelection() {
document_default().body.focus();
(document_default()).onselectstart = function () {
return false;
};
}
/**
* Turn off text selection blocking.
*/
function unblockTextSelection() {
(document_default()).onselectstart = function () {
return true;
};
}
/**
* Identical to the native `getBoundingClientRect` function, but ensures that
* the method is supported at all (it is in all browsers we claim to support)
* and that the element is in the DOM before continuing.
*
* This wrapper function also shims properties which are not provided by some
* older browsers (namely, IE8).
*
* Additionally, some browsers do not support adding properties to a
* `ClientRect`/`DOMRect` object; so, we shallow-copy it with the standard
* properties (except `x` and `y` which are not widely supported). This helps
* avoid implementations where keys are non-enumerable.
*
* @param {Element} el
* Element whose `ClientRect` we want to calculate.
*
* @return {Object|undefined}
* Always returns a plain object - or `undefined` if it cannot.
*/
function getBoundingClientRect(el) {
if (el && el.getBoundingClientRect && el.parentNode) {
const rect = el.getBoundingClientRect();
const result = {};
['bottom', 'height', 'left', 'right', 'top', 'width'].forEach(k => {
if (rect[k] !== undefined) {
result[k] = rect[k];
}
});
if (!result.height) {
result.height = parseFloat(computedStyle(el, 'height'));
}
if (!result.width) {
result.width = parseFloat(computedStyle(el, 'width'));
}
return result;
}
}
/**
* Represents the position of a DOM element on the page.
*
* @typedef {Object} module:dom~Position
*
* @property {number} left
* Pixels to the left.
*
* @property {number} top
* Pixels from the top.
*/
/**
* Get the position of an element in the DOM.
*
* Uses `getBoundingClientRect` technique from John Resig.
*
* @see http://ejohn.org/blog/getboundingclientrect-is-awesome/
*
* @param {Element} el
* Element from which to get offset.
*
* @return {module:dom~Position}
* The position of the element that was passed in.
*/
function findPosition(el) {
if (!el || el && !el.offsetParent) {
return {
left: 0,
top: 0,
width: 0,
height: 0
};
}
const width = el.offsetWidth;
const height = el.offsetHeight;
let left = 0;
let top = 0;
while (el.offsetParent && el !== (document_default())[FullscreenApi.fullscreenElement]) {
left += el.offsetLeft;
top += el.offsetTop;
el = el.offsetParent;
}
return {
left,
top,
width,
height
};
}
/**
* Represents x and y coordinates for a DOM element or mouse pointer.
*
* @typedef {Object} module:dom~Coordinates
*
* @property {number} x
* x coordinate in pixels
*
* @property {number} y
* y coordinate in pixels
*/
/**
* Get the pointer position within an element.
*
* The base on the coordinates are the bottom left of the element.
*
* @param {Element} el
* Element on which to get the pointer position on.
*
* @param {Event} event
* Event object.
*
* @return {module:dom~Coordinates}
* A coordinates object corresponding to the mouse position.
*
*/
function getPointerPosition(el, event) {
const translated = {
x: 0,
y: 0
};
if (IS_IOS) {
let item = el;
while (item && item.nodeName.toLowerCase() !== 'html') {
const transform = computedStyle(item, 'transform');
if (/^matrix/.test(transform)) {
const values = transform.slice(7, -1).split(/,\s/).map(Number);
translated.x += values[4];
translated.y += values[5];
} else if (/^matrix3d/.test(transform)) {
const values = transform.slice(9, -1).split(/,\s/).map(Number);
translated.x += values[12];
translated.y += values[13];
}
if (item.assignedSlot && item.assignedSlot.parentElement && (window_default()).WebKitCSSMatrix) {
const transformValue = window_default().getComputedStyle(item.assignedSlot.parentElement).transform;
const matrix = new (window_default()).WebKitCSSMatrix(transformValue);
translated.x += matrix.m41;
translated.y += matrix.m42;
}
item = item.parentNode || item.host;
}
}
const position = {};
const boxTarget = findPosition(event.target);
const box = findPosition(el);
const boxW = box.width;
const boxH = box.height;
let offsetY = event.offsetY - (box.top - boxTarget.top);
let offsetX = event.offsetX - (box.left - boxTarget.left);
if (event.changedTouches) {
offsetX = event.changedTouches[0].pageX - box.left;
offsetY = event.changedTouches[0].pageY + box.top;
if (IS_IOS) {
offsetX -= translated.x;
offsetY -= translated.y;
}
}
position.y = 1 - Math.max(0, Math.min(1, offsetY / boxH));
position.x = Math.max(0, Math.min(1, offsetX / boxW));
return position;
}
/**
* Determines, via duck typing, whether or not a value is a text node.
*
* @param {*} value
* Check if this value is a text node.
*
* @return {boolean}
* Will be `true` if the value is a text node, `false` otherwise.
*/
function isTextNode(value) {
return video_es_isObject(value) && value.nodeType === 3;
}
/**
* Empties the contents of an element.
*
* @param {Element} el
* The element to empty children from
*
* @return {Element}
* The element with no children
*/
function emptyEl(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
return el;
}
/**
* This is a mixed value that describes content to be injected into the DOM
* via some method. It can be of the following types:
*
* Type | Description
* -----------|-------------
* `string` | The value will be normalized into a text node.
* `Element` | The value will be accepted as-is.
* `Text` | A TextNode. The value will be accepted as-is.
* `Array` | A one-dimensional array of strings, elements, text nodes, or functions. These functions should return a string, element, or text node (any other return value, like an array, will be ignored).
* `Function` | A function, which is expected to return a string, element, text node, or array - any of the other possible values described above. This means that a content descriptor could be a function that returns an array of functions, but those second-level functions must return strings, elements, or text nodes.
*
* @typedef {string|Element|Text|Array|Function} ContentDescriptor
*/
/**
* Normalizes content for eventual insertion into the DOM.
*
* This allows a wide range of content definition methods, but helps protect
* from falling into the trap of simply writing to `innerHTML`, which could
* be an XSS concern.
*
* The content for an element can be passed in multiple types and
* combinations, whose behavior is as follows:
*
* @param {ContentDescriptor} content
* A content descriptor value.
*
* @return {Array}
* All of the content that was passed in, normalized to an array of
* elements or text nodes.
*/
function normalizeContent(content) {
// First, invoke content if it is a function. If it produces an array,
// that needs to happen before normalization.
if (typeof content === 'function') {
content = content();
}
// Next up, normalize to an array, so one or many items can be normalized,
// filtered, and returned.
return (Array.isArray(content) ? content : [content]).map(value => {
// First, invoke value if it is a function to produce a new value,
// which will be subsequently normalized to a Node of some kind.
if (typeof value === 'function') {
value = value();
}
if (isEl(value) || isTextNode(value)) {
return value;
}
if (typeof value === 'string' && /\S/.test(value)) {
return document_default().createTextNode(value);
}
}).filter(value => value);
}
/**
* Normalizes and appends content to an element.
*
* @param {Element} el
* Element to append normalized content to.
*
* @param {ContentDescriptor} content
* A content descriptor value.
*
* @return {Element}
* The element with appended normalized content.
*/
function appendContent(el, content) {
normalizeContent(content).forEach(node => el.appendChild(node));
return el;
}
/**
* Normalizes and inserts content into an element; this is identical to
* `appendContent()`, except it empties the element first.
*
* @param {Element} el
* Element to insert normalized content into.
*
* @param {ContentDescriptor} content
* A content descriptor value.
*
* @return {Element}
* The element with inserted normalized content.
*/
function insertContent(el, content) {
return appendContent(emptyEl(el), content);
}
/**
* Check if an event was a single left click.
*
* @param {MouseEvent} event
* Event object.
*
* @return {boolean}
* Will be `true` if a single left click, `false` otherwise.
*/
function isSingleLeftClick(event) {
// Note: if you create something draggable, be sure to
// call it on both `mousedown` and `mousemove` event,
// otherwise `mousedown` should be enough for a button
if (event.button === undefined && event.buttons === undefined) {
// Why do we need `buttons` ?
// Because, middle mouse sometimes have this:
// e.button === 0 and e.buttons === 4
// Furthermore, we want to prevent combination click, something like
// HOLD middlemouse then left click, that would be
// e.button === 0, e.buttons === 5
// just `button` is not gonna work
// Alright, then what this block does ?
// this is for chrome `simulate mobile devices`
// I want to support this as well
return true;
}
if (event.button === 0 && event.buttons === undefined) {
// Touch screen, sometimes on some specific device, `buttons`
// doesn't have anything (safari on ios, blackberry...)
return true;
}
// `mouseup` event on a single left click has
// `button` and `buttons` equal to 0
if (event.type === 'mouseup' && event.button === 0 && event.buttons === 0) {
return true;
}
// MacOS Sonoma trackpad when "tap to click enabled"
if (event.type === 'mousedown' && event.button === 0 && event.buttons === 0) {
return true;
}
if (event.button !== 0 || event.buttons !== 1) {
// This is the reason we have those if else block above
// if any special case we can catch and let it slide
// we do it above, when get to here, this definitely
// is-not-left-click
return false;
}
return true;
}
/**
* Finds a single DOM element matching `selector` within the optional
* `context` of another DOM element (defaulting to `document`).
*
* @param {string} selector
* A valid CSS selector, which will be passed to `querySelector`.
*
* @param {Element|String} [context=document]
* A DOM element within which to query. Can also be a selector
* string in which case the first matching element will be used
* as context. If missing (or no element matches selector), falls
* back to `document`.
*
* @return {Element|null}
* The element that was found or null.
*/
const $ = createQuerier('querySelector');
/**
* Finds a all DOM elements matching `selector` within the optional
* `context` of another DOM element (defaulting to `document`).
*
* @param {string} selector
* A valid CSS selector, which will be passed to `querySelectorAll`.
*
* @param {Element|String} [context=document]
* A DOM element within which to query. Can also be a selector
* string in which case the first matching element will be used
* as context. If missing (or no element matches selector), falls
* back to `document`.
*
* @return {NodeList}
* A element list of elements that were found. Will be empty if none
* were found.
*
*/
const $$ = createQuerier('querySelectorAll');
/**
* A safe getComputedStyle.
*
* This is needed because in Firefox, if the player is loaded in an iframe with
* `display:none`, then `getComputedStyle` returns `null`, so, we do a
* null-check to make sure that the player doesn't break in these cases.
*
* @param {Element} el
* The element you want the computed style of
*
* @param {string} prop
* The property name you want
*
* @see https://bugzilla.mozilla.org/show_bug.cgi?id=548397
*/
function computedStyle(el, prop) {
if (!el || !prop) {
return '';
}
if (typeof (window_default()).getComputedStyle === 'function') {
let computedStyleValue;
try {
computedStyleValue = window_default().getComputedStyle(el);
} catch (e) {
return '';
}
return computedStyleValue ? computedStyleValue.getPropertyValue(prop) || computedStyleValue[prop] : '';
}
return '';
}
/**
* Copy document style sheets to another window.
*
* @param {Window} win
* The window element you want to copy the document style sheets to.
*
*/
function copyStyleSheetsToWindow(win) {
[...(document_default()).styleSheets].forEach(styleSheet => {
try {
const cssRules = [...styleSheet.cssRules].map(rule => rule.cssText).join('');
const style = document_default().createElement('style');
style.textContent = cssRules;
win.document.head.appendChild(style);
} catch (e) {
const link = document_default().createElement('link');
link.rel = 'stylesheet';
link.type = styleSheet.type;
// For older Safari this has to be the string; on other browsers setting the MediaList works
link.media = styleSheet.media.mediaText;
link.href = styleSheet.href;
win.document.head.appendChild(link);
}
});
}
var Dom = /*#__PURE__*/Object.freeze({
__proto__: null,
isReal: isReal,
isEl: isEl,
isInFrame: isInFrame,
createEl: createEl,
textContent: textContent,
prependTo: prependTo,
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
setAttributes: setAttributes,
getAttributes: getAttributes,
getAttribute: getAttribute,
setAttribute: setAttribute,
removeAttribute: removeAttribute,
blockTextSelection: blockTextSelection,
unblockTextSelection: unblockTextSelection,
getBoundingClientRect: getBoundingClientRect,
findPosition: findPosition,
getPointerPosition: getPointerPosition,
isTextNode: isTextNode,
emptyEl: emptyEl,
normalizeContent: normalizeContent,
appendContent: appendContent,
insertContent: insertContent,
isSingleLeftClick: isSingleLeftClick,
$: $,
$$: $$,
computedStyle: computedStyle,
copyStyleSheetsToWindow: copyStyleSheetsToWindow
});
/**
* @file setup.js - Functions for setting up a player without
* user interaction based on the data-setup `attribute` of the video tag.
*
* @module setup
*/
let _windowLoaded = false;
let videojs$1;
/**
* Set up any tags that have a data-setup `attribute` when the player is started.
*/
const autoSetup = function () {
if (videojs$1.options.autoSetup === false) {
return;
}
const vids = Array.prototype.slice.call(document_default().getElementsByTagName('video'));
const audios = Array.prototype.slice.call(document_default().getElementsByTagName('audio'));
const divs = Array.prototype.slice.call(document_default().getElementsByTagName('video-js'));
const mediaEls = vids.concat(audios, divs);
// Check if any media elements exist
if (mediaEls && mediaEls.length > 0) {
for (let i = 0, e = mediaEls.length; i < e; i++) {
const mediaEl = mediaEls[i];
// Check if element exists, has getAttribute func.
if (mediaEl && mediaEl.getAttribute) {
// Make sure this player hasn't already been set up.
if (mediaEl.player === undefined) {
const options = mediaEl.getAttribute('data-setup');
// Check if data-setup attr exists.
// We only auto-setup if they've added the data-setup attr.
if (options !== null) {
// Create new video.js instance.
videojs$1(mediaEl);
}
}
// If getAttribute isn't defined, we need to wait for the DOM.
} else {
autoSetupTimeout(1);
break;
}
}
// No videos were found, so keep looping unless page is finished loading.
} else if (!_windowLoaded) {
autoSetupTimeout(1);
}
};
/**
* Wait until the page is loaded before running autoSetup. This will be called in
* autoSetup if `hasLoaded` returns false.
*
* @param {number} wait
* How long to wait in ms
*
* @param {module:videojs} [vjs]
* The videojs library function
*/
function autoSetupTimeout(wait, vjs) {
// Protect against breakage in non-browser environments
if (!isReal()) {
return;
}
if (vjs) {
videojs$1 = vjs;
}
window_default().setTimeout(autoSetup, wait);
}
/**
* Used to set the internal tracking of window loaded state to true.
*
* @private
*/
function setWindowLoaded() {
_windowLoaded = true;
window_default().removeEventListener('load', setWindowLoaded);
}
if (isReal()) {
if ((document_default()).readyState === 'complete') {
setWindowLoaded();
} else {
/**
* Listen for the load event on window, and set _windowLoaded to true.
*
* We use a standard event listener here to avoid incrementing the GUID
* before any players are created.
*
* @listens load
*/
window_default().addEventListener('load', setWindowLoaded);
}
}
/**
* @file stylesheet.js
* @module stylesheet
*/
/**
* Create a DOM style element given a className for it.
*
* @param {string} className
* The className to add to the created style element.
*
* @return {Element}
* The element that was created.
*/
const createStyleElement = function (className) {
const style = document_default().createElement('style');
style.className = className;
return style;
};
/**
* Add text to a DOM element.
*
* @param {Element} el
* The Element to add text content to.
*
* @param {string} content
* The text to add to the element.
*/
const setTextContent = function (el, content) {
if (el.styleSheet) {
el.styleSheet.cssText = content;
} else {
el.textContent = content;
}
};
/**
* @file dom-data.js
* @module dom-data
*/
/**
* Element Data Store.
*
* Allows for binding data to an element without putting it directly on the
* element. Ex. Event listeners are stored here.
* (also from jsninja.com, slightly modified and updated for closure compiler)
*
* @type {Object}
* @private
*/
var DomData = new WeakMap();
/**
* @file guid.js
* @module guid
*/
// Default value for GUIDs. This allows us to reset the GUID counter in tests.
//
// The initial GUID is 3 because some users have come to rely on the first
// default player ID ending up as `vjs_video_3`.
//
// See: https://github.com/videojs/video.js/pull/6216
const _initialGuid = 3;
/**
* Unique ID for an element or function
*
* @type {Number}
*/
let _guid = _initialGuid;
/**
* Get a unique auto-incrementing ID by number that has not been returned before.
*
* @return {number}
* A new unique ID.
*/
function newGUID() {
return _guid++;
}
/**
* @file events.js. An Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)
* (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)
* This should work very similarly to jQuery's events, however it's based off the book version which isn't as
* robust as jquery's, so there's probably some differences.
*
* @file events.js
* @module events
*/
/**
* Clean up the listener cache and dispatchers
*
* @param {Element|Object} elem
* Element to clean up
*
* @param {string} type
* Type of event to clean up
*/
function _cleanUpEvents(elem, type) {
if (!DomData.has(elem)) {
return;
}
const data = DomData.get(elem);
// Remove the events of a particular type if there are none left
if (data.handlers[type].length === 0) {
delete data.handlers[type];
// data.handlers[type] = null;
// Setting to null was causing an error with data.handlers
// Remove the meta-handler from the element
if (elem.removeEventListener) {
elem.removeEventListener(type, data.dispatcher, false);
} else if (elem.detachEvent) {
elem.detachEvent('on' + type, data.dispatcher);
}
}
// Remove the events object if there are no types left
if (Object.getOwnPropertyNames(data.handlers).length <= 0) {
delete data.handlers;
delete data.dispatcher;
delete data.disabled;
}
// Finally remove the element data if there is no data left
if (Object.getOwnPropertyNames(data).length === 0) {
DomData.delete(elem);
}
}
/**
* Loops through an array of event types and calls the requested method for each type.
*
* @param {Function} fn
* The event method we want to use.
*
* @param {Element|Object} elem
* Element or object to bind listeners to
*
* @param {string[]} types
* Type of event to bind to.
*
* @param {Function} callback
* Event listener.
*/
function _handleMultipleEvents(fn, elem, types, callback) {
types.forEach(function (type) {
// Call the event method for each one of the types
fn(elem, type, callback);
});
}
/**
* Fix a native event to have standard property values
*
* @param {Object} event
* Event object to fix.
*
* @return {Object}
* Fixed event object.
*/
function fixEvent(event) {
if (event.fixed_) {
return event;
}
function returnTrue() {
return true;
}
function returnFalse() {
return false;
}
// Test if fixing up is needed
// Used to check if !event.stopPropagation instead of isPropagationStopped
// But native events return true for stopPropagation, but don't have
// other expected methods like isPropagationStopped. Seems to be a problem
// with the Javascript Ninja code. So we're just overriding all events now.
if (!event || !event.isPropagationStopped || !event.isImmediatePropagationStopped) {
const old = event || (window_default()).event;
event = {};
// Clone the old object so that we can modify the values event = {};
// IE8 Doesn't like when you mess with native event properties
// Firefox returns false for event.hasOwnProperty('type') and other props
// which makes copying more difficult.
// TODO: Probably best to create an allowlist of event props
const deprecatedProps = ['layerX', 'layerY', 'keyLocation', 'path', 'webkitMovementX', 'webkitMovementY', 'mozPressure', 'mozInputSource'];
for (const key in old) {
// Safari 6.0.3 warns you if you try to copy deprecated layerX/Y
// Chrome warns you if you try to copy deprecated keyboardEvent.keyLocation
// and webkitMovementX/Y
// Lighthouse complains if Event.path is copied
if (!deprecatedProps.includes(key)) {
// Chrome 32+ warns if you try to copy deprecated returnValue, but
// we still want to if preventDefault isn't supported (IE8).
if (!(key === 'returnValue' && old.preventDefault)) {
event[key] = old[key];
}
}
}
// The event occurred on this element
if (!event.target) {
event.target = event.srcElement || (document_default());
}
// Handle which other element the event is related to
if (!event.relatedTarget) {
event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement;
}
// Stop the default browser action
event.preventDefault = function () {
if (old.preventDefault) {
old.preventDefault();
}
event.returnValue = false;
old.returnValue = false;
event.defaultPrevented = true;
};
event.defaultPrevented = false;
// Stop the event from bubbling
event.stopPropagation = function () {
if (old.stopPropagation) {
old.stopPropagation();
}
event.cancelBubble = true;
old.cancelBubble = true;
event.isPropagationStopped = returnTrue;
};
event.isPropagationStopped = returnFalse;
// Stop the event from bubbling and executing other handlers
event.stopImmediatePropagation = function () {
if (old.stopImmediatePropagation) {
old.stopImmediatePropagation();
}
event.isImmediatePropagationStopped = returnTrue;
event.stopPropagation();
};
event.isImmediatePropagationStopped = returnFalse;
// Handle mouse position
if (event.clientX !== null && event.clientX !== undefined) {
const doc = (document_default()).documentElement;
const body = (document_default()).body;
event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
}
// Handle key presses
event.which = event.charCode || event.keyCode;
// Fix button for mouse clicks:
// 0 == left; 1 == middle; 2 == right
if (event.button !== null && event.button !== undefined) {
// The following is disabled because it does not pass videojs-standard
// and... yikes.
/* eslint-disable */
event.button = event.button & 1 ? 0 : event.button & 4 ? 1 : event.button & 2 ? 2 : 0;
/* eslint-enable */
}
}
event.fixed_ = true;
// Returns fixed-up instance
return event;
}
/**
* Whether passive event listeners are supported
*/
let _supportsPassive;
const supportsPassive = function () {
if (typeof _supportsPassive !== 'boolean') {
_supportsPassive = false;
try {
const opts = Object.defineProperty({}, 'passive', {
get() {
_supportsPassive = true;
}
});
window_default().addEventListener('test', null, opts);
window_default().removeEventListener('test', null, opts);
} catch (e) {
// disregard
}
}
return _supportsPassive;
};
/**
* Touch events Chrome expects to be passive
*/
const passiveEvents = ['touchstart', 'touchmove'];
/**
* Add an event listener to element
* It stores the handler function in a separate cache object
* and adds a generic handler to the element's event,
* along with a unique id (guid) to the element.
*
* @param {Element|Object} elem
* Element or object to bind listeners to
*
* @param {string|string[]} type
* Type of event to bind to.
*
* @param {Function} fn
* Event listener.
*/
function on(elem, type, fn) {
if (Array.isArray(type)) {
return _handleMultipleEvents(on, elem, type, fn);
}
if (!DomData.has(elem)) {
DomData.set(elem, {});
}
const data = DomData.get(elem);
// We need a place to store all our handler data
if (!data.handlers) {
data.handlers = {};
}
if (!data.handlers[type]) {
data.handlers[type] = [];
}
if (!fn.guid) {
fn.guid = newGUID();
}
data.handlers[type].push(fn);
if (!data.dispatcher) {
data.disabled = false;
data.dispatcher = function (event, hash) {
if (data.disabled) {
return;
}
event = fixEvent(event);
const handlers = data.handlers[event.type];
if (handlers) {
// Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.
const handlersCopy = handlers.slice(0);
for (let m = 0, n = handlersCopy.length; m < n; m++) {
if (event.isImmediatePropagationStopped()) {
break;
} else {
try {
handlersCopy[m].call(elem, event, hash);
} catch (e) {
log$1.error(e);
}
}
}
}
};
}
if (data.handlers[type].length === 1) {
if (elem.addEventListener) {
let options = false;
if (supportsPassive() && passiveEvents.indexOf(type) > -1) {
options = {
passive: true
};
}
elem.addEventListener(type, data.dispatcher, options);
} else if (elem.attachEvent) {
elem.attachEvent('on' + type, data.dispatcher);
}
}
}
/**
* Removes event listeners from an element
*
* @param {Element|Object} elem
* Object to remove listeners from.
*
* @param {string|string[]} [type]
* Type of listener to remove. Don't include to remove all events from element.
*
* @param {Function} [fn]
* Specific listener to remove. Don't include to remove listeners for an event
* type.
*/
function off(elem, type, fn) {
// Don't want to add a cache object through getElData if not needed
if (!DomData.has(elem)) {
return;
}
const data = DomData.get(elem);
// If no events exist, nothing to unbind
if (!data.handlers) {
return;
}
if (Array.isArray(type)) {
return _handleMultipleEvents(off, elem, type, fn);
}
// Utility function
const removeType = function (el, t) {
data.handlers[t] = [];
_cleanUpEvents(el, t);
};
// Are we removing all bound events?
if (type === undefined) {
for (const t in data.handlers) {
if (Object.prototype.hasOwnProperty.call(data.handlers || {}, t)) {
removeType(elem, t);
}
}
return;
}
const handlers = data.handlers[type];
// If no handlers exist, nothing to unbind
if (!handlers) {
return;
}
// If no listener was provided, remove all listeners for type
if (!fn) {
removeType(elem, type);
return;
}
// We're only removing a single handler
if (fn.guid) {
for (let n = 0; n < handlers.length; n++) {
if (handlers[n].guid === fn.guid) {
handlers.splice(n--, 1);
}
}
}
_cleanUpEvents(elem, type);
}
/**
* Trigger an event for an element
*
* @param {Element|Object} elem
* Element to trigger an event on
*
* @param {EventTarget~Event|string} event
* A string (the type) or an event object with a type attribute
*
* @param {Object} [hash]
* data hash to pass along with the event
*
* @return {boolean|undefined}
* Returns the opposite of `defaultPrevented` if default was
* prevented. Otherwise, returns `undefined`
*/
function trigger(elem, event, hash) {
// Fetches element data and a reference to the parent (for bubbling).
// Don't want to add a data object to cache for every parent,
// so checking hasElData first.
const elemData = DomData.has(elem) ? DomData.get(elem) : {};
const parent = elem.parentNode || elem.ownerDocument;
// type = event.type || event,
// handler;
// If an event name was passed as a string, creates an event out of it
if (typeof event === 'string') {
event = {
type: event,
target: elem
};
} else if (!event.target) {
event.target = elem;
}
// Normalizes the event properties.
event = fixEvent(event);
// If the passed element has a dispatcher, executes the established handlers.
if (elemData.dispatcher) {
elemData.dispatcher.call(elem, event, hash);
}
// Unless explicitly stopped or the event does not bubble (e.g. media events)
// recursively calls this function to bubble the event up the DOM.
if (parent && !event.isPropagationStopped() && event.bubbles === true) {
trigger.call(null, parent, event, hash);
// If at the top of the DOM, triggers the default action unless disabled.
} else if (!parent && !event.defaultPrevented && event.target && event.target[event.type]) {
if (!DomData.has(event.target)) {
DomData.set(event.target, {});
}
const targetData = DomData.get(event.target);
// Checks if the target has a default action for this event.
if (event.target[event.type]) {
// Temporarily disables event dispatching on the target as we have already executed the handler.
targetData.disabled = true;
// Executes the default action.
if (typeof event.target[event.type] === 'function') {
event.target[event.type]();
}
// Re-enables event dispatching.
targetData.disabled = false;
}
}
// Inform the triggerer if the default was prevented by returning false
return !event.defaultPrevented;
}
/**
* Trigger a listener only once for an event.
*
* @param {Element|Object} elem
* Element or object to bind to.
*
* @param {string|string[]} type
* Name/type of event
*
* @param {Event~EventListener} fn
* Event listener function
*/
function one(elem, type, fn) {
if (Array.isArray(type)) {
return _handleMultipleEvents(one, elem, type, fn);
}
const func = function () {
off(elem, type, func);
fn.apply(this, arguments);
};
// copy the guid to the new function so it can removed using the original function's ID
func.guid = fn.guid = fn.guid || newGUID();
on(elem, type, func);
}
/**
* Trigger a listener only once and then turn if off for all
* configured events
*
* @param {Element|Object} elem
* Element or object to bind to.
*
* @param {string|string[]} type
* Name/type of event
*
* @param {Event~EventListener} fn
* Event listener function
*/
function any(elem, type, fn) {
const func = function () {
off(elem, type, func);
fn.apply(this, arguments);
};
// copy the guid to the new function so it can removed using the original function's ID
func.guid = fn.guid = fn.guid || newGUID();
// multiple ons, but one off for everything
on(elem, type, func);
}
var Events = /*#__PURE__*/Object.freeze({
__proto__: null,
fixEvent: fixEvent,
on: on,
off: off,
trigger: trigger,
one: one,
any: any
});
/**
* @file fn.js
* @module fn
*/
const UPDATE_REFRESH_INTERVAL = 30;
/**
* A private, internal-only function for changing the context of a function.
*
* It also stores a unique id on the function so it can be easily removed from
* events.
*
* @private
* @function
* @param {*} context
* The object to bind as scope.
*
* @param {Function} fn
* The function to be bound to a scope.
*
* @param {number} [uid]
* An optional unique ID for the function to be set
*
* @return {Function}
* The new function that will be bound into the context given
*/
const bind_ = function (context, fn, uid) {
// Make sure the function has a unique ID
if (!fn.guid) {
fn.guid = newGUID();
}
// Create the new function that changes the context
const bound = fn.bind(context);
// Allow for the ability to individualize this function
// Needed in the case where multiple objects might share the same prototype
// IF both items add an event listener with the same function, then you try to remove just one
// it will remove both because they both have the same guid.
// when using this, you need to use the bind method when you remove the listener as well.
// currently used in text tracks
bound.guid = uid ? uid + '_' + fn.guid : fn.guid;
return bound;
};
/**
* Wraps the given function, `fn`, with a new function that only invokes `fn`
* at most once per every `wait` milliseconds.
*
* @function
* @param {Function} fn
* The function to be throttled.
*
* @param {number} wait
* The number of milliseconds by which to throttle.
*
* @return {Function}
*/
const throttle = function (fn, wait) {
let last = window_default().performance.now();
const throttled = function (...args) {
const now = window_default().performance.now();
if (now - last >= wait) {
fn(...args);
last = now;
}
};
return throttled;
};
/**
* Creates a debounced function that delays invoking `func` until after `wait`
* milliseconds have elapsed since the last time the debounced function was
* invoked.
*
* Inspired by lodash and underscore implementations.
*
* @function
* @param {Function} func
* The function to wrap with debounce behavior.
*
* @param {number} wait
* The number of milliseconds to wait after the last invocation.
*
* @param {boolean} [immediate]
* Whether or not to invoke the function immediately upon creation.
*
* @param {Object} [context=window]
* The "context" in which the debounced function should debounce. For
* example, if this function should be tied to a Video.js player,
* the player can be passed here. Alternatively, defaults to the
* global `window` object.
*
* @return {Function}
* A debounced function.
*/
const debounce$1 = function (func, wait, immediate, context = (window_default())) {
let timeout;
const cancel = () => {
context.clearTimeout(timeout);
timeout = null;
};
/* eslint-disable consistent-this */
const debounced = function () {
const self = this;
const args = arguments;
let later = function () {
timeout = null;
later = null;
if (!immediate) {
func.apply(self, args);
}
};
if (!timeout && immediate) {
func.apply(self, args);
}
context.clearTimeout(timeout);
timeout = context.setTimeout(later, wait);
};
/* eslint-enable consistent-this */
debounced.cancel = cancel;
return debounced;
};
var Fn = /*#__PURE__*/Object.freeze({
__proto__: null,
UPDATE_REFRESH_INTERVAL: UPDATE_REFRESH_INTERVAL,
bind_: bind_,
throttle: throttle,
debounce: debounce$1
});
/**
* @file src/js/event-target.js
*/
let EVENT_MAP;
/**
* `EventTarget` is a class that can have the same API as the DOM `EventTarget`. It
* adds shorthand functions that wrap around lengthy functions. For example:
* the `on` function is a wrapper around `addEventListener`.
*
* @see [EventTarget Spec]{@link https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-EventTarget}
* @class EventTarget
*/
class EventTarget$2 {
/**
* Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
* function that will get called when an event with a certain name gets triggered.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to call with `EventTarget`s
*/
on(type, fn) {
// Remove the addEventListener alias before calling Events.on
// so we don't get into an infinite type loop
const ael = this.addEventListener;
this.addEventListener = () => {};
on(this, type, fn);
this.addEventListener = ael;
}
/**
* Removes an `event listener` for a specific event from an instance of `EventTarget`.
* This makes it so that the `event listener` will no longer get called when the
* named event happens.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to remove.
*/
off(type, fn) {
off(this, type, fn);
}
/**
* This function will add an `event listener` that gets triggered only once. After the
* first trigger it will get removed. This is like adding an `event listener`
* with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to be called once for each event name.
*/
one(type, fn) {
// Remove the addEventListener aliasing Events.on
// so we don't get into an infinite type loop
const ael = this.addEventListener;
this.addEventListener = () => {};
one(this, type, fn);
this.addEventListener = ael;
}
/**
* This function will add an `event listener` that gets triggered only once and is
* removed from all events. This is like adding an array of `event listener`s
* with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
* first time it is triggered.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to be called once for each event name.
*/
any(type, fn) {
// Remove the addEventListener aliasing Events.on
// so we don't get into an infinite type loop
const ael = this.addEventListener;
this.addEventListener = () => {};
any(this, type, fn);
this.addEventListener = ael;
}
/**
* This function causes an event to happen. This will then cause any `event listeners`
* that are waiting for that event, to get called. If there are no `event listeners`
* for an event then nothing will happen.
*
* If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
* Trigger will also call the `on` + `uppercaseEventName` function.
*
* Example:
* 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
* `onClick` if it exists.
*
* @param {string|EventTarget~Event|Object} event
* The name of the event, an `Event`, or an object with a key of type set to
* an event name.
*/
trigger(event) {
const type = event.type || event;
// deprecation
// In a future version we should default target to `this`
// similar to how we default the target to `elem` in
// `Events.trigger`. Right now the default `target` will be
// `document` due to the `Event.fixEvent` call.
if (typeof event === 'string') {
event = {
type
};
}
event = fixEvent(event);
if (this.allowedEvents_[type] && this['on' + type]) {
this['on' + type](event);
}
trigger(this, event);
}
queueTrigger(event) {
// only set up EVENT_MAP if it'll be used
if (!EVENT_MAP) {
EVENT_MAP = new Map();
}
const type = event.type || event;
let map = EVENT_MAP.get(this);
if (!map) {
map = new Map();
EVENT_MAP.set(this, map);
}
const oldTimeout = map.get(type);
map.delete(type);
window_default().clearTimeout(oldTimeout);
const timeout = window_default().setTimeout(() => {
map.delete(type);
// if we cleared out all timeouts for the current target, delete its map
if (map.size === 0) {
map = null;
EVENT_MAP.delete(this);
}
this.trigger(event);
}, 0);
map.set(type, timeout);
}
}
/**
* A Custom DOM event.
*
* @typedef {CustomEvent} Event
* @see [Properties]{@link https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent}
*/
/**
* All event listeners should follow the following format.
*
* @callback EventListener
* @this {EventTarget}
*
* @param {Event} event
* the event that triggered this function
*
* @param {Object} [hash]
* hash of data sent during the event
*/
/**
* An object containing event names as keys and booleans as values.
*
* > NOTE: If an event name is set to a true value here {@link EventTarget#trigger}
* will have extra functionality. See that function for more information.
*
* @property EventTarget.prototype.allowedEvents_
* @protected
*/
EventTarget$2.prototype.allowedEvents_ = {};
/**
* An alias of {@link EventTarget#on}. Allows `EventTarget` to mimic
* the standard DOM API.
*
* @function
* @see {@link EventTarget#on}
*/
EventTarget$2.prototype.addEventListener = EventTarget$2.prototype.on;
/**
* An alias of {@link EventTarget#off}. Allows `EventTarget` to mimic
* the standard DOM API.
*
* @function
* @see {@link EventTarget#off}
*/
EventTarget$2.prototype.removeEventListener = EventTarget$2.prototype.off;
/**
* An alias of {@link EventTarget#trigger}. Allows `EventTarget` to mimic
* the standard DOM API.
*
* @function
* @see {@link EventTarget#trigger}
*/
EventTarget$2.prototype.dispatchEvent = EventTarget$2.prototype.trigger;
/**
* @file mixins/evented.js
* @module evented
*/
const objName = obj => {
if (typeof obj.name === 'function') {
return obj.name();
}
if (typeof obj.name === 'string') {
return obj.name;
}
if (obj.name_) {
return obj.name_;
}
if (obj.constructor && obj.constructor.name) {
return obj.constructor.name;
}
return typeof obj;
};
/**
* Returns whether or not an object has had the evented mixin applied.
*
* @param {Object} object
* An object to test.
*
* @return {boolean}
* Whether or not the object appears to be evented.
*/
const isEvented = object => object instanceof EventTarget$2 || !!object.eventBusEl_ && ['on', 'one', 'off', 'trigger'].every(k => typeof object[k] === 'function');
/**
* Adds a callback to run after the evented mixin applied.
*
* @param {Object} target
* An object to Add
* @param {Function} callback
* The callback to run.
*/
const addEventedCallback = (target, callback) => {
if (isEvented(target)) {
callback();
} else {
if (!target.eventedCallbacks) {
target.eventedCallbacks = [];
}
target.eventedCallbacks.push(callback);
}
};
/**
* Whether a value is a valid event type - non-empty string or array.
*
* @private
* @param {string|Array} type
* The type value to test.
*
* @return {boolean}
* Whether or not the type is a valid event type.
*/
const isValidEventType = type =>
// The regex here verifies that the `type` contains at least one non-
// whitespace character.
typeof type === 'string' && /\S/.test(type) || Array.isArray(type) && !!type.length;
/**
* Validates a value to determine if it is a valid event target. Throws if not.
*
* @private
* @throws {Error}
* If the target does not appear to be a valid event target.
*
* @param {Object} target
* The object to test.
*
* @param {Object} obj
* The evented object we are validating for
*
* @param {string} fnName
* The name of the evented mixin function that called this.
*/
const validateTarget = (target, obj, fnName) => {
if (!target || !target.nodeName && !isEvented(target)) {
throw new Error(`Invalid target for ${objName(obj)}#${fnName}; must be a DOM node or evented object.`);
}
};
/**
* Validates a value to determine if it is a valid event target. Throws if not.
*
* @private
* @throws {Error}
* If the type does not appear to be a valid event type.
*
* @param {string|Array} type
* The type to test.
*
* @param {Object} obj
* The evented object we are validating for
*
* @param {string} fnName
* The name of the evented mixin function that called this.
*/
const validateEventType = (type, obj, fnName) => {
if (!isValidEventType(type)) {
throw new Error(`Invalid event type for ${objName(obj)}#${fnName}; must be a non-empty string or array.`);
}
};
/**
* Validates a value to determine if it is a valid listener. Throws if not.
*
* @private
* @throws {Error}
* If the listener is not a function.
*
* @param {Function} listener
* The listener to test.
*
* @param {Object} obj
* The evented object we are validating for
*
* @param {string} fnName
* The name of the evented mixin function that called this.
*/
const validateListener = (listener, obj, fnName) => {
if (typeof listener !== 'function') {
throw new Error(`Invalid listener for ${objName(obj)}#${fnName}; must be a function.`);
}
};
/**
* Takes an array of arguments given to `on()` or `one()`, validates them, and
* normalizes them into an object.
*
* @private
* @param {Object} self
* The evented object on which `on()` or `one()` was called. This
* object will be bound as the `this` value for the listener.
*
* @param {Array} args
* An array of arguments passed to `on()` or `one()`.
*
* @param {string} fnName
* The name of the evented mixin function that called this.
*
* @return {Object}
* An object containing useful values for `on()` or `one()` calls.
*/
const normalizeListenArgs = (self, args, fnName) => {
// If the number of arguments is less than 3, the target is always the
// evented object itself.
const isTargetingSelf = args.length < 3 || args[0] === self || args[0] === self.eventBusEl_;
let target;
let type;
let listener;
if (isTargetingSelf) {
target = self.eventBusEl_;
// Deal with cases where we got 3 arguments, but we are still listening to
// the evented object itself.
if (args.length >= 3) {
args.shift();
}
[type, listener] = args;
} else {
// This was `[target, type, listener] = args;` but this block needs more than
// one statement to produce minified output compatible with Chrome 53.
// See https://github.com/videojs/video.js/pull/8810
target = args[0];
type = args[1];
listener = args[2];
}
validateTarget(target, self, fnName);
validateEventType(type, self, fnName);
validateListener(listener, self, fnName);
listener = bind_(self, listener);
return {
isTargetingSelf,
target,
type,
listener
};
};
/**
* Adds the listener to the event type(s) on the target, normalizing for
* the type of target.
*
* @private
* @param {Element|Object} target
* A DOM node or evented object.
*
* @param {string} method
* The event binding method to use ("on" or "one").
*
* @param {string|Array} type
* One or more event type(s).
*
* @param {Function} listener
* A listener function.
*/
const listen = (target, method, type, listener) => {
validateTarget(target, target, method);
if (target.nodeName) {
Events[method](target, type, listener);
} else {
target[method](type, listener);
}
};
/**
* Contains methods that provide event capabilities to an object which is passed
* to {@link module:evented|evented}.
*
* @mixin EventedMixin
*/
const EventedMixin = {
/**
* Add a listener to an event (or events) on this object or another evented
* object.
*
* @param {string|Array|Element|Object} targetOrType
* If this is a string or array, it represents the event type(s)
* that will trigger the listener.
*
* Another evented object can be passed here instead, which will
* cause the listener to listen for events on _that_ object.
*
* In either case, the listener's `this` value will be bound to
* this object.
*
* @param {string|Array|Function} typeOrListener
* If the first argument was a string or array, this should be the
* listener function. Otherwise, this is a string or array of event
* type(s).
*
* @param {Function} [listener]
* If the first argument was another evented object, this will be
* the listener function.
*/
on(...args) {
const {
isTargetingSelf,
target,
type,
listener
} = normalizeListenArgs(this, args, 'on');
listen(target, 'on', type, listener);
// If this object is listening to another evented object.
if (!isTargetingSelf) {
// If this object is disposed, remove the listener.
const removeListenerOnDispose = () => this.off(target, type, listener);
// Use the same function ID as the listener so we can remove it later it
// using the ID of the original listener.
removeListenerOnDispose.guid = listener.guid;
// Add a listener to the target's dispose event as well. This ensures
// that if the target is disposed BEFORE this object, we remove the
// removal listener that was just added. Otherwise, we create a memory leak.
const removeRemoverOnTargetDispose = () => this.off('dispose', removeListenerOnDispose);
// Use the same function ID as the listener so we can remove it later
// it using the ID of the original listener.
removeRemoverOnTargetDispose.guid = listener.guid;
listen(this, 'on', 'dispose', removeListenerOnDispose);
listen(target, 'on', 'dispose', removeRemoverOnTargetDispose);
}
},
/**
* Add a listener to an event (or events) on this object or another evented
* object. The listener will be called once per event and then removed.
*
* @param {string|Array|Element|Object} targetOrType
* If this is a string or array, it represents the event type(s)
* that will trigger the listener.
*
* Another evented object can be passed here instead, which will
* cause the listener to listen for events on _that_ object.
*
* In either case, the listener's `this` value will be bound to
* this object.
*
* @param {string|Array|Function} typeOrListener
* If the first argument was a string or array, this should be the
* listener function. Otherwise, this is a string or array of event
* type(s).
*
* @param {Function} [listener]
* If the first argument was another evented object, this will be
* the listener function.
*/
one(...args) {
const {
isTargetingSelf,
target,
type,
listener
} = normalizeListenArgs(this, args, 'one');
// Targeting this evented object.
if (isTargetingSelf) {
listen(target, 'one', type, listener);
// Targeting another evented object.
} else {
// TODO: This wrapper is incorrect! It should only
// remove the wrapper for the event type that called it.
// Instead all listeners are removed on the first trigger!
// see https://github.com/videojs/video.js/issues/5962
const wrapper = (...largs) => {
this.off(target, type, wrapper);
listener.apply(null, largs);
};
// Use the same function ID as the listener so we can remove it later
// it using the ID of the original listener.
wrapper.guid = listener.guid;
listen(target, 'one', type, wrapper);
}
},
/**
* Add a listener to an event (or events) on this object or another evented
* object. The listener will only be called once for the first event that is triggered
* then removed.
*
* @param {string|Array|Element|Object} targetOrType
* If this is a string or array, it represents the event type(s)
* that will trigger the listener.
*
* Another evented object can be passed here instead, which will
* cause the listener to listen for events on _that_ object.
*
* In either case, the listener's `this` value will be bound to
* this object.
*
* @param {string|Array|Function} typeOrListener
* If the first argument was a string or array, this should be the
* listener function. Otherwise, this is a string or array of event
* type(s).
*
* @param {Function} [listener]
* If the first argument was another evented object, this will be
* the listener function.
*/
any(...args) {
const {
isTargetingSelf,
target,
type,
listener
} = normalizeListenArgs(this, args, 'any');
// Targeting this evented object.
if (isTargetingSelf) {
listen(target, 'any', type, listener);
// Targeting another evented object.
} else {
const wrapper = (...largs) => {
this.off(target, type, wrapper);
listener.apply(null, largs);
};
// Use the same function ID as the listener so we can remove it later
// it using the ID of the original listener.
wrapper.guid = listener.guid;
listen(target, 'any', type, wrapper);
}
},
/**
* Removes listener(s) from event(s) on an evented object.
*
* @param {string|Array|Element|Object} [targetOrType]
* If this is a string or array, it represents the event type(s).
*
* Another evented object can be passed here instead, in which case
* ALL 3 arguments are _required_.
*
* @param {string|Array|Function} [typeOrListener]
* If the first argument was a string or array, this may be the
* listener function. Otherwise, this is a string or array of event
* type(s).
*
* @param {Function} [listener]
* If the first argument was another evented object, this will be
* the listener function; otherwise, _all_ listeners bound to the
* event type(s) will be removed.
*/
off(targetOrType, typeOrListener, listener) {
// Targeting this evented object.
if (!targetOrType || isValidEventType(targetOrType)) {
off(this.eventBusEl_, targetOrType, typeOrListener);
// Targeting another evented object.
} else {
const target = targetOrType;
const type = typeOrListener;
// Fail fast and in a meaningful way!
validateTarget(target, this, 'off');
validateEventType(type, this, 'off');
validateListener(listener, this, 'off');
// Ensure there's at least a guid, even if the function hasn't been used
listener = bind_(this, listener);
// Remove the dispose listener on this evented object, which was given
// the same guid as the event listener in on().
this.off('dispose', listener);
if (target.nodeName) {
off(target, type, listener);
off(target, 'dispose', listener);
} else if (isEvented(target)) {
target.off(type, listener);
target.off('dispose', listener);
}
}
},
/**
* Fire an event on this evented object, causing its listeners to be called.
*
* @param {string|Object} event
* An event type or an object with a type property.
*
* @param {Object} [hash]
* An additional object to pass along to listeners.
*
* @return {boolean}
* Whether or not the default behavior was prevented.
*/
trigger(event, hash) {
validateTarget(this.eventBusEl_, this, 'trigger');
const type = event && typeof event !== 'string' ? event.type : event;
if (!isValidEventType(type)) {
throw new Error(`Invalid event type for ${objName(this)}#trigger; ` + 'must be a non-empty string or object with a type key that has a non-empty value.');
}
return trigger(this.eventBusEl_, event, hash);
}
};
/**
* Applies {@link module:evented~EventedMixin|EventedMixin} to a target object.
*
* @param {Object} target
* The object to which to add event methods.
*
* @param {Object} [options={}]
* Options for customizing the mixin behavior.
*
* @param {string} [options.eventBusKey]
* By default, adds a `eventBusEl_` DOM element to the target object,
* which is used as an event bus. If the target object already has a
* DOM element that should be used, pass its key here.
*
* @return {Object}
* The target object.
*/
function evented(target, options = {}) {
const {
eventBusKey
} = options;
// Set or create the eventBusEl_.
if (eventBusKey) {
if (!target[eventBusKey].nodeName) {
throw new Error(`The eventBusKey "${eventBusKey}" does not refer to an element.`);
}
target.eventBusEl_ = target[eventBusKey];
} else {
target.eventBusEl_ = createEl('span', {
className: 'vjs-event-bus'
});
}
Object.assign(target, EventedMixin);
if (target.eventedCallbacks) {
target.eventedCallbacks.forEach(callback => {
callback();
});
}
// When any evented object is disposed, it removes all its listeners.
target.on('dispose', () => {
target.off();
[target, target.el_, target.eventBusEl_].forEach(function (val) {
if (val && DomData.has(val)) {
DomData.delete(val);
}
});
window_default().setTimeout(() => {
target.eventBusEl_ = null;
}, 0);
});
return target;
}
/**
* @file mixins/stateful.js
* @module stateful
*/
/**
* Contains methods that provide statefulness to an object which is passed
* to {@link module:stateful}.
*
* @mixin StatefulMixin
*/
const StatefulMixin = {
/**
* A hash containing arbitrary keys and values representing the state of
* the object.
*
* @type {Object}
*/
state: {},
/**
* Set the state of an object by mutating its
* {@link module:stateful~StatefulMixin.state|state} object in place.
*
* @fires module:stateful~StatefulMixin#statechanged
* @param {Object|Function} stateUpdates
* A new set of properties to shallow-merge into the plugin state.
* Can be a plain object or a function returning a plain object.
*
* @return {Object|undefined}
* An object containing changes that occurred. If no changes
* occurred, returns `undefined`.
*/
setState(stateUpdates) {
// Support providing the `stateUpdates` state as a function.
if (typeof stateUpdates === 'function') {
stateUpdates = stateUpdates();
}
let changes;
each(stateUpdates, (value, key) => {
// Record the change if the value is different from what's in the
// current state.
if (this.state[key] !== value) {
changes = changes || {};
changes[key] = {
from: this.state[key],
to: value
};
}
this.state[key] = value;
});
// Only trigger "statechange" if there were changes AND we have a trigger
// function. This allows us to not require that the target object be an
// evented object.
if (changes && isEvented(this)) {
/**
* An event triggered on an object that is both
* {@link module:stateful|stateful} and {@link module:evented|evented}
* indicating that its state has changed.
*
* @event module:stateful~StatefulMixin#statechanged
* @type {Object}
* @property {Object} changes
* A hash containing the properties that were changed and
* the values they were changed `from` and `to`.
*/
this.trigger({
changes,
type: 'statechanged'
});
}
return changes;
}
};
/**
* Applies {@link module:stateful~StatefulMixin|StatefulMixin} to a target
* object.
*
* If the target object is {@link module:evented|evented} and has a
* `handleStateChanged` method, that method will be automatically bound to the
* `statechanged` event on itself.
*
* @param {Object} target
* The object to be made stateful.
*
* @param {Object} [defaultState]
* A default set of properties to populate the newly-stateful object's
* `state` property.
*
* @return {Object}
* Returns the `target`.
*/
function stateful(target, defaultState) {
Object.assign(target, StatefulMixin);
// This happens after the mixing-in because we need to replace the `state`
// added in that step.
target.state = Object.assign({}, target.state, defaultState);
// Auto-bind the `handleStateChanged` method of the target object if it exists.
if (typeof target.handleStateChanged === 'function' && isEvented(target)) {
target.on('statechanged', target.handleStateChanged);
}
return target;
}
/**
* @file str.js
* @module to-lower-case
*/
/**
* Lowercase the first letter of a string.
*
* @param {string} string
* String to be lowercased
*
* @return {string}
* The string with a lowercased first letter
*/
const toLowerCase = function (string) {
if (typeof string !== 'string') {
return string;
}
return string.replace(/./, w => w.toLowerCase());
};
/**
* Uppercase the first letter of a string.
*
* @param {string} string
* String to be uppercased
*
* @return {string}
* The string with an uppercased first letter
*/
const toTitleCase$1 = function (string) {
if (typeof string !== 'string') {
return string;
}
return string.replace(/./, w => w.toUpperCase());
};
/**
* Compares the TitleCase versions of the two strings for equality.
*
* @param {string} str1
* The first string to compare
*
* @param {string} str2
* The second string to compare
*
* @return {boolean}
* Whether the TitleCase versions of the strings are equal
*/
const titleCaseEquals = function (str1, str2) {
return toTitleCase$1(str1) === toTitleCase$1(str2);
};
var Str = /*#__PURE__*/Object.freeze({
__proto__: null,
toLowerCase: toLowerCase,
toTitleCase: toTitleCase$1,
titleCaseEquals: titleCaseEquals
});
/**
* Player Component - Base class for all UI objects
*
* @file component.js
*/
/** @import Player from './player' */
/**
* A callback to be called if and when the component is ready.
* `this` will be the Component instance.
*
* @callback ReadyCallback
* @returns {void}
*/
/**
* Base class for all UI Components.
* Components are UI objects which represent both a javascript object and an element
* in the DOM. They can be children of other components, and can have
* children themselves.
*
* Components can also use methods from {@link EventTarget}
*/
class Component$1 {
/**
* Creates an instance of this class.
*
* @param {Player} player
* The `Player` that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of component options.
*
* @param {Object[]} [options.children]
* An array of children objects to initialize this component with. Children objects have
* a name property that will be used if more than one component of the same type needs to be
* added.
*
* @param {string} [options.className]
* A class or space separated list of classes to add the component
*
* @param {ReadyCallback} [ready]
* Function that gets called when the `Component` is ready.
*/
constructor(player, options, ready) {
// The component might be the player itself and we can't pass `this` to super
if (!player && this.play) {
this.player_ = player = this; // eslint-disable-line
} else {
this.player_ = player;
}
this.isDisposed_ = false;
// Hold the reference to the parent component via `addChild` method
this.parentComponent_ = null;
// Make a copy of prototype.options_ to protect against overriding defaults
this.options_ = merge$1({}, this.options_);
// Updated options with supplied options
options = this.options_ = merge$1(this.options_, options);
// Get ID from options or options element if one is supplied
this.id_ = options.id || options.el && options.el.id;
// If there was no ID from the options, generate one
if (!this.id_) {
// Don't require the player ID function in the case of mock players
const id = player && player.id && player.id() || 'no_player';
this.id_ = `${id}_component_${newGUID()}`;
}
this.name_ = options.name || null;
// Create element if one wasn't provided in options
if (options.el) {
this.el_ = options.el;
} else if (options.createEl !== false) {
this.el_ = this.createEl();
}
if (options.className && this.el_) {
options.className.split(' ').forEach(c => this.addClass(c));
}
// Remove the placeholder event methods. If the component is evented, the
// real methods are added next
['on', 'off', 'one', 'any', 'trigger'].forEach(fn => {
this[fn] = undefined;
});
// if evented is anything except false, we want to mixin in evented
if (options.evented !== false) {
// Make this an evented object and use `el_`, if available, as its event bus
evented(this, {
eventBusKey: this.el_ ? 'el_' : null
});
this.handleLanguagechange = this.handleLanguagechange.bind(this);
this.on(this.player_, 'languagechange', this.handleLanguagechange);
}
stateful(this, this.constructor.defaultState);
this.children_ = [];
this.childIndex_ = {};
this.childNameIndex_ = {};
this.setTimeoutIds_ = new Set();
this.setIntervalIds_ = new Set();
this.rafIds_ = new Set();
this.namedRafs_ = new Map();
this.clearingTimersOnDispose_ = false;
// Add any child components in options
if (options.initChildren !== false) {
this.initChildren();
}
// Don't want to trigger ready here or it will go before init is actually
// finished for all children that run this constructor
this.ready(ready);
if (options.reportTouchActivity !== false) {
this.enableTouchActivity();
}
}
// `on`, `off`, `one`, `any` and `trigger` are here so tsc includes them in definitions.
// They are replaced or removed in the constructor
/**
* Adds an `event listener` to an instance of an `EventTarget`. An `event listener` is a
* function that will get called when an event with a certain name gets triggered.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to call with `EventTarget`s
*/
/**
* Removes an `event listener` for a specific event from an instance of `EventTarget`.
* This makes it so that the `event listener` will no longer get called when the
* named event happens.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} [fn]
* The function to remove. If not specified, all listeners managed by Video.js will be removed.
*/
/**
* This function will add an `event listener` that gets triggered only once. After the
* first trigger it will get removed. This is like adding an `event listener`
* with {@link EventTarget#on} that calls {@link EventTarget#off} on itself.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to be called once for each event name.
*/
/**
* This function will add an `event listener` that gets triggered only once and is
* removed from all events. This is like adding an array of `event listener`s
* with {@link EventTarget#on} that calls {@link EventTarget#off} on all events the
* first time it is triggered.
*
* @param {string|string[]} type
* An event name or an array of event names.
*
* @param {Function} fn
* The function to be called once for each event name.
*/
/**
* This function causes an event to happen. This will then cause any `event listeners`
* that are waiting for that event, to get called. If there are no `event listeners`
* for an event then nothing will happen.
*
* If the name of the `Event` that is being triggered is in `EventTarget.allowedEvents_`.
* Trigger will also call the `on` + `uppercaseEventName` function.
*
* Example:
* 'click' is in `EventTarget.allowedEvents_`, so, trigger will attempt to call
* `onClick` if it exists.
*
* @param {string|Event|Object} event
* The name of the event, an `Event`, or an object with a key of type set to
* an event name.
*
* @param {Object} [hash]
* Optionally extra argument to pass through to an event listener
*/
/**
* Dispose of the `Component` and all child components.
*
* @fires Component#dispose
*
* @param {Object} options
* @param {Element} options.originalEl element with which to replace player element
*/
dispose(options = {}) {
// Bail out if the component has already been disposed.
if (this.isDisposed_) {
return;
}
if (this.readyQueue_) {
this.readyQueue_.length = 0;
}
/**
* Triggered when a `Component` is disposed.
*
* @event Component#dispose
* @type {Event}
*
* @property {boolean} [bubbles=false]
* set to false so that the dispose event does not
* bubble up
*/
this.trigger({
type: 'dispose',
bubbles: false
});
this.isDisposed_ = true;
// Dispose all children.
if (this.children_) {
for (let i = this.children_.length - 1; i >= 0; i--) {
if (this.children_[i].dispose) {
this.children_[i].dispose();
}
}
}
// Delete child references
this.children_ = null;
this.childIndex_ = null;
this.childNameIndex_ = null;
this.parentComponent_ = null;
if (this.el_) {
// Remove element from DOM
if (this.el_.parentNode) {
if (options.restoreEl) {
this.el_.parentNode.replaceChild(options.restoreEl, this.el_);
} else {
this.el_.parentNode.removeChild(this.el_);
}
}
this.el_ = null;
}
// remove reference to the player after disposing of the element
this.player_ = null;
}
/**
* Determine whether or not this component has been disposed.
*
* @return {boolean}
* If the component has been disposed, will be `true`. Otherwise, `false`.
*/
isDisposed() {
return Boolean(this.isDisposed_);
}
/**
* Return the {@link Player} that the `Component` has attached to.
*
* @return {Player}
* The player that this `Component` has attached to.
*/
player() {
return this.player_;
}
/**
* Deep merge of options objects with new options.
* > Note: When both `obj` and `options` contain properties whose values are objects.
* The two properties get merged using {@link module:obj.merge}
*
* @param {Object} obj
* The object that contains new options.
*
* @return {Object}
* A new object of `this.options_` and `obj` merged together.
*/
options(obj) {
if (!obj) {
return this.options_;
}
this.options_ = merge$1(this.options_, obj);
return this.options_;
}
/**
* Get the `Component`s DOM element
*
* @return {Element}
* The DOM element for this `Component`.
*/
el() {
return this.el_;
}
/**
* Create the `Component`s DOM element.
*
* @param {string} [tagName]
* Element's DOM node type. e.g. 'div'
*
* @param {Object} [properties]
* An object of properties that should be set.
*
* @param {Object} [attributes]
* An object of attributes that should be set.
*
* @return {Element}
* The element that gets created.
*/
createEl(tagName, properties, attributes) {
return createEl(tagName, properties, attributes);
}
/**
* Localize a string given the string in english.
*
* If tokens are provided, it'll try and run a simple token replacement on the provided string.
* The tokens it looks for look like `{1}` with the index being 1-indexed into the tokens array.
*
* If a `defaultValue` is provided, it'll use that over `string`,
* if a value isn't found in provided language files.
* This is useful if you want to have a descriptive key for token replacement
* but have a succinct localized string and not require `en.json` to be included.
*
* Currently, it is used for the progress bar timing.
* ```js
* {
* "progress bar timing: currentTime={1} duration={2}": "{1} of {2}"
* }
* ```
* It is then used like so:
* ```js
* this.localize('progress bar timing: currentTime={1} duration{2}',
* [this.player_.currentTime(), this.player_.duration()],
* '{1} of {2}');
* ```
*
* Which outputs something like: `01:23 of 24:56`.
*
*
* @param {string} string
* The string to localize and the key to lookup in the language files.
* @param {string[]} [tokens]
* If the current item has token replacements, provide the tokens here.
* @param {string} [defaultValue]
* Defaults to `string`. Can be a default value to use for token replacement
* if the lookup key is needed to be separate.
*
* @return {string}
* The localized string or if no localization exists the english string.
*/
localize(string, tokens, defaultValue = string) {
const code = this.player_.language && this.player_.language();
const languages = this.player_.languages && this.player_.languages();
const language = languages && languages[code];
const primaryCode = code && code.split('-')[0];
const primaryLang = languages && languages[primaryCode];
let localizedString = defaultValue;
if (language && language[string]) {
localizedString = language[string];
} else if (primaryLang && primaryLang[string]) {
localizedString = primaryLang[string];
}
if (tokens) {
localizedString = localizedString.replace(/\{(\d+)\}/g, function (match, index) {
const value = tokens[index - 1];
let ret = value;
if (typeof value === 'undefined') {
ret = match;
}
return ret;
});
}
return localizedString;
}
/**
* Handles language change for the player in components. Should be overridden by sub-components.
*
* @abstract
*/
handleLanguagechange() {}
/**
* Return the `Component`s DOM element. This is where children get inserted.
* This will usually be the the same as the element returned in {@link Component#el}.
*
* @return {Element}
* The content element for this `Component`.
*/
contentEl() {
return this.contentEl_ || this.el_;
}
/**
* Get this `Component`s ID
*
* @return {string}
* The id of this `Component`
*/
id() {
return this.id_;
}
/**
* Get the `Component`s name. The name gets used to reference the `Component`
* and is set during registration.
*
* @return {string}
* The name of this `Component`.
*/
name() {
return this.name_;
}
/**
* Get an array of all child components
*
* @return {Array}
* The children
*/
children() {
return this.children_;
}
/**
* Returns the child `Component` with the given `id`.
*
* @param {string} id
* The id of the child `Component` to get.
*
* @return {Component|undefined}
* The child `Component` with the given `id` or undefined.
*/
getChildById(id) {
return this.childIndex_[id];
}
/**
* Returns the child `Component` with the given `name`.
*
* @param {string} name
* The name of the child `Component` to get.
*
* @return {Component|undefined}
* The child `Component` with the given `name` or undefined.
*/
getChild(name) {
if (!name) {
return;
}
return this.childNameIndex_[name];
}
/**
* Returns the descendant `Component` following the givent
* descendant `names`. For instance ['foo', 'bar', 'baz'] would
* try to get 'foo' on the current component, 'bar' on the 'foo'
* component and 'baz' on the 'bar' component and return undefined
* if any of those don't exist.
*
* @param {...string[]|...string} names
* The name of the child `Component` to get.
*
* @return {Component|undefined}
* The descendant `Component` following the given descendant
* `names` or undefined.
*/
getDescendant(...names) {
// flatten array argument into the main array
names = names.reduce((acc, n) => acc.concat(n), []);
let currentChild = this;
for (let i = 0; i < names.length; i++) {
currentChild = currentChild.getChild(names[i]);
if (!currentChild || !currentChild.getChild) {
return;
}
}
return currentChild;
}
/**
* Adds an SVG icon element to another element or component.
*
* @param {string} iconName
* The name of icon. A list of all the icon names can be found at 'sandbox/svg-icons.html'
*
* @param {Element} [el=this.el()]
* Element to set the title on. Defaults to the current Component's element.
*
* @return {Element}
* The newly created icon element.
*/
setIcon(iconName, el = this.el()) {
// TODO: In v9 of video.js, we will want to remove font icons entirely.
// This means this check, as well as the others throughout the code, and
// the unecessary CSS for font icons, will need to be removed.
// See https://github.com/videojs/video.js/pull/8260 as to which components
// need updating.
if (!this.player_.options_.experimentalSvgIcons) {
return;
}
const xmlnsURL = 'http://www.w3.org/2000/svg';
// The below creates an element in the format of:
//
const iconContainer = createEl('span', {
className: 'vjs-icon-placeholder vjs-svg-icon'
}, {
'aria-hidden': 'true'
});
const svgEl = document_default().createElementNS(xmlnsURL, 'svg');
svgEl.setAttributeNS(null, 'viewBox', '0 0 512 512');
const useEl = document_default().createElementNS(xmlnsURL, 'use');
svgEl.appendChild(useEl);
useEl.setAttributeNS(null, 'href', `#vjs-icon-${iconName}`);
iconContainer.appendChild(svgEl);
// Replace a pre-existing icon if one exists.
if (this.iconIsSet_) {
el.replaceChild(iconContainer, el.querySelector('.vjs-icon-placeholder'));
} else {
el.appendChild(iconContainer);
}
this.iconIsSet_ = true;
return iconContainer;
}
/**
* Add a child `Component` inside the current `Component`.
*
* @param {string|Component} child
* The name or instance of a child to add.
*
* @param {Object} [options={}]
* The key/value store of options that will get passed to children of
* the child.
*
* @param {number} [index=this.children_.length]
* The index to attempt to add a child into.
*
*
* @return {Component}
* The `Component` that gets added as a child. When using a string the
* `Component` will get created by this process.
*/
addChild(child, options = {}, index = this.children_.length) {
let component;
let componentName;
// If child is a string, create component with options
if (typeof child === 'string') {
componentName = toTitleCase$1(child);
const componentClassName = options.componentClass || componentName;
// Set name through options
options.name = componentName;
// Create a new object & element for this controls set
// If there's no .player_, this is a player
const ComponentClass = Component$1.getComponent(componentClassName);
if (!ComponentClass) {
throw new Error(`Component ${componentClassName} does not exist`);
}
// data stored directly on the videojs object may be
// misidentified as a component to retain
// backwards-compatibility with 4.x. check to make sure the
// component class can be instantiated.
if (typeof ComponentClass !== 'function') {
return null;
}
component = new ComponentClass(this.player_ || this, options);
// child is a component instance
} else {
component = child;
}
if (component.parentComponent_) {
component.parentComponent_.removeChild(component);
}
this.children_.splice(index, 0, component);
component.parentComponent_ = this;
if (typeof component.id === 'function') {
this.childIndex_[component.id()] = component;
}
// If a name wasn't used to create the component, check if we can use the
// name function of the component
componentName = componentName || component.name && toTitleCase$1(component.name());
if (componentName) {
this.childNameIndex_[componentName] = component;
this.childNameIndex_[toLowerCase(componentName)] = component;
}
// Add the UI object's element to the container div (box)
// Having an element is not required
if (typeof component.el === 'function' && component.el()) {
// If inserting before a component, insert before that component's element
let refNode = null;
if (this.children_[index + 1]) {
// Most children are components, but the video tech is an HTML element
if (this.children_[index + 1].el_) {
refNode = this.children_[index + 1].el_;
} else if (isEl(this.children_[index + 1])) {
refNode = this.children_[index + 1];
}
}
this.contentEl().insertBefore(component.el(), refNode);
}
// Return so it can stored on parent object if desired.
return component;
}
/**
* Remove a child `Component` from this `Component`s list of children. Also removes
* the child `Component`s element from this `Component`s element.
*
* @param {string|Component} component
* The name or instance of a child to remove.
*/
removeChild(component) {
if (typeof component === 'string') {
component = this.getChild(component);
}
if (!component || !this.children_) {
return;
}
let childFound = false;
for (let i = this.children_.length - 1; i >= 0; i--) {
if (this.children_[i] === component) {
childFound = true;
this.children_.splice(i, 1);
break;
}
}
if (!childFound) {
return;
}
component.parentComponent_ = null;
this.childIndex_[component.id()] = null;
this.childNameIndex_[toTitleCase$1(component.name())] = null;
this.childNameIndex_[toLowerCase(component.name())] = null;
const compEl = component.el();
if (compEl && compEl.parentNode === this.contentEl()) {
this.contentEl().removeChild(component.el());
}
}
/**
* Add and initialize default child `Component`s based upon options.
*/
initChildren() {
const children = this.options_.children;
if (children) {
// `this` is `parent`
const parentOptions = this.options_;
const handleAdd = child => {
const name = child.name;
let opts = child.opts;
// Allow options for children to be set at the parent options
// e.g. videojs(id, { controlBar: false });
// instead of videojs(id, { children: { controlBar: false });
if (parentOptions[name] !== undefined) {
opts = parentOptions[name];
}
// Allow for disabling default components
// e.g. options['children']['posterImage'] = false
if (opts === false) {
return;
}
// Allow options to be passed as a simple boolean if no configuration
// is necessary.
if (opts === true) {
opts = {};
}
// We also want to pass the original player options
// to each component as well so they don't need to
// reach back into the player for options later.
opts.playerOptions = this.options_.playerOptions;
// Create and add the child component.
// Add a direct reference to the child by name on the parent instance.
// If two of the same component are used, different names should be supplied
// for each
const newChild = this.addChild(name, opts);
if (newChild) {
this[name] = newChild;
}
};
// Allow for an array of children details to passed in the options
let workingChildren;
const Tech = Component$1.getComponent('Tech');
if (Array.isArray(children)) {
workingChildren = children;
} else {
workingChildren = Object.keys(children);
}
workingChildren
// children that are in this.options_ but also in workingChildren would
// give us extra children we do not want. So, we want to filter them out.
.concat(Object.keys(this.options_).filter(function (child) {
return !workingChildren.some(function (wchild) {
if (typeof wchild === 'string') {
return child === wchild;
}
return child === wchild.name;
});
})).map(child => {
let name;
let opts;
if (typeof child === 'string') {
name = child;
opts = children[name] || this.options_[name] || {};
} else {
name = child.name;
opts = child;
}
return {
name,
opts
};
}).filter(child => {
// we have to make sure that child.name isn't in the techOrder since
// techs are registered as Components but can't aren't compatible
// See https://github.com/videojs/video.js/issues/2772
const c = Component$1.getComponent(child.opts.componentClass || toTitleCase$1(child.name));
return c && !Tech.isTech(c);
}).forEach(handleAdd);
}
}
/**
* Builds the default DOM class name. Should be overridden by sub-components.
*
* @return {string}
* The DOM class name for this object.
*
* @abstract
*/
buildCSSClass() {
// Child classes can include a function that does:
// return 'CLASS NAME' + this._super();
return '';
}
/**
* Bind a listener to the component's ready state.
* Different from event listeners in that if the ready event has already happened
* it will trigger the function immediately.
*
* @param {ReadyCallback} fn
* Function that gets called when the `Component` is ready.
*/
ready(fn, sync = false) {
if (!fn) {
return;
}
if (!this.isReady_) {
this.readyQueue_ = this.readyQueue_ || [];
this.readyQueue_.push(fn);
return;
}
if (sync) {
fn.call(this);
} else {
// Call the function asynchronously by default for consistency
this.setTimeout(fn, 1);
}
}
/**
* Trigger all the ready listeners for this `Component`.
*
* @fires Component#ready
*/
triggerReady() {
this.isReady_ = true;
// Ensure ready is triggered asynchronously
this.setTimeout(function () {
const readyQueue = this.readyQueue_;
// Reset Ready Queue
this.readyQueue_ = [];
if (readyQueue && readyQueue.length > 0) {
readyQueue.forEach(function (fn) {
fn.call(this);
}, this);
}
// Allow for using event listeners also
/**
* Triggered when a `Component` is ready.
*
* @event Component#ready
* @type {Event}
*/
this.trigger('ready');
}, 1);
}
/**
* Find a single DOM element matching a `selector`. This can be within the `Component`s
* `contentEl()` or another custom context.
*
* @param {string} selector
* A valid CSS selector, which will be passed to `querySelector`.
*
* @param {Element|string} [context=this.contentEl()]
* A DOM element within which to query. Can also be a selector string in
* which case the first matching element will get used as context. If
* missing `this.contentEl()` gets used. If `this.contentEl()` returns
* nothing it falls back to `document`.
*
* @return {Element|null}
* the dom element that was found, or null
*
* @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
*/
$(selector, context) {
return $(selector, context || this.contentEl());
}
/**
* Finds all DOM element matching a `selector`. This can be within the `Component`s
* `contentEl()` or another custom context.
*
* @param {string} selector
* A valid CSS selector, which will be passed to `querySelectorAll`.
*
* @param {Element|string} [context=this.contentEl()]
* A DOM element within which to query. Can also be a selector string in
* which case the first matching element will get used as context. If
* missing `this.contentEl()` gets used. If `this.contentEl()` returns
* nothing it falls back to `document`.
*
* @return {NodeList}
* a list of dom elements that were found
*
* @see [Information on CSS Selectors](https://developer.mozilla.org/en-US/docs/Web/Guide/CSS/Getting_Started/Selectors)
*/
$$(selector, context) {
return $$(selector, context || this.contentEl());
}
/**
* Check if a component's element has a CSS class name.
*
* @param {string} classToCheck
* CSS class name to check.
*
* @return {boolean}
* - True if the `Component` has the class.
* - False if the `Component` does not have the class`
*/
hasClass(classToCheck) {
return hasClass(this.el_, classToCheck);
}
/**
* Add a CSS class name to the `Component`s element.
*
* @param {...string} classesToAdd
* One or more CSS class name to add.
*/
addClass(...classesToAdd) {
addClass(this.el_, ...classesToAdd);
}
/**
* Remove a CSS class name from the `Component`s element.
*
* @param {...string} classesToRemove
* One or more CSS class name to remove.
*/
removeClass(...classesToRemove) {
removeClass(this.el_, ...classesToRemove);
}
/**
* Add or remove a CSS class name from the component's element.
* - `classToToggle` gets added when {@link Component#hasClass} would return false.
* - `classToToggle` gets removed when {@link Component#hasClass} would return true.
*
* @param {string} classToToggle
* The class to add or remove. Passed to DOMTokenList's toggle()
*
* @param {boolean|Dom.PredicateCallback} [predicate]
* A boolean or function that returns a boolean. Passed to DOMTokenList's toggle().
*/
toggleClass(classToToggle, predicate) {
toggleClass(this.el_, classToToggle, predicate);
}
/**
* Show the `Component`s element if it is hidden by removing the
* 'vjs-hidden' class name from it.
*/
show() {
this.removeClass('vjs-hidden');
}
/**
* Hide the `Component`s element if it is currently showing by adding the
* 'vjs-hidden` class name to it.
*/
hide() {
this.addClass('vjs-hidden');
}
/**
* Lock a `Component`s element in its visible state by adding the 'vjs-lock-showing'
* class name to it. Used during fadeIn/fadeOut.
*
* @private
*/
lockShowing() {
this.addClass('vjs-lock-showing');
}
/**
* Unlock a `Component`s element from its visible state by removing the 'vjs-lock-showing'
* class name from it. Used during fadeIn/fadeOut.
*
* @private
*/
unlockShowing() {
this.removeClass('vjs-lock-showing');
}
/**
* Get the value of an attribute on the `Component`s element.
*
* @param {string} attribute
* Name of the attribute to get the value from.
*
* @return {string|null}
* - The value of the attribute that was asked for.
* - Can be an empty string on some browsers if the attribute does not exist
* or has no value
* - Most browsers will return null if the attribute does not exist or has
* no value.
*
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/getAttribute}
*/
getAttribute(attribute) {
return getAttribute(this.el_, attribute);
}
/**
* Set the value of an attribute on the `Component`'s element
*
* @param {string} attribute
* Name of the attribute to set.
*
* @param {string} value
* Value to set the attribute to.
*
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/setAttribute}
*/
setAttribute(attribute, value) {
setAttribute(this.el_, attribute, value);
}
/**
* Remove an attribute from the `Component`s element.
*
* @param {string} attribute
* Name of the attribute to remove.
*
* @see [DOM API]{@link https://developer.mozilla.org/en-US/docs/Web/API/Element/removeAttribute}
*/
removeAttribute(attribute) {
removeAttribute(this.el_, attribute);
}
/**
* Get or set the width of the component based upon the CSS styles.
* See {@link Component#dimension} for more detailed information.
*
* @param {number|string} [num]
* The width that you want to set postfixed with '%', 'px' or nothing.
*
* @param {boolean} [skipListeners]
* Skip the componentresize event trigger
*
* @return {number|undefined}
* The width when getting, zero if there is no width
*/
width(num, skipListeners) {
return this.dimension('width', num, skipListeners);
}
/**
* Get or set the height of the component based upon the CSS styles.
* See {@link Component#dimension} for more detailed information.
*
* @param {number|string} [num]
* The height that you want to set postfixed with '%', 'px' or nothing.
*
* @param {boolean} [skipListeners]
* Skip the componentresize event trigger
*
* @return {number|undefined}
* The height when getting, zero if there is no height
*/
height(num, skipListeners) {
return this.dimension('height', num, skipListeners);
}
/**
* Set both the width and height of the `Component` element at the same time.
*
* @param {number|string} width
* Width to set the `Component`s element to.
*
* @param {number|string} height
* Height to set the `Component`s element to.
*/
dimensions(width, height) {
// Skip componentresize listeners on width for optimization
this.width(width, true);
this.height(height);
}
/**
* Get or set width or height of the `Component` element. This is the shared code
* for the {@link Component#width} and {@link Component#height}.
*
* Things to know:
* - If the width or height in an number this will return the number postfixed with 'px'.
* - If the width/height is a percent this will return the percent postfixed with '%'
* - Hidden elements have a width of 0 with `window.getComputedStyle`. This function
* defaults to the `Component`s `style.width` and falls back to `window.getComputedStyle`.
* See [this]{@link http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/}
* for more information
* - If you want the computed style of the component, use {@link Component#currentWidth}
* and {@link {Component#currentHeight}
*
* @fires Component#componentresize
*
* @param {string} widthOrHeight
8 'width' or 'height'
*
* @param {number|string} [num]
8 New dimension
*
* @param {boolean} [skipListeners]
* Skip componentresize event trigger
*
* @return {number|undefined}
* The dimension when getting or 0 if unset
*/
dimension(widthOrHeight, num, skipListeners) {
if (num !== undefined) {
// Set to zero if null or literally NaN (NaN !== NaN)
if (num === null || num !== num) {
num = 0;
}
// Check if using css width/height (% or px) and adjust
if (('' + num).indexOf('%') !== -1 || ('' + num).indexOf('px') !== -1) {
this.el_.style[widthOrHeight] = num;
} else if (num === 'auto') {
this.el_.style[widthOrHeight] = '';
} else {
this.el_.style[widthOrHeight] = num + 'px';
}
// skipListeners allows us to avoid triggering the resize event when setting both width and height
if (!skipListeners) {
/**
* Triggered when a component is resized.
*
* @event Component#componentresize
* @type {Event}
*/
this.trigger('componentresize');
}
return;
}
// Not setting a value, so getting it
// Make sure element exists
if (!this.el_) {
return 0;
}
// Get dimension value from style
const val = this.el_.style[widthOrHeight];
const pxIndex = val.indexOf('px');
if (pxIndex !== -1) {
// Return the pixel value with no 'px'
return parseInt(val.slice(0, pxIndex), 10);
}
// No px so using % or no style was set, so falling back to offsetWidth/height
// If component has display:none, offset will return 0
// TODO: handle display:none and no dimension style using px
return parseInt(this.el_['offset' + toTitleCase$1(widthOrHeight)], 10);
}
/**
* Get the computed width or the height of the component's element.
*
* Uses `window.getComputedStyle`.
*
* @param {string} widthOrHeight
* A string containing 'width' or 'height'. Whichever one you want to get.
*
* @return {number}
* The dimension that gets asked for or 0 if nothing was set
* for that dimension.
*/
currentDimension(widthOrHeight) {
let computedWidthOrHeight = 0;
if (widthOrHeight !== 'width' && widthOrHeight !== 'height') {
throw new Error('currentDimension only accepts width or height value');
}
computedWidthOrHeight = computedStyle(this.el_, widthOrHeight);
// remove 'px' from variable and parse as integer
computedWidthOrHeight = parseFloat(computedWidthOrHeight);
// if the computed value is still 0, it's possible that the browser is lying
// and we want to check the offset values.
// This code also runs wherever getComputedStyle doesn't exist.
if (computedWidthOrHeight === 0 || isNaN(computedWidthOrHeight)) {
const rule = `offset${toTitleCase$1(widthOrHeight)}`;
computedWidthOrHeight = this.el_[rule];
}
return computedWidthOrHeight;
}
/**
* An object that contains width and height values of the `Component`s
* computed style. Uses `window.getComputedStyle`.
*
* @typedef {Object} Component~DimensionObject
*
* @property {number} width
* The width of the `Component`s computed style.
*
* @property {number} height
* The height of the `Component`s computed style.
*/
/**
* Get an object that contains computed width and height values of the
* component's element.
*
* Uses `window.getComputedStyle`.
*
* @return {Component~DimensionObject}
* The computed dimensions of the component's element.
*/
currentDimensions() {
return {
width: this.currentDimension('width'),
height: this.currentDimension('height')
};
}
/**
* Get the computed width of the component's element.
*
* Uses `window.getComputedStyle`.
*
* @return {number}
* The computed width of the component's element.
*/
currentWidth() {
return this.currentDimension('width');
}
/**
* Get the computed height of the component's element.
*
* Uses `window.getComputedStyle`.
*
* @return {number}
* The computed height of the component's element.
*/
currentHeight() {
return this.currentDimension('height');
}
/**
* Retrieves the position and size information of the component's element.
*
* @return {Object} An object with `boundingClientRect` and `center` properties.
* - `boundingClientRect`: An object with properties `x`, `y`, `width`,
* `height`, `top`, `right`, `bottom`, and `left`, representing
* the bounding rectangle of the element.
* - `center`: An object with properties `x` and `y`, representing
* the center point of the element. `width` and `height` are set to 0.
*/
getPositions() {
const rect = this.el_.getBoundingClientRect();
// Creating objects that mirror DOMRectReadOnly for boundingClientRect and center
const boundingClientRect = {
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
top: rect.top,
right: rect.right,
bottom: rect.bottom,
left: rect.left
};
// Calculating the center position
const center = {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2,
width: 0,
height: 0,
top: rect.top + rect.height / 2,
right: rect.left + rect.width / 2,
bottom: rect.top + rect.height / 2,
left: rect.left + rect.width / 2
};
return {
boundingClientRect,
center
};
}
/**
* Set the focus to this component
*/
focus() {
this.el_.focus();
}
/**
* Remove the focus from this component
*/
blur() {
this.el_.blur();
}
/**
* When this Component receives a `keydown` event which it does not process,
* it passes the event to the Player for handling.
*
* @param {KeyboardEvent} event
* The `keydown` event that caused this function to be called.
*/
handleKeyDown(event) {
if (this.player_) {
// We only stop propagation here because we want unhandled events to fall
// back to the browser. Exclude Tab for focus trapping, exclude also when spatialNavigation is enabled.
if (event.key !== 'Tab' && !(this.player_.options_.playerOptions.spatialNavigation && this.player_.options_.playerOptions.spatialNavigation.enabled)) {
event.stopPropagation();
}
this.player_.handleKeyDown(event);
}
}
/**
* Many components used to have a `handleKeyPress` method, which was poorly
* named because it listened to a `keydown` event. This method name now
* delegates to `handleKeyDown`. This means anyone calling `handleKeyPress`
* will not see their method calls stop working.
*
* @param {KeyboardEvent} event
* The event that caused this function to be called.
*/
handleKeyPress(event) {
this.handleKeyDown(event);
}
/**
* Emit a 'tap' events when touch event support gets detected. This gets used to
* support toggling the controls through a tap on the video. They get enabled
* because every sub-component would have extra overhead otherwise.
*
* @protected
* @fires Component#tap
* @listens Component#touchstart
* @listens Component#touchmove
* @listens Component#touchleave
* @listens Component#touchcancel
* @listens Component#touchend
*/
emitTapEvents() {
// Track the start time so we can determine how long the touch lasted
let touchStart = 0;
let firstTouch = null;
// Maximum movement allowed during a touch event to still be considered a tap
// Other popular libs use anywhere from 2 (hammer.js) to 15,
// so 10 seems like a nice, round number.
const tapMovementThreshold = 10;
// The maximum length a touch can be while still being considered a tap
const touchTimeThreshold = 200;
let couldBeTap;
this.on('touchstart', function (event) {
// If more than one finger, don't consider treating this as a click
if (event.touches.length === 1) {
// Copy pageX/pageY from the object
firstTouch = {
pageX: event.touches[0].pageX,
pageY: event.touches[0].pageY
};
// Record start time so we can detect a tap vs. "touch and hold"
touchStart = window_default().performance.now();
// Reset couldBeTap tracking
couldBeTap = true;
}
});
this.on('touchmove', function (event) {
// If more than one finger, don't consider treating this as a click
if (event.touches.length > 1) {
couldBeTap = false;
} else if (firstTouch) {
// Some devices will throw touchmoves for all but the slightest of taps.
// So, if we moved only a small distance, this could still be a tap
const xdiff = event.touches[0].pageX - firstTouch.pageX;
const ydiff = event.touches[0].pageY - firstTouch.pageY;
const touchDistance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
if (touchDistance > tapMovementThreshold) {
couldBeTap = false;
}
}
});
const noTap = function () {
couldBeTap = false;
};
// TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s
this.on('touchleave', noTap);
this.on('touchcancel', noTap);
// When the touch ends, measure how long it took and trigger the appropriate
// event
this.on('touchend', function (event) {
firstTouch = null;
// Proceed only if the touchmove/leave/cancel event didn't happen
if (couldBeTap === true) {
// Measure how long the touch lasted
const touchTime = window_default().performance.now() - touchStart;
// Make sure the touch was less than the threshold to be considered a tap
if (touchTime < touchTimeThreshold) {
// Don't let browser turn this into a click
event.preventDefault();
/**
* Triggered when a `Component` is tapped.
*
* @event Component#tap
* @type {MouseEvent}
*/
this.trigger('tap');
// It may be good to copy the touchend event object and change the
// type to tap, if the other event properties aren't exact after
// Events.fixEvent runs (e.g. event.target)
}
}
});
}
/**
* This function reports user activity whenever touch events happen. This can get
* turned off by any sub-components that wants touch events to act another way.
*
* Report user touch activity when touch events occur. User activity gets used to
* determine when controls should show/hide. It is simple when it comes to mouse
* events, because any mouse event should show the controls. So we capture mouse
* events that bubble up to the player and report activity when that happens.
* With touch events it isn't as easy as `touchstart` and `touchend` toggle player
* controls. So touch events can't help us at the player level either.
*
* User activity gets checked asynchronously. So what could happen is a tap event
* on the video turns the controls off. Then the `touchend` event bubbles up to
* the player. Which, if it reported user activity, would turn the controls right
* back on. We also don't want to completely block touch events from bubbling up.
* Furthermore a `touchmove` event and anything other than a tap, should not turn
* controls back on.
*
* @listens Component#touchstart
* @listens Component#touchmove
* @listens Component#touchend
* @listens Component#touchcancel
*/
enableTouchActivity() {
// Don't continue if the root player doesn't support reporting user activity
if (!this.player() || !this.player().reportUserActivity) {
return;
}
// listener for reporting that the user is active
const report = bind_(this.player(), this.player().reportUserActivity);
let touchHolding;
this.on('touchstart', function () {
report();
// For as long as the they are touching the device or have their mouse down,
// we consider them active even if they're not moving their finger or mouse.
// So we want to continue to update that they are active
this.clearInterval(touchHolding);
// report at the same interval as activityCheck
touchHolding = this.setInterval(report, 250);
});
const touchEnd = function (event) {
report();
// stop the interval that maintains activity if the touch is holding
this.clearInterval(touchHolding);
};
this.on('touchmove', report);
this.on('touchend', touchEnd);
this.on('touchcancel', touchEnd);
}
/**
* A callback that has no parameters and is bound into `Component`s context.
*
* @callback Component~GenericCallback
* @this Component
*/
/**
* Creates a function that runs after an `x` millisecond timeout. This function is a
* wrapper around `window.setTimeout`. There are a few reasons to use this one
* instead though:
* 1. It gets cleared via {@link Component#clearTimeout} when
* {@link Component#dispose} gets called.
* 2. The function callback will gets turned into a {@link Component~GenericCallback}
*
* > Note: You can't use `window.clearTimeout` on the id returned by this function. This
* will cause its dispose listener not to get cleaned up! Please use
* {@link Component#clearTimeout} or {@link Component#dispose} instead.
*
* @param {Component~GenericCallback} fn
* The function that will be run after `timeout`.
*
* @param {number} timeout
* Timeout in milliseconds to delay before executing the specified function.
*
* @return {number}
* Returns a timeout ID that gets used to identify the timeout. It can also
* get used in {@link Component#clearTimeout} to clear the timeout that
* was set.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout}
*/
setTimeout(fn, timeout) {
// declare as variables so they are properly available in timeout function
// eslint-disable-next-line
var timeoutId;
fn = bind_(this, fn);
this.clearTimersOnDispose_();
timeoutId = window_default().setTimeout(() => {
if (this.setTimeoutIds_.has(timeoutId)) {
this.setTimeoutIds_.delete(timeoutId);
}
fn();
}, timeout);
this.setTimeoutIds_.add(timeoutId);
return timeoutId;
}
/**
* Clears a timeout that gets created via `window.setTimeout` or
* {@link Component#setTimeout}. If you set a timeout via {@link Component#setTimeout}
* use this function instead of `window.clearTimout`. If you don't your dispose
* listener will not get cleaned up until {@link Component#dispose}!
*
* @param {number} timeoutId
* The id of the timeout to clear. The return value of
* {@link Component#setTimeout} or `window.setTimeout`.
*
* @return {number}
* Returns the timeout id that was cleared.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearTimeout}
*/
clearTimeout(timeoutId) {
if (this.setTimeoutIds_.has(timeoutId)) {
this.setTimeoutIds_.delete(timeoutId);
window_default().clearTimeout(timeoutId);
}
return timeoutId;
}
/**
* Creates a function that gets run every `x` milliseconds. This function is a wrapper
* around `window.setInterval`. There are a few reasons to use this one instead though.
* 1. It gets cleared via {@link Component#clearInterval} when
* {@link Component#dispose} gets called.
* 2. The function callback will be a {@link Component~GenericCallback}
*
* @param {Component~GenericCallback} fn
* The function to run every `x` seconds.
*
* @param {number} interval
* Execute the specified function every `x` milliseconds.
*
* @return {number}
* Returns an id that can be used to identify the interval. It can also be be used in
* {@link Component#clearInterval} to clear the interval.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval}
*/
setInterval(fn, interval) {
fn = bind_(this, fn);
this.clearTimersOnDispose_();
const intervalId = window_default().setInterval(fn, interval);
this.setIntervalIds_.add(intervalId);
return intervalId;
}
/**
* Clears an interval that gets created via `window.setInterval` or
* {@link Component#setInterval}. If you set an interval via {@link Component#setInterval}
* use this function instead of `window.clearInterval`. If you don't your dispose
* listener will not get cleaned up until {@link Component#dispose}!
*
* @param {number} intervalId
* The id of the interval to clear. The return value of
* {@link Component#setInterval} or `window.setInterval`.
*
* @return {number}
* Returns the interval id that was cleared.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/clearInterval}
*/
clearInterval(intervalId) {
if (this.setIntervalIds_.has(intervalId)) {
this.setIntervalIds_.delete(intervalId);
window_default().clearInterval(intervalId);
}
return intervalId;
}
/**
* Queues up a callback to be passed to requestAnimationFrame (rAF), but
* with a few extra bonuses:
*
* - Supports browsers that do not support rAF by falling back to
* {@link Component#setTimeout}.
*
* - The callback is turned into a {@link Component~GenericCallback} (i.e.
* bound to the component).
*
* - Automatic cancellation of the rAF callback is handled if the component
* is disposed before it is called.
*
* @param {Component~GenericCallback} fn
* A function that will be bound to this component and executed just
* before the browser's next repaint.
*
* @return {number}
* Returns an rAF ID that gets used to identify the timeout. It can
* also be used in {@link Component#cancelAnimationFrame} to cancel
* the animation frame callback.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
*/
requestAnimationFrame(fn) {
this.clearTimersOnDispose_();
// declare as variables so they are properly available in rAF function
// eslint-disable-next-line
var id;
fn = bind_(this, fn);
id = window_default().requestAnimationFrame(() => {
if (this.rafIds_.has(id)) {
this.rafIds_.delete(id);
}
fn();
});
this.rafIds_.add(id);
return id;
}
/**
* Request an animation frame, but only one named animation
* frame will be queued. Another will never be added until
* the previous one finishes.
*
* @param {string} name
* The name to give this requestAnimationFrame
*
* @param {Component~GenericCallback} fn
* A function that will be bound to this component and executed just
* before the browser's next repaint.
*/
requestNamedAnimationFrame(name, fn) {
if (this.namedRafs_.has(name)) {
this.cancelNamedAnimationFrame(name);
}
this.clearTimersOnDispose_();
fn = bind_(this, fn);
const id = this.requestAnimationFrame(() => {
fn();
if (this.namedRafs_.has(name)) {
this.namedRafs_.delete(name);
}
});
this.namedRafs_.set(name, id);
return name;
}
/**
* Cancels a current named animation frame if it exists.
*
* @param {string} name
* The name of the requestAnimationFrame to cancel.
*/
cancelNamedAnimationFrame(name) {
if (!this.namedRafs_.has(name)) {
return;
}
this.cancelAnimationFrame(this.namedRafs_.get(name));
this.namedRafs_.delete(name);
}
/**
* Cancels a queued callback passed to {@link Component#requestAnimationFrame}
* (rAF).
*
* If you queue an rAF callback via {@link Component#requestAnimationFrame},
* use this function instead of `window.cancelAnimationFrame`. If you don't,
* your dispose listener will not get cleaned up until {@link Component#dispose}!
*
* @param {number} id
* The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
*
* @return {number}
* Returns the rAF ID that was cleared.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
*/
cancelAnimationFrame(id) {
if (this.rafIds_.has(id)) {
this.rafIds_.delete(id);
window_default().cancelAnimationFrame(id);
}
return id;
}
/**
* A function to setup `requestAnimationFrame`, `setTimeout`,
* and `setInterval`, clearing on dispose.
*
* > Previously each timer added and removed dispose listeners on it's own.
* For better performance it was decided to batch them all, and use `Set`s
* to track outstanding timer ids.
*
* @private
*/
clearTimersOnDispose_() {
if (this.clearingTimersOnDispose_) {
return;
}
this.clearingTimersOnDispose_ = true;
this.one('dispose', () => {
[['namedRafs_', 'cancelNamedAnimationFrame'], ['rafIds_', 'cancelAnimationFrame'], ['setTimeoutIds_', 'clearTimeout'], ['setIntervalIds_', 'clearInterval']].forEach(([idName, cancelName]) => {
// for a `Set` key will actually be the value again
// so forEach((val, val) =>` but for maps we want to use
// the key.
this[idName].forEach((val, key) => this[cancelName](key));
});
this.clearingTimersOnDispose_ = false;
});
}
/**
* Decide whether an element is actually disabled or not.
*
* @function isActuallyDisabled
* @param element {Node}
* @return {boolean}
*
* @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled}
*/
getIsDisabled() {
return Boolean(this.el_.disabled);
}
/**
* Decide whether the element is expressly inert or not.
*
* @see {@link https://html.spec.whatwg.org/multipage/interaction.html#expressly-inert}
* @function isExpresslyInert
* @param element {Node}
* @return {boolean}
*/
getIsExpresslyInert() {
return this.el_.inert && !this.el_.ownerDocument.documentElement.inert;
}
/**
* Determine whether or not this component can be considered as focusable component.
*
* @param {HTMLElement} el - The HTML element representing the component.
* @return {boolean}
* If the component can be focused, will be `true`. Otherwise, `false`.
*/
getIsFocusable(el) {
const element = el || this.el_;
return element.tabIndex >= 0 && !(this.getIsDisabled() || this.getIsExpresslyInert());
}
/**
* Determine whether or not this component is currently visible/enabled/etc...
*
* @param {HTMLElement} el - The HTML element representing the component.
* @return {boolean}
* If the component can is currently visible & enabled, will be `true`. Otherwise, `false`.
*/
getIsAvailableToBeFocused(el) {
/**
* Decide the style property of this element is specified whether it's visible or not.
*
* @function isVisibleStyleProperty
* @param element {CSSStyleDeclaration}
* @return {boolean}
*/
function isVisibleStyleProperty(element) {
const elementStyle = window_default().getComputedStyle(element, null);
const thisVisibility = elementStyle.getPropertyValue('visibility');
const thisDisplay = elementStyle.getPropertyValue('display');
const invisibleStyle = ['hidden', 'collapse'];
return thisDisplay !== 'none' && !invisibleStyle.includes(thisVisibility);
}
/**
* Decide whether the element is being rendered or not.
* 1. If an element has the style as "visibility: hidden | collapse" or "display: none", it is not being rendered.
* 2. If an element has the style as "opacity: 0", it is not being rendered.(that is, invisible).
* 3. If width and height of an element are explicitly set to 0, it is not being rendered.
* 4. If a parent element is hidden, an element itself is not being rendered.
* (CSS visibility property and display property are inherited.)
*
* @see {@link https://html.spec.whatwg.org/multipage/rendering.html#being-rendered}
* @function isBeingRendered
* @param element {Node}
* @return {boolean}
*/
function isBeingRendered(element) {
if (!isVisibleStyleProperty(element.parentElement)) {
return false;
}
if (!isVisibleStyleProperty(element) || element.style.opacity === '0' || window_default().getComputedStyle(element).height === '0px' || window_default().getComputedStyle(element).width === '0px') {
return false;
}
return true;
}
/**
* Determine if the element is visible for the user or not.
* 1. If an element sum of its offsetWidth, offsetHeight, height and width is less than 1 is not visible.
* 2. If elementCenter.x is less than is not visible.
* 3. If elementCenter.x is more than the document's width is not visible.
* 4. If elementCenter.y is less than 0 is not visible.
* 5. If elementCenter.y is the document's height is not visible.
*
* @function isVisible
* @param element {Node}
* @return {boolean}
*/
function isVisible(element) {
if (element.offsetWidth + element.offsetHeight + element.getBoundingClientRect().height + element.getBoundingClientRect().width === 0) {
return false;
}
// Define elementCenter object with props of x and y
// x: Left position relative to the viewport plus element's width (no margin) divided between 2.
// y: Top position relative to the viewport plus element's height (no margin) divided between 2.
const elementCenter = {
x: element.getBoundingClientRect().left + element.offsetWidth / 2,
y: element.getBoundingClientRect().top + element.offsetHeight / 2
};
if (elementCenter.x < 0) {
return false;
}
if (elementCenter.x > ((document_default()).documentElement.clientWidth || (window_default()).innerWidth)) {
return false;
}
if (elementCenter.y < 0) {
return false;
}
if (elementCenter.y > ((document_default()).documentElement.clientHeight || (window_default()).innerHeight)) {
return false;
}
let pointContainer = document_default().elementFromPoint(elementCenter.x, elementCenter.y);
while (pointContainer) {
if (pointContainer === element) {
return true;
}
if (pointContainer.parentNode) {
pointContainer = pointContainer.parentNode;
} else {
return false;
}
}
}
// If no DOM element was passed as argument use this component's element.
if (!el) {
el = this.el();
}
// If element is visible, is being rendered & either does not have a parent element or its tabIndex is not negative.
if (isVisible(el) && isBeingRendered(el) && (!el.parentElement || el.tabIndex >= 0)) {
return true;
}
return false;
}
/**
* Register a `Component` with `videojs` given the name and the component.
*
* > NOTE: {@link Tech}s should not be registered as a `Component`. {@link Tech}s
* should be registered using {@link Tech.registerTech} or
* {@link videojs:videojs.registerTech}.
*
* > NOTE: This function can also be seen on videojs as
* {@link videojs:videojs.registerComponent}.
*
* @param {string} name
* The name of the `Component` to register.
*
* @param {Component} ComponentToRegister
* The `Component` class to register.
*
* @return {Component}
* The `Component` that was registered.
*/
static registerComponent(name, ComponentToRegister) {
if (typeof name !== 'string' || !name) {
throw new Error(`Illegal component name, "${name}"; must be a non-empty string.`);
}
const Tech = Component$1.getComponent('Tech');
// We need to make sure this check is only done if Tech has been registered.
const isTech = Tech && Tech.isTech(ComponentToRegister);
const isComp = Component$1 === ComponentToRegister || Component$1.prototype.isPrototypeOf(ComponentToRegister.prototype);
if (isTech || !isComp) {
let reason;
if (isTech) {
reason = 'techs must be registered using Tech.registerTech()';
} else {
reason = 'must be a Component subclass';
}
throw new Error(`Illegal component, "${name}"; ${reason}.`);
}
name = toTitleCase$1(name);
if (!Component$1.components_) {
Component$1.components_ = {};
}
const Player = Component$1.getComponent('Player');
if (name === 'Player' && Player && Player.players) {
const players = Player.players;
const playerNames = Object.keys(players);
// If we have players that were disposed, then their name will still be
// in Players.players. So, we must loop through and verify that the value
// for each item is null. This allows registration of the Player component
// after all players have been disposed or before any were created.
if (players && playerNames.length > 0) {
for (let i = 0; i < playerNames.length; i++) {
if (players[playerNames[i]] !== null) {
throw new Error('Can not register Player component after player has been created.');
}
}
}
}
Component$1.components_[name] = ComponentToRegister;
Component$1.components_[toLowerCase(name)] = ComponentToRegister;
return ComponentToRegister;
}
/**
* Get a `Component` based on the name it was registered with.
*
* @param {string} name
* The Name of the component to get.
*
* @return {typeof Component}
* The `Component` that got registered under the given name.
*/
static getComponent(name) {
if (!name || !Component$1.components_) {
return;
}
return Component$1.components_[name];
}
}
Component$1.registerComponent('Component', Component$1);
/**
* @file time.js
* @module time
*/
/**
* Returns the time for the specified index at the start or end
* of a TimeRange object.
*
* @typedef {Function} TimeRangeIndex
*
* @param {number} [index=0]
* The range number to return the time for.
*
* @return {number}
* The time offset at the specified index.
*
* @deprecated The index argument must be provided.
* In the future, leaving it out will throw an error.
*/
/**
* An object that contains ranges of time, which mimics {@link TimeRanges}.
*
* @typedef {Object} TimeRange
*
* @property {number} length
* The number of time ranges represented by this object.
*
* @property {module:time~TimeRangeIndex} start
* Returns the time offset at which a specified time range begins.
*
* @property {module:time~TimeRangeIndex} end
* Returns the time offset at which a specified time range ends.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges
*/
/**
* Check if any of the time ranges are over the maximum index.
*
* @private
* @param {string} fnName
* The function name to use for logging
*
* @param {number} index
* The index to check
*
* @param {number} maxIndex
* The maximum possible index
*
* @throws {Error} if the timeRanges provided are over the maxIndex
*/
function rangeCheck(fnName, index, maxIndex) {
if (typeof index !== 'number' || index < 0 || index > maxIndex) {
throw new Error(`Failed to execute '${fnName}' on 'TimeRanges': The index provided (${index}) is non-numeric or out of bounds (0-${maxIndex}).`);
}
}
/**
* Get the time for the specified index at the start or end
* of a TimeRange object.
*
* @private
* @param {string} fnName
* The function name to use for logging
*
* @param {string} valueIndex
* The property that should be used to get the time. should be
* 'start' or 'end'
*
* @param {Array} ranges
* An array of time ranges
*
* @param {Array} [rangeIndex=0]
* The index to start the search at
*
* @return {number}
* The time that offset at the specified index.
*
* @deprecated rangeIndex must be set to a value, in the future this will throw an error.
* @throws {Error} if rangeIndex is more than the length of ranges
*/
function getRange(fnName, valueIndex, ranges, rangeIndex) {
rangeCheck(fnName, rangeIndex, ranges.length - 1);
return ranges[rangeIndex][valueIndex];
}
/**
* Create a time range object given ranges of time.
*
* @private
* @param {Array} [ranges]
* An array of time ranges.
*
* @return {TimeRange}
*/
function createTimeRangesObj(ranges) {
let timeRangesObj;
if (ranges === undefined || ranges.length === 0) {
timeRangesObj = {
length: 0,
start() {
throw new Error('This TimeRanges object is empty');
},
end() {
throw new Error('This TimeRanges object is empty');
}
};
} else {
timeRangesObj = {
length: ranges.length,
start: getRange.bind(null, 'start', 0, ranges),
end: getRange.bind(null, 'end', 1, ranges)
};
}
if ((window_default()).Symbol && (window_default()).Symbol.iterator) {
timeRangesObj[(window_default()).Symbol.iterator] = () => (ranges || []).values();
}
return timeRangesObj;
}
/**
* Create a `TimeRange` object which mimics an
* {@link https://developer.mozilla.org/en-US/docs/Web/API/TimeRanges|HTML5 TimeRanges instance}.
*
* @param {number|Array[]} start
* The start of a single range (a number) or an array of ranges (an
* array of arrays of two numbers each).
*
* @param {number} end
* The end of a single range. Cannot be used with the array form of
* the `start` argument.
*
* @return {TimeRange}
*/
function createTimeRanges$1(start, end) {
if (Array.isArray(start)) {
return createTimeRangesObj(start);
} else if (start === undefined || end === undefined) {
return createTimeRangesObj();
}
return createTimeRangesObj([[start, end]]);
}
/**
* Format seconds as a time string, H:MM:SS or M:SS. Supplying a guide (in
* seconds) will force a number of leading zeros to cover the length of the
* guide.
*
* @private
* @param {number} seconds
* Number of seconds to be turned into a string
*
* @param {number} guide
* Number (in seconds) to model the string after
*
* @return {string}
* Time formatted as H:MM:SS or M:SS
*/
const defaultImplementation = function (seconds, guide) {
seconds = seconds < 0 ? 0 : seconds;
let s = Math.floor(seconds % 60);
let m = Math.floor(seconds / 60 % 60);
let h = Math.floor(seconds / 3600);
const gm = Math.floor(guide / 60 % 60);
const gh = Math.floor(guide / 3600);
// handle invalid times
if (isNaN(seconds) || seconds === Infinity) {
// '-' is false for all relational operators (e.g. <, >=) so this setting
// will add the minimum number of fields specified by the guide
h = m = s = '-';
}
// Check if we need to show hours
h = h > 0 || gh > 0 ? h + ':' : '';
// If hours are showing, we may need to add a leading zero.
// Always show at least one digit of minutes.
m = ((h || gm >= 10) && m < 10 ? '0' + m : m) + ':';
// Check if leading zero is need for seconds
s = s < 10 ? '0' + s : s;
return h + m + s;
};
// Internal pointer to the current implementation.
let implementation = defaultImplementation;
/**
* Replaces the default formatTime implementation with a custom implementation.
*
* @param {Function} customImplementation
* A function which will be used in place of the default formatTime
* implementation. Will receive the current time in seconds and the
* guide (in seconds) as arguments.
*/
function setFormatTime(customImplementation) {
implementation = customImplementation;
}
/**
* Resets formatTime to the default implementation.
*/
function resetFormatTime() {
implementation = defaultImplementation;
}
/**
* Delegates to either the default time formatting function or a custom
* function supplied via `setFormatTime`.
*
* Formats seconds as a time string (H:MM:SS or M:SS). Supplying a
* guide (in seconds) will force a number of leading zeros to cover the
* length of the guide.
*
* @example formatTime(125, 600) === "02:05"
* @param {number} seconds
* Number of seconds to be turned into a string
*
* @param {number} guide
* Number (in seconds) to model the string after
*
* @return {string}
* Time formatted as H:MM:SS or M:SS
*/
function formatTime(seconds, guide = seconds) {
return implementation(seconds, guide);
}
var Time = /*#__PURE__*/Object.freeze({
__proto__: null,
createTimeRanges: createTimeRanges$1,
createTimeRange: createTimeRanges$1,
setFormatTime: setFormatTime,
resetFormatTime: resetFormatTime,
formatTime: formatTime
});
/**
* @file buffer.js
* @module buffer
*/
/** @import { TimeRange } from './time' */
/**
* Compute the percentage of the media that has been buffered.
*
* @param {TimeRange} buffered
* The current `TimeRanges` object representing buffered time ranges
*
* @param {number} duration
* Total duration of the media
*
* @return {number}
* Percent buffered of the total duration in decimal form.
*/
function bufferedPercent(buffered, duration) {
let bufferedDuration = 0;
let start;
let end;
if (!duration) {
return 0;
}
if (!buffered || !buffered.length) {
buffered = createTimeRanges$1(0, 0);
}
for (let i = 0; i < buffered.length; i++) {
start = buffered.start(i);
end = buffered.end(i);
// buffered end can be bigger than duration by a very small fraction
if (end > duration) {
end = duration;
}
bufferedDuration += end - start;
}
return bufferedDuration / duration;
}
/**
* @file media-error.js
*/
/**
* A Custom `MediaError` class which mimics the standard HTML5 `MediaError` class.
*
* @param {number|string|Object|MediaError} value
* This can be of multiple types:
* - number: should be a standard error code
* - string: an error message (the code will be 0)
* - Object: arbitrary properties
* - `MediaError` (native): used to populate a video.js `MediaError` object
* - `MediaError` (video.js): will return itself if it's already a
* video.js `MediaError` object.
*
* @see [MediaError Spec]{@link https://dev.w3.org/html5/spec-author-view/video.html#mediaerror}
* @see [Encrypted MediaError Spec]{@link https://www.w3.org/TR/2013/WD-encrypted-media-20130510/#error-codes}
*
* @class MediaError
*/
function MediaError(value) {
// Allow redundant calls to this constructor to avoid having `instanceof`
// checks peppered around the code.
if (value instanceof MediaError) {
return value;
}
if (typeof value === 'number') {
this.code = value;
} else if (typeof value === 'string') {
// default code is zero, so this is a custom error
this.message = value;
} else if (video_es_isObject(value)) {
// We assign the `code` property manually because native `MediaError` objects
// do not expose it as an own/enumerable property of the object.
if (typeof value.code === 'number') {
this.code = value.code;
}
Object.assign(this, value);
}
if (!this.message) {
this.message = MediaError.defaultMessages[this.code] || '';
}
}
/**
* The error code that refers two one of the defined `MediaError` types
*
* @type {Number}
*/
MediaError.prototype.code = 0;
/**
* An optional message that to show with the error. Message is not part of the HTML5
* video spec but allows for more informative custom errors.
*
* @type {String}
*/
MediaError.prototype.message = '';
/**
* An optional status code that can be set by plugins to allow even more detail about
* the error. For example a plugin might provide a specific HTTP status code and an
* error message for that code. Then when the plugin gets that error this class will
* know how to display an error message for it. This allows a custom message to show
* up on the `Player` error overlay.
*
* @type {Array}
*/
MediaError.prototype.status = null;
/**
* An object containing an error type, as well as other information regarding the error.
*
* @typedef {{errorType: string, [key: string]: any}} ErrorMetadata
*/
/**
* An optional object to give more detail about the error. This can be used to give
* a higher level of specificity to an error versus the more generic MediaError codes.
* `metadata` expects an `errorType` string that should align with the values from videojs.Error.
*
* @type {ErrorMetadata}
*/
MediaError.prototype.metadata = null;
/**
* Errors indexed by the W3C standard. The order **CANNOT CHANGE**! See the
* specification listed under {@link MediaError} for more information.
*
* @enum {array}
* @readonly
* @property {string} 0 - MEDIA_ERR_CUSTOM
* @property {string} 1 - MEDIA_ERR_ABORTED
* @property {string} 2 - MEDIA_ERR_NETWORK
* @property {string} 3 - MEDIA_ERR_DECODE
* @property {string} 4 - MEDIA_ERR_SRC_NOT_SUPPORTED
* @property {string} 5 - MEDIA_ERR_ENCRYPTED
*/
MediaError.errorTypes = ['MEDIA_ERR_CUSTOM', 'MEDIA_ERR_ABORTED', 'MEDIA_ERR_NETWORK', 'MEDIA_ERR_DECODE', 'MEDIA_ERR_SRC_NOT_SUPPORTED', 'MEDIA_ERR_ENCRYPTED'];
/**
* The default `MediaError` messages based on the {@link MediaError.errorTypes}.
*
* @type {Array}
* @constant
*/
MediaError.defaultMessages = {
1: 'You aborted the media playback',
2: 'A network error caused the media download to fail part-way.',
3: 'The media playback was aborted due to a corruption problem or because the media used features your browser did not support.',
4: 'The media could not be loaded, either because the server or network failed or because the format is not supported.',
5: 'The media is encrypted and we do not have the keys to decrypt it.'
};
/**
* W3C error code for any custom error.
*
* @member MediaError#MEDIA_ERR_CUSTOM
* @constant {number}
* @default 0
*/
MediaError.MEDIA_ERR_CUSTOM = 0;
/**
* W3C error code for any custom error.
*
* @member MediaError.MEDIA_ERR_CUSTOM
* @constant {number}
* @default 0
*/
MediaError.prototype.MEDIA_ERR_CUSTOM = 0;
/**
* W3C error code for media error aborted.
*
* @member MediaError#MEDIA_ERR_ABORTED
* @constant {number}
* @default 1
*/
MediaError.MEDIA_ERR_ABORTED = 1;
/**
* W3C error code for media error aborted.
*
* @member MediaError.MEDIA_ERR_ABORTED
* @constant {number}
* @default 1
*/
MediaError.prototype.MEDIA_ERR_ABORTED = 1;
/**
* W3C error code for any network error.
*
* @member MediaError#MEDIA_ERR_NETWORK
* @constant {number}
* @default 2
*/
MediaError.MEDIA_ERR_NETWORK = 2;
/**
* W3C error code for any network error.
*
* @member MediaError.MEDIA_ERR_NETWORK
* @constant {number}
* @default 2
*/
MediaError.prototype.MEDIA_ERR_NETWORK = 2;
/**
* W3C error code for any decoding error.
*
* @member MediaError#MEDIA_ERR_DECODE
* @constant {number}
* @default 3
*/
MediaError.MEDIA_ERR_DECODE = 3;
/**
* W3C error code for any decoding error.
*
* @member MediaError.MEDIA_ERR_DECODE
* @constant {number}
* @default 3
*/
MediaError.prototype.MEDIA_ERR_DECODE = 3;
/**
* W3C error code for any time that a source is not supported.
*
* @member MediaError#MEDIA_ERR_SRC_NOT_SUPPORTED
* @constant {number}
* @default 4
*/
MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
/**
* W3C error code for any time that a source is not supported.
*
* @member MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED
* @constant {number}
* @default 4
*/
MediaError.prototype.MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
/**
* W3C error code for any time that a source is encrypted.
*
* @member MediaError#MEDIA_ERR_ENCRYPTED
* @constant {number}
* @default 5
*/
MediaError.MEDIA_ERR_ENCRYPTED = 5;
/**
* W3C error code for any time that a source is encrypted.
*
* @member MediaError.MEDIA_ERR_ENCRYPTED
* @constant {number}
* @default 5
*/
MediaError.prototype.MEDIA_ERR_ENCRYPTED = 5;
/**
* Returns whether an object is `Promise`-like (i.e. has a `then` method).
*
* @param {Object} value
* An object that may or may not be `Promise`-like.
*
* @return {boolean}
* Whether or not the object is `Promise`-like.
*/
function isPromise(value) {
return value !== undefined && value !== null && typeof value.then === 'function';
}
/**
* Silence a Promise-like object.
*
* This is useful for avoiding non-harmful, but potentially confusing "uncaught
* play promise" rejection error messages.
*
* @param {Object} value
* An object that may or may not be `Promise`-like.
*/
function silencePromise(value) {
if (isPromise(value)) {
value.then(null, e => {});
}
}
/**
* @file text-track-list-converter.js Utilities for capturing text track state and
* re-creating tracks based on a capture.
*
* @module text-track-list-converter
*/
/** @import Tech from '../tech/tech' */
/**
* Examine a single {@link TextTrack} and return a JSON-compatible javascript object that
* represents the {@link TextTrack}'s state.
*
* @param {TextTrack} track
* The text track to query.
*
* @return {Object}
* A serializable javascript representation of the TextTrack.
* @private
*/
const trackToJson = function (track) {
const ret = ['kind', 'label', 'language', 'id', 'inBandMetadataTrackDispatchType', 'mode', 'src'].reduce((acc, prop, i) => {
if (track[prop]) {
acc[prop] = track[prop];
}
return acc;
}, {
cues: track.cues && Array.prototype.map.call(track.cues, function (cue) {
return {
startTime: cue.startTime,
endTime: cue.endTime,
text: cue.text,
id: cue.id
};
})
});
return ret;
};
/**
* Examine a {@link Tech} and return a JSON-compatible javascript array that represents the
* state of all {@link TextTrack}s currently configured. The return array is compatible with
* {@link text-track-list-converter:jsonToTextTracks}.
*
* @param {Tech} tech
* The tech object to query
*
* @return {Array}
* A serializable javascript representation of the {@link Tech}s
* {@link TextTrackList}.
*/
const textTracksToJson = function (tech) {
const trackEls = tech.$$('track');
const trackObjs = Array.prototype.map.call(trackEls, t => t.track);
const tracks = Array.prototype.map.call(trackEls, function (trackEl) {
const json = trackToJson(trackEl.track);
if (trackEl.src) {
json.src = trackEl.src;
}
return json;
});
return tracks.concat(Array.prototype.filter.call(tech.textTracks(), function (track) {
return trackObjs.indexOf(track) === -1;
}).map(trackToJson));
};
/**
* Create a set of remote {@link TextTrack}s on a {@link Tech} based on an array of javascript
* object {@link TextTrack} representations.
*
* @param {Array} json
* An array of `TextTrack` representation objects, like those that would be
* produced by `textTracksToJson`.
*
* @param {Tech} tech
* The `Tech` to create the `TextTrack`s on.
*/
const jsonToTextTracks = function (json, tech) {
json.forEach(function (track) {
const addedTrack = tech.addRemoteTextTrack(track).track;
if (!track.src && track.cues) {
track.cues.forEach(cue => addedTrack.addCue(cue));
}
});
return tech.textTracks();
};
var textTrackConverter = {
textTracksToJson,
jsonToTextTracks,
trackToJson
};
/**
* @file modal-dialog.js
*/
/** @import Player from './player' */
/** @import { ContentDescriptor } from './utils/dom' */
const MODAL_CLASS_NAME = 'vjs-modal-dialog';
/**
* The `ModalDialog` displays over the video and its controls, which blocks
* interaction with the player until it is closed.
*
* Modal dialogs include a "Close" button and will close when that button
* is activated - or when ESC is pressed anywhere.
*
* @extends Component
*/
class ModalDialog extends Component$1 {
/**
* Creates an instance of this class.
*
* @param {Player} player
* The `Player` that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of player options.
*
* @param {ContentDescriptor} [options.content=undefined]
* Provide customized content for this modal.
*
* @param {string} [options.description]
* A text description for the modal, primarily for accessibility.
*
* @param {boolean} [options.fillAlways=false]
* Normally, modals are automatically filled only the first time
* they open. This tells the modal to refresh its content
* every time it opens.
*
* @param {string} [options.label]
* A text label for the modal, primarily for accessibility.
*
* @param {boolean} [options.pauseOnOpen=true]
* If `true`, playback will will be paused if playing when
* the modal opens, and resumed when it closes.
*
* @param {boolean} [options.temporary=true]
* If `true`, the modal can only be opened once; it will be
* disposed as soon as it's closed.
*
* @param {boolean} [options.uncloseable=false]
* If `true`, the user will not be able to close the modal
* through the UI in the normal ways. Programmatic closing is
* still possible.
*/
constructor(player, options) {
super(player, options);
this.handleKeyDown_ = e => this.handleKeyDown(e);
this.close_ = e => this.close(e);
this.opened_ = this.hasBeenOpened_ = this.hasBeenFilled_ = false;
this.closeable(!this.options_.uncloseable);
this.content(this.options_.content);
// Make sure the contentEl is defined AFTER any children are initialized
// because we only want the contents of the modal in the contentEl
// (not the UI elements like the close button).
this.contentEl_ = createEl('div', {
className: `${MODAL_CLASS_NAME}-content`
}, {
role: 'document'
});
this.descEl_ = createEl('p', {
className: `${MODAL_CLASS_NAME}-description vjs-control-text`,
id: this.el().getAttribute('aria-describedby')
});
textContent(this.descEl_, this.description());
this.el_.appendChild(this.descEl_);
this.el_.appendChild(this.contentEl_);
}
/**
* Create the `ModalDialog`'s DOM element
*
* @return {Element}
* The DOM element that gets created.
*/
createEl() {
return super.createEl('div', {
className: this.buildCSSClass(),
tabIndex: -1
}, {
'aria-describedby': `${this.id()}_description`,
'aria-hidden': 'true',
'aria-label': this.label(),
'role': 'dialog',
'aria-live': 'polite'
});
}
dispose() {
this.contentEl_ = null;
this.descEl_ = null;
this.previouslyActiveEl_ = null;
super.dispose();
}
/**
* Builds the default DOM `className`.
*
* @return {string}
* The DOM `className` for this object.
*/
buildCSSClass() {
return `${MODAL_CLASS_NAME} vjs-hidden ${super.buildCSSClass()}`;
}
/**
* Returns the label string for this modal. Primarily used for accessibility.
*
* @return {string}
* the localized or raw label of this modal.
*/
label() {
return this.localize(this.options_.label || 'Modal Window');
}
/**
* Returns the description string for this modal. Primarily used for
* accessibility.
*
* @return {string}
* The localized or raw description of this modal.
*/
description() {
let desc = this.options_.description || this.localize('This is a modal window.');
// Append a universal closeability message if the modal is closeable.
if (this.closeable()) {
desc += ' ' + this.localize('This modal can be closed by pressing the Escape key or activating the close button.');
}
return desc;
}
/**
* Opens the modal.
*
* @fires ModalDialog#beforemodalopen
* @fires ModalDialog#modalopen
*/
open() {
if (this.opened_) {
if (this.options_.fillAlways) {
this.fill();
}
return;
}
const player = this.player();
/**
* Fired just before a `ModalDialog` is opened.
*
* @event ModalDialog#beforemodalopen
* @type {Event}
*/
this.trigger('beforemodalopen');
this.opened_ = true;
// Fill content if the modal has never opened before and
// never been filled.
if (this.options_.fillAlways || !this.hasBeenOpened_ && !this.hasBeenFilled_) {
this.fill();
}
// If the player was playing, pause it and take note of its previously
// playing state.
this.wasPlaying_ = !player.paused();
if (this.options_.pauseOnOpen && this.wasPlaying_) {
player.pause();
}
this.on('keydown', this.handleKeyDown_);
// Hide controls and note if they were enabled.
this.hadControls_ = player.controls();
player.controls(false);
this.show();
this.conditionalFocus_();
this.el().setAttribute('aria-hidden', 'false');
/**
* Fired just after a `ModalDialog` is opened.
*
* @event ModalDialog#modalopen
* @type {Event}
*/
this.trigger('modalopen');
this.hasBeenOpened_ = true;
}
/**
* If the `ModalDialog` is currently open or closed.
*
* @param {boolean} [value]
* If given, it will open (`true`) or close (`false`) the modal.
*
* @return {boolean}
* the current open state of the modaldialog
*/
opened(value) {
if (typeof value === 'boolean') {
this[value ? 'open' : 'close']();
}
return this.opened_;
}
/**
* Closes the modal, does nothing if the `ModalDialog` is
* not open.
*
* @fires ModalDialog#beforemodalclose
* @fires ModalDialog#modalclose
*/
close() {
if (!this.opened_) {
return;
}
const player = this.player();
/**
* Fired just before a `ModalDialog` is closed.
*
* @event ModalDialog#beforemodalclose
* @type {Event}
*/
this.trigger('beforemodalclose');
this.opened_ = false;
if (this.wasPlaying_ && this.options_.pauseOnOpen) {
player.play();
}
this.off('keydown', this.handleKeyDown_);
if (this.hadControls_) {
player.controls(true);
}
this.hide();
this.el().setAttribute('aria-hidden', 'true');
/**
* Fired just after a `ModalDialog` is closed.
*
* @event ModalDialog#modalclose
* @type {Event}
*
* @property {boolean} [bubbles=true]
*/
this.trigger({
type: 'modalclose',
bubbles: true
});
this.conditionalBlur_();
if (this.options_.temporary) {
this.dispose();
}
}
/**
* Check to see if the `ModalDialog` is closeable via the UI.
*
* @param {boolean} [value]
* If given as a boolean, it will set the `closeable` option.
*
* @return {boolean}
* Returns the final value of the closable option.
*/
closeable(value) {
if (typeof value === 'boolean') {
const closeable = this.closeable_ = !!value;
let close = this.getChild('closeButton');
// If this is being made closeable and has no close button, add one.
if (closeable && !close) {
// The close button should be a child of the modal - not its
// content element, so temporarily change the content element.
const temp = this.contentEl_;
this.contentEl_ = this.el_;
close = this.addChild('closeButton', {
controlText: 'Close Modal Dialog'
});
this.contentEl_ = temp;
this.on(close, 'close', this.close_);
}
// If this is being made uncloseable and has a close button, remove it.
if (!closeable && close) {
this.off(close, 'close', this.close_);
this.removeChild(close);
close.dispose();
}
}
return this.closeable_;
}
/**
* Fill the modal's content element with the modal's "content" option.
* The content element will be emptied before this change takes place.
*/
fill() {
this.fillWith(this.content());
}
/**
* Fill the modal's content element with arbitrary content.
* The content element will be emptied before this change takes place.
*
* @fires ModalDialog#beforemodalfill
* @fires ModalDialog#modalfill
*
* @param {ContentDescriptor} [content]
* The same rules apply to this as apply to the `content` option.
*/
fillWith(content) {
const contentEl = this.contentEl();
const parentEl = contentEl.parentNode;
const nextSiblingEl = contentEl.nextSibling;
/**
* Fired just before a `ModalDialog` is filled with content.
*
* @event ModalDialog#beforemodalfill
* @type {Event}
*/
this.trigger('beforemodalfill');
this.hasBeenFilled_ = true;
// Detach the content element from the DOM before performing
// manipulation to avoid modifying the live DOM multiple times.
parentEl.removeChild(contentEl);
this.empty();
insertContent(contentEl, content);
/**
* Fired just after a `ModalDialog` is filled with content.
*
* @event ModalDialog#modalfill
* @type {Event}
*/
this.trigger('modalfill');
// Re-inject the re-filled content element.
if (nextSiblingEl) {
parentEl.insertBefore(contentEl, nextSiblingEl);
} else {
parentEl.appendChild(contentEl);
}
// make sure that the close button is last in the dialog DOM
const closeButton = this.getChild('closeButton');
if (closeButton) {
parentEl.appendChild(closeButton.el_);
}
/**
* Fired after `ModalDialog` is re-filled with content & close button is appended.
*
* @event ModalDialog#aftermodalfill
* @type {Event}
*/
this.trigger('aftermodalfill');
}
/**
* Empties the content element. This happens anytime the modal is filled.
*
* @fires ModalDialog#beforemodalempty
* @fires ModalDialog#modalempty
*/
empty() {
/**
* Fired just before a `ModalDialog` is emptied.
*
* @event ModalDialog#beforemodalempty
* @type {Event}
*/
this.trigger('beforemodalempty');
emptyEl(this.contentEl());
/**
* Fired just after a `ModalDialog` is emptied.
*
* @event ModalDialog#modalempty
* @type {Event}
*/
this.trigger('modalempty');
}
/**
* Gets or sets the modal content, which gets normalized before being
* rendered into the DOM.
*
* This does not update the DOM or fill the modal, but it is called during
* that process.
*
* @param {ContentDescriptor} [value]
* If defined, sets the internal content value to be used on the
* next call(s) to `fill`. This value is normalized before being
* inserted. To "clear" the internal content value, pass `null`.
*
* @return {ContentDescriptor}
* The current content of the modal dialog
*/
content(value) {
if (typeof value !== 'undefined') {
this.content_ = value;
}
return this.content_;
}
/**
* conditionally focus the modal dialog if focus was previously on the player.
*
* @private
*/
conditionalFocus_() {
const activeEl = (document_default()).activeElement;
const playerEl = this.player_.el_;
this.previouslyActiveEl_ = null;
if (playerEl.contains(activeEl) || playerEl === activeEl) {
this.previouslyActiveEl_ = activeEl;
this.focus();
}
}
/**
* conditionally blur the element and refocus the last focused element
*
* @private
*/
conditionalBlur_() {
if (this.previouslyActiveEl_) {
this.previouslyActiveEl_.focus();
this.previouslyActiveEl_ = null;
}
}
/**
* Keydown handler. Attached when modal is focused.
*
* @listens keydown
*/
handleKeyDown(event) {
/**
* Fired a custom keyDown event that bubbles.
*
* @event ModalDialog#modalKeydown
* @type {Event}
*/
this.trigger({
type: 'modalKeydown',
originalEvent: event,
target: this,
bubbles: true
});
// Do not allow keydowns to reach out of the modal dialog.
event.stopPropagation();
if (event.key === 'Escape' && this.closeable()) {
event.preventDefault();
this.close();
return;
}
// exit early if it isn't a tab key
if (event.key !== 'Tab') {
return;
}
const focusableEls = this.focusableEls_();
const activeEl = this.el_.querySelector(':focus');
let focusIndex;
for (let i = 0; i < focusableEls.length; i++) {
if (activeEl === focusableEls[i]) {
focusIndex = i;
break;
}
}
if ((document_default()).activeElement === this.el_) {
focusIndex = 0;
}
if (event.shiftKey && focusIndex === 0) {
focusableEls[focusableEls.length - 1].focus();
event.preventDefault();
} else if (!event.shiftKey && focusIndex === focusableEls.length - 1) {
focusableEls[0].focus();
event.preventDefault();
}
}
/**
* get all focusable elements
*
* @private
*/
focusableEls_() {
const allChildren = this.el_.querySelectorAll('*');
return Array.prototype.filter.call(allChildren, child => {
return (child instanceof (window_default()).HTMLAnchorElement || child instanceof (window_default()).HTMLAreaElement) && child.hasAttribute('href') || (child instanceof (window_default()).HTMLInputElement || child instanceof (window_default()).HTMLSelectElement || child instanceof (window_default()).HTMLTextAreaElement || child instanceof (window_default()).HTMLButtonElement) && !child.hasAttribute('disabled') || child instanceof (window_default()).HTMLIFrameElement || child instanceof (window_default()).HTMLObjectElement || child instanceof (window_default()).HTMLEmbedElement || child.hasAttribute('tabindex') && child.getAttribute('tabindex') !== -1 || child.hasAttribute('contenteditable');
});
}
}
/**
* Default options for `ModalDialog` default options.
*
* @type {Object}
* @private
*/
ModalDialog.prototype.options_ = {
pauseOnOpen: true,
temporary: true
};
Component$1.registerComponent('ModalDialog', ModalDialog);
/**
* @file track-list.js
*/
/** @import Track from './track' */
/**
* Common functionaliy between {@link TextTrackList}, {@link AudioTrackList}, and
* {@link VideoTrackList}
*
* @extends EventTarget
*/
class TrackList extends EventTarget$2 {
/**
* Create an instance of this class
*
* @param { Track[] } tracks
* A list of tracks to initialize the list with.
*
* @abstract
*/
constructor(tracks = []) {
super();
this.tracks_ = [];
for (let i = 0; i < tracks.length; i++) {
this.addTrack(tracks[i]);
}
}
/**
* The current number of `Track`s in this TrackList.
*
* @type {number}
*/
get length() {
return this.tracks_.length;
}
/**
* Add a {@link Track} to the `TrackList`
*
* @param {Track} track
* The audio, video, or text track to add to the list.
*
* @fires TrackList#addtrack
*/
addTrack(track) {
const index = this.tracks_.length;
if (!('' + index in this)) {
Object.defineProperty(this, index, {
get() {
return this.tracks_[index];
}
});
}
// Do not add duplicate tracks
if (this.tracks_.indexOf(track) === -1) {
this.tracks_.push(track);
/**
* Triggered when a track is added to a track list.
*
* @event TrackList#addtrack
* @type {Event}
* @property {Track} track
* A reference to track that was added.
*/
this.trigger({
track,
type: 'addtrack',
target: this
});
}
/**
* Triggered when a track label is changed.
*
* @event TrackList#labelchange
* @type {Event}
* @property {Track} track
* A reference to track whose label was changed.
*/
track.labelchange_ = () => {
this.trigger({
track,
type: 'labelchange',
target: this
});
};
if (isEvented(track)) {
track.addEventListener('labelchange', track.labelchange_);
}
}
/**
* Remove a {@link Track} from the `TrackList`
*
* @param {Track} rtrack
* The audio, video, or text track to remove from the list.
*
* @fires TrackList#removetrack
*/
removeTrack(rtrack) {
let track;
for (let i = 0, l = this.length; i < l; i++) {
if (this[i] === rtrack) {
track = this[i];
if (track.off) {
track.off();
}
this.tracks_.splice(i, 1);
break;
}
}
if (!track) {
return;
}
/**
* Triggered when a track is removed from track list.
*
* @event TrackList#removetrack
* @type {Event}
* @property {Track} track
* A reference to track that was removed.
*/
this.trigger({
track,
type: 'removetrack',
target: this
});
}
/**
* Get a Track from the TrackList by a tracks id
*
* @param {string} id - the id of the track to get
* @method getTrackById
* @return {Track}
* @private
*/
getTrackById(id) {
let result = null;
for (let i = 0, l = this.length; i < l; i++) {
const track = this[i];
if (track.id === id) {
result = track;
break;
}
}
return result;
}
}
/**
* Triggered when a different track is selected/enabled.
*
* @event TrackList#change
* @type {Event}
*/
/**
* Events that can be called with on + eventName. See {@link EventHandler}.
*
* @property {Object} TrackList#allowedEvents_
* @protected
*/
TrackList.prototype.allowedEvents_ = {
change: 'change',
addtrack: 'addtrack',
removetrack: 'removetrack',
labelchange: 'labelchange'
};
// emulate attribute EventHandler support to allow for feature detection
for (const event in TrackList.prototype.allowedEvents_) {
TrackList.prototype['on' + event] = null;
}
/**
* @file audio-track-list.js
*/
/** @import AudioTrack from './audio-track' */
/**
* Anywhere we call this function we diverge from the spec
* as we only support one enabled audiotrack at a time
*
* @param {AudioTrackList} list
* list to work on
*
* @param {AudioTrack} track
* The track to skip
*
* @private
*/
const disableOthers$1 = function (list, track) {
for (let i = 0; i < list.length; i++) {
if (!Object.keys(list[i]).length || track.id === list[i].id) {
continue;
}
// another audio track is enabled, disable it
list[i].enabled = false;
}
};
/**
* The current list of {@link AudioTrack} for a media file.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotracklist}
* @extends TrackList
*/
class AudioTrackList extends TrackList {
/**
* Create an instance of this class.
*
* @param {AudioTrack[]} [tracks=[]]
* A list of `AudioTrack` to instantiate the list with.
*/
constructor(tracks = []) {
// make sure only 1 track is enabled
// sorted from last index to first index
for (let i = tracks.length - 1; i >= 0; i--) {
if (tracks[i].enabled) {
disableOthers$1(tracks, tracks[i]);
break;
}
}
super(tracks);
this.changing_ = false;
}
/**
* Add an {@link AudioTrack} to the `AudioTrackList`.
*
* @param {AudioTrack} track
* The AudioTrack to add to the list
*
* @fires TrackList#addtrack
*/
addTrack(track) {
if (track.enabled) {
disableOthers$1(this, track);
}
super.addTrack(track);
// native tracks don't have this
if (!track.addEventListener) {
return;
}
track.enabledChange_ = () => {
// when we are disabling other tracks (since we don't support
// more than one track at a time) we will set changing_
// to true so that we don't trigger additional change events
if (this.changing_) {
return;
}
this.changing_ = true;
disableOthers$1(this, track);
this.changing_ = false;
this.trigger('change');
};
/**
* @listens AudioTrack#enabledchange
* @fires TrackList#change
*/
track.addEventListener('enabledchange', track.enabledChange_);
}
removeTrack(rtrack) {
super.removeTrack(rtrack);
if (rtrack.removeEventListener && rtrack.enabledChange_) {
rtrack.removeEventListener('enabledchange', rtrack.enabledChange_);
rtrack.enabledChange_ = null;
}
}
}
/**
* @file video-track-list.js
*/
/** @import VideoTrack from './video-track' */
/**
* Un-select all other {@link VideoTrack}s that are selected.
*
* @param {VideoTrackList} list
* list to work on
*
* @param {VideoTrack} track
* The track to skip
*
* @private
*/
const disableOthers = function (list, track) {
for (let i = 0; i < list.length; i++) {
if (!Object.keys(list[i]).length || track.id === list[i].id) {
continue;
}
// another video track is enabled, disable it
list[i].selected = false;
}
};
/**
* The current list of {@link VideoTrack} for a video.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotracklist}
* @extends TrackList
*/
class VideoTrackList extends TrackList {
/**
* Create an instance of this class.
*
* @param {VideoTrack[]} [tracks=[]]
* A list of `VideoTrack` to instantiate the list with.
*/
constructor(tracks = []) {
// make sure only 1 track is enabled
// sorted from last index to first index
for (let i = tracks.length - 1; i >= 0; i--) {
if (tracks[i].selected) {
disableOthers(tracks, tracks[i]);
break;
}
}
super(tracks);
this.changing_ = false;
/**
* @member {number} VideoTrackList#selectedIndex
* The current index of the selected {@link VideoTrack`}.
*/
Object.defineProperty(this, 'selectedIndex', {
get() {
for (let i = 0; i < this.length; i++) {
if (this[i].selected) {
return i;
}
}
return -1;
},
set() {}
});
}
/**
* Add a {@link VideoTrack} to the `VideoTrackList`.
*
* @param {VideoTrack} track
* The VideoTrack to add to the list
*
* @fires TrackList#addtrack
*/
addTrack(track) {
if (track.selected) {
disableOthers(this, track);
}
super.addTrack(track);
// native tracks don't have this
if (!track.addEventListener) {
return;
}
track.selectedChange_ = () => {
if (this.changing_) {
return;
}
this.changing_ = true;
disableOthers(this, track);
this.changing_ = false;
this.trigger('change');
};
/**
* @listens VideoTrack#selectedchange
* @fires TrackList#change
*/
track.addEventListener('selectedchange', track.selectedChange_);
}
removeTrack(rtrack) {
super.removeTrack(rtrack);
if (rtrack.removeEventListener && rtrack.selectedChange_) {
rtrack.removeEventListener('selectedchange', rtrack.selectedChange_);
rtrack.selectedChange_ = null;
}
}
}
/**
* @file text-track-list.js
*/
/** @import TextTrack from './text-track' */
/**
* The current list of {@link TextTrack} for a media file.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttracklist}
* @extends TrackList
*/
class TextTrackList extends TrackList {
/**
* Add a {@link TextTrack} to the `TextTrackList`
*
* @param {TextTrack} track
* The text track to add to the list.
*
* @fires TrackList#addtrack
*/
addTrack(track) {
super.addTrack(track);
if (!this.queueChange_) {
this.queueChange_ = () => this.queueTrigger('change');
}
if (!this.triggerSelectedlanguagechange) {
this.triggerSelectedlanguagechange_ = () => this.trigger('selectedlanguagechange');
}
/**
* @listens TextTrack#modechange
* @fires TrackList#change
*/
track.addEventListener('modechange', this.queueChange_);
const nonLanguageTextTrackKind = ['metadata', 'chapters'];
if (nonLanguageTextTrackKind.indexOf(track.kind) === -1) {
track.addEventListener('modechange', this.triggerSelectedlanguagechange_);
}
}
removeTrack(rtrack) {
super.removeTrack(rtrack);
// manually remove the event handlers we added
if (rtrack.removeEventListener) {
if (this.queueChange_) {
rtrack.removeEventListener('modechange', this.queueChange_);
}
if (this.selectedlanguagechange_) {
rtrack.removeEventListener('modechange', this.triggerSelectedlanguagechange_);
}
}
}
/**
* Creates a serializable array of objects that contains serialized copies
* of each text track.
*
* @return {Object[]} A serializable list of objects for the text track list
*/
toJSON() {
return this.tracks_.map(track => track.toJSON());
}
}
/**
* @file html-track-element-list.js
*/
/**
* The current list of {@link HtmlTrackElement}s.
*/
class HtmlTrackElementList {
/**
* Create an instance of this class.
*
* @param {HtmlTrackElement[]} [tracks=[]]
* A list of `HtmlTrackElement` to instantiate the list with.
*/
constructor(trackElements = []) {
this.trackElements_ = [];
/**
* @memberof HtmlTrackElementList
* @member {number} length
* The current number of `Track`s in the this Trackist.
* @instance
*/
Object.defineProperty(this, 'length', {
get() {
return this.trackElements_.length;
}
});
for (let i = 0, length = trackElements.length; i < length; i++) {
this.addTrackElement_(trackElements[i]);
}
}
/**
* Add an {@link HtmlTrackElement} to the `HtmlTrackElementList`
*
* @param {HtmlTrackElement} trackElement
* The track element to add to the list.
*
* @private
*/
addTrackElement_(trackElement) {
const index = this.trackElements_.length;
if (!('' + index in this)) {
Object.defineProperty(this, index, {
get() {
return this.trackElements_[index];
}
});
}
// Do not add duplicate elements
if (this.trackElements_.indexOf(trackElement) === -1) {
this.trackElements_.push(trackElement);
}
}
/**
* Get an {@link HtmlTrackElement} from the `HtmlTrackElementList` given an
* {@link TextTrack}.
*
* @param {TextTrack} track
* The track associated with a track element.
*
* @return {HtmlTrackElement|undefined}
* The track element that was found or undefined.
*
* @private
*/
getTrackElementByTrack_(track) {
let trackElement_;
for (let i = 0, length = this.trackElements_.length; i < length; i++) {
if (track === this.trackElements_[i].track) {
trackElement_ = this.trackElements_[i];
break;
}
}
return trackElement_;
}
/**
* Remove a {@link HtmlTrackElement} from the `HtmlTrackElementList`
*
* @param {HtmlTrackElement} trackElement
* The track element to remove from the list.
*
* @private
*/
removeTrackElement_(trackElement) {
for (let i = 0, length = this.trackElements_.length; i < length; i++) {
if (trackElement === this.trackElements_[i]) {
if (this.trackElements_[i].track && typeof this.trackElements_[i].track.off === 'function') {
this.trackElements_[i].track.off();
}
if (typeof this.trackElements_[i].off === 'function') {
this.trackElements_[i].off();
}
this.trackElements_.splice(i, 1);
break;
}
}
}
}
/**
* @file text-track-cue-list.js
*/
/**
* @typedef {Object} TextTrackCueList~TextTrackCue
*
* @property {string} id
* The unique id for this text track cue
*
* @property {number} startTime
* The start time for this text track cue
*
* @property {number} endTime
* The end time for this text track cue
*
* @property {boolean} pauseOnExit
* Pause when the end time is reached if true.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcue}
*/
/**
* A List of TextTrackCues.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackcuelist}
*/
class TextTrackCueList {
/**
* Create an instance of this class..
*
* @param {Array} cues
* A list of cues to be initialized with
*/
constructor(cues) {
TextTrackCueList.prototype.setCues_.call(this, cues);
/**
* @memberof TextTrackCueList
* @member {number} length
* The current number of `TextTrackCue`s in the TextTrackCueList.
* @instance
*/
Object.defineProperty(this, 'length', {
get() {
return this.length_;
}
});
}
/**
* A setter for cues in this list. Creates getters
* an an index for the cues.
*
* @param {Array} cues
* An array of cues to set
*
* @private
*/
setCues_(cues) {
const oldLength = this.length || 0;
let i = 0;
const l = cues.length;
this.cues_ = cues;
this.length_ = cues.length;
const defineProp = function (index) {
if (!('' + index in this)) {
Object.defineProperty(this, '' + index, {
get() {
return this.cues_[index];
}
});
}
};
if (oldLength < l) {
i = oldLength;
for (; i < l; i++) {
defineProp.call(this, i);
}
}
}
/**
* Get a `TextTrackCue` that is currently in the `TextTrackCueList` by id.
*
* @param {string} id
* The id of the cue that should be searched for.
*
* @return {TextTrackCueList~TextTrackCue|null}
* A single cue or null if none was found.
*/
getCueById(id) {
let result = null;
for (let i = 0, l = this.length; i < l; i++) {
const cue = this[i];
if (cue.id === id) {
result = cue;
break;
}
}
return result;
}
}
/**
* @file track-kinds.js
*/
/**
* All possible `VideoTrackKind`s
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-videotrack-kind
* @typedef VideoTrack~Kind
* @enum
*/
const VideoTrackKind = {
alternative: 'alternative',
captions: 'captions',
main: 'main',
sign: 'sign',
subtitles: 'subtitles',
commentary: 'commentary'
};
/**
* All possible `AudioTrackKind`s
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-audiotrack-kind
* @typedef AudioTrack~Kind
* @enum
*/
const AudioTrackKind = {
'alternative': 'alternative',
'descriptions': 'descriptions',
'main': 'main',
'main-desc': 'main-desc',
'translation': 'translation',
'commentary': 'commentary'
};
/**
* All possible `TextTrackKind`s
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#dom-texttrack-kind
* @typedef TextTrack~Kind
* @enum
*/
const TextTrackKind = {
subtitles: 'subtitles',
captions: 'captions',
descriptions: 'descriptions',
chapters: 'chapters',
metadata: 'metadata'
};
/**
* All possible `TextTrackMode`s
*
* @see https://html.spec.whatwg.org/multipage/embedded-content.html#texttrackmode
* @typedef TextTrack~Mode
* @enum
*/
const TextTrackMode = {
disabled: 'disabled',
hidden: 'hidden',
showing: 'showing'
};
/**
* @file track.js
*/
/**
* A Track class that contains all of the common functionality for {@link AudioTrack},
* {@link VideoTrack}, and {@link TextTrack}.
*
* > Note: This class should not be used directly
*
* @see {@link https://html.spec.whatwg.org/multipage/embedded-content.html}
* @extends EventTarget
* @abstract
*/
class Track extends EventTarget$2 {
/**
* Create an instance of this class.
*
* @param {Object} [options={}]
* Object of option names and values
*
* @param {string} [options.kind='']
* A valid kind for the track type you are creating.
*
* @param {string} [options.id='vjs_track_' + Guid.newGUID()]
* A unique id for this AudioTrack.
*
* @param {string} [options.label='']
* The menu label for this track.
*
* @param {string} [options.language='']
* A valid two character language code.
*
* @abstract
*/
constructor(options = {}) {
super();
const trackProps = {
id: options.id || 'vjs_track_' + newGUID(),
kind: options.kind || '',
language: options.language || ''
};
let label = options.label || '';
/**
* @memberof Track
* @member {string} id
* The id of this track. Cannot be changed after creation.
* @instance
*
* @readonly
*/
/**
* @memberof Track
* @member {string} kind
* The kind of track that this is. Cannot be changed after creation.
* @instance
*
* @readonly
*/
/**
* @memberof Track
* @member {string} language
* The two letter language code for this track. Cannot be changed after
* creation.
* @instance
*
* @readonly
*/
for (const key in trackProps) {
Object.defineProperty(this, key, {
get() {
return trackProps[key];
},
set() {}
});
}
/**
* @memberof Track
* @member {string} label
* The label of this track. Cannot be changed after creation.
* @instance
*
* @fires Track#labelchange
*/
Object.defineProperty(this, 'label', {
get() {
return label;
},
set(newLabel) {
if (newLabel !== label) {
label = newLabel;
/**
* An event that fires when label changes on this track.
*
* > Note: This is not part of the spec!
*
* @event Track#labelchange
* @type {Event}
*/
this.trigger('labelchange');
}
}
});
}
}
/**
* @file url.js
* @module url
*/
/**
* Resolve and parse the elements of a URL.
*
* @function
* @param {string} url
* The url to parse
*
* @return {URL}
* An object of url details
*/
const parseUrl = function (url) {
return new URL(url, (document_default()).baseURI);
};
/**
* Get absolute version of relative URL.
*
* @function
* @param {string} url
* URL to make absolute
*
* @return {string}
* Absolute URL
*/
const getAbsoluteURL = function (url) {
return new URL(url, (document_default()).baseURI).href;
};
/**
* Returns the extension of the passed file name. It will return an empty string
* if passed an invalid path.
*
* @function
* @param {string} path
* The fileName path like '/path/to/file.mp4'
*
* @return {string}
* The extension in lower case or an empty string if no
* extension could be found.
*/
const getFileExtension = function (path) {
if (typeof path === 'string') {
const cleanPath = path.split('?')[0].replace(/\/+$/, '');
const match = cleanPath.match(/\.([^.\/]+)$/);
return match ? match[1].toLowerCase() : '';
}
return '';
};
/**
* Returns whether the url passed is a cross domain request or not.
*
* @function
* @param {string} url
* The url to check.
*
* @param {URL} [winLoc]
* the domain to check the url against, defaults to window.location
*
* @return {boolean}
* Whether it is a cross domain request or not.
*/
const isCrossOrigin = function (url, winLoc = (window_default()).location) {
return parseUrl(url).origin !== winLoc.origin;
};
var Url = /*#__PURE__*/Object.freeze({
__proto__: null,
parseUrl: parseUrl,
getAbsoluteURL: getAbsoluteURL,
getFileExtension: getFileExtension,
isCrossOrigin: isCrossOrigin
});
/**
* @file text-track.js
*/
/** @import Tech from '../tech/tech' */
/**
* Takes a webvtt file contents and parses it into cues
*
* @param {string} srcContent
* webVTT file contents
*
* @param {TextTrack} track
* TextTrack to add cues to. Cues come from the srcContent.
*
* @private
*/
const parseCues = function (srcContent, track) {
const parser = new (window_default()).WebVTT.Parser((window_default()), (window_default()).vttjs, window_default().WebVTT.StringDecoder());
const errors = [];
parser.oncue = function (cue) {
track.addCue(cue);
};
parser.onparsingerror = function (error) {
errors.push(error);
};
parser.onflush = function () {
track.trigger({
type: 'loadeddata',
target: track
});
};
parser.parse(srcContent);
if (errors.length > 0) {
if ((window_default()).console && (window_default()).console.groupCollapsed) {
window_default().console.groupCollapsed(`Text Track parsing errors for ${track.src}`);
}
errors.forEach(error => log$1.error(error));
if ((window_default()).console && (window_default()).console.groupEnd) {
window_default().console.groupEnd();
}
}
parser.flush();
};
/**
* Load a `TextTrack` from a specified url.
*
* @param {string} src
* Url to load track from.
*
* @param {TextTrack} track
* Track to add cues to. Comes from the content at the end of `url`.
*
* @private
*/
const loadTrack = function (src, track) {
const opts = {
uri: src
};
const crossOrigin = isCrossOrigin(src);
if (crossOrigin) {
opts.cors = crossOrigin;
}
const withCredentials = track.tech_.crossOrigin() === 'use-credentials';
if (withCredentials) {
opts.withCredentials = withCredentials;
}
lib_default()(opts, bind_(this, function (err, response, responseBody) {
if (err) {
return log$1.error(err, response);
}
track.loaded_ = true;
// Make sure that vttjs has loaded, otherwise, wait till it finished loading
// NOTE: this is only used for the alt/video.novtt.js build
if (typeof (window_default()).WebVTT !== 'function') {
if (track.tech_) {
// to prevent use before define eslint error, we define loadHandler
// as a let here
track.tech_.any(['vttjsloaded', 'vttjserror'], event => {
if (event.type === 'vttjserror') {
log$1.error(`vttjs failed to load, stopping trying to process ${track.src}`);
return;
}
return parseCues(responseBody, track);
});
}
} else {
parseCues(responseBody, track);
}
}));
};
/**
* A representation of a single `TextTrack`.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#texttrack}
* @extends Track
*/
class TextTrack extends Track {
/**
* Create an instance of this class.
*
* @param {Object} options={}
* Object of option names and values
*
* @param {Tech} options.tech
* A reference to the tech that owns this TextTrack.
*
* @param {TextTrack~Kind} [options.kind='subtitles']
* A valid text track kind.
*
* @param {TextTrack~Mode} [options.mode='disabled']
* A valid text track mode.
*
* @param {string} [options.id='vjs_track_' + Guid.newGUID()]
* A unique id for this TextTrack.
*
* @param {string} [options.label='']
* The menu label for this track.
*
* @param {string} [options.language='']
* A valid two character language code.
*
* @param {string} [options.srclang='']
* A valid two character language code. An alternative, but deprioritized
* version of `options.language`
*
* @param {string} [options.src]
* A url to TextTrack cues.
*
* @param {boolean} [options.default]
* If this track should default to on or off.
*/
constructor(options = {}) {
if (!options.tech) {
throw new Error('A tech was not provided.');
}
const settings = merge$1(options, {
kind: TextTrackKind[options.kind] || 'subtitles',
language: options.language || options.srclang || ''
});
let mode = TextTrackMode[settings.mode] || 'disabled';
const default_ = settings.default;
if (settings.kind === 'metadata' || settings.kind === 'chapters') {
mode = 'hidden';
}
super(settings);
this.tech_ = settings.tech;
this.cues_ = [];
this.activeCues_ = [];
this.preload_ = this.tech_.preloadTextTracks !== false;
const cues = new TextTrackCueList(this.cues_);
const activeCues = new TextTrackCueList(this.activeCues_);
let changed = false;
this.timeupdateHandler = bind_(this, function (event = {}) {
if (this.tech_.isDisposed()) {
return;
}
if (!this.tech_.isReady_) {
if (event.type !== 'timeupdate') {
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
}
return;
}
// Accessing this.activeCues for the side-effects of updating itself
// due to its nature as a getter function. Do not remove or cues will
// stop updating!
// Use the setter to prevent deletion from uglify (pure_getters rule)
this.activeCues = this.activeCues;
if (changed) {
this.trigger('cuechange');
changed = false;
}
if (event.type !== 'timeupdate') {
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
}
});
const disposeHandler = () => {
this.stopTracking();
};
this.tech_.one('dispose', disposeHandler);
if (mode !== 'disabled') {
this.startTracking();
}
Object.defineProperties(this, {
/**
* @memberof TextTrack
* @member {boolean} default
* If this track was set to be on or off by default. Cannot be changed after
* creation.
* @instance
*
* @readonly
*/
default: {
get() {
return default_;
},
set() {}
},
/**
* @memberof TextTrack
* @member {string} mode
* Set the mode of this TextTrack to a valid {@link TextTrack~Mode}. Will
* not be set if setting to an invalid mode.
* @instance
*
* @fires TextTrack#modechange
*/
mode: {
get() {
return mode;
},
set(newMode) {
if (!TextTrackMode[newMode]) {
return;
}
if (mode === newMode) {
return;
}
mode = newMode;
if (!this.preload_ && mode !== 'disabled' && this.cues.length === 0) {
// On-demand load.
loadTrack(this.src, this);
}
this.stopTracking();
if (mode !== 'disabled') {
this.startTracking();
}
/**
* An event that fires when mode changes on this track. This allows
* the TextTrackList that holds this track to act accordingly.
*
* > Note: This is not part of the spec!
*
* @event TextTrack#modechange
* @type {Event}
*/
this.trigger('modechange');
}
},
/**
* @memberof TextTrack
* @member {TextTrackCueList} cues
* The text track cue list for this TextTrack.
* @instance
*/
cues: {
get() {
if (!this.loaded_) {
return null;
}
return cues;
},
set() {}
},
/**
* @memberof TextTrack
* @member {TextTrackCueList} activeCues
* The list text track cues that are currently active for this TextTrack.
* @instance
*/
activeCues: {
get() {
if (!this.loaded_) {
return null;
}
// nothing to do
if (this.cues.length === 0) {
return activeCues;
}
const ct = this.tech_.currentTime();
const active = [];
for (let i = 0, l = this.cues.length; i < l; i++) {
const cue = this.cues[i];
if (cue.startTime <= ct && cue.endTime >= ct) {
active.push(cue);
}
}
changed = false;
if (active.length !== this.activeCues_.length) {
changed = true;
} else {
for (let i = 0; i < active.length; i++) {
if (this.activeCues_.indexOf(active[i]) === -1) {
changed = true;
}
}
}
this.activeCues_ = active;
activeCues.setCues_(this.activeCues_);
return activeCues;
},
// /!\ Keep this setter empty (see the timeupdate handler above)
set() {}
}
});
if (settings.src) {
this.src = settings.src;
if (!this.preload_) {
// Tracks will load on-demand.
// Act like we're loaded for other purposes.
this.loaded_ = true;
}
if (this.preload_ || settings.kind !== 'subtitles' && settings.kind !== 'captions') {
loadTrack(this.src, this);
}
} else {
this.loaded_ = true;
}
}
startTracking() {
// More precise cues based on requestVideoFrameCallback with a requestAnimationFram fallback
this.rvf_ = this.tech_.requestVideoFrameCallback(this.timeupdateHandler);
// Also listen to timeupdate in case rVFC/rAF stops (window in background, audio in video el)
this.tech_.on('timeupdate', this.timeupdateHandler);
}
stopTracking() {
if (this.rvf_) {
this.tech_.cancelVideoFrameCallback(this.rvf_);
this.rvf_ = undefined;
}
this.tech_.off('timeupdate', this.timeupdateHandler);
}
/**
* Add a cue to the internal list of cues.
*
* @param {TextTrack~Cue} cue
* The cue to add to our internal list
*/
addCue(originalCue) {
let cue = originalCue;
// Testing if the cue is a VTTCue in a way that survives minification
if (!('getCueAsHTML' in cue)) {
cue = new (window_default()).vttjs.VTTCue(originalCue.startTime, originalCue.endTime, originalCue.text);
for (const prop in originalCue) {
if (!(prop in cue)) {
cue[prop] = originalCue[prop];
}
}
// make sure that `id` is copied over
cue.id = originalCue.id;
cue.originalCue_ = originalCue;
}
const tracks = this.tech_.textTracks();
for (let i = 0; i < tracks.length; i++) {
if (tracks[i] !== this) {
tracks[i].removeCue(cue);
}
}
this.cues_.push(cue);
this.cues.setCues_(this.cues_);
}
/**
* Creates a copy of the text track and makes it serializable
* by removing circular dependencies.
*
* @return {Object} The track information as a serializable object
*/
toJSON() {
return textTrackConverter.trackToJson(this);
}
/**
* Remove a cue from our internal list
*
* @param {TextTrack~Cue} removeCue
* The cue to remove from our internal list
*/
removeCue(removeCue) {
let i = this.cues_.length;
while (i--) {
const cue = this.cues_[i];
if (cue === removeCue || cue.originalCue_ && cue.originalCue_ === removeCue) {
this.cues_.splice(i, 1);
this.cues.setCues_(this.cues_);
break;
}
}
}
}
/**
* cuechange - One or more cues in the track have become active or stopped being active.
*
* @protected
*/
TextTrack.prototype.allowedEvents_ = {
cuechange: 'cuechange'
};
/**
* A representation of a single `AudioTrack`. If it is part of an {@link AudioTrackList}
* only one `AudioTrack` in the list will be enabled at a time.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#audiotrack}
* @extends Track
*/
class AudioTrack extends Track {
/**
* Create an instance of this class.
*
* @param {Object} [options={}]
* Object of option names and values
*
* @param {AudioTrack~Kind} [options.kind='']
* A valid audio track kind
*
* @param {string} [options.id='vjs_track_' + Guid.newGUID()]
* A unique id for this AudioTrack.
*
* @param {string} [options.label='']
* The menu label for this track.
*
* @param {string} [options.language='']
* A valid two character language code.
*
* @param {boolean} [options.enabled]
* If this track is the one that is currently playing. If this track is part of
* an {@link AudioTrackList}, only one {@link AudioTrack} will be enabled.
*/
constructor(options = {}) {
const settings = merge$1(options, {
kind: AudioTrackKind[options.kind] || ''
});
super(settings);
let enabled = false;
/**
* @memberof AudioTrack
* @member {boolean} enabled
* If this `AudioTrack` is enabled or not. When setting this will
* fire {@link AudioTrack#enabledchange} if the state of enabled is changed.
* @instance
*
* @fires VideoTrack#selectedchange
*/
Object.defineProperty(this, 'enabled', {
get() {
return enabled;
},
set(newEnabled) {
// an invalid or unchanged value
if (typeof newEnabled !== 'boolean' || newEnabled === enabled) {
return;
}
enabled = newEnabled;
/**
* An event that fires when enabled changes on this track. This allows
* the AudioTrackList that holds this track to act accordingly.
*
* > Note: This is not part of the spec! Native tracks will do
* this internally without an event.
*
* @event AudioTrack#enabledchange
* @type {Event}
*/
this.trigger('enabledchange');
}
});
// if the user sets this track to selected then
// set selected to that true value otherwise
// we keep it false
if (settings.enabled) {
this.enabled = settings.enabled;
}
this.loaded_ = true;
}
}
/**
* A representation of a single `VideoTrack`.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#videotrack}
* @extends Track
*/
class VideoTrack extends Track {
/**
* Create an instance of this class.
*
* @param {Object} [options={}]
* Object of option names and values
*
* @param {string} [options.kind='']
* A valid {@link VideoTrack~Kind}
*
* @param {string} [options.id='vjs_track_' + Guid.newGUID()]
* A unique id for this AudioTrack.
*
* @param {string} [options.label='']
* The menu label for this track.
*
* @param {string} [options.language='']
* A valid two character language code.
*
* @param {boolean} [options.selected]
* If this track is the one that is currently playing.
*/
constructor(options = {}) {
const settings = merge$1(options, {
kind: VideoTrackKind[options.kind] || ''
});
super(settings);
let selected = false;
/**
* @memberof VideoTrack
* @member {boolean} selected
* If this `VideoTrack` is selected or not. When setting this will
* fire {@link VideoTrack#selectedchange} if the state of selected changed.
* @instance
*
* @fires VideoTrack#selectedchange
*/
Object.defineProperty(this, 'selected', {
get() {
return selected;
},
set(newSelected) {
// an invalid or unchanged value
if (typeof newSelected !== 'boolean' || newSelected === selected) {
return;
}
selected = newSelected;
/**
* An event that fires when selected changes on this track. This allows
* the VideoTrackList that holds this track to act accordingly.
*
* > Note: This is not part of the spec! Native tracks will do
* this internally without an event.
*
* @event VideoTrack#selectedchange
* @type {Event}
*/
this.trigger('selectedchange');
}
});
// if the user sets this track to selected then
// set selected to that true value otherwise
// we keep it false
if (settings.selected) {
this.selected = settings.selected;
}
}
}
/**
* @file html-track-element.js
*/
/** @import Tech from '../tech/tech' */
/**
* A single track represented in the DOM.
*
* @see [Spec]{@link https://html.spec.whatwg.org/multipage/embedded-content.html#htmltrackelement}
* @extends EventTarget
*/
class HTMLTrackElement extends EventTarget$2 {
/**
* Create an instance of this class.
*
* @param {Object} options={}
* Object of option names and values
*
* @param {Tech} options.tech
* A reference to the tech that owns this HTMLTrackElement.
*
* @param {TextTrack~Kind} [options.kind='subtitles']
* A valid text track kind.
*
* @param {TextTrack~Mode} [options.mode='disabled']
* A valid text track mode.
*
* @param {string} [options.id='vjs_track_' + Guid.newGUID()]
* A unique id for this TextTrack.
*
* @param {string} [options.label='']
* The menu label for this track.
*
* @param {string} [options.language='']
* A valid two character language code.
*
* @param {string} [options.srclang='']
* A valid two character language code. An alternative, but deprioritized
* version of `options.language`
*
* @param {string} [options.src]
* A url to TextTrack cues.
*
* @param {boolean} [options.default]
* If this track should default to on or off.
*/
constructor(options = {}) {
super();
let readyState;
const track = new TextTrack(options);
this.kind = track.kind;
this.src = track.src;
this.srclang = track.language;
this.label = track.label;
this.default = track.default;
Object.defineProperties(this, {
/**
* @memberof HTMLTrackElement
* @member {HTMLTrackElement~ReadyState} readyState
* The current ready state of the track element.
* @instance
*/
readyState: {
get() {
return readyState;
}
},
/**
* @memberof HTMLTrackElement
* @member {TextTrack} track
* The underlying TextTrack object.
* @instance
*
*/
track: {
get() {
return track;
}
}
});
readyState = HTMLTrackElement.NONE;
/**
* @listens TextTrack#loadeddata
* @fires HTMLTrackElement#load
*/
track.addEventListener('loadeddata', () => {
readyState = HTMLTrackElement.LOADED;
this.trigger({
type: 'load',
target: this
});
});
}
}
/**
* @protected
*/
HTMLTrackElement.prototype.allowedEvents_ = {
load: 'load'
};
/**
* The text track not loaded state.
*
* @type {number}
* @static
*/
HTMLTrackElement.NONE = 0;
/**
* The text track loading state.
*
* @type {number}
* @static
*/
HTMLTrackElement.LOADING = 1;
/**
* The text track loaded state.
*
* @type {number}
* @static
*/
HTMLTrackElement.LOADED = 2;
/**
* The text track failed to load state.
*
* @type {number}
* @static
*/
HTMLTrackElement.ERROR = 3;
/*
* This file contains all track properties that are used in
* player.js, tech.js, html5.js and possibly other techs in the future.
*/
const NORMAL = {
audio: {
ListClass: AudioTrackList,
TrackClass: AudioTrack,
capitalName: 'Audio'
},
video: {
ListClass: VideoTrackList,
TrackClass: VideoTrack,
capitalName: 'Video'
},
text: {
ListClass: TextTrackList,
TrackClass: TextTrack,
capitalName: 'Text'
}
};
Object.keys(NORMAL).forEach(function (type) {
NORMAL[type].getterName = `${type}Tracks`;
NORMAL[type].privateName = `${type}Tracks_`;
});
const REMOTE = {
remoteText: {
ListClass: TextTrackList,
TrackClass: TextTrack,
capitalName: 'RemoteText',
getterName: 'remoteTextTracks',
privateName: 'remoteTextTracks_'
},
remoteTextEl: {
ListClass: HtmlTrackElementList,
TrackClass: HTMLTrackElement,
capitalName: 'RemoteTextTrackEls',
getterName: 'remoteTextTrackEls',
privateName: 'remoteTextTrackEls_'
}
};
const ALL = Object.assign({}, NORMAL, REMOTE);
REMOTE.names = Object.keys(REMOTE);
NORMAL.names = Object.keys(NORMAL);
ALL.names = [].concat(REMOTE.names).concat(NORMAL.names);
/**
* @file tech.js
*/
/** @import { TimeRange } from '../utils/time' */
/**
* An Object containing a structure like: `{src: 'url', type: 'mimetype'}` or string
* that just contains the src url alone.
* * `var SourceObject = {src: 'http://ex.com/video.mp4', type: 'video/mp4'};`
* `var SourceString = 'http://example.com/some-video.mp4';`
*
* @typedef {Object|string} SourceObject
*
* @property {string} src
* The url to the source
*
* @property {string} type
* The mime type of the source
*/
/**
* A function used by {@link Tech} to create a new {@link TextTrack}.
*
* @private
*
* @param {Tech} self
* An instance of the Tech class.
*
* @param {string} kind
* `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
*
* @param {string} [label]
* Label to identify the text track
*
* @param {string} [language]
* Two letter language abbreviation
*
* @param {Object} [options={}]
* An object with additional text track options
*
* @return {TextTrack}
* The text track that was created.
*/
function createTrackHelper(self, kind, label, language, options = {}) {
const tracks = self.textTracks();
options.kind = kind;
if (label) {
options.label = label;
}
if (language) {
options.language = language;
}
options.tech = self;
const track = new ALL.text.TrackClass(options);
tracks.addTrack(track);
return track;
}
/**
* This is the base class for media playback technology controllers, such as
* {@link HTML5}
*
* @extends Component
*/
class Tech extends Component$1 {
/**
* Create an instance of this Tech.
*
* @param {Object} [options]
* The key/value store of player options.
*
* @param {Function} [ready]
* Callback function to call when the `HTML5` Tech is ready.
*/
constructor(options = {}, ready = function () {}) {
// we don't want the tech to report user activity automatically.
// This is done manually in addControlsListeners
options.reportTouchActivity = false;
super(null, options, ready);
this.onDurationChange_ = e => this.onDurationChange(e);
this.trackProgress_ = e => this.trackProgress(e);
this.trackCurrentTime_ = e => this.trackCurrentTime(e);
this.stopTrackingCurrentTime_ = e => this.stopTrackingCurrentTime(e);
this.disposeSourceHandler_ = e => this.disposeSourceHandler(e);
this.queuedHanders_ = new Set();
// keep track of whether the current source has played at all to
// implement a very limited played()
this.hasStarted_ = false;
this.on('playing', function () {
this.hasStarted_ = true;
});
this.on('loadstart', function () {
this.hasStarted_ = false;
});
ALL.names.forEach(name => {
const props = ALL[name];
if (options && options[props.getterName]) {
this[props.privateName] = options[props.getterName];
}
});
// Manually track progress in cases where the browser/tech doesn't report it.
if (!this.featuresProgressEvents) {
this.manualProgressOn();
}
// Manually track timeupdates in cases where the browser/tech doesn't report it.
if (!this.featuresTimeupdateEvents) {
this.manualTimeUpdatesOn();
}
['Text', 'Audio', 'Video'].forEach(track => {
if (options[`native${track}Tracks`] === false) {
this[`featuresNative${track}Tracks`] = false;
}
});
if (options.nativeCaptions === false || options.nativeTextTracks === false) {
this.featuresNativeTextTracks = false;
} else if (options.nativeCaptions === true || options.nativeTextTracks === true) {
this.featuresNativeTextTracks = true;
}
if (!this.featuresNativeTextTracks) {
this.emulateTextTracks();
}
this.preloadTextTracks = options.preloadTextTracks !== false;
this.autoRemoteTextTracks_ = new ALL.text.ListClass();
this.initTrackListeners();
// Turn on component tap events only if not using native controls
if (!options.nativeControlsForTouch) {
this.emitTapEvents();
}
if (this.constructor) {
this.name_ = this.constructor.name || 'Unknown Tech';
}
}
/**
* A special function to trigger source set in a way that will allow player
* to re-trigger if the player or tech are not ready yet.
*
* @fires Tech#sourceset
* @param {string} src The source string at the time of the source changing.
*/
triggerSourceset(src) {
if (!this.isReady_) {
// on initial ready we have to trigger source set
// 1ms after ready so that player can watch for it.
this.one('ready', () => this.setTimeout(() => this.triggerSourceset(src), 1));
}
/**
* Fired when the source is set on the tech causing the media element
* to reload.
*
* @see {@link Player#event:sourceset}
* @event Tech#sourceset
* @type {Event}
*/
this.trigger({
src,
type: 'sourceset'
});
}
/* Fallbacks for unsupported event types
================================================================================ */
/**
* Polyfill the `progress` event for browsers that don't support it natively.
*
* @see {@link Tech#trackProgress}
*/
manualProgressOn() {
this.on('durationchange', this.onDurationChange_);
this.manualProgress = true;
// Trigger progress watching when a source begins loading
this.one('ready', this.trackProgress_);
}
/**
* Turn off the polyfill for `progress` events that was created in
* {@link Tech#manualProgressOn}
*/
manualProgressOff() {
this.manualProgress = false;
this.stopTrackingProgress();
this.off('durationchange', this.onDurationChange_);
}
/**
* This is used to trigger a `progress` event when the buffered percent changes. It
* sets an interval function that will be called every 500 milliseconds to check if the
* buffer end percent has changed.
*
* > This function is called by {@link Tech#manualProgressOn}
*
* @param {Event} event
* The `ready` event that caused this to run.
*
* @listens Tech#ready
* @fires Tech#progress
*/
trackProgress(event) {
this.stopTrackingProgress();
this.progressInterval = this.setInterval(bind_(this, function () {
// Don't trigger unless buffered amount is greater than last time
const numBufferedPercent = this.bufferedPercent();
if (this.bufferedPercent_ !== numBufferedPercent) {
/**
* See {@link Player#progress}
*
* @event Tech#progress
* @type {Event}
*/
this.trigger('progress');
}
this.bufferedPercent_ = numBufferedPercent;
if (numBufferedPercent === 1) {
this.stopTrackingProgress();
}
}), 500);
}
/**
* Update our internal duration on a `durationchange` event by calling
* {@link Tech#duration}.
*
* @param {Event} event
* The `durationchange` event that caused this to run.
*
* @listens Tech#durationchange
*/
onDurationChange(event) {
this.duration_ = this.duration();
}
/**
* Get and create a `TimeRange` object for buffering.
*
* @return {TimeRange}
* The time range object that was created.
*/
buffered() {
return createTimeRanges$1(0, 0);
}
/**
* Get the percentage of the current video that is currently buffered.
*
* @return {number}
* A number from 0 to 1 that represents the decimal percentage of the
* video that is buffered.
*
*/
bufferedPercent() {
return bufferedPercent(this.buffered(), this.duration_);
}
/**
* Turn off the polyfill for `progress` events that was created in
* {@link Tech#manualProgressOn}
* Stop manually tracking progress events by clearing the interval that was set in
* {@link Tech#trackProgress}.
*/
stopTrackingProgress() {
this.clearInterval(this.progressInterval);
}
/**
* Polyfill the `timeupdate` event for browsers that don't support it.
*
* @see {@link Tech#trackCurrentTime}
*/
manualTimeUpdatesOn() {
this.manualTimeUpdates = true;
this.on('play', this.trackCurrentTime_);
this.on('pause', this.stopTrackingCurrentTime_);
}
/**
* Turn off the polyfill for `timeupdate` events that was created in
* {@link Tech#manualTimeUpdatesOn}
*/
manualTimeUpdatesOff() {
this.manualTimeUpdates = false;
this.stopTrackingCurrentTime();
this.off('play', this.trackCurrentTime_);
this.off('pause', this.stopTrackingCurrentTime_);
}
/**
* Sets up an interval function to track current time and trigger `timeupdate` every
* 250 milliseconds.
*
* @listens Tech#play
* @triggers Tech#timeupdate
*/
trackCurrentTime() {
if (this.currentTimeInterval) {
this.stopTrackingCurrentTime();
}
this.currentTimeInterval = this.setInterval(function () {
/**
* Triggered at an interval of 250ms to indicated that time is passing in the video.
*
* @event Tech#timeupdate
* @type {Event}
*/
this.trigger({
type: 'timeupdate',
target: this,
manuallyTriggered: true
});
// 42 = 24 fps // 250 is what Webkit uses // FF uses 15
}, 250);
}
/**
* Stop the interval function created in {@link Tech#trackCurrentTime} so that the
* `timeupdate` event is no longer triggered.
*
* @listens {Tech#pause}
*/
stopTrackingCurrentTime() {
this.clearInterval(this.currentTimeInterval);
// #1002 - if the video ends right before the next timeupdate would happen,
// the progress bar won't make it all the way to the end
this.trigger({
type: 'timeupdate',
target: this,
manuallyTriggered: true
});
}
/**
* Turn off all event polyfills, clear the `Tech`s {@link AudioTrackList},
* {@link VideoTrackList}, and {@link TextTrackList}, and dispose of this Tech.
*
* @fires Component#dispose
*/
dispose() {
// clear out all tracks because we can't reuse them between techs
this.clearTracks(NORMAL.names);
// Turn off any manual progress or timeupdate tracking
if (this.manualProgress) {
this.manualProgressOff();
}
if (this.manualTimeUpdates) {
this.manualTimeUpdatesOff();
}
super.dispose();
}
/**
* Clear out a single `TrackList` or an array of `TrackLists` given their names.
*
* > Note: Techs without source handlers should call this between sources for `video`
* & `audio` tracks. You don't want to use them between tracks!
*
* @param {string[]|string} types
* TrackList names to clear, valid names are `video`, `audio`, and
* `text`.
*/
clearTracks(types) {
types = [].concat(types);
// clear out all tracks because we can't reuse them between techs
types.forEach(type => {
const list = this[`${type}Tracks`]() || [];
let i = list.length;
while (i--) {
const track = list[i];
if (type === 'text') {
this.removeRemoteTextTrack(track);
}
list.removeTrack(track);
}
});
}
/**
* Remove any TextTracks added via addRemoteTextTrack that are
* flagged for automatic garbage collection
*/
cleanupAutoTextTracks() {
const list = this.autoRemoteTextTracks_ || [];
let i = list.length;
while (i--) {
const track = list[i];
this.removeRemoteTextTrack(track);
}
}
/**
* Reset the tech, which will removes all sources and reset the internal readyState.
*
* @abstract
*/
reset() {}
/**
* Get the value of `crossOrigin` from the tech.
*
* @abstract
*
* @see {Html5#crossOrigin}
*/
crossOrigin() {}
/**
* Set the value of `crossOrigin` on the tech.
*
* @abstract
*
* @param {string} crossOrigin the crossOrigin value
* @see {Html5#setCrossOrigin}
*/
setCrossOrigin() {}
/**
* Get or set an error on the Tech.
*
* @param {MediaError} [err]
* Error to set on the Tech
*
* @return {MediaError|null}
* The current error object on the tech, or null if there isn't one.
*/
error(err) {
if (err !== undefined) {
this.error_ = new MediaError(err);
this.trigger('error');
}
return this.error_;
}
/**
* Returns the `TimeRange`s that have been played through for the current source.
*
* > NOTE: This implementation is incomplete. It does not track the played `TimeRange`.
* It only checks whether the source has played at all or not.
*
* @return {TimeRange}
* - A single time range if this video has played
* - An empty set of ranges if not.
*/
played() {
if (this.hasStarted_) {
return createTimeRanges$1(0, 0);
}
return createTimeRanges$1();
}
/**
* Start playback
*
* @abstract
*
* @see {Html5#play}
*/
play() {}
/**
* Set whether we are scrubbing or not
*
* @abstract
* @param {boolean} _isScrubbing
* - true for we are currently scrubbing
* - false for we are no longer scrubbing
*
* @see {Html5#setScrubbing}
*/
setScrubbing(_isScrubbing) {}
/**
* Get whether we are scrubbing or not
*
* @abstract
*
* @see {Html5#scrubbing}
*/
scrubbing() {}
/**
* Causes a manual time update to occur if {@link Tech#manualTimeUpdatesOn} was
* previously called.
*
* @param {number} _seconds
* Set the current time of the media to this.
* @fires Tech#timeupdate
*/
setCurrentTime(_seconds) {
// improve the accuracy of manual timeupdates
if (this.manualTimeUpdates) {
/**
* A manual `timeupdate` event.
*
* @event Tech#timeupdate
* @type {Event}
*/
this.trigger({
type: 'timeupdate',
target: this,
manuallyTriggered: true
});
}
}
/**
* Turn on listeners for {@link VideoTrackList}, {@link {AudioTrackList}, and
* {@link TextTrackList} events.
*
* This adds {@link EventTarget~EventListeners} for `addtrack`, and `removetrack`.
*
* @fires Tech#audiotrackchange
* @fires Tech#videotrackchange
* @fires Tech#texttrackchange
*/
initTrackListeners() {
/**
* Triggered when tracks are added or removed on the Tech {@link AudioTrackList}
*
* @event Tech#audiotrackchange
* @type {Event}
*/
/**
* Triggered when tracks are added or removed on the Tech {@link VideoTrackList}
*
* @event Tech#videotrackchange
* @type {Event}
*/
/**
* Triggered when tracks are added or removed on the Tech {@link TextTrackList}
*
* @event Tech#texttrackchange
* @type {Event}
*/
NORMAL.names.forEach(name => {
const props = NORMAL[name];
const trackListChanges = () => {
this.trigger(`${name}trackchange`);
};
const tracks = this[props.getterName]();
tracks.addEventListener('removetrack', trackListChanges);
tracks.addEventListener('addtrack', trackListChanges);
this.on('dispose', () => {
tracks.removeEventListener('removetrack', trackListChanges);
tracks.removeEventListener('addtrack', trackListChanges);
});
});
}
/**
* Emulate TextTracks using vtt.js if necessary
*
* @fires Tech#vttjsloaded
* @fires Tech#vttjserror
*/
addWebVttScript_() {
if ((window_default()).WebVTT) {
return;
}
// Initially, Tech.el_ is a child of a dummy-div wait until the Component system
// signals that the Tech is ready at which point Tech.el_ is part of the DOM
// before inserting the WebVTT script
if (document_default().body.contains(this.el())) {
// load via require if available and vtt.js script location was not passed in
// as an option. novtt builds will turn the above require call into an empty object
// which will cause this if check to always fail.
if (!this.options_['vtt.js'] && isPlain((browser_index_default())) && Object.keys((browser_index_default())).length > 0) {
this.trigger('vttjsloaded');
return;
}
// load vtt.js via the script location option or the cdn of no location was
// passed in
const script = document_default().createElement('script');
script.src = this.options_['vtt.js'] || 'https://vjs.zencdn.net/vttjs/0.14.1/vtt.min.js';
script.onload = () => {
/**
* Fired when vtt.js is loaded.
*
* @event Tech#vttjsloaded
* @type {Event}
*/
this.trigger('vttjsloaded');
};
script.onerror = () => {
/**
* Fired when vtt.js was not loaded due to an error
*
* @event Tech#vttjsloaded
* @type {Event}
*/
this.trigger('vttjserror');
};
this.on('dispose', () => {
script.onload = null;
script.onerror = null;
});
// but have not loaded yet and we set it to true before the inject so that
// we don't overwrite the injected window.WebVTT if it loads right away
(window_default()).WebVTT = true;
this.el().parentNode.appendChild(script);
} else {
this.ready(this.addWebVttScript_);
}
}
/**
* Emulate texttracks
*
*/
emulateTextTracks() {
const tracks = this.textTracks();
const remoteTracks = this.remoteTextTracks();
const handleAddTrack = e => tracks.addTrack(e.track);
const handleRemoveTrack = e => tracks.removeTrack(e.track);
remoteTracks.on('addtrack', handleAddTrack);
remoteTracks.on('removetrack', handleRemoveTrack);
this.addWebVttScript_();
const updateDisplay = () => this.trigger('texttrackchange');
const textTracksChanges = () => {
updateDisplay();
for (let i = 0; i < tracks.length; i++) {
const track = tracks[i];
track.removeEventListener('cuechange', updateDisplay);
if (track.mode === 'showing') {
track.addEventListener('cuechange', updateDisplay);
}
}
};
textTracksChanges();
tracks.addEventListener('change', textTracksChanges);
tracks.addEventListener('addtrack', textTracksChanges);
tracks.addEventListener('removetrack', textTracksChanges);
this.on('dispose', function () {
remoteTracks.off('addtrack', handleAddTrack);
remoteTracks.off('removetrack', handleRemoveTrack);
tracks.removeEventListener('change', textTracksChanges);
tracks.removeEventListener('addtrack', textTracksChanges);
tracks.removeEventListener('removetrack', textTracksChanges);
for (let i = 0; i < tracks.length; i++) {
const track = tracks[i];
track.removeEventListener('cuechange', updateDisplay);
}
});
}
/**
* Create and returns a remote {@link TextTrack} object.
*
* @param {string} kind
* `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata)
*
* @param {string} [label]
* Label to identify the text track
*
* @param {string} [language]
* Two letter language abbreviation
*
* @return {TextTrack}
* The TextTrack that gets created.
*/
addTextTrack(kind, label, language) {
if (!kind) {
throw new Error('TextTrack kind is required but was not provided');
}
return createTrackHelper(this, kind, label, language);
}
/**
* Create an emulated TextTrack for use by addRemoteTextTrack
*
* This is intended to be overridden by classes that inherit from
* Tech in order to create native or custom TextTracks.
*
* @param {Object} options
* The object should contain the options to initialize the TextTrack with.
*
* @param {string} [options.kind]
* `TextTrack` kind (subtitles, captions, descriptions, chapters, or metadata).
*
* @param {string} [options.label].
* Label to identify the text track
*
* @param {string} [options.language]
* Two letter language abbreviation.
*
* @return {HTMLTrackElement}
* The track element that gets created.
*/
createRemoteTextTrack(options) {
const track = merge$1(options, {
tech: this
});
return new REMOTE.remoteTextEl.TrackClass(track);
}
/**
* Creates a remote text track object and returns an html track element.
*
* > Note: This can be an emulated {@link HTMLTrackElement} or a native one.
*
* @param {Object} options
* See {@link Tech#createRemoteTextTrack} for more detailed properties.
*
* @param {boolean} [manualCleanup=false]
* - When false: the TextTrack will be automatically removed from the video
* element whenever the source changes
* - When True: The TextTrack will have to be cleaned up manually
*
* @return {HTMLTrackElement}
* An Html Track Element.
*
*/
addRemoteTextTrack(options = {}, manualCleanup) {
const htmlTrackElement = this.createRemoteTextTrack(options);
if (typeof manualCleanup !== 'boolean') {
manualCleanup = false;
}
// store HTMLTrackElement and TextTrack to remote list
this.remoteTextTrackEls().addTrackElement_(htmlTrackElement);
this.remoteTextTracks().addTrack(htmlTrackElement.track);
if (manualCleanup === false) {
// create the TextTrackList if it doesn't exist
this.ready(() => this.autoRemoteTextTracks_.addTrack(htmlTrackElement.track));
}
return htmlTrackElement;
}
/**
* Remove a remote text track from the remote `TextTrackList`.
*
* @param {TextTrack} track
* `TextTrack` to remove from the `TextTrackList`
*/
removeRemoteTextTrack(track) {
const trackElement = this.remoteTextTrackEls().getTrackElementByTrack_(track);
// remove HTMLTrackElement and TextTrack from remote list
this.remoteTextTrackEls().removeTrackElement_(trackElement);
this.remoteTextTracks().removeTrack(track);
this.autoRemoteTextTracks_.removeTrack(track);
}
/**
* Gets available media playback quality metrics as specified by the W3C's Media
* Playback Quality API.
*
* @see [Spec]{@link https://wicg.github.io/media-playback-quality}
*
* @return {Object}
* An object with supported media playback quality metrics
*
* @abstract
*/
getVideoPlaybackQuality() {
return {};
}
/**
* Attempt to create a floating video window always on top of other windows
* so that users may continue consuming media while they interact with other
* content sites, or applications on their device.
*
* @see [Spec]{@link https://wicg.github.io/picture-in-picture}
*
* @return {Promise|undefined}
* A promise with a Picture-in-Picture window if the browser supports
* Promises (or one was passed in as an option). It returns undefined
* otherwise.
*
* @abstract
*/
requestPictureInPicture() {
return Promise.reject();
}
/**
* A method to check for the value of the 'disablePictureInPicture'