Relay selection
How a viewer is given a list of relays and how the player picks one, fails over and waits when every relay is full.
The API lists relays, the player measures them. The API never decides which relay a viewer uses, because only the viewer's browser knows which one is close to it.
What the API lists
Every answer that seats a viewer (a viewer token,
POST /v1/play/{id}/admit, a token refresh) carries relays:
"relays": [
{ "url": "https://edge1.east.example.com/tn_...", "region": "east", "id": "east-edge1" },
{ "url": "https://edge1.west.example.com/tn_...", "region": "west", "id": "west-edge1" }
],
"relay_cert_sha256": "..."- One relay, no fleet (
NEARGLASS_API_RELAYS): each configured URL, in the order you wrote them, with noregionorid. - A fleet: for each region in the fleet file's order, its
least loaded edges that are up, at most
per_region(default 2). An edge that is down or saturated is left out; if nothing is up the list falls back to saturated edges, then to every edge. - The URL ends with the account's scope (
/tn_...). The relay checks the token against that scope, so a list is a hint and never access. relay_cert_sha256is present only for a self-signed relay certificate (NEARGLASS_API_RELAY_CERT_SHA256); the player trusts that hash.
What the player does
With one relay, it uses it. With more, @nearglass/player opens a WebTransport
handshake to every listed relay at once, times each, and plays from the
nearest by round trip. The handshake needs no token (the token goes in the
MoQ setup that follows), so probing costs the relays nothing in seats. If no
relay answers within 1.5 s it uses the first listed, in the API's order.
It keeps the measured order and asks for a relay before every connect:
| Situation | What it does |
|---|---|
| a connect got no session (the relay is gone or does not answer) | tries the next nearest, and holds the failed relay out for 30 s |
| after the hold, a reconnect (a stall, a token renewal) | goes back to the nearest usable relay |
| a fresh descriptor arrives (every connection fetches one) | replaces the list, so a relay the API has marked down is not chosen even before a connect to it fails |
| the relay answers EDGE_FULL | holds that relay out for its retry interval and tries the next nearest, the same region's other edge first |
| every listed relay is full | waits, then asks again |
A relay that is full does not drop the viewers it has, and a refused session holds nothing, so its token is used again on the next relay. A viewer whose token renews on a new session, or reconnects within 30 s of leaving, keeps its place even on a full edge.
When every relay is full the player is in the waiting state with
{ full: true, waitMs }, and the page's status line shows that the stream
is full. The wait starts at the relay's retry interval (2 s by default, the
relay's --admission-retry-ms) and doubles each round, up to 16 s. That is
a different wait from the stream's own waiting room, which counts seats:
see Concepts.
A viewer sees which relay it is on through the player's relay event
(url, region, rttMs, failover, full) and, in the player's stats,
relay, region and rtt (the measured round trip per relay, in ms).
Putting relays in a region
To have viewers pick between regions you need a fleet: the API
chooses which edges are in the list, the player which one is nearest. To
give viewers two relays without a fleet, list both in NEARGLASS_API_RELAYS;
they are probed and failed over the same way, but the API cannot tell if one
is down or full, so the player finds out by trying.
Why not choose on the server
The API sees the viewer's address at best, not its network. A browser's own round-trip time to each relay is the measure that matters, and it sees failures at once. The API still keeps an unhealthy relay out of the list, so a dead edge is not offered, and the player still copes with one that has died since.
What was run, and what was not
The list and its shape were run against the API with a fleet file (the
descriptor carried relays with id and region, and without a fleet
only url). The chooser's rules are from the player's code and its unit
tests.