Fix video playback from R2 with proper range request handling
Critical fixes for video streaming: - Fix range request handling by using HEAD request first - Get total file size before processing range requests - Properly calculate Content-Range header with accurate boundaries - Prevent issues where object.size wasn't available on range requests - Add proper CORS headers for video streaming - Expose Content-Length, Content-Range, Accept-Ranges headers - Allow Range header in requests - Enables video seeking and progressive loading in browsers - Improve range request logic - Ensure end byte doesn't exceed file size - Calculate correct Content-Length for partial responses - Always return accurate byte ranges in 206 responses These fixes resolve issues where videos wouldn't play or seek properly due to incorrect Content-Range headers and missing CORS headers. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2d71a13621
commit
fd5868f569
|
|
@ -139,7 +139,8 @@ export default {
|
|||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, HEAD, POST, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
'Access-Control-Allow-Headers': 'Range, Content-Type, Accept',
|
||||
'Access-Control-Expose-Headers': 'Content-Length, Content-Range, Accept-Ranges',
|
||||
};
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
|
|
@ -450,15 +451,32 @@ async function handleClip(request, env, path, corsHeaders) {
|
|||
async function serveVideo(request, bucket, filename, corsHeaders) {
|
||||
const range = request.headers.get('Range');
|
||||
|
||||
// First, get the file metadata to know the total size
|
||||
const objectHead = await bucket.head(filename);
|
||||
if (!objectHead) {
|
||||
return new Response('Video not found', { status: 404, headers: corsHeaders });
|
||||
}
|
||||
|
||||
const totalSize = objectHead.size;
|
||||
const contentType = getContentType(filename);
|
||||
|
||||
let object;
|
||||
let start = 0;
|
||||
let end = totalSize - 1;
|
||||
|
||||
if (range) {
|
||||
const rangeMatch = range.match(/bytes=(\d+)-(\d*)/);
|
||||
if (rangeMatch) {
|
||||
const start = parseInt(rangeMatch[1]);
|
||||
const end = rangeMatch[2] ? parseInt(rangeMatch[2]) : undefined;
|
||||
start = parseInt(rangeMatch[1]);
|
||||
end = rangeMatch[2] ? parseInt(rangeMatch[2]) : totalSize - 1;
|
||||
|
||||
// Ensure end doesn't exceed file size
|
||||
if (end >= totalSize) {
|
||||
end = totalSize - 1;
|
||||
}
|
||||
|
||||
object = await bucket.get(filename, {
|
||||
range: { offset: start, length: end ? end - start + 1 : undefined }
|
||||
range: { offset: start, length: end - start + 1 }
|
||||
});
|
||||
} else {
|
||||
object = await bucket.get(filename);
|
||||
|
|
@ -471,7 +489,6 @@ async function serveVideo(request, bucket, filename, corsHeaders) {
|
|||
return new Response('Video not found', { status: 404, headers: corsHeaders });
|
||||
}
|
||||
|
||||
const contentType = getContentType(filename);
|
||||
const headers = {
|
||||
'Content-Type': contentType,
|
||||
'Cache-Control': 'public, max-age=31536000',
|
||||
|
|
@ -479,13 +496,13 @@ async function serveVideo(request, bucket, filename, corsHeaders) {
|
|||
...corsHeaders,
|
||||
};
|
||||
|
||||
if (range && object.range) {
|
||||
headers['Content-Range'] = `bytes ${object.range.offset}-${object.range.offset + object.range.length - 1}/${object.size}`;
|
||||
headers['Content-Length'] = object.range.length;
|
||||
if (range) {
|
||||
headers['Content-Range'] = `bytes ${start}-${end}/${totalSize}`;
|
||||
headers['Content-Length'] = end - start + 1;
|
||||
|
||||
return new Response(object.body, { status: 206, headers });
|
||||
} else {
|
||||
headers['Content-Length'] = object.size;
|
||||
headers['Content-Length'] = totalSize;
|
||||
return new Response(object.body, { status: 200, headers });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue