UNPKG

@exadel/esl

Version:

Exadel Smart Library (ESL) is the lightweight custom elements library that provide a set of super-flexible components

486 lines (485 loc) 16.7 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var ESLMedia_1; import { ESLBaseElement } from '../../esl-base-element/core'; import { ExportNs } from '../../esl-utils/environment/export-ns'; import { isSafeContains } from '../../esl-utils/dom/traversing'; import { CSSClassUtils } from '../../esl-utils/dom/class'; import { SPACE, PAUSE } from '../../esl-utils/dom/keys'; import { isInViewport } from '../../esl-utils/dom/visible'; import { prop, attr, boolAttr, listen, memoize } from '../../esl-utils/decorators'; import { debounce } from '../../esl-utils/async'; import { parseAspectRatio, parseBoolean, parseLazyAttr } from '../../esl-utils/misc/format'; import { ESLMediaQuery } from '../../esl-media-query/core'; import { ESLResizeObserverTarget } from '../../esl-event-listener/core'; import { ESLTraversingQuery } from '../../esl-traversing-query/core'; import { getIObserver } from './esl-media-iobserver'; import { PlayerStates } from './esl-media-provider'; import { ESLMediaProviderRegistry } from './esl-media-registry'; import { ESLMediaManager } from './esl-media-manager'; import { ESLMediaHookEvent } from './esl-media.events'; /** * ESLMedia - custom element, that provides an ability to add and configure media (video / audio) * using a single tag as well as work with external providers using simple native-like API. * * @author Alexey Stsefanovich (ala'n), Yuliya Adamskaya */ let ESLMedia = ESLMedia_1 = class ESLMedia extends ESLBaseElement { constructor() { super(...arguments); /** Deferred reinitialize handler, to prevent multiple reinitialization calls in bound of the macro-task */ this.deferredReinitialize = debounce(() => this.reinitInstance()); } /** Singleton instance of {@link ESLMediaManager} */ static get manager() { return new ESLMediaManager(); } /** * Map object with possible Player States, values: * BUFFERING, ENDED, PAUSED, PLAYING, UNSTARTED, VIDEO_CUED, UNINITIALIZED */ static get PLAYER_STATES() { return PlayerStates; } /** Returns true if the provider with given name is supported */ static supports(name) { return ESLMediaProviderRegistry.instance.has(name); } /** @readonly {@link ESLMediaManager} used for current instance */ get manager() { return this.constructor.manager; } connectedCallback() { super.connectedCallback(); this.manager._onInit(this); if (!this.hasAttribute('role')) { this.setAttribute('role', 'application'); } this.innerHTML += '<!-- Inner Content, do not modify it manually -->'; this.deferredReinitialize(); this.reattachViewportConstraint(); } disconnectedCallback() { this.manager._onDestroy(this); super.disconnectedCallback(); this.detachViewportConstraint(); this._provider && this._provider.unbind(); } attributeChangedCallback(attrName, oldVal, newVal) { if (!this.connected || oldVal === newVal) return; switch (attrName) { case 'media-id': case 'media-src': case 'media-type': case 'start-time': case 'lazy': this.reattachViewportConstraint(); this.deferredReinitialize(); break; case 'loop': case 'muted': case 'controls': this._provider && this._provider.onSafeConfigChange(attrName, newVal !== null); break; case 'fill-mode': case 'aspect-ratio': this.$$on(this._onResize); this._onResize(); break; case 'play-in-viewport': this.reattachViewportConstraint(); break; case 'load-condition': this.$$on(this._onConditionChange); this.deferredReinitialize(); break; } } canActivate() { return this.lazy === 'none' && this.conditionQuery.matches; } reinitInstance() { var _a; (_a = this._provider) === null || _a === void 0 ? void 0 : _a.unbind(); this._provider = null; this._isManualAction = false; if (this.canActivate()) { this._provider = ESLMediaProviderRegistry.instance.createFor(this); if (!this._provider) this._onError(); } this.updateContainerMarkers(); } updateContainerMarkers() { const $target = ESLTraversingQuery.first(this.loadConditionClassTarget, this); $target && CSSClassUtils.toggle($target, this.loadConditionClass, this.conditionQuery.matches); } /** Seek to given position of media */ seekTo(pos) { var _a; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.safeSeekTo(pos)) || null; } /** * Start playing media * @param allowActivate - allows to remove manual lazy loading restrictions * @param system - marks that the action was initiated by the system */ play(allowActivate = false, system = false) { var _a; if (!this.ready && allowActivate) { this.lazy = 'none'; this.deferredReinitialize.cancel(); this.reinitInstance(); } this._isManualAction = !system; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.safePlay(system)) || null; } /** Pause playing media */ pause(system = false) { var _a; this._isManualAction = !system; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.safePause()) || null; } /** Stop playing media */ stop(system = false) { var _a; this._isManualAction = !system; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.safeStop()) || null; } /** * Executes toggle action: * If the player is PAUSED then it starts playing otherwise it pause playing */ toggle(allowActivate = false) { const shouldActivate = [PlayerStates.PAUSED, PlayerStates.UNSTARTED, PlayerStates.VIDEO_CUED, PlayerStates.UNINITIALIZED].includes(this.state); return shouldActivate ? this.play(allowActivate) : this.pause(); } /** Focus inner player **/ focusPlayer() { var _a; (_a = this._provider) === null || _a === void 0 ? void 0 : _a.focus(); } /** Detects if the user manipulate trough native controls */ detectUserInteraction(cmd) { var _a; if (!this.controls || this._isManualAction) return; if (((_a = this._provider) === null || _a === void 0 ? void 0 : _a.lastCommand) === cmd) return; // User cannot manipulate the player outside the viewport const tolerance = this.RATIO_TO_ACTIVATE * this.clientWidth * this.clientHeight; if (!isInViewport(this, tolerance)) return; console.debug('[ESL]: User %s interaction detected', cmd); this._isManualAction = true; } // media live-cycle handlers _onReady() { this.$$attr('ready', true); this.$$attr('error', false); this.updateReadyClass(); this.$$fire(this.READY_EVENT); this._onResize(); } _onError(detail, setReadyState = true) { this.$$attr('ready', true); this.$$attr('error', true); this.$$fire(this.ERROR_EVENT, { detail }); setReadyState && this.$$fire(this.READY_EVENT); } _onDetach() { this.$$attr('active', false); this.$$attr('ready', false); this.$$attr('played', false); this.updateReadyClass(); this.$$fire(this.DETACHED_EVENT); } _onBeforePlay(initiator) { const event = new ESLMediaHookEvent(this.BEFORE_PLAY_EVENT, { initiator }); return this.dispatchEvent(event); } _onPlay() { this.detectUserInteraction('play'); if (this.autofocus) this.focus(); this.$$attr('active', true); this.$$attr('played', true); this.$$fire(this.PLAY_EVENT); this.manager._onAfterPlay(this); this._onResize(); } _onPaused() { this.detectUserInteraction('pause'); this.$$attr('active', false); this.$$fire(this.PAUSED_EVENT); } _onEnded() { this.detectUserInteraction('pause'); this.$$attr('active', false); this.$$fire(this.ENDED_EVENT); } _onResize() { if (!this._provider) return; const { actualAspectRatio } = this; this.$$attr('wide', this.offsetWidth / this.offsetHeight > actualAspectRatio); this._provider.setAspectRatio(actualAspectRatio); } _onRefresh(e) { if (!isSafeContains(e.target, this)) return; this._onResize(); } _onRegistryStateChange(e) { if (e.isRelates(this.mediaType)) this.reinitInstance(); } _onConditionChange() { this.deferredReinitialize(); } _onKeydown(e) { if (e.target !== this) return; if (![SPACE, PAUSE].includes(e.key)) return; e.preventDefault(); e.stopPropagation(); this.toggle(); } /** Update ready class state */ updateReadyClass() { const target = ESLTraversingQuery.first(this.readyClassTarget, this); target && CSSClassUtils.toggle(target, this.readyClass, this.ready); } /** Applied provider */ get providerType() { var _a; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.name) || ''; } /** * Marker if the last action (play/pause/stop) was initiated by the user * (direct method call or by embed player controls) */ get isUserInitiated() { return this._isManualAction; } /** Current player state, see {@link ESLMedia.PLAYER_STATES} values */ get state() { return this._provider ? this._provider.state : PlayerStates.UNINITIALIZED; } /** Duration of the media resource */ get duration() { var _a; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.duration) || 0; } /** Current time of media resource */ get currentTime() { var _a; return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.currentTime) || 0; } /** Set current time of media resource */ set currentTime(time) { var _a; (_a = this._provider) === null || _a === void 0 ? void 0 : _a.safeSeekTo(time); } /** ESLMediaQuery to limit ESLMedia loading */ get conditionQuery() { return ESLMediaQuery.for(this.loadCondition); } /** Fill mode should be handled for element */ get fillModeEnabled() { return this.fillMode === 'cover' || this.fillMode === 'inscribe'; } /** Used resource aspect ratio forced by attribute or returned by provider */ get actualAspectRatio() { var _a; if (this.aspectRatio && this.aspectRatio !== 'auto') return parseAspectRatio(this.aspectRatio); return ((_a = this._provider) === null || _a === void 0 ? void 0 : _a.defaultAspectRatio) || 0; } reattachViewportConstraint() { this.detachViewportConstraint(); if (!this.playInViewport && this.lazy !== 'auto') return; getIObserver().observe(this); } detachViewportConstraint() { var _a; (_a = getIObserver(true)) === null || _a === void 0 ? void 0 : _a.unobserve(this); } }; ESLMedia.is = 'esl-media'; ESLMedia.observedAttributes = [ 'load-condition', 'media-type', 'media-id', 'media-src', 'fill-mode', 'aspect-ratio', 'play-in-viewport', 'muted', 'loop', 'controls', 'lazy', 'start-time' ]; __decorate([ prop(0.05) ], ESLMedia.prototype, "RATIO_TO_ACTIVATE", void 0); __decorate([ prop(0.2) ], ESLMedia.prototype, "RATIO_TO_STOP", void 0); __decorate([ prop(0.33) ], ESLMedia.prototype, "RATIO_TO_PLAY", void 0); __decorate([ prop('esl:media:ready') ], ESLMedia.prototype, "READY_EVENT", void 0); __decorate([ prop('esl:media:error') ], ESLMedia.prototype, "ERROR_EVENT", void 0); __decorate([ prop('esl:media:before:play') ], ESLMedia.prototype, "BEFORE_PLAY_EVENT", void 0); __decorate([ prop('esl:media:play') ], ESLMedia.prototype, "PLAY_EVENT", void 0); __decorate([ prop('esl:media:paused') ], ESLMedia.prototype, "PAUSED_EVENT", void 0); __decorate([ prop('esl:media:ended') ], ESLMedia.prototype, "ENDED_EVENT", void 0); __decorate([ prop('esl:media:detached') ], ESLMedia.prototype, "DETACHED_EVENT", void 0); __decorate([ prop('esl:media:managedpause') ], ESLMedia.prototype, "MANAGED_PAUSE_EVENT", void 0); __decorate([ attr() ], ESLMedia.prototype, "mediaId", void 0); __decorate([ attr() ], ESLMedia.prototype, "mediaSrc", void 0); __decorate([ attr() ], ESLMedia.prototype, "mediaType", void 0); __decorate([ attr() ], ESLMedia.prototype, "group", void 0); __decorate([ attr() ], ESLMedia.prototype, "fillMode", void 0); __decorate([ attr() ], ESLMedia.prototype, "aspectRatio", void 0); __decorate([ attr({ parser: parseLazyAttr }) ], ESLMedia.prototype, "lazy", void 0); __decorate([ boolAttr() ], ESLMedia.prototype, "autoplay", void 0); __decorate([ boolAttr() ], ESLMedia.prototype, "autofocus", void 0); __decorate([ boolAttr() ], ESLMedia.prototype, "muted", void 0); __decorate([ boolAttr() ], ESLMedia.prototype, "loop", void 0); __decorate([ boolAttr() ], ESLMedia.prototype, "controls", void 0); __decorate([ boolAttr({ name: 'playsinline' }) ], ESLMedia.prototype, "playsInline", void 0); __decorate([ boolAttr({ name: 'disablepictureinpicture' }) ], ESLMedia.prototype, "disablePictureInPicture", void 0); __decorate([ boolAttr() ], ESLMedia.prototype, "playInViewport", void 0); __decorate([ attr({ defaultValue: 0, parser: parseInt }) ], ESLMedia.prototype, "startTime", void 0); __decorate([ attr({ parser: parseBoolean, defaultValue: ($this) => $this.controls }) ], ESLMedia.prototype, "focusable", void 0); __decorate([ attr({ defaultValue: 'auto' }) ], ESLMedia.prototype, "preload", void 0); __decorate([ attr() ], ESLMedia.prototype, "readyClass", void 0); __decorate([ attr() ], ESLMedia.prototype, "readyClassTarget", void 0); __decorate([ attr({ defaultValue: 'all' }) ], ESLMedia.prototype, "loadCondition", void 0); __decorate([ attr() ], ESLMedia.prototype, "loadConditionClass", void 0); __decorate([ attr({ defaultValue: '::parent' }) ], ESLMedia.prototype, "loadConditionClassTarget", void 0); __decorate([ boolAttr({ readonly: true }) ], ESLMedia.prototype, "ready", void 0); __decorate([ boolAttr({ readonly: true }) ], ESLMedia.prototype, "active", void 0); __decorate([ boolAttr({ readonly: true }) ], ESLMedia.prototype, "played", void 0); __decorate([ boolAttr({ readonly: true }) ], ESLMedia.prototype, "error", void 0); __decorate([ boolAttr({ readonly: true }) ], ESLMedia.prototype, "wide", void 0); __decorate([ listen({ event: 'resize', target: ESLResizeObserverTarget.for, condition: ($this) => $this.fillModeEnabled }) ], ESLMedia.prototype, "_onResize", null); __decorate([ listen({ event: ($this) => $this.REFRESH_EVENT, target: window }) ], ESLMedia.prototype, "_onRefresh", null); __decorate([ listen({ event: 'change', target: () => ESLMediaProviderRegistry.instance }) ], ESLMedia.prototype, "_onRegistryStateChange", null); __decorate([ listen({ event: 'change', target: ($this) => $this.conditionQuery }) ], ESLMedia.prototype, "_onConditionChange", null); __decorate([ listen('keydown') ], ESLMedia.prototype, "_onKeydown", null); __decorate([ memoize() ], ESLMedia, "manager", null); ESLMedia = ESLMedia_1 = __decorate([ ExportNs('Media') ], ESLMedia); export { ESLMedia };