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