1
0
Files
VR-Web-Player/src/vr180player/dom/two-d-control-panel.ts
2026-06-10 11:55:14 +10:00

201 lines
6.0 KiB
TypeScript

import { setLucideIcon } from './icons.js';
import { formatTime } from '../utils/time.js';
type TwoDControlPanelCallbacks = {
onForward: () => void;
onMute: () => void;
onPlayPause: () => void;
onRewind: () => void;
onSeek: (progress: number) => void;
};
type TwoDControlPanelOptions = {
callbacks: TwoDControlPanelCallbacks;
fullscreenTarget: HTMLElement;
getIsActive: () => boolean;
playerContainer: HTMLElement;
title: string;
};
const CONTROL_PANEL_HIDE_DELAY = 3000;
export class TwoDControlPanel {
private readonly callbacks: TwoDControlPanelCallbacks;
private readonly fullscreenTarget: HTMLElement;
private readonly getIsActive: () => boolean;
private readonly playerContainer: HTMLElement;
private controlPanel: HTMLElement | null;
private currentTimeDisplay: HTMLElement | null;
private hideTimeout: number | undefined;
private playedBar: HTMLElement | null;
private progressBar: HTMLElement | null;
private totalTimeDisplay: HTMLElement | null;
private playButton: HTMLButtonElement | null;
private muteButton: HTMLButtonElement | null;
constructor({ callbacks, fullscreenTarget, getIsActive, playerContainer, title }: TwoDControlPanelOptions) {
this.callbacks = callbacks;
this.fullscreenTarget = fullscreenTarget;
this.getIsActive = getIsActive;
this.playerContainer = playerContainer;
this.controlPanel = playerContainer.querySelector('.vrwp-panel');
const videoTitle = playerContainer.querySelector<HTMLElement>('.vrwp-video-title');
this.currentTimeDisplay = playerContainer.querySelector('.vrwp-current-time');
this.totalTimeDisplay = playerContainer.querySelector('.vrwp-total-time');
this.progressBar = playerContainer.querySelector('.vrwp-bar');
this.playedBar = playerContainer.querySelector('.vrwp-played');
this.playButton = playerContainer.querySelector('.vrwp-play-toggle');
this.muteButton = playerContainer.querySelector('.vrwp-mute');
if (!this.controlPanel) {
console.error('VR_WEB_PLAYER_DOM: 2D control panel was not found.');
return;
}
if (videoTitle) {
videoTitle.textContent = title;
}
this.bindControls(playerContainer);
}
show(): void {
if (!this.getIsActive() || !this.controlPanel) return;
this.clearHideTimeout();
this.controlPanel.classList.add('visible');
this.hideTimeout = window.setTimeout(() => this.hide(), CONTROL_PANEL_HIDE_DELAY);
}
showPersistent(): void {
if (!this.getIsActive() || !this.controlPanel) return;
this.clearHideTimeout();
this.controlPanel.classList.add('visible');
}
hide(): void {
if (!this.controlPanel) return;
this.clearHideTimeout();
this.controlPanel.classList.remove('visible');
}
position(canvas: HTMLElement): void {
if (!this.getIsActive() || !this.controlPanel) return;
const canvasRect = canvas.getBoundingClientRect();
const containerRect = this.playerContainer.getBoundingClientRect();
const bottomOffset = canvasRect.height * 0.1;
const panelHeight = this.controlPanel.offsetHeight;
const topPosition = (canvasRect.bottom - containerRect.top) - bottomOffset - panelHeight;
this.controlPanel.style.position = 'absolute';
this.controlPanel.style.top = `${topPosition}px`;
this.controlPanel.style.bottom = 'auto';
this.controlPanel.style.left = '50%';
this.controlPanel.style.transform = 'translateX(-50%)';
this.controlPanel.style.zIndex = '1000';
}
updateMuteButton(isMuted: boolean): void {
if (!this.getIsActive() || !this.muteButton) return;
if (isMuted) {
this.muteButton.classList.remove('muted');
this.muteButton.classList.add('unmuted');
setLucideIcon(this.muteButton, 'volume-x');
} else {
this.muteButton.classList.remove('unmuted');
this.muteButton.classList.add('muted');
setLucideIcon(this.muteButton, 'volume-2');
}
}
updatePlaybackButton(isPausedOrEnded: boolean): void {
if (!this.getIsActive() || !this.playButton) return;
if (isPausedOrEnded) {
this.playButton.classList.remove('playing');
this.playButton.classList.add('paused');
setLucideIcon(this.playButton, 'play');
} else {
this.playButton.classList.remove('paused');
this.playButton.classList.add('playing');
setLucideIcon(this.playButton, 'pause');
}
}
updateTime(currentTime: number, duration: number): void {
if (!this.getIsActive()) return;
if (this.currentTimeDisplay) {
this.currentTimeDisplay.textContent = formatTime(currentTime);
}
if (this.totalTimeDisplay && isFinite(duration)) {
this.totalTimeDisplay.textContent = formatTime(duration);
}
if (this.playedBar && isFinite(duration) && duration > 0) {
const progress = (currentTime / duration) * 100;
this.playedBar.style.width = `${progress}%`;
}
}
private bindControls(playerContainer: HTMLElement): void {
playerContainer.querySelector('.vrwp-fullscreen')?.addEventListener('click', () => {
this.toggleFullscreen();
});
playerContainer.querySelector('.vrwp-back')?.addEventListener('click', () => {
this.callbacks.onRewind();
this.show();
});
this.playButton?.addEventListener('click', () => {
this.callbacks.onPlayPause();
this.show();
});
playerContainer.querySelector('.vrwp-forward')?.addEventListener('click', () => {
this.callbacks.onForward();
this.show();
});
this.muteButton?.addEventListener('click', () => {
this.callbacks.onMute();
this.show();
});
this.progressBar?.addEventListener('click', (event) => {
const rect = this.progressBar?.getBoundingClientRect();
if (rect && rect.width > 0) {
this.callbacks.onSeek((event.clientX - rect.left) / rect.width);
}
this.show();
});
}
private clearHideTimeout(): void {
if (this.hideTimeout !== undefined) {
clearTimeout(this.hideTimeout);
this.hideTimeout = undefined;
}
}
private toggleFullscreen(): void {
if (!document.fullscreenElement) {
this.fullscreenTarget.requestFullscreen().catch((err) => {
console.error('Error attempting to enable fullscreen:', err);
});
return;
}
document.exitFullscreen().catch((err) => {
console.error('Error attempting to exit fullscreen:', err);
});
}
}