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/streamctlBinary
/etc/streamctl/config.jsonConfiguration
/etc/streamctl/license.jsonLicense 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
The dashboard listens on 127.0.0.1 by default. Set up nginx reverse proxy with SSL for remote access. See Nginx + SSL.

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.

ConceptMeaning
StreamA live input and output definition, usually deployed as transcode-{id}.service
ChannelThe user-facing result: a live or looping program feed delivered over UDP, RTMP, HLS, DASH, ABR, or file output
ProfileAn FFmpeg command template. Profiles decide whether to copy, remux, transcode, segment, or publish to another protocol
OutputThe destination for a channel: auto-assigned UDP, custom URL, web segment path, RTMP endpoint, or file path
PlayoutA file playlist that becomes a continuous live channel and loops until stopped
ScheduleA timed playout channel with configurable multi-day cycles, fixed slots, XMLTV output, and EPG-style operation

Common workflows

WorkflowInputOutput
Live signal pass-throughUDP/RTP multicast or unicastClean UDP output for downstream muxes, providers, or local networks
Phone or camera contributionRTMP/SRT/UDP contribution feedRTMP relay, HLS/DASH web delivery, or UDP handoff
Web/mobile channelLive input or playlistHLS/DASH segments served by nginx or a CDN
24/7 thematic channelFiles and playlistsLooping UDP, HLS, DASH, ABR, or RTMP channel
MPTS cleanupOne multicast carrying many programsSeparate 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
AreaUse it for
StreamsAdd live inputs, choose profiles, deploy/stop/restart services, inspect status and logs
MPTS โ†’ SPTSScan a multi-program transport stream and create individual output channels
PlayoutCreate file playlists, fill a 24h loop, and deploy the result as a live channel
SchedulesCreate timed channels with fixed start times and multi-day cycles
MonitorWatch bitrate, CC errors, packet loss, FFmpeg warnings, service restarts, and stream health
ConfigSet 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.

INPUT STREAMCTL OUTPUT Broadcast Feed MPTS / SPTS Camera / Phone RTMP / SRT UDP Source multicast / unicast Files / Loops MKV / TS / MP4 VLAN Interfaces eth1.100 ยท eth1.200 Output Interface streamctl0 ยท 10.99.99.1 FFmpeg + NVIDIA NVENC Transcode ยท Remux ยท Playout ยท Segment GPU: Tesla T4 NVENC ยท Enc 47% ยท 2.1/15 GB Stream Monitor CC errors ยท bitrate ยท logs Dashboard + REST API 127.0.0.1:8080 โ†’ nginx SSL systemd services transcode / playout / schedule Web Segmenter HLS / DASH / ABR 13 Profiles built-in + custom Backup config + data + binary UDP / RTMP live handoff Mux / Provider downstream processing Customers private network HLS / DASH nginx / CDN / player File Output .mp4 / .ts

Network planes

PlaneTypical interfaceTrafficNotes
Managementlo / LAN NICDashboard, REST API, nginx, license checksKeep the dashboard behind nginx/SSL; bind StreamCTL itself to 127.0.0.1:8080 unless you intentionally expose it
Inputeth1, eth1.100, eth1.200UDP/RTP multicast, UDP unicast, SRT/RTMP contribution, Astra feedsUse VLANs and multicast routes when feeds arrive on tagged provider networks
Processinglocal hostFFmpeg, NVENC, playout, schedules, file jobsEach deployed channel is a systemd service; logs and health checks are attached to that service
Outputstreamctl0, output NIC, output VLAN, web rootUDP handoff, RTMP publish, HLS/DASH/ABR segments, file exportsUse generated UDP for local handoff/monitoring, or set a custom output URL for multicast groups, provider receivers, RTMP origins, or file paths

Common topologies

TopologyRecommended setup
Local channel labUse streamctl0 and generated UDP ports. Monitor locally and test with ffprobe/VLC on the StreamCTL host
Provider handoffSet 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 deliveryUse HLS, DASH, or ABR profiles writing to /var/www/hls, then serve with nginx or a CDN
MPTS fanoutUse 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 channelBuild a playout or scheduled channel, then output to UDP, RTMP, HLS, DASH, or ABR depending on the audience
Do not treat 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"
  }
}
Make sure the switch port is configured as a VLAN trunk carrying your input VLANs. If 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.

The 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 IDOutput PortFull Address
a11010110udp://10.99.99.1:10110
a09810098udp://10.99.99.1:10098
a00110001udp://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.

For MPTS โ†’ SPTS fanout, multicast input is strongly recommended. StreamCTL creates one FFmpeg service per selected program, so each service needs to receive a full copy of the same MPTS. Multicast gives every listener the same packets; unicast UDP usually does not.

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
StreamCTL handles IGMP joins automatically. You can see active memberships in the IGMP tab of the dashboard.

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.

FieldExampleDescription
IDa110Unique identifier, used for port calculation and service name
Inputudp://239.1.1.1:1234Source stream URL: UDP, RTP, SRT, RTSP, RTMP, HTTP(S), or file path
Outputudp://10.99.99.1:10110Destination URL or path. Leave blank for auto UDP output
Port10110Output UDP port, auto-calculated from ID when using generated UDP output
PNR110Program number for MPEG-TS metadata
TSID110Transport Stream ID (must be โ‰ฅ 1)
Profilehd_nvencEncoding profile to use
NameAMC HDChannel name (injected into stream metadata)
ProviderAMC NetworksService provider tag

Input examples

SourceExampleNotes
UDP multicastudp://239.1.1.1:1234Use VLAN and multicast routing when the group arrives on a tagged network
UDP unicastudp://@:1234Accepts a stream sent directly to the server
RTMP contributionrtmp://encoder/live/cam1Useful for phone, camera, OBS, or remote encoder workflows
SRT contributionsrt://0.0.0.0:9000?mode=listenerUseful for internet contribution links with packet recovery
RTSP camerartsp://camera.local/stream1Useful for IP cameras and NVR sources
HTTP inputhttps://origin.example.com/live.m3u8Useful for web-origin streams or pull-based contribution
Local file/videos/source.mp4Use for one-time file jobs or playout playlists

Output examples

TargetExampleNotes
Auto UDPblank output + port 10110StreamCTL builds udp://10.99.99.1:10110
Custom UDPudp://239.20.1.10:1234Use when downstream systems expect a fixed multicast group
Custom UDP via VLANudp://239.20.1.10:1234?ttl=16&localaddr=192.168.114.25Use localaddr when a specific VLAN/local IP should source the multicast output
RTMP publishrtmp://origin/live/channel1Use a custom profile or output URL for RTMP handoff
HLS path/var/www/hls/channel.m3u8Use HLS profiles and serve the directory with nginx
File/exports/channel.tsUse 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:

ProfileUse Case
hd_nvenc1080p H.264 NVENC, audio copy
sd_nvenc720p H.264 NVENC, audio copy
hd_nvenc_aac1080p + AAC audio transcode
audio_onlyVideo copy, audio โ†’ AAC
copyRemux only (no re-encoding)
file_nvencFile โ†’ file GPU transcode
file_copyFile 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

  1. Go to the MPTSโ†’SPTS tab
  2. Enter the MPTS multicast URL and click Scan
  3. Select which programs to extract
  4. Choose a profile for each (usually copy for just demuxing)
  5. 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.

When demuxing more than one program, use multicast input such as 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:

UDP/RTP inputs and outputs get MPEG-TS transport tuning such as 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):

ProfileDescription
hls_copyRemux to HLS segments, no re-encoding
hls_nvencTranscode to 1080p H.264 HLS with NVENC
hls_nvenc_sdTranscode 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:

ProfileDescription
hd_nvenc_hls1080p NVENC โ†’ UDP + HLS simultaneously
sd_nvenc_hls720p NVENC โ†’ UDP + HLS simultaneously
copy_hlsRemux โ†’ 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:

FieldDefaultDescription
HLS Output Path/var/www/hlsDirectory where .m3u8 and .ts files are written
Segment Time10Duration of each .ts segment in seconds
Playlist Size6Number of segments kept in the m3u8 playlist
Lower segment time (e.g. 4-6 seconds) reduces latency but increases segment count. Higher values (10-15) are more efficient for CDN caching.

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:

eventWhen fired
file_job_runningFFmpeg process started
file_job_successJob finished โ€” output probed and ready
file_job_failedFFmpeg 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

  1. Open the Playout tab in the dashboard
  2. Click + New Playlist โ€” give it a name and choose an output type
  3. Add video files by path โ€” each is auto-probed for duration and codecs
  4. A progress bar shows how much of the 24h schedule is filled
  5. Click โ–ถ Deploy โ€” creates a systemd service that loops the playlist forever

Output types

TypeOutputUse case
UDPudp://10.99.99.1:PORTMulticast channel for your network
RTMPrtmp://origin/live/namePublish to another origin, encoder chain, or provider
HLS/var/www/hls/name.m3u8Web/mobile streaming, single quality
DASH/var/www/hls/name/manifest.mpdWeb/player delivery using MPEG-DASH packaging
ABR/var/www/hls/name/master.m3u8Adaptive 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
Playout requires a licensed version. The playlist loops automatically โ€” when it reaches the end, it starts over from the beginning.

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 valueEffect
blank10% of video width (default)
15%15% of video width โ€” scales with resolution
200Fixed 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.

Scheduled channels require a licensed version. Use schedules when start times matter; use loop channels when simple continuous playout is enough.

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:

The System sub-tab helps operators spot host bottlenecks next to GPU load:

MetricSource / meaning
CPUOverall CPU utilization, core count, and 1/5/15 minute load averages
RAM / swapTotal, used, available, cache, and swap use
InterfacesState, IPs, RX/TX byte counters, live RX/TX rate in the browser, errors, and drops
DisksUsage 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.

GPU and system monitoring require a licensed version. In demo mode, the GPU tab is hidden.

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.

MetricWhat it means
CC ErrorsContinuity counter gaps โ€” packets lost in transit
CC Error RateErrors per second โ€” sustained rate matters more than one-time spikes
BitrateMeasured throughput in kbps/Mbps
Packet CountTotal TS packets received during the probe window
PIDsPer-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:

MetricSource
CC ErrorsFFmpeg "continuity error" / "discontinuity" messages
Dropped FramesFFmpeg drop=N counter
SpeedFFmpeg speed=1.0x โ€” below 1.0x means falling behind
Errors / WarningsAny error or warning lines in the journal
RestartsHow many times the service restarted

Status levels

โœ“ okZero CC errors, stream healthy
โš  warningCC errors detected but rate below 1/sec
โœ• errorCC error rate โ‰ฅ 1/sec โ€” significant packet loss
โ€” offlineNo 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.

Stream monitoring requires a licensed version. Use 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:

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:

Keep StreamCTL's built-in token auth enabled when exposing the dashboard. Nginx basic auth is optional as a second layer, but it is no longer required as the first security step.
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

PathContents
/etc/streamctl/*config.json, license.json, profiles.json
/var/lib/streamctl/*streams.json, file_jobs.json, stream_info.json
/etc/systemd/system/transcode-*.serviceLive stream service files
/etc/systemd/system/playout-*.serviceLooping playout service files
/etc/systemd/system/schedule-*.serviceScheduled channel service files
/etc/systemd/system/streamctl.serviceMain service file
/usr/local/bin/streamctlThe 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
Schedule automated backups with a cron job: 0 3 * * * /usr/local/bin/streamctl backup /backup/ 2>&1 | logger -t streamctl-backup

Licensing

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

  1. Get your Hardware ID: streamctl hwid
  2. Purchase a monthly or yearly license with your HWID at streamctl.com
  3. Download license.json from the payment success page or from the delivery email
  4. Upload license.json via the dashboard Config tab, or copy it manually
$ sudo cp license.json /etc/streamctl/
$ sudo systemctl restart streamctl
PlanStreamsTermVersion Lock
Free2No time limitโ€”
ProfessionalUnlimited, hardware-limitedMonthly or yearlyCurrent major version
EnterpriseUnlimited, hardware-limitedMonthly or yearlyNone

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.

  1. Open the renewal link from the license email
  2. Choose monthly or yearly, and optionally upgrade the plan
  3. After payment, download the new license.json
  4. 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
Renewals extend from the current paid expiry when the existing license is still active. If the license already expired, the new term starts from the renewal payment time.
Renew before the license expires. Expired licenses are rejected by verification; upload the new license file after renewal to restore paid status.

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.

Professional licenses are locked to the major version purchased (e.g. v2.x.x). Upgrading to v3.0 requires a renewal or upgrade license. Enterprise licenses work on any version.