Files
supersonic/backend/playbackcommands.go
T
2024-08-28 08:25:13 -07:00

183 lines
4.2 KiB
Go

package backend
import (
"slices"
"sync"
"github.com/dweymouth/supersonic/backend/mediaprovider"
)
type playbackCommandType int
const (
cmdStop playbackCommandType = iota
cmdContinue
cmdPause
cmdPlayTrackAt // arg: int
cmdSeekSeconds // arg: float64
cmdSeekFwdBackN // arg: int
cmdVolume // arg: int
cmdLoopMode // arg: LoopMode
cmdStopAndClearPlayQueue
cmdUpdatePlayQueue // arg: []mediaprovider.MediaItem
cmdRemoveTracksFromQueue // arg: []int
// arg: []mediaprovider.MediaItem
// arg2: InsertMode
// arg3: bool (shuffle)
cmdLoadItems
cmdLoadRadioStation // arg: *mediaprovider.RadioStation, arg2: InsertQueueMode
)
type PlaybackCommand struct {
Type playbackCommandType
Arg any
Arg2 any
Arg3 any
}
type CommandQueue struct {
mutex sync.Mutex
queue []PlaybackCommand
cmdAvailable *sync.Cond
nextChan chan (PlaybackCommand)
}
func NewCommandQueue() *CommandQueue {
c := &CommandQueue{}
c.cmdAvailable = sync.NewCond(&c.mutex)
go c.chanWriter()
return c
}
func (c *CommandQueue) C() <-chan PlaybackCommand {
return c.nextChan
}
func (c *CommandQueue) Stop() {
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
PlaybackCommand{Type: cmdStop})
}
func (c *CommandQueue) Continue() {
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
PlaybackCommand{Type: cmdContinue})
}
func (c *CommandQueue) Pause() {
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
PlaybackCommand{Type: cmdPause})
}
func (c *CommandQueue) StopAndClearPlayQueue() {
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop, cmdStopAndClearPlayQueue},
PlaybackCommand{Type: cmdStopAndClearPlayQueue})
}
func (c *CommandQueue) SetVolume(vol int) {
c.filterCommandsAndAdd([]playbackCommandType{cmdVolume},
PlaybackCommand{Type: cmdVolume, Arg: vol})
}
func (c *CommandQueue) SetLoopMode(mode LoopMode) {
c.filterCommandsAndAdd([]playbackCommandType{cmdLoopMode},
PlaybackCommand{Type: cmdLoopMode, Arg: mode})
}
func (c *CommandQueue) SeekSeconds(s float64) {
c.filterCommandsAndAdd([]playbackCommandType{cmdSeekSeconds},
PlaybackCommand{Type: cmdSeekSeconds, Arg: s})
}
func (c *CommandQueue) SeekNext() {
c.seekBackOrFwd(1)
}
func (c *CommandQueue) SeekBackOrPrevious() {
c.seekBackOrFwd(-1)
}
func (c *CommandQueue) UpdatePlayQueue(items []mediaprovider.MediaItem) {
c.filterCommandsAndAdd([]playbackCommandType{cmdUpdatePlayQueue},
PlaybackCommand{Type: cmdUpdatePlayQueue, Arg: items})
}
func (c *CommandQueue) RemoveItemsFromQueue(idxs []int) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.queue = append(c.queue, PlaybackCommand{
Type: cmdRemoveTracksFromQueue,
Arg: idxs,
})
}
func (c *CommandQueue) LoadRadioStation(radio *mediaprovider.RadioStation, insertMode InsertQueueMode) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.queue = append(c.queue, PlaybackCommand{
Type: cmdLoadRadioStation,
Arg: radio,
Arg2: insertMode,
})
}
func (c *CommandQueue) LoadItems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) {
c.mutex.Lock()
defer c.mutex.Unlock()
c.queue = append(c.queue, PlaybackCommand{
Type: cmdLoadItems,
Arg: items,
Arg2: insertQueueMode,
Arg3: shuffle,
})
}
func (c *CommandQueue) filterCommandsAndAdd(excludeTypes []playbackCommandType, command PlaybackCommand) {
c.mutex.Lock()
defer c.mutex.Unlock()
j := 0
for _, cmd := range c.queue {
if slices.Contains(excludeTypes, cmd.Type) {
continue
}
c.queue[j] = cmd
j++
}
c.queue = c.queue[:j]
c.queue = append(c.queue, command)
}
func (c *CommandQueue) seekBackOrFwd(direction int) {
c.mutex.Lock()
defer c.mutex.Unlock()
j := 0
n := 0
for _, cmd := range c.queue {
if cmd.Type == cmdSeekFwdBackN {
n += cmd.Arg.(int)
} else {
c.queue[j] = cmd
j++
}
}
c.queue = c.queue[:j]
c.queue = append(c.queue, PlaybackCommand{
Type: cmdSeekFwdBackN,
Arg: n + direction})
}
func (c *CommandQueue) chanWriter() {
for {
c.mutex.Lock()
for len(c.queue) == 0 {
c.cmdAvailable.Wait()
}
cmd := c.queue[0]
copy(c.queue, c.queue[1:])
c.queue = c.queue[:len(c.queue)-1]
c.mutex.Unlock()
c.nextChan <- cmd
}
}