11 KiB
RTSP to HLS Streaming in SvelteKit
This guide walks you through setting up real-time RTSP stream conversion to HLS format in your SvelteKit application.
What's Included
- Backend: FFmpeg-based RTSP to HLS converter running on Node.js
- Frontend: SvelteKit component with HLS.js video player
- API: RESTful endpoints to control streams
Prerequisites
System Requirements
- Node.js: 18 or higher
- FFmpeg: Must be installed and in your PATH
- Package Manager: npm or bun
Install FFmpeg
macOS
brew install ffmpeg
Ubuntu/Debian
sudo apt-get update
sudo apt-get install ffmpeg
Windows (Chocolatey)
choco install ffmpeg
Windows (Scoop)
scoop install ffmpeg
Verify Installation
ffmpeg -version
Installation
1. Install Dependencies
npm install
# or with bun
bun install
This installs hls.js for browser-based HLS playback.
2. Project Structure
The setup creates the following files:
src/
├── lib/
│ ├── server/
│ │ └── streaming.ts # FFmpeg stream manager
│ └── components/
│ └── RTSPVideoPlayer.svelte # Video player component
├── routes/
│ ├── +page.svelte # Home page
│ └── api/
│ └── stream/
│ └── +server.ts # Stream API endpoints
└── ...
static/
└── hls/ # HLS playlists & segments (auto-created)
3. Run Development Server
npm run dev
Open http://localhost:5173 in your browser.
Usage
Web Interface
- Stream ID: Enter a unique identifier (e.g., "camera-1")
- RTSP URL: Enter your camera's RTSP stream URL
- Start Stream: Click to begin conversion and playback
- Stop Stream: Click to terminate the stream
Common RTSP URLs
Hikvision Cameras
rtsp://admin:password@192.168.1.100:554/stream
rtsp://admin:password@192.168.1.100:554/Streaming/Channels/101
Dahua Cameras
rtsp://admin:password@192.168.1.100:554/live
Generic IP Cameras
rtsp://user:password@camera-ip:554/stream
rtsp://camera-ip:554/stream1
API Reference
Start Stream
Request:
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "start",
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream"
}'
Response:
{
"playlistUrl": "/hls/camera-1.m3u8"
}
Stop Stream
Request:
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "stop",
"streamId": "camera-1"
}'
Response:
{
"success": true
}
Get Stream Status
Request:
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{
"action": "status",
"streamId": "camera-1"
}'
Response:
{
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream",
"startedAt": "2024-01-15T10:30:45.123Z",
"isRunning": true,
"playlistUrl": "/hls/camera-1.m3u8"
}
List All Streams
Request:
curl -X POST http://localhost:5173/api/stream \
-H "Content-Type: application/json" \
-d '{"action": "list"}'
Response:
{
"streams": [
{
"streamId": "camera-1",
"rtspUrl": "rtsp://192.168.1.100:554/stream",
"startedAt": "2024-01-15T10:30:45.123Z",
"isRunning": true,
"playlistUrl": "/hls/camera-1.m3u8"
}
]
}
Configuration
FFmpeg Parameters
Edit src/lib/server/streaming.ts to adjust encoding parameters:
// Current defaults
'-hls_time', '10', // Segment duration (seconds)
'-hls_list_size', '3', // Number of segments to keep
'-preset', 'fast', // Encoding speed
'-b:a', '128k', // Audio bitrate
Low Latency Setup
For real-time applications, modify the FFmpeg arguments:
'-hls_time', '2', // 2-second segments
'-hls_list_size', '5', // Keep more segments
'-preset', 'ultrafast', // Fastest encoding
'-flags', '+low_delay', // Low-delay mode
High Quality Setup
'-crf', '23', // Quality (0-51, lower=better)
'-b:v', '2500k', // Video bitrate
'-c:a', 'aac',
'-b:a', '192k', // Higher audio quality
GPU Acceleration (NVIDIA)
'-c:v', 'h264_nvenc', // NVIDIA encoder
'-preset', 'fast', // fast, medium, slow
Troubleshooting
"ffmpeg: command not found"
FFmpeg is not installed or not in your system PATH.
Solution: Reinstall FFmpeg and ensure it's in your PATH, then restart your terminal.
# Verify FFmpeg is accessible
which ffmpeg
ffmpeg -version
Stream won't connect
Check the following:
- RTSP URL is correct: Test with VLC player first
- Network connectivity: Ping the camera IP
- Firewall rules: Ensure port 554 (default RTSP) is open
- Camera credentials: Verify username/password in URL
- FFmpeg logs: Check browser console and terminal output
"Playlist not found" Error
This usually means FFmpeg hasn't created the HLS segments yet.
Solution: Increase the wait time in the component:
// In RTSPVideoPlayer.svelte, startStream function
await new Promise((resolve) => setTimeout(resolve, 3000)); // Increase from 1000 to 3000
Video won't play in Safari
HLS.js may have issues with some configurations.
Solution: Check that the HLS.js library is loaded:
// Verify HLS.js is available
if (typeof window !== 'undefined' && !(window as any).HLS) {
console.error('HLS.js not loaded');
}
High CPU Usage
The FFmpeg process is using too many resources.
Solution: Use a faster preset or reduce resolution:
// Use ultrafast preset
'-preset', 'ultrafast',
// Or reduce resolution
'-vf', 'scale=1280:720',
High Latency / Buffering
Segments are taking too long to generate or playback is laggy.
Solutions:
- Reduce segment duration to 2-5 seconds
- Enable low-latency mode
- Check network bandwidth
- Reduce video resolution
- Close other CPU-intensive applications
Browser Support
| Browser | HLS Support | Notes |
|---|---|---|
| Chrome | ✓ HLS.js | Full support via HLS.js library |
| Firefox | ✓ HLS.js | Full support via HLS.js library |
| Safari | ✓ Native | Native HLS support |
| Edge | ✓ HLS.js | Chromium-based, full support |
| Mobile Chrome | ✓ HLS.js | Full support |
| Mobile Safari | ✓ Native | Native HLS support |
| Opera | ✓ HLS.js | Full support |
Security Best Practices
1. Environment Variables for Credentials
Never hardcode camera credentials in your code.
// Load from environment
const rtspUrl = `rtsp://${process.env.CAMERA_USER}:${process.env.CAMERA_PASS}@${process.env.CAMERA_IP}:554/stream`;
Create a .env.local file:
CAMERA_USER=admin
CAMERA_PASS=password
CAMERA_IP=192.168.1.100
2. Restrict API Access
Implement authentication on the /api/stream endpoint:
// src/routes/api/stream/+server.ts
export async function POST({ request, locals }) {
// Check authentication
if (!locals.user) {
return json({ error: 'Unauthorized' }, { status: 401 });
}
// Continue with stream logic
}
3. Network Security
- Use HTTPS in production
- Restrict camera access to internal network only
- Use VPN for remote access
- Implement IP whitelisting
4. Process Management
FFmpeg runs with server privileges. Ensure:
- Minimal file system access
- Process limits to prevent DoS
- Regular monitoring and logging
Performance Optimization
1. Adaptive Bitrate
Implement multiple quality levels:
// Start multiple streams at different resolutions
const streams = [
{ id: 'high', resolution: '1920:1080', bitrate: '5000k' },
{ id: 'medium', resolution: '1280:720', bitrate: '2500k' },
{ id: 'low', resolution: '640:360', bitrate: '1000k' }
];
2. Connection Pooling
For multiple concurrent streams, optimize memory usage:
// Limit concurrent streams
const MAX_STREAMS = 5;
if (activeStreams.size >= MAX_STREAMS) {
return { error: 'Too many concurrent streams' };
}
3. Caching
Cache HLS segments on a CDN for better performance.
4. Hardware Acceleration
Use GPU encoding when available:
- NVIDIA:
h264_nvenc - Intel:
h264_qsv - AMD:
h264_amf
Production Deployment
Docker
Create Dockerfile:
FROM node:18-slim
RUN apt-get update && apt-get install -y ffmpeg && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "build/index.js"]
Build and run:
docker build -t rtsp-hls-app .
docker run -p 3000:3000 \
-e CAMERA_USER=admin \
-e CAMERA_PASS=password \
-e CAMERA_IP=192.168.1.100 \
rtsp-hls-app
Docker Compose
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
CAMERA_USER: admin
CAMERA_PASS: password
CAMERA_IP: 192.168.1.100
volumes:
- ./static/hls:/app/static/hls
restart: unless-stopped
Nginx Reverse Proxy
server {
listen 443 ssl http2;
server_name stream.example.com;
ssl_certificate /etc/ssl/certs/cert.pem;
ssl_certificate_key /etc/ssl/private/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
location /hls/ {
alias /app/static/hls/;
expires 1h;
add_header Cache-Control "public, max-age=3600";
}
}
Advanced Topics
Custom Video Filters
Add effects or transformations:
'-vf', 'scale=1280:720,fps=30,format=yuv420p'
Audio Processing
'-af', 'aresample=44100' // Resample to 44.1kHz
Statistics and Monitoring
Log stream statistics:
ffmpegProcess.stdout.on('data', (data) => {
console.log(`Stream stats: ${data}`);
});
Multiple Bitrate HLS (Adaptive)
Generate multiple quality versions:
// Create master playlist pointing to multiple variants
const masterPlaylist = `#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=5000000
high.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2500000
medium.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000
low.m3u8`;
Support & Resources
- FFmpeg Documentation
- HLS.js Documentation
- SvelteKit Documentation
- ONVIF Protocol - For camera device discovery
License
MIT