217 lines
5.1 KiB
Go
217 lines
5.1 KiB
Go
package jukebox
|
|
|
|
import (
|
|
"context"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func genFile(t *testing.T, dir, name string) string {
|
|
t.Helper()
|
|
if _, err := exec.LookPath("ffmpeg"); err != nil {
|
|
t.Skip("ffmpeg not installed")
|
|
}
|
|
out := filepath.Join(dir, name)
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
cmd := exec.CommandContext(ctx, "ffmpeg", "-f", "lavfi", "-i", "sine=frequency=440:duration=3", "-codec:a", "libmp3lame", out)
|
|
if combined, err := cmd.CombinedOutput(); err != nil {
|
|
t.Fatalf("ffmpeg: %v\n%s", err, combined)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func requireMPV(t *testing.T) {
|
|
t.Helper()
|
|
if _, err := exec.LookPath("mpv"); err != nil {
|
|
t.Skip("mpv not installed")
|
|
}
|
|
}
|
|
|
|
func TestNewUnavailableWithoutMPV(t *testing.T) {
|
|
j := New("/nonexistent/mpv/binary")
|
|
if j.Available {
|
|
t.Fatal("Available = true for a nonexistent mpv binary")
|
|
}
|
|
if _, err := j.Status(); err != ErrUnavailable {
|
|
t.Fatalf("Status() error = %v, want ErrUnavailable", err)
|
|
}
|
|
}
|
|
|
|
func TestJukeboxSetAndStatus(t *testing.T) {
|
|
requireMPV(t)
|
|
dir := t.TempDir()
|
|
song1 := genFile(t, dir, "song1.mp3")
|
|
song2 := genFile(t, dir, "song2.mp3")
|
|
|
|
j := New("")
|
|
defer j.Close()
|
|
if !j.Available {
|
|
t.Fatal("expected jukebox to be available with mpv installed")
|
|
}
|
|
|
|
paths := map[string]string{"s1": song1, "s2": song2}
|
|
resolve := func(id string) (string, error) { return paths[id], nil }
|
|
|
|
state, err := j.Set([]string{"s1", "s2"}, resolve)
|
|
if err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
if len(state.SongIDs) != 2 {
|
|
t.Fatalf("SongIDs = %v, want 2", state.SongIDs)
|
|
}
|
|
|
|
// Give mpv a beat to actually start playback.
|
|
time.Sleep(300 * time.Millisecond)
|
|
state, err = j.Status()
|
|
if err != nil {
|
|
t.Fatalf("Status: %v", err)
|
|
}
|
|
if !state.Playing {
|
|
t.Error("expected jukebox to be playing after Set")
|
|
}
|
|
}
|
|
|
|
func TestJukeboxStartStop(t *testing.T) {
|
|
requireMPV(t)
|
|
dir := t.TempDir()
|
|
song1 := genFile(t, dir, "song1.mp3")
|
|
|
|
j := New("")
|
|
defer j.Close()
|
|
resolve := func(id string) (string, error) { return song1, nil }
|
|
if _, err := j.Set([]string{"s1"}, resolve); err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
state, err := j.Stop()
|
|
if err != nil {
|
|
t.Fatalf("Stop: %v", err)
|
|
}
|
|
if state.Playing {
|
|
t.Error("expected paused after Stop")
|
|
}
|
|
|
|
state, err = j.Start()
|
|
if err != nil {
|
|
t.Fatalf("Start: %v", err)
|
|
}
|
|
time.Sleep(200 * time.Millisecond)
|
|
state, err = j.Status()
|
|
if err != nil {
|
|
t.Fatalf("Status: %v", err)
|
|
}
|
|
if !state.Playing {
|
|
t.Error("expected playing after Start")
|
|
}
|
|
}
|
|
|
|
func TestJukeboxAddClearRemove(t *testing.T) {
|
|
requireMPV(t)
|
|
dir := t.TempDir()
|
|
song1 := genFile(t, dir, "song1.mp3")
|
|
song2 := genFile(t, dir, "song2.mp3")
|
|
|
|
j := New("")
|
|
defer j.Close()
|
|
paths := map[string]string{"s1": song1, "s2": song2}
|
|
resolve := func(id string) (string, error) { return paths[id], nil }
|
|
|
|
if _, err := j.Set([]string{"s1"}, resolve); err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
state, err := j.Add([]string{"s2"}, resolve)
|
|
if err != nil {
|
|
t.Fatalf("Add: %v", err)
|
|
}
|
|
if len(state.SongIDs) != 2 {
|
|
t.Fatalf("SongIDs after Add = %v, want 2", state.SongIDs)
|
|
}
|
|
|
|
state, err = j.Remove(0)
|
|
if err != nil {
|
|
t.Fatalf("Remove: %v", err)
|
|
}
|
|
if len(state.SongIDs) != 1 || state.SongIDs[0] != "s2" {
|
|
t.Fatalf("SongIDs after Remove(0) = %v, want [s2]", state.SongIDs)
|
|
}
|
|
|
|
state, err = j.Clear()
|
|
if err != nil {
|
|
t.Fatalf("Clear: %v", err)
|
|
}
|
|
if len(state.SongIDs) != 0 {
|
|
t.Fatalf("SongIDs after Clear = %v, want empty", state.SongIDs)
|
|
}
|
|
}
|
|
|
|
func TestJukeboxSetGain(t *testing.T) {
|
|
requireMPV(t)
|
|
dir := t.TempDir()
|
|
song1 := genFile(t, dir, "song1.mp3")
|
|
|
|
j := New("")
|
|
defer j.Close()
|
|
resolve := func(id string) (string, error) { return song1, nil }
|
|
if _, err := j.Set([]string{"s1"}, resolve); err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
|
|
state, err := j.SetGain(0.5)
|
|
if err != nil {
|
|
t.Fatalf("SetGain: %v", err)
|
|
}
|
|
if state.Gain != 0.5 {
|
|
t.Fatalf("Gain = %v, want 0.5", state.Gain)
|
|
}
|
|
}
|
|
|
|
func TestJukeboxShuffle(t *testing.T) {
|
|
requireMPV(t)
|
|
dir := t.TempDir()
|
|
song1 := genFile(t, dir, "song1.mp3")
|
|
song2 := genFile(t, dir, "song2.mp3")
|
|
|
|
j := New("")
|
|
defer j.Close()
|
|
paths := map[string]string{"s1": song1, "s2": song2}
|
|
resolve := func(id string) (string, error) { return paths[id], nil }
|
|
if _, err := j.Set([]string{"s1", "s2"}, resolve); err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
|
|
state, err := j.Shuffle(resolve)
|
|
if err != nil {
|
|
t.Fatalf("Shuffle: %v", err)
|
|
}
|
|
if len(state.SongIDs) != 2 {
|
|
t.Fatalf("SongIDs after Shuffle = %v, want 2 entries preserved", state.SongIDs)
|
|
}
|
|
}
|
|
|
|
func TestJukeboxSkipTo(t *testing.T) {
|
|
requireMPV(t)
|
|
dir := t.TempDir()
|
|
song1 := genFile(t, dir, "song1.mp3")
|
|
song2 := genFile(t, dir, "song2.mp3")
|
|
|
|
j := New("")
|
|
defer j.Close()
|
|
paths := map[string]string{"s1": song1, "s2": song2}
|
|
resolve := func(id string) (string, error) { return paths[id], nil }
|
|
if _, err := j.Set([]string{"s1", "s2"}, resolve); err != nil {
|
|
t.Fatalf("Set: %v", err)
|
|
}
|
|
|
|
state, err := j.SkipTo(1, 0)
|
|
if err != nil {
|
|
t.Fatalf("SkipTo: %v", err)
|
|
}
|
|
if state.CurrentIndex != 1 {
|
|
t.Fatalf("CurrentIndex = %d, want 1", state.CurrentIndex)
|
|
}
|
|
}
|