Documentation
Complete guide to installing StreamCTL, creating live channels, choosing output protocols, and operating streams in production.
Installation
One-line install
The installer auto-detects your architecture (amd64/arm64), downloads the latest release, and sets up the systemd service.
$ curl -fsSL https://streamctl.com/install.sh | sudo bash
This installs to:
| /usr/local/bin/streamctl | Binary |
| /etc/streamctl/config.json | Configuration |
| /etc/streamctl/license.json | License key (after purchase) |
| /var/lib/streamctl/ | Data (streams, profiles, cache) |
Manual install
# Download $ VERSION=$(curl -fsSL https://streamctl.com/releases/latest) $ wget https://streamctl.com/releases/${VERSION}/streamctl-${VERSION}-linux-amd64.tar.gz $ tar xzf streamctl-${VERSION}-linux-amd64.tar.gz $ sudo install -m 755 streamctl /usr/local/bin/ $ sudo mkdir -p /etc/streamctl /var/lib/streamctl $ sudo cp config.example.json /etc/streamctl/config.json
Uninstall
$ curl -fsSL https://streamctl.com/uninstall.sh | sudo bash
First Run
Edit the config file to match your network:
$ sudo nano /etc/streamctl/config.json
{
"transcode_ip": "10.99.99.1", // output IP for transcoded streams
"transcode_iface": "streamctl0", // output network interface
"start_port": 10000, // first UDP port for output
"hardware_stream_limit": 0, // 0 = unlimited on licensed installs
"listen_addr": "127.0.0.1:8080", // dashboard (behind nginx)
"service_user": "root", // user for generated stream services
"service_group": "video", // group with GPU/video permissions
"vlan_interfaces": { // input VLANs
"eth1.100": "172.16.100.10",
"eth1.200": "172.16.200.10"
}
}
Start the service:
$ sudo systemctl enable --now streamctl $ sudo systemctl status streamctl
Core Concepts
StreamCTL is a channel control plane. FFmpeg does the media processing; StreamCTL stores the channel definition, renders the right FFmpeg command from a profile, creates a systemd service, and monitors the running output.
| Concept | Meaning |
|---|---|
| Stream | A live input and output definition, usually deployed as transcode-{id}.service |
| Channel | The user-facing result: a live or looping program feed delivered over UDP, RTMP, HLS, DASH, ABR, or file output |
| Profile | An FFmpeg command template. Profiles decide whether to copy, remux, transcode, segment, or publish to another protocol |
| Output | The destination for a channel: auto-assigned UDP, custom URL, web segment path, RTMP endpoint, or file path |
| Playout | A file playlist that becomes a continuous live channel and loops until stopped |
| Schedule | A timed playout channel with configurable multi-day cycles, fixed slots, XMLTV output, and EPG-style operation |
Common workflows
| Workflow | Input | Output |
|---|---|---|
| Live signal pass-through | UDP/RTP multicast or unicast | Clean UDP output for downstream muxes, providers, or local networks |
| Phone or camera contribution | RTMP/SRT/UDP contribution feed | RTMP relay, HLS/DASH web delivery, or UDP handoff |
| Web/mobile channel | Live input or playlist | HLS/DASH segments served by nginx or a CDN |
| 24/7 thematic channel | Files and playlists | Looping UDP, HLS, DASH, ABR, or RTMP channel |
| MPTS cleanup | One multicast carrying many programs | Separate SPTS services, one per selected program |
Dashboard
The dashboard is the main operating surface for StreamCTL. It uses the same REST API documented in API Reference, so anything you do in the browser can also be automated.
Fresh installs create a random API token at /etc/streamctl/api-token. When token auth is enabled, the dashboard shows a login page before any panel or API data is available. Use this command on the server to copy the token:
$ sudo cat /etc/streamctl/api-token
To create or rotate the token manually, generate a new token, store it with root-only permissions, and copy the printed SHA-256 hash into api_token_sha256 in /etc/streamctl/config.json:
$ sudo mkdir -p /etc/streamctl TOKEN="$(openssl rand -hex 32)" printf "%s\n" "$TOKEN" | sudo tee /etc/streamctl/api-token >/dev/null sudo chown root:root /etc/streamctl/api-token sudo chmod 600 /etc/streamctl/api-token printf "%s" "$TOKEN" | sha256sum | awk '{print $1}'
API clients use the same token as a bearer token:
$ curl -H "Authorization: Bearer $(sudo cat /etc/streamctl/api-token)" \ http://127.0.0.1:8080/api/streams
| Area | Use it for |
|---|---|
| Streams | Add live inputs, choose profiles, deploy/stop/restart services, inspect status and logs |
| MPTS โ SPTS | Scan a multi-program transport stream and create individual output channels |
| Playout | Create file playlists, fill a 24h loop, and deploy the result as a live channel |
| Schedules | Create timed channels with fixed start times and multi-day cycles |
| Monitor | Watch bitrate, CC errors, packet loss, FFmpeg warnings, service restarts, and stream health |
| Config | Set network output defaults, service user/group, alert webhook, license file, and backup/restore settings |
Architecture
StreamCTL sits between your sources and your delivery targets. It is not only an IPTV transcoder: it is a stream management layer that receives live signals, file loops, schedules, and contribution feeds, then runs the right FFmpeg service for each channel. The cleanest deployments separate three planes: management traffic for the dashboard/API, input traffic from contribution or provider networks, and output traffic for handoff to muxes, CDNs, players, or other providers.
Network planes
| Plane | Typical interface | Traffic | Notes |
|---|---|---|---|
| Management | lo / LAN NIC | Dashboard, REST API, nginx, license checks | Keep the dashboard behind nginx/SSL; bind StreamCTL itself to 127.0.0.1:8080 unless you intentionally expose it |
| Input | eth1, eth1.100, eth1.200 | UDP/RTP multicast, UDP unicast, SRT/RTMP contribution, Astra feeds | Use VLANs and multicast routes when feeds arrive on tagged provider networks |
| Processing | local host | FFmpeg, NVENC, playout, schedules, file jobs | Each deployed channel is a systemd service; logs and health checks are attached to that service |
| Output | streamctl0, output NIC, output VLAN, web root | UDP handoff, RTMP publish, HLS/DASH/ABR segments, file exports | Use generated UDP for local handoff/monitoring, or set a custom output URL for multicast groups, provider receivers, RTMP origins, or file paths |
Common topologies
| Topology | Recommended setup |
|---|---|
| Local channel lab | Use streamctl0 and generated UDP ports. Monitor locally and test with ffprobe/VLC on the StreamCTL host |
| Provider handoff | Set the stream output to the provider destination, for example udp://239.20.1.10:1234, udp://203.0.113.20:5000, or an RTMP/SRT endpoint |
| Web/mobile delivery | Use HLS, DASH, or ABR profiles writing to /var/www/hls, then serve with nginx or a CDN |
| MPTS fanout | Use multicast input for multi-program MPTS demux. If the source is unicast UDP, demux one program or first republish it as multicast |
| 24/7 file channel | Build a playout or scheduled channel, then output to UDP, RTMP, HLS, DASH, or ABR depending on the audience |
udp://10.99.99.1:PORT as a universal provider delivery address. It is the default local StreamCTL handoff address. For another server, mux, provider, or customer network to receive the stream directly, use a custom output URL that points to that receiver or multicast group.VLAN Setup
Use this section when your live inputs arrive on tagged VLANs, which is common for multicast contribution or provider feeds. If your inputs are RTMP/SRT over a normal network, local files, or a single untagged UDP source, you can skip VLAN setup.
Create VLAN interfaces
# Install VLAN support $ sudo apt install vlan $ sudo modprobe 8021q $ echo "8021q" | sudo tee /etc/modules-load.d/8021q.conf # Create VLANs on eth1 (your trunk port) $ sudo ip link add link eth1 name eth1.100 type vlan id 100 $ sudo ip link add link eth1 name eth1.200 type vlan id 200 # Assign IPs $ sudo ip addr add 172.16.100.10/24 dev eth1.100 $ sudo ip addr add 172.16.200.10/24 dev eth1.200 # Bring up $ sudo ip link set eth1 up $ sudo ip link set eth1.100 up $ sudo ip link set eth1.200 up
Make persistent
Add to /etc/network/interfaces:
auto eth1
iface eth1 inet manual
auto eth1.100
iface eth1.100 inet static
address 172.16.100.10/24
auto eth1.200
iface eth1.200 inet static
address 172.16.200.10/24
Configure in StreamCTL
Add VLANs to config.json:
{
"vlan_interfaces": {
"eth1.100": "172.16.100.10",
"eth1.200": "172.16.200.10"
}
}
eth1 shows NO-CARRIER, the cable is disconnected or the switch port is down.Output Interface (streamctl0)
StreamCTL creates a virtual network interface called streamctl0 during installation. This is a dummy interface with IP 10.99.99.1/24 used as the default local UDP output address. It gives generated channels a stable address for local monitoring, local handoff, and simple lab setups.
Automatic setup (default)
The installer creates the interface automatically:
# Created during install: $ ip addr show streamctl0 streamctl0: <BROADCAST,NOARP,UP> mtu 1500 inet 10.99.99.1/24 scope global streamctl0
This is the default output address for generated UDP channels. Use it when the consumer is local to the StreamCTL host or when your network is explicitly routed to that address. For a remote receiver, provider, multicast group, or customer network, set a custom output URL on the stream instead of relying on 10.99.99.1:PORT.
streamctl0 interface is a kernel dummy interface โ no physical cable needed. It provides a stable IP that never changes regardless of your hardware.How it's created
# What the installer runs: $ sudo ip link add streamctl0 type dummy $ sudo ip addr add 10.99.99.1/24 dev streamctl0 $ sudo ip link set streamctl0 up # Made persistent in /etc/network/interfaces or netplan
Alternative: Use a dedicated NIC
If generated UDP outputs should leave on a physical network, configure the output IP and interface for that network:
# In config.json: "transcode_ip": "192.168.1.100", "transcode_iface": "eno3"
Alternative: Use an existing VLAN
Output on an existing VLAN with an address that downstream systems can route to:
# In config.json: "transcode_ip": "172.16.100.50", "transcode_iface": "eth1.100"
Port assignment
StreamCTL assigns output ports deterministically from the stream ID:
| Stream ID | Output Port | Full Address |
|---|---|---|
| a110 | 10110 | udp://10.99.99.1:10110 |
| a098 | 10098 | udp://10.99.99.1:10098 |
| a001 | 10001 | udp://10.99.99.1:10001 |
Multicast & IGMP
StreamCTL automatically sends IGMP join requests when you deploy a stream with a multicast input. This tells your upstream switch/router to forward the multicast to the server.
Multicast routing
Add a multicast route so the OS knows which interface to use for IGMP:
$ sudo ip route add 239.0.0.0/8 dev eth1.100
For multiple VLANs with different multicast ranges:
$ sudo ip route add 239.1.0.0/16 dev eth1.100 $ sudo ip route add 239.2.0.0/16 dev eth1.200
Verify multicast traffic
$ tcpdump -i eth1.100 -c 10 udp and host 239.1.1.1
Adding Streams
Click + Add in the dashboard to create a stream-backed channel. A stream needs an input, a profile, and an output. The output can be generated automatically for UDP workflows or supplied explicitly when you want to publish to another URL or file path.
| Field | Example | Description |
|---|---|---|
| ID | a110 | Unique identifier, used for port calculation and service name |
| Input | udp://239.1.1.1:1234 | Source stream URL: UDP, RTP, SRT, RTSP, RTMP, HTTP(S), or file path |
| Output | udp://10.99.99.1:10110 | Destination URL or path. Leave blank for auto UDP output |
| Port | 10110 | Output UDP port, auto-calculated from ID when using generated UDP output |
| PNR | 110 | Program number for MPEG-TS metadata |
| TSID | 110 | Transport Stream ID (must be โฅ 1) |
| Profile | hd_nvenc | Encoding profile to use |
| Name | AMC HD | Channel name (injected into stream metadata) |
| Provider | AMC Networks | Service provider tag |
Input examples
| Source | Example | Notes |
|---|---|---|
| UDP multicast | udp://239.1.1.1:1234 | Use VLAN and multicast routing when the group arrives on a tagged network |
| UDP unicast | udp://@:1234 | Accepts a stream sent directly to the server |
| RTMP contribution | rtmp://encoder/live/cam1 | Useful for phone, camera, OBS, or remote encoder workflows |
| SRT contribution | srt://0.0.0.0:9000?mode=listener | Useful for internet contribution links with packet recovery |
| RTSP camera | rtsp://camera.local/stream1 | Useful for IP cameras and NVR sources |
| HTTP input | https://origin.example.com/live.m3u8 | Useful for web-origin streams or pull-based contribution |
| Local file | /videos/source.mp4 | Use for one-time file jobs or playout playlists |
Output examples
| Target | Example | Notes |
|---|---|---|
| Auto UDP | blank output + port 10110 | StreamCTL builds udp://10.99.99.1:10110 |
| Custom UDP | udp://239.20.1.10:1234 | Use when downstream systems expect a fixed multicast group |
| Custom UDP via VLAN | udp://239.20.1.10:1234?ttl=16&localaddr=192.168.114.25 | Use localaddr when a specific VLAN/local IP should source the multicast output |
| RTMP publish | rtmp://origin/live/channel1 | Use a custom profile or output URL for RTMP handoff |
| HLS path | /var/www/hls/channel.m3u8 | Use HLS profiles and serve the directory with nginx |
| File | /exports/channel.ts | Use for recording, remuxing, or one-time file conversion |
Profiles
Profiles are full FFmpeg command templates with variable substitution. They are the bridge between StreamCTL's channel model and FFmpeg's media engine. StreamCTL ships with built-in profiles such as:
| Profile | Use Case |
|---|---|
| hd_nvenc | 1080p H.264 NVENC, audio copy |
| sd_nvenc | 720p H.264 NVENC, audio copy |
| hd_nvenc_aac | 1080p + AAC audio transcode |
| audio_only | Video copy, audio โ AAC |
| copy | Remux only (no re-encoding) |
| file_nvenc | File โ file GPU transcode |
| file_copy | File remux (container change) |
Template variables
Available in all profile commands:
| ${INPUT} | Source URL with fifo_size for network streams |
| ${OUTPUT} | Output address or path. For generated UDP outputs, StreamCTL adds the configured address and packet options |
| ${PNR} | Program number |
| ${TSID} | Transport Stream ID |
| ${CHANNEL_NAME} | Channel name for service_name metadata |
| ${PROVIDER} | Provider name for service_provider metadata |
MPTS โ SPTS Demuxing
A single multicast may carry multiple TV channels (MPTS). StreamCTL can scan the input, show all programs, and split each into its own SPTS output.
How to use
- Go to the MPTSโSPTS tab
- Enter the MPTS multicast URL and click Scan
- Select which programs to extract
- Choose a profile for each (usually
copyfor just demuxing) - Click Demux Selected โ SPTS
Each program becomes a separate stream with its own UDP output port and systemd service. Uses -map 0:p:PNR to extract only the selected program.
udp://239.20.20.20:1234 or udp://streamctl0@239.20.20.20:1234. A unicast source such as udp://10.99.99.1:1234 may feed only one listener reliably, which can leave some SPTS outputs with no frames.Remuxing
Remuxing means changing the container or routing without re-encoding. Use the copy profile โ it copies both video and audio streams as-is.
Use cases:
- Route a channel from one multicast group to another
- Change TSID/PNR metadata without re-encoding
- Convert between UDP unicast and multicast
- Inject service_name and service_provider tags
- Pass through compatible SRT, RTSP, RTMP, HTTP(S), or file sources when the chosen container/profile supports them
fifo_size, overrun_nonfatal, and pkt_size=1316. For multicast output on a VLAN, add FFmpeg's UDP localaddr option, for example udp://239.20.1.10:1234?ttl=16&localaddr=192.168.114.25. Other protocols such as SRT, RTSP, RTMP, and HTTP(S) are treated as network sources too, but StreamCTL leaves their URLs untouched because those protocols use different option sets.Web Outputs: HLS, DASH, and ABR
StreamCTL can generate web-player outputs from live streams or playout channels. HLS is available through built-in profiles. DASH can be produced with a custom FFmpeg profile that writes an MPD and segments to your web root. ABR output creates multiple qualities for adaptive playback.
HLS-only profiles
These output only HLS segments (no UDP):
| Profile | Description |
|---|---|
| hls_copy | Remux to HLS segments, no re-encoding |
| hls_nvenc | Transcode to 1080p H.264 HLS with NVENC |
| hls_nvenc_sd | Transcode to 720p H.264 HLS with NVENC |
Dual output profiles (UDP + HLS)
These output both UDP multicast and HLS from a single FFmpeg process โ one decode, one encode, two outputs:
| Profile | Description |
|---|---|
| hd_nvenc_hls | 1080p NVENC โ UDP + HLS simultaneously |
| sd_nvenc_hls | 720p NVENC โ UDP + HLS simultaneously |
| copy_hls | Remux โ UDP + HLS simultaneously |
DASH and custom web outputs
For DASH or a custom packaging workflow, create a profile that writes to your web directory and uses variables such as ${INPUT}, ${CHANNEL_NAME}, and ${HLS_PATH}. The dashboard still manages deploy, stop, restart, logs, and service status.
ffmpeg -hide_banner -y -i "${INPUT}" \
-c:v h264_nvenc -c:a aac \
-f dash -seg_duration 4 \
"${HLS_PATH}/${CHANNEL_NAME}/manifest.mpd"
HLS configuration
When adding a stream with an HLS profile, three additional fields appear:
| Field | Default | Description |
|---|---|---|
| HLS Output Path | /var/www/hls | Directory where .m3u8 and .ts files are written |
| Segment Time | 10 | Duration of each .ts segment in seconds |
| Playlist Size | 6 | Number of segments kept in the m3u8 playlist |
Example: Dual output setup
# Stream config: Input: udp://239.1.1.1:1234 Profile: hd_nvenc_hls Output: udp://10.99.99.1:10110 # multicast for your network HLS Path: /var/www/hls # web server root Name: sport_hd Segment Time: 6 List Size: 4 # Results in: โ UDP stream at 10.99.99.1:10110 โ /var/www/hls/sport_hd.m3u8 โ /var/www/hls/sport_hd_0.ts, sport_hd_1.ts, ... # Old segments auto-deleted, playlist rolls
Serve HLS with nginx
location /hls/ {
root /var/www;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
}
Access at: https://yourserver.com/hls/sport_hd.m3u8
Template variables for HLS
| ${HLS_PATH} | Output directory for segments and playlist |
| ${HLS_SEGMENT_TIME} | Segment duration (seconds) |
| ${HLS_LIST_SIZE} | Number of segments in rolling playlist |
| ${CHANNEL_NAME} | Used for filenames: channel.m3u8, channel_0.ts |
File Transcoding
StreamCTL can transcode or remux files. Input a file path instead of a URL, and set the output to a file path.
Input: /home/videos/source.mkv
Output: /home/videos/output.mp4
Profile: file_nvenc
The service runs once, detects completion automatically, probes the output for metadata (codecs, duration, size), and cleans up the systemd service. Results appear in the File Transcode Jobs section.
File job webhook
StreamCTL can POST a JSON notification to a webhook URL when a file transcode job changes state. Set file_job_webhook_url in Config. If left blank, the system falls back to alert_webhook_url when one is configured.
Three events are sent โ one when the job starts, one on success, one on failure:
| event | When fired |
|---|---|
file_job_running | FFmpeg process started |
file_job_success | Job finished โ output probed and ready |
file_job_failed | FFmpeg exited with an error |
# Example payload โ file_job_success { "event": "file_job_success", "job_id": "movie01", "name": "Movie 01", "input": "/videos/source.mkv", "output": "/videos/output.mp4", "profile": "file_nvenc", "status": "success", "duration": "4m 12s", "file_size_bytes": 1267890000, "started_at": "2026-05-09T10:00:00Z", "completed_at": "2026-05-09T10:04:12Z" }
# Set the webhook URL via API $ curl -X POST http://127.0.0.1:8080/api/config/save \ -H "Content-Type: application/json" \ -d '{"file_job_webhook_url":"https://example.com/hooks/filejob"}'
24/7 Playout
StreamCTL can turn a collection of video files into a continuously looping live channel. This is useful for movie channels, promo loops, backup slates, local channels, music channels, training streams, or any feed that should keep running even when there is no live input.
How it works
- Open the Playout tab in the dashboard
- Click + New Playlist โ give it a name and choose an output type
- Add video files by path โ each is auto-probed for duration and codecs
- A progress bar shows how much of the 24h schedule is filled
- Click โถ Deploy โ creates a systemd service that loops the playlist forever
Output types
| Type | Output | Use case |
|---|---|---|
| UDP | udp://10.99.99.1:PORT | Multicast channel for your network |
| RTMP | rtmp://origin/live/name | Publish to another origin, encoder chain, or provider |
| HLS | /var/www/hls/name.m3u8 | Web/mobile streaming, single quality |
| DASH | /var/www/hls/name/manifest.mpd | Web/player delivery using MPEG-DASH packaging |
| ABR | /var/www/hls/name/master.m3u8 | Adaptive 480p+720p+1080p for web |
Playlist file format
Internally, StreamCTL generates an FFmpeg concat demuxer file and runs it with -stream_loop -1 for infinite looping:
# /var/lib/streamctl/playlist_movie_ch.txt file '/videos/inception.mkv' file '/videos/interstellar.mkv' file '/videos/arrival.mkv'
Example: Movie channel to HLS
# Create playlist via API $ curl -X POST http://127.0.0.1:8080/api/playlists \ -H "Content-Type: application/json" \ -d '{"id":"movies","name":"Movie Channel","output_type":"hls","loop":true}' # Add files $ curl -X POST http://127.0.0.1:8080/api/playlist/movies/add \ -d '{"path":"/videos/inception.mkv"}' # โ {"total":"2h 28m","remaining_24h":"21h 32m"} # Deploy $ curl -X POST http://127.0.0.1:8080/api/playlist/deploy/movies # โ playout-movies.service started, looping forever
Watermark overlay
Both loop and schedule channels support a PNG watermark that is composited top-right over the video at encode time. The watermark is rendered with alpha transparency, so use a PNG with a transparent background for a clean result.
Set the watermark in the channel edit panel or via the API using watermark_path (local file path) and watermark_scale (size). A per-channel value overrides the global default set in Config โ Global Watermark Path.
| watermark_scale value | Effect |
|---|---|
| blank | 10% of video width (default) |
15% | 15% of video width โ scales with resolution |
200 | Fixed 200 px wide on all outputs |
For percentage values, StreamCTL uses FFmpeg's scale2ref filter so the watermark automatically adapts to any source resolution. For ABR (multi-quality) output the watermark is scaled independently for each rendition (1080p / 720p / 480p).
# Set a global watermark at 15% of video width $ curl -X POST http://127.0.0.1:8080/api/config/save \ -H "Content-Type: application/json" \ -d '{"watermark_path":"/var/www/watermark.png","watermark_scale":"15%"}' # Override per-channel $ curl -X PUT http://127.0.0.1:8080/api/playlist/movies \ -H "Content-Type: application/json" \ -d '{"watermark_path":"/var/www/logo.png","watermark_scale":"200"}'
Scheduled channels
For EPG-style operation, create a scheduled channel instead of a simple loop channel. Schedules support configurable multi-day cycles, fixed start times, automatic next-free-slot placement, and the same UDP, RTMP, HLS, DASH, and ABR output patterns.
# Create a 7-day scheduled channel $ curl -X POST http://127.0.0.1:8080/api/schedules \ -H "Content-Type: application/json" \ -d '{"id":"prime","name":"Prime Channel","days":7,"output_type":"hls","hls_path":"/var/www/hls","xmltv_id":"prime.tv","epg_channel_name":"Prime Channel HD","provider":"StreamCTL","logo_url":"https://example.com/prime.png"}' # Add media to the next free slot $ curl -X POST http://127.0.0.1:8080/api/schedule/prime/slot \ -H "Content-Type: application/json" \ -d '{"path":"/videos/news.mp4","day":-1,"start_h":-1,"start_m":-1,"epg_title":"News Hour","description":"Morning edition"}' # Deploy $ curl -X POST http://127.0.0.1:8080/api/schedule/prime/deploy # โ schedule-prime.service started
Schedules can also generate XMLTV using channel metadata such as xmltv_id, epg_channel_name, provider, and logo_url. Programmes use each slot's epg_title and description.
$ streamctl schedule xmltv prime /tmp/prime.xml $ curl http://127.0.0.1:8080/api/schedule/prime/xmltv
Clock-driven positioning โ starting where the EPG left off
When a scheduled channel is deployed (or restarted), StreamCTL calculates the current wall-clock position inside the EPG cycle and seeks FFmpeg to that exact point. This means the channel always broadcasts the correct programme for the current time of day โ even after a server reboot, a crash-restart, or a manual redeploy. There is no drift: the start epoch anchors the cycle, and the scheduler computes day mod cycle_length and elapsed seconds within that day to find the right file and byte offset.
Astra Cesbo Import
Click Import Astra to read your Astra config file and import all stream entries. StreamCTL reads the make_stream entries and creates matching streams with auto-detected PNR, port, and name.
Configure the Astra config path:
"astra_config": "/etc/astra/astra.conf"
GPU & System Monitoring
The GPU tab is a paid operations view with two sub-tabs: GPU and System. GPU data comes from nvidia-smi; system data comes from local Linux counters under /proc and filesystem stats.
The GPU sub-tab shows real-time NVIDIA stats:
- Encoder / Decoder utilization %
- GPU memory used / total
- Temperature and power draw
- Per-process GPU usage, process command line, GPU index, and StreamCTL channel match when the FFmpeg command can be matched to a stream, file job, loop, or schedule
The System sub-tab helps operators spot host bottlenecks next to GPU load:
| Metric | Source / meaning |
|---|---|
| CPU | Overall CPU utilization, core count, and 1/5/15 minute load averages |
| RAM / swap | Total, used, available, cache, and swap use |
| Interfaces | State, IPs, RX/TX byte counters, live RX/TX rate in the browser, errors, and drops |
| Disks | Usage for root, StreamCTL data, configured HLS directory, custom HLS paths, and running local file job outputs when available |
The same data is available through GET /api/nvidia and GET /api/system for automation.
Stream Health Monitor
StreamCTL continuously monitors the quality of your running streams by combining two methods:
TS Probe (packet-level)
For MPEG-TS outputs, StreamCTL reads raw packets and checks the continuity counter on every PID. Any gap in the counter means a packet was lost. This is most useful for UDP/RTP transport streams.
| Metric | What it means |
|---|---|
| CC Errors | Continuity counter gaps โ packets lost in transit |
| CC Error Rate | Errors per second โ sustained rate matters more than one-time spikes |
| Bitrate | Measured throughput in kbps/Mbps |
| Packet Count | Total TS packets received during the probe window |
| PIDs | Per-PID breakdown of errors (video PID vs audio PID) |
Log Parser (FFmpeg-level)
Parses the systemd journal for each generated stream service over the last 30 minutes. This works across live, playout, scheduled, and file workflows because FFmpeg writes progress, warnings, and errors to the service log:
| Metric | Source |
|---|---|
| CC Errors | FFmpeg "continuity error" / "discontinuity" messages |
| Dropped Frames | FFmpeg drop=N counter |
| Speed | FFmpeg speed=1.0x โ below 1.0x means falling behind |
| Errors / Warnings | Any error or warning lines in the journal |
| Restarts | How many times the service restarted |
Status levels
| โ ok | Zero CC errors, stream healthy |
| โ warning | CC errors detected but rate below 1/sec |
| โ error | CC error rate โฅ 1/sec โ significant packet loss |
| โ offline | No data received from stream |
Background probing
StreamCTL automatically probes active UDP/RTP targets every 5 minutes when the target can be inspected directly. Results appear in the Monitor tab. The Monitor Refresh button calls POST /api/monitor/refresh, which runs a fresh probe for probeable UDP/RTP targets, refreshes recent logs, and updates the check time for service-only outputs.
HLS, RTMP, ABR, and other non-UDP outputs still appear in the Monitor tab, but they are service/log checks rather than packet probes. Their last_check value means StreamCTL checked the service and parsed recent logs; packet-only fields such as bitrate, packet count, and per-PID CC errors are only available for UDP/RTP probe targets.
streamctl probe <url> from the CLI to test any stream without a license.CLI Commands
StreamCTL includes built-in CLI tools โ no extra packages needed.
# Show version $ streamctl version streamctl 2.5.0 # Show hardware ID for licensing $ streamctl hwid 076158593ef21c5cb6e9424a637d734e # Scan a stream โ show codecs, resolution, bitrate $ streamctl scan udp://239.1.1.1:1234 Format: mpegts [0] video: h264 1920x1080 @ 6000000 bps [1] audio: aac 2ch 48000Hz (eng) # Probe for CC errors (default 3s, custom: 10s) $ streamctl probe udp://239.1.1.1:1234 10s Status: ok CC Errors: 0 Bitrate: 8542 kbps (8.5 Mbps) Packets: 150,230 # Show running streams $ streamctl status โ sport_hd active 2d 5h โ news_24 active 1d 12h 126 running # List available profiles $ streamctl profiles hd_nvenc HD H.264 NVENC hd_nvenc_hls HD NVENC + HLS copy Copy (Remux) ... # Manage channels through the local dashboard API $ streamctl streams list $ streamctl streams import $ streamctl stream deploy sport_hd $ streamctl stream logs sport_hd $ streamctl playlists list $ streamctl schedule deploy prime $ streamctl schedule xmltv prime /tmp/prime.xml # Update config values $ sudo STREAMCTL_CONFIG=/etc/streamctl/config.json streamctl config set hardware_stream_limit 32 # Create full backup $ sudo streamctl backup โ /etc/streamctl/config.json โ /var/lib/streamctl/streams.json โ /usr/local/bin/streamctl Backup: ./streamctl_2026-04-25_230000.backup.tar.gz # Show help $ streamctl help
Systemd Services
Every deployed live stream creates a systemd service at /etc/systemd/system/transcode-{id}.service. Playout channels use playout-{id}.service, and scheduled channels use schedule-{id}.service.
# View generated stream services $ systemctl list-units 'transcode-*' 'playout-*' 'schedule-*' # Check specific stream $ systemctl status transcode-a110 # View logs $ journalctl -u transcode-a110 -f
Live, playout, and scheduled channels use restart policies so they recover after FFmpeg exits or the server reboots. File transcodes use Restart=no because they are one-time jobs.
Editing a unit file from the dashboard
Every deployed loop or schedule channel shows a Unit button in its action row. Clicking it expands an inline editor pre-filled with the raw .service file content. After making changes, click Save & Restart โ StreamCTL writes the file, runs systemctl daemon-reload, and restarts the service in one step. This is useful for one-off FFmpeg flag tweaks without a full redeploy.
Security โ ffmpeg-only restriction
Before writing any unit file, StreamCTL validates the submitted content server-side. The save is rejected if any of the following conditions are met:
ExecStart=does not begin with/usr/bin/ffmpegor/usr/local/bin/ffmpegโ shell wrappers, scripts, and any other binary are blocked- Extra exec directives are present:
ExecStartPre,ExecStartPost,ExecReload,ExecStop,ExecStopPost - More than one
ExecStart=line, or noExecStart=at all
This prevents the unit editor from being used to run arbitrary commands even if an attacker gains dashboard access. Only FFmpeg arguments can be changed.
The same operation is available via the API:
# Read the current unit file $ curl http://127.0.0.1:8080/api/service-unit/playout-movies # Write edited content โ validated โ daemon-reload โ restart $ curl -X POST http://127.0.0.1:8080/api/service-unit/playout-movies \ --data-binary @/tmp/edited.service # โ {"ok":true} or {"error":"ExecStart must call the ffmpeg binary..."}
Service user and group
Generated stream services run as the configured service_user and service_group. The defaults are root and video, which work on most GPU servers. If you run streams as a restricted user, make sure it can read input files, write HLS output, use NVIDIA devices, and bind any required network interfaces.
{
"service_user": "streamctl",
"service_group": "video"
}
Nginx + SSL
StreamCTL always listens on 127.0.0.1:8080. Use nginx as a reverse proxy with SSL:
server {
listen 443 ssl http2;
server_name stream.yourcompany.com;
ssl_certificate /etc/letsencrypt/live/.../fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/.../privkey.pem;
# Optional extra layer:
# auth_basic "StreamCTL";
# auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
$ sudo certbot --nginx -d stream.yourcompany.com
Backup & Restore
StreamCTL can create a complete backup of your configuration, data, services, and binary in a single archive.
Create backup
Via CLI (recommended):
$ sudo streamctl backup โ /etc/streamctl/config.json (1.2 KB) โ /etc/streamctl/license.json (0.4 KB) โ /var/lib/streamctl/streams.json (8.5 KB) โ /etc/systemd/system/transcode-a110.service (0.5 KB) โ /usr/local/bin/streamctl (14.8 MB) Backup: ./streamctl_2026-04-25_230000.backup.tar.gz Files: 5 Size: 14.9 MB (compressed: 5.2 MB) # Custom output directory $ sudo streamctl backup /backup/
Via API (from dashboard or script):
$ curl -O http://127.0.0.1:8080/api/backup
What's included
| Path | Contents |
|---|---|
| /etc/streamctl/* | config.json, license.json, profiles.json |
| /var/lib/streamctl/* | streams.json, file_jobs.json, stream_info.json |
| /etc/systemd/system/transcode-*.service | Live stream service files |
| /etc/systemd/system/playout-*.service | Looping playout service files |
| /etc/systemd/system/schedule-*.service | Scheduled channel service files |
| /etc/systemd/system/streamctl.service | Main service file |
| /usr/local/bin/streamctl | The binary |
Restore from backup
# Extract to root filesystem $ sudo tar xzf streamctl_2026-04-25_230000.backup.tar.gz -C / # Reload systemd and restart $ sudo systemctl daemon-reload $ sudo systemctl restart streamctl
0 3 * * * /usr/local/bin/streamctl backup /backup/ 2>&1 | logger -t streamctl-backupLicensing
StreamCTL works in demo mode (2 streams) without a license. Paid licenses are hardware-locked to one server and are sold monthly or yearly. To unlock all features, purchase a license at streamctl.com.
Activate
- Get your Hardware ID:
streamctl hwid - Purchase a monthly or yearly license with your HWID at streamctl.com
- Download
license.jsonfrom the payment success page or from the delivery email - Upload
license.jsonvia the dashboard Config tab, or copy it manually
$ sudo cp license.json /etc/streamctl/ $ sudo systemctl restart streamctl
| Plan | Streams | Term | Version Lock |
|---|---|---|---|
| Free | 2 | No time limit | โ |
| Professional | Unlimited, hardware-limited | Monthly or yearly | Current major version |
| Enterprise | Unlimited, hardware-limited | Monthly or yearly | None |
Renew or upgrade
Every paid license email includes a renewal link. Use that link when renewing or upgrading so the store can attach the new payment to the same hardware license.
Newer license.json files also include the private renewal link. Run streamctl license on the server to show it when it is present.
- Open the renewal link from the license email
- Choose monthly or yearly, and optionally upgrade the plan
- After payment, download the new
license.json - Upload it in the dashboard Config tab, or replace the file manually and restart StreamCTL
$ sudo cp license.json /etc/streamctl/license.json $ sudo systemctl restart streamctl
Updates
Re-run the install script to update to the latest version:
$ curl -fsSL https://streamctl.com/install.sh | sudo bash
Your config, data, and license are preserved. Only the binary is replaced.