From 942ab5e0ee8f22c7c383caf48e871e1cb575eba5 Mon Sep 17 00:00:00 2001 From: Dejvino Date: Thu, 13 Nov 2025 20:49:54 +0100 Subject: [PATCH] Refactoring: move out video contoller --- tv-player/index.html | 113 +--------------------------------- tv-player/src/video-player.js | 112 +++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 112 deletions(-) create mode 100644 tv-player/src/video-player.js diff --git a/tv-player/index.html b/tv-player/index.html index ba3fc4e..4b86fa8 100644 --- a/tv-player/index.html +++ b/tv-player/index.html @@ -48,6 +48,7 @@ + @@ -137,118 +138,6 @@ return `${paddedMinutes}:${paddedSeconds}`; } - // --- 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); - } // --- Animation Loop --- function animate() { diff --git a/tv-player/src/video-player.js b/tv-player/src/video-player.js new file mode 100644 index 0000000..7adde0a --- /dev/null +++ b/tv-player/src/video-player.js @@ -0,0 +1,112 @@ +// --- 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); +} \ No newline at end of file