Merge pull request #674 from E1int/feature/play-cli-commands
Add play/search CLI commands for albums, playlists and tracks
This commit is contained in:
+28
-1
@@ -193,7 +193,10 @@ func StartupApp(appName, displayAppName, appVersion, appVersionTag, latestReleas
|
|||||||
ipc.DestroyConn() // cleanup socket possibly orphaned by crashed process
|
ipc.DestroyConn() // cleanup socket possibly orphaned by crashed process
|
||||||
listener, err := ipc.Listen()
|
listener, err := ipc.Listen()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
a.ipcServer = ipc.NewServer(a.PlaybackManager, a.callOnReactivate,
|
a.ipcServer = ipc.NewServer(
|
||||||
|
a.PlaybackManager,
|
||||||
|
a.ServerManager,
|
||||||
|
a.callOnReactivate,
|
||||||
func() { _ = a.callOnExit() })
|
func() { _ = a.callOnExit() })
|
||||||
go a.ipcServer.Serve(listener)
|
go a.ipcServer.Serve(listener)
|
||||||
} else {
|
} else {
|
||||||
@@ -582,6 +585,30 @@ func (a *App) checkFlagsAndSendIPCMsg(cli *ipc.Client) error {
|
|||||||
return cli.SeekSeconds(SeekToCLIArg)
|
return cli.SeekSeconds(SeekToCLIArg)
|
||||||
case SeekByCLIArg != 0:
|
case SeekByCLIArg != 0:
|
||||||
return cli.SeekBySeconds(SeekByCLIArg)
|
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)
|
||||||
|
case SearchAlbumCLIArg != "":
|
||||||
|
data, err := cli.SearchAlbum(SearchAlbumCLIArg)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(data)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
case SearchPlaylistCLIArg != "":
|
||||||
|
data, err := cli.SearchPlaylist(SearchPlaylistCLIArg)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(data)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
case SearchTrackCLIArg != "":
|
||||||
|
data, err := cli.SearchTrack(SearchTrackCLIArg)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Println(data)
|
||||||
|
}
|
||||||
|
return err
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,25 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
VolumeCLIArg int = -1
|
VolumeCLIArg int = -1
|
||||||
SeekToCLIArg float64 = -1
|
SeekToCLIArg float64 = -1
|
||||||
SeekByCLIArg float64 = 0
|
SeekByCLIArg float64 = 0
|
||||||
VolumePctCLIArg float64 = 0
|
VolumePctCLIArg float64 = 0
|
||||||
|
PlayAlbumCLIArg string = ""
|
||||||
|
PlayPlaylistCLIArg string = ""
|
||||||
|
PlayTrackCLIArg string = ""
|
||||||
|
FirstTrackCLIArg int = 0
|
||||||
|
SearchAlbumCLIArg string = ""
|
||||||
|
SearchPlaylistCLIArg string = ""
|
||||||
|
SearchTrackCLIArg string = ""
|
||||||
|
|
||||||
FlagPlay = flag.Bool("play", false, "unpause or begin playback")
|
FlagPlay = flag.Bool("play", false, "unpause or begin playback")
|
||||||
FlagPause = flag.Bool("pause", false, "pause playback")
|
FlagPause = flag.Bool("pause", false, "pause playback")
|
||||||
@@ -21,8 +31,13 @@ var (
|
|||||||
FlagStopAfterCurrent = flag.Bool("stop-after-current", false, "stop playback after current track")
|
FlagStopAfterCurrent = flag.Bool("stop-after-current", false, "stop playback after current track")
|
||||||
FlagStartMinimized = flag.Bool("start-minimized", false, "start app minimized")
|
FlagStartMinimized = flag.Bool("start-minimized", false, "start app minimized")
|
||||||
FlagShow = flag.Bool("show", false, "show minimized app")
|
FlagShow = flag.Bool("show", false, "show minimized app")
|
||||||
|
FlagShuffle = flag.Bool("shuffle", false, "shuffle the tracklist (to be used with either -play-album-by-id or -play-playlist-by-id)")
|
||||||
FlagVersion = flag.Bool("version", false, "print app version and exit")
|
FlagVersion = flag.Bool("version", false, "print app version and exit")
|
||||||
FlagHelp = flag.Bool("help", false, "print command line options and exit")
|
FlagHelp = flag.Bool("help", false, "print command line options and exit")
|
||||||
|
|
||||||
|
FlagPlayAlbum *bool
|
||||||
|
FlagPlayPlaylist *bool
|
||||||
|
FlagPlayTrack *bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -50,6 +65,43 @@ func init() {
|
|||||||
VolumePctCLIArg = v
|
VolumePctCLIArg = v
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
|
flag.Func("play-album-by-id", "start playing the album with the given ID (can also be passed from standard input)", func(s string) error {
|
||||||
|
PlayAlbumCLIArg = s
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
flag.Func("play-playlist-by-id", "start playing the playlist with the given ID (can also be passed from standard input)", func(s string) error {
|
||||||
|
PlayPlaylistCLIArg = s
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
flag.Func("play-track-by-id", "start playing the track with the given ID (can also be passed from standard input)", func(s string) error {
|
||||||
|
PlayTrackCLIArg = s
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
FlagPlayAlbum = flag.Bool("play-album-by-id", false, "")
|
||||||
|
FlagPlayPlaylist = flag.Bool("play-playlist-by-id", false, "")
|
||||||
|
FlagPlayTrack = flag.Bool("play-track-by-id", false, "")
|
||||||
|
}
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
flag.Func("search-album", "search album by name, return results as JSON", func(s string) error {
|
||||||
|
SearchAlbumCLIArg = s
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
flag.Func("search-playlist", "search playlist by name, return results as JSON", func(s string) error {
|
||||||
|
SearchPlaylistCLIArg = s
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
flag.Func("search-track", "search track by name, return results as JSON", func(s string) error {
|
||||||
|
SearchTrackCLIArg = s
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func HaveCommandLineOptions() bool {
|
func HaveCommandLineOptions() bool {
|
||||||
|
|||||||
+40
-2
@@ -1,10 +1,20 @@
|
|||||||
package ipc
|
package ipc
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
PingPath = "/ping"
|
PingPath = "/ping"
|
||||||
PlayPath = "/transport/play"
|
PlayPath = "/transport/play"
|
||||||
|
PlayAlbumPath = "/transport/play-album" // ?id=<album ID>&t=<firstTrack>&s=<shuffle>
|
||||||
|
PlayPlaylistPath = "/transport/play-playlist" // ?id=<playlist ID>&t=<firstTrack>&s=<shuffle>
|
||||||
|
PlayTrackPath = "/transport/play-track" // ?id=<track ID>
|
||||||
|
SearchAlbumPath = "/transport/search-album" // ?s=<searchQuery>
|
||||||
|
SearchPlaylistPath = "/transport/search-playlist" // ?s=<searchQuery>
|
||||||
|
SearchTrackPath = "/transport/search-track" // ?s=<searchQuery>
|
||||||
PlayPausePath = "/transport/playpause"
|
PlayPausePath = "/transport/playpause"
|
||||||
PausePath = "/transport/pause"
|
PausePath = "/transport/pause"
|
||||||
StopPath = "/transport/stop"
|
StopPath = "/transport/stop"
|
||||||
@@ -20,7 +30,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
Error string `json:"error"`
|
Data json.RawMessage `json:"data"`
|
||||||
|
Error string `json:"error"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetVolumePath(vol int) string {
|
func SetVolumePath(vol int) string {
|
||||||
@@ -38,3 +49,30 @@ func SeekToSecondsPath(secs float64) string {
|
|||||||
func SeekBySecondsPath(secs float64) string {
|
func SeekBySecondsPath(secs float64) string {
|
||||||
return fmt.Sprintf("%s?s=%0.2f", SeekByPath, secs)
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildSearchAlbumPath(search string) string {
|
||||||
|
s := url.QueryEscape(search)
|
||||||
|
return fmt.Sprintf("%s?s=%s", SearchAlbumPath, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildSearchPlaylistPath(search string) string {
|
||||||
|
s := url.QueryEscape(search)
|
||||||
|
return fmt.Sprintf("%s?s=%s", SearchPlaylistPath, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildSearchTrackPath(search string) string {
|
||||||
|
s := url.QueryEscape(search)
|
||||||
|
return fmt.Sprintf("%s?s=%s", SearchTrackPath, s)
|
||||||
|
}
|
||||||
|
|||||||
+60
-20
@@ -30,75 +30,115 @@ func Connect() (*Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Ping() error {
|
func (c *Client) Ping() error {
|
||||||
if c.sendRequest(PingPath) != nil {
|
if _, err := c.sendRequest(PingPath); err != nil {
|
||||||
return ErrPingFail
|
return ErrPingFail
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Play() error {
|
func (c *Client) Play() error {
|
||||||
return c.sendRequest(PlayPath)
|
_, err := c.sendRequest(PlayPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Pause() error {
|
func (c *Client) Pause() error {
|
||||||
return c.sendRequest(PausePath)
|
_, err := c.sendRequest(PausePath)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PlayAlbum(id string, firstTrack int, shuffle bool) error {
|
||||||
|
_, err := c.sendRequest(BuildPlayAlbumPath(id, firstTrack, shuffle))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PlayPlaylist(id string, firstTrack int, shuffle bool) error {
|
||||||
|
_, err := c.sendRequest(BuildPlayPlaylistPath(id, firstTrack, shuffle))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SearchAlbum(search string) (string, error) {
|
||||||
|
return c.sendRequest(BuildSearchAlbumPath(search))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SearchPlaylist(search string) (string, error) {
|
||||||
|
return c.sendRequest(BuildSearchPlaylistPath(search))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) SearchTrack(search string) (string, error) {
|
||||||
|
return c.sendRequest(BuildSearchTrackPath(search))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) PlayTrack(id string) error {
|
||||||
|
_, err := c.sendRequest(BuildPlayTrackPath(id))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) PlayPause() error {
|
func (c *Client) PlayPause() error {
|
||||||
return c.sendRequest(PlayPausePath)
|
_, err := c.sendRequest(PlayPausePath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Stop() error {
|
func (c *Client) Stop() error {
|
||||||
return c.sendRequest(StopPath)
|
_, err := c.sendRequest(StopPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) StopAfterCurrent() error {
|
func (c *Client) StopAfterCurrent() error {
|
||||||
return c.sendRequest(StopAfterCurrentPath)
|
_, err := c.sendRequest(StopAfterCurrentPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SeekNext() error {
|
func (c *Client) SeekNext() error {
|
||||||
return c.sendRequest(NextPath)
|
_, err := c.sendRequest(NextPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SeekBackOrPrevious() error {
|
func (c *Client) SeekBackOrPrevious() error {
|
||||||
return c.sendRequest(PreviousPath)
|
_, err := c.sendRequest(PreviousPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SeekSeconds(secs float64) error {
|
func (c *Client) SeekSeconds(secs float64) error {
|
||||||
return c.sendRequest(SeekToSecondsPath(secs))
|
_, err := c.sendRequest(SeekToSecondsPath(secs))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SeekBySeconds(secs float64) error {
|
func (c *Client) SeekBySeconds(secs float64) error {
|
||||||
return c.sendRequest(SeekBySecondsPath(secs))
|
_, err := c.sendRequest(SeekBySecondsPath(secs))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SetVolume(vol int) error {
|
func (c *Client) SetVolume(vol int) error {
|
||||||
return c.sendRequest(SetVolumePath(vol))
|
_, err := c.sendRequest(SetVolumePath(vol))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) AdjustVolumePct(pct float64) error {
|
func (c *Client) AdjustVolumePct(pct float64) error {
|
||||||
return c.sendRequest(AdjustVolumePctPath(pct))
|
_, err := c.sendRequest(AdjustVolumePctPath(pct))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Show() error {
|
func (c *Client) Show() error {
|
||||||
return c.sendRequest(ShowPath)
|
_, err := c.sendRequest(ShowPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) Quit() error {
|
func (c *Client) Quit() error {
|
||||||
return c.sendRequest(QuitPath)
|
_, err := c.sendRequest(QuitPath)
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) sendRequest(path string) error {
|
func (c *Client) sendRequest(path string) (string, error) {
|
||||||
resp, err := c.httpC.Get("http://supersonic/" + path)
|
resp, err := c.httpC.Get("http://supersonic/" + path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
var r Response
|
||||||
|
json.NewDecoder(resp.Body).Decode(&r)
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
var r Response
|
return "", errors.New(r.Error)
|
||||||
json.NewDecoder(resp.Body).Decode(&r)
|
|
||||||
return errors.New(r.Error)
|
|
||||||
}
|
}
|
||||||
return nil
|
return string(r.Data), nil
|
||||||
}
|
}
|
||||||
|
|||||||
+129
-2
@@ -3,11 +3,17 @@ package ipc
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrNoServerConnection = errors.New("not connected to a server")
|
||||||
|
|
||||||
type PlaybackHandler interface {
|
type PlaybackHandler interface {
|
||||||
PlayPause()
|
PlayPause()
|
||||||
Stop()
|
Stop()
|
||||||
@@ -20,6 +26,9 @@ type PlaybackHandler interface {
|
|||||||
SeekBySeconds(float64)
|
SeekBySeconds(float64)
|
||||||
Volume() int
|
Volume() int
|
||||||
SetVolume(int)
|
SetVolume(int)
|
||||||
|
PlayAlbum(string, int, bool) error
|
||||||
|
PlayPlaylist(string, int, bool) error
|
||||||
|
PlayTrack(string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type IPCServer interface {
|
type IPCServer interface {
|
||||||
@@ -27,15 +36,20 @@ type IPCServer interface {
|
|||||||
Shutdown(context.Context) error
|
Shutdown(context.Context) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ServerManager interface {
|
||||||
|
GetServer() mediaprovider.MediaProvider
|
||||||
|
}
|
||||||
|
|
||||||
type serverImpl struct {
|
type serverImpl struct {
|
||||||
server *http.Server
|
server *http.Server
|
||||||
pbHandler PlaybackHandler
|
pbHandler PlaybackHandler
|
||||||
|
sm ServerManager
|
||||||
showFn func()
|
showFn func()
|
||||||
quitFn func()
|
quitFn func()
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewServer(pbHandler PlaybackHandler, showFn, quitFn func()) IPCServer {
|
func NewServer(pbHandler PlaybackHandler, sm ServerManager, showFn, quitFn func()) IPCServer {
|
||||||
s := &serverImpl{pbHandler: pbHandler, showFn: showFn, quitFn: quitFn}
|
s := &serverImpl{pbHandler: pbHandler, sm: sm, showFn: showFn, quitFn: quitFn}
|
||||||
s.server = &http.Server{
|
s.server = &http.Server{
|
||||||
Handler: s.createHandler(),
|
Handler: s.createHandler(),
|
||||||
}
|
}
|
||||||
@@ -90,6 +104,74 @@ func (s *serverImpl) createHandler() http.Handler {
|
|||||||
vol = vol + int(float64(vol)*(pct/100))
|
vol = vol + int(float64(vol)*(pct/100))
|
||||||
s.pbHandler.SetVolume(vol) // will clamp to range for us
|
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)
|
||||||
|
})
|
||||||
|
m.HandleFunc(SearchAlbumPath, s.makeSearchEndpointHandler(func(search string) (any, error) {
|
||||||
|
mp := s.sm.GetServer()
|
||||||
|
if mp == nil {
|
||||||
|
return nil, ErrNoServerConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
filter := mediaprovider.NewAlbumFilter(mediaprovider.AlbumFilterOptions{})
|
||||||
|
i := mp.SearchAlbums(search, filter)
|
||||||
|
|
||||||
|
album := i.Next()
|
||||||
|
albums := make([]mediaprovider.Album, 0)
|
||||||
|
for album != nil {
|
||||||
|
albums = append(albums, *album)
|
||||||
|
album = i.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return albums, nil
|
||||||
|
}))
|
||||||
|
m.HandleFunc(SearchPlaylistPath, s.makeSearchEndpointHandler(func(search string) (any, error) {
|
||||||
|
mp := s.sm.GetServer()
|
||||||
|
if mp == nil {
|
||||||
|
return nil, ErrNoServerConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
all, err := mp.GetPlaylists()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
search = strings.ReplaceAll(search, " ", "")
|
||||||
|
search = strings.ToLower(search)
|
||||||
|
|
||||||
|
filtered := make([]mediaprovider.Playlist, 0)
|
||||||
|
for i := 0; i < len(all); i++ {
|
||||||
|
playlist := all[i]
|
||||||
|
name := strings.ReplaceAll(playlist.Name, " ", "")
|
||||||
|
name = strings.ToLower(name)
|
||||||
|
if strings.Contains(name, search) {
|
||||||
|
filtered = append(filtered, *playlist)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filtered, nil
|
||||||
|
}))
|
||||||
|
m.HandleFunc(SearchTrackPath, s.makeSearchEndpointHandler(func(search string) (any, error) {
|
||||||
|
mp := s.sm.GetServer()
|
||||||
|
if mp == nil {
|
||||||
|
return nil, ErrNoServerConnection
|
||||||
|
}
|
||||||
|
|
||||||
|
i := mp.IterateTracks(search)
|
||||||
|
|
||||||
|
track := i.Next()
|
||||||
|
tracks := make([]mediaprovider.Track, 0)
|
||||||
|
for track != nil {
|
||||||
|
tracks = append(tracks, *track)
|
||||||
|
track = i.Next()
|
||||||
|
}
|
||||||
|
|
||||||
|
return tracks, nil
|
||||||
|
}))
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +194,41 @@ 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) makeSearchEndpointHandler(f func(string) (any, error)) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
search := r.URL.Query().Get("s")
|
||||||
|
data, err := f(search)
|
||||||
|
if err != nil {
|
||||||
|
s.writeErr(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
s.writeErr(w, err)
|
||||||
|
}
|
||||||
|
s.writeData(w, bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serverImpl) writeOK(w http.ResponseWriter) (int, error) {
|
func (s *serverImpl) writeOK(w http.ResponseWriter) (int, error) {
|
||||||
var r Response
|
var r Response
|
||||||
b, err := json.Marshal(&r)
|
b, err := json.Marshal(&r)
|
||||||
@@ -121,6 +238,16 @@ func (s *serverImpl) writeOK(w http.ResponseWriter) (int, error) {
|
|||||||
return w.Write(b)
|
return w.Write(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *serverImpl) writeData(w http.ResponseWriter, data []byte) (int, error) {
|
||||||
|
r := Response{Data: data}
|
||||||
|
b, err := json.Marshal(&r)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
return w.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *serverImpl) writeErr(w http.ResponseWriter, err error) (int, error) {
|
func (s *serverImpl) writeErr(w http.ResponseWriter, err error) (int, error) {
|
||||||
r := Response{Error: err.Error()}
|
r := Response{Error: err.Error()}
|
||||||
b, err := json.Marshal(&r)
|
b, err := json.Marshal(&r)
|
||||||
|
|||||||
@@ -262,3 +262,7 @@ func (s *ServerManager) checkSetInsecureSkipVerify(cli *http.Client) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ServerManager) GetServer() mediaprovider.MediaProvider {
|
||||||
|
return a.Server
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ require (
|
|||||||
github.com/zalando/go-keyring v0.2.6
|
github.com/zalando/go-keyring v0.2.6
|
||||||
golang.org/x/net v0.35.0
|
golang.org/x/net v0.35.0
|
||||||
golang.org/x/sys v0.30.0
|
golang.org/x/sys v0.30.0
|
||||||
|
golang.org/x/term v0.29.0
|
||||||
golang.org/x/text v0.22.0
|
golang.org/x/text v0.22.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -170,6 +170,8 @@ golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/dweymouth/supersonic/res/wintaskbarthumbs"
|
"github.com/dweymouth/supersonic/res/wintaskbarthumbs"
|
||||||
"github.com/dweymouth/supersonic/ui"
|
"github.com/dweymouth/supersonic/ui"
|
||||||
"github.com/dweymouth/supersonic/ui/util"
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
|
"golang.org/x/term"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/app"
|
"fyne.io/fyne/v2/app"
|
||||||
@@ -35,6 +36,15 @@ func main() {
|
|||||||
flag.Usage()
|
flag.Usage()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !term.IsTerminal(int(os.Stdin.Fd())) {
|
||||||
|
if *backend.FlagPlayAlbum {
|
||||||
|
fmt.Scanln(&backend.PlayAlbumCLIArg)
|
||||||
|
} else if *backend.FlagPlayPlaylist {
|
||||||
|
fmt.Scanln(&backend.PlayPlaylistCLIArg)
|
||||||
|
} else if *backend.FlagPlayTrack {
|
||||||
|
fmt.Scanln(&backend.PlayTrackCLIArg)
|
||||||
|
}
|
||||||
|
}
|
||||||
// rest of flag actions are handled in backend.StartupApp
|
// rest of flag actions are handled in backend.StartupApp
|
||||||
|
|
||||||
myApp, err := backend.StartupApp(res.AppName, res.DisplayName, res.AppVersion, res.AppVersionTag, res.LatestReleaseURL)
|
myApp, err := backend.StartupApp(res.AppName, res.DisplayName, res.AppVersion, res.AppVersionTag, res.LatestReleaseURL)
|
||||||
|
|||||||
Reference in New Issue
Block a user