import { DeesElement, html, customElement, type TemplateResult, property, state, cssManager, } from '@design.estate/dees-element'; import '../../00group-utility/dees-icon/dees-icon.js'; import { demo } from './demo.js'; declare global { interface HTMLElementTagNameMap { 'dees-video-viewer': DeesVideoViewer; } } @customElement('dees-video-viewer') export class DeesVideoViewer extends DeesElement { public static demo = demo; public static demoGroups = ['Media']; @property() accessor src: string = ''; @property() accessor poster: string = ''; @property({ type: Boolean }) accessor showControls: boolean = true; @property({ type: Boolean }) accessor autoplay: boolean = false; @property({ type: Boolean }) accessor loop: boolean = false; @property({ type: Boolean }) accessor muted: boolean = false; @state() accessor isPlaying: boolean = false; @state() accessor currentTime: number = 0; @state() accessor duration: number = 0; @state() accessor volume: number = 1; @state() accessor loading: boolean = true; @state() accessor error: string = ''; @state() accessor isFullscreen: boolean = false; @state() accessor controlsVisible: boolean = true; private hideControlsTimer: ReturnType | null = null; private videoElement: HTMLVideoElement | null = null; public render(): TemplateResult { return html`
${this.showControls ? html`
e.stopPropagation()}>
${this.formatTime(this.currentTime)} / ${this.formatTime(this.duration)}
` : ''} ${this.loading && !this.error ? html`
` : ''} ${this.error ? html`
${this.error}
` : ''}
`; } public async firstUpdated(): Promise { this.videoElement = this.shadowRoot?.querySelector('video') || null; document.addEventListener('fullscreenchange', this.handleFullscreenChange); } public async disconnectedCallback(): Promise { await super.disconnectedCallback(); document.removeEventListener('fullscreenchange', this.handleFullscreenChange); if (this.hideControlsTimer) { clearTimeout(this.hideControlsTimer); } } public play(): void { this.videoElement?.play(); } public pause(): void { this.videoElement?.pause(); } public togglePlay(): void { if (this.isPlaying) { this.pause(); } else { this.play(); } } public seek(time: number): void { if (this.videoElement) { this.videoElement.currentTime = time; } } public setVolume(v: number): void { this.volume = Math.max(0, Math.min(1, v)); if (this.videoElement) { this.videoElement.volume = this.volume; } } public toggleFullscreen(): void { const container = this.shadowRoot?.querySelector('.video-container') as HTMLElement; if (!container) return; if (this.isFullscreen) { document.exitFullscreen?.(); } else { container.requestFullscreen?.(); } } private handleLoadedMetadata(): void { if (this.videoElement) { this.duration = this.videoElement.duration; this.loading = false; } } private handlePlay(): void { this.isPlaying = true; this.scheduleHideControls(); } private handlePause(): void { this.isPlaying = false; this.controlsVisible = true; } private handleEnded(): void { this.isPlaying = false; this.controlsVisible = true; } private handleTimeUpdate(): void { if (this.videoElement) { this.currentTime = this.videoElement.currentTime; } } private handleError(): void { this.error = 'Failed to load video'; this.loading = false; } private handleOverlayClick(): void { this.togglePlay(); } private handleSeek(e: MouseEvent): void { const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const ratio = (e.clientX - rect.left) / rect.width; this.seek(ratio * this.duration); } private handleVolumeChange(e: Event): void { const value = parseFloat((e.target as HTMLInputElement).value); this.setVolume(value); this.muted = value === 0; } private toggleMute(): void { this.muted = !this.muted; if (this.videoElement) { this.videoElement.muted = this.muted; } } private handleMouseMove(): void { this.controlsVisible = true; this.scheduleHideControls(); } private handleMouseLeave(): void { if (this.isPlaying) { this.controlsVisible = false; } } private scheduleHideControls(): void { if (this.hideControlsTimer) { clearTimeout(this.hideControlsTimer); } if (this.isPlaying) { this.hideControlsTimer = setTimeout(() => { this.controlsVisible = false; }, 3000); } } private handleFullscreenChange = (): void => { this.isFullscreen = !!document.fullscreenElement; }; private formatTime(seconds: number): string { if (!isFinite(seconds) || seconds < 0) return '0:00'; const mins = Math.floor(seconds / 60); const secs = Math.floor(seconds % 60); return `${mins}:${secs.toString().padStart(2, '0')}`; } }