Appearance
HLS Streaming
ForgeVis provides built-in HLS (HTTP Live Streaming) server for web-based video playback. HLS uses HTTP protocol, making it firewall-friendly and compatible with CDNs.
Features
- ✅ Low-latency HLS with fMP4 segments
- ✅ Automatic stream initialization on first request
- ✅ Support for main and substreams
- ✅ CORS enabled for cross-origin playback
- ✅ Automatic cleanup of idle streams
- ✅ No transcoding by default: video from the camera is served as is. Transcoding starts only when a viewer picks 720p
Configuration
Enable HLS in config.yaml:
yaml
hls:
enabled: true
address: "0.0.0.0:8888" # Listen address and port
segmentDuration: "1s" # Segment duration for HLS segments
partDuration: "200ms" # Duration of LL-HLS parts
variant: "lowLatency" # "fmp4" (classic) or "lowLatency"
transcodeMaxConcurrent: 2 # How many 720p steps are encoded at onceURL Format
Main Stream
http://server:8888/{camera_id}/index.m3u8Example:
http://localhost:8888/camera_001/index.m3u8Substream
http://server:8888/{camera_id}/sub/index.m3u8Example:
http://localhost:8888/camera_001/sub/index.m3u8Choosing the quality
A camera's index.m3u8 and master.m3u8 are always its source stream alone. For a player that offers a quality choice, the camera has a separate playlist, variants.m3u8:
http://server:8888/{camera_id}/variants.m3u8It lists the variants in order: the source stream, the 720p step when one can be encoded, and the substream when one is configured.
#EXT-X-STREAM-INF:BANDWIDTH=1056000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1920x1080,AUDIO="audio"
media_0.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=483000,CODECS="avc1.640028,mp4a.40.2",RESOLUTION=1280x720,AUDIO="audio"
720p/media_0.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=282000,AUDIO="audio"
sub/media_0.m3u8- Which variant plays is the player's choice. hls.js without a
startLevelstarts on whatever fits its bandwidth estimate — usually not the source stream. Sovariants.m3u8is meant for a player that sets the variant itself, whilemaster.m3u8gives any client the source stream. - The 720p step and the substream start only once a player picks that variant. A substream nobody has watched is listed without resolution and codec — they become known after it is first watched.
- The 720p step is encoded by ffmpeg from this node's own RTSP restream, so it is offered only when the node has ffmpeg and the RTSP server is enabled (
rtsp.enabled). A camera no taller than 720p gets none, nor does a camera whose substream already reaches 720p. The step's keyframes fall on the camera's, and its bitrate is the source's share by pixel count, at most 1.5 Mbit/s. - At most
transcodeMaxConcurrentsteps are encoded at once (2 by default); while all are busy, no new step is offered. A step nobody watches stops aftermuxerCloseAfter. - Segment times of every variant follow the wall clock, and every segment carries
EXT-X-PROGRAM-DATE-TIMEwith the moment it starts. The variants line up in time, so a player can switch between them. - All variants share the audio of the source stream. Every player, Safari included, accepts the playlist that way, and the substream does not have to be started to find out whether it has audio.
- The camera encodes its substream with a separate encoder whose keyframes do not coincide with the source stream's. A switch to it goes through a short rebuffer rather than instantly.
- A cluster node with the
streamerrole serves another node's camera without its substream — there it has nosubvariant.
Camera Configuration
yaml
cameras:
camera_001:
source: "rtsp://camera-ip:554/main" # Main stream (HD)
sub: "rtsp://camera-ip:554/sub" # Substream (SD, optional)HLS URLs:
- Main:
http://localhost:8888/camera_001/index.m3u8 - Sub:
http://localhost:8888/camera_001/sub/index.m3u8
Web Player Integration
HTML5 Video
html
<!DOCTYPE html>
<html>
<head>
<title>ForgeVis HLS Player</title>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<video id="video" controls width="640" height="480"></video>
<script>
const video = document.getElementById('video');
const source = 'http://localhost:8888/camera_001/index.m3u8';
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(source);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
video.play();
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Native HLS support (Safari)
video.src = source;
video.addEventListener('loadedmetadata', function() {
video.play();
});
}
</script>
</body>
</html>Video.js
html
<!DOCTYPE html>
<html>
<head>
<link href="https://vjs.zencdn.net/8.6.1/video-js.css" rel="stylesheet" />
<script src="https://vjs.zencdn.net/8.6.1/video.min.js"></script>
</head>
<body>
<video id="player" class="video-js vjs-default-skin" controls preload="auto"
width="640" height="480">
<source src="http://localhost:8888/camera_001/index.m3u8"
type="application/x-mpegURL">
</video>
<script>
const player = videojs('player', {
liveui: true,
controls: true
});
</script>
</body>
</html>Multi-camera Dashboard
html
<div class="camera-grid">
<div class="camera">
<h3>Camera 001 - Main</h3>
<video id="cam1" controls autoplay muted width="400"></video>
</div>
<div class="camera">
<h3>Camera 001 - Sub</h3>
<video id="cam1-sub" controls autoplay muted width="400"></video>
</div>
</div>
<script>
function setupHLS(videoId, url) {
const video = document.getElementById(videoId);
if (Hls.isSupported()) {
const hls = new Hls({
lowLatencyMode: true,
backBufferLength: 90
});
hls.loadSource(url);
hls.attachMedia(video);
}
}
setupHLS('cam1', 'http://localhost:8888/camera_001/index.m3u8');
setupHLS('cam1-sub', 'http://localhost:8888/camera_001/sub/index.m3u8');
</script>Use Cases
Main Stream (High Quality)
- Recording archives
- Detailed monitoring
- Evidence collection
- Bandwidth is sufficient
http://localhost:8888/camera_001/index.m3u8Substream (Low Bandwidth)
- Multi-camera dashboards
- Mobile clients
- Remote viewing over limited bandwidth
- Video walls with many cameras
http://localhost:8888/camera_001/sub/index.m3u8Performance
Lazy Initialization
HLS muxers are created on-demand:
- First request to
/camera_001/index.m3u8creates muxer - Muxer subscribes to StreamHub (camera connection)
- Converts H.264 frames to HLS segments
- Subsequent requests use existing muxer
Automatic Cleanup
Idle muxers are automatically removed:
- Default timeout: 60 seconds after last request
- Configurable via
muxerCloseAfterin config - Frees memory and CPU resources
Resource Usage
Per active HLS stream:
- Memory: ~5-10 MB (segment buffer)
- CPU: Minimal — the camera stream is only repackaged
- 720p step: a separate ffmpeg process while it is watched; noticeably more CPU than repackaging
- Network: Same as camera bitrate
Troubleshooting
Stream Not Loading
- Check HLS server is enabled and running:
bash
curl http://localhost:8888/camera_001/index.m3u8Verify camera is configured correctly
Check browser console for CORS errors
CORS Errors
HLS server allows all origins by default. If issues persist:
- Check browser network tab for actual error
- Verify
Access-Control-Allow-Origin: *header in response - For credentials, configure specific origin in nginx reverse proxy
High Latency
HLS inherently has 3-6 second latency. For lower latency:
- Use RTSP restreaming instead
- Configure
lowLatencyModein hls.js - Reduce
segmentDurationandpartDurationin config
Playback Stuttering
- Check network bandwidth
- Use substream for low-bandwidth scenarios
- Increase buffer length in player configuration
- Check server CPU usage
Advanced Configuration
Behind Reverse Proxy
nginx
server {
listen 80;
server_name video.example.com;
location /hls/ {
proxy_pass http://forgevis:8888/;
proxy_http_version 1.1;
# CORS
add_header Access-Control-Allow-Origin *;
# Cache HLS segments
proxy_cache hls_cache;
proxy_cache_valid 200 1s;
}
}Load Balancing Multiple Servers
nginx
upstream hls_backend {
least_conn;
server forgevis1:8888;
server forgevis2:8888;
server forgevis3:8888;
}
server {
location /hls/ {
proxy_pass http://hls_backend/;
}
}Comparison with RTSP
| Feature | HLS | RTSP |
|---|---|---|
| Protocol | HTTP | RTSP/RTP |
| Latency | 3-6 seconds | <1 second |
| Firewall | ✅ Friendly | ❌ Often blocked |
| CDN Support | ✅ Yes | ❌ No |
| Browser Support | ✅ Native/hls.js | ❌ Requires plugin |
| Mobile Apps | ✅ Excellent | ⚠️ Limited |
| Seeking | ✅ Fast | ⚠️ Limited |
When to use HLS:
- Web-based playback
- Mobile applications
- Through firewalls/NAT
- CDN distribution
- Latency 3-6s acceptable
When to use RTSP:
- Real-time monitoring (<1s latency)
- VMS software
- Professional NVR systems
- Local network only