forked from EXT/VR180-Web-Player
280 lines
7.7 KiB
TypeScript
280 lines
7.7 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';
|
|
import type { MediaCapabilities } from '../media/media-adapter.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;
|
|
mediaCapabilities: MediaCapabilities;
|
|
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 mediaCapabilities: MediaCapabilities;
|
|
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.mediaCapabilities = options.mediaCapabilities;
|
|
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);
|
|
}
|
|
},
|
|
mediaCapabilities: this.mediaCapabilities,
|
|
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();
|
|
}
|
|
|
|
if (this.mediaCapabilities.playback) {
|
|
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;
|
|
if (!this.mediaCapabilities.timeline) return;
|
|
|
|
const video = this.getVideo();
|
|
if (video) {
|
|
this.controls.updateTime(video.currentTime, video.duration);
|
|
}
|
|
}
|
|
|
|
updatePlaybackButton(): void {
|
|
if (!this.active) return;
|
|
if (!this.mediaCapabilities.playback) return;
|
|
|
|
const video = this.getVideo();
|
|
if (video) {
|
|
this.controls.updatePlaybackButton(video.paused || video.ended);
|
|
}
|
|
}
|
|
|
|
updateMuteButton(): void {
|
|
if (!this.active) return;
|
|
if (!this.mediaCapabilities.audio) return;
|
|
|
|
const video = this.getVideo();
|
|
if (video) {
|
|
this.controls.updateMuteButton(video.muted);
|
|
}
|
|
}
|
|
|
|
handleVideoEnd(): void {
|
|
if (!this.active) return;
|
|
if (!this.mediaCapabilities.playback) 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
|
|
});
|
|
}
|
|
}
|