import * as THREE from 'three'; import { state } from '../state.js'; // --- Play video by index --- export function playVideoByIndex(index) { state.currentVideoIndex = index; const url = state.videoUrls[index]; // Dispose of previous texture to free resources if (state.videoTexture) { state.videoTexture.dispose(); state.videoTexture = null; } if (index < 0 || index >= state.videoUrls.length) { console.info('End of playlist reached. Reload tapes to start again.'); state.screenLight.intensity = 0.0; state.tvScreen.material.dispose(); state.tvScreen.material = new THREE.MeshPhongMaterial({ color: 0x0a0a0a, // Deep black shininess: 5, specular: 0x111111 }); state.tvScreen.material.needsUpdate = true; state.isVideoLoaded = false; state.lastUpdateTime = -1; // force VCR to redraw return; } state.videoElement.src = url; state.videoElement.muted = true; state.videoElement.load(); // Set loop property: only loop if it's the only video loaded state.videoElement.loop = false; //state.videoUrls.length === 1; state.videoElement.onloadeddata = () => { // 1. Create the Three.js texture state.videoTexture = new THREE.VideoTexture(state.videoElement); state.videoTexture.minFilter = THREE.LinearFilter; state.videoTexture.magFilter = THREE.LinearFilter; state.videoTexture.format = THREE.RGBAFormat; state.videoTexture.needsUpdate = true; // 2. Apply the video texture to the screen mesh state.tvScreen.material.dispose(); state.tvScreen.material = new THREE.MeshBasicMaterial({ map: state.videoTexture }); state.tvScreen.material.needsUpdate = true; // 3. Start playback state.videoElement.play().then(() => { state.isVideoLoaded = true; // Use the defined base intensity for screen glow state.screenLight.intensity = state.originalScreenIntensity; // Initial status message with tape count console.info(`Playing tape ${state.currentVideoIndex + 1} of ${state.videoUrls.length}.`); }).catch(error => { state.screenLight.intensity = state.originalScreenIntensity * 0.5; // Dim the light if playback fails console.error(`Playback blocked for tape ${state.currentVideoIndex + 1}. Click Next Tape to try again.`); console.error('Playback Error: Could not start video playback.', error); }); }; state.videoElement.onerror = (e) => { state.screenLight.intensity = 0.1; // Keep minimum intensity for shadow map console.error(`Error loading tape ${state.currentVideoIndex + 1}.`); console.error('Video Load Error:', e); }; } // --- Cycle to the next video --- export function playNextVideo() { // Determine the next index, cycling back to 0 if we reach the end let nextIndex = state.currentVideoIndex + 1; if (nextIndex < state.videoUrls.length) { state.baseTime += state.videoElement.duration; } playVideoByIndex(nextIndex); } // --- Video Loading Logic (handles multiple files) --- export function loadVideoFile(event) { const files = event.target.files; if (files.length === 0) { console.info('File selection cancelled.'); return; } // 1. Clear previous URLs and revoke object URLs to prevent memory leaks state.videoUrls.forEach(url => URL.revokeObjectURL(url)); state.videoUrls = []; // 2. Populate the new videoUrls array for (let i = 0; i < files.length; i++) { const file = files[i]; if (file.type.startsWith('video/')) { state.videoUrls.push(URL.createObjectURL(file)); } } if (state.videoUrls.length === 0) { console.info('No valid video files selected.'); return; } // 3. Start playback of the first video console.info(`Loaded ${state.videoUrls.length} tapes. Starting playback...`); state.loadTapeButton.classList.add("hidden"); const startDelay = 5; console.info(`Video will start in ${startDelay} seconds.`); setTimeout(() => { playVideoByIndex(0); }, startDelay * 1000); }