Improve video gallery filtering and thumbnail display

- Filter out videos smaller than 1KB (test files)
- Verify videos exist in R2 before displaying in gallery
- Add thumbnail error handling with placeholder icons
- Improve video preview with proper metadata loading
- Update API list endpoint to exclude small/test files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2025-11-25 16:07:42 -08:00
parent 76b9485d2c
commit 50e44f7c24
1 changed files with 61 additions and 7 deletions

View File

@ -347,8 +347,15 @@ async function handlePublicList(bucket, kv, corsHeaders) {
})
);
// Filter to only shareable videos
const shareableVideos = videos.filter(v => v.visibility === 'shareable');
// Filter to only shareable videos and exclude small/test files
const validVideoExtensions = ['.mp4', '.mkv', '.mov', '.avi', '.webm', '.flv', '.wmv'];
const shareableVideos = videos.filter(v => {
if (v.visibility !== 'shareable') return false;
if (v.name.startsWith('live/')) return false;
if (v.size < 1024) return false; // Exclude files smaller than 1KB
const ext = v.name.substring(v.name.lastIndexOf('.')).toLowerCase();
return validVideoExtensions.includes(ext);
});
return new Response(JSON.stringify({ count: shareableVideos.length, videos: shareableVideos }), {
headers: { 'Content-Type': 'application/json', ...corsHeaders }
@ -502,7 +509,7 @@ async function handlePublicGallery(bucket, kv, corsHeaders) {
// Filter to only shareable videos AND exclude HLS live stream files
const validVideoExtensions = ['.mp4', '.mkv', '.mov', '.avi', '.webm', '.flv', '.wmv'];
const shareableVideos = videos.filter(v => {
const candidateVideos = videos.filter(v => {
// Must be shareable
if (v.visibility !== 'shareable') return false;
@ -511,9 +518,28 @@ async function handlePublicGallery(bucket, kv, corsHeaders) {
// Only include actual video files (not HLS chunks)
const ext = v.key.substring(v.key.lastIndexOf('.')).toLowerCase();
// Filter out test files and files that are too small to be real videos (< 1KB)
if (v.size < 1024) return false;
return validVideoExtensions.includes(ext);
});
// Verify each video actually exists and is accessible by trying to read first byte
const shareableVideos = [];
for (const video of candidateVideos) {
try {
// Try to actually fetch the first 1 byte to verify the video exists
const testObject = await bucket.get(video.key, { range: { offset: 0, length: 1 } });
if (testObject && testObject.body) {
shareableVideos.push(video);
}
} catch (e) {
// Video doesn't exist or isn't accessible, skip it
console.log(`Skipping inaccessible video: ${video.key}`);
}
}
const videoItems = shareableVideos
.map(obj => {
const sizeInMB = (obj.size / (1024 * 1024)).toFixed(2);
@ -522,8 +548,8 @@ async function handlePublicGallery(bucket, kv, corsHeaders) {
return `
<div class="video-item">
<div class="video-thumbnail" onclick="window.location.href='/watch/${encodedKey}'">
<video preload="metadata">
<div class="video-thumbnail" onclick="playVideo('${encodedKey}')">
<video id="thumb-${encodedKey}" preload="metadata" muted>
<source src="/${obj.key}#t=0.5" type="${getContentType(obj.key)}">
</video>
<div class="play-overlay">
@ -531,10 +557,10 @@ async function handlePublicGallery(bucket, kv, corsHeaders) {
</div>
</div>
<div class="video-info">
<h3 onclick="window.location.href='/watch/${encodedKey}'" style="cursor: pointer;">${obj.key}</h3>
<h3 onclick="playVideo('${encodedKey}')" style="cursor: pointer;">${obj.key}</h3>
<p>Size: ${sizeInMB} MB | Uploaded: ${uploadDate}</p>
<div class="button-group">
<button onclick="window.location.href='/watch/${encodedKey}'">Watch</button>
<button onclick="playVideo('${encodedKey}')">Watch</button>
<button onclick="copyLink('${obj.key}')">Copy Link</button>
</div>
</div>
@ -685,10 +711,38 @@ async function handlePublicGallery(bucket, kv, corsHeaders) {
${emptyState}
</div>
<script>
function playVideo(encodedKey) {
window.location.href = '/watch/' + encodedKey;
}
function copyLink(filename) {
const url = window.location.origin + '/' + filename;
navigator.clipboard.writeText(url).then(() => alert('Link copied!'));
}
// Load thumbnails on page load
document.addEventListener('DOMContentLoaded', function() {
const videos = document.querySelectorAll('.video-thumbnail video');
videos.forEach(video => {
// Set up error handling for videos that fail to load
video.addEventListener('error', function() {
// Hide the video element and show a placeholder
const thumbnail = this.closest('.video-thumbnail');
const overlay = thumbnail.querySelector('.play-overlay');
this.style.display = 'none';
thumbnail.style.background = 'linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%)';
// Add a video icon placeholder
const icon = document.createElement('div');
icon.style.cssText = 'position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 3rem; opacity: 0.3;';
icon.textContent = '🎬';
thumbnail.insertBefore(icon, overlay);
});
// Try to load the video
video.load();
});
});
</script>
</body>
</html>`;