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:
parent
76b9485d2c
commit
50e44f7c24
|
|
@ -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>`;
|
||||
|
|
|
|||
Loading…
Reference in New Issue