diff --git a/backend/src/routes/videos.ts b/backend/src/routes/videos.ts index b8e5d51..c6507ef 100644 --- a/backend/src/routes/videos.ts +++ b/backend/src/routes/videos.ts @@ -382,8 +382,23 @@ export const videoRoutes = new Elysia({ prefix: "/api/videos" }) }); } } catch (err: any) { - console.error("Stream error:", err); - return status(404, { error: "Video not found" }); + const msg = err?.message || String(err); + console.error( + "Stream error for video", + id, + "key", + video.transcodedKey ?? video.videoKey, + ":", + msg, + ); + // S3 errors are server errors, not "not found" + const statusCode = msg.includes("NoSuchKey") ? 404 : 502; + return status(statusCode, { + error: + statusCode === 404 + ? "Video not found" + : "Failed to stream video", + }); } }) diff --git a/backend/src/services/rustfs.ts b/backend/src/services/rustfs.ts index 1f54aec..4854d36 100644 --- a/backend/src/services/rustfs.ts +++ b/backend/src/services/rustfs.ts @@ -175,7 +175,25 @@ export async function getVideoStream( ...(rangeHeader ? { Range: rangeHeader } : {}), }); - const result = await s3.send(command); + // Retry once on transient failures (cold S3 connections can fail the first request) + let result; + try { + result = await s3.send(command); + } catch (firstError: any) { + console.warn( + `[stream] First attempt failed for key=${key}:`, + firstError?.message || firstError, + ); + try { + result = await s3.send(command); + } catch (secondError: any) { + console.error( + `[stream] Second attempt also failed for key=${key}:`, + secondError?.message || secondError, + ); + throw secondError; + } + } if (!result.Body) { throw new Error("Empty response body from RustFS"); }