Remove deprecated classes and static singleton-based architecture
This commit is contained in:
1 parent
54a27883d1
commit
b8786c8dc9
133 files changed
+4984
-1038
No files matched your search
@@ -0,0 +1,93 @@
|
||||
# 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<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()`](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).
|
||||
Reference in new issue
Block a user