diff --git a/backend/app.go b/backend/app.go index 1eae20e..53d7f18 100644 --- a/backend/app.go +++ b/backend/app.go @@ -558,6 +558,12 @@ func (a *App) checkFlagsAndSendIPCMsg(cli *ipc.Client) error { return cli.SeekSeconds(SeekToCLIArg) case SeekByCLIArg != 0: return cli.SeekBySeconds(SeekByCLIArg) + case PlayAlbumCLIArg != "": + return cli.PlayAlbum(PlayAlbumCLIArg, FirstTrackCLIArg, *FlagShuffle) + case PlayPlaylistCLIArg != "": + return cli.PlayPlaylist(PlayPlaylistCLIArg, FirstTrackCLIArg, *FlagShuffle) + case PlayTrackCLIArg != "": + return cli.PlayTrack(PlayTrackCLIArg) default: return nil } diff --git a/backend/cmdlineoptions.go b/backend/cmdlineoptions.go index c7252ed..883431d 100644 --- a/backend/cmdlineoptions.go +++ b/backend/cmdlineoptions.go @@ -7,10 +7,14 @@ import ( ) var ( - VolumeCLIArg int = -1 - SeekToCLIArg float64 = -1 - SeekByCLIArg float64 = 0 - VolumePctCLIArg float64 = 0 + VolumeCLIArg int = -1 + SeekToCLIArg float64 = -1 + SeekByCLIArg float64 = 0 + VolumePctCLIArg float64 = 0 + PlayAlbumCLIArg string = "" + PlayPlaylistCLIArg string = "" + PlayTrackCLIArg string = "" + FirstTrackCLIArg int = 0 FlagPlay = flag.Bool("play", false, "unpause or begin playback") FlagPause = flag.Bool("pause", false, "pause playback") @@ -19,6 +23,7 @@ var ( FlagNext = flag.Bool("next", false, "seek to next track") FlagStartMinimized = flag.Bool("start-minimized", false, "start app minimized") FlagShow = flag.Bool("show", false, "show minimized app") + FlagShuffle = flag.Bool("shuffle", false, "shuffle the tracklist (to be used with either -play-album or -play-playlist)") FlagVersion = flag.Bool("version", false, "print app version and exit") FlagHelp = flag.Bool("help", false, "print command line options and exit") ) @@ -48,6 +53,24 @@ func init() { VolumePctCLIArg = v return err }) + + flag.Func("play-album", "start playing the given album (ID)", func(s string) error { + PlayAlbumCLIArg = s + return nil + }) + flag.Func("play-playlist", "start playing the given playlist (ID)", func(s string) error { + PlayPlaylistCLIArg = s + return nil + }) + flag.Func("play-track", "start playing the given track (ID)", func(s string) error { + PlayTrackCLIArg = s + return nil + }) + flag.Func("first-track", "start playing from given track (positive integer, to be used with either -play-album or -play-playlist)", func(s string) error { + v, err := strconv.Atoi(s) + FirstTrackCLIArg = v + return err + }) } func HaveCommandLineOptions() bool { diff --git a/backend/ipc/api.go b/backend/ipc/api.go index f53a4a4..266a815 100644 --- a/backend/ipc/api.go +++ b/backend/ipc/api.go @@ -5,6 +5,9 @@ import "fmt" const ( PingPath = "/ping" PlayPath = "/transport/play" + PlayAlbumPath = "/transport/play-album" // ?id=&t=&s= + PlayPlaylistPath = "/transport/play-playlist" // ?id=&t=&s= + PlayTrackPath = "/transport/play-track" // ?id= PlayPausePath = "/transport/playpause" PausePath = "/transport/pause" StopPath = "/transport/stop" @@ -37,3 +40,15 @@ func SeekToSecondsPath(secs float64) string { func SeekBySecondsPath(secs float64) string { return fmt.Sprintf("%s?s=%0.2f", SeekByPath, secs) } + +func BuildPlayAlbumPath(id string, firstTrack int, shuffle bool) string { + return fmt.Sprintf("%s?id=%s&t=%d&s=%t", PlayAlbumPath, id, firstTrack, shuffle) +} + +func BuildPlayPlaylistPath(id string, firstTrack int, shuffle bool) string { + return fmt.Sprintf("%s?id=%s&t=%d&s=%t", PlayPlaylistPath, id, firstTrack, shuffle) +} + +func BuildPlayTrackPath(id string) string { + return fmt.Sprintf("%s?id=%s", PlayTrackPath, id) +} diff --git a/backend/ipc/client.go b/backend/ipc/client.go index fbc6737..4c0a22e 100644 --- a/backend/ipc/client.go +++ b/backend/ipc/client.go @@ -44,6 +44,18 @@ func (c *Client) Pause() error { return c.sendRequest(PausePath) } +func (c *Client) PlayAlbum(id string, firstTrack int, shuffle bool) error { + return c.sendRequest(BuildPlayAlbumPath(id, firstTrack, shuffle)) +} + +func (c *Client) PlayPlaylist(id string, firstTrack int, shuffle bool) error { + return c.sendRequest(BuildPlayPlaylistPath(id, firstTrack, shuffle)) +} + +func (c *Client) PlayTrack(id string) error { + return c.sendRequest(BuildPlayTrackPath(id)) +} + func (c *Client) PlayPause() error { return c.sendRequest(PlayPausePath) } diff --git a/backend/ipc/server.go b/backend/ipc/server.go index 8de46ad..5d688ae 100644 --- a/backend/ipc/server.go +++ b/backend/ipc/server.go @@ -19,6 +19,9 @@ type PlaybackHandler interface { SeekBySeconds(float64) Volume() int SetVolume(int) + PlayAlbum(string, int, bool) error + PlayPlaylist(string, int, bool) error + PlayTrack(string) error } type IPCServer interface { @@ -86,6 +89,13 @@ func (s *serverImpl) createHandler() http.Handler { vol = vol + int(float64(vol)*(pct/100)) s.pbHandler.SetVolume(vol) // will clamp to range for us })) + m.HandleFunc(PlayAlbumPath, s.makeTracklistEndpointHandler(s.pbHandler.PlayAlbum)) + m.HandleFunc(PlayPlaylistPath, s.makeTracklistEndpointHandler(s.pbHandler.PlayPlaylist)) + m.HandleFunc(PlayTrackPath, func(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + s.pbHandler.PlayTrack(id) + s.writeOK(w) + }) return m } @@ -108,6 +118,25 @@ func (s *serverImpl) makeFloatEndpointHandler(queryParam string, f func(float64) } } +func (s *serverImpl) makeTracklistEndpointHandler(f func(string, int, bool) error) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + query := r.URL.Query() + id := query.Get("id") + firstTrack, err := strconv.Atoi(query.Get("t")) + if err != nil { + s.writeErr(w, err) + return + } + shuffle, err := strconv.ParseBool(query.Get("s")) + if err != nil { + s.writeErr(w, err) + return + } + f(id, firstTrack, shuffle) + s.writeOK(w) + } +} + func (s *serverImpl) writeOK(w http.ResponseWriter) (int, error) { var r Response b, err := json.Marshal(&r)