Skip to content

Commit

Permalink
fix(jukebox): restore play index only when incoming new track has ind…
Browse files Browse the repository at this point in the history
…ex >0

related #411
  • Loading branch information
sentriz committed Nov 24, 2023
1 parent 5bc29f4 commit 82c3c5b
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 72 deletions.
1 change: 0 additions & 1 deletion .golangci.yml
Expand Up @@ -30,7 +30,6 @@ linters:
- gocritic
- gocyclo
- gofmt
- gofumpt
- goheader
- goimports
- gomoddirectives
Expand Down
62 changes: 23 additions & 39 deletions jukebox/jukebox.go
Expand Up @@ -121,43 +121,44 @@ func (j *Jukebox) SetPlaylist(items []string) error {
if err := getDecode(j.conn, &playlist, "playlist"); err != nil {
return fmt.Errorf("get playlist: %w", err)
}
current, currentIndex := find(playlist, func(item mpvPlaylistItem) bool {
return item.Current
})

filteredItems, foundExistingTrack := filter(items, func(filename string) bool {
return filename != current.Filename
currentPlayingIndex := slices.IndexFunc(playlist, func(pl mpvPlaylistItem) bool {
return pl.Current
})

tmp, cleanup, err := tmp()
if err != nil {
return fmt.Errorf("create temp file: %w", err)
}
defer cleanup()
for _, item := range filteredItems {

var newPlayingIndex = -1
for i, item := range items {
item, _ = filepath.Abs(item)
if currentPlayingIndex >= 0 && playlist[currentPlayingIndex].Filename == item {
newPlayingIndex = i
continue // don't add current track to loadlist
}

fmt.Fprintln(tmp, item)
}

if !foundExistingTrack {
// easy case - a brand new set of tracks that we can overwrite
if newPlayingIndex < 0 {
if _, err := j.conn.Call("loadlist", tmp.Name(), "replace"); err != nil {
return fmt.Errorf("load list: %w", err)
}
return nil
}

// not so easy, we need to clear the playlist except what's playing, load everything
// except for what we're playing, then move what's playing back to its original index
// clear all items except what's playing (will be at index 0)
if _, err := j.conn.Call("playlist-clear"); err != nil {
if _, err := j.conn.Call("playlist-clear"); err != nil { // leaves current at index 0
return fmt.Errorf("clear playlist: %w", err)
}
if _, err := j.conn.Call("loadlist", tmp.Name(), "append-play"); err != nil {
return fmt.Errorf("load list: %w", err)
}
if _, err := j.conn.Call("playlist-move", 0, currentIndex+1); err != nil {
return fmt.Errorf("playlist move: %w", err)
if newPlayingIndex > 0 {
if _, err := j.conn.Call("playlist-move", 0, newPlayingIndex+1); err != nil {
return fmt.Errorf("playlist move: %w", err)
}
}
return nil
}
Expand Down Expand Up @@ -203,9 +204,11 @@ func (j *Jukebox) SkipToPlaylistIndex(i int, offsetSecs int) error {
return fmt.Errorf("pause: %w", err)
}
}

if _, err := j.conn.Call("playlist-play-index", i); err != nil {
return fmt.Errorf("playlist play index: %w", err)
}

if offsetSecs > 0 {
if err := waitFor(j.events, matchEventSeekable); err != nil {
return fmt.Errorf("waiting for file load: %w", err)
Expand Down Expand Up @@ -393,32 +396,13 @@ func tmp() (*os.File, func(), error) {
return tmp, cleanup, nil
}

func find[T any](items []T, f func(T) bool) (T, int) {
for i, item := range items {
if f(item) {
return item, i
}
}
var t T
return t, -1
}

func filter[T comparable](items []T, f func(T) bool) ([]T, bool) {
var found bool
var ret []T
for _, item := range items {
if !f(item) {
found = true
continue
}
ret = append(ret, item)
}
return ret, found
}

func lock(mu *sync.RWMutex) func() {
mu.Lock()
return mu.Unlock
return func() {
// let mpv "settle" since it seems to return from ipc calls before everything is sorted out
time.Sleep(200 * time.Millisecond)
mu.Unlock()
}
}

func lockr(mu *sync.RWMutex) func() {
Expand Down
87 changes: 55 additions & 32 deletions jukebox/jukebox_test.go
Expand Up @@ -12,7 +12,7 @@ import (
)

func TestPlaySkipReset(t *testing.T) {
t.Skip("bit flakey currently")
t.Skip("bit flakey since mpv ipc doesn't block while internal state has settled")

t.Parallel()
j := newJukebox(t)
Expand Down Expand Up @@ -74,7 +74,7 @@ func TestPlaySkipReset(t *testing.T) {
require.Equal(t, true, status.Playing)

// just add one more by overwriting the playlist like some clients do
// we should keep the current track unchaned if we find it
// we should move keep the playing indedx
require.NoError(t, j.SetPlaylist([]string{
"testdata/tr_0.mp3",
"testdata/tr_1.mp3",
Expand All @@ -86,65 +86,88 @@ func TestPlaySkipReset(t *testing.T) {

status, err = j.GetStatus()
require.NoError(t, err)
require.Equal(t, 3, status.CurrentIndex) // index unchanged
require.Equal(t, 3, status.CurrentIndex) // index moved to start
require.Equal(t, testPath("tr_3.mp3"), status.CurrentFilename)
require.Equal(t, 6, status.Length) // we added one more track
require.Equal(t, true, status.Playing)

// skip to 3 again
require.NoError(t, j.SkipToPlaylistIndex(3, 0))

status, err = j.GetStatus()
require.NoError(t, err)
require.Equal(t, 3, status.CurrentIndex)
require.Equal(t, testPath("tr_3.mp3"), status.CurrentFilename)
require.Equal(t, 6, status.Length)
require.Equal(t, true, status.Playing)

// remove all but 3
// new playlist with out current track (tr_2)
require.NoError(t, j.SetPlaylist([]string{
"testdata/tr_0.mp3",
"testdata/tr_1.mp3",
"testdata/tr_2.mp3",
"testdata/tr_3.mp3",
"testdata/tr_6.mp3",
"testdata/tr_7.mp3",
"testdata/tr_8.mp3",
"testdata/tr_9.mp3",
}))

status, err = j.GetStatus()
require.NoError(t, err)
require.Equal(t, 3, status.CurrentIndex) // index unchanged
require.Equal(t, testPath("tr_3.mp3"), status.CurrentFilename)
require.Equal(t, 0, status.CurrentIndex) // index unchanged
require.Equal(t, testPath("tr_6.mp3"), status.CurrentFilename)
require.Equal(t, 4, status.Length)
require.Equal(t, true, status.Playing)

// skip to 2 (5s long) in the middle of the track
// skip to index 2 (5s long) in the middle of the track
require.NoError(t, j.SkipToPlaylistIndex(2, 2))

status, err = j.GetStatus()
require.NoError(t, err)
require.Equal(t, 2, status.CurrentIndex) // index unchanged
require.Equal(t, testPath("tr_2.mp3"), status.CurrentFilename)
require.Equal(t, testPath("tr_8.mp3"), status.CurrentFilename)
require.Equal(t, 4, status.Length)
require.Equal(t, true, status.Playing)
require.Equal(t, 2, status.Position) // at new position
}

func TestShuffle(t *testing.T) {
t.Skip("bit flakey since mpv ipc doesn't block while internal state has settled")

t.Parallel()
j := newJukebox(t)

// overwrite completely
require.NoError(t, j.SetPlaylist([]string{
"testdata/tr_5.mp3",
"testdata/tr_6.mp3",
"testdata/tr_7.mp3",
"testdata/tr_8.mp3",
"testdata/tr_9.mp3",
testPath("tr_0.mp3"),
testPath("tr_1.mp3"),
testPath("tr_2.mp3"),
testPath("tr_3.mp3"),
testPath("tr_4.mp3"),
testPath("tr_5.mp3"),
testPath("tr_6.mp3"),
testPath("tr_7.mp3"),
}))

require.NoError(t, j.SkipToPlaylistIndex(2, 0))

status, err := j.GetStatus()
require.NoError(t, err)
require.Equal(t, 2, status.CurrentIndex)
require.True(t, status.Playing)

desiredOrder := []string{
testPath("tr_2.mp3"), // the was-playing index moves to position 0
testPath("tr_1.mp3"),
testPath("tr_0.mp3"),
testPath("tr_3.mp3"),
testPath("tr_5.mp3"),
testPath("tr_6.mp3"),
testPath("tr_7.mp3"),
testPath("tr_4.mp3"),
}

require.NoError(t, j.SetPlaylist(desiredOrder))

status, err = j.GetStatus()
require.NoError(t, err)
require.Equal(t, 0, status.CurrentIndex) // index unchanged
require.Equal(t, testPath("tr_5.mp3"), status.CurrentFilename)
require.Equal(t, 5, status.Length)
require.Equal(t, true, status.Playing)
require.Equal(t, 0, status.CurrentIndex)
require.Equal(t, len(desiredOrder), status.Length)

playlist, err := j.GetPlaylist()
require.NoError(t, err)
require.Equal(t, desiredOrder, playlist)
}

func TestVolume(t *testing.T) {
t.Skip("bit flakey since mpv ipc doesn't block while internal state has settled")

t.Parallel()
j := newJukebox(t)

Expand Down

0 comments on commit 82c3c5b

Please sign in to comment.