Stream (live)

One connection in, every city out.

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.

01

Create a channel

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.

Create a channel
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.

FieldWhat it sets
nameWhat the channel is called. Up to 60 characters.
visibilityunlisted (default) or public. There is no directory, so both are reached only by address.
cityWhere ingest lands. Omit it and the city with room takes it.
placementHow 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.
renditionsHow 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.
02

Go on air

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 and key
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:

AddressWhat it does
One city’s
ingest-<city>.edge.yunzheng.space
You pick the city and every broadcast goes there. Nothing else answers on it.
One global
ingest.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.
With the global address the watch address still does not change between broadcasts. It answers from whichever city is currently holding the broadcast, so what you hand to an audience stays the same whatever the network decides.

Both are in the console and in the reply that created the channel. From the command line the same thing looks like this:

Publish with ffmpeg
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 acceptedDetail
VideoH.264.
ResolutionUp to 2K (2560×1440).
AudioAAC.
KeyframesOne every two seconds or more often. Segments are cut on keyframes, so a longer interval makes a viewer wait longer to start.
What you publish is always offered to viewers unchanged — it is never re-encoded on the way to becoming the top quality. What renditions adds is qualities below it, produced here, for viewers whose connection cannot carry the one you sent.
03

Plain RTMP

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.

Two servers, one key
Server (RTMPS):  rtmps://ingest-<city>.edge.yunzheng.space:1936/live
Server (RTMP):   rtmp://ingest-<city>.edge.yunzheng.space:1935/live
Key:             osk_…
Turn it on for one channel
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.

The key is sent unencrypted on the plain address. It is the stream name inside the publish command, so anyone on the path between the encoder and the city can read it every time the software connects, and use it until a new one is issued. Turn this on only for an encoder that cannot do the encrypted address; each broadcast records which of the two it used.
04

Latency, and a delay you choose

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.

SettingWhat a viewer gets
low
the default
About 3–5 seconds behind, with one-second pieces. It needs a keyframe every 1 or 2 seconds in your encoder.
standardAbout 15 seconds behind, with four-second pieces. Tolerant of any keyframe interval, and steadier on a poor connection.
The keyframe interval is yours, not ours. What you publish is offered to viewers unchanged, so it can only be cut where your encoder put a keyframe: an encoder sending one every 8 seconds produces 8-second pieces and stays about 15 seconds behind, whichever setting the channel is on. In OBS that field is Keyframe Interval, under Output → Streaming; set it to 1 or 2 seconds.

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.

As close to live as it goes, then held thirty seconds
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.

Recording is not delayed with the audience. A recorded broadcast keeps every second of what you published, from the moment you published it.

Both take effect on the next broadcast, and both are in the console beside the server address.

05

Near-real-time playback

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.

Turn it on for one channel
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 getWhat 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.
B-frames, in one line of your encoder. In OBS, Output → Streaming → Encoder Settings, put 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.

Keep large audiences on the default path. HLS is cached at every edge city, so a thousand viewers cost the ingest city one request a second. WebRTC has nothing an edge can cache: every near-real-time viewer is a connection to the city you publish to, and every one of them is a copy of the stream leaving that machine. It is the right path for a room full of people who need to be a second behind, and the wrong one for a broadcast with an audience.

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.

06

How many qualities

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 qualitiesWhat is produced
0Only what was published. Nothing is decoded here at all.
1 … 41080p, 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.

Each extra quality is an encoder running beside the broadcast, so it is priced and counted: a channel with three extra qualities takes four of a city’s capacity, not one. Packs state the ceiling per channel.
07

Watermark

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.

Turn it on, bottom right
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.

Turning it on re-encodes the top quality. Without a mark, what you publish is passed through untouched and costs no CPU at all; with one, it has to be decoded, drawn on and encoded again. So the channel uses one more encoder on the ingest city — a channel with three extra qualities goes from four of that city’s capacity to five — and what viewers receive is no longer byte-for-byte what you sent. It takes effect on the next broadcast.

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.

08

A programme, for when nobody is publishing

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.

A loop of two past broadcasts and one file
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" }
        ]
      }'
ItemWhat it points at
recordingOne 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.
urlAn https address of an MP4 or an HLS playlist. Plain http is refused.
channelAnother 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.
ModeWhat it does
loopPlays the items in the order given and starts again at the top. Up to 50 items.
relayRe-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.

A programme is re-encoded into the channel’s own qualities, so it costs a city the same as a broadcast on that channel does — a channel with three extra qualities takes four of a city’s capacity whether it is being broadcast to or playing its programme. What leaves us is metered as transfer, the same as a broadcast. It counts no ingest-minutes, because nothing was published.
09

The watch address

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.

A page, and a manifest
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 player

Both 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 reachedWhat that means for a viewer
One city you pickViewers reach that city, across the public internet, wherever they are. One city, one address, the same one every time.
Our networkViewers reach the city nearest them and it carries the broadcast the rest of the way. Included from the Pro pack.
Both are cached the same way: a city pulls the broadcast across once per second whatever the audience, so one viewer and a thousand cost the broadcast the same. What differs between them is the path, not the number of pulls.
WhatHow long the edge keeps it
The list of qualities10 seconds — it names the broadcast and changes once per broadcast.
The list of segments1 second on low, 2 on standard — half a piece, because it changes as the broadcast goes on.
A segment2 minutes. A segment never changes once written.
Nothing is kept here unless the channel has recording switched on. When a broadcast ends its segments age out and the address answers as offline; a recorded broadcast is kept separately and played back from its own address (see Recording below).
A recording kept with us plays at full quality for 7 days after the broadcast ends, and is then kept as a 720p copy. Each recording in the console shows which of the two it is.
One stream in, every location outA broadcast is published once into an Orbit Compute node, offered as the quality it was sent at plus lower ones, and pulled across to each location, which serves every viewer near it.Broadcasting softwareOrbit ComputeRTMPS in, one connectionqualities offered to a playeras sent720p480p360pNearestlocationNearestlocationNearestlocationViewersViewersViewers
The broadcast comes in once and is offered at the quality it was sent at plus any below it. A location pulls it across once a second whatever the audience, so one viewer and a thousand cost the same.
10

The player

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.

Use it as it is
<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>
Pass 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.

Near-real-time first, the playlist as the fallback
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
});
There is no seeking back on the realtime path. WebRTC delivers packets rather than a list of pieces, so there is no window behind the live edge: the position line is hidden, 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:

LevelHow
RestyleSet 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" }.
ExtendSwitch 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 owncontrols: 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.
The quality menu names pictures, not bandwidth: 1080p, 720p, and so on. What each one costs to carry is in the statistics panel for whoever wants it, and is not put in front of somebody choosing what to watch.
KeyDoes
Space, Kplay / pause
J, Leftstep back ten (five) seconds into the window behind the edge
L, Rightback to live
Mmute
Ffull screen
Up, Downvolume

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.

11

Changing a key

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.

Issue a new key
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.

12

Knowing what happened

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.

Past broadcasts
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.

Next

Related

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.

DNS hosting

Add a zone, point your registrar at our name servers, manage records: weighted answers, health-checked records, ALIAS at the apex, zone-file import.

Connect a hostname

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.

← All documentation