◆ ForgeVis
Skip to content

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 once

URL Format ​

Main Stream ​

http://server:8888/{camera_id}/index.m3u8

Example:

http://localhost:8888/camera_001/index.m3u8

Substream ​

http://server:8888/{camera_id}/sub/index.m3u8

Example:

http://localhost:8888/camera_001/sub/index.m3u8

Choosing 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.m3u8

It 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 startLevel starts on whatever fits its bandwidth estimate — usually not the source stream. So variants.m3u8 is meant for a player that sets the variant itself, while master.m3u8 gives 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 transcodeMaxConcurrent steps are encoded at once (2 by default); while all are busy, no new step is offered. A step nobody watches stops after muxerCloseAfter.
  • Segment times of every variant follow the wall clock, and every segment carries EXT-X-PROGRAM-DATE-TIME with 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 streamer role serves another node's camera without its substream — there it has no sub variant.

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.m3u8

Substream (Low Bandwidth) ​

  • Multi-camera dashboards
  • Mobile clients
  • Remote viewing over limited bandwidth
  • Video walls with many cameras
http://localhost:8888/camera_001/sub/index.m3u8

Performance ​

Lazy Initialization ​

HLS muxers are created on-demand:

  1. First request to /camera_001/index.m3u8 creates muxer
  2. Muxer subscribes to StreamHub (camera connection)
  3. Converts H.264 frames to HLS segments
  4. Subsequent requests use existing muxer

Automatic Cleanup ​

Idle muxers are automatically removed:

  • Default timeout: 60 seconds after last request
  • Configurable via muxerCloseAfter in 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 ​

  1. Check HLS server is enabled and running:
bash
curl http://localhost:8888/camera_001/index.m3u8
  1. Verify camera is configured correctly

  2. Check browser console for CORS errors

CORS Errors ​

HLS server allows all origins by default. If issues persist:

  1. Check browser network tab for actual error
  2. Verify Access-Control-Allow-Origin: * header in response
  3. 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 lowLatencyMode in hls.js
  • Reduce segmentDuration and partDuration in config

Playback Stuttering ​

  1. Check network bandwidth
  2. Use substream for low-bandwidth scenarios
  3. Increase buffer length in player configuration
  4. 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 ​

FeatureHLSRTSP
ProtocolHTTPRTSP/RTP
Latency3-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

See Also ​

Proprietary software.