# Music **Package:** `me.bobert.netneutrality.feature.music` **Engine:** [Lavaplayer](https://github.com/lavalink-devs/lavaplayer) 2.2.6 + the Lavalink YouTube source 1.18.2. The music subsystem provides per-guild audio playback: searching/loading tracks from multiple sources, queueing, scheduling, and streaming audio into a Discord voice channel. ## Key classes | Class | Responsibility | |---|---| | `MusicManager` | Central per-guild coordinator; owns the `AudioPlayer` and `TrackScheduler`. | | `QueueManager` | Holds the guild's track queue (`LinkedBlockingDeque`); enqueue/skip/shuffle. | | `TrackScheduler` | `AudioEventAdapter`; reacts to Lavaplayer events (`onTrackEnd`, `onTrackStuck`). | | `TrackLoadingHandler` | `AudioLoadResultHandler`; handles track/playlist/no-match/failure results. | | `TrackInfo` | Value object pairing an `AudioTrack` with its requester (`Member`). | | `SourceFinder` | Identifies the source/type of a query (YouTube, Udio, HTTP, …). | | `AudioPlayerSendHandler` | Bridges Lavaplayer output to JDA's `AudioSendHandler`. | | `source/UdioAudioSourceManager` | Custom Lavaplayer source manager for udio.com. | | `source/UdioAudioTrack` | Udio track; delegates playback to `Mp3AudioTrack`. | `MusicManager` keeps a static `Map` of managers keyed by guild id, so each guild has its own player, scheduler, and queue. ## Startup `MusicManager.initPlayerManager()` (called from [`App.startup()`](architecture.md#startup-lifecycle)) creates the Lavaplayer `AudioPlayerManager` and registers the source managers (YouTube, SoundCloud, the custom [Udio source](#custom-udio-source), and HTTP). ## Audio pipeline (end-to-end) ``` /play query │ ▼ MusicManager.play(query, ctx) │ SourceFinder identifies the source ▼ audioPlayerManager.loadItemOrdered(guild, identifier, new TrackLoadingHandler(...)) │ ├─ trackLoaded ──▶ connect to voice (MusicUtils) ──▶ QueueManager.queue(track, member) ├─ playlistLoaded / searchResults ──▶ install a LoadPlaylistTask (choice buttons) ├─ noMatches ──▶ reply "nothing found" └─ loadFailed ──▶ reply / log error │ ▼ QueueManager: if the player is idle, start immediately; else append to the deque │ ▼ AudioPlayerSendHandler streams PCM/Opus frames to the voice channel │ ▼ (track ends) TrackScheduler.onTrackEnd ──▶ QueueManager.startNextTrack() ``` - **Requester tracking** — each queued item is a `TrackInfo` (track + requesting `Member`), used for now-playing/queue displays and vote-skip. - **Playlists / search** — playlist and multi-result loads produce a [`LoadPlaylistTask`](tasks.md#concrete-task-catalog) with per-result buttons (`LoadTrackFromPlaylistTask`), tying the music and [task](tasks.md) subsystems together. Playlist size is bounded by the [`MUSIC_MAXPLAYLISTSIZE`](persistence.md#gsettings) guild setting. - **Stuck tracks** — `TrackScheduler.onTrackStuck` attempts recovery (reload/skip). - **Output** — playback messages are posted through a [`MusicChannelContext`](contexts.md#musicchannelcontext) so they land in the guild's configured music channel. ## Custom Udio source `UdioAudioSourceManager` scrapes udio.com track pages: it extracts track metadata (title, artist, duration) and a direct playback URL from the Next.js payload (`self.__next_f.push(...)` calls) in the page HTML. Because Udio playback URLs expire, `UdioAudioTrack` **re-fetches** the URL during the `process` phase of the pipeline before streaming, delegating the actual decode/playback to Lavaplayer's `Mp3AudioTrack`. ## Guild lifecycle & cleanup - `MusicPlayerAutoDisconnect` (scheduled task) leaves voice when the player has been idle. - `MusicManagerCleanup` (scheduled task) evicts unused per-guild `MusicManager` instances from the static map. - On shutdown, `MusicManager.disconnectPlayers()` stops all players and disconnects from voice (called from [`App.shutdown()`](architecture.md#shutdown-lifecycle)). ## Related commands All music commands extend [`MusicCommand`](commands.md#base-implementations-corecommandimpl) (guild only + music role/channel enforced): `PlayCommand`, `SkipCommand`/`VoteSkipCommand`, `QueueCommand`/`ViewQueueCommand`, `ClearQueueCommand`, `ShuffleCommand`, `JoinCommand`/`LeaveCommand`, `CurrentlyPlayingCommand`, `ResumeCommand`/`StopCommand`, `VolumeCommand`, `BitconnectCommand`. See [commands.md](commands.md#command-catalog).