@mica/sdk (SDK contract v1)
    Preparing search index...

    Function useMusic

    • 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.ts for what is withheld and why.

      playSource takes whatever a person pasted and reduces it to a YouTube video or playlist id before anything is stored, then plays it. enqueue is the same parse without the interruption. Neither reports back — ask canPlay first, 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 musicNowPlaying is null at first and may stay null in CEF. Every label an app draws needs a fallback to the id. A playlist row is the sharper case: the row is a PL… id and the thing making noise is a video inside it that the phone did not choose, so the row's own title stays null while musicNowPlaying names what is actually playing.

      Position is pushed, never polled. musicPosition updates when the player reports, and there is no way to ask it — the command channel does not answer getters. Treat a duration of 0 as "no scrubber", not as "zero seconds long".

      A track can refuse to play, and the app has to say so. musicStatus: 'error' with a musicError is 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. describeMusicError is 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. nearbyBroadcasts is who is playing something within earshot and audibleBroadcasts is the subset this phone is actually rendering — after the mute list, and after a cap of maxAudibleBroadcasts on 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 are muteBroadcaster (per person, persisted across sessions) and setMuteAllNearby (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, never source. A row carries both: token is an opaque, stable handle on the person and is the only thing muteBroadcaster accepts; source is a FiveM server id, a connection the server reuses, and a mute keyed there is void the moment its target relogs. A token survives a reconnect and not a resource restart, which is the right trade — mute evasion is a relog, not an ensure.

      A broadcast is anonymous unless the server named it. label is optional and null is normal; neither token nor source is 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 from thumbnailUrlFor is often all there is to look at.

      Returns {
          audibleBroadcasts: Readable<AudibleBroadcast[]>;
          canPlay: (input: string) => boolean;
          clearMutedBroadcasters: () => void;
          clearQueue: () => void;
          cycleRepeat: () => void;
          describeMusicError: (
              reason: MusicErrorReason,
              translate?: Translate,
          ) => string;
          enqueue: (input: string) => void;
          maxAudibleBroadcasts: number;
          musicError: Readable<MusicError | null>;
          musicHasNext: Readable<boolean>;
          musicHasPrevious: Readable<boolean>;
          musicIndex: Readable<number>;
          musicMuted: Writable<boolean>;
          musicNowPlaying: Readable<MusicNowPlaying | null>;
          musicPosition: Readable<MusicPosition>;
          musicQueue: Readable<QueueEntry[]>;
          musicRepeat: Readable<MusicRepeat>;
          musicShuffle: Readable<boolean>;
          musicSource: Readable<MusicSource | null>;
          musicStatus: Readable<MusicStatus>;
          musicVolume: Writable<number>;
          muteAllNearby: Readable<boolean>;
          muteBroadcaster: (token: string) => void;
          mutedBroadcasters: Readable<string[]>;
          nearbyBroadcasts: Readable<NearbyBroadcast[]>;
          nextTrack: () => void;
          pauseMusic: () => void;
          playQueueIndex: (index: number) => void;
          playSource: (input: string) => void;
          previousTrack: () => void;
          removeFromQueue: (key: string) => void;
          resumeMusic: () => void;
          seekMusic: (to: number) => void;
          setMusicMuted: (muted: boolean) => void;
          setMusicVolume: (value: number) => void;
          setMuteAllNearby: (on: boolean) => void;
          setRepeat: (mode: MusicRepeat) => void;
          stopMusic: () => void;
          thumbnailUrlFor: (videoId: string | null | undefined) => string | null;
          toggleBroadcasterMute: (token: string) => void;
          toggleMusicMute: () => void;
          toggleMuteAllNearby: () => void;
          toggleShuffle: () => void;
          unmuteBroadcaster: (token: string) => void;
      }

      • audibleBroadcasts: Readable<AudibleBroadcast[]>

        The ones actually playing: not muted, in earshot, and inside the cap.

      • canPlay: (input: string) => boolean

        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.

      • clearMutedBroadcasters: () => void

        Forget every individual mute. Leaves muteAllNearby alone; it is its own switch.

      • clearQueue: () => void

        Empty the queue and stop. stopMusic alone keeps it.

      • cycleRepeat: () => void

        Off, then all, then one.

      • describeMusicError: (reason: MusicErrorReason, translate?: Translate) => string

        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.

      • enqueue: (input: string) => void

        Add it to the end of the queue without interrupting anything, and without starting.

      • maxAudibleBroadcasts: number

        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.

      • musicError: Readable<MusicError | null>

        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.

      • musicHasNext: Readable<boolean>

        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.

      • musicHasPrevious: Readable<boolean>
      • musicIndex: Readable<number>

        Which row of musicQueue is loaded, or -1 when none is.

      • musicMuted: Writable<boolean>

        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.

      • musicNowPlaying: Readable<MusicNowPlaying | null>

        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.

      • musicPosition: Readable<MusicPosition>

        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.

      • musicQueue: Readable<QueueEntry[]>

        Everything queued, in play order.

      • musicRepeat: Readable<MusicRepeat>

        Whether a finished queue repeats, repeats one track, or stops.

      • musicShuffle: Readable<boolean>
      • musicSource: Readable<MusicSource | null>

        What is loaded, as ids. null when nothing is.

      • musicStatus: Readable<MusicStatus>

        What the phone has been asked to do with it.

      • musicVolume: Writable<number>

        Music's own volume, 0–1 — not the phone's UI-sound volume.

      • muteAllNearby: Readable<boolean>

        Whether every nearby broadcast is silenced, whoever it belongs to.

      • muteBroadcaster: (token: string) => void
      • mutedBroadcasters: Readable<string[]>

        Broadcaster tokens this phone refuses to play. Never server ids — see useMusic.

      • nearbyBroadcasts: Readable<NearbyBroadcast[]>

        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.

      • nextTrack: () => void
      • pauseMusic: () => void
      • playQueueIndex: (index: number) => void

        Play a specific row. The row already playing restarts.

      • playSource: (input: string) => void

        Play it now: inserted after whatever is playing, and jumped to. Silently drops anything canPlay would have refused.

      • previousTrack: () => void
      • removeFromQueue: (key: string) => void

        Drop a row by its key. Removing the row that is playing falls through to the next.

      • resumeMusic: () => void
      • seekMusic: (to: number) => void

        Move the playhead, in seconds. Bounded by the reported duration; does not resume.

      • setMusicMuted: (muted: boolean) => void
      • setMusicVolume: (value: number) => void

        Set the level, 0–1. Zero mutes and moving off zero unmutes, like the system slider.

      • setMuteAllNearby: (on: boolean) => void
      • setRepeat: (mode: MusicRepeat) => void
      • stopMusic: () => void
      • thumbnailUrlFor: (videoId: string | null | undefined) => string | null

        The still frame for a video id, or null. A plain image; no API key, no script.

      • toggleBroadcasterMute: (token: string) => void
      • toggleMusicMute: () => void

        Silence music without disturbing the level it comes back to.

      • toggleMuteAllNearby: () => void
      • toggleShuffle: () => void
      • unmuteBroadcaster: (token: string) => void