---
url: https://live777.pages.dev/guide/live777.md
---
# Live777 Core (liveion)

A Pure Single SFU Server for WebRTC.

`WHIP` / `WHEP` protocols are supported by default. When built with the `rtsp`
feature, liveion can also act as an RTSP server: push media in via
`ANNOUNCE/RECORD` and pull media out via `DESCRIBE/PLAY`.

a core SFU server, If you need a single server, use this

## Current support codecs {#codec}

| protocol | video codecs                        | audio codecs   |
| -------- | ----------------------------------- | -------------- |
| `WHIP`   | `AV1`, `VP9`, `VP8`, `H265`, `H264` | `Opus`, `G722` |
| `WHEP`   | `AV1`, `VP9`, `VP8`, `H265`, `H264` | `Opus`, `G722` |
| `RTSP`   | `AV1`, `VP9`, `VP8`, `H265`, `H264` | `Opus`, `G722` |

![live777-apps](/live777-apps.excalidraw.svg)

## Current client support {#client}

Application          | `AV1`  | `VP9`  | `VP8`  | `H265` | `H264` | `OPUS` | `G722` |
\------------------   | :----: | :----: | :----: | :----: | :----: | :----: | :----: |
Browser `whip`       | :star: | :star: | :star: | :star: | :star: | :star: | :star: |
Browser `whep`       | :star: | :star: | :star: | :star: | :star: | :star: | :star: |
Gstreamer `whip`     | :star: | :star: | :star: | :star: | :star: | :star: | :star: |
Gstreamer `whep`     | :tv: 2 | :star: | :star: | :star: | :star: | :star: | :star: |
Gstreamer `whipinto` | :tv: 1 | :star: | :star: | :star: | :star: | :tv: 1 | :star: |
Gstreamer `whepfrom` | :tv: 2 | :star: | :star: | :star: | :star: | :star: | :star: |
FFmpeg `whipinto`    | :shit: | :star: | :star: | :star: | :star: | :star: | :star: |
FFmpeg `whepfrom`    | :shit: | :star: | :star: | :star: | :star: | :star: | :star: |
VLC `whipinto`       | :shit: | :shit: | :star: | :star: | :star: | :star: | :shit: |
VLC `whepfrom`       | :shit: | :shit: | :star: | :star: | :star: | :star: | :shit: |
OBS Studio `whip`    | :tv: 3 | :shit: | :shit: | :star: | :star: | :star: | :shit: |

* :star: It's working
* :shit: Don't support
* :bulb: I don't know, No testing
* :tv: Note have some problem
  1. Working, But Browser can't player this video, Gstreamer to Gstreamer is working
  2. I don't know why av1 and whep error
  3. [OBS av1 codec can't play](https://github.com/binbat/live777/issues/169)

## Authentication

### No Authentication {#noauth}

::: danger
No Authentication is Default
:::

If no set any about `[auth]` section in configuration file, There will no authentication

### Bearer token {#token}

Static HTTP bearer token is super admin access, you should use in develop, test, debug or cluster manager

```toml
# WHIP/WHEP auth token
# Headers["Authorization"] = "Bearer {token}"
[auth]
# static JWT token, superadmin, debuggger can use this token
tokens = ["live777"]
```

### JWT(JSON Web Token) {#JWT}

Use this authentication, the token include access, you can control stream, publish, subscribe...

```toml
# WHIP/WHEP auth token
# Headers["Authorization"] = "Bearer {token}"
[auth]
# JSON WEB TOKEN secret
secret = "<jwt_secret>"
```

## Cascade

### What is cascade?

![cascade](/cascade.excalidraw.svg)

### Very large cluster

![mash-cascade](/mash-cascade.excalidraw.svg)

live777 Cascade have two mode:

* `cascade-pull`
* `cascade-push`

![live777-cascade](/live777-cascade.excalidraw.svg)

### Static cascade-pull (WHEP source)

`cascade-pull` can also be declared as a static stream input instead of an
API call. Build with the `source-whep` feature and add a `whep://` source to
a provisioned stream; it behaves like any other configured source
(`on_demand` start/stop, reconnect, RTCP keyframe feedback):

```toml
[[stream.cam1.sources]]
url = "whep://edge-0:7777/whep/cam1"
# With Bearer auth:
# url = "whep://token@edge-0:7777/whep/cam1"
```

When this source is used with `on_demand = true`, live777 waits at least
`35000ms` for the upstream WHEP source to become ready, even if
`on_demand_start_timeout_ms` is lower. This covers cold upstream on-demand
pulls that may spend the WHEP HTTP request timeout before delivering media.

The outgoing WHEP peer gathers ICE candidates using the server's own
`[[ice_servers]]` configuration (empty by default — host candidates only,
no hardcoded STUN server) and binds the UDP sockets from
`[webrtc] ice_udp_addrs`.

Chained on-demand pulls work one hop deep by default: this source's WHEP
HTTP request waits up to `40000ms` for the answer, covering an upstream
that itself holds subscribes for up to `35000ms` (an on-demand WHEP
source). Deeper chains need their budgets aligned per deployment. Stopping
this source (e.g. when the last subscriber leaves an `on_demand` stream)
may wait out an in-flight WHEP HTTP request, bounded by that same `40000ms`
timeout.

### Static cascade-push (WHIP target)

`cascade-push` can also be declared as a static stream output instead of an
API call. Build with the `target-whip` feature and add a `whip://` target
to a provisioned stream:

```toml
[[stream.cam1.targets]]
url = "whip://edge-1:7777/whip/cam1"
# With Bearer auth:
# url = "whip://token@edge-1:7777/whip/cam1"
```

The push is media-driven: it is established when the stream gains a
publisher (WHIP or a configured source) and torn down when the publisher
goes away, so the downstream node sees ordinary publisher attach/detach
cycles and its own `auto_delete_*`/on-demand strategies keep working. A
configured target on an `on_demand` stream acts as standing demand: its
sources are (re)started whenever the stream has neither a publisher nor a
push session, so the relay recovers on its own once an unreachable
downstream is back. A lost session (downstream close, ICE failure,
provisioned-stream reset) is re-established with exponential backoff
(5s doubling, 60s cap) — the same pacing bounds the on-demand source
restarts, at roughly one attempt per minute.

## DataChannel Forward

> NOTE: About `createDataChannel()`
>
> 1. Live777 Don't support `label`, `createDataChannel(label)` this `label` is not used
> 2. Live777 Don't support `negotiated`, `{ id: 42, negotiated: true }` this don't support

![live777-datachannel](/live777-datachannel.excalidraw.svg)

## RTSP Server

Build with the `rtsp` feature to expose an RTSP server alongside WHIP/WHEP:

```bash
cargo build --release --bin live777 --features rtsp
```

Configure the listen URL in `live777.toml`.  When credentials are present in
the URL, Digest authentication is enabled automatically:

```toml
[rtsp]
# No auth:
listen = "0.0.0.0:8554"
# With Digest auth:
# listen = "rtsp://admin:secret@0.0.0.0:8554"
```

The same port handles both directions:

* Push media into a stream: `rtsp://host:8554/{stream_id}` (`ANNOUNCE` + `RECORD`)
* Pull media from a stream: `rtsp://host:8554/{stream_id}` (`DESCRIBE` + `PLAY`)

Both UDP and TCP (`RTP/AVP/TCP`) transports are supported.  The first URL path
segment is used as the liveion stream identifier.

## Stream Hooks

Live777 can run external scripts on stream lifecycle events: stream
creation/deletion, and publish start/stop (a WHIP/cascade publisher
attaching, or a configured source starting/stopping). A typical use is
on-demand source activation for devices live777 cannot drive directly: a
hook starts a capture device / hardware encoder when media is actually
wanted, and stops it when the last consumer leaves to save resources.

> Built-in alternative: configured sources (`[[stream.<name>.sources]]`)
> support `on_demand = true`, which starts/stops the camera or RTSP pull
> automatically with subscriber presence — no scripts needed. See
> [livehal — Provisioned streams and on-demand sources](./livehal#provisioned-streams-and-on-demand-sources).

> Which events signal "someone is watching"? For **provisioned** streams
> (any `[stream.<name>]` entry) `stream-created` fires once at startup, so
> it cannot drive power control — use `publish-started` / `publish-stopped`
> instead. Configured sources report their start/stop as publish events
> with the session id `virtual-source`. For **dynamic** streams
> (`auto_create_whep`), `stream-created` still coincides with first use.

```toml
# Global hooks, run for every stream.
[hooks]
timeout_ms = 5000    # per-script timeout, 0 disables
on_error = "stop"    # "stop" skips the remaining hooks of the same event;
                     # "continue" runs them anyway
on_stream_created = ["/etc/live777/hooks/notify.sh"]
on_stream_deleted = ["/etc/live777/hooks/notify.sh", "/etc/live777/hooks/cleanup.sh"]
on_publish_started = ["/etc/live777/hooks/camera-power.sh"]
on_publish_stopped = ["/etc/live777/hooks/camera-power.sh"]

# Per-stream hooks, run after the global ones.
[stream.cam1.hooks]
on_stream_created = ["/etc/live777/hooks/cam1-created.sh"]
on_stream_deleted = ["/etc/live777/hooks/cam1-deleted.sh"]
on_publish_started = ["/etc/live777/hooks/cam1-power-on.sh"]
on_publish_stopped = ["/etc/live777/hooks/cam1-power-off.sh"]
```

Execution guarantees:

* Hooks of one event run sequentially: global first, then per-stream, in
  configured order.
* Events are processed in a single FIFO queue — all hooks of an earlier
  event finish before any hook of a later event starts, so a stream's
  `stream-created` hooks always complete before its `stream-deleted` hooks begin.
* A failed script (non-zero exit, spawn error, timeout kill) is logged and
  handled per `on_error`; it never affects later events or the server.

Scripts are executed directly (no shell) and receive the event metadata both
as argv and as environment variables:

| argv                | env               | value                                                                                     |
| ------------------- | ----------------- | ----------------------------------------------------------------------------------------- |
| `$1`                | `LIVE777_EVENT`   | `stream-created` / `stream-deleted` / `publish-started` / `publish-stopped`               |
| `$2`                | `LIVE777_STREAM`  | stream name                                                                               |
| `$3` (deleted/stopped only) | `LIVE777_REASON` | stream-deleted: `api-deleted` / `publish-leave-timeout` / `subscribe-leave-timeout` / `orphaned` / `reset`; publish-stopped: `peer-closed` / `api-deleted` / `replaced` / `idle-timeout` |
| —                   | `LIVE777_SESSION` | (publish events only) publisher session id; `virtual-source` for configured sources       |

Notes:

* Scripts should return quickly after initiating their work (e.g. launch an
  encoder in the background). A blocked script blocks the whole hook queue.
* Make scripts idempotent: a `stream-deleted` hook also runs when the publisher
  itself died (`publish-leave-timeout`), so stop scripts must tolerate an
  already-stopped device.
* For on-demand sources, combine with `[strategy] auto_create_whep = true`
  and a generous `auto_delete_whep` (e.g. `30000`) so brief subscriber
  flapping does not cycle the hardware. Per-stream overrides live under
  `[stream.<name>.strategy]`. Streams configured with `on_demand = true`
  already debounce via `on_demand_close_after_ms`.
* No hooks fire on server shutdown (no `stream-deleted` events are emitted
  then).

## Source change hooks

`on_source_changed` hooks fire when a source's parameter set is changed
through the admin tier API, `POST /api/sources/{stream}/tier` — a tier is
a named preset of source parameters (see
[Bitrate telemetry & quality tiers](./livehal#bitrate-telemetry--quality-tiers)),
so applying one changes the source. Unlike the lifecycle hooks above,
they run **synchronously inside the apply**, before the tier's
capture+encoder re-provisioning — they never go through the FIFO hook
queue.

This is where hardware that must match the new rung *before* the pipeline
rebuild belongs: on platforms where framerate is a sensor-mode property the
capture node cannot change (e.g. Rockchip V4L2 pipelines, `VIDIOC_S_PARM`
unsupported on rkisp1), the script pokes the sensor's mode gear, then the
rebuild streams into the already-correct hardware state.

Execution contract:

* Global `[hooks]` scripts first, then per-stream, in configured order —
  all awaited before the re-provisioning. Rapid tier applies are
  serialized, so a script cannot interleave with another tier's rebuild.
* A failing script aborts the tier apply when `on_error = "stop"`, before
  the pipeline is touched (the API call fails).
* When the apply does not reach the target state — aborted, or the
  pipeline rebuild failed and rolled back — the scripts run **again** with
  the source's *current* state, so hardware they switched is switched
  back. This compensation run is best effort (failures are only logged).
* Make scripts idempotent: "bring the hardware in line with the given
  values" is the whole contract, and the same script serves both runs.

Metadata (argv is `<stream> <tier>`; there is no `LIVE777_EVENT` — the
hook only ever fires for this one event):

| argv / env               | value                                                                       |
| ------------------------ | --------------------------------------------------------------------------- |
| `$1` / `LIVE777_STREAM`  | stream name                                                                 |
| `$2` / `LIVE777_SOURCE_TIER` | tier name (empty when the source runs its configured base profile)      |
| `LIVE777_SOURCE_WIDTH`   | pre-apply run: declared tier capture width (empty when inheriting base); compensation run: the pipeline's actual current width |
| `LIVE777_SOURCE_HEIGHT`  | declared tier capture height / actual current height                        |
| `LIVE777_SOURCE_FPS`     | declared tier capture framerate / actual current framerate                  |
| `LIVE777_SOURCE_BITRATE` | tier bitrate in bits/s / actual current bitrate                             |

```toml
[stream.cam.hooks]
# Switch the sensor mode gear ahead of the pipeline rebuild; on a failed
# apply the same script runs again with the restored state and switches
# the gear back.
on_source_changed = ["/etc/live777/hooks/sensor-gear.sh"]
```
