Files

208 lines
4.3 KiB
Go

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)
}