load full queue state on start
This commit is contained in:
+21
-20
@@ -622,11 +622,11 @@ func (a *App) SavePlayQueueIfEnabled() {
|
||||
func (a *App) LoadSavedPlayQueue() error {
|
||||
queueFilePath := path.Join(a.configDir, savedQueueFile)
|
||||
playQueue, err := LoadPlayQueue(queueFilePath, a.ServerManager, a.Config.Application.SaveQueueToServer)
|
||||
|
||||
var unshuffledPlayQueue *SavedPlayQueue
|
||||
var shuffledPlayQueue *SavedPlayQueue
|
||||
|
||||
isShuffe := a.Config.Playback.Shuffle
|
||||
|
||||
if isShuffe {
|
||||
unshuffledQueueFilePath := path.Join(a.configDir, savedUnshuffledQueueFile)
|
||||
unshuffledPlayQueue, err = LoadPlayQueue(unshuffledQueueFilePath, a.ServerManager, false)
|
||||
@@ -635,7 +635,7 @@ func (a *App) LoadSavedPlayQueue() error {
|
||||
return err
|
||||
}
|
||||
|
||||
shuffledQueueFilePath := path.Join(a.configDir, savedUnshuffledQueueFile)
|
||||
shuffledQueueFilePath := path.Join(a.configDir, savedShuffledQueueFile)
|
||||
shuffledPlayQueue, err = LoadPlayQueue(shuffledQueueFilePath, a.ServerManager, false)
|
||||
|
||||
if err != nil {
|
||||
@@ -653,7 +653,25 @@ func (a *App) LoadSavedPlayQueue() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
a.PlaybackManager.LoadTracks(playQueue.Tracks, Replace, false)
|
||||
if isShuffe {
|
||||
serverStatePlayQueue := sharedutil.CopyTrackSliceToMediaItemSlice(playQueue.Tracks)
|
||||
clientStatePlayQueue := sharedutil.CopyTrackSliceToMediaItemSlice(shuffledPlayQueue.Tracks)
|
||||
|
||||
// Compare items by ID. This fails if any 2 elements don't match up. Two queues with the same items but different order will thus not count as same
|
||||
if slices.EqualFunc(serverStatePlayQueue, clientStatePlayQueue, func(a, b mediaprovider.MediaItem) bool {
|
||||
return (a.Metadata().ID == b.Metadata().ID)
|
||||
}) {
|
||||
a.PlaybackManager.SetQueueState(playQueue.Tracks, ShuffledPlayQueue)
|
||||
a.PlaybackManager.SetQueueState(unshuffledPlayQueue.Tracks, PlayQueue)
|
||||
} else {
|
||||
a.PlaybackManager.SetShuffle(false)
|
||||
a.PlaybackManager.SetQueueState(playQueue.Tracks, PlayQueue)
|
||||
}
|
||||
|
||||
} else {
|
||||
a.PlaybackManager.SetQueueState(playQueue.Tracks, PlayQueue)
|
||||
}
|
||||
|
||||
if playQueue.TrackIndex >= 0 && playQueue.TrackIndex < len(playQueue.Tracks) {
|
||||
// TODO: This isn't ideal but doesn't seem to cause an audible play-for-a-split-second artifact
|
||||
a.PlaybackManager.PlayTrackAt(playQueue.TrackIndex)
|
||||
@@ -662,23 +680,6 @@ func (a *App) LoadSavedPlayQueue() error {
|
||||
a.PlaybackManager.SeekSeconds(playQueue.TimePos)
|
||||
}
|
||||
|
||||
if isShuffe {
|
||||
// check if queue was changed server side
|
||||
if len(playQueue.Tracks) != len(unshuffledPlayQueue.Tracks) {
|
||||
// queue was changed server side, no need to compare elements
|
||||
a.PlaybackManager.SetShuffle(false)
|
||||
} else {
|
||||
serverStatePlayQueue := sharedutil.CopyTrackSliceToMediaItemSlice(playQueue.Tracks)
|
||||
clientStatePlayQueue := sharedutil.CopyTrackSliceToMediaItemSlice(shuffledPlayQueue.Tracks)
|
||||
|
||||
if slices.Equal(serverStatePlayQueue, clientStatePlayQueue) {
|
||||
fmt.Println("Loading unshuffled items into playQueue")
|
||||
a.PlaybackManager.LoadTracks(unshuffledPlayQueue.Tracks, Replace, false)
|
||||
} else {
|
||||
a.PlaybackManager.SetShuffle(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
// arg2: InsertMode
|
||||
// arg3: bool (shuffle)
|
||||
cmdLoadItems
|
||||
cmdSetQueueState
|
||||
cmdLoadRadioStation // arg: *mediaprovider.RadioStation, arg2: InsertQueueMode
|
||||
|
||||
cmdForceRestartPlayback
|
||||
@@ -160,6 +161,17 @@ func (c *playbackCommandQueue) LoadItems(items []mediaprovider.MediaItem, insert
|
||||
c.cmdAvailable.Signal()
|
||||
}
|
||||
|
||||
func (c *playbackCommandQueue) SetQueueState(tracks []*mediaprovider.Track, queueType QueueType) {
|
||||
c.mutex.Lock()
|
||||
c.queue = append(c.queue, playbackCommand{
|
||||
Type: cmdSetQueueState,
|
||||
Arg: tracks,
|
||||
Arg2: queueType,
|
||||
})
|
||||
c.mutex.Unlock()
|
||||
c.cmdAvailable.Signal()
|
||||
}
|
||||
|
||||
func (c *playbackCommandQueue) addCommand(command playbackCommand) {
|
||||
c.mutex.Lock()
|
||||
c.queue = append(c.queue, command)
|
||||
@@ -194,7 +206,7 @@ func (c *playbackCommandQueue) seekBackOrFwd(direction int) {
|
||||
switch cmd.Type {
|
||||
case cmdSeekFwdBackN:
|
||||
lastIdx = i
|
||||
case cmdRemoveTracksFromQueue, cmdLoadItems, cmdPlayTrackAt,
|
||||
case cmdRemoveTracksFromQueue, cmdLoadItems, cmdSetQueueState, cmdPlayTrackAt,
|
||||
cmdLoadRadioStation, cmdUpdatePlayQueue, cmdStopAndClearPlayQueue:
|
||||
// any queue-modifying command means we can't coalesce any
|
||||
// more seekFwdBackN commands before here
|
||||
|
||||
+40
-20
@@ -110,6 +110,7 @@ type playbackEngine struct {
|
||||
onSongChange []func(nowPlaying mediaprovider.MediaItem, justScrobbledIfAny *mediaprovider.Track)
|
||||
onPlayTimeUpdate []func(float64, float64, bool)
|
||||
onLoopModeChange []func(LoopMode)
|
||||
onShuffleChange []func(bool)
|
||||
onVolumeChange []func(int)
|
||||
onSeek []func()
|
||||
onPaused []func()
|
||||
@@ -149,6 +150,8 @@ func NewPlaybackEngine(
|
||||
pm.loopMode = LoopOne
|
||||
}
|
||||
|
||||
pm.shuffle = playbackCfg.Shuffle
|
||||
|
||||
pm.registerPlayerCallbacks(p)
|
||||
s.OnLogout(func() {
|
||||
pm.StopAndClearPlayQueue()
|
||||
@@ -231,12 +234,13 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error {
|
||||
// ======================= START PLAY QUEUE FUNCS ===========================
|
||||
|
||||
func (p *playbackEngine) getPlayQueue() []mediaprovider.MediaItem {
|
||||
if p.shuffle {
|
||||
return p.shuffledPlayQueue
|
||||
}
|
||||
return p.playQueue
|
||||
}
|
||||
|
||||
func (p *playbackEngine) getShuffledPlayQueue() []mediaprovider.MediaItem {
|
||||
return p.shuffledPlayQueue
|
||||
}
|
||||
|
||||
func (p *playbackEngine) getActivePlayQueue() []mediaprovider.MediaItem {
|
||||
if p.shuffle {
|
||||
return p.shuffledPlayQueue
|
||||
@@ -245,10 +249,12 @@ func (p *playbackEngine) getActivePlayQueue() []mediaprovider.MediaItem {
|
||||
}
|
||||
|
||||
func (p *playbackEngine) getPlayQueueLength() int {
|
||||
return len(p.getPlayQueue())
|
||||
return len(p.getActivePlayQueue())
|
||||
}
|
||||
|
||||
func (p *playbackEngine) clearPlayQueue() {
|
||||
p.player.Stop(false)
|
||||
p.nowPlayingIdx = -1
|
||||
p.playQueue = nil
|
||||
p.shuffledPlayQueue = nil
|
||||
}
|
||||
@@ -262,7 +268,7 @@ func (p *playbackEngine) setShuffledPlayQueue(items []mediaprovider.MediaItem) {
|
||||
}
|
||||
|
||||
func (p *playbackEngine) getPlayQueueItemAt(idx int) mediaprovider.MediaItem {
|
||||
return p.getPlayQueue()[idx]
|
||||
return p.getActivePlayQueue()[idx]
|
||||
}
|
||||
|
||||
func (p *playbackEngine) insertItemsIntoPlayQueueAt(items []mediaprovider.MediaItem, idx int, queueType QueueType) {
|
||||
@@ -282,7 +288,7 @@ func (p *playbackEngine) GetPlayQueueDeepCopy() []mediaprovider.MediaItem {
|
||||
}
|
||||
|
||||
func (p *playbackEngine) GetShuffledPlayQueueDeepCopy() []mediaprovider.MediaItem {
|
||||
return deepCopyMediaItemSlice(p.getPlayQueue())
|
||||
return deepCopyMediaItemSlice(p.getShuffledPlayQueue())
|
||||
}
|
||||
|
||||
func (p *playbackEngine) GetActivePlayQueueDeepCopy() []mediaprovider.MediaItem {
|
||||
@@ -351,23 +357,26 @@ func (p *playbackEngine) GetNowPlayingIdxFrom(items []mediaprovider.MediaItem) i
|
||||
}
|
||||
|
||||
func (p *playbackEngine) SetShuffle(shuffle bool) {
|
||||
for _, cb := range p.onShuffleChange {
|
||||
cb(shuffle)
|
||||
}
|
||||
|
||||
if p.shuffle == shuffle {
|
||||
return
|
||||
}
|
||||
|
||||
var newNowPlayingIdx int
|
||||
newNowPlayingIdx := 0
|
||||
if shuffle {
|
||||
shuffledQueue := deepCopyMediaItemSlice(p.playQueue)
|
||||
rand.Shuffle(len(shuffledQueue), func(i, j int) {
|
||||
shuffledQueue[i], shuffledQueue[j] = shuffledQueue[j], shuffledQueue[i]
|
||||
})
|
||||
p.shuffledPlayQueue = sharedutil.ReorderItems(shuffledQueue, []int{p.GetNowPlayingIdxFrom(shuffledQueue)}, 0)
|
||||
newNowPlayingIdx = 0
|
||||
p.setShuffledPlayQueue(sharedutil.ReorderItems(shuffledQueue, []int{p.GetNowPlayingIdxFrom(shuffledQueue)}, 0))
|
||||
} else {
|
||||
newNowPlayingIdx = p.GetNowPlayingIdxFrom(p.playQueue)
|
||||
}
|
||||
|
||||
p.shuffle = shuffle
|
||||
|
||||
if p.nowPlayingIdx >= 0 && newNowPlayingIdx == -1 {
|
||||
return
|
||||
}
|
||||
@@ -471,6 +480,23 @@ func (p *playbackEngine) Continue() error {
|
||||
return p.player.Continue()
|
||||
}
|
||||
|
||||
// Load items into the specified queue(s). This overrides the queue. For standard inserts or replaces use p.LoadItems
|
||||
// This is used on program startup to populate both the playQueue and shuffledPlayQueue with the previously saved client state.
|
||||
func (p *playbackEngine) SetQueueState(tracks []*mediaprovider.Track, queueType QueueType) error {
|
||||
newTracks := sharedutil.CopyTrackSliceToMediaItemSlice(tracks)
|
||||
switch queueType {
|
||||
case PlayQueue:
|
||||
p.setPlayQueue(newTracks)
|
||||
case ShuffledPlayQueue:
|
||||
p.setShuffledPlayQueue(newTracks)
|
||||
case Both:
|
||||
p.setPlayQueue(newTracks)
|
||||
p.setShuffledPlayQueue(newTracks)
|
||||
}
|
||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -489,8 +515,6 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu
|
||||
queueType := PlayQueue
|
||||
|
||||
if insertQueueMode == Replace {
|
||||
p.player.Stop(false)
|
||||
p.nowPlayingIdx = -1
|
||||
p.clearPlayQueue()
|
||||
}
|
||||
if nextChanged := len(items) > 0 && (insertQueueMode != Append || (p.nowPlayingIdx == p.getPlayQueueLength()-1)); nextChanged {
|
||||
@@ -528,8 +552,6 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu
|
||||
|
||||
func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, insertMode InsertQueueMode) {
|
||||
if insertMode == Replace {
|
||||
p.player.Stop(false)
|
||||
p.nowPlayingIdx = -1
|
||||
p.clearPlayQueue()
|
||||
}
|
||||
if nextChanged := insertMode == InsertNext || (insertMode == Append && p.nowPlayingIdx == p.getPlayQueueLength()-1); nextChanged {
|
||||
@@ -546,9 +568,7 @@ func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, ins
|
||||
// Stop playback and clear the play queue.
|
||||
func (p *playbackEngine) StopAndClearPlayQueue() {
|
||||
changed := p.getPlayQueueLength() > 0
|
||||
p.player.Stop(false)
|
||||
p.clearPlayQueue()
|
||||
p.nowPlayingIdx = -1
|
||||
if changed {
|
||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||
}
|
||||
@@ -557,7 +577,7 @@ func (p *playbackEngine) StopAndClearPlayQueue() {
|
||||
// Any time the user changes the favorite status of a track elsewhere in the app,
|
||||
// this should be called to ensure the in-memory track model is updated.
|
||||
func (p *playbackEngine) OnTrackFavoriteStatusChanged(id string, fav bool) {
|
||||
if item := sharedutil.FindMediaItemByID(id, p.getPlayQueue()); item != nil {
|
||||
if item := sharedutil.FindMediaItemByID(id, p.getActivePlayQueue()); item != nil {
|
||||
if tr, ok := item.(*mediaprovider.Track); ok {
|
||||
tr.Favorite = fav
|
||||
}
|
||||
@@ -567,7 +587,7 @@ func (p *playbackEngine) OnTrackFavoriteStatusChanged(id string, fav bool) {
|
||||
// Any time the user changes the rating of a track elsewhere in the app,
|
||||
// this should be called to ensure the in-memory track model is updated.
|
||||
func (p *playbackEngine) OnTrackRatingChanged(id string, rating int) {
|
||||
if item := sharedutil.FindMediaItemByID(id, p.getPlayQueue()); item != nil {
|
||||
if item := sharedutil.FindMediaItemByID(id, p.getActivePlayQueue()); item != nil {
|
||||
if tr, ok := item.(*mediaprovider.Track); ok {
|
||||
tr.Rating = rating
|
||||
}
|
||||
@@ -616,7 +636,7 @@ func (p *playbackEngine) RemoveTracksFromQueue(idxs []int) {
|
||||
// remove tracks by ID from playQueue
|
||||
ids := p.GetTrackIdsFromIdx(idxs)
|
||||
newPlayQueue := make([]mediaprovider.MediaItem, 0, p.getPlayQueueLength()-len(idxs))
|
||||
for _, tr := range p.getPlayQueue() {
|
||||
for _, tr := range p.getActivePlayQueue() {
|
||||
if slices.Contains(ids, tr.Metadata().ID) {
|
||||
//remove id from id list, handles having the same track present multiple times in playQueue
|
||||
idx := slices.Index(ids, tr.Metadata().ID)
|
||||
@@ -664,7 +684,7 @@ func (p *playbackEngine) RemoveTracksFromQueueByIdx(idxs []int, newQueue *[]medi
|
||||
idxSet := sharedutil.ToSet(idxs)
|
||||
nowPlaying := p.NowPlayingIndex()
|
||||
|
||||
for i, tr := range p.getPlayQueue() {
|
||||
for i, tr := range p.getActivePlayQueue() {
|
||||
if _, ok := idxSet[i]; ok {
|
||||
if i < nowPlaying {
|
||||
// if removing a track earlier than the currently playing one (if any),
|
||||
|
||||
@@ -333,6 +333,11 @@ func (p *PlaybackManager) OnLoopModeChange(cb func(LoopMode)) {
|
||||
p.engine.onLoopModeChange = append(p.engine.onLoopModeChange, cb)
|
||||
}
|
||||
|
||||
// Registers a callback that is notified whenever the shuffle state changes.
|
||||
func (p *PlaybackManager) OnShuffleChange(cb func(bool)) {
|
||||
p.engine.onShuffleChange = append(p.engine.onShuffleChange, cb)
|
||||
}
|
||||
|
||||
// Registers a callback that is notified whenever the volume changes.
|
||||
func (p *PlaybackManager) OnVolumeChange(cb func(int)) {
|
||||
p.engine.onVolumeChange = append(p.engine.onVolumeChange, cb)
|
||||
@@ -400,6 +405,12 @@ func (p *PlaybackManager) LoadItems(items []mediaprovider.MediaItem, insertQueue
|
||||
p.cmdQueue.LoadItems(items, insertQueueMode, shuffle)
|
||||
}
|
||||
|
||||
// Load items into the currently active queue. (shuffledPlayQueue/playQueue)
|
||||
// If replacing the current queue (!appendToQueue), playback will be stopped.
|
||||
func (p *PlaybackManager) SetQueueState(tracks []*mediaprovider.Track, queueType QueueType) {
|
||||
p.cmdQueue.SetQueueState(tracks, queueType)
|
||||
}
|
||||
|
||||
// Replaces the play queue with the given set of tracks.
|
||||
// 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.
|
||||
@@ -743,7 +754,7 @@ func (p *PlaybackManager) enqueueAutoplayTracks() {
|
||||
}
|
||||
|
||||
// last 500 played items
|
||||
queue := p.GetPlayQueue()
|
||||
queue := p.GetActivePlayQueue()
|
||||
if l := len(queue); l > 500 {
|
||||
queue = queue[l-500:]
|
||||
}
|
||||
@@ -858,6 +869,12 @@ func (p *PlaybackManager) runCmdQueue(ctx context.Context) {
|
||||
c.Arg3.(bool),
|
||||
)
|
||||
logIfErr("LoadItems", err)
|
||||
case cmdSetQueueState:
|
||||
err := p.engine.SetQueueState(
|
||||
c.Arg.([]*mediaprovider.Track),
|
||||
c.Arg2.(QueueType),
|
||||
)
|
||||
logIfErr("SetQueueState", err)
|
||||
case cmdLoadRadioStation:
|
||||
p.engine.LoadRadioStation(
|
||||
c.Arg.(*mediaprovider.RadioStation),
|
||||
|
||||
Reference in New Issue
Block a user