more playback commands
This commit is contained in:
+105
-25
@@ -3,24 +3,36 @@ package backend
|
|||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PlaybackCommandType int
|
type playbackCommandType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
CmdStop PlaybackCommandType = iota
|
cmdStop playbackCommandType = iota
|
||||||
CmdContinue
|
cmdContinue
|
||||||
CmdPause
|
cmdPause
|
||||||
CmdPlayTrackAt
|
cmdPlayTrackAt // arg: int
|
||||||
CmdSeekSeconds
|
cmdSeekSeconds // arg: float64
|
||||||
CmdSeekFwdBackN
|
cmdSeekFwdBackN // arg: int
|
||||||
CmdVolume
|
cmdVolume // arg: int
|
||||||
CmdLoopMode
|
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 PlaybackCommand struct {
|
||||||
Type PlaybackCommandType
|
Type playbackCommandType
|
||||||
Arg any
|
Arg any
|
||||||
|
Arg2 any
|
||||||
|
Arg3 any
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandQueue struct {
|
type CommandQueue struct {
|
||||||
@@ -42,36 +54,84 @@ func (c *CommandQueue) C() <-chan PlaybackCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandQueue) Stop() {
|
func (c *CommandQueue) Stop() {
|
||||||
c.filterCommandsAndAdd([]PlaybackCommandType{CmdContinue, CmdPause, CmdStop},
|
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
|
||||||
PlaybackCommand{Type: CmdStop})
|
PlaybackCommand{Type: cmdStop})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandQueue) Continue() {
|
func (c *CommandQueue) Continue() {
|
||||||
c.filterCommandsAndAdd([]PlaybackCommandType{CmdContinue, CmdPause, CmdStop},
|
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
|
||||||
PlaybackCommand{Type: CmdContinue})
|
PlaybackCommand{Type: cmdContinue})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandQueue) Pause() {
|
func (c *CommandQueue) Pause() {
|
||||||
c.filterCommandsAndAdd([]PlaybackCommandType{CmdContinue, CmdPause, CmdStop},
|
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop},
|
||||||
PlaybackCommand{Type: CmdPause})
|
PlaybackCommand{Type: cmdPause})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandQueue) Volume(vol int) {
|
func (c *CommandQueue) StopAndClearPlayQueue() {
|
||||||
c.filterCommandsAndAdd([]PlaybackCommandType{CmdVolume},
|
c.filterCommandsAndAdd([]playbackCommandType{cmdContinue, cmdPause, cmdStop, cmdStopAndClearPlayQueue},
|
||||||
PlaybackCommand{Type: CmdVolume, Arg: vol})
|
PlaybackCommand{Type: cmdStopAndClearPlayQueue})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CommandQueue) LoopMode(mode LoopMode) {
|
func (c *CommandQueue) SetVolume(vol int) {
|
||||||
c.filterCommandsAndAdd([]PlaybackCommandType{CmdLoopMode},
|
c.filterCommandsAndAdd([]playbackCommandType{cmdVolume},
|
||||||
PlaybackCommand{Type: CmdLoopMode, Arg: mode})
|
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) {
|
func (c *CommandQueue) SeekSeconds(s float64) {
|
||||||
c.filterCommandsAndAdd([]PlaybackCommandType{CmdVolume},
|
c.filterCommandsAndAdd([]playbackCommandType{cmdSeekSeconds},
|
||||||
PlaybackCommand{Type: CmdSeekSeconds, Arg: s})
|
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()
|
c.mutex.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
@@ -87,6 +147,26 @@ func (c *CommandQueue) filterCommandsAndAdd(excludeTypes []PlaybackCommandType,
|
|||||||
c.queue = append(c.queue, command)
|
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() {
|
func (c *CommandQueue) chanWriter() {
|
||||||
for {
|
for {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
|||||||
@@ -213,14 +213,14 @@ func (p *playbackEngine) Continue() error {
|
|||||||
// Load items into the play queue.
|
// Load items into the play queue.
|
||||||
// If replacing the current queue (!appendToQueue), playback will be stopped.
|
// If replacing the current queue (!appendToQueue), playback will be stopped.
|
||||||
func (p *playbackEngine) LoadItems(items []mediaprovider.MediaItem, insertQueueMode InsertQueueMode, shuffle bool) error {
|
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)
|
return p.doLoaditems(newItems, insertQueueMode, shuffle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load tracks into the play queue.
|
// Load tracks into the play queue.
|
||||||
// If replacing the current queue (!appendToQueue), playback will be stopped.
|
// If replacing the current queue (!appendToQueue), playback will be stopped.
|
||||||
func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMode InsertQueueMode, shuffle bool) error {
|
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)
|
return p.doLoaditems(newTracks, insertQueueMode, shuffle)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,7 +288,7 @@ func (p *playbackEngine) StopAndClearPlayQueue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) GetPlayQueue() []mediaprovider.MediaItem {
|
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,
|
// 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,
|
// 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.
|
// 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 {
|
func (p *playbackEngine) UpdatePlayQueue(items []mediaprovider.MediaItem) error {
|
||||||
newQueue := p.deepCopyMediaItemSlice(items)
|
newQueue := deepCopyMediaItemSlice(items)
|
||||||
newNowPlayingIdx := -1
|
newNowPlayingIdx := -1
|
||||||
if p.nowPlayingIdx >= 0 {
|
if p.nowPlayingIdx >= 0 {
|
||||||
nowPlayingID := p.playQueue[p.nowPlayingIdx].Metadata().ID
|
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
|
// 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
|
// (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))
|
newTracks := make([]mediaprovider.MediaItem, len(tracks))
|
||||||
for i, tr := range tracks {
|
for i, tr := range tracks {
|
||||||
newTracks[i] = tr.Copy()
|
newTracks[i] = tr.Copy()
|
||||||
@@ -599,7 +599,7 @@ func (p *playbackEngine) deepCopyMediaItemSlice(tracks []mediaprovider.MediaItem
|
|||||||
return newTracks
|
return newTracks
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) copyTrackSliceToMediaItemSlice(tracks []*mediaprovider.Track) []mediaprovider.MediaItem {
|
func copyTrackSliceToMediaItemSlice(tracks []*mediaprovider.Track) []mediaprovider.MediaItem {
|
||||||
newTracks := make([]mediaprovider.MediaItem, len(tracks))
|
newTracks := make([]mediaprovider.MediaItem, len(tracks))
|
||||||
for i, tr := range tracks {
|
for i, tr := range tracks {
|
||||||
newTracks[i] = tr.Copy()
|
newTracks[i] = tr.Copy()
|
||||||
|
|||||||
Reference in New Issue
Block a user