Stream video through backend with Range request support

Previously videos were served via direct RustFS URLs, which meant:
- No HTTP Range support (browsers had to download the entire file)
- Large videos couldn't play at all
- Video player rendered broken

Now videos stream through GET /api/videos/:id/stream which:
- Proxies video data from RustFS to the browser
- Supports Range requests (HTTP 206 Partial Content) for seeking
- Sets proper headers (Accept-Ranges, Content-Range, Content-Type)
- Caches for 24 hours

Frontend changes:
- VideoPlayer: added playsInline, preload=metadata, object-contain, error state
- VideoDetail: removed duplicate inline video, now uses VideoPlayer component
- index.css: style WebKit video controls (dark panel, no border-radius)
This commit is contained in:
CallMeVerity
2026-06-03 03:56:39 +01:00
parent 1693b3849b
commit 2f62e68688
5 changed files with 184 additions and 62 deletions
+5 -54
View File
@@ -1,5 +1,6 @@
import { useEffect, useState, useRef } from "react";
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import VideoPlayer from "../components/VideoPlayer";
import VideoSidebar from "../components/VideoSidebar";
import { fetchVideo, formatRunTime } from "../api/client";
import type { VideoDetail } from "../types/video";
@@ -60,9 +61,7 @@ function PageSkeleton() {
export default function VideoDetail() {
const { id } = useParams<{ id: string }>();
const [video, setVideo] = useState<VideoDetail | null>(null);
const [videoReady, setVideoReady] = useState(false);
const [error, setError] = useState<string | null>(null);
const videoRef = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (!id) return;
@@ -79,22 +78,8 @@ export default function VideoDetail() {
);
}
if (!video || !videoReady) {
return (
<>
<PageSkeleton />
{video && (
<video
ref={videoRef}
src={video.videoUrl}
preload="auto"
onCanPlay={() => setVideoReady(true)}
className="fixed opacity-0 pointer-events-none"
aria-hidden
/>
)}
</>
);
if (!video) {
return <PageSkeleton />;
}
return (
@@ -114,41 +99,7 @@ export default function VideoDetail() {
<div className="flex flex-col lg:flex-row gap-4 lg:items-start">
<div className="lg:w-1/2 min-w-0">
<div className="rounded-lg border border-white/5 overflow-hidden">
<div className="aspect-video bg-black">
<video
ref={videoRef}
src={video.videoUrl}
controls
className="w-full h-full"
poster={video.thumbnailUrl}
>
Your browser does not support video playback.
</video>
</div>
<div className="px-4 py-3 flex items-center justify-between border-t border-white/5 bg-white/3">
<a
href={video.mtvUrl}
download
className="inline-flex items-center gap-2 rounded-md bg-white/4 border border-white/7 px-3 py-1.5 text-xs font-medium text-white/50 hover:bg-white/8 hover:text-white/80 hover:border-white/15 transition-colors no-underline"
>
<svg
className="w-3.5 h-3.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
/>
</svg>
.mtv replay
</a>
</div>
</div>
<VideoPlayer video={video} />
</div>
<div className="lg:w-1/2">
<VideoSidebar video={video} />