CLI playlist support

This commit is contained in:
Dmitry Sergeev
2026-09-01 22:16:36 +03:00
parent 362ae15867
commit cb5321cd8b
3 changed files with 110 additions and 0 deletions
+44
View File
@@ -53,6 +53,7 @@ type appArgs struct {
ListGPU bool ListGPU bool
SyncRequested bool SyncRequested bool
MaxAttempts int MaxAttempts int
PlaylistPath string
} }
func printCliHelp(fs *pflag.FlagSet) { func printCliHelp(fs *pflag.FlagSet) {
@@ -129,6 +130,12 @@ func main() {
0, 0,
"Maximum connection attempts per playback lifecycle; 0 retries indefinitely", "Maximum connection attempts per playback lifecycle; 0 retries indefinitely",
) )
flagSet.StringVar(
&args.PlaylistPath,
"playlist",
"",
"Load playlist from a JSON file",
)
flagSet.BoolVarP(&args.IsFullscreen, "fullscreen", "f", false, "Run app in fullscreen mode") flagSet.BoolVarP(&args.IsFullscreen, "fullscreen", "f", false, "Run app in fullscreen mode")
flagSet.Uint32VarP(&args.GpuId, "gpu-id", "g", 0, "GPU id [TODO]") flagSet.Uint32VarP(&args.GpuId, "gpu-id", "g", 0, "GPU id [TODO]")
flagSet.Uint32VarP(&args.PlaybackId, "playback-id", "p", 0, "Playback audio device id") flagSet.Uint32VarP(&args.PlaybackId, "playback-id", "p", 0, "Playback audio device id")
@@ -159,6 +166,16 @@ func main() {
fmt.Fprintln(os.Stderr, "invalid retry configuration:", err) fmt.Fprintln(os.Stderr, "invalid retry configuration:", err)
os.Exit(2) os.Exit(2)
} }
configuredPlaylist := playback.Playlist{}
hasPlaylist := args.PlaylistPath != ""
if hasPlaylist {
playlist, err := loadPlaylistFile(args.PlaylistPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(2)
}
configuredPlaylist = playlist
}
if args.VideoDomain == "" { if args.VideoDomain == "" {
args.VideoDomain = args.Domain args.VideoDomain = args.Domain
} }
@@ -415,6 +432,28 @@ func main() {
) )
}() }()
var playlistRuntime *playerPlaylist
var playlistDone chan error
if hasPlaylist {
playlistRuntime, err = newPlayerPlaylist(
configuredPlaylist,
retryPolicy,
player,
)
if err != nil {
panic(err)
}
playlistDone = make(chan error, 1)
go func() {
playlistDone <- playlistRuntime.Run(ctx)
}()
if shouldAutoStartPlaylist(args, configuredPlaylist) &&
!playlistRuntime.Select(0) {
log.Print("playlist command queue is full")
}
}
running := true running := true
resized := false resized := false
fullscreen := args.IsFullscreen fullscreen := args.IsFullscreen
@@ -774,6 +813,11 @@ func main() {
if err := <-playbackDone; err != nil && !errors.Is(err, context.Canceled) { if err := <-playbackDone; err != nil && !errors.Is(err, context.Canceled) {
log.Printf("playback controller: %v", err) log.Printf("playback controller: %v", err)
} }
if playlistDone != nil {
if err := <-playlistDone; err != nil && !errors.Is(err, context.Canceled) {
log.Printf("playlist runtime: %v", err)
}
}
if err := player.Close(); err != nil { if err := player.Close(); err != nil {
log.Printf("close playback: %v", err) log.Printf("close playback: %v", err)
} }
+10
View File
@@ -125,3 +125,13 @@ func playlistRuntimeError(err error) error {
} }
return err return err
} }
func shouldAutoStartPlaylist(
args appArgs,
playlist playback.Playlist,
) bool {
return args.PlaylistPath != "" &&
args.VideoFlowId == "" &&
args.AudioFlowId == "" &&
len(playlist.Entries) > 0
}
+56
View File
@@ -242,6 +242,62 @@ func TestPlayerPlaylistTimedEntryAdvances(t *testing.T) {
} }
} }
func TestShouldAutoStartPlaylist(t *testing.T) {
playlist := playback.Playlist{Entries: []playback.PlaylistEntry{
{Video: playback.PlaylistFeed{Domain: "domain", UUID: "video"}},
}}
tests := []struct {
name string
args appArgs
playlist playback.Playlist
want bool
}{
{
name: "playlist only",
args: appArgs{PlaylistPath: "playlist.json"},
playlist: playlist,
want: true,
},
{
name: "direct video",
args: appArgs{PlaylistPath: "playlist.json", VideoFlowId: "video"},
playlist: playlist,
},
{
name: "direct audio",
args: appArgs{PlaylistPath: "playlist.json", AudioFlowId: "audio"},
playlist: playlist,
},
{
name: "both direct feeds",
args: appArgs{
PlaylistPath: "playlist.json",
VideoFlowId: "video",
AudioFlowId: "audio",
},
playlist: playlist,
},
{
name: "empty playlist",
args: appArgs{PlaylistPath: "playlist.json"},
playlist: playback.Playlist{},
},
{
name: "no playlist flag",
args: appArgs{},
playlist: playlist,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := shouldAutoStartPlaylist(test.args, test.playlist); got != test.want {
t.Fatalf("shouldAutoStartPlaylist() = %v, want %v", got, test.want)
}
})
}
}
func newPlaylistTestPlayer(t *testing.T) *playerPlayback { func newPlaylistTestPlayer(t *testing.T) *playerPlayback {
t.Helper() t.Helper()
return &playerPlayback{ return &playerPlayback{