@aidenlx/player
Version:
Headless web components that make integrating media on the a web a breeze.
204 lines (202 loc) • 5.99 kB
JavaScript
import {
__decorateClass
} from "../../chunks/chunk.LNH2V2XS.js";
import {
deferredPromise,
DisposalBin,
isMouseEvent,
isPointerEvent,
isTouchEvent,
listen,
vdsEvent
} from "@vidstack/foundation";
import { css, LitElement } from "lit";
import { property } from "lit/decorators.js";
import { mediaContainerContext, mediaStoreContext } from "../../media";
const pendingActions = /* @__PURE__ */ new Map();
class GestureElement extends LitElement {
constructor() {
super(...arguments);
this._disposal = new DisposalBin();
this._mediaContainerConsumer = mediaContainerContext.consume(this);
this.repeat = 0;
this.priority = 10;
this._mediaCurrentTime = 0;
this._currentToggleState = false;
this._mediaStoreConsumer = mediaStoreContext.consume(this);
}
static get styles() {
return [
css`
:host {
display: block;
contain: content;
z-index: 0;
opacity: 0;
visibility: hidden;
pointer-events: none !important;
}
:host([hidden]) {
display: none;
}
`
];
}
get _mediaContainer() {
return this._mediaContainerConsumer.value.value;
}
get _pendingActions() {
return this._mediaContainer ? pendingActions.get(this._mediaContainer) : void 0;
}
get _pendingAction() {
return this._pendingActions?.get(this);
}
connectedCallback() {
super.connectedCallback();
window.requestAnimationFrame(() => {
if (this._mediaContainer) {
pendingActions.set(this._mediaContainer, /* @__PURE__ */ new Map());
}
});
}
willUpdate(changedProperties) {
this._attachListener();
this._subscribeToToggleStore();
this._subscribeToSeekStore();
super.willUpdate(changedProperties);
}
disconnectedCallback() {
this._disposal.empty();
this._pendingAction?.[1].resolve();
this._pendingActions?.delete(this);
super.disconnectedCallback();
}
performAction(event) {
if (!this.action)
return;
let detail;
let eventType = this.action;
if (this.action.startsWith("toggle:")) {
eventType = this._getToggleEventType();
}
if (this.action.startsWith("seek:")) {
eventType = "seek";
detail = this._mediaCurrentTime + Number(this.action.split(":")[1]);
}
this.dispatchEvent(vdsEvent(`vds-${eventType}-request`, {
bubbles: true,
composed: true,
detail,
triggerEvent: event
}));
}
_attachListener() {
this._disposal.empty();
if (!this._mediaContainer || !this.type || !this.action)
return;
let count = 0;
let timeoutId;
const resolve = (skip = false) => {
count += 1;
window.clearTimeout(timeoutId);
timeoutId = window.setTimeout(() => {
const promise = this._pendingAction?.[1];
if (skip) {
this._pendingActions?.delete(this);
}
processPendingActions(this._mediaContainer);
count = 0;
promise?.resolve();
}, 250);
};
const off = listen(this._mediaContainer, this.type, (event) => {
if (!this._validateEvent(event))
return;
event.preventDefault();
if (count == 0) {
this._pendingActions?.set(this, [event, deferredPromise()]);
}
resolve(count < this.repeat);
});
this._disposal.add(off);
}
_validateEvent(event) {
if (isPointerEvent(event) || isMouseEvent(event) || isTouchEvent(event)) {
const touch = isTouchEvent(event) ? event.touches[0] : void 0;
const clientX = touch?.clientX ?? event.clientX;
const clientY = touch?.clientY ?? event.clientY;
const rect = this.getBoundingClientRect();
const isValidRegion = clientY >= rect.top && clientY <= rect.bottom && clientX >= rect.left && clientX <= rect.right;
return event.type.includes("leave") ? !isValidRegion : isValidRegion;
}
return true;
}
_subscribeToSeekStore() {
if (!this.action?.startsWith("seek"))
return;
const unsub = this._mediaStore.currentTime.subscribe(($currentTime) => {
this._mediaCurrentTime = $currentTime;
});
this._disposal.add(unsub);
}
get _mediaStore() {
return this._mediaStoreConsumer.value;
}
_getToggleEventType() {
const toggleType = this.action?.split(":")[1];
switch (toggleType) {
case "paused":
return this._currentToggleState ? "play" : "pause";
case "muted":
return this._currentToggleState ? "unmute" : "mute";
case "fullscreen":
return `${this._currentToggleState ? "exit" : "enter"}-fullscreen`;
default:
return "";
}
}
_subscribeToToggleStore() {
if (!this.action?.startsWith("toggle:"))
return;
const action = this.action.split(":")[1];
const unsub = this._mediaStore[action]?.subscribe(($value) => {
this._currentToggleState = $value;
});
if (unsub)
this._disposal.add(unsub);
}
}
__decorateClass([
property()
], GestureElement.prototype, "type", 2);
__decorateClass([
property({ type: Number })
], GestureElement.prototype, "repeat", 2);
__decorateClass([
property({ type: Number })
], GestureElement.prototype, "priority", 2);
__decorateClass([
property()
], GestureElement.prototype, "action", 2);
const inProgress = /* @__PURE__ */ new WeakSet();
async function processPendingActions(container) {
if (inProgress.has(container))
return;
const actions = pendingActions.get(container);
if (!actions)
return;
inProgress.add(container);
const wait = Array.from(actions.values()).map((action) => action[1].promise);
await Promise.all(wait);
const gestures = Array.from(actions.keys());
const highestPriority = Math.min(...gestures.map((g) => g.priority));
gestures.filter((g) => g.priority <= highestPriority).map((g) => {
const event = actions.get(g)[0];
g.performAction(event);
});
actions.clear();
inProgress.delete(container);
}
export {
GestureElement
};