208 lines
4.3 KiB
Go
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)
|
|
}
|