import { AtomBinder } from "@web-atoms/core/dist/core/AtomBinder"; import Bind from "@web-atoms/core/dist/core/Bind"; import { BindableProperty } from "@web-atoms/core/dist/core/BindableProperty"; import XNode from "@web-atoms/core/dist/core/XNode"; import { AtomControl } from "@web-atoms/core/dist/web/controls/AtomControl"; import { ChildEnumerator } from "@web-atoms/core/dist/web/core/AtomUI"; // check if it is a mobile.. const isTouchEnabled = /android|iPhone|iPad/i.test(navigator.userAgent); import "./AtomVideoPlayer.global.css"; const gatherElements = (e: HTMLElement, data = {}) => { const ce = ChildEnumerator.enumerate(e); for (const iterator of ce) { const elementName = iterator.dataset.element?.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); if (elementName) { data[elementName] = iterator; } gatherElements(iterator, data); } return data; }; const numberToText = (n: number) => { if (n < 10) { return "0" + n; } return n.toString(); }; const durationText = (n: number, total: number) => { if (n === null || n === undefined) { return ""; } const minutes = Math.floor(n / 60); const seconds = numberToText(Math.ceil(n % 60)); const totalMinutes = Math.floor(total / 60); const totalSeconds = numberToText(Math.ceil(total % 60)); return `${minutes}:${seconds} / ${totalMinutes}:${totalSeconds}`; }; const noSoundIcon = "fa-duotone fa-volume-slash"; const mute = "fa-duotone fa-volume-xmark"; const low = "fa-duotone fa-volume-low"; const mid = "fa-duotone fa-volume"; const high = "fa-duotone fa-volume-high"; export type playerState = "playing" | "paused" | "ended" | "waiting" | "aborted" | "none"; const getPlayIcon = (state: playerState) => { switch(state) { case "ended": return "fa-solid fa-refresh"; case "paused": return "fa-solid fa-play"; case "playing": return "fa-solid fa-pause"; } return "fa-solid fa-play"; }; export default class AtomVideoPlayer extends AtomControl { @BindableProperty public source: any; @BindableProperty public logo: any; @BindableProperty public logoTitle: string; @BindableProperty public logoDescription: string; public get poster() { return this.video.poster; } public set poster(v: string) { this.video.poster = v; } /** * Use this inside a mobile app */ public useStageView: boolean; public get state() { return this.element.getAttribute("data-state") as playerState; } public set state(v: playerState) { this.element.setAttribute("data-state", v); AtomBinder.refreshValue(this, "paused"); AtomBinder.refreshValue(this, "state"); } public get duration() { return this.video.duration; } public get time() { return this.video.currentTime; } public set time(v) { this.video.currentTime = v; } public get paused() { return this.video.paused; } public get isFullScreen() { return (document.fullscreenEnabled && this.element === document.fullscreenElement) ?? false; } private video: HTMLVideoElement; private progress: HTMLCanvasElement; private currentTimeSpan: HTMLSpanElement; private soundIcon: HTMLElement; private volumeRange: HTMLInputElement; private maxWidth: string = ""; public stopFullscreen() { if(this.isFullScreen) { return document.exitFullscreen(); } return Promise.resolve(); } public pause() { this.video.pause(); } public play() { // tslint:disable-next-line: no-console this.video.play().catch(console.error); } public onPropertyChanged(name: keyof AtomVideoPlayer): void { switch (name) { case "source": this.updateSource(); break; } } protected setCurrentTime(n: number) { // n = Math.round(n * 100) / 100; this.video.currentTime = n * this.video.duration; } protected create(): void { this.element.dataset.videoPlayer = "video-player"; this.bindEvent(this.element, "togglePlay", (e: CustomEvent) => { if (e.defaultPrevented) { return; } e.preventDefault(); if (isTouchEnabled) { if (this.state === "playing") { if (e.target === this.video) { if (this.element.dataset.controls === "true") { this.element.dataset.controls = "false"; } else { this.element.dataset.controls = "true"; } return; } } if ((e.target as HTMLCanvasElement).tagName === "CANVAS") { return; } // if (e.target === e.currentTarget) { // if (this.element.dataset.controls === "true") { // this.element.dataset.controls = "false"; // } else { // this.element.dataset.controls = "true"; // } // return; // } } if (this.video.paused) { this.video.play(); this.element.dataset.controls = "false"; } else { this.video.pause(); this.element.dataset.controls = "true"; } }); this.bindEvent(this.element, "volume", (e: CustomEvent) => { this.video.muted = !this.video.muted; this.updateVolume(); }); this.bindEvent(this.element, "fullScreen", async (e: CustomEvent) => { if (!this.element.requestFullscreen) { (this.video as any).webkitEnterFullscreen(); return; } if (this.isFullScreen) { await document.exitFullscreen(); return; } await this.element.requestFullscreen({navigationUI: "show" }); }); this.bindEvent(document as any, "fullscreenchange", () => { AtomBinder.refreshValue(this, "isFullScreen"); }); this.render(