diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6737e08 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +mpd: + mkdir -p /tmp/sanic/{music,playlists} + mpd --no-daemon ./mpd.conf diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..d3ce923 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,20 @@ +# mpd commands + +- [x] previous track +- [x] next track +- [x] stop +- [x] play/pause +- [x] set track progress +- [x] set volume +- [x] repeat queue +- [x] shuffle queue +- [ ] set fade +- [ ] add track to queue +- [ ] rm track from queue +- [ ] move track in queue +- [ ] clear queue +- [ ] list tracks in music db +- [ ] list playlists +- [ ] save playlist +- [ ] delete playlist + diff --git a/flake.lock b/flake.lock index 8edcf61..bc26a35 100644 --- a/flake.lock +++ b/flake.lock @@ -28,11 +28,11 @@ ] }, "locked": { - "lastModified": 1694616124, - "narHash": "sha256-c49BVhQKw3XDRgt+y+uPAbArtgUlMXCET6VxEBmzHXE=", + "lastModified": 1699950847, + "narHash": "sha256-xN/yVtqHb7kimHA/WvQFrEG5WS38t0K+A/W+j/WhQWM=", "owner": "tweag", "repo": "gomod2nix", - "rev": "f95720e89af6165c8c0aa77f180461fe786f3c21", + "rev": "05c993c9a5bd55a629cd45ed49951557b7e9c61a", "type": "github" }, "original": { @@ -43,11 +43,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1697915759, - "narHash": "sha256-WyMj5jGcecD+KC8gEs+wFth1J1wjisZf8kVZH13f1Zo=", + "lastModified": 1701237617, + "narHash": "sha256-Ryd8xpNDY9MJnBFDYhB37XSFIxCPVVVXAbInNPa95vs=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "51d906d2341c9e866e48c2efcaac0f2d70bfd43e", + "rev": "85306ef2470ba705c97ce72741d56e42d0264015", "type": "github" }, "original": { diff --git a/server.go b/server.go index 3b3e73e..80d1eec 100644 --- a/server.go +++ b/server.go @@ -1,13 +1,17 @@ package main import ( + "fmt" + "github.com/fhs/gompd/v2/mpd" "github.com/labstack/echo-contrib/echoprometheus" + "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "golang.org/x/net/websocket" + "log" "net/http" + "strconv" "strings" - - "github.com/labstack/echo/v4" + "time" ) func main() { @@ -25,6 +29,7 @@ func main() { e.GET("/metrics", echoprometheus.NewHandler()) e.GET("/", func(c echo.Context) (err error) { + // HTTP/2 Server Push pusher, ok := c.Response().Writer.(http.Pusher) if ok { if err = pusher.Push("/style.css", nil); err != nil { @@ -40,6 +45,17 @@ func main() { return c.File("index.html") }) + g := e.Group("/api") + g.GET("/previous_track", previousTrack) + g.GET("/next_track", nextTrack) + g.GET("/stop", stopPlayback) + g.GET("/play", resumePlayback) + g.GET("/pause", pausePlayback) + g.GET("/seek/:seconds", seek) + g.GET("/repeat", toggleRepeat) + g.GET("/random", toggleRandom) + g.GET("/volume/:level", setVolume) + e.GET("/ws", wsServe) e.Logger.Fatal(e.StartTLS(":1323", "cert.pem", "key.pem")) @@ -47,33 +63,217 @@ func main() { } func wsServe(c echo.Context) error { + fmt.Println("wsServe") websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() + fmt.Println("handler") for { // Read msg := "" err := websocket.Message.Receive(ws, &msg) if err != nil { c.Logger().Error(err) - } - // Forward MPD communication - if strings.HasPrefix(strings.ToUpper(msg), "MPD#") { - // TODO: forward request to mpd and response back to client - err := websocket.Message.Send(ws, "MPD command received, processing... processing...") - if err != nil { - c.Logger().Error(err) + break + } else { + if strings.HasPrefix(strings.ToUpper(msg), "MPD#") { + // Forward MPD communication + // TODO: forward request to mpd and response back to client + err := websocket.Message.Send(ws, "MPD command received, processing... processing...") + if err != nil { + c.Logger().Error(err) + } + + } else if strings.HasPrefix(strings.ToUpper(msg), "YT#") { + // Download video link as audio file + // TODO: implement yt-dlp integration + err := websocket.Message.Send(ws, "YT-DLP command received, processing... processing...") + if err != nil { + c.Logger().Error(err) + } } } - // Download video link as audio file - if strings.HasPrefix(strings.ToUpper(msg), "YT#") { - // TODO: implement yt-dlp integration - err := websocket.Message.Send(ws, "YT-DLP command received, processing... processing...") - if err != nil { - c.Logger().Error(err) - } - } - //fmt.Printf("%s\n", msg) + //fmt.Println(msg) } }).ServeHTTP(c.Response(), c.Request()) return nil } + +// API calls + +func previousTrack(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + err = conn.Previous() + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func nextTrack(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + err = conn.Next() + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func stopPlayback(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + err = conn.Stop() + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func resumePlayback(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + err = conn.Pause(false) + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func pausePlayback(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + err = conn.Pause(true) + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func seek(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + seconds, err := strconv.Atoi(c.Param("seconds")) + if err != nil { + log.Fatalln(err) + } + + if seconds < 0 { + return c.String(http.StatusBadRequest, "seconds must be positive integer") + } + + err = conn.SeekCur(time.Duration(seconds)*time.Second, false) + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func toggleRepeat(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + status, err := conn.Status() + if err != nil { + log.Fatalln(err) + } + if status["repeat"] == "1" { + err = conn.Repeat(false) + } else { + err = conn.Repeat(true) + } + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func toggleRandom(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + status, err := conn.Status() + if err != nil { + log.Fatalln(err) + } + if status["toggleRandom"] == "1" { + err = conn.Random(false) + } else { + err = conn.Random(true) + } + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +} + +func setVolume(c echo.Context) error { + // Connect to MPD server + conn, err := mpd.Dial("tcp", "localhost:6600") + if err != nil { + log.Fatalln(err) + } + defer conn.Close() + + level, err := strconv.Atoi(c.Param("level")) + if err != nil { + log.Fatalln(err) + } + + if level > 100 || level < 0 { + return c.String(http.StatusBadRequest, "Volume must be between 0 and 100") + } + + err = conn.SetVolume(level) + if err != nil { + log.Fatalln(err) + } + + return c.String(http.StatusNoContent, "") +}