-
Notifications
You must be signed in to change notification settings - Fork 0
Stream Playback
wiki-sync-bot edited this page Jul 26, 2026
·
1 revision
- Frontend calls
POST /api/v1/cameras/{id}/live/start?stream=sub - API delegates to stream-manager via HTTP (
http://nvr-stream-manager:8001) - Stream-manager spawns FFmpeg:
rtsp://camera_sub → libx264 → rtsp://nvr-mediamtx:8554/{id}_sub - MediaMTX creates LL-HLS at
/hls/{id}_sub/ - Frontend polls manifest → initializes hls.js → plays in
<video>
All video playback MUST use the shared useStreamPlayer hook (src/hooks/useStreamPlayer.ts):
import { useStreamPlayer, type StreamType } from "../hooks/useStreamPlayer";
const { state, retrySec, attachVideo, startStream } = useStreamPlayer({
cameraId, // UUID string
streamType, // "main" | "sub"
pollAttempts, // optional, default 30
retryIntervalMs, // optional, default 60000
});| Value | Type | Description |
|---|---|---|
state |
"connecting" | "loading" | "playing" | "retrying" | "error"
|
Current stream state |
retrySec |
number |
Countdown seconds when in retrying state |
attachVideo |
(el: HTMLVideoElement | null) => void |
Ref callback for <video> element |
startStream |
() => Promise<void> |
Manually retry/start |
<video ref={attachVideo} muted autoPlay playsInline
className={state === "playing" ? "opacity-80" : "opacity-0"} />
{state === "connecting" && <Spinner label="Connecting..." />}
{state === "loading" && <Spinner label="Buffering..." />}
{state === "retrying" && <button onClick={startStream}>Retry ({retrySec}s)</button>}
{state === "playing" && <StreamToggle streamType={streamType} onChange={setStreamType} />}-
MiniLivePreview.tsx— dashboard grid tiles -
CameraGrid.tsx:ExpandedView— modal on single click -
LiveViewPage.tsx— full live view with PTZ
- Browser requests
/api/v1/recordings/{id}/stream - API serves MP4 via HTTP Range requests:
- Full file request (
Range: bytes=0-) →200 OK(progressive download, browser buffers) - Partial request (seeking) →
206 Partial Content+Content-Rangeheader
- Full file request (
- Native
<video>element plays MP4 — no hls.js needed
// RecordingPlayer uses native <video> ONLY — NO hls.js
<video controls muted autoPlay playsInline preload="auto"
src={`${streamUrl}?token=${authToken}`} />- Playback speed controls: 0.125x, 0.25x, 0.5x, 0.75x, 1x, 1.5x, 2x, 4x, 8x (slow/fast color coded)
-
Download button:
?download=true→Content-Disposition: attachment -
Token auth:
?token=query parameter viaget_current_user
| Feature | Chrome | Safari | Firefox |
|---|---|---|---|
| Live HLS (LL-HLS) | ✅ hls.js | ✅ hls.js | ✅ hls.js |
| Recording MP4 playback | ✅ Progressive <video>
|
✅ Progressive <video>
|
✅ Progressive <video>
|
CSP script-src enforcement |
new Function()) |
Lenient | Moderate |
Range: bytes=0- handling |
Requires 206 Partial Content
|
Accepts 200 OK
|
Accepts 200 OK
|
pix_fmt=yuvj420p full-range |
❌ Rejects | ✅ Accepts | ❌ Rejects |
- RecordingPlayer uses progressive MP4 download (NO hls.js) — avoids CSP
unsafe-evalconflict - Recording engine normalizes all codecs to H.264
yuv420pcolor_range=tv— Chrome-safe format - Stream endpoint returns
200 OKfor full-file requests,206 Partial Contentfor seeking — satisfies Chrome's strict Range handling
See Troubleshooting page for common issues:
- CSP blocks hls.js in Chrome (works in Safari)
- Chrome vs Safari video playback differences
- HEVC codec normalization
- HTTP Range request handling
-
Content-Dispositionheader issues