/** * Enhanced Cloudflare Worker for serving videos from R2 storage * Features: * - Admin panel with authentication * - Video visibility controls (private, shareable, clip_shareable) * - Clip generation and serving * - Permission-based access control */ const ADMIN_HTML = `
Manage video visibility and sharing
Loading videos...
Please replace this with the full admin.html content using a build step or module import.
For now, use: /admin/api/videos to see the API.
`; // TODO: In production, import admin.html content here } /** * List all videos for admin (includes metadata) */ async function handleAdminListVideos(bucket, kv, corsHeaders) { const objects = await bucket.list(); const videos = await Promise.all( objects.objects.map(async (obj) => { const metadataStr = await kv.get(obj.key); const metadata = metadataStr ? JSON.parse(metadataStr) : {}; return { name: obj.key, size: obj.size, uploaded: obj.uploaded, visibility: metadata.visibility || 'shareable', url: `/${obj.key}`, }; }) ); return new Response(JSON.stringify({ count: videos.length, videos }), { headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } /** * List public videos only (shareable ones) */ async function handlePublicList(bucket, kv, corsHeaders) { const objects = await bucket.list(); const videos = await Promise.all( objects.objects.map(async (obj) => { const metadataStr = await kv.get(obj.key); const metadata = metadataStr ? JSON.parse(metadataStr) : {}; return { name: obj.key, size: obj.size, uploaded: obj.uploaded, visibility: metadata.visibility || 'shareable', url: `/${obj.key}`, }; }) ); // Filter to only shareable videos const shareableVideos = videos.filter(v => v.visibility === 'shareable'); return new Response(JSON.stringify({ count: shareableVideos.length, videos: shareableVideos }), { headers: { 'Content-Type': 'application/json', ...corsHeaders } }); } /** * Serve video file with permission check */ async function handleVideoFile(request, env, filename, corsHeaders) { // Check permissions const metadataStr = await env.VIDEO_METADATA.get(filename); const metadata = metadataStr ? JSON.parse(metadataStr) : {}; const visibility = metadata.visibility || 'shareable'; // If private, require authentication if (visibility === 'private') { const isAuth = await verifyAuth(request, env); if (!isAuth) { return new Response('This video is private', { status: 403, headers: corsHeaders }); } } // If clip_shareable, only allow clips if (visibility === 'clip_shareable') { const isAuth = await verifyAuth(request, env); if (!isAuth) { return new Response('Full video not available. Only clips can be shared.', { status: 403, headers: corsHeaders }); } } // Serve the video return await serveVideo(request, env.R2_BUCKET, filename, corsHeaders); } /** * Handle clip requests */ async function handleClip(request, env, path, corsHeaders) { const filename = path.replace('/clip/', '').split('?')[0]; const url = new URL(request.url); const start = url.searchParams.get('start') || '0'; const end = url.searchParams.get('end'); // Check permissions const metadataStr = await env.VIDEO_METADATA.get(filename); const metadata = metadataStr ? JSON.parse(metadataStr) : {}; const visibility = metadata.visibility || 'shareable'; // Clips are allowed for clip_shareable and shareable videos if (visibility === 'private') { const isAuth = await verifyAuth(request, env); if (!isAuth) { return new Response('This video is private', { status: 403, headers: corsHeaders }); } } // For actual clip generation, you'd need to: // 1. Use ffmpeg in a separate service, or // 2. Use byte-range requests to approximate clips, or // 3. Pre-generate clips on upload // For now, we'll serve with Content-Range headers as an approximation // This won't give exact frame-accurate clips but will seek to the right position return new Response(`Size: ${sizeInMB} MB | Uploaded: ${uploadDate}
This gallery is currently empty. Check back soon for new content!
Upload videos through the admin panel to make them available here.
${shareableVideos.length} video${shareableVideos.length === 1 ? '' : 's'} available