4.4 KiB
Music
Package: me.bobert.netneutrality.feature.music
Engine: 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<TrackInfo>); 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<String, MusicManager> of managers keyed by guild id, so each guild
has its own player, scheduler, and queue.
Startup
MusicManager.initPlayerManager() (called from
App.startup()) creates the Lavaplayer
AudioPlayerManager and registers the source managers (YouTube, SoundCloud, the 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 + requestingMember), used for now-playing/queue displays and vote-skip. - Playlists / search — playlist and multi-result loads produce a
LoadPlaylistTaskwith per-result buttons (LoadTrackFromPlaylistTask), tying the music and task subsystems together. Playlist size is bounded by theMUSIC_MAXPLAYLISTSIZEguild setting. - Stuck tracks —
TrackScheduler.onTrackStuckattempts recovery (reload/skip). - Output — playback messages are posted through a
MusicChannelContextso 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-guildMusicManagerinstances from the static map.- On shutdown,
MusicManager.disconnectPlayers()stops all players and disconnects from voice (called fromApp.shutdown()).
Related commands
All music commands extend MusicCommand (guild
only + music role/channel enforced): PlayCommand, SkipCommand/VoteSkipCommand,
QueueCommand/ViewQueueCommand, ClearQueueCommand, ShuffleCommand, JoinCommand/LeaveCommand,
CurrentlyPlayingCommand, ResumeCommand/StopCommand, VolumeCommand, BitconnectCommand. See
commands.md.