Skip to content

Channels & upstreams

Configure channels and upstreams, understand each field, and manage enablement, import, export, and preflight checks.

Updated View as Markdown

A channel is Kiln’s core object. It combines an upstream address, an input format, and a run policy, then exposes one playback URL and one entry in the playlist. The sections below follow the order of the configuration fields.

The channel model

Identity and presentation

  • id: the unique identifier. It appears in playback URLs, in the playlist’s tvg-id attribute, and in access logs. Changing it creates a different channel as far as players are concerned, so favorites and history on the client side are lost.
  • title: display name. Falls back to id when empty.
  • group: group name, written to the playlist’s group-title attribute.
  • logo_url: logo address. When it is empty and a built-in logo candidate matches, the playlist points at /v1/logo/{id} instead and Kiln fetches the image for you.
  • epg_id, epg_name, epg_source: guide matching fields. epg_name falls back to title. See EPG.

Stream source

There are two ways to point at a source, and you pick exactly one:

  • upstream + path: upstream references an id from [[upstreams]], and path is appended to that upstream’s base_url. A path starting with http:// or https:// is treated as absolute and used as is.
  • source_url: a complete address written inline. It replaces both upstream and path, and a channel using it inherits no upstream-level headers.

ingress decides how the source is handled. It is hls or dash, and defaults to hls when empty:

  • hls: same-origin segment proxying. The playlist is rewritten before it reaches the player and segments are fetched on demand.
  • dash: decrypted locally against the global kid:key catalog and repackaged to HLS. An enabled DASH channel requires [packager].keys_file to be configured, otherwise saving fails.

Run policy

  • on_demand: release the upstream connection when nobody is watching. Enabling it puts the channel under idle reclamation.
  • autostart: pull the channel up when the process starts, without waiting for a viewer.
  • idle_timeout_sec: how long counts as nobody watching. Values of zero or below normalize to 90 seconds. Only applies to on_demand channels.
  • max_viewers: concurrent viewer ceiling. Above zero it enables viewer leases and returns 429 once the ceiling is hit; empty or zero means unlimited.
  • restart_on_failure: restart automatically after the upstream drops. Forced on for DASH channels at save time.

The two switches combine into the three run modes shown in the admin console:

autostart on_demand Console label Behavior
off on Start when viewed The first viewer starts it, idle reclaims it
on on Prewarm at startup Started at boot, still reclaimed when idle
on off Always running Started at boot, never reclaimed

With both switches off, on_demand is turned on automatically, so there is no such thing as a channel that neither prewarms nor starts on demand.

Media selection

These fields apply only to the DASH repackaging path. HLS channels are proxied same-origin and no track plan is made:

  • prefer_height: target vertical resolution. Zero or empty falls back to [ffmpeg].prefer_height.
  • preferred_audio_languages: audio language codes in priority order, for example ["zh", "en"].
  • selection: finer-grained track selection with a separate mode and track for video, audio, and subtitles.
  • packager: sets the packaging engine for this channel and accepts the same values as [packager].engine. Leave it empty to use the global setting. Any other value fails configuration validation.

Fine-grained track selection

Video can be selected automatically, capped at a maximum resolution, or matched to one exact track. Audio can follow a language preference or accept only a specified track. Subtitles can also be turned off. Selectors can match key, language, role, codec, resolution, or DASH AdaptationSet and Representation IDs. The exact and only modes require at least one selector field.

With the ffmpeg engine, subtitles support auto and off only. See the channel configuration reference for every field and accepted value.

Request headers

  • user_agent: the User-Agent used when fetching this channel.
  • headers: extra request headers for this channel. Merged with the upstream-level headers, with the channel winning on duplicate keys.

Fixed headers are sent only to requests on the source origin. Cross-origin redirects and external playlist resources do not receive them. The HTTPS FFmpeg compatibility path rejects a request when it cannot preserve that origin boundary, preventing credential leakage.

Disabling

A channel with disabled set does not appear in the channel list or the playlist, and playback requests are answered as not found. A disabled DASH channel can be saved even before the global key file exists, which makes it easy to land the definition first and supply keys later.

Upstream definitions

Upstreams are defined in the configuration file only. They let several channels share one origin’s address and credentials:

[[upstreams]]
id = "origin"
base_url = "http://127.0.0.1:5050"

[upstreams.headers]
X-Custom = "value"
  • id: what a channel’s upstream field references.
  • base_url: the origin’s base address; a channel’s path is appended to it.
  • headers: request headers injected into every fetch for channels on this upstream. A channel’s own headers can override individual keys.

The matching channel:

[[channels]]
id = "hls-demo"
title = "Demo HLS"
group = "Demo"
upstream = "origin"
path = "/live/demo-hls"
ingress = "hls"
on_demand = true
autostart = false

Config file versus admin console

The two are not the same store, so keep these boundaries in mind:

  • Channels: the [[channels]] blocks are written into SQLite only on first start, while the channel table is still empty. From then on the database is the single source of truth: editing the config file neither reseeds nor overwrites console changes. Bulk-adjust existing channels through the console or the admin API instead.
  • Upstreams: [[upstreams]] is always read from the configuration file, and the console cannot add or edit them. Saving a channel validates that the referenced upstream exists in the config, and rejects it otherwise.
  • Public base URL: server.public_base_url is seeded into the settings table on first start, after which the settings table wins and the console can change it.
  • Outbound routes and EPG sources: seeded the same way, database-authoritative afterwards.

The lite variant creates no database. Its channels come entirely from the configuration file, read-only, with the admin API unavailable.

Enabling and disabling

  • Individual channels toggle through the disabled field; both the console switch and the admin API write to it.
  • Bulk operations map to POST /v1/admin/channels/enable-all and POST /v1/admin/channels/disable-all, and require the write scope.
  • Disabling a channel does not delete its definition or change its position in the list. It does stop the current playback session immediately and removes the channel from public listings.

M3U import and export

Import

Import runs in two passes, preview and apply, both through POST /v1/admin/import/m3u with the apply flag deciding which. The preview returns the planned action and reason for every entry so you can confirm before writing.

Parsing and generation rules:

  • Reads the group-title, tvg-logo, tvg-id, and tvg-name attributes on #EXTINF lines, plus the trailing title.
  • The channel id is slugged from tvg-id, falling back to the title, then to the URL path. Collisions get -2, -3, and so on, capped at 48 characters.
  • An address ending in .mpd is inferred as dash; everything else as hls.
  • Imported channels always write source_url and clear upstream and path.
  • Newly created channels default to on_demand enabled with a 90-second idle timeout.
  • An entry whose id already exists is treated as an update, and only non-empty fields overwrite the existing values.
  • Entries with an invalid address, or that fail validation, are skipped with the reason recorded in the preview.
  • Import never deletes channels that are missing from the list.
  • Apply carries the revisions read during the preview, so if anything changed in the meantime the whole batch returns 409 instead of clobbering someone else’s edit.

Export

POST /v1/admin/exports/m3u returns an M3U you can hand straight to a player. It automatically creates an unscoped playback key and builds every address under the /p/{token}/play/ prefix, so an admin credential is never embedded. When the export is no longer needed, revoke that key under Playback Access Control in the console to invalidate the whole file at once.

Self-checks: warmup, probe, and preview

These three are easy to confuse but do entirely different things. All of them require the refresh scope.

probe: test the source, create nothing

POST /v1/admin/channels/{id}/probe runs a single upstream check without creating a session or producing any media. For an HLS channel it fetches the source address, reads a small opening chunk, and returns the status code, content type, final address, and elapsed time. For a DASH channel it parses the manifest, plans tracks once against prefer_height and selection, and verifies that the global keys are in place. The channel editor can also probe an unsaved draft through POST /v1/admin/source-probes. Use it to confirm the address, headers, and route actually work.

warmup: bring the session up early

POST /v1/admin/channels/{id}/warmup starts a session in the background, or simply refreshes the activity timestamp if the channel is already running. It returns 202 immediately without waiting for the first playlist, so success means the startup path has begun, not that media is flowing. Its purpose is removing cold-start latency on on-demand channels.

preview: get a short-lived playback credential

POST /v1/admin/channels/{id}/preview issues a credential valid for five minutes and returns a complete playback URL carrying ?token=, ready for the console’s built-in player. A preview credential can only play; every admin endpoint rejects it, which makes it safe to share briefly with a colleague to confirm the picture.

The usual order is probe to confirm the source is reachable, warmup to confirm a session starts, and preview to confirm the picture is right. If all three pass and something is still wrong, head to Troubleshooting.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close