From fef74b340981b43c26ef49c2453c2348670634fe Mon Sep 17 00:00:00 2001 From: Ilya Glebov Date: Wed, 31 Dec 2025 00:14:08 +0100 Subject: [PATCH 1/6] Add project configuration and dependencies --- .gitattributes | 3 ++ .gitignore | 21 ++++++++++++ LICENSE | 21 ++++++++++++ Makefile | 21 ++++++++++++ go.mod | 28 ++++++++++++++++ go.sum | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 184 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..73a1135 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +* text=auto + +go.sum -diff diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29fe8e1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +### Build ### +/somafm +/somafm-* +/out/ +/dist/ + +### Go ### +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work +go.work.sum +vendor/ + +### Secrets ### +.env +.idea/ diff --git a/LICENSE b/LICENSE index e69de29..c87ce14 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Ilya Glebov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..16b5f67 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +BINARY_NAME=somafm +VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null | sed 's/^v//' || echo "dev") +LDFLAGS=-ldflags "-X github.com/glebovdev/somafm-cli/internal/config.AppVersion=$(VERSION)" + +build: + go build $(LDFLAGS) -o $(BINARY_NAME) cmd/somafm/main.go + +# Linux cross-compilation requires CGO for ALSA audio. Use GitHub Actions for Linux builds. +build-all: + GOOS=darwin GOARCH=amd64 go build $(LDFLAGS) -o $(BINARY_NAME)-darwin-amd64 cmd/somafm/main.go + GOOS=darwin GOARCH=arm64 go build $(LDFLAGS) -o $(BINARY_NAME)-darwin-arm64 cmd/somafm/main.go + GOOS=windows GOARCH=amd64 go build $(LDFLAGS) -o $(BINARY_NAME)-windows-amd64.exe cmd/somafm/main.go + +test: + go test ./... + +clean: + go clean + rm -f $(BINARY_NAME) $(BINARY_NAME)-* + +.PHONY: build build-all test clean \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2627622 --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module github.com/glebovdev/somafm-cli + +go 1.24.0 + +require ( + github.com/gdamore/tcell/v2 v2.13.5 + github.com/go-resty/resty/v2 v2.17.1 + github.com/gopxl/beep/v2 v2.1.1 + github.com/rivo/tview v0.42.0 + github.com/rs/zerolog v1.34.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/ebitengine/oto/v3 v3.4.0 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/gdamore/encoding v1.0.1 // indirect + github.com/hajimehoshi/go-mp3 v0.3.4 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..79c0e05 --- /dev/null +++ b/go.sum @@ -0,0 +1,90 @@ +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/ebitengine/oto/v3 v3.4.0 h1:br0PgASsEWaoWn38b2Goe7m1GKFYfNgnsjSd5Gg+/bQ= +github.com/ebitengine/oto/v3 v3.4.0/go.mod h1:IOleLVD0m+CMak3mRVwsYY8vTctQgOM0iiL6S7Ar7eI= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw= +github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo= +github.com/gdamore/tcell/v2 v2.13.5 h1:YvWYCSr6gr2Ovs84dXbZLjDuOfQchhj8buOEqY52rpA= +github.com/gdamore/tcell/v2 v2.13.5/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo= +github.com/go-resty/resty/v2 v2.17.1 h1:x3aMpHK1YM9e4va/TMDRlusDDoZiQ+ViDu/WpA6xTM4= +github.com/go-resty/resty/v2 v2.17.1/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gopxl/beep/v2 v2.1.1 h1:6FYIYMm2qPAdWkjX+7xwKrViS1x0Po5kDMdRkq8NVbU= +github.com/gopxl/beep/v2 v2.1.1/go.mod h1:ZAm9TGQ9lvpoiFLd4zf5B1IuyxZhgRACMId1XJbaW0E= +github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= +github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= +github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/tview v0.42.0 h1:b/ftp+RxtDsHSaynXTbJb+/n/BxDEi+W3UfF5jILK6c= +github.com/rivo/tview v0.42.0/go.mod h1:cSfIYfhpSGCjp3r/ECJb+GKS7cGJnqV8vfjQPwoXyfY= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 1a3f608f9e226edb8cf888ecddaf10432df97e80 Mon Sep 17 00:00:00 2001 From: Ilya Glebov Date: Wed, 31 Dec 2025 00:14:21 +0100 Subject: [PATCH 2/6] Add SomaFM CLI application Terminal-based music player with TUI, audio streaming, and persistent configuration. --- cmd/somafm/main.go | 138 +++ internal/api/somafm.go | 103 +++ internal/api/somafm_test.go | 290 ++++++ internal/cache/cache.go | 163 ++++ internal/cache/cache_test.go | 323 +++++++ internal/config/config.go | 226 +++++ internal/config/config_test.go | 525 +++++++++++ internal/player/player.go | 1062 ++++++++++++++++++++++ internal/player/player_test.go | 503 ++++++++++ internal/service/station_service.go | 234 +++++ internal/service/station_service_test.go | 459 ++++++++++ internal/station/station.go | 67 ++ internal/station/station_test.go | 186 ++++ internal/ui/footer.go | 318 +++++++ internal/ui/modals.go | 412 +++++++++ internal/ui/stations.go | 295 ++++++ internal/ui/ui.go | 722 +++++++++++++++ internal/ui/ui_test.go | 338 +++++++ internal/ui/volume.go | 149 +++ 19 files changed, 6513 insertions(+) create mode 100644 cmd/somafm/main.go create mode 100644 internal/api/somafm.go create mode 100644 internal/api/somafm_test.go create mode 100644 internal/cache/cache.go create mode 100644 internal/cache/cache_test.go create mode 100644 internal/config/config.go create mode 100644 internal/config/config_test.go create mode 100644 internal/player/player.go create mode 100644 internal/player/player_test.go create mode 100644 internal/service/station_service.go create mode 100644 internal/service/station_service_test.go create mode 100644 internal/station/station.go create mode 100644 internal/station/station_test.go create mode 100644 internal/ui/footer.go create mode 100644 internal/ui/modals.go create mode 100644 internal/ui/stations.go create mode 100644 internal/ui/ui.go create mode 100644 internal/ui/ui_test.go create mode 100644 internal/ui/volume.go diff --git a/cmd/somafm/main.go b/cmd/somafm/main.go new file mode 100644 index 0000000..7b7e052 --- /dev/null +++ b/cmd/somafm/main.go @@ -0,0 +1,138 @@ +package main + +import ( + "flag" + "fmt" + "os" + "os/signal" + "path/filepath" + "syscall" + + "github.com/glebovdev/somafm-cli/internal/api" + "github.com/glebovdev/somafm-cli/internal/cache" + "github.com/glebovdev/somafm-cli/internal/config" + "github.com/glebovdev/somafm-cli/internal/player" + "github.com/glebovdev/somafm-cli/internal/service" + "github.com/glebovdev/somafm-cli/internal/ui" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +var ( + versionFlag = flag.Bool("version", false, "Show version information") + debugFlag = flag.Bool("debug", false, "Enable debug logging") + randomFlag = flag.Bool("random", false, "Start with a random station") +) + +func init() { + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "%s v%s - %s\n\n", config.AppName, config.AppVersion, config.AppDescription) + fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0]) + fmt.Fprintf(os.Stderr, "Options:\n") + flag.PrintDefaults() + + configPath, err := config.GetConfigPath() + if err == nil { + if _, statErr := os.Stat(configPath); statErr == nil { + fmt.Fprintf(os.Stderr, "\nConfig file: %s\n", configPath) + } else { + fmt.Fprintf(os.Stderr, "\nConfig file will be created on first use.\n") + } + } + } +} + +func main() { + flag.Parse() + + if *versionFlag { + fmt.Printf("%s v%s\n", config.AppName, config.AppVersion) + fmt.Println(config.AppDescription) + os.Exit(0) + } + + if *debugFlag { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + + cacheDir, err := cache.GetCacheDir() + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: could not get cache dir: %v\n", err) + cacheDir = os.TempDir() + } + if err := os.MkdirAll(cacheDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Warning: could not create log dir: %v\n", err) + } + logPath := filepath.Join(cacheDir, "debug.log") + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + fmt.Fprintf(os.Stderr, "Warning: could not create log file: %v\n", err) + logFile = os.Stderr + } + log.Logger = log.Output(zerolog.ConsoleWriter{Out: logFile, TimeFormat: "15:04:05"}) + fmt.Printf("Debug log: %s\n", logPath) + log.Info().Msgf("Starting %s v%s (debug mode)", config.AppName, config.AppVersion) + } else { + // Avoid TUI corruption by only logging errors to /dev/null + zerolog.SetGlobalLevel(zerolog.ErrorLevel) + logFile, err := os.OpenFile(os.DevNull, os.O_WRONLY, 0644) + if err == nil { + log.Logger = log.Output(logFile) + } + } + + cfg, err := config.Load() + if err != nil { + log.Warn().Err(err).Msg("Failed to load config, using defaults") + cfg = config.DefaultConfig() + } + + if *debugFlag { + if configPath, err := config.GetConfigPath(); err == nil { + log.Debug().Msgf("Config: %s", configPath) + } + if cacheDir, err := cache.GetCacheDir(); err == nil { + log.Debug().Msgf("Cache: %s", cacheDir) + } + } + + apiClient := api.NewSomaFMClient() + stationService := service.NewStationService(apiClient) + somaPlayer := player.NewPlayer(cfg.BufferSeconds) + somaUi := ui.NewUI(somaPlayer, stationService, *randomFlag) + + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + + uiDone := make(chan error, 1) + + go func() { + <-sigChan + if *debugFlag { + log.Info().Msg("Received shutdown signal, cleaning up...") + } + somaUi.Shutdown() + }() + + if *debugFlag { + log.Info().Msg("Starting UI...") + } + + // Run UI in a goroutine so we can handle signals properly + go func() { + uiDone <- somaUi.Run() + }() + + if err := <-uiDone; err != nil { + if *debugFlag { + log.Error().Err(err).Msg("Error running UI") + } + somaPlayer.Stop() + os.Exit(1) + } + + // Ensure player is fully stopped before exiting + somaPlayer.Stop() + if *debugFlag { + log.Info().Msg("SomaFM CLI stopped") + } +} diff --git a/internal/api/somafm.go b/internal/api/somafm.go new file mode 100644 index 0000000..cb26825 --- /dev/null +++ b/internal/api/somafm.go @@ -0,0 +1,103 @@ +// Package api provides the HTTP client for the SomaFM API. +package api + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/glebovdev/somafm-cli/internal/station" + "github.com/go-resty/resty/v2" +) + +const ( + baseURL = "https://api.somafm.com" + requestTimeout = 30 * time.Second +) + +// SomaFMClient is the HTTP client for interacting with the SomaFM API. +type SomaFMClient struct { + client *resty.Client +} + +// NewSomaFMClient creates a new SomaFM API client with sensible defaults. +func NewSomaFMClient() *SomaFMClient { + return &SomaFMClient{ + client: resty.New(). + SetBaseURL(baseURL). + SetTimeout(requestTimeout), + } +} + +// GetStations fetches the list of available radio stations from the SomaFM API. +func (c *SomaFMClient) GetStations() ([]station.Station, error) { + resp, err := c.client.R().Get("/channels.json") + if err != nil { + return nil, fmt.Errorf("failed to fetch stations: %w", err) + } + + if !resp.IsSuccess() { + return nil, fmt.Errorf("api returned status %d: %s", resp.StatusCode(), resp.Status()) + } + + var response struct { + Channels []station.Station `json:"channels"` + } + + if err := json.Unmarshal(resp.Body(), &response); err != nil { + return nil, fmt.Errorf("failed to parse stations response: %w", err) + } + + return response.Channels, nil +} + +type SongInfo struct { + Title string `json:"title"` + Artist string `json:"artist"` + Album string `json:"album"` + Date string `json:"date"` +} + +type SongsResponse struct { + ID string `json:"id"` + Songs []SongInfo `json:"songs"` +} + +// GetRecentSongs fetches the recent song history for a specific station. +func (c *SomaFMClient) GetRecentSongs(stationID string) (*SongsResponse, error) { + resp, err := c.client.R().Get(fmt.Sprintf("/songs/%s.json", stationID)) + if err != nil { + return nil, fmt.Errorf("failed to fetch songs for station %s: %w", stationID, err) + } + + if !resp.IsSuccess() { + return nil, fmt.Errorf("api returned status %d: %s", resp.StatusCode(), resp.Status()) + } + + var response SongsResponse + if err := json.Unmarshal(resp.Body(), &response); err != nil { + return nil, fmt.Errorf("failed to parse songs response: %w", err) + } + + return &response, nil +} + +func (c *SomaFMClient) GetCurrentTrackForStation(stationID string) (string, error) { + songs, err := c.GetRecentSongs(stationID) + if err != nil { + return "", err + } + + if len(songs.Songs) == 0 { + return "", nil + } + + song := songs.Songs[0] + if song.Artist != "" && song.Title != "" { + return fmt.Sprintf("%s - %s", song.Artist, song.Title), nil + } + if song.Title != "" { + return song.Title, nil + } + return "", nil +} diff --git a/internal/api/somafm_test.go b/internal/api/somafm_test.go new file mode 100644 index 0000000..c73bc64 --- /dev/null +++ b/internal/api/somafm_test.go @@ -0,0 +1,290 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/glebovdev/somafm-cli/internal/station" + "github.com/go-resty/resty/v2" +) + +func setupTestServer(handler http.HandlerFunc) (*httptest.Server, *SomaFMClient) { + server := httptest.NewServer(handler) + client := &SomaFMClient{ + client: resty.New().SetBaseURL(server.URL), + } + return server, client +} + +func TestGetStations(t *testing.T) { + expectedStations := []station.Station{ + { + ID: "groovesalad", + Title: "Groove Salad", + Listeners: "1000", + Playlists: []station.Playlist{ + {URL: "http://example.com/stream.pls", Format: "mp3", Quality: "highest"}, + }, + }, + { + ID: "dronezone", + Title: "Drone Zone", + Listeners: "500", + }, + } + + server, client := setupTestServer(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/channels.json" { + t.Errorf("Expected path /channels.json, got %s", r.URL.Path) + } + + response := struct { + Channels []station.Station `json:"channels"` + }{ + Channels: expectedStations, + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) + }) + defer server.Close() + + stations, err := client.GetStations() + if err != nil { + t.Fatalf("GetStations() error = %v", err) + } + + if len(stations) != len(expectedStations) { + t.Fatalf("GetStations() returned %d stations, want %d", len(stations), len(expectedStations)) + } + + for i, st := range stations { + if st.ID != expectedStations[i].ID { + t.Errorf("stations[%d].ID = %q, want %q", i, st.ID, expectedStations[i].ID) + } + if st.Title != expectedStations[i].Title { + t.Errorf("stations[%d].Title = %q, want %q", i, st.Title, expectedStations[i].Title) + } + } +} + +func TestGetStationsEmptyResponse(t *testing.T) { + server, client := setupTestServer(func(w http.ResponseWriter, r *http.Request) { + response := struct { + Channels []station.Station `json:"channels"` + }{ + Channels: []station.Station{}, + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) + }) + defer server.Close() + + stations, err := client.GetStations() + if err != nil { + t.Fatalf("GetStations() error = %v", err) + } + + if len(stations) != 0 { + t.Errorf("GetStations() returned %d stations, want 0", len(stations)) + } +} + +func TestGetStationsInvalidJSON(t *testing.T) { + server, client := setupTestServer(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte("not valid json")) + }) + defer server.Close() + + _, err := client.GetStations() + if err == nil { + t.Error("GetStations() should return error for invalid JSON") + } +} + +func TestGetRecentSongs(t *testing.T) { + expectedSongs := &SongsResponse{ + ID: "groovesalad", + Songs: []SongInfo{ + {Title: "Song 1", Artist: "Artist 1", Album: "Album 1"}, + {Title: "Song 2", Artist: "Artist 2", Album: "Album 2"}, + }, + } + + server, client := setupTestServer(func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/songs/groovesalad.json" + if r.URL.Path != expectedPath { + t.Errorf("Expected path %s, got %s", expectedPath, r.URL.Path) + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(expectedSongs) + }) + defer server.Close() + + songs, err := client.GetRecentSongs("groovesalad") + if err != nil { + t.Fatalf("GetRecentSongs() error = %v", err) + } + + if songs.ID != expectedSongs.ID { + t.Errorf("GetRecentSongs().ID = %q, want %q", songs.ID, expectedSongs.ID) + } + + if len(songs.Songs) != len(expectedSongs.Songs) { + t.Fatalf("GetRecentSongs() returned %d songs, want %d", len(songs.Songs), len(expectedSongs.Songs)) + } + + for i, song := range songs.Songs { + if song.Title != expectedSongs.Songs[i].Title { + t.Errorf("songs[%d].Title = %q, want %q", i, song.Title, expectedSongs.Songs[i].Title) + } + if song.Artist != expectedSongs.Songs[i].Artist { + t.Errorf("songs[%d].Artist = %q, want %q", i, song.Artist, expectedSongs.Songs[i].Artist) + } + } +} + +func TestGetRecentSongsEmpty(t *testing.T) { + server, client := setupTestServer(func(w http.ResponseWriter, _ *http.Request) { + response := SongsResponse{ + ID: "groovesalad", + Songs: []SongInfo{}, + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) + }) + defer server.Close() + + songs, err := client.GetRecentSongs("groovesalad") + if err != nil { + t.Fatalf("GetRecentSongs() error = %v", err) + } + + if len(songs.Songs) != 0 { + t.Errorf("GetRecentSongs() returned %d songs, want 0", len(songs.Songs)) + } +} + +func TestGetCurrentTrackForStation(t *testing.T) { + tests := []struct { + name string + songs []SongInfo + expected string + }{ + { + name: "artist and title", + songs: []SongInfo{ + {Artist: "The Beatles", Title: "Hey Jude"}, + }, + expected: "The Beatles - Hey Jude", + }, + { + name: "title only", + songs: []SongInfo{ + {Title: "Unknown Track"}, + }, + expected: "Unknown Track", + }, + { + name: "artist only", + songs: []SongInfo{ + {Artist: "Mystery Artist"}, + }, + expected: "", + }, + { + name: "empty songs", + songs: []SongInfo{}, + expected: "", + }, + { + name: "multiple songs returns first", + songs: []SongInfo{ + {Artist: "First", Title: "Song"}, + {Artist: "Second", Title: "Song"}, + }, + expected: "First - Song", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server, client := setupTestServer(func(w http.ResponseWriter, _ *http.Request) { + response := SongsResponse{ + ID: "teststation", + Songs: tt.songs, + } + + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(response) + }) + defer server.Close() + + result, err := client.GetCurrentTrackForStation("teststation") + if err != nil { + t.Fatalf("GetCurrentTrackForStation() error = %v", err) + } + + if result != tt.expected { + t.Errorf("GetCurrentTrackForStation() = %q, want %q", result, tt.expected) + } + }) + } +} + +func TestNewSomaFMClient(t *testing.T) { + client := NewSomaFMClient() + + if client == nil { + t.Fatal("NewSomaFMClient() returned nil") + } + + if client.client == nil { + t.Error("NewSomaFMClient() client.client is nil") + } +} + +func TestSongInfoFields(t *testing.T) { + song := SongInfo{ + Title: "Test Title", + Artist: "Test Artist", + Album: "Test Album", + Date: "2024-01-01", + } + + if song.Title != "Test Title" { + t.Errorf("SongInfo.Title = %q, want %q", song.Title, "Test Title") + } + if song.Artist != "Test Artist" { + t.Errorf("SongInfo.Artist = %q, want %q", song.Artist, "Test Artist") + } + if song.Album != "Test Album" { + t.Errorf("SongInfo.Album = %q, want %q", song.Album, "Test Album") + } + if song.Date != "2024-01-01" { + t.Errorf("SongInfo.Date = %q, want %q", song.Date, "2024-01-01") + } +} + +func TestSongsResponseFields(t *testing.T) { + response := SongsResponse{ + ID: "teststation", + Songs: []SongInfo{ + {Title: "Song 1"}, + {Title: "Song 2"}, + }, + } + + if response.ID != "teststation" { + t.Errorf("SongsResponse.ID = %q, want %q", response.ID, "teststation") + } + if len(response.Songs) != 2 { + t.Errorf("SongsResponse.Songs length = %d, want 2", len(response.Songs)) + } +} diff --git a/internal/cache/cache.go b/internal/cache/cache.go new file mode 100644 index 0000000..fac4773 --- /dev/null +++ b/internal/cache/cache.go @@ -0,0 +1,163 @@ +// Package cache provides image caching functionality for station logos. +package cache + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + "image" + "image/png" + "os" + "path/filepath" + "time" + + "github.com/rs/zerolog/log" +) + +const ( + // DefaultExpiry is how long cached images are valid (7 days). + DefaultExpiry = 7 * 24 * time.Hour + // ImageSubdir is the subdirectory for cached images. + ImageSubdir = "images" + // AppName is used for the cache directory name. + AppName = "somafm" +) + +// Cache manages disk-based caching of station logo images. +type Cache struct { + baseDir string + expiry time.Duration +} + +// NewCache creates a new Cache instance with the default expiry. +func NewCache() (*Cache, error) { + cacheDir, err := GetCacheDir() + if err != nil { + return nil, err + } + + return &Cache{ + baseDir: cacheDir, + expiry: DefaultExpiry, + }, nil +} + +// GetCacheDir returns the platform-specific cache directory for the application. +func GetCacheDir() (string, error) { + userCacheDir, err := os.UserCacheDir() + if err != nil { + return "", fmt.Errorf("failed to get user cache directory: %w", err) + } + + cacheDir := filepath.Join(userCacheDir, AppName) + return cacheDir, nil +} + +func (c *Cache) ensureDir(dir string) error { + return os.MkdirAll(dir, 0755) +} + +func hashURL(url string) string { + hash := md5.Sum([]byte(url)) + return hex.EncodeToString(hash[:]) +} + +// GetImage retrieves a cached image by URL. Returns nil if not found or expired. +func (c *Cache) GetImage(url string) image.Image { + imageDir := filepath.Join(c.baseDir, ImageSubdir) + filename := hashURL(url) + ".png" + imagePath := filepath.Join(imageDir, filename) + + info, err := os.Stat(imagePath) + if err != nil { + return nil + } + + if time.Since(info.ModTime()) > c.expiry { + if err := os.Remove(imagePath); err != nil { + log.Debug().Err(err).Str("file", imagePath).Msg("Failed to remove expired cache file") + } + return nil + } + + file, err := os.Open(imagePath) + if err != nil { + return nil + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + log.Debug().Err(err).Str("file", imagePath).Msg("Failed to decode cached image") + return nil + } + + return img +} + +// SaveImage stores an image in the cache, keyed by its URL. +func (c *Cache) SaveImage(url string, img image.Image) error { + imageDir := filepath.Join(c.baseDir, ImageSubdir) + + if err := c.ensureDir(imageDir); err != nil { + return fmt.Errorf("failed to create cache directory: %w", err) + } + + filename := hashURL(url) + ".png" + imagePath := filepath.Join(imageDir, filename) + + file, err := os.Create(imagePath) + if err != nil { + return fmt.Errorf("failed to create cache file: %w", err) + } + defer file.Close() + + if err := png.Encode(file, img); err != nil { + return fmt.Errorf("failed to encode image: %w", err) + } + + return nil +} + +// CleanExpired removes cache files older than the expiry duration. +func (c *Cache) CleanExpired() error { + imageDir := filepath.Join(c.baseDir, ImageSubdir) + + entries, err := os.ReadDir(imageDir) + if err != nil { + if os.IsNotExist(err) { + return nil + } + return fmt.Errorf("failed to read cache directory: %w", err) + } + + now := time.Now() + var removed, failed int + for _, entry := range entries { + if entry.IsDir() { + continue + } + + info, err := entry.Info() + if err != nil { + log.Debug().Err(err).Str("file", entry.Name()).Msg("Failed to get file info") + continue + } + + if now.Sub(info.ModTime()) > c.expiry { + filePath := filepath.Join(imageDir, entry.Name()) + if err := os.Remove(filePath); err != nil { + log.Debug().Err(err).Str("file", filePath).Msg("Failed to remove expired cache file") + failed++ + } else { + removed++ + } + } + } + + if removed > 0 || failed > 0 { + log.Debug().Int("removed", removed).Int("failed", failed).Msg("Cache cleanup completed") + } + + return nil +} diff --git a/internal/cache/cache_test.go b/internal/cache/cache_test.go new file mode 100644 index 0000000..e65a10f --- /dev/null +++ b/internal/cache/cache_test.go @@ -0,0 +1,323 @@ +package cache + +import ( + "image" + "image/color" + "os" + "path/filepath" + "testing" + "time" +) + +func TestHashURL(t *testing.T) { + tests := []struct { + name string + url string + }{ + {"simple URL", "http://example.com/image.png"}, + {"URL with query params", "http://example.com/image.png?size=large"}, + {"empty string", ""}, + {"https URL", "https://somafm.com/images/logo.png"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := hashURL(tt.url) + + if len(result) != 32 { + t.Errorf("hashURL(%q) length = %d, want 32", tt.url, len(result)) + } + + for _, c := range result { + if !((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) { + t.Errorf("hashURL(%q) contains non-hex character: %c", tt.url, c) + } + } + }) + } +} + +func TestHashURLConsistency(t *testing.T) { + url := "http://somafm.com/images/groovesalad.png" + + hash1 := hashURL(url) + hash2 := hashURL(url) + + if hash1 != hash2 { + t.Errorf("hashURL is not consistent: %q != %q", hash1, hash2) + } +} + +func TestHashURLUniqueness(t *testing.T) { + url1 := "http://example.com/image1.png" + url2 := "http://example.com/image2.png" + + hash1 := hashURL(url1) + hash2 := hashURL(url2) + + if hash1 == hash2 { + t.Errorf("Different URLs produced same hash: %q", hash1) + } +} + +func createTestImage(width, height int) image.Image { + img := image.NewRGBA(image.Rect(0, 0, width, height)) + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + img.Set(x, y, color.RGBA{R: uint8(x % 256), G: uint8(y % 256), B: 128, A: 255}) + } + } + return img +} + +func TestSaveAndGetImage(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: DefaultExpiry, + } + + testURL := "http://example.com/test-image.png" + testImg := createTestImage(100, 100) + + err := cache.SaveImage(testURL, testImg) + if err != nil { + t.Fatalf("SaveImage() error = %v", err) + } + + retrievedImg := cache.GetImage(testURL) + if retrievedImg == nil { + t.Fatal("GetImage() returned nil, expected image") + } + + bounds := retrievedImg.Bounds() + if bounds.Dx() != 100 || bounds.Dy() != 100 { + t.Errorf("Retrieved image size = %dx%d, want 100x100", bounds.Dx(), bounds.Dy()) + } +} + +func TestGetImageNonExistent(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: DefaultExpiry, + } + + result := cache.GetImage("http://example.com/nonexistent.png") + if result != nil { + t.Error("GetImage() for nonexistent URL should return nil") + } +} + +func TestGetImageExpired(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: 1 * time.Millisecond, + } + + testURL := "http://example.com/expired-image.png" + testImg := createTestImage(50, 50) + + err := cache.SaveImage(testURL, testImg) + if err != nil { + t.Fatalf("SaveImage() error = %v", err) + } + + time.Sleep(10 * time.Millisecond) + + result := cache.GetImage(testURL) + if result != nil { + t.Error("GetImage() for expired image should return nil") + } + + filename := hashURL(testURL) + ".png" + imagePath := filepath.Join(tmpDir, ImageSubdir, filename) + if _, err := os.Stat(imagePath); !os.IsNotExist(err) { + t.Error("Expired image file should have been deleted") + } +} + +func TestCleanExpired(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: 1 * time.Millisecond, + } + + testImg := createTestImage(10, 10) + urls := []string{ + "http://example.com/image1.png", + "http://example.com/image2.png", + "http://example.com/image3.png", + } + + for _, url := range urls { + if err := cache.SaveImage(url, testImg); err != nil { + t.Fatalf("SaveImage(%q) error = %v", url, err) + } + } + + time.Sleep(10 * time.Millisecond) + + err := cache.CleanExpired() + if err != nil { + t.Fatalf("CleanExpired() error = %v", err) + } + + imageDir := filepath.Join(tmpDir, ImageSubdir) + entries, err := os.ReadDir(imageDir) + if err != nil { + t.Fatalf("Failed to read image directory: %v", err) + } + + if len(entries) != 0 { + t.Errorf("CleanExpired() left %d files, want 0", len(entries)) + } +} + +func TestCleanExpiredKeepsValidFiles(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: 24 * time.Hour, + } + + testImg := createTestImage(10, 10) + testURL := "http://example.com/valid-image.png" + + if err := cache.SaveImage(testURL, testImg); err != nil { + t.Fatalf("SaveImage() error = %v", err) + } + + err := cache.CleanExpired() + if err != nil { + t.Fatalf("CleanExpired() error = %v", err) + } + + result := cache.GetImage(testURL) + if result == nil { + t.Error("CleanExpired() should not remove valid (non-expired) images") + } +} + +func TestCleanExpiredNonExistentDirectory(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: DefaultExpiry, + } + + err := cache.CleanExpired() + if err != nil { + t.Errorf("CleanExpired() should not error on non-existent directory, got %v", err) + } +} + +func TestGetCacheDir(t *testing.T) { + dir, err := GetCacheDir() + if err != nil { + t.Fatalf("GetCacheDir() error = %v", err) + } + + if dir == "" { + t.Error("GetCacheDir() returned empty string") + } + + if !filepath.IsAbs(dir) { + t.Errorf("GetCacheDir() = %q, want absolute path", dir) + } + + if filepath.Base(dir) != AppName { + t.Errorf("GetCacheDir() directory name = %q, want %q", filepath.Base(dir), AppName) + } +} + +func TestNewCache(t *testing.T) { + cache, err := NewCache() + if err != nil { + t.Fatalf("NewCache() error = %v", err) + } + + if cache == nil { + t.Fatal("NewCache() returned nil") + } + + if cache.baseDir == "" { + t.Error("NewCache() cache.baseDir is empty") + } + + if cache.expiry != DefaultExpiry { + t.Errorf("NewCache() cache.expiry = %v, want %v", cache.expiry, DefaultExpiry) + } +} + +func TestSaveImageCreatesDirectory(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: DefaultExpiry, + } + + testURL := "http://example.com/image.png" + testImg := createTestImage(10, 10) + + err := cache.SaveImage(testURL, testImg) + if err != nil { + t.Fatalf("SaveImage() error = %v", err) + } + + imageDir := filepath.Join(tmpDir, ImageSubdir) + info, err := os.Stat(imageDir) + if err != nil { + t.Fatalf("Image directory was not created: %v", err) + } + + if !info.IsDir() { + t.Error("ImageSubdir should be a directory") + } +} + +func TestMultipleImagesSameCache(t *testing.T) { + tmpDir := t.TempDir() + + cache := &Cache{ + baseDir: tmpDir, + expiry: DefaultExpiry, + } + + images := map[string]image.Image{ + "http://example.com/image1.png": createTestImage(50, 50), + "http://example.com/image2.png": createTestImage(100, 100), + "http://example.com/image3.png": createTestImage(200, 200), + } + + for url, img := range images { + if err := cache.SaveImage(url, img); err != nil { + t.Fatalf("SaveImage(%q) error = %v", url, err) + } + } + + for url, originalImg := range images { + retrieved := cache.GetImage(url) + if retrieved == nil { + t.Errorf("GetImage(%q) returned nil", url) + continue + } + + expectedBounds := originalImg.Bounds() + retrievedBounds := retrieved.Bounds() + if retrievedBounds.Dx() != expectedBounds.Dx() || retrievedBounds.Dy() != expectedBounds.Dy() { + t.Errorf("GetImage(%q) size = %dx%d, want %dx%d", + url, retrievedBounds.Dx(), retrievedBounds.Dy(), + expectedBounds.Dx(), expectedBounds.Dy()) + } + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..2d38215 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,226 @@ +package config + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/gdamore/tcell/v2" + "gopkg.in/yaml.v3" +) + +const ( + AppName = "SomaFM CLI" + AppTagline = "Terminal radio player" + AppDescription = "A terminal-based music player for SomaFM radio stations" + AppAuthor = "Ilya Glebov" + AppAuthorURL = "https://ilyaglebov.dev" + AppAuthorURLShort = "ilyaglebov.dev" + AppProjectURL = "https://github.com/glebovdev/somafm-cli" + AppProjectShort = "github.com/glebovdev/somafm-cli" + AppDonateURL = "https://somafm.com/donate/" + AppDonateShort = "somafm.com/donate" + + ConfigDir = ".config/somafm" + ConfigFileName = "config.yml" + DefaultVolume = 70 + MinVolume = 0 + MaxVolume = 100 + DefaultBufferSecs = 5 + MinBufferSecs = 0 + MaxBufferSecs = 60 +) + +// ClampVolume ensures volume is within the valid range [0, 100]. +func ClampVolume(volume int) int { + if volume < MinVolume { + return MinVolume + } + if volume > MaxVolume { + return MaxVolume + } + return volume +} + +// AppVersion can be overridden at build time using ldflags: +// go build -ldflags "-X github.com/glebovdev/somafm-cli/internal/config.AppVersion=1.0.0" +var AppVersion = "dev" + +type Theme struct { + Background string `yaml:"background"` + Foreground string `yaml:"foreground"` + Borders string `yaml:"borders"` + Highlight string `yaml:"highlight"` + MutedVolume string `yaml:"muted_volume"` + HeaderBackground string `yaml:"header_background"` + StationListHeaderBackground string `yaml:"station_list_header_background"` + StationListHeaderForeground string `yaml:"station_list_header_foreground"` + HelpBackground string `yaml:"help_background"` + HelpForeground string `yaml:"help_foreground"` + HelpHotkey string `yaml:"help_hotkey"` + GenreTagBackground string `yaml:"genre_tag_background"` + ModalBackground string `yaml:"modal_background"` +} + +type Config struct { + Volume int `yaml:"volume"` + BufferSeconds int `yaml:"buffer_seconds"` + LastStation string `yaml:"last_station"` + Favorites []string `yaml:"favorites"` + Theme Theme `yaml:"theme"` +} + +func GetConfigPath() (string, error) { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("failed to get user home directory: %w", err) + } + + configPath := filepath.Join(home, ConfigDir, ConfigFileName) + return configPath, nil +} + +func Load() (*Config, error) { + configPath, err := GetConfigPath() + if err != nil { + return DefaultConfig(), err + } + + if _, err := os.Stat(configPath); os.IsNotExist(err) { + return DefaultConfig(), nil + } + + data, err := os.ReadFile(configPath) + if err != nil { + return DefaultConfig(), fmt.Errorf("failed to read config file: %w", err) + } + + var cfg Config + if err := yaml.Unmarshal(data, &cfg); err != nil { + return DefaultConfig(), fmt.Errorf("failed to parse config file: %w", err) + } + + cfg.Volume = ClampVolume(cfg.Volume) + + if cfg.BufferSeconds < MinBufferSecs { + cfg.BufferSeconds = MinBufferSecs + } + if cfg.BufferSeconds > MaxBufferSecs { + cfg.BufferSeconds = MaxBufferSecs + } + if cfg.BufferSeconds == 0 && data != nil { + cfg.BufferSeconds = DefaultBufferSecs + } + + if cfg.Theme.Background == "" { + cfg.Theme = DefaultConfig().Theme + } + + return &cfg, nil +} + +// Save writes the configuration to disk atomically using temp file + rename. +func (c *Config) Save() error { + configPath, err := GetConfigPath() + if err != nil { + return err + } + + configDir := filepath.Dir(configPath) + if err := os.MkdirAll(configDir, 0755); err != nil { + return fmt.Errorf("failed to create config directory: %w", err) + } + + data, err := yaml.Marshal(c) + if err != nil { + return fmt.Errorf("failed to marshal config: %w", err) + } + + tmpFile, err := os.CreateTemp(configDir, ".config-*.tmp") + if err != nil { + return fmt.Errorf("failed to create temp file: %w", err) + } + tmpPath := tmpFile.Name() + + defer func() { + if tmpPath != "" { + os.Remove(tmpPath) + } + }() + + if _, err := tmpFile.Write(data); err != nil { + tmpFile.Close() + return fmt.Errorf("failed to write temp file: %w", err) + } + + if err := tmpFile.Close(); err != nil { + return fmt.Errorf("failed to close temp file: %w", err) + } + + if err := os.Rename(tmpPath, configPath); err != nil { + return fmt.Errorf("failed to rename config file: %w", err) + } + + tmpPath = "" // Prevent defer from removing the final file + return nil +} + +func DefaultConfig() *Config { + return &Config{ + Volume: DefaultVolume, + BufferSeconds: DefaultBufferSecs, + LastStation: "", + Favorites: []string{}, + Theme: Theme{ + Background: "#1a1b25", + Foreground: "#a3aacb", + Borders: "#40445b", + Highlight: "#ff9d65", + MutedVolume: "#fe0702", + HeaderBackground: "#473533", + StationListHeaderBackground: "#3a3d4f", + StationListHeaderForeground: "#c8d0e8", + HelpBackground: "#322f45", + HelpForeground: "#9aa3c6", + HelpHotkey: "#ff9d65", + GenreTagBackground: "#3a3d4f", + ModalBackground: "#282a36", + }, + } +} + +func (c *Config) IsFavorite(stationID string) bool { + for _, id := range c.Favorites { + if id == stationID { + return true + } + } + return false +} + +func (c *Config) ToggleFavorite(stationID string) { + for i, id := range c.Favorites { + if id == stationID { + c.Favorites = append(c.Favorites[:i], c.Favorites[i+1:]...) + return + } + } + c.Favorites = append(c.Favorites, stationID) +} + +func (c *Config) CleanupFavorites(validStationIDs map[string]bool) { + cleaned := []string{} + for _, id := range c.Favorites { + if validStationIDs[id] { + cleaned = append(cleaned, id) + } + } + c.Favorites = cleaned +} + +func GetColor(colorStr string) tcell.Color { + if colorStr == "" || colorStr == "default" { + return tcell.ColorDefault + } + return tcell.GetColor(colorStr) +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..1ff3429 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,525 @@ +package config + +import ( + "os" + "path/filepath" + "testing" +) + +func TestDefaultConfig(t *testing.T) { + cfg := DefaultConfig() + + if cfg.Volume != DefaultVolume { + t.Errorf("DefaultConfig().Volume = %d, want %d", cfg.Volume, DefaultVolume) + } + + if cfg.LastStation != "" { + t.Errorf("DefaultConfig().LastStation = %q, want empty string", cfg.LastStation) + } +} + +func TestConfigSaveAndLoad(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + testCfg := &Config{ + Volume: 85, + LastStation: "groovesalad", + } + + err := testCfg.Save() + if err != nil { + t.Fatalf("Save() error = %v", err) + } + + configPath := filepath.Join(tmpDir, ConfigDir, ConfigFileName) + if _, err := os.Stat(configPath); os.IsNotExist(err) { + t.Fatalf("Config file was not created at %s", configPath) + } + + loadedCfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if loadedCfg.Volume != testCfg.Volume { + t.Errorf("Load().Volume = %d, want %d", loadedCfg.Volume, testCfg.Volume) + } + + if loadedCfg.LastStation != testCfg.LastStation { + t.Errorf("Load().LastStation = %q, want %q", loadedCfg.LastStation, testCfg.LastStation) + } +} + +func TestLoadNonExistentConfig(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + cfg, err := Load() + if err != nil { + t.Logf("Load() error (expected): %v", err) + } + + if cfg.Volume != DefaultVolume { + t.Errorf("Load() with non-existent file returned Volume = %d, want %d", cfg.Volume, DefaultVolume) + } + + if cfg.LastStation != "" { + t.Errorf("Load() with non-existent file returned LastStation = %q, want empty string", cfg.LastStation) + } +} + +func TestVolumeValidation(t *testing.T) { + tests := []struct { + name string + inputVolume int + expectedVolume int + }{ + {"valid volume 50", 50, 50}, + {"valid volume 0", 0, 0}, + {"valid volume 100", 100, 100}, + {"negative volume", -10, 0}, + {"volume over 100", 150, 100}, + {"volume way over 100", 1000, 100}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + testCfg := &Config{ + Volume: tt.inputVolume, + LastStation: "groovesalad", + } + + err := testCfg.Save() + if err != nil { + t.Fatalf("Save() error = %v", err) + } + + loadedCfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if loadedCfg.Volume != tt.expectedVolume { + t.Errorf("Load().Volume = %d, want %d", loadedCfg.Volume, tt.expectedVolume) + } + }) + } +} + +func TestThemeDefaults(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + cfg, err := Load() + if err != nil { + t.Logf("Load() error (expected): %v", err) + } + + if cfg.Theme.Background != "#1a1b25" { + t.Errorf("Theme.Background = %q, want %q", cfg.Theme.Background, "#1a1b25") + } + if cfg.Theme.Foreground != "#a3aacb" { + t.Errorf("Theme.Foreground = %q, want %q", cfg.Theme.Foreground, "#a3aacb") + } + if cfg.Theme.Borders != "#40445b" { + t.Errorf("Theme.Borders = %q, want %q", cfg.Theme.Borders, "#40445b") + } + if cfg.Theme.Highlight != "#ff9d65" { + t.Errorf("Theme.Highlight = %q, want %q", cfg.Theme.Highlight, "#ff9d65") + } + if cfg.Theme.MutedVolume != "#fe0702" { + t.Errorf("Theme.MutedVolume = %q, want %q", cfg.Theme.MutedVolume, "#fe0702") + } +} + +func TestThemePersistence(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + testCfg := &Config{ + Volume: 70, + LastStation: "groovesalad", + Theme: Theme{ + Background: "black", + Foreground: "yellow", + Borders: "blue", + Highlight: "red", + }, + } + + err := testCfg.Save() + if err != nil { + t.Fatalf("Save() error = %v", err) + } + + loadedCfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if loadedCfg.Theme.Background != "black" { + t.Errorf("Theme.Background = %q, want %q", loadedCfg.Theme.Background, "black") + } + if loadedCfg.Theme.Foreground != "yellow" { + t.Errorf("Theme.Foreground = %q, want %q", loadedCfg.Theme.Foreground, "yellow") + } + if loadedCfg.Theme.Borders != "blue" { + t.Errorf("Theme.Borders = %q, want %q", loadedCfg.Theme.Borders, "blue") + } + if loadedCfg.Theme.Highlight != "red" { + t.Errorf("Theme.Highlight = %q, want %q", loadedCfg.Theme.Highlight, "red") + } +} + +func TestIsFavorite(t *testing.T) { + tests := []struct { + name string + favorites []string + stationID string + expected bool + }{ + { + name: "station is favorite", + favorites: []string{"groovesalad", "dronezone", "lush"}, + stationID: "dronezone", + expected: true, + }, + { + name: "station is not favorite", + favorites: []string{"groovesalad", "dronezone"}, + stationID: "lush", + expected: false, + }, + { + name: "empty favorites list", + favorites: []string{}, + stationID: "groovesalad", + expected: false, + }, + { + name: "first item in list", + favorites: []string{"groovesalad", "dronezone"}, + stationID: "groovesalad", + expected: true, + }, + { + name: "last item in list", + favorites: []string{"groovesalad", "dronezone", "lush"}, + stationID: "lush", + expected: true, + }, + { + name: "nil favorites", + favorites: nil, + stationID: "groovesalad", + expected: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{Favorites: tt.favorites} + result := cfg.IsFavorite(tt.stationID) + if result != tt.expected { + t.Errorf("IsFavorite(%q) = %v, want %v", tt.stationID, result, tt.expected) + } + }) + } +} + +func TestToggleFavorite(t *testing.T) { + tests := []struct { + name string + initialFavorites []string + stationID string + expectedFavorites []string + }{ + { + name: "add to empty list", + initialFavorites: []string{}, + stationID: "groovesalad", + expectedFavorites: []string{"groovesalad"}, + }, + { + name: "add to existing list", + initialFavorites: []string{"dronezone"}, + stationID: "groovesalad", + expectedFavorites: []string{"dronezone", "groovesalad"}, + }, + { + name: "remove from list", + initialFavorites: []string{"groovesalad", "dronezone", "lush"}, + stationID: "dronezone", + expectedFavorites: []string{"groovesalad", "lush"}, + }, + { + name: "remove first item", + initialFavorites: []string{"groovesalad", "dronezone"}, + stationID: "groovesalad", + expectedFavorites: []string{"dronezone"}, + }, + { + name: "remove last item", + initialFavorites: []string{"groovesalad", "dronezone"}, + stationID: "dronezone", + expectedFavorites: []string{"groovesalad"}, + }, + { + name: "remove only item", + initialFavorites: []string{"groovesalad"}, + stationID: "groovesalad", + expectedFavorites: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{Favorites: make([]string, len(tt.initialFavorites))} + copy(cfg.Favorites, tt.initialFavorites) + + cfg.ToggleFavorite(tt.stationID) + + if len(cfg.Favorites) != len(tt.expectedFavorites) { + t.Fatalf("ToggleFavorite(%q) resulted in %d favorites, want %d", + tt.stationID, len(cfg.Favorites), len(tt.expectedFavorites)) + } + + for i, fav := range cfg.Favorites { + if fav != tt.expectedFavorites[i] { + t.Errorf("Favorites[%d] = %q, want %q", i, fav, tt.expectedFavorites[i]) + } + } + }) + } +} + +func TestToggleFavoriteDoubleToggle(t *testing.T) { + cfg := &Config{Favorites: []string{}} + + cfg.ToggleFavorite("groovesalad") + if !cfg.IsFavorite("groovesalad") { + t.Error("After first toggle, groovesalad should be favorite") + } + + cfg.ToggleFavorite("groovesalad") + if cfg.IsFavorite("groovesalad") { + t.Error("After second toggle, groovesalad should not be favorite") + } +} + +func TestCleanupFavorites(t *testing.T) { + tests := []struct { + name string + initialFavorites []string + validStationIDs map[string]bool + expectedFavorites []string + }{ + { + name: "all valid", + initialFavorites: []string{"groovesalad", "dronezone"}, + validStationIDs: map[string]bool{"groovesalad": true, "dronezone": true, "lush": true}, + expectedFavorites: []string{"groovesalad", "dronezone"}, + }, + { + name: "some invalid", + initialFavorites: []string{"groovesalad", "deleted_station", "dronezone"}, + validStationIDs: map[string]bool{"groovesalad": true, "dronezone": true}, + expectedFavorites: []string{"groovesalad", "dronezone"}, + }, + { + name: "all invalid", + initialFavorites: []string{"deleted1", "deleted2"}, + validStationIDs: map[string]bool{"groovesalad": true}, + expectedFavorites: []string{}, + }, + { + name: "empty favorites", + initialFavorites: []string{}, + validStationIDs: map[string]bool{"groovesalad": true}, + expectedFavorites: []string{}, + }, + { + name: "empty valid IDs", + initialFavorites: []string{"groovesalad"}, + validStationIDs: map[string]bool{}, + expectedFavorites: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cfg := &Config{Favorites: make([]string, len(tt.initialFavorites))} + copy(cfg.Favorites, tt.initialFavorites) + + cfg.CleanupFavorites(tt.validStationIDs) + + if len(cfg.Favorites) != len(tt.expectedFavorites) { + t.Fatalf("CleanupFavorites resulted in %d favorites, want %d", + len(cfg.Favorites), len(tt.expectedFavorites)) + } + + for i, fav := range cfg.Favorites { + if fav != tt.expectedFavorites[i] { + t.Errorf("Favorites[%d] = %q, want %q", i, fav, tt.expectedFavorites[i]) + } + } + }) + } +} + +func TestGetColor(t *testing.T) { + tests := []struct { + name string + colorStr string + isNonNil bool + }{ + {"empty string returns default", "", true}, + {"default keyword returns default", "default", true}, + {"named color white", "white", true}, + {"named color red", "red", true}, + {"named color darkcyan", "darkcyan", true}, + {"hex color", "#FF0000", true}, + {"hex color lowercase", "#ff0000", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := GetColor(tt.colorStr) + if tt.colorStr == "" || tt.colorStr == "default" { + if result != 0 { + t.Errorf("GetColor(%q) = %v, want ColorDefault (0)", tt.colorStr, result) + } + } + }) + } +} + +func TestBufferSecondsValidation(t *testing.T) { + tests := []struct { + name string + inputBuffer int + expectedBuffer int + }{ + {"valid buffer 5", 5, 5}, + {"valid buffer 0", 0, DefaultBufferSecs}, + {"valid buffer 60", 60, 60}, + {"negative buffer", -10, DefaultBufferSecs}, + {"buffer over 60", 100, MaxBufferSecs}, + {"buffer way over max", 1000, MaxBufferSecs}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + testCfg := &Config{ + Volume: 70, + BufferSeconds: tt.inputBuffer, + Theme: DefaultConfig().Theme, + } + + err := testCfg.Save() + if err != nil { + t.Fatalf("Save() error = %v", err) + } + + loadedCfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if loadedCfg.BufferSeconds != tt.expectedBuffer { + t.Errorf("Load().BufferSeconds = %d, want %d", loadedCfg.BufferSeconds, tt.expectedBuffer) + } + }) + } +} + +func TestFavoritesPersistence(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + testCfg := &Config{ + Volume: 70, + Favorites: []string{"groovesalad", "dronezone", "lush"}, + Theme: DefaultConfig().Theme, + } + + err := testCfg.Save() + if err != nil { + t.Fatalf("Save() error = %v", err) + } + + loadedCfg, err := Load() + if err != nil { + t.Fatalf("Load() error = %v", err) + } + + if len(loadedCfg.Favorites) != 3 { + t.Fatalf("Load().Favorites has %d items, want 3", len(loadedCfg.Favorites)) + } + + expected := []string{"groovesalad", "dronezone", "lush"} + for i, fav := range loadedCfg.Favorites { + if fav != expected[i] { + t.Errorf("Favorites[%d] = %q, want %q", i, fav, expected[i]) + } + } +} + +func TestLoadInvalidYAML(t *testing.T) { + tmpDir := t.TempDir() + originalHome := os.Getenv("HOME") + os.Setenv("HOME", tmpDir) + defer os.Setenv("HOME", originalHome) + + configDir := filepath.Join(tmpDir, ConfigDir) + _ = os.MkdirAll(configDir, 0755) + configPath := filepath.Join(configDir, ConfigFileName) + + invalidYAML := []byte("this is not: valid: yaml: [") + _ = os.WriteFile(configPath, invalidYAML, 0644) + + cfg, err := Load() + if err == nil { + t.Log("Load() returned no error for invalid YAML, but returned default config") + } + + if cfg.Volume != DefaultVolume { + t.Errorf("Load() with invalid YAML returned Volume = %d, want default %d", cfg.Volume, DefaultVolume) + } +} + +func TestGetConfigPath(t *testing.T) { + path, err := GetConfigPath() + if err != nil { + t.Fatalf("GetConfigPath() error = %v", err) + } + + if path == "" { + t.Error("GetConfigPath() returned empty string") + } + + if !filepath.IsAbs(path) { + t.Errorf("GetConfigPath() = %q, want absolute path", path) + } +} diff --git a/internal/player/player.go b/internal/player/player.go new file mode 100644 index 0000000..cd443a5 --- /dev/null +++ b/internal/player/player.go @@ -0,0 +1,1062 @@ +package player + +import ( + "bufio" + "context" + "errors" + "fmt" + "io" + "math" + "net/http" + "strings" + "sync" + "time" + + "github.com/glebovdev/somafm-cli/internal/config" + "github.com/glebovdev/somafm-cli/internal/station" + "github.com/gopxl/beep/v2" + "github.com/gopxl/beep/v2/effects" + "github.com/gopxl/beep/v2/mp3" + "github.com/gopxl/beep/v2/speaker" + "github.com/rs/zerolog/log" +) + +const ( + DefaultSampleRate = beep.SampleRate(44100) + SpeakerBufferSize = time.Millisecond * 250 + NetworkReadSize = 4096 + SampleChannelSize = 8192 + MaxRetries = 3 + RetryDelay = time.Second * 2 + VolumeCurveExponent = 0.5 + MinVolumeDB = -10.0 + ReadTimeout = 10 * time.Second + MaxErrorsToKeep = 10 // Limit error accumulation during retries +) + +type PlayerState int + +const ( + StateIdle PlayerState = iota + StateBuffering + StatePlaying + StatePaused + StateReconnecting + StateError +) + +func (s PlayerState) String() string { + switch s { + case StateIdle: + return "IDLE" + case StateBuffering: + return "BUFFERING" + case StatePlaying: + return "LIVE" + case StatePaused: + return "PAUSED" + case StateReconnecting: + return "RECONNECTING" + case StateError: + return "ERROR" + default: + return "UNKNOWN" + } +} + +// StreamInfo contains metadata about the current audio stream. +type StreamInfo struct { + Format string + Quality string + Bitrate int + SampleRate int +} + +// contextReader wraps a reader with context-aware timeout detection. +// When a read blocks longer than the timeout, it returns an error +// without leaking goroutines (relies on context cancellation for cleanup). +type contextReader struct { + reader io.Reader + ctx context.Context + timeout time.Duration +} + +func (cr *contextReader) Read(p []byte) (n int, err error) { + select { + case <-cr.ctx.Done(): + return 0, cr.ctx.Err() + default: + } + + timer := time.NewTimer(cr.timeout) + defer timer.Stop() + + type result struct { + n int + err error + } + done := make(chan result, 1) + + go func() { + n, err := cr.reader.Read(p) + select { + case done <- result{n, err}: + case <-cr.ctx.Done(): + } + }() + + select { + case res := <-done: + return res.n, res.err + case <-timer.C: + return 0, fmt.Errorf("read timeout: no data received for %v", cr.timeout) + case <-cr.ctx.Done(): + return 0, cr.ctx.Err() + } +} + +// Player handles audio streaming and playback for SomaFM radio stations. +// It manages the audio pipeline including network streaming, decoding, buffering, +// and volume control. +type Player struct { + format beep.Format + volume *effects.Volume + ctrl *beep.Ctrl + mu sync.Mutex + cancelFunc context.CancelFunc + isPaused bool + isPlaying bool + speakerInit bool + volumePercent int + httpClient *http.Client // Reused for all stream connections + + buffer [][2]float64 + bufferSize int + writeIdx int64 + readBackOffset int + bufferMu sync.Mutex + sampleCh chan [2]float64 + wg sync.WaitGroup + streamDone chan struct{} + streamDoneOnce sync.Once // Prevents double-close panic on streamDone + streamErr chan error + + currentTrack string + trackMu sync.RWMutex + + state PlayerState + streamInfo StreamInfo + retryAttempt int + maxRetries int + sessionStart time.Time + lastError string + stateMu sync.RWMutex + + currentStation *station.Station + streamAlive bool + streamAliveMu sync.RWMutex +} + +// closeStreamDone safely closes the streamDone channel exactly once. +// This prevents panics from double-close when multiple goroutines try to signal completion. +func (p *Player) closeStreamDone() { + p.streamDoneOnce.Do(func() { + if p.streamDone != nil { + close(p.streamDone) + } + }) +} + +// NewPlayer creates a new Player with the specified buffer size in seconds. +func NewPlayer(bufferSeconds int) *Player { + var buffer [][2]float64 + if bufferSeconds > 0 { + bufferLen := int(DefaultSampleRate) * bufferSeconds + buffer = make([][2]float64, bufferLen) + log.Debug().Msgf("Initialized circular buffer: %d seconds (%d samples, ~%.2f MB)", + bufferSeconds, bufferLen, float64(bufferLen*2*8)/1000000) + } + + // Create a reusable HTTP client with appropriate settings for streaming + httpClient := &http.Client{ + Timeout: 0, // No timeout for streaming connections + Transport: &http.Transport{ + DisableKeepAlives: false, + MaxIdleConns: 10, + IdleConnTimeout: 90 * time.Second, + DisableCompression: true, // Audio streams are already compressed + }, + } + + return &Player{ + format: beep.Format{ + SampleRate: DefaultSampleRate, + NumChannels: 2, + Precision: 2, + }, + speakerInit: false, + isPaused: false, + isPlaying: false, + volumePercent: -1, + httpClient: httpClient, + buffer: buffer, + bufferSize: bufferSeconds, + currentTrack: "", + } +} + +func (p *Player) initSpeaker(sampleRate beep.SampleRate) error { + p.mu.Lock() + defer p.mu.Unlock() + + if !p.speakerInit || sampleRate != p.format.SampleRate { + err := speaker.Init(sampleRate, sampleRate.N(SpeakerBufferSize)) + if err != nil { + return fmt.Errorf("failed to initialize speaker: %w", err) + } + p.format.SampleRate = sampleRate + p.speakerInit = true + log.Debug().Msgf("Speaker initialized with sample rate: %d Hz, buffer: %v", sampleRate, SpeakerBufferSize) + } + return nil +} + +func (p *Player) Stop() { + p.mu.Lock() + defer p.mu.Unlock() + + if p.cancelFunc != nil { + p.cancelFunc() + p.cancelFunc = nil + } + + speaker.Clear() + p.isPlaying = false + p.isPaused = false + + p.streamAliveMu.Lock() + p.streamAlive = false + p.streamAliveMu.Unlock() + + p.stateMu.Lock() + p.state = StateIdle + p.sessionStart = time.Time{} + p.streamInfo = StreamInfo{} + p.stateMu.Unlock() + + log.Debug().Msg("Playback stopped") +} + +func (p *Player) TogglePause() { + p.mu.Lock() + defer p.mu.Unlock() + + if p.ctrl == nil || !p.isPlaying { + return + } + + speaker.Lock() + p.ctrl.Paused = !p.ctrl.Paused + p.isPaused = p.ctrl.Paused + speaker.Unlock() + + if p.isPaused { + p.stateMu.Lock() + p.state = StatePaused + p.stateMu.Unlock() + log.Debug().Msg("Playback paused") + } else { + p.stateMu.Lock() + p.state = StatePlaying + p.stateMu.Unlock() + log.Debug().Msg("Playback resumed") + } +} + +func (p *Player) SetVolume(volumePercent int) { + p.mu.Lock() + defer p.mu.Unlock() + + p.volumePercent = volumePercent + + if p.volume == nil { + log.Debug().Msgf("Volume stored as %d%% (will be applied when playback starts)", volumePercent) + return + } + + volumeLevel := percentToExponent(float64(volumePercent)) + + speaker.Lock() + p.volume.Volume = volumeLevel + p.volume.Silent = volumePercent == 0 + speaker.Unlock() + + log.Debug().Msgf("Volume set to %d%% (%.2f dB)", volumePercent, volumeLevel) +} + +func percentToExponent(p float64) float64 { + if p <= 0 { + return MinVolumeDB + } + if p >= 100 { + return 0 + } + + normalized := p / 100.0 + adjusted := math.Pow(normalized, VolumeCurveExponent) + return (1.0 - adjusted) * MinVolumeDB +} + +func (p *Player) IsPlaying() bool { + p.mu.Lock() + defer p.mu.Unlock() + return p.isPlaying && !p.isPaused +} + +func (p *Player) IsPaused() bool { + p.mu.Lock() + defer p.mu.Unlock() + return p.isPaused +} + +func (p *Player) GetCurrentTrack() string { + p.trackMu.RLock() + defer p.trackMu.RUnlock() + + if p.currentTrack == "" { + return "Waiting for track info..." + } + return p.currentTrack +} + +func (p *Player) GetCurrentStation() *station.Station { + p.mu.Lock() + defer p.mu.Unlock() + return p.currentStation +} + +func (p *Player) setCurrentTrack(track string) { + p.trackMu.Lock() + defer p.trackMu.Unlock() + + if track != p.currentTrack { + p.currentTrack = track + log.Debug().Msgf("Now playing: %s", track) + } +} + +func (p *Player) SetInitialTrack(track string) { + p.trackMu.Lock() + defer p.trackMu.Unlock() + + // Don't overwrite ICY metadata if already set + if p.currentTrack == "" { + p.currentTrack = track + log.Debug().Msgf("Initial track set from songs API: %s", track) + } +} + +func (p *Player) GetState() PlayerState { + p.stateMu.RLock() + defer p.stateMu.RUnlock() + return p.state +} + +func (p *Player) setState(state PlayerState) { + p.stateMu.Lock() + defer p.stateMu.Unlock() + if p.state != state { + log.Debug().Msgf("Player state: %s -> %s", p.state.String(), state.String()) + p.state = state + } +} + +func (p *Player) GetStreamInfo() StreamInfo { + p.stateMu.RLock() + defer p.stateMu.RUnlock() + return p.streamInfo +} + +func (p *Player) setStreamInfo(info StreamInfo) { + p.stateMu.Lock() + defer p.stateMu.Unlock() + p.streamInfo = info + log.Debug().Msgf("Stream info: %s %dk %dHz", info.Format, info.Bitrate, info.SampleRate) +} + +func (p *Player) GetBufferFillPercent() int { + if len(p.buffer) == 0 { + return 0 + } + + p.bufferMu.Lock() + defer p.bufferMu.Unlock() + + bufferLen := int64(len(p.buffer)) + fillLevel := p.writeIdx + if fillLevel > bufferLen { + fillLevel = bufferLen + } + + return int((fillLevel * 100) / bufferLen) +} + +// GetBufferHealth returns the current buffer fill level as a percentage (0-100). +func (p *Player) GetBufferHealth() int { + p.mu.Lock() + ch := p.sampleCh + p.mu.Unlock() + + if ch == nil { + return 0 + } + + channelLen := len(ch) + channelCap := cap(ch) + + if channelCap == 0 { + return 0 + } + + return (channelLen * 100) / channelCap +} + +func (p *Player) GetRetryInfo() (current, max int) { + p.stateMu.RLock() + defer p.stateMu.RUnlock() + return p.retryAttempt, p.maxRetries +} + +func (p *Player) setRetryInfo(current, max int) { + p.stateMu.Lock() + defer p.stateMu.Unlock() + p.retryAttempt = current + p.maxRetries = max +} + +func (p *Player) GetSessionDuration() time.Duration { + p.stateMu.RLock() + defer p.stateMu.RUnlock() + + if p.sessionStart.IsZero() { + return 0 + } + return time.Since(p.sessionStart) +} + +func (p *Player) startSession() { + p.stateMu.Lock() + defer p.stateMu.Unlock() + p.sessionStart = time.Now() +} + +func (p *Player) GetLastError() string { + p.stateMu.RLock() + defer p.stateMu.RUnlock() + return p.lastError +} + +func (p *Player) setLastError(err string) { + p.stateMu.Lock() + defer p.stateMu.Unlock() + p.lastError = err +} + +func (p *Player) Play(s *station.Station) error { + return p.PlayWithRetry(s, MaxRetries) +} + +func (p *Player) PlayWithRetry(s *station.Station, maxRetries int) error { + playlistURLs := s.GetAllPlaylistURLs() + if len(playlistURLs) == 0 { + p.setState(StateError) + p.setLastError("No playlists available") + return fmt.Errorf("no playlists available for station: %s", s.Title) + } + + p.setState(StateBuffering) + p.setRetryInfo(0, maxRetries) + + allErrors := make([]string, 0, MaxErrorsToKeep) + totalAttempts := 0 + + addError := func(msg string) { + if len(allErrors) < MaxErrorsToKeep { + allErrors = append(allErrors, msg) + } + } + + for playlistIdx, playlistURL := range playlistURLs { + log.Debug().Msgf("Trying playlist %d/%d: %s", playlistIdx+1, len(playlistURLs), playlistURL) + + streamInfo := parseStreamInfoFromURL(playlistURL) + + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + streamURLs, err := p.fetchAndParsePLS(ctx, playlistURL) + cancel() + + if err != nil { + log.Warn().Err(err).Msgf("Failed to fetch playlist: %s", playlistURL) + addError(fmt.Sprintf("playlist %s: %v", playlistURL, err)) + continue + } + + log.Debug().Msgf("Found %d stream URLs in playlist", len(streamURLs)) + + for urlIdx, streamURL := range streamURLs { + for attempt := 1; attempt <= maxRetries; attempt++ { + totalAttempts++ + + if attempt > 1 { + p.setState(StateReconnecting) + p.setRetryInfo(attempt, maxRetries) + } + + log.Debug().Msgf("Trying stream %d/%d (attempt %d/%d): %s", + urlIdx+1, len(streamURLs), attempt, maxRetries, streamURL) + + ctx, cancel := context.WithCancel(context.Background()) + + p.mu.Lock() + if p.cancelFunc != nil { + p.cancelFunc() + } + p.cancelFunc = cancel + p.mu.Unlock() + + p.setStreamInfo(streamInfo) + + err := p.playStreamURL(ctx, s, streamURL) + if err == nil { + return nil + } + + if errors.Is(err, context.Canceled) { + return context.Canceled + } + + if isNonRetryableError(err) { + log.Warn().Err(err).Msgf("Non-retryable error for %s, moving to next URL", streamURL) + addError(fmt.Sprintf("%s: %v", streamURL, err)) + break + } + + if isNetworkDownError(err) { + log.Warn().Err(err).Msg("Network appears to be down, stopping retries") + p.setState(StateError) + p.setLastError("Network connection lost") + return fmt.Errorf("network connection lost: %w", err) + } + + addError(fmt.Sprintf("%s (attempt %d): %v", streamURL, attempt, err)) + + if attempt < maxRetries { + log.Warn().Err(err).Msgf("Stream failed, retrying in %v...", RetryDelay) + time.Sleep(RetryDelay) + } + } + } + } + + p.setState(StateError) + p.setLastError("Connection failed") + return fmt.Errorf("playback failed after %d total attempts across all streams. Errors: %s", + totalAttempts, strings.Join(allErrors, "; ")) +} + +func isNonRetryableError(err error) bool { + errStr := err.Error() + // HTTP errors that won't change with retry on this specific URL + return strings.Contains(errStr, "status 401") || + strings.Contains(errStr, "status 403") || + strings.Contains(errStr, "status 404") || + strings.Contains(errStr, "status 410") +} + +func isNetworkDownError(err error) bool { + errStr := err.Error() + return strings.Contains(errStr, "no such host") || + strings.Contains(errStr, "network is unreachable") || + strings.Contains(errStr, "no route to host") || + strings.Contains(errStr, "network is down") || + strings.Contains(errStr, "DNS lookup failed") || + strings.Contains(errStr, "read timeout") +} + +func (p *Player) playStreamURL(ctx context.Context, s *station.Station, streamURL string) error { + speaker.Clear() + p.setCurrentTrack("") + + log.Debug().Msgf("Connecting to stream: %s", streamURL) + + req, err := http.NewRequestWithContext(ctx, "GET", streamURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("User-Agent", fmt.Sprintf("SomaFM-CLI/%s", config.AppVersion)) + req.Header.Set("Icy-MetaData", "1") + + resp, err := p.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to fetch MP3 stream: %w", err) + } + + log.Debug().Msgf("Stream response status: %d, Content-Type: %s", resp.StatusCode, resp.Header.Get("Content-Type")) + + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + return fmt.Errorf("stream returned status %d: %s", resp.StatusCode, resp.Status) + } + + var icyMetaint int + if val := resp.Header.Get("icy-metaint"); val != "" { + _, _ = fmt.Sscanf(val, "%d", &icyMetaint) + log.Debug().Msgf("ICY metadata interval: %d bytes", icyMetaint) + } + + pipeReader, pipeWriter := io.Pipe() + + p.mu.Lock() + p.sampleCh = make(chan [2]float64, SampleChannelSize) + p.streamDone = make(chan struct{}) + p.streamDoneOnce = sync.Once{} // Reset for new stream + p.streamErr = make(chan error, 1) + p.mu.Unlock() + + if len(p.buffer) > 0 { + p.bufferMu.Lock() + for i := range p.buffer { + p.buffer[i] = [2]float64{0, 0} + } + p.writeIdx = 0 + p.readBackOffset = 0 + p.bufferMu.Unlock() + } + + // Use context-aware reader for timeout detection + // The goroutine in contextReader will exit when context is cancelled + timeoutBody := &contextReader{ + reader: resp.Body, + ctx: ctx, + timeout: ReadTimeout, + } + + p.wg.Add(1) + go p.readNetworkStream(ctx, resp.Body, timeoutBody, pipeWriter, icyMetaint) + + log.Debug().Msg("Decoding MP3 stream...") + streamer, format, err := mp3.Decode(pipeReader) + if err != nil { + pipeReader.Close() + pipeWriter.Close() + resp.Body.Close() + return fmt.Errorf("failed to decode MP3 stream: %w", err) + } + + log.Debug().Msgf("Initializing audio output (sample rate: %d Hz)...", format.SampleRate) + if err := p.initSpeaker(format.SampleRate); err != nil { + streamer.Close() + pipeReader.Close() + pipeWriter.Close() + resp.Body.Close() + return fmt.Errorf("failed to initialize audio output: %w", err) + } + + p.mu.Lock() + p.format = format + p.mu.Unlock() + + p.streamAliveMu.Lock() + p.streamAlive = true + p.streamAliveMu.Unlock() + + p.mu.Lock() + p.currentStation = s + p.mu.Unlock() + + p.wg.Add(1) + go p.decodeAndBuffer(ctx, streamer, pipeReader, resp.Body) + + p.mu.Lock() + volumePercent := p.volumePercent + if volumePercent < 0 { + volumePercent = config.DefaultVolume + } + volumeLevel := percentToExponent(float64(volumePercent)) + + bufferedStreamer := &bufferedStreamerWrapper{player: p} + + p.volume = &effects.Volume{ + Streamer: bufferedStreamer, + Base: 2, + Volume: volumeLevel, + Silent: volumePercent == 0, + } + + p.ctrl = &beep.Ctrl{ + Streamer: p.volume, + Paused: false, + } + p.isPlaying = true + p.isPaused = false + p.mu.Unlock() + + speaker.Play(p.ctrl) + + p.setState(StatePlaying) + p.startSession() + + p.stateMu.Lock() + p.streamInfo.SampleRate = int(format.SampleRate) + p.stateMu.Unlock() + + log.Debug().Msgf("Now playing: %s (buffer: %ds)", s.Title, p.bufferSize) + + select { + case <-ctx.Done(): + speaker.Clear() + p.mu.Lock() + p.isPlaying = false + p.isPaused = false + p.mu.Unlock() + + p.closeStreamDone() + p.wg.Wait() + + return ctx.Err() + case err := <-p.streamErr: + speaker.Clear() + p.mu.Lock() + p.isPlaying = false + p.isPaused = false + p.mu.Unlock() + + p.closeStreamDone() + p.wg.Wait() + + return fmt.Errorf("stream error: %w", err) + case <-p.streamDone: + p.mu.Lock() + p.isPlaying = false + p.isPaused = false + p.mu.Unlock() + return fmt.Errorf("stream ended unexpectedly") + } +} + +func (p *Player) readNetworkStream(ctx context.Context, respBody io.ReadCloser, bodyReader io.Reader, pipeWriter *io.PipeWriter, icyMetaint int) { + defer func() { + respBody.Close() + pipeWriter.Close() + p.wg.Done() + log.Debug().Msg("Network stream reader stopped") + }() + + reportError := func(err error) { + select { + case p.streamErr <- err: + default: + // Channel full or closed, error already reported + } + } + + chunkSize := int64(icyMetaint) + if chunkSize == 0 { + chunkSize = NetworkReadSize + } + + bufReader := bufio.NewReader(bodyReader) + + for { + select { + case <-ctx.Done(): + log.Debug().Msg("Network reader context cancelled") + return + case <-p.streamDone: + return + default: + _, err := io.CopyN(pipeWriter, bufReader, chunkSize) + if err != nil { + // Don't log errors during intentional shutdown (station switch) + if ctx.Err() != nil || errors.Is(err, io.ErrClosedPipe) || strings.Contains(err.Error(), "closed pipe") { + return + } + if err != io.EOF { + log.Error().Err(err).Msg("Error reading audio data from stream") + reportError(fmt.Errorf("network read error: %w", err)) + } + return + } + + if icyMetaint > 0 { + metaLenByte, err := bufReader.ReadByte() + if err != nil { + if ctx.Err() != nil || err == io.EOF { + return + } + log.Error().Err(err).Msg("Error reading metadata length") + reportError(fmt.Errorf("metadata read error: %w", err)) + return + } + + metaLen := int(metaLenByte) * 16 + if metaLen > 0 { + metaData := make([]byte, metaLen) + n, err := io.ReadFull(bufReader, metaData) + if err != nil { + if ctx.Err() != nil { + return + } + log.Error().Err(err).Msg("Error reading metadata content") + reportError(fmt.Errorf("metadata content error: %w", err)) + return + } + + metaStr := string(metaData[:n]) + if strings.Contains(metaStr, "StreamTitle='") { + start := strings.Index(metaStr, "StreamTitle='") + len("StreamTitle='") + end := strings.Index(metaStr[start:], "';") + if end > 0 { + title := metaStr[start : start+end] + p.setCurrentTrack(title) + } + } + } + } + } + } +} + +func (p *Player) decodeAndBuffer(ctx context.Context, streamer beep.StreamSeekCloser, pipeReader *io.PipeReader, respBody io.ReadCloser) { + defer func() { + streamer.Close() + pipeReader.Close() + close(p.sampleCh) + p.wg.Done() + + p.streamAliveMu.Lock() + p.streamAlive = false + p.streamAliveMu.Unlock() + + log.Debug().Msg("Decoder and buffer goroutine stopped") + + if ctx.Err() == nil { + p.mu.Lock() + station := p.currentStation + stationID := "" + if station != nil { + stationID = station.ID + } + shouldReconnect := p.isPlaying && !p.isPaused + p.mu.Unlock() + + if shouldReconnect && station != nil { + log.Info().Msg("Stream ended unexpectedly, auto-reconnecting...") + go func() { + p.setState(StateReconnecting) + p.Stop() + + // Verify station hasn't changed during reconnect + p.mu.Lock() + currentStation := p.currentStation + stationChanged := currentStation == nil || currentStation.ID != stationID + p.mu.Unlock() + + if stationChanged { + log.Debug().Msg("Station changed during reconnect, aborting") + return + } + + if err := p.Play(station); err != nil { + log.Error().Err(err).Msg("Auto-reconnect failed") + p.setState(StateError) + p.setLastError("Reconnection failed") + } + }() + } + } + }() + + decodedSamples := make([][2]float64, 4096) + + for { + select { + case <-ctx.Done(): + return + case <-p.streamDone: + return + default: + n, ok := streamer.Stream(decodedSamples) + if !ok { + if err := streamer.Err(); err != nil { + log.Error().Err(err).Msg("Stream decoding error") + } + return + } + + for i := 0; i < n; i++ { + sample := decodedSamples[i] + + select { + case <-ctx.Done(): + return + case <-p.streamDone: + return + case p.sampleCh <- sample: + if len(p.buffer) > 0 { + p.bufferMu.Lock() + idx := p.writeIdx % int64(len(p.buffer)) + p.buffer[idx] = sample + p.writeIdx++ + p.bufferMu.Unlock() + } + } + } + } + } +} + +type bufferedStreamerWrapper struct { + player *Player +} + +func (b *bufferedStreamerWrapper) Stream(samples [][2]float64) (n int, ok bool) { + p := b.player + i := 0 + + if len(p.buffer) > 0 { + i = b.readFromBuffer(samples) + if i == len(samples) { + return i, true + } + } + + return b.readFromChannel(samples, i) +} + +func (b *bufferedStreamerWrapper) readFromBuffer(samples [][2]float64) int { + p := b.player + i := 0 + + p.bufferMu.Lock() + defer p.bufferMu.Unlock() + + for p.readBackOffset < 0 && i < len(samples) { + bufLen := int64(len(p.buffer)) + idx := (p.writeIdx + int64(p.readBackOffset) + bufLen) % bufLen + + if idx >= p.writeIdx { + break + } + + samples[i] = p.buffer[idx] + p.readBackOffset++ + i++ + } + + return i +} + +func (b *bufferedStreamerWrapper) readFromChannel(samples [][2]float64, startIdx int) (n int, ok bool) { + p := b.player + i := startIdx + + for i < len(samples) { + select { + case sample, more := <-p.sampleCh: + if !more { + return i, i > 0 + } + samples[i] = sample + i++ + case <-p.streamDone: + return i, i > 0 + } + } + + return len(samples), len(samples) > 0 +} + +func (b *bufferedStreamerWrapper) Err() error { + return nil +} + +func (p *Player) fetchAndParsePLS(ctx context.Context, plsURL string) ([]string, error) { + req, err := http.NewRequestWithContext(ctx, "GET", plsURL, nil) + if err != nil { + return nil, fmt.Errorf("failed to create PLS request: %w", err) + } + + resp, err := p.httpClient.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch PLS file: %w", err) + } + defer resp.Body.Close() + + var urls []string + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "File") && strings.Contains(line, "=") { + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + url := strings.TrimSpace(parts[1]) + if url != "" { + urls = append(urls, url) + } + } + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("error reading PLS file: %w", err) + } + + if len(urls) == 0 { + return nil, fmt.Errorf("no valid stream URL found in PLS file") + } + + return urls, nil +} + +// parseStreamInfoFromURL extracts format and bitrate from SomaFM playlist URLs. +// URL patterns: groovesalad130.pls (MP3 128k), groovesalad-aac.pls (AAC), etc. +func parseStreamInfoFromURL(url string) StreamInfo { + info := StreamInfo{ + Format: "MP3", + Quality: "high", + Bitrate: 128, + SampleRate: 44100, + } + + urlLower := strings.ToLower(url) + + if strings.Contains(urlLower, "aac") || strings.Contains(urlLower, "aacp") { + info.Format = "AAC" + } + + bitrates := []int{320, 256, 192, 130, 128, 64, 32} + for _, br := range bitrates { + brStr := fmt.Sprintf("%d", br) + if strings.Contains(url, brStr+".pls") || strings.Contains(url, brStr+".") { + info.Bitrate = br + if br == 130 { // SomaFM uses 130 for 128kbps streams + info.Bitrate = 128 + } + break + } + } + + switch { + case info.Bitrate >= 256: + info.Quality = "highest" + case info.Bitrate >= 128: + info.Quality = "high" + case info.Bitrate >= 64: + info.Quality = "medium" + default: + info.Quality = "low" + } + + return info +} diff --git a/internal/player/player_test.go b/internal/player/player_test.go new file mode 100644 index 0000000..d3e7bab --- /dev/null +++ b/internal/player/player_test.go @@ -0,0 +1,503 @@ +package player + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + "time" +) + +func TestPercentToExponent(t *testing.T) { + tests := []struct { + percent float64 + expected float64 + }{ + {0, MinVolumeDB}, + {100, 0}, + {-10, MinVolumeDB}, + {150, 0}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("percent_%v", tt.percent), func(t *testing.T) { + result := percentToExponent(tt.percent) + if result != tt.expected { + t.Errorf("percentToExponent(%v) = %v, want %v", tt.percent, result, tt.expected) + } + }) + } +} + +func TestPercentToExponentCurve(t *testing.T) { + p25 := percentToExponent(25) + p50 := percentToExponent(50) + p75 := percentToExponent(75) + + if p25 >= p50 || p50 >= p75 { + t.Error("Volume curve should be monotonically increasing") + } + + if p25 <= MinVolumeDB || p75 >= 0 { + t.Error("Mid-range volumes should be between min and max") + } +} + +func TestPlayerStateString(t *testing.T) { + tests := []struct { + state PlayerState + expected string + }{ + {StateIdle, "IDLE"}, + {StateBuffering, "BUFFERING"}, + {StatePlaying, "LIVE"}, + {StatePaused, "PAUSED"}, + {StateReconnecting, "RECONNECTING"}, + {StateError, "ERROR"}, + {PlayerState(99), "UNKNOWN"}, + } + + for _, tt := range tests { + t.Run(tt.expected, func(t *testing.T) { + result := tt.state.String() + if result != tt.expected { + t.Errorf("PlayerState(%d).String() = %q, want %q", tt.state, result, tt.expected) + } + }) + } +} + +func TestNewPlayer(t *testing.T) { + tests := []struct { + bufferSeconds int + expectBuffer bool + }{ + {0, false}, + {5, true}, + {10, true}, + } + + for _, tt := range tests { + t.Run(fmt.Sprintf("buffer_%ds", tt.bufferSeconds), func(t *testing.T) { + p := NewPlayer(tt.bufferSeconds) + + if p == nil { + t.Fatal("NewPlayer returned nil") + } + + if tt.expectBuffer && len(p.buffer) == 0 { + t.Error("Expected buffer to be allocated") + } + + if !tt.expectBuffer && len(p.buffer) != 0 { + t.Error("Expected no buffer") + } + + if p.isPlaying { + t.Error("New player should not be playing") + } + + if p.isPaused { + t.Error("New player should not be paused") + } + }) + } +} + +func TestNewPlayerBufferSize(t *testing.T) { + p := NewPlayer(5) + expectedSize := int(DefaultSampleRate) * 5 + + if len(p.buffer) != expectedSize { + t.Errorf("Buffer size = %d, want %d", len(p.buffer), expectedSize) + } +} + +func TestIsNonRetryableError(t *testing.T) { + tests := []struct { + err error + expected bool + }{ + {errors.New("status 401"), true}, + {errors.New("status 403"), true}, + {errors.New("status 404"), true}, + {errors.New("status 410"), true}, + {errors.New("status 500"), false}, + {errors.New("connection refused"), false}, + {errors.New("timeout"), false}, + } + + for _, tt := range tests { + t.Run(tt.err.Error(), func(t *testing.T) { + result := isNonRetryableError(tt.err) + if result != tt.expected { + t.Errorf("isNonRetryableError(%q) = %v, want %v", tt.err, result, tt.expected) + } + }) + } +} + +func TestIsNetworkDownError(t *testing.T) { + tests := []struct { + err error + expected bool + }{ + {errors.New("no such host"), true}, + {errors.New("network is unreachable"), true}, + {errors.New("no route to host"), true}, + {errors.New("network is down"), true}, + {errors.New("DNS lookup failed"), true}, + {errors.New("read timeout"), true}, + {errors.New("connection refused"), false}, + {errors.New("status 500"), false}, + } + + for _, tt := range tests { + t.Run(tt.err.Error(), func(t *testing.T) { + result := isNetworkDownError(tt.err) + if result != tt.expected { + t.Errorf("isNetworkDownError(%q) = %v, want %v", tt.err, result, tt.expected) + } + }) + } +} + +func TestParseStreamInfoFromURL(t *testing.T) { + tests := []struct { + url string + expectedFormat string + expectedBitrate int + expectedQuality string + }{ + {"https://somafm.com/groovesalad130.pls", "MP3", 128, "high"}, + {"https://somafm.com/groovesalad256.pls", "MP3", 256, "highest"}, + {"https://somafm.com/groovesalad64.pls", "MP3", 64, "medium"}, + {"https://somafm.com/groovesalad32.pls", "MP3", 32, "low"}, + {"https://somafm.com/groovesalad.pls", "MP3", 128, "high"}, + {"https://somafm.com/groovesalad-aac.pls", "AAC", 128, "high"}, + {"https://somafm.com/groovesalad-aacp64.pls", "AAC", 64, "medium"}, + {"https://somafm.com/station320.pls", "MP3", 320, "highest"}, + } + + for _, tt := range tests { + t.Run(tt.url, func(t *testing.T) { + info := parseStreamInfoFromURL(tt.url) + + if info.Format != tt.expectedFormat { + t.Errorf("Format = %q, want %q", info.Format, tt.expectedFormat) + } + if info.Bitrate != tt.expectedBitrate { + t.Errorf("Bitrate = %d, want %d", info.Bitrate, tt.expectedBitrate) + } + if info.Quality != tt.expectedQuality { + t.Errorf("Quality = %q, want %q", info.Quality, tt.expectedQuality) + } + }) + } +} + +func TestPlayerTrackManagement(t *testing.T) { + p := NewPlayer(0) + + initial := p.GetCurrentTrack() + if initial != "Waiting for track info..." { + t.Errorf("Initial track = %q, want 'Waiting for track info...'", initial) + } + + p.SetInitialTrack("Test Song - Test Artist") + track := p.GetCurrentTrack() + if track != "Test Song - Test Artist" { + t.Errorf("After SetInitialTrack, track = %q", track) + } + + p.SetInitialTrack("Should Not Override") + track = p.GetCurrentTrack() + if track != "Test Song - Test Artist" { + t.Errorf("SetInitialTrack should not override existing track, got %q", track) + } + + p.setCurrentTrack("New Track via ICY") + track = p.GetCurrentTrack() + if track != "New Track via ICY" { + t.Errorf("setCurrentTrack should override, got %q", track) + } +} + +func TestPlayerStateManagement(t *testing.T) { + p := NewPlayer(0) + + if p.GetState() != StateIdle { + t.Errorf("Initial state = %v, want StateIdle", p.GetState()) + } + + p.setState(StateBuffering) + if p.GetState() != StateBuffering { + t.Errorf("State after setState = %v, want StateBuffering", p.GetState()) + } + + p.setState(StatePlaying) + if p.GetState() != StatePlaying { + t.Errorf("State = %v, want StatePlaying", p.GetState()) + } +} + +func TestPlayerRetryInfo(t *testing.T) { + p := NewPlayer(0) + + current, max := p.GetRetryInfo() + if current != 0 || max != 0 { + t.Errorf("Initial retry info = (%d, %d), want (0, 0)", current, max) + } + + p.setRetryInfo(2, 5) + current, max = p.GetRetryInfo() + if current != 2 || max != 5 { + t.Errorf("Retry info = (%d, %d), want (2, 5)", current, max) + } +} + +func TestPlayerLastError(t *testing.T) { + p := NewPlayer(0) + + if p.GetLastError() != "" { + t.Errorf("Initial error = %q, want empty", p.GetLastError()) + } + + p.setLastError("Connection failed") + if p.GetLastError() != "Connection failed" { + t.Errorf("Error = %q, want 'Connection failed'", p.GetLastError()) + } +} + +func TestPlayerSessionDuration(t *testing.T) { + p := NewPlayer(0) + + if p.GetSessionDuration() != 0 { + t.Error("Initial session duration should be 0") + } + + p.startSession() + time.Sleep(10 * time.Millisecond) + + duration := p.GetSessionDuration() + if duration < 10*time.Millisecond { + t.Errorf("Session duration = %v, expected >= 10ms", duration) + } +} + +func TestPlayerBufferStats(t *testing.T) { + t.Run("no buffer", func(t *testing.T) { + p := NewPlayer(0) + + if p.GetBufferFillPercent() != 0 { + t.Error("Buffer fill should be 0 with no buffer") + } + if p.GetBufferHealth() != 0 { + t.Error("Buffer health should be 0 with no sample channel") + } + }) + + t.Run("with buffer", func(t *testing.T) { + p := NewPlayer(1) + + p.bufferMu.Lock() + p.writeIdx = int64(len(p.buffer) / 2) + p.bufferMu.Unlock() + + fill := p.GetBufferFillPercent() + if fill != 50 { + t.Errorf("Buffer fill = %d%%, want 50%%", fill) + } + }) +} + +func TestPlayerStreamInfo(t *testing.T) { + p := NewPlayer(0) + + info := p.GetStreamInfo() + if info.Format != "" || info.Bitrate != 0 { + t.Error("Initial stream info should be empty") + } + + p.setStreamInfo(StreamInfo{ + Format: "MP3", + Quality: "high", + Bitrate: 128, + SampleRate: 44100, + }) + + info = p.GetStreamInfo() + if info.Format != "MP3" || info.Bitrate != 128 { + t.Errorf("Stream info = %+v, expected MP3/128", info) + } +} + +func TestPlayerIsPlayingIsPaused(t *testing.T) { + p := NewPlayer(0) + + if p.IsPlaying() { + t.Error("New player should not be playing") + } + if p.IsPaused() { + t.Error("New player should not be paused") + } + + p.mu.Lock() + p.isPlaying = true + p.mu.Unlock() + + if !p.IsPlaying() { + t.Error("Player should be playing") + } + + p.mu.Lock() + p.isPaused = true + p.mu.Unlock() + + if p.IsPlaying() { + t.Error("Paused player should not report IsPlaying=true") + } + if !p.IsPaused() { + t.Error("Player should be paused") + } +} + +func TestFetchAndParsePLS(t *testing.T) { + plsContent := `[playlist] +NumberOfEntries=3 +File1=http://stream1.example.com/radio.mp3 +Title1=Stream 1 +File2=http://stream2.example.com/radio.mp3 +Title2=Stream 2 +File3=http://stream3.example.com/radio.mp3 +Title3=Stream 3 +` + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(plsContent)) + })) + defer server.Close() + + p := NewPlayer(0) + + ctx := context.Background() + urls, err := p.fetchAndParsePLS(ctx, server.URL) + if err != nil { + t.Fatalf("fetchAndParsePLS error: %v", err) + } + + if len(urls) != 3 { + t.Fatalf("Expected 3 URLs, got %d", len(urls)) + } + + expectedURLs := []string{ + "http://stream1.example.com/radio.mp3", + "http://stream2.example.com/radio.mp3", + "http://stream3.example.com/radio.mp3", + } + + for i, expected := range expectedURLs { + if urls[i] != expected { + t.Errorf("URL[%d] = %q, want %q", i, urls[i], expected) + } + } +} + +func TestFetchAndParsePLSEmpty(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte("[playlist]\nNumberOfEntries=0\n")) + })) + defer server.Close() + + p := NewPlayer(0) + ctx := context.Background() + + _, err := p.fetchAndParsePLS(ctx, server.URL) + if err == nil { + t.Error("Expected error for empty PLS file") + } +} + +func TestFetchAndParsePLSInvalidServer(t *testing.T) { + p := NewPlayer(0) + ctx := context.Background() + + _, err := p.fetchAndParsePLS(ctx, "http://invalid.invalid.invalid/test.pls") + if err == nil { + t.Error("Expected error for invalid server") + } +} + +func TestContextReader(t *testing.T) { + t.Run("successful read", func(t *testing.T) { + reader := strings.NewReader("test data") + ctx := context.Background() + cr := &contextReader{reader: reader, ctx: ctx, timeout: time.Second} + + buf := make([]byte, 100) + n, err := cr.Read(buf) + + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + if n != 9 { + t.Errorf("Read %d bytes, want 9", n) + } + if string(buf[:n]) != "test data" { + t.Errorf("Data = %q, want 'test data'", string(buf[:n])) + } + }) + + t.Run("timeout", func(t *testing.T) { + blockingReader := &blockingReader{} + ctx := context.Background() + cr := &contextReader{reader: blockingReader, ctx: ctx, timeout: 10 * time.Millisecond} + + buf := make([]byte, 100) + _, err := cr.Read(buf) + + if err == nil { + t.Error("Expected timeout error") + } + if !strings.Contains(err.Error(), "timeout") { + t.Errorf("Error = %q, expected to contain 'timeout'", err.Error()) + } + }) + + t.Run("context cancelled", func(t *testing.T) { + blockingReader := &blockingReader{} + ctx, cancel := context.WithCancel(context.Background()) + cr := &contextReader{reader: blockingReader, ctx: ctx, timeout: time.Hour} + + // Cancel context immediately + cancel() + + buf := make([]byte, 100) + _, err := cr.Read(buf) + + if err == nil { + t.Error("Expected context cancelled error") + } + if !errors.Is(err, context.Canceled) { + t.Errorf("Error = %v, expected context.Canceled", err) + } + }) +} + +type blockingReader struct{} + +func (b *blockingReader) Read(p []byte) (int, error) { + time.Sleep(time.Hour) + return 0, nil +} + +func TestGetCurrentStation(t *testing.T) { + p := NewPlayer(0) + + if p.GetCurrentStation() != nil { + t.Error("Initial station should be nil") + } +} diff --git a/internal/service/station_service.go b/internal/service/station_service.go new file mode 100644 index 0000000..ccdf800 --- /dev/null +++ b/internal/service/station_service.go @@ -0,0 +1,234 @@ +// Package service provides the business logic layer for managing station data. +package service + +import ( + "context" + "image" + _ "image/jpeg" + _ "image/png" + "net/http" + "sort" + "strconv" + "sync" + "time" + + "github.com/glebovdev/somafm-cli/internal/api" + "github.com/glebovdev/somafm-cli/internal/cache" + "github.com/glebovdev/somafm-cli/internal/station" + "github.com/rs/zerolog/log" +) + +const imageLoadTimeout = 15 * time.Second + +// StationService manages station data, including fetching, caching, and periodic refresh. +type StationService struct { + apiClient *api.SomaFMClient + stations []station.Station + mu sync.RWMutex + imageCache *cache.Cache + refreshTicker *time.Ticker + stopRefresh chan struct{} + onRefresh func([]station.Station) +} + +// NewStationService creates a new StationService with the given API client. +func NewStationService(apiClient *api.SomaFMClient) *StationService { + imageCache, err := cache.NewCache() + if err != nil { + log.Warn().Err(err).Msg("Failed to initialize image cache, images will not be cached") + } + + if imageCache != nil { + go func() { + if err := imageCache.CleanExpired(); err != nil { + log.Debug().Err(err).Msg("Failed to clean expired cache") + } + }() + } + + return &StationService{ + apiClient: apiClient, + imageCache: imageCache, + } +} + +func (s *StationService) GetStations() ([]station.Station, error) { + stations, err := s.apiClient.GetStations() + if err != nil { + return nil, err + } + + s.sortStationsByListeners(stations) + + s.mu.Lock() + s.stations = stations + s.mu.Unlock() + + return stations, nil +} + +func (s *StationService) GetCachedStations() []station.Station { + s.mu.RLock() + defer s.mu.RUnlock() + result := make([]station.Station, len(s.stations)) + copy(result, s.stations) + return result +} + +func (s *StationService) sortStationsByListeners(stations []station.Station) { + sort.Slice(stations, func(i, j int) bool { + listenersI, errI := strconv.Atoi(stations[i].Listeners) + listenersJ, errJ := strconv.Atoi(stations[j].Listeners) + + if errI != nil { + return false + } + if errJ != nil { + return true + } + + return listenersI > listenersJ + }) +} + +func (s *StationService) GetValidStationIDs() map[string]bool { + s.mu.RLock() + defer s.mu.RUnlock() + + validIDs := make(map[string]bool) + for _, st := range s.stations { + validIDs[st.ID] = true + } + return validIDs +} + +func (s *StationService) FindIndexByID(stationID string) int { + s.mu.RLock() + defer s.mu.RUnlock() + + for i, st := range s.stations { + if st.ID == stationID { + return i + } + } + return -1 +} + +func (s *StationService) StationCount() int { + s.mu.RLock() + defer s.mu.RUnlock() + return len(s.stations) +} + +// GetStation returns a copy of the station at the given index. +// Returns nil if the index is out of bounds. +// The returned station is a copy to prevent invalidation when the internal slice is refreshed. +func (s *StationService) GetStation(index int) *station.Station { + s.mu.RLock() + defer s.mu.RUnlock() + + if index < 0 || index >= len(s.stations) { + return nil + } + // Return a copy to prevent pointer invalidation on slice refresh + st := s.stations[index] + return &st +} + +func (s *StationService) LoadImage(url string) (image.Image, error) { + if s.imageCache != nil { + if img := s.imageCache.GetImage(url); img != nil { + log.Debug().Str("url", url).Msg("Image loaded from cache") + return img, nil + } + } + + ctx, cancel := context.WithTimeout(context.Background(), imageLoadTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + img, _, err := image.Decode(resp.Body) + if err != nil { + return nil, err + } + + if s.imageCache != nil { + go func() { + if err := s.imageCache.SaveImage(url, img); err != nil { + log.Debug().Err(err).Str("url", url).Msg("Failed to cache image") + } else { + log.Debug().Str("url", url).Msg("Image cached") + } + }() + } + + return img, nil +} + +func (s *StationService) GetCurrentTrackForStation(stationID string) (string, error) { + return s.apiClient.GetCurrentTrackForStation(stationID) +} + +func (s *StationService) StartPeriodicRefresh(interval time.Duration, callback func([]station.Station)) { + s.mu.Lock() + s.onRefresh = callback + s.stopRefresh = make(chan struct{}) + s.refreshTicker = time.NewTicker(interval) + s.mu.Unlock() + + go func() { + for { + select { + case <-s.refreshTicker.C: + s.refreshStationsInBackground() + case <-s.stopRefresh: + s.refreshTicker.Stop() + return + } + } + }() + + log.Debug().Dur("interval", interval).Msg("Started periodic station refresh") +} + +func (s *StationService) StopPeriodicRefresh() { + s.mu.Lock() + defer s.mu.Unlock() + + if s.stopRefresh != nil { + close(s.stopRefresh) + s.stopRefresh = nil + } + log.Debug().Msg("Stopped periodic station refresh") +} + +func (s *StationService) refreshStationsInBackground() { + newStations, err := s.apiClient.GetStations() + if err != nil { + log.Warn().Err(err).Msg("Background refresh failed, keeping cached data") + return + } + + s.sortStationsByListeners(newStations) + + s.mu.Lock() + s.stations = newStations + callback := s.onRefresh + s.mu.Unlock() + + if callback != nil { + callback(newStations) + } + + log.Debug().Int("count", len(s.stations)).Msg("Station data refreshed in background") +} diff --git a/internal/service/station_service_test.go b/internal/service/station_service_test.go new file mode 100644 index 0000000..719f16e --- /dev/null +++ b/internal/service/station_service_test.go @@ -0,0 +1,459 @@ +package service + +import ( + "image" + "image/color" + "image/png" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/glebovdev/somafm-cli/internal/cache" + "github.com/glebovdev/somafm-cli/internal/station" +) + +func TestSortStationsByListeners(t *testing.T) { + service := &StationService{} + + tests := []struct { + name string + stations []station.Station + expected []string // expected order of station IDs + }{ + { + name: "sort by listener count descending", + stations: []station.Station{ + {ID: "low", Listeners: "100"}, + {ID: "high", Listeners: "1000"}, + {ID: "mid", Listeners: "500"}, + }, + expected: []string{"high", "mid", "low"}, + }, + { + name: "handle invalid listener strings", + stations: []station.Station{ + {ID: "valid1", Listeners: "500"}, + {ID: "invalid", Listeners: "not-a-number"}, + {ID: "valid2", Listeners: "1000"}, + }, + expected: []string{"valid2", "valid1", "invalid"}, + }, + { + name: "handle empty listener strings", + stations: []station.Station{ + {ID: "valid", Listeners: "500"}, + {ID: "empty", Listeners: ""}, + }, + expected: []string{"valid", "empty"}, + }, + { + name: "handle single station", + stations: []station.Station{ + {ID: "only", Listeners: "100"}, + }, + expected: []string{"only"}, + }, + { + name: "handle empty list", + stations: []station.Station{}, + expected: []string{}, + }, + { + name: "handle equal listener counts", + stations: []station.Station{ + {ID: "first", Listeners: "500"}, + {ID: "second", Listeners: "500"}, + }, + expected: []string{"first", "second"}, + }, + { + name: "handle zero listeners", + stations: []station.Station{ + {ID: "zero", Listeners: "0"}, + {ID: "some", Listeners: "100"}, + }, + expected: []string{"some", "zero"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + stations := make([]station.Station, len(tt.stations)) + copy(stations, tt.stations) + + service.sortStationsByListeners(stations) + + if len(stations) != len(tt.expected) { + t.Fatalf("sortStationsByListeners resulted in %d stations, want %d", + len(stations), len(tt.expected)) + } + + for i, st := range stations { + if st.ID != tt.expected[i] { + t.Errorf("stations[%d].ID = %q, want %q", i, st.ID, tt.expected[i]) + } + } + }) + } +} + +func TestGetValidStationIDs(t *testing.T) { + service := &StationService{ + stations: []station.Station{ + {ID: "groovesalad"}, + {ID: "dronezone"}, + {ID: "lush"}, + }, + } + + validIDs := service.GetValidStationIDs() + + if len(validIDs) != 3 { + t.Fatalf("GetValidStationIDs() returned %d IDs, want 3", len(validIDs)) + } + + expectedIDs := []string{"groovesalad", "dronezone", "lush"} + for _, id := range expectedIDs { + if !validIDs[id] { + t.Errorf("GetValidStationIDs() missing %q", id) + } + } + + if validIDs["nonexistent"] { + t.Error("GetValidStationIDs() should return false for nonexistent ID") + } +} + +func TestGetValidStationIDsEmpty(t *testing.T) { + service := &StationService{ + stations: []station.Station{}, + } + + validIDs := service.GetValidStationIDs() + + if len(validIDs) != 0 { + t.Errorf("GetValidStationIDs() with empty stations returned %d IDs, want 0", len(validIDs)) + } +} + +func TestFindIndexByID(t *testing.T) { + service := &StationService{ + stations: []station.Station{ + {ID: "groovesalad"}, + {ID: "dronezone"}, + {ID: "lush"}, + }, + } + + tests := []struct { + name string + id string + expected int + }{ + {"first station", "groovesalad", 0}, + {"middle station", "dronezone", 1}, + {"last station", "lush", 2}, + {"nonexistent station", "notfound", -1}, + {"empty string", "", -1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := service.FindIndexByID(tt.id) + if result != tt.expected { + t.Errorf("FindIndexByID(%q) = %d, want %d", tt.id, result, tt.expected) + } + }) + } +} + +func TestFindIndexByIDEmptyList(t *testing.T) { + service := &StationService{ + stations: []station.Station{}, + } + + result := service.FindIndexByID("anything") + if result != -1 { + t.Errorf("FindIndexByID with empty list = %d, want -1", result) + } +} + +func TestGetStation(t *testing.T) { + stations := []station.Station{ + {ID: "groovesalad", Title: "Groove Salad"}, + {ID: "dronezone", Title: "Drone Zone"}, + {ID: "lush", Title: "Lush"}, + } + + service := &StationService{ + stations: stations, + } + + tests := []struct { + name string + index int + expectedID string + expectedNil bool + }{ + {"first station", 0, "groovesalad", false}, + {"middle station", 1, "dronezone", false}, + {"last station", 2, "lush", false}, + {"negative index", -1, "", true}, + {"index out of bounds", 3, "", true}, + {"index way out of bounds", 100, "", true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := service.GetStation(tt.index) + + if tt.expectedNil { + if result != nil { + t.Errorf("GetStation(%d) = %v, want nil", tt.index, result) + } + } else { + if result == nil { + t.Fatalf("GetStation(%d) = nil, want station", tt.index) + } + if result.ID != tt.expectedID { + t.Errorf("GetStation(%d).ID = %q, want %q", tt.index, result.ID, tt.expectedID) + } + } + }) + } +} + +func TestGetStationEmptyList(t *testing.T) { + service := &StationService{ + stations: []station.Station{}, + } + + result := service.GetStation(0) + if result != nil { + t.Errorf("GetStation(0) with empty list = %v, want nil", result) + } +} + +func TestStationCount(t *testing.T) { + tests := []struct { + name string + stations []station.Station + expected int + }{ + { + name: "multiple stations", + stations: []station.Station{ + {ID: "a"}, {ID: "b"}, {ID: "c"}, + }, + expected: 3, + }, + { + name: "empty list", + stations: []station.Station{}, + expected: 0, + }, + { + name: "single station", + stations: []station.Station{ + {ID: "only"}, + }, + expected: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + service := &StationService{stations: tt.stations} + result := service.StationCount() + if result != tt.expected { + t.Errorf("StationCount() = %d, want %d", result, tt.expected) + } + }) + } +} + +func TestGetCachedStations(t *testing.T) { + expectedStations := []station.Station{ + {ID: "groovesalad", Title: "Groove Salad"}, + {ID: "dronezone", Title: "Drone Zone"}, + } + + service := &StationService{ + stations: expectedStations, + } + + result := service.GetCachedStations() + + if len(result) != len(expectedStations) { + t.Fatalf("GetCachedStations() returned %d stations, want %d", + len(result), len(expectedStations)) + } + + for i, st := range result { + if st.ID != expectedStations[i].ID { + t.Errorf("GetCachedStations()[%d].ID = %q, want %q", + i, st.ID, expectedStations[i].ID) + } + } +} + +func TestGetCachedStationsEmpty(t *testing.T) { + service := &StationService{ + stations: []station.Station{}, + } + + result := service.GetCachedStations() + + if len(result) != 0 { + t.Errorf("GetCachedStations() with empty list returned %d stations, want 0", + len(result)) + } +} + +func TestNewStationService(t *testing.T) { + service := NewStationService(nil) + + if service == nil { + t.Fatal("NewStationService() returned nil") + } + + if service.StationCount() != 0 { + t.Errorf("NewStationService() created service with %d stations, want 0", + service.StationCount()) + } +} + +func TestLoadImage(t *testing.T) { + img := image.NewRGBA(image.Rect(0, 0, 100, 100)) + for y := 0; y < 100; y++ { + for x := 0; x < 100; x++ { + img.Set(x, y, color.RGBA{R: 255, G: 0, B: 0, A: 255}) + } + } + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + _ = png.Encode(w, img) + })) + defer server.Close() + + service := &StationService{ + imageCache: nil, + } + + loadedImg, err := service.LoadImage(server.URL + "/test.png") + if err != nil { + t.Fatalf("LoadImage() error = %v", err) + } + + if loadedImg == nil { + t.Fatal("LoadImage() returned nil image") + } + + bounds := loadedImg.Bounds() + if bounds.Dx() != 100 || bounds.Dy() != 100 { + t.Errorf("LoadImage() returned image with size %dx%d, want 100x100", + bounds.Dx(), bounds.Dy()) + } +} + +func TestLoadImageInvalidURL(t *testing.T) { + service := &StationService{ + imageCache: nil, + } + + _, err := service.LoadImage("http://invalid.invalid.invalid/image.png") + if err == nil { + t.Error("LoadImage() should return error for invalid URL") + } +} + +func TestLoadImageWithCache(t *testing.T) { + img := image.NewRGBA(image.Rect(0, 0, 50, 50)) + for y := 0; y < 50; y++ { + for x := 0; x < 50; x++ { + img.Set(x, y, color.RGBA{R: 0, G: 255, B: 0, A: 255}) + } + } + + requestCount := 0 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + requestCount++ + w.Header().Set("Content-Type", "image/png") + _ = png.Encode(w, img) + })) + defer server.Close() + + imageCache, err := cache.NewCache() + if err != nil { + t.Skipf("Could not create cache: %v", err) + } + + service := &StationService{ + imageCache: imageCache, + } + + testURL := server.URL + "/test-cache.png" + + loadedImg1, err := service.LoadImage(testURL) + if err != nil { + t.Fatalf("First LoadImage() error = %v", err) + } + + if loadedImg1 == nil { + t.Fatal("First LoadImage() returned nil") + } + + if requestCount != 1 { + t.Errorf("Expected 1 HTTP request, got %d", requestCount) + } + + time.Sleep(100 * time.Millisecond) + + loadedImg2, err := service.LoadImage(testURL) + if err != nil { + t.Fatalf("Second LoadImage() error = %v", err) + } + + if loadedImg2 == nil { + t.Fatal("Second LoadImage() returned nil") + } + + if requestCount != 1 { + t.Logf("Got %d HTTP requests (cache may not be working)", requestCount) + } +} + +func TestLoadImageInvalidResponse(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + _, _ = w.Write([]byte("not a valid image")) + })) + defer server.Close() + + service := &StationService{ + imageCache: nil, + } + + _, err := service.LoadImage(server.URL + "/test.png") + if err == nil { + t.Error("LoadImage() should return error for invalid image data") + } +} + +func TestStartAndStopPeriodicRefresh(t *testing.T) { + service := &StationService{} + + callback := func(stations []station.Station) {} + + service.StartPeriodicRefresh(50*time.Millisecond, callback) + time.Sleep(10 * time.Millisecond) + service.StopPeriodicRefresh() + service.StopPeriodicRefresh() +} + +func TestStopPeriodicRefreshBeforeStart(t *testing.T) { + service := &StationService{} + service.StopPeriodicRefresh() +} diff --git a/internal/station/station.go b/internal/station/station.go new file mode 100644 index 0000000..4617703 --- /dev/null +++ b/internal/station/station.go @@ -0,0 +1,67 @@ +// Package station defines the data structures for SomaFM radio stations. +package station + +// Playlist represents a streaming endpoint for a radio station. +type Playlist struct { + URL string `json:"url"` + Format string `json:"format"` // Audio format (e.g., "mp3", "aac") + Quality string `json:"quality"` // Quality level (e.g., "highest", "high") +} + +// Station represents a SomaFM radio station with its metadata and streaming options. +type Station struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + DJ string `json:"dj"` + DJMail string `json:"djmail"` + Genre string `json:"genre"` // Pipe-separated genre list + Image string `json:"image"` + LargeImage string `json:"largeimage"` + XLImage string `json:"xlimage"` + Twitter string `json:"twitter"` + Updated string `json:"updated"` + Playlists []Playlist `json:"playlists"` + Preroll []string `json:"preroll"` + Listeners string `json:"listeners"` + LastPlaying string `json:"lastPlaying"` +} + +// GetBestPlaylistURL returns the URL of the highest quality MP3 playlist. +// Falls back to the first available playlist if no MP3 "highest" quality is found. +func (s *Station) GetBestPlaylistURL() string { + for _, playlist := range s.Playlists { + if playlist.Format == "mp3" && playlist.Quality == "highest" { + return playlist.URL + } + } + if len(s.Playlists) > 0 { + return s.Playlists[0].URL + } + return "" +} + +// GetAllPlaylistURLs returns all playlist URLs sorted by preference: +// MP3 highest quality first, then other MP3, then other formats. +func (s *Station) GetAllPlaylistURLs() []string { + var mp3Highest, mp3Other, other []string + + for _, playlist := range s.Playlists { + if playlist.Format == "mp3" { + if playlist.Quality == "highest" { + mp3Highest = append(mp3Highest, playlist.URL) + } else { + mp3Other = append(mp3Other, playlist.URL) + } + } else { + other = append(other, playlist.URL) + } + } + + result := make([]string, 0, len(s.Playlists)) + result = append(result, mp3Highest...) + result = append(result, mp3Other...) + result = append(result, other...) + + return result +} diff --git a/internal/station/station_test.go b/internal/station/station_test.go new file mode 100644 index 0000000..03aa9b1 --- /dev/null +++ b/internal/station/station_test.go @@ -0,0 +1,186 @@ +package station + +import ( + "testing" +) + +func TestGetBestPlaylistURL(t *testing.T) { + tests := []struct { + name string + station Station + expected string + }{ + { + name: "Returns mp3 highest quality", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/low.pls", Format: "mp3", Quality: "low"}, + {URL: "http://example.com/high.pls", Format: "mp3", Quality: "highest"}, + {URL: "http://example.com/med.pls", Format: "aac", Quality: "high"}, + }, + }, + expected: "http://example.com/high.pls", + }, + { + name: "Returns first playlist when no mp3 highest", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/first.pls", Format: "aac", Quality: "high"}, + {URL: "http://example.com/second.pls", Format: "mp3", Quality: "low"}, + }, + }, + expected: "http://example.com/first.pls", + }, + { + name: "Returns empty string when no playlists", + station: Station{ + Playlists: []Playlist{}, + }, + expected: "", + }, + { + name: "Returns single playlist", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/only.pls", Format: "mp3", Quality: "high"}, + }, + }, + expected: "http://example.com/only.pls", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.station.GetBestPlaylistURL() + if result != tt.expected { + t.Errorf("GetBestPlaylistURL() = %v, want %v", result, tt.expected) + } + }) + } +} + +func TestGetAllPlaylistURLs(t *testing.T) { + tests := []struct { + name string + station Station + expected []string + }{ + { + name: "Prioritizes mp3 highest, then mp3 other, then other formats", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/aac-high.pls", Format: "aac", Quality: "high"}, + {URL: "http://example.com/mp3-low.pls", Format: "mp3", Quality: "low"}, + {URL: "http://example.com/mp3-highest.pls", Format: "mp3", Quality: "highest"}, + {URL: "http://example.com/ogg-med.pls", Format: "ogg", Quality: "medium"}, + }, + }, + expected: []string{ + "http://example.com/mp3-highest.pls", + "http://example.com/mp3-low.pls", + "http://example.com/aac-high.pls", + "http://example.com/ogg-med.pls", + }, + }, + { + name: "Multiple mp3 highest quality", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/mp3-highest-1.pls", Format: "mp3", Quality: "highest"}, + {URL: "http://example.com/mp3-highest-2.pls", Format: "mp3", Quality: "highest"}, + }, + }, + expected: []string{ + "http://example.com/mp3-highest-1.pls", + "http://example.com/mp3-highest-2.pls", + }, + }, + { + name: "Only non-mp3 formats", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/aac.pls", Format: "aac", Quality: "high"}, + {URL: "http://example.com/ogg.pls", Format: "ogg", Quality: "medium"}, + }, + }, + expected: []string{ + "http://example.com/aac.pls", + "http://example.com/ogg.pls", + }, + }, + { + name: "Empty playlists", + station: Station{ + Playlists: []Playlist{}, + }, + expected: []string{}, + }, + { + name: "Single playlist", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/only.pls", Format: "mp3", Quality: "high"}, + }, + }, + expected: []string{"http://example.com/only.pls"}, + }, + { + name: "Mp3 non-highest only", + station: Station{ + Playlists: []Playlist{ + {URL: "http://example.com/mp3-low.pls", Format: "mp3", Quality: "low"}, + {URL: "http://example.com/mp3-med.pls", Format: "mp3", Quality: "medium"}, + }, + }, + expected: []string{ + "http://example.com/mp3-low.pls", + "http://example.com/mp3-med.pls", + }, + }, + { + name: "Mixed with nil playlists field results in empty", + station: Station{ + Playlists: nil, + }, + expected: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.station.GetAllPlaylistURLs() + + if len(result) != len(tt.expected) { + t.Fatalf("GetAllPlaylistURLs() returned %d items, want %d: got %v", len(result), len(tt.expected), result) + } + + for i, url := range result { + if url != tt.expected[i] { + t.Errorf("GetAllPlaylistURLs()[%d] = %q, want %q", i, url, tt.expected[i]) + } + } + }) + } +} + +func TestStationFields(t *testing.T) { + station := Station{ + ID: "groovesalad", + Title: "Groove Salad", + Description: "A nicely chilled plate of ambient/downtempo beats and grooves.", + DJ: "Rusty Hodge", + Genre: "ambient|electronica", + Listeners: "1234", + LastPlaying: "Artist - Song Title", + } + + if station.ID != "groovesalad" { + t.Errorf("Station.ID = %q, want %q", station.ID, "groovesalad") + } + if station.Title != "Groove Salad" { + t.Errorf("Station.Title = %q, want %q", station.Title, "Groove Salad") + } + if station.Listeners != "1234" { + t.Errorf("Station.Listeners = %q, want %q", station.Listeners, "1234") + } +} diff --git a/internal/ui/footer.go b/internal/ui/footer.go new file mode 100644 index 0000000..e7a96f9 --- /dev/null +++ b/internal/ui/footer.go @@ -0,0 +1,318 @@ +package ui + +import ( + "fmt" + + "github.com/gdamore/tcell/v2" + "github.com/glebovdev/somafm-cli/internal/player" + "github.com/rivo/tview" +) + +type StatusRenderer struct { + player *player.Player + isMuted bool + animFrame int + maxAnimFrame int + tickCount int + ticksPerFrame int + + bufferHealth int + bufferTickCount int + bufferTicksPerUpdate int + + primaryColor string +} + +func NewStatusRenderer(p *player.Player) *StatusRenderer { + return &StatusRenderer{ + player: p, + maxAnimFrame: 4, + ticksPerFrame: 8, // Slow down animation (8 ticks per frame) + bufferTicksPerUpdate: 16, // Update buffer ~1 per second (16 * 60ms ≈ 960ms) + } +} + +func (s *StatusRenderer) SetMuted(muted bool) { + s.isMuted = muted +} + +func (s *StatusRenderer) SetPrimaryColor(color string) { + s.primaryColor = color +} + +func (s *StatusRenderer) AdvanceAnimation() { + s.tickCount++ + if s.tickCount >= s.ticksPerFrame { + s.tickCount = 0 + s.animFrame = (s.animFrame + 1) % s.maxAnimFrame + } + + s.bufferTickCount++ + if s.bufferTickCount >= s.bufferTicksPerUpdate { + s.bufferTickCount = 0 + if s.player != nil { + s.bufferHealth = s.player.GetBufferHealth() + } + } +} + +func (s *StatusRenderer) Render() string { + if s.player == nil { + return s.renderIdle() + } + + state := s.player.GetState() + + switch state { + case player.StateIdle: + return s.renderIdle() + case player.StateBuffering: + return s.renderBuffering() + case player.StatePlaying: + return s.renderPlaying() + case player.StatePaused: + return s.renderPaused() + case player.StateReconnecting: + return s.renderReconnecting() + case player.StateError: + return s.renderError() + default: + return s.renderIdle() + } +} + +func (s *StatusRenderer) renderIdle() string { + if s.isMuted { + return "○ IDLE │ [red]MUTED[-] │ Select a station" + } + return "○ IDLE │ Select a station" +} + +func (s *StatusRenderer) renderBuffering() string { + circles := []string{"◐", "◓", "◑", "◒"} + return fmt.Sprintf("%s BUFFERING", circles[s.animFrame]) +} + +func (s *StatusRenderer) renderPlaying() string { + dots := []string{"●", "◉", "○", "◉"} + dot := dots[s.animFrame] + + if s.primaryColor != "" { + dot = fmt.Sprintf("[%s]%s[-]", s.primaryColor, dot) + } + + parts := []string{dot + " LIVE"} + + if s.isMuted { + parts = append(parts, "[red]MUTED[-]") + } + + streamInfo := s.player.GetStreamInfo() + if streamInfo.Format != "" { + sampleRateKHz := float64(streamInfo.SampleRate) / 1000.0 + parts = append(parts, fmt.Sprintf("%s %s %dk %.1fkHz", + streamInfo.Format, + qualityShort(streamInfo.Quality), + streamInfo.Bitrate, + sampleRateKHz)) + } + + parts = append(parts, s.formatBufferHealth(s.bufferHealth)) + + return joinParts(parts) +} + +func (s *StatusRenderer) renderPaused() string { + parts := []string{"⏸︎ PAUSED"} + + if s.isMuted { + parts = append(parts, "[red]MUTED[-]") + } + + streamInfo := s.player.GetStreamInfo() + if streamInfo.Format != "" { + sampleRateKHz := float64(streamInfo.SampleRate) / 1000.0 + parts = append(parts, fmt.Sprintf("%s %s %dk %.1fkHz", + streamInfo.Format, + qualityShort(streamInfo.Quality), + streamInfo.Bitrate, + sampleRateKHz)) + } + + return joinParts(parts) +} + +func (s *StatusRenderer) renderReconnecting() string { + current, max := s.player.GetRetryInfo() + return fmt.Sprintf("↻ RETRY %d/%d", current, max) +} + +func (s *StatusRenderer) renderError() string { + errMsg := s.player.GetLastError() + if errMsg == "" { + errMsg = "ERROR" + } + return fmt.Sprintf("✗ %s", errMsg) +} + +func (s *StatusRenderer) formatBufferHealth(percent int) string { + signalBars := []string{"▁", "▂", "▃", "▅", "▇"} + const numBars = 5 + + filled := (percent * numBars) / 100 + if filled > numBars { + filled = numBars + } + + bar := "" + for i := 0; i < numBars; i++ { + if i < filled { + bar += signalBars[i] + } else { + bar += "▁" + } + } + + return bar +} + +func qualityShort(quality string) string { + switch quality { + case "highest", "high": + return "HQ" + case "medium": + return "MQ" + case "low": + return "LQ" + default: + return "" + } +} + +func joinParts(parts []string) string { + if len(parts) == 0 { + return "" + } + result := parts[0] + for i := 1; i < len(parts); i++ { + result += " │ " + parts[i] + } + return result +} + +func (ui *UI) getPlaybackHint(keyColor string) string { + state := ui.player.GetState() + + switch state { + case player.StatePaused: + return fmt.Sprintf("[%s]Enter[-] play [%s]Space[-] resume", keyColor, keyColor) + case player.StatePlaying, player.StateBuffering, player.StateReconnecting: + return fmt.Sprintf("[%s]Enter[-] play [%s]Space[-] pause", keyColor, keyColor) + default: + return fmt.Sprintf("[%s]Space[-] play", keyColor) + } +} + +func (ui *UI) getHelpText() string { + keyColor := ui.colors.helpHotkey.String() + playbackHint := ui.getPlaybackHint(keyColor) + + muteText := "mute" + if ui.isMuted { + muteText = "unmute" + } + + return fmt.Sprintf(" %s [%s]+/-[-] vol [%s]m[-] %s [%s]?[-] help [%s]a[-] about [%s]q[-] quit ", + playbackHint, keyColor, keyColor, muteText, keyColor, keyColor, keyColor) +} + +func (ui *UI) handleFooterResize(width int) { + isWide := width >= FooterBreakpoint + wasWide := ui.lastFooterWidth >= FooterBreakpoint + + if ui.lastFooterWidth > 0 && isWide != wasWide && ui.contentLayout != nil { + newHeight := FooterHeightWide + if !isWide { + newHeight = FooterHeightNarrow + } + ui.contentLayout.ResizeItem(ui.helpPanel, newHeight, 0) + } + ui.lastFooterWidth = width +} + +func (ui *UI) drawWideFooter(screen tcell.Screen, x, y, width, height int, helpText, statusText string) { + helpWidth := width / 2 + statusWidth := width - helpWidth + + for row := y; row < y+height; row++ { + for col := x; col < x+helpWidth; col++ { + screen.SetContent(col, row, ' ', nil, tcell.StyleDefault.Background(ui.colors.helpBackground)) + } + } + + for row := y; row < y+height; row++ { + for col := x + helpWidth; col < x+width; col++ { + screen.SetContent(col, row, ' ', nil, tcell.StyleDefault.Background(ui.colors.background)) + } + } + + centerY := y + height/2 + tview.Print(screen, helpText, x, centerY, helpWidth, tview.AlignCenter, ui.colors.helpForeground) + tview.Print(screen, statusText, x+helpWidth, centerY, statusWidth-2, tview.AlignRight, ui.colors.foreground) +} + +func (ui *UI) drawNarrowFooter(screen tcell.Screen, x, y, width, height int, helpText, statusText string) { + helpHeight := height / 2 + if helpHeight < 1 { + helpHeight = 1 + } + statusHeight := height - helpHeight + helpBoxEnd := y + helpHeight + + for row := y; row < helpBoxEnd; row++ { + for col := x; col < x+width; col++ { + screen.SetContent(col, row, ' ', nil, tcell.StyleDefault.Background(ui.colors.helpBackground)) + } + } + + for row := helpBoxEnd; row < y+height; row++ { + for col := x; col < x+width; col++ { + screen.SetContent(col, row, ' ', nil, tcell.StyleDefault.Background(ui.colors.background)) + } + } + + helpTextY := y + helpHeight/2 + tview.Print(screen, helpText, x, helpTextY, width, tview.AlignCenter, ui.colors.helpForeground) + + if statusHeight > 0 { + statusTextY := helpBoxEnd + statusHeight/2 + tview.Print(screen, statusText, x, statusTextY, width-2, tview.AlignRight, ui.colors.foreground) + } +} + +func (ui *UI) createFooter() *tview.Box { + box := tview.NewBox().SetBackgroundColor(ui.colors.background) + + box.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { + ui.handleFooterResize(width) + + helpText := ui.getHelpText() + statusText := " " + ui.statusRenderer.Render() + " " + + isWide := width >= FooterBreakpoint + usedHeight := height + if isWide && height > FooterHeightWide { + usedHeight = FooterHeightWide + } + + if isWide { + ui.drawWideFooter(screen, x, y, width, usedHeight, helpText, statusText) + } else { + ui.drawNarrowFooter(screen, x, y, width, height, helpText, statusText) + } + + return x, y, width, height + }) + + return box +} diff --git a/internal/ui/modals.go b/internal/ui/modals.go new file mode 100644 index 0000000..1287d8c --- /dev/null +++ b/internal/ui/modals.go @@ -0,0 +1,412 @@ +package ui + +import ( + "fmt" + "strings" + + "github.com/gdamore/tcell/v2" + "github.com/glebovdev/somafm-cli/internal/config" + "github.com/rivo/tview" +) + +func extractErrorReason(err error) string { + errStr := err.Error() + + // Common network errors + if strings.Contains(errStr, "no such host") { + return "Unable to connect to server.\nPlease check your internet connection." + } + if strings.Contains(errStr, "connection refused") { + return "Connection refused by server.\nThe service may be temporarily unavailable." + } + if strings.Contains(errStr, "timeout") || strings.Contains(errStr, "deadline exceeded") { + return "Connection timed out.\nPlease check your internet connection." + } + if strings.Contains(errStr, "network is unreachable") { + return "Network is unreachable.\nPlease check your internet connection." + } + if strings.Contains(errStr, "status 401") { + return "Stream access denied (401).\nTrying alternative servers..." + } + if strings.Contains(errStr, "status 403") { + return "Stream access forbidden (403)." + } + if strings.Contains(errStr, "status 404") { + return "Stream not found (404)." + } + + if idx := strings.Index(errStr, ": dial"); idx > 0 { + return errStr[:idx] + } + if len(errStr) > 100 { + return errStr[:100] + "..." + } + return errStr +} + +func (ui *UI) showErrorModal(title, message string, onDismiss func()) { + doDismiss := func() { + ui.pages.RemovePage("modal") + ui.app.SetFocus(ui.stationList) + if onDismiss != nil { + onDismiss() + } + } + + messageView := tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText(fmt.Sprintf("\n[::b]%s[::-]\n\n%s", title, message)) + messageView.SetTextColor(ui.colors.foreground) + messageView.SetBackgroundColor(ui.colors.modalBackground) + + hintView := tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText("[::d]Press Enter or Esc to continue[::-]") + hintView.SetTextColor(tcell.ColorDarkGray) + hintView.SetBackgroundColor(ui.colors.modalBackground) + + content := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(messageView, 0, 1, false). + AddItem(hintView, 1, 0, false). + AddItem(nil, 1, 0, false) + content.SetBackgroundColor(ui.colors.modalBackground) + + frame := tview.NewFrame(content). + SetBorders(0, 0, 1, 1, 1, 1) + frame.SetBorder(true). + SetBorderColor(ui.colors.highlight). + SetBackgroundColor(ui.colors.modalBackground). + SetTitle(" Error "). + SetTitleColor(ui.colors.highlight). + SetTitleAlign(tview.AlignCenter) + + modalWidth := 50 + modalHeight := 10 + + lines := strings.Count(message, "\n") + 1 + if lines > 2 { + modalHeight += lines - 2 + } + if modalHeight > 15 { + modalHeight = 15 + } + + modal := tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(frame, modalHeight, 0, true). + AddItem(nil, 0, 1, false), + modalWidth, 0, true). + AddItem(nil, 0, 1, false) + modal.SetBackgroundColor(ui.colors.background) + + modal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyEnter, tcell.KeyEscape: + doDismiss() + return nil + } + return event + }) + + ui.pages.AddPage("modal", modal, true, true) + ui.app.SetFocus(modal) +} + +func (ui *UI) showError(message string) { + friendlyMsg := message + if strings.HasPrefix(message, "Failed to play station: ") { + errPart := strings.TrimPrefix(message, "Failed to play station: ") + if strings.Contains(errPart, "no such host") { + friendlyMsg = "Unable to connect to stream server.\nPlease check your internet connection." + } else if strings.Contains(errPart, "connection refused") { + friendlyMsg = "Connection refused by stream server." + } else if strings.Contains(errPart, "timeout") { + friendlyMsg = "Connection timed out." + } else if strings.Contains(errPart, "network read error") { + friendlyMsg = "Network connection lost.\nPlease check your internet connection." + } else { + if len(errPart) > 80 { + friendlyMsg = "Unable to play station.\nPlease try again later." + } else { + friendlyMsg = errPart + } + } + } + + ui.showErrorModal("Playback Error", friendlyMsg, nil) +} + +func (ui *UI) showHelpModal() { + keyColor := ui.colors.helpHotkey.String() + + configPath, _ := config.GetConfigPath() + + helpText := fmt.Sprintf(`[::b]KEYBOARD SHORTCUTS[::-] + +[%s]PLAYBACK[-] + [%s]Enter[-] Play selected station + [%s]Space[-] Pause / Resume + [%s]<[-] Previous station + [%s]>[-] Next station + [%s]r[-] Random station + +[%s]VOLUME[-] + [%s]+[-] / [%s]-[-] Volume up / down + [%s]←[-] / [%s]→[-] Volume up / down + [%s]m[-] Mute / Unmute + +[%s]STATIONS[-] + [%s]↑[-] / [%s]↓[-] Navigate list + [%s]f[-] Toggle favorite + +[%s]APPLICATION[-] + [%s]?[-] Show this help + [%s]a[-] About %s + [%s]q[-] / [%s]Esc[-] Quit + +[%s]CONFIG[-]: %s`, + keyColor, + keyColor, keyColor, keyColor, keyColor, keyColor, + keyColor, + keyColor, keyColor, keyColor, keyColor, keyColor, + keyColor, + keyColor, keyColor, keyColor, + keyColor, + keyColor, keyColor, config.AppName, keyColor, keyColor, + keyColor, configPath) + + ui.showInfoModal("Help", helpText) +} + +func (ui *UI) showAboutModal() { + doDismiss := func() { + ui.pages.RemovePage("modal") + ui.app.SetFocus(ui.stationList) + } + + linkColor := "skyblue" + dimColor := "gray" + + aboutText := fmt.Sprintf(`[::b]%s[::-] +[%s]%s[-] + +Version: %s +Author: %s ([%s:::%s]%s[-:::-]) +Project: [%s:::%s]%s[-:::-] +License: MIT + +─────────────────────────────────────────── + +[%s]Radio content from[-] [::b]SomaFM[::-] +Listener-supported • [%s:::%s]%s[-:::-]`, + config.AppName, + dimColor, config.AppTagline, + config.AppVersion, + config.AppAuthor, linkColor, config.AppAuthorURL, config.AppAuthorURLShort, + linkColor, config.AppProjectURL, config.AppProjectShort, + dimColor, + linkColor, config.AppDonateURL, config.AppDonateShort) + + messageView := tview.NewTextView(). + SetTextAlign(tview.AlignLeft). + SetDynamicColors(true). + SetText("\n" + aboutText) + messageView.SetTextColor(ui.colors.foreground) + messageView.SetBackgroundColor(ui.colors.modalBackground) + + hintView := tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText("[::d]Press any key to close[::-]") + hintView.SetTextColor(tcell.ColorDarkGray) + hintView.SetBackgroundColor(ui.colors.modalBackground) + + content := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(messageView, 0, 1, false). + AddItem(nil, 2, 0, false). + AddItem(hintView, 1, 0, false). + AddItem(nil, 1, 0, false) + content.SetBackgroundColor(ui.colors.modalBackground) + + frame := tview.NewFrame(content). + SetBorders(1, 0, 1, 1, 2, 2) + frame.SetBorder(true). + SetBorderColor(ui.colors.borders). + SetBackgroundColor(ui.colors.modalBackground). + SetTitle(" About "). + SetTitleColor(ui.colors.highlight). + SetTitleAlign(tview.AlignCenter) + + modalWidth := 50 + modalHeight := 20 + + modal := tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(frame, modalHeight, 0, true). + AddItem(nil, 0, 1, false), + modalWidth, 0, true). + AddItem(nil, 0, 1, false) + modal.SetBackgroundColor(ui.colors.background) + + modal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + doDismiss() + return nil + }) + + ui.pages.AddPage("modal", modal, true, true) + ui.app.SetFocus(modal) +} + +func (ui *UI) showInfoModal(title, message string) { + doDismiss := func() { + ui.pages.RemovePage("modal") + ui.app.SetFocus(ui.stationList) + } + + messageView := tview.NewTextView(). + SetTextAlign(tview.AlignLeft). + SetDynamicColors(true). + SetWordWrap(true). + SetText("\n" + message) + messageView.SetTextColor(ui.colors.foreground) + messageView.SetBackgroundColor(ui.colors.modalBackground) + + hintView := tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText("[::d]Press any key to close[::-]") + hintView.SetTextColor(tcell.ColorDarkGray) + hintView.SetBackgroundColor(ui.colors.modalBackground) + + content := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(messageView, 0, 1, false). + AddItem(nil, 2, 0, false). + AddItem(hintView, 1, 0, false). + AddItem(nil, 1, 0, false) + content.SetBackgroundColor(ui.colors.modalBackground) + + frame := tview.NewFrame(content). + SetBorders(1, 0, 1, 1, 2, 2) + frame.SetBorder(true). + SetBorderColor(ui.colors.borders). + SetBackgroundColor(ui.colors.modalBackground). + SetTitle(" " + title + " "). + SetTitleColor(ui.colors.highlight). + SetTitleAlign(tview.AlignCenter) + + lines := strings.Count(message, "\n") + 1 + modalWidth := 45 + modalHeight := lines + 10 + if modalHeight > 38 { + modalHeight = 38 + } + + modal := tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(frame, modalHeight, 0, true). + AddItem(nil, 0, 1, false), + modalWidth, 0, true). + AddItem(nil, 0, 1, false) + modal.SetBackgroundColor(ui.colors.background) + + modal.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + doDismiss() + return nil + }) + + ui.pages.AddPage("modal", modal, true, true) + ui.app.SetFocus(modal) +} + +func (ui *UI) showInitialErrorScreen(title, message string, onRetry, onQuit func()) { + content := fmt.Sprintf("[::b]%s[::-]\n\n%s", title, message) + + textView := tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText(content) + textView.SetTextColor(ui.colors.foreground) + textView.SetBackgroundColor(ui.colors.modalBackground) + + helpText := tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText("[::d]Press [::b]R[::d] to retry • Press [::b]Q[::d] to quit[::-]") + helpText.SetTextColor(ui.colors.foreground) + helpText.SetBackgroundColor(ui.colors.background) + + frame := tview.NewFrame(textView). + SetBorders(2, 2, 2, 2, 2, 2) + frame.SetBorder(true). + SetBorderColor(ui.colors.highlight). + SetBackgroundColor(ui.colors.modalBackground). + SetTitle(" Connection Error "). + SetTitleColor(ui.colors.highlight) + + layout := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(frame, 60, 1, true). + AddItem(nil, 0, 1, false), 10, 1, true). + AddItem(helpText, 2, 0, false). + AddItem(nil, 0, 1, false) + layout.SetBackgroundColor(ui.colors.background) + + layout.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyRune: + switch event.Rune() { + case 'r', 'R': + if onRetry != nil { + onRetry() + } + return nil + case 'q', 'Q': + if onQuit != nil { + onQuit() + } + return nil + } + case tcell.KeyEscape: + if onQuit != nil { + onQuit() + } + return nil + } + return event + }) + + ui.app.SetRoot(layout, true) + ui.app.SetFocus(layout) +} + +func (ui *UI) handleInitialError(err error) { + friendlyMsg := extractErrorReason(err) + + ui.showInitialErrorScreen( + "Unable to Load Stations", + friendlyMsg, + func() { // onRetry + ui.app.SetRoot(ui.loadingScreen, true) + go func() { + if err := ui.fetchStationsAndInitUI(); err != nil { + ui.app.QueueUpdateDraw(func() { + ui.handleInitialError(err) + }) + } + }() + }, + func() { // onQuit + ui.app.Stop() + }, + ) +} diff --git a/internal/ui/stations.go b/internal/ui/stations.go new file mode 100644 index 0000000..b64eb42 --- /dev/null +++ b/internal/ui/stations.go @@ -0,0 +1,295 @@ +package ui + +import ( + "fmt" + "math/rand/v2" + "strings" + + "github.com/gdamore/tcell/v2" + "github.com/rivo/tview" + "github.com/rs/zerolog/log" +) + +func (ui *UI) createStationListTable() *tview.Table { + table := tview.NewTable(). + SetBorders(false). + SetSeparator(' '). + SetSelectable(true, false). + SetFixed(1, 0) + + table.SetBorder(true). + SetTitle(fmt.Sprintf("Stations (%d)", ui.stationService.StationCount())). + SetBorderColor(ui.colors.borders). + SetTitleColor(ui.colors.foreground). + SetBackgroundColor(ui.colors.background). + SetBorderPadding(1, 0, 1, 1) + + table.SetSelectedStyle(tcell.StyleDefault. + Foreground(ui.colors.background). + Background(ui.colors.highlight)) + + table.SetCell(0, 0, tview.NewTableCell(" "). + SetTextColor(ui.colors.stationListHeaderForeground). + SetBackgroundColor(ui.colors.stationListHeaderBackground). + SetMaxWidth(2). + SetSelectable(false)) + + table.SetCell(0, 1, tview.NewTableCell(" "). + SetTextColor(ui.colors.stationListHeaderForeground). + SetBackgroundColor(ui.colors.stationListHeaderBackground). + SetMaxWidth(2). + SetSelectable(false)) + + table.SetCell(0, 2, tview.NewTableCell("Name"). + SetTextColor(ui.colors.stationListHeaderForeground). + SetBackgroundColor(ui.colors.stationListHeaderBackground). + SetExpansion(1). + SetSelectable(false)) + + table.SetCell(0, 3, tview.NewTableCell("Genre"). + SetTextColor(ui.colors.stationListHeaderForeground). + SetBackgroundColor(ui.colors.stationListHeaderBackground). + SetExpansion(1). + SetSelectable(false)) + + table.SetCell(0, 4, tview.NewTableCell("Listeners"). + SetTextColor(ui.colors.stationListHeaderForeground). + SetBackgroundColor(ui.colors.stationListHeaderBackground). + SetAlign(tview.AlignRight). + SetSelectable(false)) + + stationCount := ui.stationService.StationCount() + for i := 0; i < stationCount; i++ { + ui.setStationRow(table, i+1, i) + } + + // Track selected station ID for preserving selection after refresh + table.SetSelectionChangedFunc(func(row, column int) { + count := ui.stationService.StationCount() + if row > 0 && row <= count { + if s := ui.stationService.GetStation(row - 1); s != nil { + ui.selectedStationID = s.ID + } + } + }) + + return table +} + +func (ui *UI) setStationRow(table *tview.Table, row int, stationIndex int) { + s := ui.stationService.GetStation(stationIndex) + if s == nil { + return + } + + favIcon := " " + if ui.config.IsFavorite(s.ID) { + favIcon = "★" + } + table.SetCell(row, 0, tview.NewTableCell(favIcon). + SetTextColor(ui.colors.foreground). + SetMaxWidth(2)) + + playIcon := " " + if stationIndex == ui.playingIndex { + if ui.player.IsPaused() { + playIcon = "⏸" + } else { + playIcon = "➤" + } + } + table.SetCell(row, 1, tview.NewTableCell(playIcon). + SetTextColor(ui.colors.foreground). + SetMaxWidth(2)) + + table.SetCell(row, 2, tview.NewTableCell(s.Title). + SetTextColor(ui.colors.foreground). + SetMaxWidth(35). + SetExpansion(2)) + + genreText := strings.ReplaceAll(s.Genre, "|", ", ") + table.SetCell(row, 3, tview.NewTableCell(genreText). + SetTextColor(ui.colors.foreground). + SetMaxWidth(27). + SetExpansion(1)) + + table.SetCell(row, 4, tview.NewTableCell(s.Listeners). + SetTextColor(ui.colors.foreground). + SetAlign(tview.AlignRight)) +} + +func (ui *UI) nextStation() { + stationCount := ui.stationService.StationCount() + if stationCount == 0 { + return + } + + row, _ := ui.stationList.GetSelection() + currentIndex := row - 1 + nextIndex := (currentIndex + 1) % stationCount + ui.stationList.Select(nextIndex+1, 0) + ui.onStationSelected(nextIndex) +} + +func (ui *UI) prevStation() { + stationCount := ui.stationService.StationCount() + if stationCount == 0 { + return + } + + row, _ := ui.stationList.GetSelection() + currentIndex := row - 1 + prevIndex := currentIndex - 1 + if prevIndex < 0 { + prevIndex = stationCount - 1 + } + ui.stationList.Select(prevIndex+1, 0) + ui.onStationSelected(prevIndex) +} + +func (ui *UI) randomStation() { + stationCount := ui.stationService.StationCount() + if stationCount == 0 { + return + } + + randomIndex := rand.IntN(stationCount) + ui.stationList.Select(randomIndex+1, 0) + ui.onStationSelected(randomIndex) +} + +func (ui *UI) selectAndShowStation(index int) { + stationCount := ui.stationService.StationCount() + if stationCount == 0 || index < 0 || index >= stationCount { + return + } + + ui.currentStation = ui.stationService.GetStation(index) + + ui.stationList.Select(index+1, 0) + + ui.playerPanel.Clear() + contentPanel := ui.createContentPanel() + ui.playerPanel.AddItem(contentPanel, 0, 1, false) + + ui.updateLogoPanel(ui.currentStation) + + log.Debug().Msgf("Showing station info (without playing): %s", ui.currentStation.Title) +} + +func (ui *UI) selectAndShowStationByID(stationID string) bool { + index := ui.stationService.FindIndexByID(stationID) + if index < 0 { + log.Debug().Msgf("Last played station '%s' not found in station list", stationID) + return false + } + + ui.selectAndShowStation(index) + if s := ui.stationService.GetStation(index); s != nil { + log.Debug().Msgf("Auto-selected last played station: %s", s.Title) + } + return true +} + +func (ui *UI) toggleFavorite() { + row, _ := ui.stationList.GetSelection() + stationCount := ui.stationService.StationCount() + if row <= 0 || row > stationCount { + return + } + + stationIndex := row - 1 + selectedStation := ui.stationService.GetStation(stationIndex) + if selectedStation == nil { + return + } + + ui.config.ToggleFavorite(selectedStation.ID) + + favCell := ui.stationList.GetCell(row, 0) + if favCell != nil { + if ui.config.IsFavorite(selectedStation.ID) { + favCell.SetText("★") + } else { + favCell.SetText(" ") + } + } + + go func() { + if err := ui.config.Save(); err != nil { + log.Error().Err(err).Msg("Failed to save config") + } + }() + + log.Debug().Msgf("Toggled favorite for station: %s", selectedStation.Title) +} + +func (ui *UI) refreshStationTable() { + stationCount := ui.stationService.StationCount() + + // Stations may have been re-sorted, so update index by ID + if ui.playingStationID != "" { + newIndex := ui.stationService.FindIndexByID(ui.playingStationID) + if newIndex >= 0 { + ui.playingIndex = newIndex + } + } + + for i := 0; i < stationCount; i++ { + ui.setStationRow(ui.stationList, i+1, i) + } + + if ui.selectedStationID != "" { + newIndex := ui.stationService.FindIndexByID(ui.selectedStationID) + if newIndex >= 0 { + ui.stationList.Select(newIndex+1, 0) + } + } + + ui.stationList.SetTitle(fmt.Sprintf("Stations (%d)", stationCount)) + + log.Debug().Int("count", stationCount).Msg("Station table refreshed") +} + +func (ui *UI) updateStationListPlayingIndicator() { + stationCount := ui.stationService.StationCount() + if ui.playingIndex < 0 || ui.playingIndex >= stationCount { + return + } + + if !ui.player.IsPlaying() && !ui.player.IsPaused() { + return + } + + row := ui.playingIndex + 1 + s := ui.stationService.GetStation(ui.playingIndex) + if s == nil { + return + } + + playCell := ui.stationList.GetCell(row, 1) + if playCell != nil { + if ui.player.IsPaused() { + playCell.SetText("⏸") + } else { + playCell.SetText("➤") + } + } + + nameCell := ui.stationList.GetCell(row, 2) + if nameCell == nil { + return + } + + name := s.Title + indicator := ui.getPlayingIndicator() + + const maxNameWidth = 35 + maxLen := maxNameWidth - len(indicator) - 1 + if len(name) > maxLen { + name = name[:maxLen-3] + "..." + } + + nameText := name + " " + indicator + nameCell.SetText(nameText) +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go new file mode 100644 index 0000000..19a87a7 --- /dev/null +++ b/internal/ui/ui.go @@ -0,0 +1,722 @@ +package ui + +import ( + "context" + "errors" + "fmt" + "strings" + "sync" + "time" + + "github.com/gdamore/tcell/v2" + "github.com/glebovdev/somafm-cli/internal/config" + "github.com/glebovdev/somafm-cli/internal/player" + "github.com/glebovdev/somafm-cli/internal/service" + "github.com/glebovdev/somafm-cli/internal/station" + "github.com/rivo/tview" + "github.com/rs/zerolog/log" +) + +const ( + VolumeStep = 5 + HeaderHeight = 3 + FooterHeightWide = 3 // Wide: 1 row with padding (top + text + bottom) + FooterHeightNarrow = 6 // Narrow: 2 rows × 3 lines each + CoverWidth = 26 + CoverHeight = 12 + PlayerPanelHeight = 12 + FooterBreakpoint = 130 // Width threshold for responsive footer +) + +type UI struct { + app *tview.Application + stationService *service.StationService + player *player.Player + currentStation *station.Station + stationList *tview.Table + helpPanel *tview.Box + contentLayout *tview.Flex + playerPanel *tview.Flex + currentTrackView *tview.TextView + logoPanel *tview.Image + volumeView *tview.Flex + mainLayout *tview.Flex + loadingScreen *tview.Flex + progressBar *tview.TextView + loadingText *tview.TextView + pages *tview.Pages + stopUpdates chan struct{} + playingIndex int + playingStationID string + selectedStationID string + currentVolume int + isMuted bool + config *config.Config + startRandom bool + lastFooterWidth int // Track width to detect layout changes + mu sync.Mutex + animationFrame int + playingSpinner *PlayingSpinner + statusRenderer *StatusRenderer + colors struct { + background tcell.Color + foreground tcell.Color + borders tcell.Color + highlight tcell.Color + headerBackground tcell.Color + stationListHeaderBackground tcell.Color + stationListHeaderForeground tcell.Color + helpBackground tcell.Color + helpForeground tcell.Color + helpHotkey tcell.Color + genreTagBackground tcell.Color + modalBackground tcell.Color + } +} + +func NewUI(player *player.Player, stationService *service.StationService, startRandom bool) *UI { + cfg, err := config.Load() + if err != nil { + log.Warn().Err(err).Msg("Failed to load config, using defaults") + cfg = config.DefaultConfig() + } + + ui := &UI{ + app: tview.NewApplication(), + player: player, + stationService: stationService, + stopUpdates: make(chan struct{}), + playingIndex: -1, + currentVolume: cfg.Volume, + isMuted: false, + config: cfg, + startRandom: startRandom, + } + + ui.colors.background = config.GetColor(cfg.Theme.Background) + ui.colors.foreground = config.GetColor(cfg.Theme.Foreground) + ui.colors.borders = config.GetColor(cfg.Theme.Borders) + ui.colors.highlight = config.GetColor(cfg.Theme.Highlight) + ui.colors.headerBackground = config.GetColor(cfg.Theme.HeaderBackground) + ui.colors.stationListHeaderBackground = config.GetColor(cfg.Theme.StationListHeaderBackground) + ui.colors.stationListHeaderForeground = config.GetColor(cfg.Theme.StationListHeaderForeground) + ui.colors.helpBackground = config.GetColor(cfg.Theme.HelpBackground) + ui.colors.helpForeground = config.GetColor(cfg.Theme.HelpForeground) + ui.colors.helpHotkey = config.GetColor(cfg.Theme.HelpHotkey) + ui.colors.genreTagBackground = config.GetColor(cfg.Theme.GenreTagBackground) + ui.colors.modalBackground = config.GetColor(cfg.Theme.ModalBackground) + + player.SetVolume(cfg.Volume) + log.Debug().Msgf("Loaded volume from config: %d%%", cfg.Volume) + + ui.statusRenderer = NewStatusRenderer(player) + ui.statusRenderer.SetPrimaryColor(ui.colors.highlight.String()) + + return ui +} + +func (ui *UI) SaveConfig() { + ui.mu.Lock() + if !ui.isMuted { + ui.config.Volume = ui.currentVolume + } + if ui.currentStation != nil { + ui.config.LastStation = ui.currentStation.ID + } + ui.mu.Unlock() + + if err := ui.config.Save(); err != nil { + log.Error().Err(err).Msg("Failed to save config") + } +} + +func (ui *UI) safeCloseChannel() { + ui.mu.Lock() + defer ui.mu.Unlock() + + if ui.stopUpdates != nil { + select { + case <-ui.stopUpdates: + // Already closed + default: + close(ui.stopUpdates) + } + ui.stopUpdates = nil + } +} + +func (ui *UI) recreateStopChannel() { + ui.mu.Lock() + defer ui.mu.Unlock() + ui.stopUpdates = make(chan struct{}) +} + +func (ui *UI) stop() { + ui.stationService.StopPeriodicRefresh() + ui.player.Stop() + ui.safeCloseChannel() + ui.app.Stop() +} + +// Shutdown stops the UI gracefully from external callers (e.g., signal handlers). +func (ui *UI) Shutdown() { + ui.app.QueueUpdateDraw(func() { + ui.stop() + }) +} + +func (ui *UI) Run() error { + ui.setupLoadingScreen() + ui.app.SetRoot(ui.loadingScreen, true) + + ui.app.SetBeforeDrawFunc(func(screen tcell.Screen) bool { + screen.SetStyle(tcell.StyleDefault.Background(ui.colors.background)) + screen.Clear() + return false + }) + + go func() { + if err := ui.fetchStationsAndInitUI(); err != nil { + ui.app.QueueUpdateDraw(func() { + ui.handleInitialError(err) + }) + } + }() + + return ui.app.Run() +} + +func (ui *UI) setupLoadingScreen() { + ui.progressBar = tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetDynamicColors(true). + SetText("Loading: 0%") + + ui.progressBar.SetTextColor(ui.colors.foreground). + SetBackgroundColor(ui.colors.background) + + ui.loadingText = tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetText("Initializing...") + ui.loadingText.SetTextColor(ui.colors.foreground). + SetBackgroundColor(ui.colors.background) + + ui.loadingScreen = tview.NewFlex(). + SetDirection(tview.FlexRow). + AddItem(nil, 0, 1, false). + AddItem(tview.NewFlex(). + AddItem(nil, 0, 1, false). + AddItem(ui.loadingText, 0, 1, false). + AddItem(nil, 0, 1, false), 3, 1, false). + AddItem(ui.progressBar, 1, 1, false). + AddItem(nil, 0, 1, false) + + ui.loadingScreen.SetBackgroundColor(ui.colors.background) +} + +func (ui *UI) fetchStationsAndInitUI() error { + // Show real progress stages instead of fake loading animation + ui.app.QueueUpdateDraw(func() { + ui.loadingText.SetText("Connecting to SomaFM...") + ui.progressBar.SetText("[::b]Loading: 10%[-:-:-]") + }) + + stations, err := ui.stationService.GetStations() + if err != nil { + return fmt.Errorf("failed to fetch stations: %w", err) + } + log.Debug().Msgf("Loaded %d stations", len(stations)) + + ui.app.QueueUpdateDraw(func() { + ui.loadingText.SetText("Loading configuration...") + ui.progressBar.SetText("[::b]Loading: 50%[-:-:-]") + }) + + ui.config.CleanupFavorites(ui.stationService.GetValidStationIDs()) + ui.SaveConfig() + + ui.app.QueueUpdateDraw(func() { + ui.loadingText.SetText("Building interface...") + ui.progressBar.SetText("[::b]Loading: 80%[-:-:-]") + }) + + ui.setupUI() + + ui.stationService.StartPeriodicRefresh(30*time.Second, ui.onStationsRefreshed) + + ui.app.QueueUpdateDraw(func() { + ui.progressBar.SetText("[::b]Loading: 100%[-:-:-]") + ui.pages.SwitchToPage("main") + ui.app.SetFocus(ui.stationList) + }) + + return nil +} + +func (ui *UI) setupUI() { + header := ui.createHeader() + + ui.playerPanel = tview.NewFlex().SetDirection(tview.FlexRow) + ui.playerPanel.SetBackgroundColor(ui.colors.background) + + ui.stationList = ui.createStationListTable() + + ui.helpPanel = ui.createFooter() + + ui.contentLayout = tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(header, HeaderHeight, 0, false). + AddItem(nil, 1, 0, false). + AddItem(ui.playerPanel, PlayerPanelHeight, 0, false). + AddItem(nil, 1, 0, false). + AddItem(ui.stationList, 0, 1, true). + AddItem(ui.helpPanel, FooterHeightWide, 0, false) + ui.contentLayout.SetBackgroundColor(ui.colors.background) + + wrapper := tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(nil, 3, 0, false). + AddItem(ui.contentLayout, 0, 1, true). + AddItem(nil, 3, 0, false) + wrapper.SetBackgroundColor(ui.colors.background) + + ui.mainLayout = tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(nil, 1, 0, false). + AddItem(wrapper, 0, 1, true). + AddItem(nil, 1, 0, false) + ui.mainLayout.SetBackgroundColor(ui.colors.background) + + ui.pages = tview.NewPages(). + AddPage("main", ui.mainLayout, true, true) + ui.pages.SetBackgroundColor(ui.colors.background) + + ui.app.SetRoot(ui.pages, true). + EnableMouse(true) + + ui.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + if ui.pages.HasPage("modal") { + return event + } + return ui.globalInputHandler(event) + }) + + ui.app.Draw() + + ui.app.QueueUpdateDraw(func() { + if ui.startRandom { + ui.randomStation() + } else if ui.config.LastStation != "" { + if !ui.selectAndShowStationByID(ui.config.LastStation) { + ui.selectAndShowStation(0) + } + } else { + ui.selectAndShowStation(0) + } + }) +} + +func (ui *UI) createHeader() tview.Primitive { + titleView := tview.NewTextView() + titleView.SetText(" " + config.AppName) + titleView.SetTextAlign(tview.AlignLeft) + titleView.SetTextColor(ui.colors.foreground) + titleView.SetBackgroundColor(ui.colors.headerBackground) + + versionView := tview.NewTextView() + versionView.SetText("v" + config.AppVersion + " ") + versionView.SetTextAlign(tview.AlignRight) + versionView.SetTextColor(ui.colors.foreground) + versionView.SetBackgroundColor(ui.colors.headerBackground) + + textFlex := tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(titleView, 0, 1, false). + AddItem(versionView, 10, 0, false) + textFlex.SetBackgroundColor(ui.colors.headerBackground) + + topSpacer := tview.NewBox().SetBackgroundColor(ui.colors.headerBackground) + bottomSpacer := tview.NewBox().SetBackgroundColor(ui.colors.headerBackground) + leftSpacer := tview.NewBox().SetBackgroundColor(ui.colors.headerBackground) + rightSpacer := tview.NewBox().SetBackgroundColor(ui.colors.headerBackground) + + textWithPadding := tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(leftSpacer, 1, 0, false). + AddItem(textFlex, 0, 1, false). + AddItem(rightSpacer, 1, 0, false) + textWithPadding.SetBackgroundColor(ui.colors.headerBackground) + + headerFlex := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(topSpacer, 1, 0, false). + AddItem(textWithPadding, 1, 0, false). + AddItem(bottomSpacer, 1, 0, false) + headerFlex.SetBackgroundColor(ui.colors.headerBackground) + + return headerFlex +} + +func (ui *UI) updateLogoPanel(s *station.Station) { + go func() { + img, err := ui.stationService.LoadImage(s.XLImage) + if err != nil { + ui.app.QueueUpdateDraw(func() { + ui.logoPanel.SetDrawFunc(func(screen tcell.Screen, x, y, width, height int) (int, int, int, int) { + errorMsg := fmt.Sprintf("Failed to load image: %v", err) + tview.Print(screen, errorMsg, x, y, 10, tview.AlignCenter, tcell.ColorRed) + return x, y, 10, 10 + }) + }) + return + } + + ui.app.QueueUpdateDraw(func() { + ui.logoPanel.SetImage(img) + }) + }() +} + +func (ui *UI) onStationSelected(index int) { + stationCount := ui.stationService.StationCount() + if index < 0 || index >= stationCount { + return + } + + if index == ui.playingIndex && ui.player.IsPlaying() { + return + } + + ui.player.Stop() + ui.safeCloseChannel() + ui.recreateStopChannel() + + previousPlayingIndex := ui.playingIndex + + ui.playingIndex = index + ui.currentStation = ui.stationService.GetStation(index) + ui.playingStationID = ui.currentStation.ID + + if previousPlayingIndex >= 0 && previousPlayingIndex < stationCount && previousPlayingIndex != index { + ui.setStationRow(ui.stationList, previousPlayingIndex+1, previousPlayingIndex) + } + + ui.updateStationListPlayingIndicator() + + ui.SaveConfig() + + ui.playerPanel.Clear() + + contentPanel := ui.createContentPanel() + ui.playerPanel.AddItem(contentPanel, 0, 1, false) + + ui.updateLogoPanel(ui.currentStation) + + go func() { + stationID := ui.currentStation.ID + track, err := ui.stationService.GetCurrentTrackForStation(stationID) + if err != nil { + log.Debug().Err(err).Msg("Failed to fetch song history, using lastPlaying") + return + } + if track != "" { + ui.app.QueueUpdateDraw(func() { + if ui.currentTrackView != nil { + ui.currentTrackView.SetText(fmt.Sprintf(" [%s]%s[-]", + ui.colors.highlight.String(), + track)) + } + }) + ui.player.SetInitialTrack(track) + } + }() + + ui.startPlayingAnimation() + + go func() { + log.Info().Msgf("Starting playback for station: %s", ui.currentStation.Title) + err := ui.player.Play(ui.currentStation) + if err != nil { + if errors.Is(err, context.Canceled) { + log.Debug().Msg("Playback stopped (station changed)") + return + } + log.Error().Err(err).Msg("Failed to play station") + ui.app.QueueUpdateDraw(func() { + ui.showError(fmt.Sprintf("Failed to play station: %v", err)) + }) + } + }() +} + +func (ui *UI) createGenreTags(genre string) *tview.Flex { + container := tview.NewFlex().SetDirection(tview.FlexColumn) + container.SetBackgroundColor(ui.colors.background) + + container.AddItem(tview.NewBox().SetBackgroundColor(ui.colors.background), 1, 0, false) + + if genre == "" { + noGenre := tview.NewTextView() + noGenre.SetText("N/A") + noGenre.SetTextColor(ui.colors.foreground) + noGenre.SetBackgroundColor(ui.colors.background) + container.AddItem(noGenre, 3, 0, false) + return container + } + + genres := strings.Split(genre, "|") + for i, g := range genres { + g = strings.TrimSpace(g) + + tag := tview.NewTextView() + tag.SetText(" " + g + " ") + tag.SetTextColor(ui.colors.foreground) + tag.SetBackgroundColor(ui.colors.genreTagBackground) + tag.SetTextAlign(tview.AlignCenter) + + tagWidth := len(g) + 2 + container.AddItem(tag, tagWidth, 0, false) + + if i < len(genres)-1 { + spacer := tview.NewBox().SetBackgroundColor(ui.colors.background) + container.AddItem(spacer, 1, 0, false) + } + } + + container.AddItem(tview.NewBox().SetBackgroundColor(ui.colors.background), 0, 1, false) + + return container +} + +func (ui *UI) createContentPanel() *tview.Flex { + ui.logoPanel = tview.NewImage() + ui.logoPanel.SetBackgroundColor(ui.colors.background) + ui.logoPanel.SetAlign(tview.AlignLeft, tview.AlignTop) + + stationLabel := tview.NewTextView() + stationLabel.SetText(" Station:") + stationLabel.SetTextColor(ui.colors.foreground) + stationLabel.SetBackgroundColor(ui.colors.background) + stationLabel.SetWrap(false) + + stationNameView := tview.NewTextView() + stationNameView.SetDynamicColors(true) + stationNameView.SetText(fmt.Sprintf(" [%s]%s[-]", + ui.colors.highlight.String(), + ui.currentStation.Title)) + stationNameView.SetTextColor(ui.colors.highlight) + stationNameView.SetBackgroundColor(ui.colors.background) + stationNameView.SetWrap(false) + stationNameView.SetTextStyle(tcell.StyleDefault.Background(ui.colors.background).Attributes(tcell.AttrBold)) + + playingLabel := tview.NewTextView() + playingLabel.SetText(" Playing:") + playingLabel.SetTextColor(ui.colors.foreground) + playingLabel.SetBackgroundColor(ui.colors.background) + playingLabel.SetWrap(false) + + ui.currentTrackView = tview.NewTextView() + ui.currentTrackView.SetDynamicColors(true) + ui.currentTrackView.SetText(fmt.Sprintf(" [%s]%s[-]", + ui.colors.highlight.String(), + ui.currentStation.LastPlaying)) + ui.currentTrackView.SetTextColor(ui.colors.highlight) + ui.currentTrackView.SetBackgroundColor(ui.colors.background) + ui.currentTrackView.SetWrap(true) + ui.currentTrackView.SetTextStyle(tcell.StyleDefault.Background(ui.colors.background).Attributes(tcell.AttrBold)) + + genreLabel := tview.NewTextView() + genreLabel.SetText(" Genre:") + genreLabel.SetTextColor(ui.colors.foreground) + genreLabel.SetBackgroundColor(ui.colors.background) + genreLabel.SetWrap(false) + + genreView := ui.createGenreTags(ui.currentStation.Genre) + + descriptionLabel := tview.NewTextView() + descriptionLabel.SetText(" Description:") + descriptionLabel.SetTextColor(ui.colors.foreground) + descriptionLabel.SetBackgroundColor(ui.colors.background) + descriptionLabel.SetWrap(false) + + descriptionView := tview.NewTextView() + descriptionView.SetDynamicColors(true) + descriptionView.SetText(fmt.Sprintf(" [%s]%s[-]", + ui.colors.foreground.String(), + ui.currentStation.Description)) + descriptionView.SetTextColor(ui.colors.foreground) + descriptionView.SetBackgroundColor(ui.colors.background) + descriptionView.SetWrap(true) + + infoSpacer := tview.NewBox().SetBackgroundColor(ui.colors.background) + + infoContent := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(stationLabel, 1, 0, false). + AddItem(stationNameView, 1, 0, false). + AddItem(nil, 1, 0, false). + AddItem(playingLabel, 1, 0, false). + AddItem(ui.currentTrackView, 1, 0, false). + AddItem(nil, 1, 0, false). + AddItem(genreLabel, 1, 0, false). + AddItem(genreView, 1, 0, false). + AddItem(nil, 1, 0, false). + AddItem(descriptionLabel, 1, 0, false). + AddItem(descriptionView, 0, 1, false). + AddItem(infoSpacer, 0, 1, false) + infoContent.SetBackgroundColor(ui.colors.background) + + ui.volumeView = ui.createGraphicalVolumeBar() + + // Wrap logo in vertical flex to constrain height + logoWrapper := tview.NewFlex().SetDirection(tview.FlexRow). + AddItem(ui.logoPanel, CoverHeight, 0, false). + AddItem(nil, 0, 1, false) + logoWrapper.SetBackgroundColor(ui.colors.background) + + contentFlex := tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(logoWrapper, CoverWidth, 0, false). + AddItem(infoContent, 0, 1, false). + AddItem(ui.volumeView, 7, 0, false) + contentFlex.SetBackgroundColor(ui.colors.background) + + contentWithPadding := tview.NewFlex().SetDirection(tview.FlexColumn). + AddItem(nil, 4, 0, false). + AddItem(contentFlex, 0, 1, false). + AddItem(nil, 4, 0, false) + contentWithPadding.SetBackgroundColor(ui.colors.background) + + return contentWithPadding +} + +type PlayingSpinner struct { + Frames []string + FPS time.Duration +} + +func NewPlayingSpinner() *PlayingSpinner { + return &PlayingSpinner{ + Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "}, + FPS: time.Second / 10, + } +} + +func (ui *UI) getPlayingIndicator() string { + if ui.playingSpinner == nil { + ui.playingSpinner = NewPlayingSpinner() + } + + frameIndex := ui.animationFrame % len(ui.playingSpinner.Frames) + return ui.playingSpinner.Frames[frameIndex] +} + +func (ui *UI) startPlayingAnimation() { + if ui.playingSpinner == nil { + ui.playingSpinner = NewPlayingSpinner() + } + + go func() { + animationTicker := time.NewTicker(ui.playingSpinner.FPS) + trackUpdateTicker := time.NewTicker(5 * time.Second) + defer animationTicker.Stop() + defer trackUpdateTicker.Stop() + + for { + select { + case <-ui.stopUpdates: + return + case <-animationTicker.C: + ui.mu.Lock() + ui.animationFrame++ + ui.mu.Unlock() + + ui.statusRenderer.AdvanceAnimation() + + ui.app.QueueUpdateDraw(func() { + ui.updateStationListPlayingIndicator() + }) + case <-trackUpdateTicker.C: + ui.app.QueueUpdateDraw(func() { + ui.updateTrackInfo() + }) + } + } + }() +} + +func (ui *UI) updateTrackInfo() { + if ui.currentTrackView == nil || !ui.player.IsPlaying() { + return + } + + trackInfo := ui.player.GetCurrentTrack() + ui.currentTrackView.SetText(fmt.Sprintf(" [%s]%s[-]", + ui.colors.highlight.String(), + trackInfo)) +} + +func (ui *UI) onStationsRefreshed(stations []station.Station) { + ui.app.QueueUpdateDraw(func() { + ui.refreshStationTable() + }) +} + +func (ui *UI) globalInputHandler(event *tcell.EventKey) *tcell.EventKey { + switch event.Key() { + case tcell.KeyRune: + switch event.Rune() { + case 'q', 'Q': + ui.stop() + return nil + case ' ': + if ui.player.IsPlaying() || ui.player.IsPaused() { + ui.player.TogglePause() + ui.updateStationListPlayingIndicator() + } else { + row, _ := ui.stationList.GetSelection() + if row > 0 && row <= ui.stationService.StationCount() { + ui.onStationSelected(row - 1) + } + } + return nil + case '>': + ui.nextStation() + return nil + case '<': + ui.prevStation() + return nil + case 'r', 'R': + ui.randomStation() + return nil + case 'f', 'F': + ui.toggleFavorite() + return nil + case '+', '=': + ui.adjustVolume(VolumeStep) + return nil + case '-', '_': + ui.adjustVolume(-VolumeStep) + return nil + case 'm', 'M': + ui.toggleMute() + return nil + case '?': + ui.showHelpModal() + return nil + case 'a', 'A': + ui.showAboutModal() + return nil + } + case tcell.KeyEnter: + row, _ := ui.stationList.GetSelection() + if row > 0 && row <= ui.stationService.StationCount() { + ui.onStationSelected(row - 1) + } + return nil + case tcell.KeyEscape: + ui.stop() + return nil + case tcell.KeyRight: + // Right arrow - volume up (hidden shortcut) + ui.adjustVolume(VolumeStep) + return nil + case tcell.KeyLeft: + // Left arrow - volume down (hidden shortcut) + ui.adjustVolume(-VolumeStep) + return nil + } + return event +} diff --git a/internal/ui/ui_test.go b/internal/ui/ui_test.go new file mode 100644 index 0000000..c38c191 --- /dev/null +++ b/internal/ui/ui_test.go @@ -0,0 +1,338 @@ +package ui + +import ( + "errors" + "testing" +) + +func TestNewPlayingSpinner(t *testing.T) { + spinner := NewPlayingSpinner() + + if spinner == nil { + t.Fatal("NewPlayingSpinner() returned nil") + } + + if len(spinner.Frames) == 0 { + t.Error("PlayingSpinner.Frames is empty") + } + + if spinner.FPS <= 0 { + t.Error("PlayingSpinner.FPS should be positive") + } +} + +func TestPlayingSpinnerFrames(t *testing.T) { + spinner := NewPlayingSpinner() + + for i, frame := range spinner.Frames { + if frame == "" { + t.Errorf("Frame[%d] is empty", i) + } + } + + if len(spinner.Frames) < 2 { + t.Errorf("Expected at least 2 frames, got %d", len(spinner.Frames)) + } +} + +func TestQualityShort(t *testing.T) { + tests := []struct { + input string + expected string + }{ + {"highest", "HQ"}, + {"high", "HQ"}, + {"medium", "MQ"}, + {"low", "LQ"}, + {"", ""}, + {"unknown", ""}, + {"HIGHEST", ""}, + {"very high", ""}, + } + + for _, tt := range tests { + t.Run(tt.input, func(t *testing.T) { + result := qualityShort(tt.input) + if result != tt.expected { + t.Errorf("qualityShort(%q) = %q, want %q", tt.input, result, tt.expected) + } + }) + } +} + +func TestJoinParts(t *testing.T) { + tests := []struct { + name string + parts []string + expected string + }{ + { + name: "empty slice", + parts: []string{}, + expected: "", + }, + { + name: "single part", + parts: []string{"PLAYING"}, + expected: "PLAYING", + }, + { + name: "two parts", + parts: []string{"PLAYING", "MP3"}, + expected: "PLAYING │ MP3", + }, + { + name: "three parts", + parts: []string{"● LIVE", "MP3 HQ", "44.1kHz"}, + expected: "● LIVE │ MP3 HQ │ 44.1kHz", + }, + { + name: "nil slice", + parts: nil, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := joinParts(tt.parts) + if result != tt.expected { + t.Errorf("joinParts(%v) = %q, want %q", tt.parts, result, tt.expected) + } + }) + } +} + +func TestExtractErrorReason(t *testing.T) { + tests := []struct { + name string + err error + contains string + }{ + { + name: "no such host", + err: errors.New("dial tcp: lookup example.com: no such host"), + contains: "Unable to connect", + }, + { + name: "connection refused", + err: errors.New("dial tcp 127.0.0.1:80: connection refused"), + contains: "Connection refused", + }, + { + name: "timeout", + err: errors.New("context deadline exceeded (Client.Timeout exceeded)"), + contains: "timed out", + }, + { + name: "network unreachable", + err: errors.New("dial tcp: network is unreachable"), + contains: "Network is unreachable", + }, + { + name: "401 unauthorized", + err: errors.New("unexpected status 401"), + contains: "401", + }, + { + name: "403 forbidden", + err: errors.New("unexpected status 403"), + contains: "403", + }, + { + name: "404 not found", + err: errors.New("unexpected status 404"), + contains: "404", + }, + { + name: "generic error (short)", + err: errors.New("some error"), + contains: "some error", + }, + { + name: "dial error truncation", + err: errors.New("failed to connect: dial tcp something something"), + contains: "failed to connect", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := extractErrorReason(tt.err) + if result == "" { + t.Error("extractErrorReason returned empty string") + } + if !containsString(result, tt.contains) { + t.Errorf("extractErrorReason(%v) = %q, expected to contain %q", + tt.err, result, tt.contains) + } + }) + } +} + +func TestExtractErrorReasonLongError(t *testing.T) { + longErr := errors.New(string(make([]byte, 200))) + result := extractErrorReason(longErr) + + if len(result) > 110 { + t.Errorf("Long error not truncated properly, got length %d", len(result)) + } +} + +func containsString(s, substr string) bool { + return len(s) >= len(substr) && (s == substr || len(substr) == 0 || + (len(s) > 0 && len(substr) > 0 && findSubstring(s, substr))) +} + +func findSubstring(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +} + +func TestStatusRendererFormatBufferHealth(t *testing.T) { + renderer := &StatusRenderer{} + + tests := []struct { + percent int + expected int // expected number of characters (5 bars) + }{ + {0, 5}, + {50, 5}, + {100, 5}, + {120, 5}, + } + + for _, tt := range tests { + t.Run("", func(t *testing.T) { + result := renderer.formatBufferHealth(tt.percent) + runeCount := 0 + for range result { + runeCount++ + } + if runeCount != tt.expected { + t.Errorf("formatBufferHealth(%d) returned %d runes, want %d", + tt.percent, runeCount, tt.expected) + } + }) + } +} + +func TestStatusRendererFormatBufferHealthProgression(t *testing.T) { + renderer := &StatusRenderer{} + + result0 := renderer.formatBufferHealth(0) + result50 := renderer.formatBufferHealth(50) + result100 := renderer.formatBufferHealth(100) + + if result0 == result100 { + t.Error("0% and 100% buffer health should look different") + } + + if result50 == result0 || result50 == result100 { + t.Log("50% buffer health representation may equal 0% or 100% due to rounding") + } +} + +func TestStatusRendererSetters(t *testing.T) { + renderer := NewStatusRenderer(nil) + + renderer.SetMuted(true) + if !renderer.isMuted { + t.Error("SetMuted(true) did not set isMuted") + } + + renderer.SetMuted(false) + if renderer.isMuted { + t.Error("SetMuted(false) did not clear isMuted") + } + + renderer.SetPrimaryColor("red") + if renderer.primaryColor != "red" { + t.Errorf("SetPrimaryColor set %q, want %q", renderer.primaryColor, "red") + } +} + +func TestStatusRendererAdvanceAnimation(t *testing.T) { + renderer := NewStatusRenderer(nil) + + initialFrame := renderer.animFrame + + for i := 0; i < renderer.ticksPerFrame-1; i++ { + renderer.AdvanceAnimation() + } + + if renderer.animFrame != initialFrame { + t.Error("Animation frame changed before ticksPerFrame ticks") + } + + renderer.AdvanceAnimation() + + if renderer.animFrame != (initialFrame+1)%renderer.maxAnimFrame { + t.Errorf("Animation frame = %d, want %d", + renderer.animFrame, (initialFrame+1)%renderer.maxAnimFrame) + } + + if renderer.tickCount != 0 { + t.Errorf("tickCount = %d, want 0 after frame advance", renderer.tickCount) + } +} + +func TestStatusRendererRenderIdle(t *testing.T) { + renderer := NewStatusRenderer(nil) + + result := renderer.renderIdle() + if result == "" { + t.Error("renderIdle() returned empty string") + } + if !findSubstring(result, "IDLE") { + t.Errorf("renderIdle() = %q, expected to contain 'IDLE'", result) + } + + renderer.SetMuted(true) + result = renderer.renderIdle() + if !findSubstring(result, "MUTED") { + t.Errorf("renderIdle() when muted = %q, expected to contain 'MUTED'", result) + } +} + +func TestStatusRendererRenderBuffering(t *testing.T) { + renderer := NewStatusRenderer(nil) + + result := renderer.renderBuffering() + if result == "" { + t.Error("renderBuffering() returned empty string") + } + if !findSubstring(result, "BUFFERING") { + t.Errorf("renderBuffering() = %q, expected to contain 'BUFFERING'", result) + } +} + +// Note: renderReconnecting, renderPlaying, renderPaused, and renderError +// require a non-nil player to get state info. Testing those would require +// a mock player, which is beyond the scope of these pure function tests. +// The core formatting logic is still covered by formatBufferHealth, +// formatDuration, qualityShort, and joinParts tests. + +func TestNewStatusRenderer(t *testing.T) { + renderer := NewStatusRenderer(nil) + + if renderer == nil { + t.Fatal("NewStatusRenderer() returned nil") + } + + if renderer.maxAnimFrame <= 0 { + t.Error("maxAnimFrame should be positive") + } + + if renderer.ticksPerFrame <= 0 { + t.Error("ticksPerFrame should be positive") + } + + if renderer.bufferTicksPerUpdate <= 0 { + t.Error("bufferTicksPerUpdate should be positive") + } +} diff --git a/internal/ui/volume.go b/internal/ui/volume.go new file mode 100644 index 0000000..bd89200 --- /dev/null +++ b/internal/ui/volume.go @@ -0,0 +1,149 @@ +package ui + +import ( + "fmt" + + "github.com/gdamore/tcell/v2" + "github.com/glebovdev/somafm-cli/internal/config" + "github.com/rivo/tview" + "github.com/rs/zerolog/log" +) + +func (ui *UI) buildVolumeBar(container *tview.Flex) { + const barHeight = 10 + + ui.mu.Lock() + displayVolume := ui.currentVolume + isMuted := ui.isMuted + if isMuted { + displayVolume = ui.config.Volume + } + ui.mu.Unlock() + + filledLines := (displayVolume * barHeight) / 100 + emptyLines := barHeight - filledLines + + createText := func(text string, color tcell.Color) *tview.TextView { + tv := tview.NewTextView() + tv.SetText(text) + tv.SetTextAlign(tview.AlignRight) + tv.SetTextColor(color) + tv.SetBackgroundColor(ui.colors.background) + return tv + } + + createBarLine := func(barText string, barColor tcell.Color, showPercent bool) *tview.Flex { + line := tview.NewFlex().SetDirection(tview.FlexColumn) + line.SetBackgroundColor(ui.colors.background) + + if showPercent { + percentText := fmt.Sprintf("%d%%", displayVolume) + + var percentColor tcell.Color + if isMuted { + percentColor = config.GetColor(ui.config.Theme.MutedVolume) + } else { + percentColor = ui.colors.highlight + } + + percentView := createText(percentText, percentColor) + percentView.SetTextAlign(tview.AlignRight) + + if isMuted { + percentView.SetTextStyle(tcell.StyleDefault. + Foreground(percentColor). + Background(ui.colors.background). + Attributes(tcell.AttrStrikeThrough)) + } + + line.AddItem(percentView, 4, 0, false) + } else { + line.AddItem(createText(" ", ui.colors.foreground), 4, 0, false) + } + + line.AddItem(createText(barText, barColor), 0, 1, false) + + return line + } + + container.AddItem(createText(" max", ui.colors.foreground), 1, 0, false) + + for i := 0; i < emptyLines; i++ { + container.AddItem(createBarLine(" ░░", ui.colors.foreground, false), 1, 0, false) + } + + barColor := ui.colors.highlight + if isMuted { + barColor = config.GetColor(ui.config.Theme.MutedVolume) + } + for i := 0; i < filledLines; i++ { + showPercent := (i == 0) + container.AddItem(createBarLine(" ██", barColor, showPercent), 1, 0, false) + } + + container.AddItem(createText(" min", ui.colors.foreground), 1, 0, false) + + container.AddItem(nil, 0, 1, false) +} + +func (ui *UI) createGraphicalVolumeBar() *tview.Flex { + volumeContainer := tview.NewFlex().SetDirection(tview.FlexRow) + volumeContainer.SetBackgroundColor(ui.colors.background) + ui.buildVolumeBar(volumeContainer) + return volumeContainer +} + +func (ui *UI) updateVolumeDisplay() { + if ui.volumeView != nil { + ui.volumeView.Clear() + ui.buildVolumeBar(ui.volumeView) + } +} + +func (ui *UI) adjustVolume(delta int) { + ui.mu.Lock() + + if ui.isMuted { + ui.currentVolume = ui.config.Volume + ui.isMuted = false + ui.statusRenderer.SetMuted(false) + ui.mu.Unlock() + + ui.player.SetVolume(ui.currentVolume) + ui.updateVolumeDisplay() + log.Debug().Msgf("Auto-unmuted, restored volume to %d%%", ui.currentVolume) + return + } + + ui.currentVolume = config.ClampVolume(ui.currentVolume + delta) + ui.mu.Unlock() + + ui.player.SetVolume(ui.currentVolume) + ui.updateVolumeDisplay() + ui.SaveConfig() + log.Debug().Msgf("Volume adjusted to %d%%", ui.currentVolume) +} + +func (ui *UI) toggleMute() { + ui.mu.Lock() + if ui.isMuted { + ui.currentVolume = ui.config.Volume + ui.isMuted = false + log.Debug().Msgf("Unmuted, restored volume to %d%%", ui.currentVolume) + } else { + if ui.currentVolume == 0 { + ui.config.Volume = config.DefaultVolume + } else { + ui.config.Volume = ui.currentVolume + } + ui.currentVolume = 0 + ui.isMuted = true + log.Debug().Msgf("Muted, saved volume %d%%", ui.config.Volume) + } + ui.statusRenderer.SetMuted(ui.isMuted) + ui.mu.Unlock() + + ui.player.SetVolume(ui.currentVolume) + ui.updateVolumeDisplay() + ui.SaveConfig() +} From d2ec09351292e8b5d199b4f36181b96de71b3ab4 Mon Sep 17 00:00:00 2001 From: Ilya Glebov Date: Wed, 31 Dec 2025 00:14:30 +0100 Subject: [PATCH 3/6] Add CI/CD workflows --- .github/workflows/ci.yml | 67 +++++++++++++++++++++++++++++++++++ .github/workflows/release.yml | 33 +++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..da2a1e3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: CI + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install ALSA dev libraries + run: sudo apt-get update && sudo apt-get install -y libasound2-dev + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: Build + run: go build -v ./... + + - name: Test + run: go test -v ./... + + lint: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install ALSA dev libraries + run: sudo apt-get update && sudo apt-get install -y libasound2-dev + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: latest + + goreleaser-check: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: Check GoReleaser config + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..749e847 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,33 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: "1.24" + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }} From 238152d8df999705589289a9b40d635eb7e1c662 Mon Sep 17 00:00:00 2001 From: Ilya Glebov Date: Wed, 31 Dec 2025 00:14:39 +0100 Subject: [PATCH 4/6] Add GoReleaser configuration Builds for macOS and Windows. Linux excluded (requires CGO). --- .goreleaser.yaml | 137 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 .goreleaser.yaml diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..1b28df5 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,137 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=cnqoj + +version: 2 + +project_name: somafm + +before: + hooks: + - go mod tidy + +builds: + - id: somafm + main: ./cmd/somafm + binary: somafm + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ldflags: + - -s -w + - -X github.com/glebovdev/somafm-cli/internal/config.AppVersion={{.Version}} + ignore: + - goos: windows + goarch: arm64 + # Linux requires CGO for audio (oto/v3) + - goos: linux + goarch: amd64 + - goos: linux + goarch: arm64 + +archives: + - id: default + formats: + - tar.gz + format_overrides: + - goos: windows + formats: + - zip + name_template: >- + {{ .ProjectName }}_ + {{- .Version }}_ + {{- .Os }}_ + {{- .Arch }} + files: + - README.md + - LICENSE + +checksum: + name_template: "checksums.txt" + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + - "^chore:" + - "Merge pull request" + - "Merge branch" + +release: + github: + owner: glebovdev + name: somafm-cli + draft: false + prerelease: auto + name_template: "v{{.Version}}" + header: | + ## SomaFM CLI v{{.Version}} + + A terminal-based music player for SomaFM radio stations. + footer: | + --- + {{- if .PreviousTag }} + + **Full Changelog**: https://github.com/glebovdev/somafm-cli/compare/{{ .PreviousTag }}...{{ .Tag }} + {{- end }} + + ## Installation + + ### macOS + ```bash + brew install glebovdev/tap/somafm + ``` + + ### Windows + ```powershell + scoop bucket add glebovdev https://github.com/glebovdev/scoop-bucket + scoop install somafm + ``` + + ### Linux + ```bash + go install github.com/glebovdev/somafm-cli/cmd/somafm@latest + ``` + Pre-built binaries require CGO. Use `go install` which builds with your system's audio libraries. + +# Homebrew cask configuration (replaces deprecated brews) +scoops: + - name: somafm + repository: + owner: glebovdev + name: scoop-bucket + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + homepage: "https://github.com/glebovdev/somafm-cli" + description: "Terminal-based music player for SomaFM radio stations" + license: MIT + +homebrew_casks: + - name: somafm + repository: + owner: glebovdev + name: homebrew-tap + token: "{{ .Env.HOMEBREW_TAP_GITHUB_TOKEN }}" + directory: Casks + url: + verified: github.com/glebovdev/somafm-cli + homepage: "https://github.com/glebovdev/somafm-cli" + description: "Terminal-based music player for SomaFM radio stations" + license: "MIT" + commit_author: + name: goreleaserbot + email: bot@goreleaser.com + binaries: + - somafm + hooks: + post: + install: | + if OS.mac? + system_command "/usr/bin/xattr", args: ["-dr", "com.apple.quarantine", "#{staged_path}/somafm"] + end From 42678e02ef7920b0391c6fae63b83517118bc29c Mon Sep 17 00:00:00 2001 From: Ilya Glebov Date: Wed, 31 Dec 2025 00:14:46 +0100 Subject: [PATCH 5/6] Add README --- README.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..f7fd790 --- /dev/null +++ b/README.md @@ -0,0 +1,125 @@ +# SomaFM CLI + +[![CI](https://github.com/glebovdev/somafm-cli/actions/workflows/ci.yml/badge.svg)](https://github.com/glebovdev/somafm-cli/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/glebovdev/somafm-cli)](https://github.com/glebovdev/somafm-cli/releases/latest) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) + +A terminal-based music player for [SomaFM](https://somafm.com/) radio stations built in Go. + +![Demo](demo.gif) + +## Features + +- Stream all SomaFM radio stations +- Rich terminal UI with station browser +- Volume control with visual feedback +- Pause/resume playback +- Stations sorted by listener count +- Favorites management +- Persistent configuration (volume, favorites, last station) +- Customizable color themes +- Automatic retry on stream failure + +## Installation + +### macOS + +```bash +brew install glebovdev/tap/somafm +``` + +### Windows + +```powershell +scoop bucket add glebovdev https://github.com/glebovdev/scoop-bucket +scoop install somafm +``` + +Or download `somafm_*_windows_amd64.zip` from the [Releases page](https://github.com/glebovdev/somafm-cli/releases). + +### Go Install + +```bash +go install github.com/glebovdev/somafm-cli/cmd/somafm@latest +``` + +## Usage + +```bash +somafm # Start the player +somafm --random # Start with a random station +somafm --version # Show version information +somafm --debug # Enable debug logging +somafm --help # Show help and config file path +``` + +## Keyboard Shortcuts + +| Key | Action | +|--------------------|----------------------| +| `↑` `↓` | Navigate list | +| `Enter` | Play selected station| +| `Space` | Pause / Resume | +| `<` `>` | Previous / Next station | +| `r` | Random station | +| `←` `→` or `+` `-` | Volume up / down | +| `m` | Mute / Unmute | +| `f` | Toggle favorite | +| `?` | Show help | +| `a` | About | +| `q` `Esc` | Quit | + +## Configuration + +Configuration is saved automatically to `~/.config/somafm/config.yml`. + +```yaml +volume: 70 # Volume level (0-100) +buffer_seconds: 5 # Audio buffer size (0-60 seconds) +last_station: groovesalad # Last played station ID +favorites: # List of favorite station IDs + - groovesalad + - dronezone +theme: # Color customization + background: "#1a1b25" + foreground: "#a3aacb" + borders: "#40445b" + highlight: "#ff9d65" +``` + +Settings are saved when you adjust volume, select a station, or toggle favorites. + +### Theme Options + +Colors support names (`white`, `red`, `darkcyan`), hex codes (`#ff0000`), or `default` for terminal colors. + +Available theme properties: + +| Property | Description | +|----------|-------------| +| `background` | Main background color | +| `foreground` | Text color | +| `borders` | Border color | +| `highlight` | Selection highlight | +| `muted_volume` | Volume bar color when muted | +| `header_background` | Header section background | +| `help_background` | Help panel background | +| `help_foreground` | Help panel text | +| `help_hotkey` | Hotkey highlight color | +| `modal_background` | Modal dialog background | + +## Built With + +- [tview](https://github.com/rivo/tview) - Terminal UI framework +- [tcell](https://github.com/gdamore/tcell) - Terminal cell management +- [beep](https://github.com/gopxl/beep) - Audio playback +- [resty](https://github.com/go-resty/resty) - HTTP client +- [zerolog](https://github.com/rs/zerolog) - Structured logging + +## About SomaFM + +[SomaFM](https://somafm.com/) is listener-supported, commercial-free internet radio. If you enjoy their stations, please consider [supporting them](https://somafm.com/support/). + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. From 429d1c1bf22ed6e4b60f2bb0d7503e9524295ebc Mon Sep 17 00:00:00 2001 From: Ilya Glebov Date: Wed, 31 Dec 2025 00:14:55 +0100 Subject: [PATCH 6/6] Add demo assets --- demo.gif | Bin 0 -> 329386 bytes demo.tape | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 demo.gif create mode 100644 demo.tape diff --git a/demo.gif b/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..3f684e9b8231e932956753dfdcf15eeac2b86422 GIT binary patch literal 329386 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
  • Eek_FkQAG1@1tT$8_U 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 W_W}OPSEc`a007|uF!*?YNB<32&>(;S literal 0 HcmV?d00001 diff --git a/demo.tape b/demo.tape new file mode 100644 index 0000000..39d8184 --- /dev/null +++ b/demo.tape @@ -0,0 +1,54 @@ +# SomaFM CLI Demo +# Run: vhs demo.tape -o demo.gif + +Set Width 1180 +Set Height 870 +Set FontSize 13 +Set Padding 0 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 100ms + +# Start the app +Type "somafm" +Enter +Sleep 3s + +# Navigate and select station +Up +Sleep 400ms +Up +Sleep 400ms +Up +Sleep 400ms +Up +Sleep 400ms +Enter +Sleep 3.5s + +# Volume control +Left +Sleep 400ms +Left +Sleep 400ms +Right +Sleep 350ms +Right +Sleep 350ms +Right +Sleep 1s + +# Pause/resume +Space +Sleep 2.5s +Space +Sleep 2s + +# Mute/unmute +Type "m" +Sleep 2s +Type "m" +Sleep 1s + +# Quit +Type "q" +Sleep 500ms