Secure

Admission and caps

How many viewers a stream and an account take, the waiting line, what a viewer sees when a limit is reached, and how to size the limits.

Three limits stand between a viewer and the picture. They are checked in this order, and each one has a different effect on the viewer:

LimitWhereSet byA viewer who hits it
The stream's seats and waiting linethe control APIyou, per stream, when you create itwaits in line, then gets an offer
Your account's limitsthe control API, and the relay's session cap for your accountus, per accountwaits and asks again, or the relay turns the connection away
Edge admissioneach relaythe relay, from its own loadtries another relay, or waits

A viewer who is admitted is never dropped by any of them. Limits stop new viewers; they do not cut the ones already watching.

Seats and the waiting line

Every stream has viewer_cap (seats) and queue_max (places in line). You set them when you create the stream:

curl -s $API/v1/streams -H "Authorization: Bearer $SK" \
  -d '{"name":"launch","viewer_cap":500,"queue_max":1000}'
SettingDefaultRule
viewer_cap100, or your account's cap if that is lowerat least 1; at most your account's viewer_cap
queue_maxtwice viewer_capviewer_cap plus queue_max at most 20,000

Change either cap on a live stream with PATCH /v1/streams/{id}, with the same rules. A cap lowered below the viewers seated keeps them all in and seats nobody new until fewer are left; a raised cap seats the people in line as their pages next ask (Changing a stream). A stream over your account's cap is refused: 409 tenant_viewer_cap, "viewer_cap: at most 200, this tenant's viewer cap". All the streams together may hold at most 20,000 seats and places (409 too_many_places).

How a viewer moves through them (all of this ran against a test stream with viewer_cap 2 and queue_max 1):

  1. The first two viewers get a seat: 201, "state":"in".
  2. The third gets a place in line: 409 {"state":"wait","position":1,"retry_after_ms":2000}. It asks again every 2 s with the same seat. A page that stops asking for 15 s leaves the line.
  3. The fourth finds the line full: 409 {"state":"full","retry_s":30,"retry_after_ms":30000}. It is not in line; it asks again after 30 s.
  4. When a seat is freed (a kick, a leave, a heartbeat that stopped), the head of the line gets an offer: 409 {"state":"offer","offer_s":10}. The seat is held for it for 10 s. It takes the offer by asking again with join: true (your route sends join: true when the viewer presses Join), and gets 201. If it does not, the offer passes to the next page and this one leaves the line.

The offer exists so a tab that nobody is looking at does not take a seat from someone who is. With the auto-join attribute the player takes an offer at once.

A seat is kept by the player's heartbeat: 20 s without one frees it (60 s for a hidden tab). The stats call counts what a stream holds:

curl -s $API/v1/streams/$ID/stats -H "Authorization: Bearer $SK"
# {"viewers":{"cap":2,"hls":0,"peak":2,"queue_max":1,"queued":1,"seats":2},
#  "seats_with_tokens":2,"turns":0,"ingest_token_at":null}

seats and queued are now; peak is the most seats at once; hls is viewers on the LL-HLS fallback (they take a seat like any other).

On a public stream the page's own calls (/v1/play/{id}/admit, /token, /hb, /leave) are rate limited by address: a burst of 20, then 2 a second, else 429 rate_limited, "too many requests from this address". With server admission your backend makes the calls to viewer-tokens, and that limit does not apply to it.

What a viewer sees

The player turns each answer into one line under the video:

The answerThe viewer sees
inthe video
wait, position 37"In line, number 37..."
offer"A seat is free. Join within 10 s." and a Join button
full"The stream is full. Waiting for a seat..." (it asks again after retry_s)
your account's seats all taken"The stream is full. Waiting for a seat..." (it asks again every 10 s)
the seat was lost"Your seat was given up. Reload to watch again."

Your account's limits

Each account has limits that we set. GET /v1/tenant shows yours under limits:

LimitA new account starts atWhat it capsWhat is refused
viewer_cap200seats over all your streams togethera seat on any stream: 409 tenant_viewer_cap, "this tenant's N seats are all taken"
max_streams10streams you holdcreating one: 409 too_many_streams
max_sessions420live sessions at the relay for your account, every kind (viewers, publishers, drivers)a new connection: closed with "scope full"
max_publishers20live publishing sessionsa new publisher connection
max_egress_mbps2000the relay's send rate for your accounta new viewer connection (reason "egress cap"); the viewers already in keep watching

A seat check and a relay check are two checks. The API counts seats; the relay counts sessions. The relay lets in only a token the API minted for a seat, one session per token, so a page that skips the API cannot get in. Because a seat has two sessions for a moment at every renewal (Token renewal), max_sessions is set to twice viewer_cap plus the publishers: 2 x 200 + 20 = 420. If you raise viewer_cap, max_sessions has to rise with it, or viewers are seated by the API and then turned away at the relay.

Run on a test stack with max_sessions set to 1: a first publisher connected, and a second connection was closed by the relay with the reason "scope full" (and, because the client retries, it was refused again and again). With viewer_cap set to 3, a fourth seat on another stream was 409 tenant_viewer_cap, and a public admit got the same.

The spending cap is a limit too: at the cap no new seat is given (402 spend_cap_reached), and a viewer already seated keeps getting tokens (Usage and spending caps).

Edge admission

A relay that is too busy gives every viewer a bad picture. So each relay measures its own load, every second, and when it is saturated it keeps the viewers it has and refuses new ones.

  • Saturated means the relay's work threads have been busy 85 % of the time or more, or a timer is running 50 ms late, for 2 s. It opens again when busy is under 70 % and the timer under 15 ms for 10 s, so a relay near its limit does not flip back and forth.
  • While open, it lets in a share of its spare room each second, so a burst of joins cannot push it past saturation before the next measurement.
  • The first request of a new viewer session decides. A refused viewer gets the error EDGE_FULL (code 0x5746) with a retry time (2 s). Once a session is admitted, it is never refused again: sound, quality changes and reconnects within it all go through.
  • Peer relays and publishers are never refused.
  • A seat keeps its place: a viewer that renews its token on a new session, or reconnects within 30 s of its last session ending, is let in even when the relay is saturated. A full relay does not turn away its own viewers at their renewal.
  • The relay reports saturation to the control API, which stops listing that relay in new viewers' answers until it is open again. The relay setting is --admission on|observe|off (on is the default; observe measures and refuses nothing), with --admission-busy to move the busy level and --admission-retry-ms for the retry time (default 2000).

What the viewer sees. The player holds the full relay out for its retry time and connects to the next nearest relay (the same region first, then others). With every relay full it shows "The stream is full. Waiting for a seat..." and asks again after the retry time, doubling each round up to 16 s. Nothing has been taken from the viewer: the token is used again.

Sizing

  • Stream cap. Choose viewer_cap for the audience you expect at once, and queue_max for how many you would rather hold in line than turn away. Both are fixed for the stream. Measure what you cost per viewer with the usage numbers (Usage and spending caps) before setting it high.
  • Account cap. The sum of your streams' caps can exceed your account's viewer_cap; the account cap wins, and the seat that would be the one-too-many gets tenant_viewer_cap at that moment. Ask us for a higher account cap before an event, not during.
  • Relay sessions. max_sessions at least 2 x seats + publishers.
  • Your own seat server. If you run your own line, size the stream's viewer_cap at least to your own cap, and heartbeat our seat from your side (Viewer tokens).
  • The line. The player does not connect to a relay while it is in line. The API gives position and no wait time, because there is no measure of how fast seats turn over; do not promise one on your page.

Reference: GET /v1/streams/{id}/stats, POST /v1/play/{id}/admit.

On this page