The ones actually playing: not muted, in earshot, and inside the cap.
Is this string a YouTube video or playlist link? Pure, synchronous, and answered
locally on both sides of the add-on seam — ask this before playSource so the app
can report a bad paste in its own words.
Forget every individual mute. Leaves muteAllNearby alone; it is its own switch.
Empty the queue and stop. stopMusic alone keeps it.
Off, then all, then one.
One phrase for a failure, shared so three screens cannot invent three wordings for the same refusal. Pure and synchronous on both sides of the seam.
Add it to the end of the queue without interrupting anything, and without starting.
How many play at once. Exposed so a screen can say the rule — "playing the closest three" — rather than leaving a person to discover it by counting.
Why the loaded track will not play, or null. Set together with
musicStatus: 'error', so an app can render one or the other and never both.
Whether nextTrack/previousTrack would go anywhere.
Exposed because a transport that cannot ask this has to offer the control anyway,
and at the end of a queue that is not repeating nextTrack stops rather than
advancing — a button whose label says one thing and whose effect is another.
musicHasNext is pickNext() !== null, the same function the button calls, rather
than a second opinion about it; musicHasPrevious is true whenever anything is
loaded, because from the first row Previous restarts the track.
Which row of musicQueue is loaded, or -1 when none is.
Whether the music channel is silenced. Independent of the phone's UI-sound mute, and of whether anything is playing — a mute stops the sound, never the queue.
What the embed reports it is actually playing — the title and the video id, plus a
position inside a playlist row. null until it says anything, and it may never:
every label an app draws from this needs a fallback to the id.
Where the player says it is, in seconds. duration is 0 until it reports one and
stays 0 for a live stream, which is the signal to draw no scrubber rather than a
scrubber that cannot move.
Everything queued, in play order.
Whether a finished queue repeats, repeats one track, or stops.
What is loaded, as ids. null when nothing is.
What the phone has been asked to do with it.
Music's own volume, 0–1 — not the phone's UI-sound volume.
Whether every nearby broadcast is silenced, whoever it belongs to.
Broadcaster tokens this phone refuses to play. Never server ids — see useMusic.
Other people's music (MICA-111 phase 2), and everything an app may do about it.
Read-and-mute, and nothing else, because there is nothing else to offer: a broadcast has no queue you can see, no position you may move and no error you could fix. The transport above is for this phone's own playback and deliberately does not accept a broadcaster id — an app that could pause a stranger's music would be an app that could pause a stranger's music.
Play a specific row. The row already playing restarts.
Play it now: inserted after whatever is playing, and jumped to. Silently drops
anything canPlay would have refused.
Drop a row by its key. Removing the row that is playing falls through to the next.
Move the playhead, in seconds. Bounded by the reported duration; does not resume.
Set the level, 0–1. Zero mutes and moving off zero unmutes, like the system slider.
The still frame for a video id, or null. A plain image; no API key, no script.
Silence music without disturbing the level it comes back to.
Control the phone's music playback (MICA-111).
The player itself is the shell's, mounted outside everything the phone's close tears down, so an app that opens and closes cannot interrupt a track. What this hands over is the intent — what to load, and whether it should be running — plus the status to render from. There is no handle on the underlying element by design; see
inProcess/facets/music.tsfor what is withheld and why.playSourcetakes whatever a person pasted and reduces it to a YouTube video or playlist id before anything is stored, then plays it.enqueueis the same parse without the interruption. Neither reports back — askcanPlayfirst, which is pure and synchronous on both sides of the add-on seam, so an app can refuse a bad paste in its own words rather than silently doing nothing.The queue is the shell's, and it outlives the app. Closing the phone destroys every component and none of this state, which is the point: a queue an app owned would empty itself the moment the phone went into a pocket. A row is identified by its
key, not by its video id, because the same track queued twice is two rows.A title is late, optional, and never fetched. It arrives from the player itself over the channel the shell already has open, after the track starts, and only for the track that is playing — so
musicNowPlayingisnullat first and may staynullin CEF. Every label an app draws needs a fallback to the id. A playlist row is the sharper case: the row is aPL…id and the thing making noise is a video inside it that the phone did not choose, so the row's owntitlestaysnullwhilemusicNowPlayingnames what is actually playing.Position is pushed, never polled.
musicPositionupdates when the player reports, and there is no way to ask it — the command channel does not answer getters. Treat adurationof0as "no scrubber", not as "zero seconds long".A track can refuse to play, and the app has to say so.
musicStatus: 'error'with amusicErroris a state on its own, not a variety of stopped — YouTube reports 101 or 150 when the uploader disabled embedding, which covers a lot of real music, and 100 when the video is gone.describeMusicErroris the shared wording. Do not render an errored track as loading: the phone's own in-game test procedure depends on that distinction.Other people's music is a different thing, and the hook says so.
nearbyBroadcastsis who is playing something within earshot andaudibleBroadcastsis the subset this phone is actually rendering — after the mute list, and after a cap ofmaxAudibleBroadcastson how many play at once, nearest first. There is no transport for them and there never will be: a broadcast has no queue you can see, no position you may move, and no error you could do anything about. The two things an app may offer aremuteBroadcaster(per person, persisted across sessions) andsetMuteAllNearby(the standing switch somebody reaches for when they are being harassed and do not care by whom). Muting is local, is never sent anywhere, and the broadcaster is not told.Mute against
token, neversource. A row carries both:tokenis an opaque, stable handle on the person and is the only thingmuteBroadcasteraccepts;sourceis a FiveM server id, a connection the server reuses, and a mute keyed there is void the moment its target relogs. Atokensurvives a reconnect and not a resource restart, which is the right trade — mute evasion is a relog, not anensure.A broadcast is anonymous unless the server named it.
labelis optional andnullis normal; neithertokennorsourceis something to put on a screen. There is also no title for a remote source — the title channel is the local player's own frame, and a stranger's is not talking to this phone — so a nearby row is named by its person or by nothing, and the artwork fromthumbnailUrlForis often all there is to look at.