A contribution gateway for Open Live. It runs on a Linux box or a laptop at the venue alongside a local Strom, and drives it: Strom captures the cameras, encodes to H.264/AAC, and pushes MPEG-TS over SRT to a cloud-hosted Strom. The gateway builds those flows, keeps them running, and registers each feed with the Open Live API so it appears as an assignable source in Open Live Studio.
VENUE CLOUD
camera ──> Strom (local) ──MPEG-TS/SRT──────> Strom (cloud) ──> Open Live ──> Studio
▲
│ REST (create + start flow)
gateway ───────HTTPS (register source)─────────────> Open Live API
The gateway owns no media pipeline and keeps no state. Strom does the media; this is the control tool that means a venue is one command rather than a set of flows built by hand in Strom's editor.
See docs/DESIGN.md for the reasoning: why media never transits Open Live, which end of the SRT link dials, what is deliberately left to Strom, and what was removed.
Setting a box up from scratch? docs/QUICKSTART.md is the exact command
sequence, with what success and each failure look like. It works for a person and for an agent.
open-live-ingest # same as `up`
open-live-ingest up --devices "FaceTime,DeckLink" # only these, by name fragment or id
open-live-ingest up --all # include virtual devices, skipped by default
open-live-ingest up --test # a test pattern and tone, to commission the link
open-live-ingest up --reconfigure # ask for every setting again
open-live-ingest status # what is running, from Strom and Open Live directly
open-live-ingest down # stop and remove the flows and sources
open-live-ingest devices # what Strom can see
open-live-ingest setup # ask for settings without starting anything
open-live-ingest check # validate the settings filestatus --json and devices --json print one JSON document instead of a table.
Run it and it asks for what it needs, checks each answer, remembers it, then registers every capture
device Strom can see and starts streaming. Built for SSH: line-by-line prompts on any terminal, lists
picked with the arrow keys, no window. Colour steps back to plain text under NO_COLOR or on a dumb
terminal.
up stays in the foreground and owns what it started. Ctrl-C or SIGTERM stops and removes the
feeds. It ignores SIGHUP, so a closed session or a dropped link does not take a venue off air.
status and down work from a second session and need nothing from the running process: flows are
recognised by their derived ids and sources by the gateway's name prefix, so they behave the same
after a kill -9.
Three credentials exist in this system and it only ever asks for one:
| Credential | Who needs it | Asked for |
|---|---|---|
Open Live: an Open Source Cloud login, or Open Live's own API_KEY |
the gateway, to register sources | yes, and the kind is inferred from the URL |
The local Strom's STROM_API_KEY |
the gateway, only if that Strom runs with auth | only when Strom actually refuses without one |
| The cloud Strom's token | Open Live holds it itself | never |
A fourth is optional: a token of the gateway's own, which lets Studio see the venue and nothing more. See Seen from Studio.
Strom does the capturing and encoding, as its own process on the same machine. If one is already
listening at strom.url the gateway adopts it and never stops it. If nothing is listening, it
starts a headless one from strom.binary, with its own data directory beside the settings, and
stops it again on the way out. down also stops a Strom left behind by a hard kill. Set
strom.manage = false to insist on a Strom you run yourself.
Optionally, the gateway pushes what status reports to Open Live over one outbound WebSocket, so
a producer in Open Live Studio sees whether the venue is reachable, which cameras are live and at
what bitrate, without anyone shelling into the box. Open Live keeps the gateway as a resource of
its own and derives its health from the age of the last heartbeat.
The gateway identifies itself with a token of its own, not the API key, so a box at a venue holds nothing that can drive a production. Whoever runs Open Live creates the gateway with the API key and hands over the id and the token, which Open Live shows once:
curl -s -X POST -H "Authorization: Bearer $API_KEY" -H 'Content-Type: application/json' \
-d '{"name":"Venue A"}' https://open-live.example.com/api/v1/gateways
# {"id":"gw-…","name":"Venue A","health":"unknown",...,"token":"olgw_v1_…"}On the box, the id is a setup flag and the token an environment variable, like every credential:
OLI_OPEN_LIVE_GATEWAY_TOKEN='olgw_v1_…' open-live-ingest setup --non-interactive --gateway-id gw-…Setup opens the socket once and reports Open Live greets gateway gw-…, so a wrong token is caught
there rather than during the show. up then pushes a heartbeat as often as Open Live asks in its
greeting, five seconds by default: hostname, Strom and gateway versions, and for each input the
flow state, the Open Live source it registered as, and the uplink's bitrate, round-trip time, and
dropped packets. A stop announces itself before the feeds go down.
The socket is outbound only and carries no commands: nothing on it can start or stop a feed. Open
Live being unreachable is a warning and a retry with backoff, never a change to a flow, the same as
registration. setup --gateway-id "" turns it off. A lost token is replaced with
POST /api/v1/gateways/<id>/rotate-token, which keeps the id.
Written by setup to a per-user file, mode 0600 because it holds the Open Live credential:
~/.config/open-live-ingest/gateway.toml on Linux, ~/Library/Application Support/open-live-ingest/gateway.toml
on macOS, or wherever --config points. The same format can be written by hand; see
gateway.toml.example for every option.
| Variable | Overrides |
|---|---|
OLI_CONFIG |
settings file path |
OLI_STROM_URL |
strom.url |
OLI_STROM_API_KEY |
strom.api_key |
OLI_OPEN_LIVE_URL |
open_live.url |
OLI_OPEN_LIVE_API_KEY |
open_live.api_key |
OLI_OPEN_LIVE_AUTH_MODE |
open_live.auth_mode (direct or osc) |
OLI_OPEN_LIVE_GATEWAY_ID |
open_live.gateway_id |
OLI_OPEN_LIVE_GATEWAY_TOKEN |
open_live.gateway_token |
OLI_LOG_LEVEL |
log.level |
Every question has a flag, and --non-interactive runs the same checks without asking, failing
on anything missing or wrong instead of prompting. That is how a script, or an agent, sets a box
up:
OLI_OPEN_LIVE_API_KEY=... open-live-ingest setup --non-interactive \
--name "Venue" --open-live-url https://venue.eyevinn-open-live.auto.prod-se.osaas.io
open-live-ingest checkCredentials are never flags, because a flag shows in process listings and shell history. They come
from the environment variables above, or for Open Source Cloud from OSC_ACCESS_TOKEN or the CLI's
saved login. Given interactively, the same flags become the defaults the prompts show. setup --help lists them all.
In caller mode, the default, the SRT ports belong to the cloud Strom. Open Live reserves ports
from that Strom's port pool and publishes them on GET /api/v1/server-info together with the Strom
host, and the gateway registers each input with port 0 and Open Live assigns it a free one, so
several gateways can feed one Open Live; uplink.port_range is only a fallback for an
Open Live that publishes none. The published ports are a list, not a range — Strom's pool can have
holes — so they are usually but not always consecutive. In listener mode the ports are the venue's
own and uplink.port_range is required. up and status print the ports in use and where they
came from.
In direct mode the key is optional: a self-hosted Open Live with API_KEY unset leaves /api/v1
open. osc mode always needs a credential, and setup asks which of three to use:
| Choice | Credential | Where it lives |
|---|---|---|
env |
the OSC CLI's OSC_ACCESS_TOKEN |
the environment; nothing is stored |
cli |
npx @osaas/cli login, a browser sign-in where you pick the workspace |
~/.osc/token, written by the CLI |
paste |
a personal access token from the OSC web console | open_live.api_key, mode 0600 |
Setup then shows the workspace the token belongs to, lists that workspace's Open Live instances to pick from, and checks the pick before moving on. A typed address is always the last entry.
A CLI login lasts one hour, which covers setup but not a show, so for up use env or paste. A
key in the settings or in OLI_OPEN_LIVE_API_KEY wins over the environment, which wins over the
saved login; the gateway reads the login again at each token exchange, so signing in again renews a
running gateway. On a box with no browser, paste a token, or copy ~/.osc/token over from a machine
you signed in on.
One command installs the latest release and, unless one is already on PATH, the Strom release
it is built against, GStreamer included. Nothing is asked, so it works from a script too:
curl -fsSL https://raw.githubusercontent.com/Eyevinn/open-live-ingest/main/install.sh | shBinaries land in /usr/local/bin when it is writable, else ~/.local/bin. INSTALL_DIR,
VERSION, and SKIP_STROM=true override that; the script's header lists every knob.
Other ways in:
- By hand. Each release carries a
static binary for Linux (x86_64, aarch64) and macOS (Apple silicon, Intel), plus
SHA256SUMSand a signed build provenance you can check withgh attestation verify. The asset names do not carry a version, soreleases/latest/download/open-live-ingest-<target>.tar.gzis stable. - Rust users.
cargo binstall open-live-ingestfetches the same tarball.cargo build --releasebuilds from source with Rust 1.97.1, pinned inrust-toolchain.toml.
To keep it running across reboots, contrib/ has a systemd user unit and a launchd
agent, each with install steps in its header.
- Linux or macOS, with a Strom instance on the same machine that can see the capture hardware.
install.shinstalls Strom v0.8.0 when none is on PATH. If Strom's installer stops on an unaccepted Xcode license on a Mac that already has GStreamer, rerun withSKIP_GSTREAMER=true, which it passes through to Strom's installer - Node.js, only to sign in to Open Source Cloud with
npx @osaas/cli login. A pasted token needs none
The gateway itself is an ordinary HTTP client: no GStreamer, no device access, no privileges.
Strom's API types come from the strom-types crate, pinned in Cargo.toml to the Strom release
the venue runs.
Bump version in Cargo.toml, commit, then tag and push:
git tag v0.5.0 && git push origin v0.5.0The release workflow refuses a tag that does not match Cargo.toml, builds the four binaries,
and publishes them with a checksum file, build provenance, and generated notes. When the
strom-types pin moves, move STROM_VERSION in install.sh with it; a test checks they agree. Run the workflow by hand from the
Actions tab to build the artifacts without publishing.
The Strom pin workflow checks Strom's latest stable release daily and, when it differs from
the pin, opens one PR that moves both pins and the Requirements line to it
(.github/scripts/bump-strom-pin.sh does the edit, and works by hand too). Its CI run is the
compatibility check; release the gateway after merging it. A manual run can force a target tag.
The workflow needs a GitHub App with Contents and Pull requests write on this repository, in the
secrets STROM_PIN_APP_CLIENT_ID and STROM_PIN_APP_PRIVATE_KEY. GitHub disables scheduled
workflows in a public repository after 60 days without repository activity; if that happens,
re-enable it from the Actions tab (select Strom pin, then "Enable workflow").
Licensed under either of MIT or Apache-2.0, at your option.