1
0
Files
VR-Web-Player/src/vr180player/modes/two-d-mode.ts
Aiden 24a166046e
All checks were successful
Test / test (push) Successful in 9m32s
more refactors
2026-06-10 12:37:48 +10:00

269 lines
7.2 KiB
TypeScript

import type { ProjectionMode } from '../config.js';
import type { FallbackCameraControls } from './fallback-camera-controls.js';
import {
hideRendererCanvas,
resizeFallbackRenderer,
showFallbackCanvas
} from '../rendering/renderer-lifecycle.js';
import { TwoDControlPanel } from '../dom/two-d-control-panel.js';
type TwoDModeCallbacks = {
createMediaTexture: () => any;
forward: () => void;
positionPlaneForPresentation: (isFallback2D?: boolean) => void;
rewind: () => void;
seekToProgress: (progress: number) => void;
showActiveContentMesh: () => void;
toggleMute: () => void;
togglePlayPause: () => void;
};
type TwoDModeOptions = {
callbacks: TwoDModeCallbacks;
fullscreenTarget: HTMLElement;
getActiveContentMesh: () => any;
getCamera: () => any;
getCameraControls: () => FallbackCameraControls | undefined;
getMaterial: () => any;
getMediaElement: () => HTMLElement | undefined;
getRenderer: () => any;
getScene: () => any;
getVideo: () => HTMLVideoElement | undefined;
playerContainer: HTMLElement;
projectionMode: ProjectionMode;
title: string;
};
const FULLSCREEN_RESIZE_DELAY = 100;
export class TwoDMode {
private readonly callbacks: TwoDModeCallbacks;
private readonly controls: TwoDControlPanel;
private readonly fullscreenTarget: HTMLElement;
private readonly getActiveContentMesh: () => any;
private readonly getCamera: () => any;
private readonly getCameraControls: () => FallbackCameraControls | undefined;
private readonly getMaterial: () => any;
private readonly getMediaElement: () => HTMLElement | undefined;
private readonly getRenderer: () => any;
private readonly getScene: () => any;
private readonly getVideo: () => HTMLVideoElement | undefined;
private readonly playerContainer: HTMLElement;
private readonly projectionMode: ProjectionMode;
private active = false;
constructor(options: TwoDModeOptions) {
this.callbacks = options.callbacks;
this.fullscreenTarget = options.fullscreenTarget;
this.getActiveContentMesh = options.getActiveContentMesh;
this.getCamera = options.getCamera;
this.getCameraControls = options.getCameraControls;
this.getMaterial = options.getMaterial;
this.getMediaElement = options.getMediaElement;
this.getRenderer = options.getRenderer;
this.getScene = options.getScene;
this.getVideo = options.getVideo;
this.playerContainer = options.playerContainer;
this.projectionMode = options.projectionMode;
this.controls = new TwoDControlPanel({
callbacks: {
onForward: () => {
this.callbacks.forward();
},
onMute: () => {
this.callbacks.toggleMute();
},
onPlayPause: this.callbacks.togglePlayPause,
onRewind: () => {
this.callbacks.rewind();
},
onSeek: (progress) => {
this.callbacks.seekToProgress(progress);
}
},
fullscreenTarget: this.fullscreenTarget,
getIsActive: () => this.active,
playerContainer: this.playerContainer,
title: options.title
});
}
get isActive(): boolean {
return this.active;
}
start(): void {
const mediaElement = this.getMediaElement();
const renderer = this.getRenderer();
const camera = this.getCamera();
if (!mediaElement || !renderer || !camera) {
console.error("Required components not available for 2D mode");
return;
}
this.active = true;
this.resizeCanvasFor2D(renderer, camera);
const canvas = showFallbackCanvas(renderer);
mediaElement.style.display = 'none';
const mediaTexture = this.callbacks.createMediaTexture();
this.callbacks.positionPlaneForPresentation(this.projectionMode === 'plane');
const material = this.getMaterial();
const activeContentMesh = this.getActiveContentMesh();
if (material && activeContentMesh) {
material.map = mediaTexture;
material.needsUpdate = true;
this.callbacks.showActiveContentMesh();
}
this.callbacks.togglePlayPause();
this.addEventListeners(canvas);
this.controls.show();
this.positionControls();
this.render();
}
stop(): void {
this.active = false;
const renderer = this.getRenderer();
if (renderer?.domElement) {
this.removeEventListeners(renderer.domElement);
hideRendererCanvas(renderer);
} else {
this.removeFullscreenEventListeners();
}
this.controls.hide();
this.getCameraControls()?.reset();
this.callbacks.positionPlaneForPresentation(false);
const mediaElement = this.getMediaElement();
if (mediaElement) {
mediaElement.style.display = '';
}
}
resize(): boolean {
if (!this.active) {
return false;
}
const renderer = this.getRenderer();
const camera = this.getCamera();
if (renderer && camera) {
this.resizeCanvasFor2D(renderer, camera);
this.positionControls();
}
return true;
}
showControls(): void {
this.controls.show();
}
hideControls(): void {
this.controls.hide();
}
updateTimeline(): void {
if (!this.active) return;
const video = this.getVideo();
if (video) {
this.controls.updateTime(video.currentTime, video.duration);
}
}
updatePlaybackButton(): void {
if (!this.active) return;
const video = this.getVideo();
if (video) {
this.controls.updatePlaybackButton(video.paused || video.ended);
}
}
updateMuteButton(): void {
if (!this.active) return;
const video = this.getVideo();
if (video) {
this.controls.updateMuteButton(video.muted);
}
}
handleVideoEnd(): void {
if (!this.active) return;
this.controls.showPersistent();
this.updatePlaybackButton();
}
private addEventListeners(canvas: HTMLElement): void {
this.getCameraControls()?.addEventListeners(canvas, this.projectionMode);
document.addEventListener('fullscreenchange', this.onFullscreenChange);
document.addEventListener('webkitfullscreenchange', this.onFullscreenChange);
document.addEventListener('mozfullscreenchange', this.onFullscreenChange);
document.addEventListener('MSFullscreenChange', this.onFullscreenChange);
}
private removeEventListeners(canvas: HTMLElement): void {
this.getCameraControls()?.removeEventListeners(canvas);
this.removeFullscreenEventListeners();
}
private removeFullscreenEventListeners(): void {
document.removeEventListener('fullscreenchange', this.onFullscreenChange);
document.removeEventListener('webkitfullscreenchange', this.onFullscreenChange);
document.removeEventListener('mozfullscreenchange', this.onFullscreenChange);
document.removeEventListener('MSFullscreenChange', this.onFullscreenChange);
}
private readonly onFullscreenChange = (): void => {
if (!this.active) return;
window.setTimeout(() => {
this.resize();
}, FULLSCREEN_RESIZE_DELAY);
};
positionControls(): void {
const renderer = this.getRenderer();
if (renderer?.domElement) {
this.controls.position(renderer.domElement);
}
}
private readonly render = (): void => {
if (!this.active) return;
const camera = this.getCamera();
if (this.projectionMode === 'vr180') {
this.getCameraControls()?.updateCameraRotation();
} else if (camera) {
camera.rotation.set(0, 0, 0);
}
const renderer = this.getRenderer();
const scene = this.getScene();
if (renderer && camera && scene) {
renderer.render(scene, camera);
}
requestAnimationFrame(this.render);
};
private resizeCanvasFor2D(renderer: any, camera: any): void {
resizeFallbackRenderer({
camera2D: camera,
playerContainer: this.playerContainer,
renderer
});
}
}