diff --git a/public/js/main.js b/public/js/main.js index 464cbb6..611d60c 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -19,32 +19,90 @@ document.addEventListener('DOMContentLoaded', () => { 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 MP4 videos found in the S3 bucket.

'; + videoListEl.innerHTML = '

No videos found.

'; return; } + // Build a tree structure from S3 keys + const tree = {}; data.videos.forEach(key => { - const li = document.createElement('li'); - li.className = 'video-item'; - li.innerHTML = ` -
- -
-
-
${key.split('/').pop()}
-
H264 / AAC
-
- `; - li.addEventListener('click', () => selectVideo(key, li)); - videoListEl.appendChild(li); + const parts = key.split('/'); + let current = tree; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (!current[part]) { + current[part] = (i === parts.length - 1) ? key : {}; + } + if (i < parts.length - 1) { + current = current[part]; + } + } }); + + // Recursive function to render the tree + const renderTree = (node, container) => { + for (const [name, value] of Object.entries(node)) { + if (typeof value === 'string') { + // It's a file + const li = document.createElement('li'); + li.className = 'video-item file-item'; + const ext = name.split('.').pop().toUpperCase(); + li.innerHTML = ` +
+ +
+
+
${name}
+
Video / ${ext}
+
+ `; + li.addEventListener('click', (e) => { + e.stopPropagation(); + selectVideo(value, li); + }); + container.appendChild(li); + } else { + // It's a folder + const li = document.createElement('li'); + li.className = 'folder-item'; + + const folderHeader = document.createElement('div'); + folderHeader.className = 'folder-header'; + folderHeader.innerHTML = ` +
+ +
+
${name}
+
+ +
+ `; + + const subListContainer = document.createElement('ul'); + subListContainer.className = 'sub-list hidden'; + + folderHeader.addEventListener('click', (e) => { + e.stopPropagation(); + li.classList.toggle('open'); + subListContainer.classList.toggle('hidden'); + }); + + li.appendChild(folderHeader); + renderTree(value, subListContainer); + li.appendChild(subListContainer); + container.appendChild(li); + } + } + }; + + renderTree(tree, videoListEl); } catch (err) { console.error(err); loadingSpinner.innerHTML = `

Error: ${err.message}

`; @@ -56,16 +114,16 @@ document.addEventListener('DOMContentLoaded', () => { // 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 { @@ -75,7 +133,7 @@ document.addEventListener('DOMContentLoaded', () => { body: JSON.stringify({ key }) }); const data = await res.json(); - + if (data.error) throw new Error(data.error); // Wait/Poll for HLS playlist to be ready diff --git a/server.js b/server.js index 2694aee..d1a42bd 100644 --- a/server.js +++ b/server.js @@ -40,10 +40,15 @@ app.get('/api/videos', async (req, res) => { const response = await s3Client.send(command); - // Filter out non-mp4 files for this boilerplate + // Filter for common video formats + const videoExtensions = ['.mp4', '.avi', '.mov', '.mkv', '.webm', '.flv', '.wmv', '.m4v']; const videos = (response.Contents || []) .map(item => item.Key) - .filter(key => key && key.toLowerCase().endsWith('.mp4')); + .filter(key => { + if (!key) return false; + const lowerKey = key.toLowerCase(); + return videoExtensions.some(ext => lowerKey.endsWith(ext)); + }); res.json({ videos }); } catch (error) {