import { NextRequest, NextResponse } from 'next/server'; export async function GET( request: NextRequest, { params }: { params: Promise<{ path: string[] }> } ) { try { const resolvedParams = await params; const path = resolvedParams.path.join('/'); // @ts-expect-error - MEDIA is bound via wrangler.toml and available in the Cloudflare context const cloudflareContext = (globalThis as Record)[Symbol.for('__cloudflare-context__')]; const MEDIA = cloudflareContext?.env?.MEDIA; if (!MEDIA) { return NextResponse.json( { error: 'Media bucket not configured' }, { status: 500 } ); } // Get the object from R2 const object = await MEDIA.get(path); if (!object) { return NextResponse.json( { error: `File not found: ${path}` }, { status: 404 } ); } // Get range header for video streaming support const range = request.headers.get('range'); // Get the full object body to handle range requests properly const body = await object.body.arrayBuffer(); const totalLength = body.byteLength; let start = 0; let end = totalLength - 1; let status = 200; if (range) { // Extract start and end positions from range header const match = range.match(/bytes=(\d+)-(\d+)?/); if (match) { start = parseInt(match[1], 10); end = match[2] ? parseInt(match[2], 10) : end; // Ensure end is within the bounds if (end >= totalLength) { end = totalLength - 1; } status = 206; // Partial content } } // Calculate the length for the response const contentLength = end - start + 1; const slicedBody = body.slice(start, end + 1); // Set headers for the response const headers = new Headers(); object.writeHttpMetadata(headers); headers.set('etag', object.httpEtag); headers.set('cache-control', 'public, max-age=31536000, immutable'); // Add CORS headers to allow video requests from the same origin headers.set('access-control-allow-origin', '*'); headers.set('access-control-allow-headers', 'range, content-type, accept'); headers.set('access-control-allow-methods', 'GET, HEAD, OPTIONS'); headers.set('access-control-expose-headers', 'content-range, accept-ranges, content-length, content-encoding'); // Add range response headers if needed if (range) { headers.set('content-range', `bytes ${start}-${end}/${totalLength}`); headers.set('accept-ranges', 'bytes'); } headers.set('content-length', contentLength.toString()); headers.set('content-type', path.endsWith('.mp4') ? 'video/mp4' : object.httpMetadata?.contentType || 'application/octet-stream'); return new NextResponse(slicedBody, { status, headers, }); } catch (error) { console.error('Error serving media:', error); return NextResponse.json( { error: 'Failed to serve media file' }, { status: 500 } ); } }