Getting started
How an Orbit account is organised, what lives where in the console, and the shortest path from signing in to serving a name from our network.
A broadcast is published to one city over RTMPS and watched from all of them. This page covers creating a channel, going on air, the watch address, and what happens to a key when you change it.
A channel is the thing that has an address; a broadcast is one session on it. Create it once and use it as often as you like — the watch address does not change between broadcasts.
curl -X POST https://dash.yunzheng.space/api/v1/stream/streams \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Sunday service"}'The reply carries the stream key. It is shown once and cannot be read back — not by the API, not in the console, not by us. If it is lost, issue a new one.
| Field | What it sets |
|---|---|
name | What the channel is called. Up to 60 characters. |
visibility | unlisted (default) or public. There is no directory, so both are reached only by address. |
city | Where ingest lands. Omit it and the city with room takes it. |
placement | How the channel is reached: city (its city’s own address, across the public internet) or anycast (our own network, nearest to whoever is watching). Included from the Pro pack. |
renditions | How many qualities below the published one to produce, 0 to 4. Defaults to what the packs allow; a number above that is refused rather than quietly reduced. |
unlisted means the address cannot be guessed. It is not access control: anybody who has the address can watch.Two fields in any broadcasting software that speaks RTMP: the server and the key. The connection is RTMPS, and that is what every channel is given: the key travels inside the publish command, so an unencrypted connection puts it on the wire every time the software reconnects.
Server: rtmps://ingest-<city>.edge.yunzheng.space:1936/live
Key: osk_…A channel is created with one of two kinds of address, and the choice is made once:
| Address | What it does |
|---|---|
One city’singest-<city>.edge.yunzheng.space | You pick the city and every broadcast goes there. Nothing else answers on it. |
One globalingest.edge.yunzheng.space | The same address from anywhere. The connection lands in the nearest city that has room, which may differ between broadcasts. Included from the Pro pack. |
Both are in the console and in the reply that created the channel. From the command line the same thing looks like this:
ffmpeg -re -i input.mp4 \
-c:v libx264 -preset veryfast -g 60 -c:a aac -ar 48000 \
-f flv rtmps://ingest-<city>.edge.yunzheng.space:1936/live/$STREAM_KEY| What is accepted | Detail |
|---|---|
| Video | H.264. |
| Resolution | Up to 2K (2560×1440). |
| Audio | AAC. |
| Keyframes | One every two seconds or more often. Segments are cut on keyframes, so a longer interval makes a viewer wait longer to start. |
renditions adds is qualities below it, produced here, for viewers whose connection cannot carry the one you sent.Some encoders and some hardware cannot make an encrypted connection at all. A channel can be switched to accept plain RTMP as well as RTMPS — one channel at a time, off until you turn it on, and the encrypted address keeps working either way.
Server (RTMPS): rtmps://ingest-<city>.edge.yunzheng.space:1936/live
Server (RTMP): rtmp://ingest-<city>.edge.yunzheng.space:1935/live
Key: osk_…curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"allow_rtmp":true}'The plain address comes back with the channel as ingest_url_rtmp, and only while the switch is on. A publish that arrives unencrypted on a channel that has not been switched is refused with rtmps-only, and the channel says so afterwards.
Every channel is delivered as close to live as HLS allows, and that is the setting it is created with. The picture is cut into one-second pieces and a viewer sits about three to five seconds behind you.
| Setting | What a viewer gets |
|---|---|
lowthe default | About 3–5 seconds behind, with one-second pieces. It needs a keyframe every 1 or 2 seconds in your encoder. |
standard | About 15 seconds behind, with four-second pieces. Tolerant of any keyframe interval, and steadier on a poor connection. |
On top of that you can hold your audience back by hand. delay_s is a whole number of seconds from 0 to 600, and 0 — no hold at all — is what every channel starts on.
curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"latency_mode":"low","delay_s":30}'The delay is applied where the broadcast is served, not in the player: the pieces exist, but the list a player reads does not name one until it is old enough. So a viewer cannot skip it by using their own player, and you cannot be overtaken by somebody watching a different way.
Both take effect on the next broadcast, and both are in the console beside the server address.
Low latency is as close to live as HLS goes, and that floor is about five seconds: a player holds whole pieces back before it starts, and the next piece is still being written while the list advertises the last one. A channel can offer a second path beside it that has no pieces at all — WebRTC, the same thing a video call uses — and on that path a viewer is about one second behind you.
curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"realtime_play":true}'The channel comes back with a whep_url beside its playback_url. Our player takes both: given the first it tries WebRTC, and if anything about it fails — a refusal, a network that will not carry it, no picture within eight seconds, a connection that drops later — it moves to the HLS address on its own, without telling the viewer anything. The HLS path is never switched off, so there is nothing to fall back from.
| What you get | What it costs |
|---|---|
| About one second behind, and it is your own picture — your resolution, your frame rate, forwarded without being decoded. | The encoder has to send no B-frames. With them, we re-encode for this path only: one more encoder on the ingest city, and what these viewers receive is no longer byte-for-byte what you published. A channel with a watermark is always re-encoded for this path, so the mark is in what these viewers see too. HLS is unaffected either way. |
bf=0 in x264 Options; or set the tune to zerolatency, which does the same thing and removes the encoder’s own lookahead delay as well. Hardware encoders call it B-frames or Consecutive B-frames — set it to 0. Nothing else about your settings has to change, and the same stream still serves the HLS path exactly as before.A viewer who opens the page between two of your keyframes waits for the next one before there is a picture — up to one keyframe interval, because on this path the keyframes are yours and no request from a viewer can produce one. With the keyframe interval low latency already asks for (1 or 2 seconds) that wait is a second or two; with an encoder sending one every 8 seconds it is up to eight, and nothing here can shorten it.
There is no seeking back on this path: WebRTC delivers packets, not a list of pieces, so the position line is hidden and the quality menu shows one entry — the picture you published, which is the only one there is. Everything else about the player is the same, and a page that wants to know which path it ended up on listens for the mode event.
A channel offers the quality it was published at, and optionally some below it. A viewer’s player picks whichever its connection can carry and moves between them as the connection changes — the alternative, for a viewer on a slow link, is not a smaller picture but a stalled one.
| Extra qualities | What is produced |
|---|---|
0 | Only what was published. Nothing is decoded here at all. |
1 … 4 | 1080p, 720p, 480p and 360p, highest first, and never above what was published — a 720p broadcast asking for four gets 480p and 360p and stops there, because upscaling invents nothing. |
The manifest lists them all and the player chooses; there is nothing to switch by hand and no second address to hand out.
A channel can carry a copyright mark in one corner of the picture: a grey, half-transparent “Orbit Stream” with “Powered by YunZheng.Space” and your UID in smaller type beneath it. Bottom right by default, top left if that corner suits your picture better, and off unless you turn it on.
curl -X PATCH https://dash.yunzheng.space/api/v1/stream/streams/$ID \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"watermark":"br"}'It is burned in while the broadcast is encoded, not drawn by the player. So it is in what every viewer receives whatever they watch with, it is in every quality, and it is in the recording — a recorded broadcast keeps the mark exactly as it went out. There is no player setting that hides it.
The wording and the UID are ours to compose, not yours to set: the mark identifies the account a broadcast belongs to, which is the whole reason it exists. Both consoles put the choice beside the server address.
A channel can play something of its own whenever nobody is publishing to it: a loop of files, a loop of its own past broadcasts, or a relay of another of your channels. It is not a video handed to each viewer — it goes out the same way a broadcast does, so everyone who opens the address is at the same point in it, and somebody who arrives an hour in arrives an hour in.
curl -X PUT https://dash.yunzheng.space/api/v1/stream/streams/$ID/programme \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"mode": "loop",
"items": [
{ "kind": "recording", "ref": "orc_…", "title": "Last Sunday" },
{ "kind": "recording", "ref": "orc_…" },
{ "kind": "url", "ref": "https://files.example/interval.mp4" }
]
}'| Item | What it points at |
|---|---|
recording | One of THIS account’s recordings, by its orc_… id. It has to be finished and kept in our storage — a recording in a bucket of your own is replayed from a signed address that expires, so it cannot be used here. |
url | An https address of an MP4 or an HLS playlist. Plain http is refused. |
channel | Another of your channels, by its ops_… playback id, relayed under this channel’s own address. Relaying somebody else’s channel needs their permission, and there is no way to give it yet. |
| Mode | What it does |
|---|---|
loop | Plays the items in the order given and starts again at the top. Up to 50 items. |
relay | Re-serves one live source under this channel’s own address. It takes exactly one item. |
Somebody publishing to the channel takes it back at once — the programme stops and the broadcast starts, with no gap longer than one piece of the stream. When the broadcaster stops, the channel waits out the same 15 seconds it waits for any dropped connection, in case they are coming back, and then the programme resumes.
A channel reports which of three states it is in, as on: live, programme or off. The hosted page calls the middle one a rerun rather than a broadcast, and the player treats it as live, because it is one timeline and the address is always at its edge.
Two forms, both from the playback identifier, which is a different string from the stream key. The key never appears in anything a browser requests.
https://live.yunzheng.space/p/<playback id> a page that plays it
https://live.yunzheng.space/s/<playback id>/index.m3u8 HLS, for your own playerBoth addresses come back with the channel, so nothing has to be assembled by hand, and neither changes between broadcasts.
Where a viewer actually connects is what placement chose, and it is the same choice on both sides of the broadcast:
| How it is reached | What that means for a viewer |
|---|---|
| One city you pick | Viewers reach that city, across the public internet, wherever they are. One city, one address, the same one every time. |
| Our network | Viewers reach the city nearest them and it carries the broadcast the rest of the way. Included from the Pro pack. |
| What | How long the edge keeps it |
|---|---|
| The list of qualities | 10 seconds — it names the broadcast and changes once per broadcast. |
| The list of segments | 1 second on low, 2 on standard — half a piece, because it changes as the broadcast goes on. |
| A segment | 2 minutes. A segment never changes once written. |
The hosted page uses a player you can use too. It is one script with no dependencies: it draws the controls, offers the qualities, shows how far behind the broadcast the viewer is, and plays natively where a browser can and with a component where it cannot.
<div id="player"></div>
<script src="https://live.yunzheng.space/v1.js"></script>
<script>
OrbitPlayer.mount(document.getElementById("player"), {
src: "<the channel's playback_url>",
hlsjs: "https://live.yunzheng.space/static/player.js",
lang: "en",
latency: "low" // match the channel: "low" (default) or "standard"
});
</script>latency to match the channel’s own setting. It is "low" unless you say otherwise, which is also what a channel is unless you say otherwise; a player told "standard" on a low-latency channel throws the short window away, and one told "low" on a standard channel buffers more than it needs to. On Safari, which plays HLS itself, there is nothing to configure and this option does nothing.Pass whep as well, and the player tries near-real-time playback first and falls back to src on its own. Nothing else about your page changes: the same controls, the same events, the same handle.
OrbitPlayer.mount(document.getElementById("player"), {
src: "<the channel's playback_url>",
whep: "<the channel's whep_url>", // near-real-time; omit it and nothing changes
hlsjs: "https://live.yunzheng.space/static/player.js",
latency: "low"
}).on("mode", function (m) {
// "realtime" or "hls" - which engine ended up playing
});seekLive() and the step-back keys do nothing, latency() returns null, and levels() is the one picture you published. handle.mode() says which engine is playing at any moment, and the mode event fires when it changes — including when a realtime attempt gives up and HLS takes over, which is the only signal a page gets, because the viewer is deliberately told nothing.Three ways to make it yours, from lightest to heaviest:
| Level | How |
|---|---|
| Restyle | Set the custom properties on the element you mounted into: --op-fg, --op-frame, --op-panel, --op-hover, --op-live, --op-warn, --op-line, --op-muted. Override any word with text: { live: "On air" }. |
| Extend | Switch single controls off with controls: { stats: false, pip: false }; add your own with handle.addButton({ icon, aria, onClick }), which is built exactly like ours and lands in the same bar; listen with handle.on("state" | "levels" | "level" | "latency" | "mode", fn). |
| Build your own | controls: false mounts the engine alone: the video element, the ladder (levels(), setLevel(), currentLevel()), latency(), seekLive() and the events, with nothing drawn. Draw every control yourself. |
| Key | Does |
|---|---|
| Space, K | play / pause |
| J, Left | step back ten (five) seconds into the window behind the edge |
| L, Right | back to live |
| M | mute |
| F | full screen |
| Up, Down | volume |
Live means live: while nobody has stepped back, the line stays full and the badge reads live whatever the measured lag, and a picture that fell far behind on its own (a tab the browser throttled) is caught up quietly — above 8 seconds on a low-latency channel, above 30 on a standard one. Pass title to name the broadcast on the lock screen; play, pause and the media keys work from there.
Issue a new key at any time. By default the broadcast that is on air keeps running on the old key until it ends, and the new key is the one that works for the next one — so a key can be rotated in the middle of an event without taking it off the air.
curl -X POST https://dash.yunzheng.space/api/v1/stream/streams/<id>/key \
-H "Authorization: Bearer $ORBIT_TOKEN"Send {"revoke_now": true} to end the broadcast on air at the same time. That is the one to use if a key has leaked.
A channel says whether it is on air, since when, and in which city. Past broadcasts are kept as a record of when each one ran, how long, how much was published and how much was served, and how it ended.
curl https://dash.yunzheng.space/api/v1/stream/streams/<id>/sessions \
-H "Authorization: Bearer $ORBIT_TOKEN"A channel that cannot go on air says so on itself, in a sentence: a pack limit reached, or a key that has been revoked. The broadcasting software will only report that it could not connect, so the reason lives here.
How an Orbit account is organised, what lives where in the console, and the shortest path from signing in to serving a name from our network.
Add a zone, point your registrar at our name servers, manage records: weighted answers, health-checked records, ALIAS at the apex, zone-file import.
Delegate the whole domain, delegate one hostname, or add a CNAME and leave your DNS where it is. What each one costs you and when to pick it.