forked from EXT/VR180-Web-Player
201 lines
6.0 KiB
TypeScript
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);
|
|
});
|
|
}
|
|
}
|