diff --git a/.gitignore b/.gitignore index 6bddd99..e5e4408 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ test/ todo.txt stmp.toml stmps +data diff --git a/logger/logger.go b/logger/logger.go index d5d317b..2f23d2d 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -5,6 +5,8 @@ package logger import "fmt" +// TODO Add option to also log to file +// TODO Support log levels github.com/d2g/logfilter type Logger struct { Prints chan string } diff --git a/mpvplayer/player.go b/mpvplayer/player.go index a13d104..c6ac7d7 100644 --- a/mpvplayer/player.go +++ b/mpvplayer/player.go @@ -162,6 +162,11 @@ func (p *Player) IsSongLoaded() (bool, error) { return !idle, err } +func (p *Player) IsSeekable() (bool, error) { + seekable, err := p.getPropertyBool("seekable") + return seekable, err +} + func (p *Player) IsPaused() (bool, error) { pause, err := p.getPropertyBool("pause") return pause, err diff --git a/page_browser.go b/page_browser.go index bcdc9dd..3c79aa9 100644 --- a/page_browser.go +++ b/page_browser.go @@ -43,6 +43,7 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { } // artist list + // TODO Subsonic can provide artist images. Find a place to display them in the browser browserPage.artistList = tview.NewList(). ShowSecondaryText(false) browserPage.artistList.Box. @@ -56,6 +57,7 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { browserPage.artistObjectList = artists // album list + // TODO add filter/search to entity list (albums/songs) browserPage.entityList = tview.NewList(). ShowSecondaryText(false). SetSelectedFocusOnly(true) @@ -86,6 +88,8 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { browserPage.Root = tview.NewFlex().SetDirection(tview.FlexRow) browserPage.showSearchField(false) // add artist/search items + // TODO Add a toggle to switch the browser to a directory browser + // going right from the artist list should focus the album/song list browserPage.artistList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyRight { @@ -97,6 +101,7 @@ func (ui *Ui) createBrowserPage(artists []subsonic.Artist) *BrowserPage { ui.app.SetFocus(browserPage.artistList) return nil } + // TODO Enter on an artist should... what? Add & play? Switch to the Entity list? switch event.Rune() { case 'a': diff --git a/page_playlist.go b/page_playlist.go index 66b66db..90b8bfe 100644 --- a/page_playlist.go +++ b/page_playlist.go @@ -123,6 +123,7 @@ func (ui *Ui) createPlaylistPage() *PlaylistPage { return event }) + // TODO Add filter/search to playlist coluumn playlistPage.selectedPlaylist.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { if event.Key() == tcell.KeyLeft { ui.app.SetFocus(playlistPage.playlistList) @@ -197,6 +198,7 @@ func (p *PlaylistPage) UpdatePlaylists() { return } p.isUpdating = true + // FIXME Stop pro-actively deeply loading the playlists. This will remove all of the threading and spinner code. var spinnerText []rune = []rune(viper.GetString("ui.spinner")) if len(spinnerText) == 0 { diff --git a/page_queue.go b/page_queue.go index c3bdbfc..fd2e5ab 100644 --- a/page_queue.go +++ b/page_queue.go @@ -131,10 +131,16 @@ func (ui *Ui) createQueuePage() *QueuePage { if err := ui.player.Play(); err != nil { queuePage.logger.Printf("error playing: %s", err) } + _ = ui.player.Pause() + for { + if seekable, err := ui.player.IsSeekable(); err == nil && seekable { + break + } + time.Sleep(100 * time.Millisecond) + } if err = ui.player.Seek(playQueue.Position); err != nil { queuePage.logger.Printf("unable to seek to position %s: %s", time.Duration(playQueue.Position)*time.Second, err) } - _ = ui.player.Pause() } }() case 'i': @@ -193,6 +199,7 @@ func (ui *Ui) createQueuePage() *QueuePage { } func (q *QueuePage) changeSelection(row, column int) { + // TODO Merge concurrent cover art code q.songInfo.Clear() if row >= len(q.queueData.playerQueue) || row < 0 || column < 0 { q.coverArt.SetImage(STMPS_LOGO) diff --git a/page_search.go b/page_search.go index 3112443..bf89793 100644 --- a/page_search.go +++ b/page_search.go @@ -83,6 +83,8 @@ func (ui *Ui) createSearchPage() *SearchPage { AddItem(searchPage.columnsFlex, 0, 1, true). AddItem(searchPage.searchField, 1, 1, false) + // TODO add filter/search to all of the results columns + // TODO browsing genres should autoload the genres, rather than waiting for Enter searchPage.artistList.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { switch event.Key() { case tcell.KeyLeft: diff --git a/stmps.go b/stmps.go index 52969fb..4d25a4c 100644 --- a/stmps.go +++ b/stmps.go @@ -20,9 +20,16 @@ import ( "github.com/spezifisch/stmps/remote" "github.com/spezifisch/stmps/subsonic" tviewcommand "github.com/spezifisch/tview-command" + + // TODO consider replacing viper with claptrap "github.com/spf13/viper" ) +// TODO Update screenshots in the README +// TODO Add mocking library +// TODO Get unit tests up to some non-embarassing percentage +// TODO Merge feature_27_save_queue / issue-54-save-queue-on-exit / seekable-queue-load, and finish the restoring play location on first run, or hotkey + var osExit = os.Exit // A variable to allow mocking os.Exit in tests var headlessMode bool // This can be set to true during tests var testMode bool // This can be set to true during tests, too @@ -116,6 +123,7 @@ func initCommandHandler(logger *logger.Logger) { // 2 - keybinding config errors func main() { // parse flags and config + // TODO help should better explain the arguments, especially the currently undocumented server URL argument help := flag.Bool("help", false, "Print usage") enableMpris := flag.Bool("mpris", false, "Enable MPRIS2") list := flag.Bool("list", false, "list server data") diff --git a/subsonic/connection.go b/subsonic/connection.go index eaa4fe2..1a29648 100644 --- a/subsonic/connection.go +++ b/subsonic/connection.go @@ -34,7 +34,8 @@ type Connection struct { clientName string clientVersion string - logger logger.LoggerInterface + logger logger.LoggerInterface + // TODO Connect album art and album caches to an LRU; artists probably don't take up much space, but review. directoryCache map[string]Directory albumCache map[string]Album artistCache map[string]Artist