Implement per guild music queue and player
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
package music
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/disgoorg/disgolink/v4/disgolink"
|
||||
"github.com/disgoorg/disgolink/v4/lavalink"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoTrackPlaying = errors.New("no track is currently playing")
|
||||
ErrStreamNotSeekable = errors.New("cannot seek within a live stream")
|
||||
)
|
||||
|
||||
type LoopMode int
|
||||
|
||||
const (
|
||||
LoopOff LoopMode = iota
|
||||
LoopTrack
|
||||
LoopQueue
|
||||
)
|
||||
|
||||
func (m LoopMode) String() string {
|
||||
switch m {
|
||||
case LoopTrack:
|
||||
return "track"
|
||||
case LoopQueue:
|
||||
return "queue"
|
||||
default:
|
||||
return "off"
|
||||
}
|
||||
}
|
||||
|
||||
type GuildPlayer struct {
|
||||
mu sync.Mutex
|
||||
player *disgolink.Player
|
||||
queue *Queue
|
||||
loopMode LoopMode
|
||||
}
|
||||
|
||||
func newGuildPlayer(player *disgolink.Player) *GuildPlayer {
|
||||
return &GuildPlayer{
|
||||
player: player,
|
||||
queue: NewQueue(),
|
||||
}
|
||||
}
|
||||
|
||||
type EnqueueResult struct {
|
||||
Started lavalink.Track
|
||||
PlayingNow bool
|
||||
Queued int
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) EnqueueMany(ctx context.Context, tracks []lavalink.Track) (EnqueueResult, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if len(tracks) == 0 {
|
||||
return EnqueueResult{}, nil
|
||||
}
|
||||
|
||||
if g.player.Track != nil {
|
||||
for _, track := range tracks {
|
||||
g.queue.Push(track)
|
||||
}
|
||||
return EnqueueResult{Queued: len(tracks)}, nil
|
||||
}
|
||||
|
||||
first := tracks[0]
|
||||
if err := g.player.Update(ctx, disgolink.WithTrack(first)); err != nil {
|
||||
return EnqueueResult{}, fmt.Errorf("starting track: %w", err)
|
||||
}
|
||||
for _, track := range tracks[1:] {
|
||||
g.queue.Push(track)
|
||||
}
|
||||
return EnqueueResult{Started: first, PlayingNow: true, Queued: len(tracks) - 1}, nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) Skip(ctx context.Context) (next lavalink.Track, hasNext bool, err error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
next, hasNext = g.queue.Pop()
|
||||
if !hasNext {
|
||||
if err := g.player.Update(ctx, disgolink.WithNullTrack()); err != nil {
|
||||
return lavalink.Track{}, false, fmt.Errorf("stopping player: %w", err)
|
||||
}
|
||||
return lavalink.Track{}, false, nil
|
||||
}
|
||||
|
||||
if err := g.player.Update(ctx, disgolink.WithTrack(next)); err != nil {
|
||||
return lavalink.Track{}, false, fmt.Errorf("starting next track: %w", err)
|
||||
}
|
||||
return next, true, nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) PlayNext(ctx context.Context, ended lavalink.Track) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.loopMode == LoopTrack {
|
||||
if err := g.player.Update(ctx, disgolink.WithTrack(ended)); err != nil {
|
||||
return fmt.Errorf("repeating track: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if g.loopMode == LoopQueue {
|
||||
g.queue.Push(ended)
|
||||
}
|
||||
|
||||
next, ok := g.queue.Pop()
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := g.player.Update(ctx, disgolink.WithTrack(next)); err != nil {
|
||||
return fmt.Errorf("starting queued track: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) Seek(ctx context.Context, position lavalink.Duration) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.player.Track == nil {
|
||||
return ErrNoTrackPlaying
|
||||
}
|
||||
if g.player.Track.Info.IsStream {
|
||||
return ErrStreamNotSeekable
|
||||
}
|
||||
|
||||
if position < 0 {
|
||||
position = 0
|
||||
} else if position > g.player.Track.Info.Length {
|
||||
position = g.player.Track.Info.Length
|
||||
}
|
||||
|
||||
if err := g.player.Update(ctx, disgolink.WithPosition(position)); err != nil {
|
||||
return fmt.Errorf("seeking: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) SetVolume(ctx context.Context, volume int) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if err := g.player.Update(ctx, disgolink.WithVolume(volume)); err != nil {
|
||||
return fmt.Errorf("updating volume: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) SetLoopMode(mode LoopMode) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.loopMode = mode
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) Stop(ctx context.Context) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
g.queue.Clear()
|
||||
if err := g.player.Update(ctx, disgolink.WithNullTrack()); err != nil {
|
||||
return fmt.Errorf("stopping player: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) SetPaused(ctx context.Context, paused bool) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if err := g.player.Update(ctx, disgolink.WithPaused(paused)); err != nil {
|
||||
return fmt.Errorf("updating pause state: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) NowPlaying() (lavalink.Track, bool) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
|
||||
if g.player.Track == nil {
|
||||
return lavalink.Track{}, false
|
||||
}
|
||||
return *g.player.Track, true
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) Queued() []lavalink.Track {
|
||||
return g.queue.List()
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) Shuffle() int {
|
||||
return g.queue.Shuffle()
|
||||
}
|
||||
|
||||
func (g *GuildPlayer) RemoveQueued(index int) (lavalink.Track, bool) {
|
||||
return g.queue.RemoveAt(index)
|
||||
}
|
||||
Reference in New Issue
Block a user