Fix video streaming: parse total size from S3 Content-Range header

This commit is contained in:
CallMeVerity
2026-06-03 04:18:21 +01:00
parent 2f62e68688
commit 4fd31ba07d
3 changed files with 38 additions and 123 deletions
+28 -41
View File
@@ -7,7 +7,6 @@ import {
CompleteMultipartUploadCommand,
AbortMultipartUploadCommand,
GetObjectCommand,
HeadObjectCommand,
} from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
@@ -169,61 +168,49 @@ export async function getVideoStream(
): Promise<VideoStreamResult> {
const s3 = getRustFsClient();
// Get object metadata first to know total size
const headResult = await s3.send(
new HeadObjectCommand({
Bucket: RUSTFS_BUCKET,
Key: key,
}),
);
const totalSize = headResult.ContentLength ?? 0;
const contentType = headResult.ContentType || "video/webm";
let range: { start: number; end: number } | undefined;
let command: GetObjectCommand;
if (rangeHeader) {
// Parse Range header like "bytes=0-1023" or "bytes=1024-"
const match = rangeHeader.match(/bytes=(\d*)-(\d*)/);
if (match) {
const start = match[1] ? parseInt(match[1], 10) : 0;
const end = match[2] ? parseInt(match[2], 10) : totalSize - 1;
range = { start, end: Math.min(end, totalSize - 1) };
command = new GetObjectCommand({
Bucket: RUSTFS_BUCKET,
Key: key,
Range: `bytes=${range.start}-${range.end}`,
});
} else {
command = new GetObjectCommand({
Bucket: RUSTFS_BUCKET,
Key: key,
});
}
} else {
command = new GetObjectCommand({
Bucket: RUSTFS_BUCKET,
Key: key,
});
}
// Build the GetObject command, optionally with a Range
const command = new GetObjectCommand({
Bucket: RUSTFS_BUCKET,
Key: key,
...(rangeHeader ? { Range: rangeHeader } : {}),
});
const result = await s3.send(command);
if (!result.Body) {
throw new Error("Empty response body from RustFS");
}
const contentType = result.ContentType || "video/webm";
// Convert SDK stream to web ReadableStream
const sdkStream = result.Body as any;
const webStream: ReadableStream<Uint8Array> = sdkStream.transformToWebStream
? sdkStream.transformToWebStream()
: sdkStream;
// If we sent a range request, parse the Content-Range header from S3
// Format: "bytes start-end/total"
if (rangeHeader && result.ContentRange) {
const match = result.ContentRange.match(/bytes (\d+)-(\d+)\/(\d+)/);
if (match) {
const start = parseInt(match[1], 10);
const end = parseInt(match[2], 10);
const totalSize = parseInt(match[3], 10);
return {
stream: webStream,
contentType,
contentLength: totalSize,
range: { start, end },
};
}
}
// No range request (or no Content-Range in response) — full object
return {
stream: webStream,
contentType,
contentLength: totalSize,
range,
contentLength: result.ContentLength ?? 0,
range: undefined,
};
}