document.addEventListener('DOMContentLoaded', () => { const videoListEl = document.getElementById('video-list'); const loadingSpinner = document.getElementById('loading-spinner'); const refreshBtn = document.getElementById('refresh-btn'); const codecSelect = document.getElementById('codec-select'); const playerOverlay = document.getElementById('player-overlay'); const transcodingOverlay = document.getElementById('transcoding-overlay'); const videoPlayer = document.getElementById('video-player'); const nowPlaying = document.getElementById('now-playing'); const currentVideoTitle = document.getElementById('current-video-title'); let currentPollInterval = null; // Fetch list of videos from the backend const fetchVideos = async () => { videoListEl.classList.add('hidden'); loadingSpinner.classList.remove('hidden'); videoListEl.innerHTML = ''; try { const res = await fetch('/api/videos'); if (!res.ok) throw new Error('Failed to fetch videos. Check S3 Config.'); const data = await res.json(); loadingSpinner.classList.add('hidden'); videoListEl.classList.remove('hidden'); if (data.videos.length === 0) { videoListEl.innerHTML = '
No videos found.
'; return; } // Build a tree structure from S3 keys, preserving original object storage directories const tree = {}; data.videos.forEach(key => { const parts = key.split('/'); let current = tree; parts.forEach((part, index) => { if (!current[part]) { current[part] = {}; } if (index === parts.length - 1) { current[part].__file = key; } current = current[part]; }); }); const createFileItem = (name, key) => { const li = document.createElement('li'); li.className = 'video-item file-item'; const ext = name.split('.').pop().toUpperCase(); li.innerHTML = `Error: ${err.message}
`; } }; // Handle video selection and trigger transcode const selectVideo = async (key, listItemNode) => { // Update UI document.querySelectorAll('.video-item').forEach(n => n.classList.remove('active')); listItemNode.classList.add('active'); // Reset player UI stopPolling(); playerOverlay.classList.add('hidden'); videoPlayer.classList.add('hidden'); videoPlayer.pause(); nowPlaying.classList.remove('hidden'); currentVideoTitle.textContent = key.split('/').pop(); transcodingOverlay.classList.remove('hidden'); try { const codec = codecSelect?.value || 'h264'; const res = await fetch('/api/transcode', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ key, codec }) }); const data = await res.json(); if (data.error) throw new Error(data.error); // Wait/Poll for HLS playlist to be ready pollForHlsReady(key, data.hlsUrl); } catch (err) { console.error(err); transcodingOverlay.innerHTML = `Transcode Failed: ${err.message}
`; } }; // Poll the backend to check if the generated m3u8 file is accessible const pollForHlsReady = (key, hlsUrl) => { let attempts = 0; const maxAttempts = 60; // 60 seconds max wait for first segment currentPollInterval = setInterval(async () => { attempts++; try { const res = await fetch(`/api/status?key=${encodeURIComponent(key)}`); const data = await res.json(); if (data.ready) { stopPolling(); playHlsStream(data.hlsUrl); } else if (attempts >= maxAttempts) { stopPolling(); transcodingOverlay.innerHTML = `Timeout waiting for HLS segments.
`; } } catch (err) { console.error("Poll Error:", err); } }, 1000); }; const stopPolling = () => { if (currentPollInterval) { clearInterval(currentPollInterval); currentPollInterval = null; } }; // Initialize HLS Player const playHlsStream = (url) => { transcodingOverlay.classList.add('hidden'); videoPlayer.classList.remove('hidden'); if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(url); hls.attachMedia(videoPlayer); hls.on(Hls.Events.MANIFEST_PARSED, () => { videoPlayer.play().catch(e => console.log('Auto-play blocked')); }); } else if (videoPlayer.canPlayType('application/vnd.apple.mpegurl')) { // Safari uses native HLS videoPlayer.src = url; videoPlayer.addEventListener('loadedmetadata', () => { videoPlayer.play().catch(e => console.log('Auto-play blocked')); }); } }; // Bind events refreshBtn.addEventListener('click', fetchVideos); // Initial load fetchVideos(); });