---
title: "Playback & distribution"
description: "Get playback URLs and the full playlist, choose the right credential, and understand how on-demand channels start and stop."
---

> Documentation Index
> Fetch the complete documentation index at: https://kiln.wbxdocs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Playback & distribution

After you configure a channel, two questions remain: which URL should the player open, and which credential should it use? The sections below cover every playback endpoint.

## Playback URLs

Each channel has exactly one entry point. Everything else is generated by the playlist, so you never assemble these by hand:

- `/v1/play/{id}/index.m3u8`: the entry point. HLS channels get the rewritten upstream playlist; DASH channels get the repackaged master playlist.
- `/v1/play/{id}/live/{file}`: media playlists, segments, init segments, and subtitles produced locally by the DASH repackaging path.
- `/v1/play/{id}/u/{upstream}`: the same-origin fetch proxy for HLS channels. `{upstream}` is the encoded upstream address and carries a `?sig=` signature; the target host must also be within the allowed set. Failing either check is rejected outright.

Each DASH restart produces a new publication generation, carried as `?g=` on the URL. Media playlist requests are redirected with 307 to the current generation, and a request for a generation that is already gone returns 410 with `Retry-After: 1`. Players roll onto the new generation on their own, with no manual refresh needed.

Segments and init segments carry long-lived cache headers; playlists are always `no-store`.

## Authentication

Playback routes require a credential by default, governed by `security.play_require_auth`. The two ways to pass one are exactly equivalent, with the query parameter taking precedence:

```bash
curl -s "http://127.0.0.1:8080/v1/play/hls-demo/index.m3u8?token=$TOKEN"
curl -s http://127.0.0.1:8080/v1/play/hls-demo/index.m3u8 -H "authorization: Bearer $TOKEN"
```

The query parameter exists to accommodate players: most IPTV clients will not attach custom headers to segment requests, so playlist URLs consistently carry `?token=`.

A non-admin session scoped to a channel subset can only play channels in that subset; anything else returns 403.

> **Debug switch**
>
> `KILN_PLAY_OPEN=1` disables playback authentication and `KILN_PLAY_OPEN=0` forces it on, with the environment variable overriding the config file. Use it only for local investigation, never on a deployment reachable from anywhere else.

## The full playlist

`GET /v1/playlist.m3u` returns an M3U of every enabled channel and accepts a login session only. An admin API token is rejected here, because this route is not registered among the routes a token can reach.

What it does:

- The credential used for the request is embedded verbatim as `?token=` on every playback URL, so the playlist stays valid exactly as long as the session token does.
- A session scoped to a channel subset only receives that subset.
- When the guide is enabled, the `#EXTM3U` line carries `x-tvg-url` pointing at Kiln's own EPG endpoint.
- Disabled channels are omitted.

When you need a playlist that outlives a session and can be handed to a player long-term, use a playback key instead.

## Path-based playback keys

A playback key lives in the URL path rather than a query parameter, so the whole link can be distributed as one string and usage can be attributed by key prefix in the logs.

- `/p/{token}/playlist.m3u`: the playlist within that key's scope.
- `/p/{token}/play/{id}/index.m3u8`: the entry point.
- `/p/{token}/play/{id}/live/{file}`: locally produced media.
- `/p/{token}/play/{id}/u/{upstream}`: the same-origin fetch proxy.

Playlists served under this prefix carry no `?token=`; every entry stays inside `/p/`.

### Creating and managing keys

Create and maintain keys under Playback Access Control in the admin console, or through the `/v1/admin/access-tokens` endpoints.

- **Shown once**: the plaintext is returned only at creation. The server keeps a SHA-256 digest and the first ten characters as a prefix, so a lost key can only be replaced.
- **Scopable**: a key covers every channel by default, or a chosen subset. Channels outside the scope return 403 and never appear in that key's playlist.
- **Optional expiration**: an expiration date invalidates the key automatically.
- **Revocable**: revoking or disabling takes effect immediately, with no restart.
- **Auditable**: every use writes a playback access log entry.

### Playback access log

Each use records the key prefix, channel, status code, client IP, and request path, and failed authentication attempts are recorded too. The logged path is redacted down to the key prefix, so a complete key never reaches the log.

Entries are retained by day count, 30 days by default and adjustable in settings. The console filters by key and by channel, which is what answers "who used this link, and when".

## On-demand start and reclaim

From a viewer's point of view, the full lifecycle of an `on_demand` channel looks like this:

1. **The first viewer triggers the start**

   Kiln only begins pulling the upstream when an `index.m3u8` request arrives with no session running. Concurrent first requests for the same channel trigger exactly one start and the rest wait on that same result, so the upstream never sees duplicate connections.
2. **Waiting for the first playlist**

   An HLS channel fetches the upstream playlist as soon as it has a session. A DASH channel has to wait for the packager to produce its first playlist, and returns 502 with `playlist not ready` until then, which players simply retry. To remove that wait, prewarm the channel with Start now in the console, or set it to prewarm at startup.
3. **Playback keeps the lease alive**

   Every playlist or segment fetch afterwards refreshes the session's activity timestamp. As long as someone is watching, the session is never reclaimed.
4. **Reclaimed once nobody watches**

   The reaper sweeps every 5 seconds. An `on_demand` channel idle for longer than `idle_timeout_sec` (90 seconds by default) has its session stopped, its upstream connection dropped, and its working directory cleaned up. The next viewer starts the whole sequence again.

A channel with `on_demand` off and `autostart` on is never reclaimed and holds its upstream connection permanently, at the cost of resident memory and bandwidth. That suits frequently watched channels; leave everything else on demand.

Channels with `max_viewers` set get one more layer: the entry request is redirected with 307 to add a signed `viewer=` lease, the lease expires if it is not renewed within a minute, and viewers beyond the ceiling receive 429.

## Playlist rewriting

An HLS channel's upstream playlist is rewritten before it reaches the player: relative references are resolved to absolute addresses, and entries that must be fetched through Kiln are pointed at `/v1/play/{id}/u/...` with a signature attached. Whether an entry is rewritten is decided by the `playlist_policy` outbound setting, `rewrite` by default; channels with `max_viewers` set are rewritten unconditionally, since viewer counting is impossible otherwise. Policy values and host-based routing rules are covered in [Outbound proxying](/en/guide/proxy/).

Source: https://kiln.wbxdocs.com/en/guide/playback/index.mdx
