forked from EXT/VR180-Web-Player
This commit is contained in:
@@ -7,7 +7,7 @@
|
|||||||
"dev": "npm run build && vite --host 127.0.0.1",
|
"dev": "npm run build && vite --host 127.0.0.1",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"check": "tsc --noEmit",
|
"check": "tsc --noEmit",
|
||||||
"test": "npm run build && node --test tests/time.test.mjs tests/projection.test.mjs tests/media-controller.test.mjs",
|
"test": "npm run build && node --test tests/time.test.mjs tests/projection.test.mjs tests/media-controller.test.mjs tests/texture-manager.test.mjs",
|
||||||
"preview": "npm run build && vite preview --host 127.0.0.1"
|
"preview": "npm run build && vite preview --host 127.0.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
99
src/vr180player/bootstrap.ts
Normal file
99
src/vr180player/bootstrap.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
import {
|
||||||
|
DEFAULT_PROJECTION,
|
||||||
|
PLAYER_SELECTOR,
|
||||||
|
type ProjectionMode,
|
||||||
|
VALID_PROJECTIONS
|
||||||
|
} from './config.js';
|
||||||
|
import { create2DControlPanel, createPlayButton, injectPlayerStyles } from './dom/dom.js';
|
||||||
|
|
||||||
|
export type BootstrapContext = {
|
||||||
|
playButton: HTMLButtonElement;
|
||||||
|
playerContainer: HTMLElement;
|
||||||
|
projectionMode: ProjectionMode;
|
||||||
|
videoElement: HTMLVideoElement;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function bootstrapPlayer(playerBase: string, onReady: (context: BootstrapContext) => void): void {
|
||||||
|
injectPlayerStyles(playerBase);
|
||||||
|
|
||||||
|
onDocumentReady(() => {
|
||||||
|
const containers = document.querySelectorAll<HTMLElement>(PLAYER_SELECTOR);
|
||||||
|
|
||||||
|
if (containers.length === 0) {
|
||||||
|
console.error(`VR_WEB_PLAYER_DOM: Expected exactly one ${PLAYER_SELECTOR} container, found none.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (containers.length > 1) {
|
||||||
|
console.warn(`VR_WEB_PLAYER_DOM: This version supports exactly one ${PLAYER_SELECTOR} container per page. Found ${containers.length}; no player was initialized.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerContainer = containers[0];
|
||||||
|
playerContainer.classList.add('vrwp');
|
||||||
|
|
||||||
|
const configuredProjection = (playerContainer.dataset.projection || DEFAULT_PROJECTION).trim().toLowerCase();
|
||||||
|
if (!VALID_PROJECTIONS.has(configuredProjection as ProjectionMode)) {
|
||||||
|
console.error(`VR_WEB_PLAYER_CONFIG: Unsupported data-projection="${configuredProjection}". Use "vr180" or "plane".`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoElement = playerContainer.querySelector('video');
|
||||||
|
if (!videoElement) {
|
||||||
|
console.error(`VR_WEB_PLAYER_DOM: ${PLAYER_SELECTOR} must contain a video element.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
videoElement.classList.add('vrwp-video');
|
||||||
|
|
||||||
|
const playButton = createPlayButton();
|
||||||
|
playerContainer.appendChild(playButton);
|
||||||
|
playerContainer.appendChild(create2DControlPanel());
|
||||||
|
playButton.disabled = true;
|
||||||
|
videoElement.load();
|
||||||
|
|
||||||
|
completeXrSupportCheck(playButton, () => {
|
||||||
|
onReady({
|
||||||
|
playButton,
|
||||||
|
playerContainer,
|
||||||
|
projectionMode: configuredProjection as ProjectionMode,
|
||||||
|
videoElement
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDocumentReady(callback: () => void): void {
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', callback, { once: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
|
||||||
|
function completeXrSupportCheck(playButton: HTMLButtonElement, onComplete: () => void): void {
|
||||||
|
if (!navigator.xr) {
|
||||||
|
markXrUnsupported(playButton);
|
||||||
|
onComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
|
||||||
|
if (supported) {
|
||||||
|
playButton.dataset.xrSupported = 'true';
|
||||||
|
} else {
|
||||||
|
markXrUnsupported(playButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
onComplete();
|
||||||
|
}).catch((err) => {
|
||||||
|
console.error('XR Support Check Error:', err);
|
||||||
|
markXrUnsupported(playButton);
|
||||||
|
onComplete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function markXrUnsupported(playButton: HTMLButtonElement): void {
|
||||||
|
playButton.dataset.xrSupported = 'false';
|
||||||
|
playButton.disabled = false;
|
||||||
|
}
|
||||||
67
src/vr180player/rendering/texture-manager.ts
Normal file
67
src/vr180player/rendering/texture-manager.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
export type ManagedTexture = {
|
||||||
|
needsUpdate?: boolean;
|
||||||
|
dispose(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ManagedMaterial<TTexture extends ManagedTexture = ManagedTexture> = {
|
||||||
|
map: TTexture | null;
|
||||||
|
needsUpdate: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TextureFactory<TTexture extends ManagedTexture = ManagedTexture> = (video: HTMLVideoElement) => TTexture;
|
||||||
|
|
||||||
|
export class VideoTextureManager<TTexture extends ManagedTexture = ManagedTexture> {
|
||||||
|
private texture: TTexture | null = null;
|
||||||
|
private readonly createTexture: TextureFactory<TTexture>;
|
||||||
|
private readonly video: HTMLVideoElement;
|
||||||
|
|
||||||
|
constructor(video: HTMLVideoElement, createTexture: TextureFactory<TTexture>) {
|
||||||
|
this.createTexture = createTexture;
|
||||||
|
this.video = video;
|
||||||
|
}
|
||||||
|
|
||||||
|
get current(): TTexture | null {
|
||||||
|
return this.texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
assignToMaterial(material: ManagedMaterial<TTexture>): TTexture {
|
||||||
|
const texture = this.create();
|
||||||
|
material.map = texture;
|
||||||
|
material.needsUpdate = true;
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearMaterial(material?: ManagedMaterial<TTexture> | null): void {
|
||||||
|
if (material?.map) {
|
||||||
|
const materialTexture = material.map;
|
||||||
|
material.map = null;
|
||||||
|
material.needsUpdate = true;
|
||||||
|
materialTexture.dispose();
|
||||||
|
|
||||||
|
if (materialTexture === this.texture) {
|
||||||
|
this.texture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
create(): TTexture {
|
||||||
|
this.dispose();
|
||||||
|
this.texture = this.createTexture(this.video);
|
||||||
|
return this.texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
if (this.texture) {
|
||||||
|
this.texture.dispose();
|
||||||
|
this.texture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateIfPlaying(): void {
|
||||||
|
if (this.texture && !this.video.paused && !this.video.ended) {
|
||||||
|
this.texture.needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
DEFAULT_PROJECTION,
|
|
||||||
PLANE_2D_DISTANCE,
|
PLANE_2D_DISTANCE,
|
||||||
PLANE_DISTANCE,
|
PLANE_DISTANCE,
|
||||||
PLAYER_SELECTOR,
|
type ProjectionMode
|
||||||
type ProjectionMode,
|
|
||||||
VALID_PROJECTIONS
|
|
||||||
} from './config.js';
|
} from './config.js';
|
||||||
|
import { bootstrapPlayer } from './bootstrap.js';
|
||||||
import { createContentScene } from './rendering/content-scene.js';
|
import { createContentScene } from './rendering/content-scene.js';
|
||||||
import { create2DControlPanel, createPlayButton, injectPlayerStyles } from './dom/dom.js';
|
|
||||||
import {
|
import {
|
||||||
applySbsTextureWindow as applySbsTextureWindowCore,
|
applySbsTextureWindow as applySbsTextureWindowCore,
|
||||||
hideContentMeshes as hideContentMeshesCore,
|
hideContentMeshes as hideContentMeshesCore,
|
||||||
@@ -35,11 +32,12 @@ import {
|
|||||||
createPlayerRenderer,
|
createPlayerRenderer,
|
||||||
resizePlayerRenderer
|
resizePlayerRenderer
|
||||||
} from './rendering/renderer-lifecycle.js';
|
} from './rendering/renderer-lifecycle.js';
|
||||||
|
import { VideoTextureManager } from './rendering/texture-manager.js';
|
||||||
|
|
||||||
const _playerBase = new URL('.', import.meta.url).href;
|
const _playerBase = new URL('.', import.meta.url).href;
|
||||||
|
|
||||||
let playerContainer, projectionMode: ProjectionMode = DEFAULT_PROJECTION;
|
let playerContainer, projectionMode: ProjectionMode;
|
||||||
let scene, camera, renderer, video, videoTexture, sphereMaterial;
|
let scene, camera, renderer, video, sphereMaterial;
|
||||||
let vr180Mesh, planeMesh, activeContentMesh;
|
let vr180Mesh, planeMesh, activeContentMesh;
|
||||||
let xrSession = null;
|
let xrSession = null;
|
||||||
let raycaster, uiElements = [];
|
let raycaster, uiElements = [];
|
||||||
@@ -49,6 +47,7 @@ let frameCounter = 0;
|
|||||||
let isXrLoopActive = false;
|
let isXrLoopActive = false;
|
||||||
let vrControlPanel;
|
let vrControlPanel;
|
||||||
let mediaController: MediaController | undefined;
|
let mediaController: MediaController | undefined;
|
||||||
|
let textureManager: VideoTextureManager | undefined;
|
||||||
let vrPanel: VrControlPanel | undefined;
|
let vrPanel: VrControlPanel | undefined;
|
||||||
let twoDMode: TwoDMode | undefined;
|
let twoDMode: TwoDMode | undefined;
|
||||||
const vrPanelVisibility = new VrPanelVisibility();
|
const vrPanelVisibility = new VrPanelVisibility();
|
||||||
@@ -57,81 +56,14 @@ const vrPanelVisibility = new VrPanelVisibility();
|
|||||||
let camera2D;
|
let camera2D;
|
||||||
let fallbackCameraControls: FallbackCameraControls | undefined;
|
let fallbackCameraControls: FallbackCameraControls | undefined;
|
||||||
|
|
||||||
injectPlayerStyles(_playerBase);
|
bootstrapPlayer(_playerBase, (context) => {
|
||||||
|
playerContainer = context.playerContainer;
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
projectionMode = context.projectionMode;
|
||||||
const containers = document.querySelectorAll(PLAYER_SELECTOR);
|
videoElement = context.videoElement;
|
||||||
|
playBtn = context.playButton;
|
||||||
if (containers.length === 0) {
|
|
||||||
console.error(`VR_WEB_PLAYER_DOM: Expected exactly one ${PLAYER_SELECTOR} container, found none.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (containers.length > 1) {
|
|
||||||
console.warn(`VR_WEB_PLAYER_DOM: This version supports exactly one ${PLAYER_SELECTOR} container per page. Found ${containers.length}; no player was initialized.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
playerContainer = containers[0];
|
|
||||||
playerContainer.classList.add('vrwp');
|
|
||||||
|
|
||||||
const configuredProjection = (playerContainer.dataset.projection || DEFAULT_PROJECTION).trim().toLowerCase();
|
|
||||||
if (!VALID_PROJECTIONS.has(configuredProjection as ProjectionMode)) {
|
|
||||||
console.error(`VR_WEB_PLAYER_CONFIG: Unsupported data-projection="${configuredProjection}". Use "vr180" or "plane".`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
projectionMode = configuredProjection as ProjectionMode;
|
|
||||||
|
|
||||||
videoElement = playerContainer.querySelector('video');
|
|
||||||
if (!videoElement) {
|
|
||||||
console.error(`VR_WEB_PLAYER_DOM: ${PLAYER_SELECTOR} must contain a video element.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
videoElement.classList.add('vrwp-video');
|
|
||||||
|
|
||||||
// Create and insert play button
|
|
||||||
playBtn = createPlayButton();
|
|
||||||
playerContainer.appendChild(playBtn);
|
|
||||||
|
|
||||||
// Create and insert 2D control panel
|
|
||||||
const controlPanel = create2DControlPanel();
|
|
||||||
playerContainer.appendChild(controlPanel);
|
|
||||||
|
|
||||||
playBtn.disabled = true;
|
|
||||||
|
|
||||||
if (videoElement) {
|
|
||||||
videoElement.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (navigator.xr) {
|
|
||||||
navigator.xr.isSessionSupported('immersive-vr').then((supported) => {
|
|
||||||
if (supported) {
|
|
||||||
playBtn.dataset.xrSupported = "true";
|
|
||||||
} else {
|
|
||||||
playBtn.dataset.xrSupported = "false";
|
|
||||||
// Enable button for regular video playback when VR is not supported
|
|
||||||
playBtn.disabled = false;
|
|
||||||
}
|
|
||||||
// Always call init() regardless of VR support
|
|
||||||
init();
|
|
||||||
}).catch(err => {
|
|
||||||
console.error("XR Support Check Error:", err);
|
|
||||||
playBtn.dataset.xrSupported = "false";
|
|
||||||
// Enable button for regular video playback when VR check fails
|
|
||||||
playBtn.disabled = false;
|
|
||||||
// Call init() even when VR check fails
|
|
||||||
init();
|
init();
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
playBtn.dataset.xrSupported = "false";
|
|
||||||
// If navigator.xr itself is not available, enable button for regular video playback
|
|
||||||
if (playBtn) {
|
|
||||||
playBtn.disabled = false;
|
|
||||||
}
|
|
||||||
// Call init() even when XR is not available
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function applySbsTextureWindow(renderingRenderer, activeCamera, material) {
|
function applySbsTextureWindow(renderingRenderer, activeCamera, material) {
|
||||||
applySbsTextureWindowCore(renderingRenderer, activeCamera, material, is2DModeActive());
|
applySbsTextureWindowCore(renderingRenderer, activeCamera, material, is2DModeActive());
|
||||||
@@ -150,9 +82,10 @@ function positionPlaneForPresentation(isFallback2D = false) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createVideoTexture() {
|
function createVideoTexture() {
|
||||||
if (videoTexture) videoTexture.dispose();
|
if (!textureManager) {
|
||||||
videoTexture = createVideoTextureCore(video);
|
throw new Error('Video texture manager is not initialized.');
|
||||||
return videoTexture;
|
}
|
||||||
|
return textureManager.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
function is2DModeActive() {
|
function is2DModeActive() {
|
||||||
@@ -174,9 +107,7 @@ function closeActiveXrSessionAfterContextLoss() {
|
|||||||
|
|
||||||
function restoreVideoTextureAfterContextRestored() {
|
function restoreVideoTextureAfterContextRestored() {
|
||||||
if (video && sphereMaterial && activeContentMesh && activeContentMesh.visible && renderer.xr.isPresenting && xrSession) {
|
if (video && sphereMaterial && activeContentMesh && activeContentMesh.visible && renderer.xr.isPresenting && xrSession) {
|
||||||
videoTexture = createVideoTexture();
|
textureManager?.assignToMaterial(sphereMaterial);
|
||||||
sphereMaterial.map = videoTexture;
|
|
||||||
sphereMaterial.needsUpdate = true;
|
|
||||||
updateVRPlayPauseButtonIcon();
|
updateVRPlayPauseButtonIcon();
|
||||||
updateVRVolumeButtonIcon();
|
updateVRVolumeButtonIcon();
|
||||||
console.log("Re-initialized video texture after context restoration during VR.");
|
console.log("Re-initialized video texture after context restoration during VR.");
|
||||||
@@ -202,6 +133,7 @@ function init() {
|
|||||||
renderer = playerRenderer.renderer;
|
renderer = playerRenderer.renderer;
|
||||||
|
|
||||||
video = videoElement;
|
video = videoElement;
|
||||||
|
textureManager = new VideoTextureManager(video, createVideoTextureCore);
|
||||||
mediaController = new MediaController({
|
mediaController = new MediaController({
|
||||||
is2DModeActive,
|
is2DModeActive,
|
||||||
on2DPlaybackResume: show2DControlPanel,
|
on2DPlaybackResume: show2DControlPanel,
|
||||||
@@ -502,17 +434,18 @@ async function actualSessionToggle() {
|
|||||||
if (camera) camera.updateProjectionMatrix();
|
if (camera) camera.updateProjectionMatrix();
|
||||||
positionPlaneForPresentation(false);
|
positionPlaneForPresentation(false);
|
||||||
|
|
||||||
if (videoTexture) { videoTexture.dispose(); videoTexture = null; }
|
textureManager?.dispose();
|
||||||
if (video) {
|
if (!video) {
|
||||||
videoTexture = createVideoTexture();
|
|
||||||
if (activeContentMesh && sphereMaterial) {
|
|
||||||
sphereMaterial.map = videoTexture;
|
|
||||||
sphereMaterial.needsUpdate = true;
|
|
||||||
showActiveContentMesh();
|
|
||||||
} else { throw new Error("VR mesh components not ready for texture."); }
|
|
||||||
} else {
|
|
||||||
throw new Error("Video element not available for creating texture.");
|
throw new Error("Video element not available for creating texture.");
|
||||||
}
|
}
|
||||||
|
if (!activeContentMesh || !sphereMaterial) {
|
||||||
|
throw new Error("VR mesh components not ready for texture.");
|
||||||
|
}
|
||||||
|
if (!textureManager) {
|
||||||
|
throw new Error("Video texture manager is not initialized.");
|
||||||
|
}
|
||||||
|
textureManager.assignToMaterial(sphereMaterial);
|
||||||
|
showActiveContentMesh();
|
||||||
|
|
||||||
updateVRPlayPauseButtonIcon();
|
updateVRPlayPauseButtonIcon();
|
||||||
updateVRVolumeButtonIcon();
|
updateVRVolumeButtonIcon();
|
||||||
@@ -531,8 +464,7 @@ async function actualSessionToggle() {
|
|||||||
isXrLoopActive = false;
|
isXrLoopActive = false;
|
||||||
|
|
||||||
hideContentMeshes();
|
hideContentMeshes();
|
||||||
if (sphereMaterial) { sphereMaterial.map = null; sphereMaterial.needsUpdate = true; }
|
textureManager?.clearMaterial(sphereMaterial);
|
||||||
if (videoTexture) { videoTexture.dispose(); videoTexture = null; }
|
|
||||||
if (vrControlPanel) {
|
if (vrControlPanel) {
|
||||||
vrPanelVisibility.hideImmediately();
|
vrPanelVisibility.hideImmediately();
|
||||||
}
|
}
|
||||||
@@ -570,14 +502,7 @@ function onVRSessionEnd(event) {
|
|||||||
|
|
||||||
mediaController?.pauseIfPlaying();
|
mediaController?.pauseIfPlaying();
|
||||||
|
|
||||||
if (sphereMaterial && sphereMaterial.map) {
|
textureManager?.clearMaterial(sphereMaterial);
|
||||||
sphereMaterial.map.dispose();
|
|
||||||
sphereMaterial.map = null;
|
|
||||||
sphereMaterial.needsUpdate = true;
|
|
||||||
}
|
|
||||||
if (videoTexture) {
|
|
||||||
videoTexture.dispose(); videoTexture = null;
|
|
||||||
}
|
|
||||||
hideContentMeshes();
|
hideContentMeshes();
|
||||||
if (vrControlPanel) {
|
if (vrControlPanel) {
|
||||||
vrPanelVisibility.hideImmediately();
|
vrPanelVisibility.hideImmediately();
|
||||||
@@ -633,10 +558,7 @@ function renderXR(timestamp, frame) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Sync video texture before render to ensure frame consistency
|
textureManager?.updateIfPlaying();
|
||||||
if (videoTexture && video && !video.paused && !video.ended) {
|
|
||||||
videoTexture.needsUpdate = true;
|
|
||||||
}
|
|
||||||
renderer.render(scene, camera);
|
renderer.render(scene, camera);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const renderErrorMsg = "ERROR_IN_RENDERXR_LOOP (F" + frameCounter + "): " + (error.message || String(error));
|
const renderErrorMsg = "ERROR_IN_RENDERXR_LOOP (F" + frameCounter + "): " + (error.message || String(error));
|
||||||
|
|||||||
73
tests/texture-manager.test.mjs
Normal file
73
tests/texture-manager.test.mjs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { VideoTextureManager } from '../vr180player/rendering/texture-manager.js';
|
||||||
|
|
||||||
|
function createTexture(name) {
|
||||||
|
return {
|
||||||
|
disposed: false,
|
||||||
|
name,
|
||||||
|
needsUpdate: false,
|
||||||
|
dispose() {
|
||||||
|
this.disposed = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createVideo({ paused = false, ended = false } = {}) {
|
||||||
|
return { ended, paused };
|
||||||
|
}
|
||||||
|
|
||||||
|
test('VideoTextureManager replaces the previous texture when creating a new one', () => {
|
||||||
|
const created = [];
|
||||||
|
const manager = new VideoTextureManager(createVideo(), () => {
|
||||||
|
const texture = createTexture(`texture-${created.length}`);
|
||||||
|
created.push(texture);
|
||||||
|
return texture;
|
||||||
|
});
|
||||||
|
|
||||||
|
const first = manager.create();
|
||||||
|
const second = manager.create();
|
||||||
|
|
||||||
|
assert.equal(first.disposed, true);
|
||||||
|
assert.equal(second.disposed, false);
|
||||||
|
assert.equal(manager.current, second);
|
||||||
|
assert.equal(created.length, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('VideoTextureManager assigns and clears material maps', () => {
|
||||||
|
const material = { map: null, needsUpdate: false };
|
||||||
|
const manager = new VideoTextureManager(createVideo(), () => createTexture('assigned'));
|
||||||
|
|
||||||
|
const texture = manager.assignToMaterial(material);
|
||||||
|
|
||||||
|
assert.equal(material.map, texture);
|
||||||
|
assert.equal(material.needsUpdate, true);
|
||||||
|
assert.equal(manager.current, texture);
|
||||||
|
|
||||||
|
material.needsUpdate = false;
|
||||||
|
manager.clearMaterial(material);
|
||||||
|
|
||||||
|
assert.equal(texture.disposed, true);
|
||||||
|
assert.equal(material.map, null);
|
||||||
|
assert.equal(material.needsUpdate, true);
|
||||||
|
assert.equal(manager.current, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('VideoTextureManager only marks textures dirty while playback is active', () => {
|
||||||
|
const video = createVideo();
|
||||||
|
const manager = new VideoTextureManager(video, () => createTexture('playing'));
|
||||||
|
const texture = manager.create();
|
||||||
|
|
||||||
|
manager.updateIfPlaying();
|
||||||
|
assert.equal(texture.needsUpdate, true);
|
||||||
|
|
||||||
|
texture.needsUpdate = false;
|
||||||
|
video.paused = true;
|
||||||
|
manager.updateIfPlaying();
|
||||||
|
assert.equal(texture.needsUpdate, false);
|
||||||
|
|
||||||
|
video.paused = false;
|
||||||
|
video.ended = true;
|
||||||
|
manager.updateIfPlaying();
|
||||||
|
assert.equal(texture.needsUpdate, false);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user