Guide contents

What users expect from a server

Your server needs a device list, approval and pairing controls, and messages that help users recover from errors. This chapter covers those screens and the protocol rules behind them. The device side of the same flows is in client pairing; the reasoning behind them is in the trust model.

Discover: the device list

Show every discovered client under the name from its client/hello, with the manufacturer and model from device_info next to it, so "Kitchen" is recognizably the speaker and not the wall tablet. The specification requires three visually distinct states: paired, approved for unpaired access, and unknown (neither). A new client that claims a familiar name must never pass for the existing device. If a second "Kitchen" shows up among the unknown devices, the operator sees the difference; if the two look the same, an attacker wins by naming a laptop after a speaker.

Show a fourth state for a device that is playing from another server. Your connection to it ended with another_server or was refused with concurrent_attempt; show it as available, say where it went if you know, and let the next play command take it back.

Spec: Unpaired Access, client/goodbye.

Approve or pair

Approval for unpaired access

A device that admits unpaired access will play for any server its operator approves. Give approval a dedicated control, "Allow this device" or similar, on the device's row. For playback roles you may also take an action that clearly means "use this device", such as pressing play on it, as implied approval; pressing play on a speaker you just unboxed and hearing music is the experience people know from casting. Never imply approval for the source role: an input is approved only through the explicit control. Approval persists across restarts, the operator can revoke it, and it is discarded when the device pairs, because pairing supersedes it.

Spec: Unpaired Access, Source messages.

Pairing as an action

Offer pairing as a clearly visible action for every unknown client, and keep it available as an upgrade for approved ones. The device's client/hello tells you which code method it offers and where its static code lives: printed on the device or on a leaflet in the box. A person pairs with that code and nothing else, so the pairing action starts the one code method the device lists; if you ever see both, prefer the dynamic code. Select the qr_code format only when your interface can scan a QR code. A desktop app without a camera asks for digits.

Every device also implements the pairing PSK method, but keep it out of the user-facing flow. It is for automation: Home Assistant hands an ESPHome device's token to Music Assistant, and the device appears paired without anyone being asked anything. Expose it through an API or an advanced settings page for integrations and administrators. Keep it out of the code-entry screen. Wherever you accept a token, decode leniently: trim whitespace, uppercase, accept it with or without the SP: prefix, and read 9 as 2, since the token alphabet substitutes one for the other. Before starting, check that the client key inside the token is the client_id of the connection. If the token belongs to a different device, explain the mismatch before starting the handshake.

Spec: pair-method descriptor, Pairing Token, Pairing PSK Flow.

Entering a code

Show one slot per digit, grouped the way the device groups them: 123-456 for a dynamic code, 1234-5678 for a static one. The grouping makes the expected length obvious; strip hyphens and spaces from whatever is typed or pasted. For a device that speaks its code, add a step before you start the attempt: "Press continue and the speaker will read out a six-digit code." The device speaks the code as soon as the first round begins, and an operator who was still looking at the phone has to ask for another round, of which the device allows at most twenty before it insists on a button press.

After a wrong dynamic code, the device asks for another round and shows or speaks the same code again. After a wrong static code, the attempt fails and you start a new one, within the five the device's pairing window allows. Say "that code did not match" and put the cursor back in the first slot. While the device holds an attempt back, it sends client/pair-pending, sometimes with a message such as "Press the pairing button on the back". Show that message verbatim, as plain text attributed to the device, never as markup or a link, because it comes from a peer you have not authenticated yet. Apply your own timeout while you wait; a device that never comes back must not leave a spinner forever. A cancel button sends pair/abort with reason user_cancelled, followed by an activation that leaves pairing, so the device can show why the attempt ended.

Spec: Pairing Code Presentation, Rounds, Pairing Window, client/pair-pending, pair/abort, Entering and leaving pairing.

Music Assistant does it like this

Pairing starts from the player picker: an unknown speaker shows a pair action next to its name, the operator picks the method the device offers, and the code entry opens with one slot per digit.

Music Assistant: pairing a speaker with a pairing code. Recorded automatically from the end-to-end tests.

The Music Assistant end-to-end tests regenerate the recording on every run.

After pairing

Once both sides have persisted the record, you re-handshake the live connection to the new key. Nothing closes and a running stream keeps running. Show the device as paired from then on. Offer unpair on the device's page: server/unpair makes both sides forget the record, the device says goodbye with reason unpaired, and it is an unknown device again until someone approves or pairs it.

Spec: Re-handshake, server/unpair.

Credential mismatch

A device you paired may stop recognizing you: it was factory reset, it evicted your record to make room for another server, or a pairing was interrupted between the two finalize messages. The handshake then falls back to the Sentinel key and tells you, authenticated, that the device could not use your credential. Do not quietly continue as an unpaired session. The specification forbids playing on the device while your record exists, and a silent fallback is exactly what a man in the middle would want. Show a warning on the device, "This device no longer recognizes this server", name the likely causes, and offer re-pairing, which replaces the record.

Spec: Sentinel Fallback, Pairing Records.

Play

Renaming a device, moving it between groups and changing its volume must never interrupt playback. Reconnecting after a settings change resets the device's clock filter, and the device comes back late and out of sync for the seconds the filter takes to converge. Groups are a server-side concept, so a regroup is a group/update and a new stream, not a new connection.

Send the operator's languages in server/hello. A speaker that reads out a pairing code or a display that shows a pending message picks its language from that list, and an operator who set Catalan should not hear English digits.

Sources get their own flow: a separate, explicit approval, a clear way to start and stop streaming an input, and a visible indicator while it streams. A microphone device ships with unpaired access off. Explain that it needs pairing before the user can approve it.

Spec: server/hello, Source messages.

Music Assistant does it like this

On first run, Music Assistant lists the Sendspin devices it found, lets the user approve or pair each one, and plays on the first approved speaker straight away.

Music Assistant: first-run onboarding, from an empty device list to music playing. Recorded automatically from the end-to-end tests.

Like the pairing recording, this one is produced by the end-to-end tests on every run.

Maintain

A device that reports itself unavailable, because the TV switched to HDMI or another protocol took the output, is parked in a stopped group of its own. Show that as "in use elsewhere" rather than offline, and do not pull it back into its group when it returns; the user does that with a switch command or in your interface. A device that went to another server is "playing from another server", also not offline. Offline is for a device you cannot reach at all.

Keep the paired, approved and unknown states visible in settings too, with unpair and revoke next to them, so an operator who sells a speaker can forget it in one place.

Spec: External Source Handling.

States to design

  • Unknown device: discovered, neither paired nor approved, with approve and pair actions.
  • Approved device: plays for you unpaired, with pair as an upgrade and revoke available.
  • Paired device: with unpair available.
  • Pairing in progress: code entry; waiting for the device; wrong code, try again; device holding back, with its message shown.
  • Credential mismatch: warning and re-pair action.
  • Playing from another server.
  • In use elsewhere (unavailable), parked in its own group.
  • Offline.