UNPKG

@vime/core

Version:

Customizable, extensible, accessible and framework agnostic media player.

387 lines (377 loc) 13.1 kB
'use strict'; const index = require('./index-86498cbd.js'); /** * Listen to an event on the given DOM node. Returns a callback to remove the event listener. */ function listen(node, event, handler, options) { node.addEventListener(event, handler, options); return () => node.removeEventListener(event, handler, options); } function fireEventAndRetry(el, event, onFail, interval = 300, maxRetries = 10) { let timeout; let attempt = 0; let found = false; function retry() { if (found) return; timeout = setTimeout(() => { if (attempt === maxRetries) { onFail === null || onFail === void 0 ? void 0 : onFail(); return; } el.dispatchEvent(event); attempt += 1; retry(); }, interval); } retry(); return () => { window.clearTimeout(timeout); found = true; }; } const isColliding = (a, b, translateAx = 0, translateAy = 0, translateBx = 0, translateBy = 0) => { const aRect = a.getBoundingClientRect(); const bRect = b.getBoundingClientRect(); return (aRect.left + translateAx < bRect.right + translateBx && aRect.right + translateAx > bRect.left + translateBx && aRect.top + translateAy < bRect.bottom + translateBy && aRect.bottom + translateAy > bRect.top + translateBy); }; /** * No-operation (noop). */ // eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-unused-vars const noop = (..._) => { // ... }; /** * Checks if `value` is `null`. * * @param value - The value to check. */ const isNull = (value) => value === null; /** * Checks if `value` is `undefined`. * * @param value - The value to check. */ const isUndefined = (value) => typeof value === 'undefined'; /** * Checks if `value` is `null` or `undefined`. * * @param value - The value to check. */ const isNil = (value) => isNull(value) || isUndefined(value); /** * Returns the constructor of the given `value`. * * @param value - The value to return the constructor of. */ const getConstructor = (value) => // eslint-disable-next-line @typescript-eslint/no-explicit-any !isNil(value) ? value.constructor : undefined; /** * Checks if `value` is classified as a `Object` object. * * @param value - The value to check. */ const isObject = (value) => getConstructor(value) === Object; /** * Checks if `value` is classified as a `Number` object. * * @param value - The value to check. */ const isNumber = (value) => getConstructor(value) === Number && !Number.isNaN(value); /** * Checks if `value` is classified as a `String` object. * * @param value - The value to check. */ const isString = (value) => getConstructor(value) === String; /** * Checks if `value` is classified as a `Boolean` object. * * @param value - The value to check. */ const isBoolean = (value) => getConstructor(value) === Boolean; /** * Checks if `value` is classified as a `Function` object. * * @param value - The value to check. */ // eslint-disable-next-line @typescript-eslint/ban-types const isFunction = (value) => getConstructor(value) === Function; /** * Checks if `value` is classified as an `Array` object. * * @param value - The value to check. */ const isArray = (value) => Array.isArray(value); /** * Checks if `value` is an instanceof the given `constructor`. * * @param value - The value to check. * @param constructor - The constructor to check against. */ const isInstanceOf = (value, constructor) => Boolean(value && constructor && value instanceof constructor); /** * Creates an empty Promise and defers resolving/rejecting it. */ const deferredPromise = () => { let resolve = noop; let reject = noop; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, resolve, reject }; }; function wrapStencilHook(component, lifecycle, hook) { const prevHook = component[lifecycle]; component[lifecycle] = function () { hook(); return prevHook ? prevHook.call(component) : undefined; }; } function createStencilHook(component, onConnect, onDisconnect) { let hasLoaded = false; if (!isUndefined(onConnect)) { wrapStencilHook(component, 'componentWillLoad', () => { onConnect(); hasLoaded = true; }); wrapStencilHook(component, 'connectedCallback', () => { if (hasLoaded) onConnect(); }); } if (!isUndefined(onDisconnect)) { wrapStencilHook(component, 'disconnectedCallback', () => { onDisconnect(); }); } } const FIND_PLAYER_EVENT = 'vmFindPlayer'; function withFindPlayer(player) { const el = index.getElement(player); let off; createStencilHook(player, () => { off = listen(el, FIND_PLAYER_EVENT, (event) => { event.stopPropagation(); event.detail(el); }); }, () => { off === null || off === void 0 ? void 0 : off(); }); } /** * Finds the closest ancestor player element by firing the `vmFindPlayer` event, and waiting * for the player to catch it. This function retries finding the player (`maxRetries`) until it * gives up and fails. * * @param ref - A HTMLElement that is within the player's subtree. * @param interval - The length of the timeout before trying again in milliseconds. * @param maxRetries - The number of times to retry firing the event. */ const findPlayer = (ref, interval = 300, maxRetries = 10) => { const el = (isInstanceOf(ref, HTMLElement) ? ref : index.getElement(ref)); const search = deferredPromise(); // eslint-disable-next-line prefer-const let stopFiring; const event = new CustomEvent(FIND_PLAYER_EVENT, { bubbles: true, composed: true, detail: player => { search.resolve(player); stopFiring(); }, }); stopFiring = fireEventAndRetry(el, event, () => { search.reject(`Could not find player for ${el.nodeName}`); }, interval, maxRetries); return search.promise; }; class Disposal { constructor(dispose = []) { this.dispose = dispose; } add(callback) { this.dispose.push(callback); } empty() { this.dispose.forEach(fn => fn()); this.dispose = []; } } var __awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; const PLAYER_KEY = Symbol('vmPlayerKey'); const COMPONENT_NAME_KEY = Symbol('vmNameKey'); const REGISTRY_KEY = Symbol('vmRegistryKey'); const REGISTRATION_KEY = Symbol('vmRegistrationKey'); const REGISTER_COMPONENT_EVENT = 'vmComponentRegister'; const COMPONENT_REGISTERED_EVENT = 'vmComponentRegistered'; const COMPONENT_DEREGISTERED_EVENT = 'vmComponentDeregistered'; const getRegistrant = (ref) => isInstanceOf(ref, HTMLElement) ? ref : index.getElement(ref); /** * Handles registering/deregistering the given `component` in the player registry. All registries * are bound per player subtree. * * @param ref - A Stencil component instance or HTMLElement. */ function withComponentRegistry(ref, name) { const registryId = Symbol('vmRegistryId'); const registrant = getRegistrant(ref); registrant[COMPONENT_NAME_KEY] = name !== null && name !== void 0 ? name : registrant.nodeName.toLowerCase(); registrant[REGISTRATION_KEY] = registryId; const buildEvent = (eventName) => new CustomEvent(eventName, { bubbles: true, composed: true, detail: registrant, }); const registerEvent = buildEvent(REGISTER_COMPONENT_EVENT); createStencilHook(ref, () => { registrant.dispatchEvent(registerEvent); }); } function withComponentRegistrar(player) { const el = index.getElement(player); const registry = new Map(); const disposal = new Disposal(); // TODO properly type this later. // eslint-disable-next-line @typescript-eslint/no-explicit-any el[REGISTRY_KEY] = registry; function onDeregister(registrant) { delete registrant[PLAYER_KEY]; delete registrant[REGISTRY_KEY]; registry.delete(registrant[REGISTRATION_KEY]); el.dispatchEvent(new CustomEvent(COMPONENT_DEREGISTERED_EVENT, { detail: registrant })); } function onRegister(e) { const ref = e.detail; const registrant = getRegistrant(ref); registrant[PLAYER_KEY] = el; registrant[REGISTRY_KEY] = registry; registry.set(registrant[REGISTRATION_KEY], registrant); el.dispatchEvent(new CustomEvent(COMPONENT_REGISTERED_EVENT, { detail: registrant })); createStencilHook(ref, undefined, () => onDeregister(registrant)); } createStencilHook(player, () => { disposal.add(listen(el, REGISTER_COMPONENT_EVENT, onRegister)); }, () => { registry.clear(); disposal.empty(); delete player[REGISTRY_KEY]; }); } /** * Checks whether any component with the given `name` exists in the registry. All registries * are bound per player subtree. * * @param ref - A Stencil component instance or HTMLElement. * @param name - The name of the component to search for. */ function isComponentRegistered(ref, name) { var _a; const registrant = getRegistrant(ref); const registry = registrant[REGISTRY_KEY]; return Array.from((_a = registry === null || registry === void 0 ? void 0 : registry.values()) !== null && _a !== void 0 ? _a : []).some(r => r[COMPONENT_NAME_KEY] === name); } /** * Returns the player for the given `ref`. This will only work after the component has been * registered, prefer using `findPlayer`. * * @param ref - A Stencil component instance or HTMLElement. */ function getPlayerFromRegistry(ref) { const registrant = getRegistrant(ref); return registrant[PLAYER_KEY]; } /** * Returns a collection of components from the registry for the given `ref`. All registries * are bound per player subtree. * * @param ref - A Stencil component instance or HTMLElement. * @param name - The name of the components to search for in the registry. */ function getComponentFromRegistry(ref, name) { var _a, _b; const registrant = getRegistrant(ref); return Array.from((_b = (_a = registrant[REGISTRY_KEY]) === null || _a === void 0 ? void 0 : _a.values()) !== null && _b !== void 0 ? _b : []).filter(r => r[COMPONENT_NAME_KEY] === name); } /** * Watches the current registry on the given `ref` for changes. All registries are bound per * player subtree. * * @param ref - A Stencil component instance or HTMLElement. * @param name - The name of the component to watch for. * @param onChange - A callback that is called when a component is registered/deregistered. */ function watchComponentRegistry(ref, name, onChange) { var _a; return __awaiter(this, void 0, void 0, function* () { const player = yield findPlayer(ref); const disposal = new Disposal(); const registry = getRegistrant(ref)[REGISTRY_KEY]; function listener(e) { if (e.detail[COMPONENT_NAME_KEY] === name) onChange === null || onChange === void 0 ? void 0 : onChange(getComponentFromRegistry(player, name)); } // Hydrate. Array.from((_a = registry === null || registry === void 0 ? void 0 : registry.values()) !== null && _a !== void 0 ? _a : []).forEach(reg => listener(new CustomEvent('', { detail: reg }))); if (!isUndefined(player)) { disposal.add(listen(player, COMPONENT_REGISTERED_EVENT, listener)); disposal.add(listen(player, COMPONENT_DEREGISTERED_EVENT, listener)); } createStencilHook(ref, () => { // no-op }, () => { disposal.empty(); }); return () => { disposal.empty(); }; }); } exports.COMPONENT_NAME_KEY = COMPONENT_NAME_KEY; exports.Disposal = Disposal; exports.PLAYER_KEY = PLAYER_KEY; exports.REGISTRATION_KEY = REGISTRATION_KEY; exports.REGISTRY_KEY = REGISTRY_KEY; exports.createStencilHook = createStencilHook; exports.deferredPromise = deferredPromise; exports.findPlayer = findPlayer; exports.getComponentFromRegistry = getComponentFromRegistry; exports.getPlayerFromRegistry = getPlayerFromRegistry; exports.isArray = isArray; exports.isBoolean = isBoolean; exports.isColliding = isColliding; exports.isComponentRegistered = isComponentRegistered; exports.isFunction = isFunction; exports.isInstanceOf = isInstanceOf; exports.isNil = isNil; exports.isNull = isNull; exports.isNumber = isNumber; exports.isObject = isObject; exports.isString = isString; exports.isUndefined = isUndefined; exports.listen = listen; exports.noop = noop; exports.watchComponentRegistry = watchComponentRegistry; exports.withComponentRegistrar = withComponentRegistrar; exports.withComponentRegistry = withComponentRegistry; exports.withFindPlayer = withFindPlayer; exports.wrapStencilHook = wrapStencilHook;