Capabilities
Flags gate UI in code rather than in documentation.
Capabilities are sent in the capabilities object of every sync, and each sync replaces the previous declaration as a whole. A provider that stops sending a flag has not kept the old value: it has the default. So send every capability you mean, every time.
The defaults are not all the same, and the difference is deliberate. A flag that grants something we would otherwise withhold defaults to false, and only the literal true counts: a missing value, "true", and 1 all mean false. A flag that withdraws something we would otherwise assume defaults to true, and only the literal false counts. A capability this specification does not name is ignored.
Nothing verifies a declaration. A provider is taken at its word, which is why the one flag whose falsehood does damage, canAnalyseAudio, says plainly what it costs.
canTransposeKey: you can shift pitch. Default false. Requires a decoder in your path, so only providers that transcode can offer it. At present nothing in the app offers a key change, because no provider transcodes, and declaring it changes nothing on screen.canSeek: your media answers range requests, so scrubbing works. Default true. At present the app does not change what it offers according to this flag. For a bucket we measure it, by asking for a range, on every sweep.canSearchRemotely: you can answer a search we cannot serve from the mirrored catalogue, and fetch what somebody picks. Default false. One flag for both, because a source that can find a song it will not then go and get is not worth offering. See Answering on demand.canPreview: you can serve a moment of a song nobody has fetched yet, so a person choosing between six karaoke cuts can hear one first. Default false. Only meaningful alongsidecanSearchRemotely, and separate from it because fetching on demand is useful without it. See Previewing.allowsSharing: whether your catalogue may be exposed to anyone but its owner. Default true, because most sources are somebody’s own files. Set it false if your terms are per-seat. A library reporting false can only ever be attached privately, and if you flip it to false later, existing shared attachments are demoted on the next sync. Distinct from commercial use: a licence can permit private listening while forbidding you to open the catalogue to a room full of guests.maxConcurrentRooms: how many parties your catalogue may be at simultaneously. Default: no limit, which is right for someone’s own files. Send1for a per-seat licence. It is a positive number, and anything else is taken as no limit. Attaching past the limit is refused, and the refusal names the parties already using it so the person can free one up.allowsCommercialUse: whether your content is licensed for a venue. Default false, and it should stay false unless you hold an actual licence. Venue mode refuses providers that report false; that is enforced in code, not in terms.canAnalyseAudio: see below.
canAnalyseAudio
Default false. It means: every successful response to a request for my media answers a CORS request made in anonymous mode, so a page on another origin may read the bytes it is playing.
Concretely, for GET <baseUrl>/media/<externalId>?t=<mediaToken>, the address the Stage is handed, with range requests included, on every 200 and every 206. The bridge also sends them on a 416 and on the answer to OPTIONS. An error response, such as the 403 for a wrong token or the 404 for an id you do not have, need not carry them, and the bridge’s do not:
Access-Control-Allow-Originnames the requesting origin, or is*. The Stage sends no credentials, so a wildcard is acceptable. If you echo the origin, also sendVary: Origin.Access-Control-Expose-Headers: Content-Length, Content-Range, which Serving media already asks for.- If the address redirects, every hop does the same.
- The Stage’s requests are simple CORS requests, since a single
Range: bytes=N-Mis allowed in one, so no preflight is expected. AnswerOPTIONSanyway, as Serving media says.
It is a claim about CORS and nothing else. It does not say that you analyse anything, and it is separate from canPreview.
What the Stage does with it
The Stage reads the flag for each track, from what it was told when the song was about to play.
- False or absent. The Stage sets no
crossoriginattribute and builds no audio graph. Playback is exactly what it was before the capability existed, and you owe nobody any CORS headers. A track with no picture, which the Stage finds out by loading it and finding that it has no video, shows its artwork, or the Let’s DJ neon sign if it has none, always with title and artist. There is no visualiser. - True. The Stage sets
crossorigin="anonymous"before loading, for every track from you, video included, because it cannot know that a file has no picture until it has loaded it. A track with no picture additionally gets an audio analyser and a visualiser, a row of bars, shown with that same artwork or sign. Video tracks are not analysed. - The attribute is fixed for the life of the element. A sync that changes the flag affects the next song, and not the one playing.
- Only the Stage’s player does any of this. Previews on phones never set
crossoriginand are never analysed, so a provider that offers previews needs nothing new.
Why it exists, and the trap in it
A browser will not let a page analyse media that came from another origin unless that origin agreed to it. If the page routes such media through an analyser without the agreement, the browser does not raise an error: what is played goes silent and the analyser reads zeros, and nothing says why. So the Stage cannot simply try it.
Declaring true without sending the headers does not degrade quietly. With crossorigin="anonymous" set, a response that lacks them fails to load at all. The Stage treats that track as unplayable and skips it, and so it skips every track from that provider.
Leaving it unset costs nothing. Audio still plays, and still shows its artwork.
The Stage deliberately does not try the attribute and fall back when it fails. By the time a failure is visible, a cross-origin element that has been routed through an analyser is already silent, and a fallback that plays silence is worse than not trying.
The bridge declares true. A bucket never does: a presigned link carries no CORS headers unless the bucket’s owner configured them, and we do not configure other people’s buckets.