more playback commands

This commit is contained in:
Drew Weymouth
2024-08-28 08:25:13 -07:00
parent 188a94b41b
commit 0fffe9d8ed
2 changed files with 111 additions and 31 deletions
+105 -25
View File
@@ -3,24 +3,36 @@ package backend
import (
"slices"
"sync"
"github.com/dweymouth/supersonic/backend/mediaprovider"
)
type PlaybackCommandType int
type playbackCommandType int
const (
CmdStop PlaybackCommandType = iota
CmdContinue
CmdPause
CmdPlayTrackAt
CmdSeekSeconds
CmdSeekFwdBackN
CmdVolume
CmdLoopMode
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
Type playbackCommandType
Arg any
Arg2 any
Arg3 any
}
type CommandQueue struct {
@@ -42,36 +54,84 @@ func (c *CommandQueue) C() <-chan PlaybackCommand {
}
func (c *CommandQueue) Stop() {
c.filterCommandsAndAdd([]PlaybackCommandType{CmdContinue, CmdPause, CmdStop},
PlaybackCommand{Type: CmdStop})
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
PlaybackCommand{Type: cmdStop})
}
func (c *CommandQueue) Continue() {
c.filterCommandsAndAdd([]PlaybackCommandType{CmdContinue, CmdPause, CmdStop},
PlaybackCommand{Type: CmdContinue})
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
PlaybackCommand{Type: cmdContinue})
}
func (c *CommandQueue) Pause() {
c.filterCommandsAndAdd([]PlaybackCommandType{CmdContinue, CmdPause, CmdStop},
PlaybackCommand{Type: CmdPause})
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
PlaybackCommand{Type: cmdPause})
}
func (c *CommandQueue) Volume(vol int) {
c.filterCommandsAndAdd([]PlaybackCommandType{CmdVolume},
PlaybackCommand{Type: CmdVolume, Arg: vol})
func (c *CommandQueue) StopAndClearPlayQueue() {
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop, cmdStopAndClearPlayQueue},
PlaybackCommand{Type: cmdStopAndClearPlayQueue})
}
func (c *CommandQueue) LoopMode(mode LoopMode) {
c.filterCommandsAndAdd([]PlaybackCommandType{CmdLoopMode},
PlaybackCommand{Type: CmdLoopMode, Arg: mode})
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{CmdVolume},
PlaybackCommand{Type: CmdSeekSeconds, Arg: s})
c.filterCommandsAndAdd([]playbackCommandType{cmdSeekSeconds},
PlaybackCommand{Type: cmdSeekSeconds, Arg: s})
}
func (c *CommandQueue) filterCommandsAndAdd(excludeTypes []PlaybackCommandType, command PlaybackCommand) {
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()
@@ -87,6 +147,26 @@ func (c *CommandQueue) filterCommandsAndAdd(excludeTypes []PlaybackCommandType,
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()
+6 -6
View File
@@ -213,14 +213,14 @@ func (p *playbackEngine) Continue() error {
// Load items into the play queue.
// If replacing the current queue (!appendToQueue), playback will be stopped.
func (p *playbackEngine) LoadItems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) error {
newItems := p.deepCopyMediaItemSlice(items)
newItems := deepCopyMediaItemSlice(items)
return p.doLoaditems(newItems, insertQueueMode, shuffle)
}
// Load tracks into the play queue.
// If replacing the current queue (!appendToQueue), playback will be stopped.
func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error {
newTracks := p.copyTrackSliceToMediaItemSlice(tracks)
newTracks := copyTrackSliceToMediaItemSlice(tracks)
return p.doLoaditems(newTracks, insertQueueMode, shuffle)
}
@@ -288,7 +288,7 @@ func (p *playbackEngine) StopAndClearPlayQueue() {
}
func (p *playbackEngine) GetPlayQueue() []mediaprovider.MediaItem {
return p.deepCopyMediaItemSlice(p.playQueue)
return deepCopyMediaItemSlice(p.playQueue)
}
// Any time the user changes the favorite status of a track elsewhere in the app,
@@ -315,7 +315,7 @@ func (p *playbackEngine) OnTrackRatingChanged(id string, rating int) {
// Does not stop playback if the currently playing track is in the new queue,
// but updates the now playing index to point to the first instance of the track in the new queue.
func (p *playbackEngine) UpdatePlayQueue(items []mediaprovider.MediaItem) error {
newQueue := p.deepCopyMediaItemSlice(items)
newQueue := deepCopyMediaItemSlice(items)
newNowPlayingIdx := -1
if p.nowPlayingIdx >= 0 {
nowPlayingID := p.playQueue[p.nowPlayingIdx].Metadata().ID
@@ -591,7 +591,7 @@ func (p *playbackEngine) sendNowPlayingScrobble() {
// creates a deep copy of the track info so that we can maintain our own state
// (play count increases, favorite, and rating) without messing up other views' track models
func (p *playbackEngine) deepCopyMediaItemSlice(tracks []mediaprovider.MediaItem) []mediaprovider.MediaItem {
func deepCopyMediaItemSlice(tracks []mediaprovider.MediaItem) []mediaprovider.MediaItem {
newTracks := make([]mediaprovider.MediaItem, len(tracks))
for i, tr := range tracks {
newTracks[i] = tr.Copy()
@@ -599,7 +599,7 @@ func (p *playbackEngine) deepCopyMediaItemSlice(tracks []mediaprovider.MediaItem
return newTracks
}
func (p *playbackEngine) copyTrackSliceToMediaItemSlice(tracks []*mediaprovider.Track) []mediaprovider.MediaItem {
func copyTrackSliceToMediaItemSlice(tracks []*mediaprovider.Track) []mediaprovider.MediaItem {
newTracks := make([]mediaprovider.MediaItem, len(tracks))
for i, tr := range tracks {
newTracks[i] = tr.Copy()