diff --git a/.gitignore b/.gitignore index c3da6ad..819f242 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ gspt token.txt +.fuse* diff --git a/spt/auth.go b/spt/auth.go index 003f5b8..316c164 100644 --- a/spt/auth.go +++ b/spt/auth.go @@ -25,6 +25,8 @@ var ( sptauth.ScopePlaylistModifyPrivate, sptauth.ScopePlaylistReadPrivate, sptauth.ScopePlaylistReadCollaborative, + sptauth.ScopeUserReadPlaybackState, + sptauth.ScopeUserModifyPlaybackState, sptauth.ScopeUserLibraryModify, sptauth.ScopeUserLibraryRead, sptauth.ScopeUserReadPrivate, diff --git a/spt/get.go b/spt/get.go index 49c5142..7032ebf 100644 --- a/spt/get.go +++ b/spt/get.go @@ -213,3 +213,7 @@ func CurrentUserSavedTracks(errHandler func(err error)) (*LikedSongs, error) { func RecentlyPlayed() ([]spotify.RecentlyPlayedItem, error) { return Client.PlayerRecentlyPlayedOpt(ctx(), &spotify.RecentlyPlayedOptions{Limit: 50}) } + +func GetPlayerState() (*spotify.PlayerState, error) { + return Client.PlayerState(ctx()) +} diff --git a/ui/app.go b/ui/app.go index f2a2e93..205009c 100644 --- a/ui/app.go +++ b/ui/app.go @@ -2,6 +2,7 @@ package ui import ( "fmt" + "time" "github.com/gdamore/tcell/v2" "github.com/rivo/tview" @@ -30,7 +31,7 @@ type Application struct { Main *interactiveView NavMenu *NavMenu SearchBar *tview.Box - ProgressBar *tview.Box + ProgressBar *ProgressBar Root *Root ImagePreviewer *tview.Box } @@ -39,7 +40,7 @@ func NewApplication() *Application { App := tview.NewApplication() Root := NewRoot() - pBar := tview.NewBox().SetBorder(true).SetTitle("PROGRESS").SetBackgroundColor(tcell.ColorDefault) + pBar := NewProgressBar().SetProgressFunc(progressFunc) searchbar := tview.NewBox().SetBorder(true).SetTitle("SEARCH").SetBackgroundColor(tcell.ColorDefault) SetCurrentView(playlistView) Main := NewInteractiveView() @@ -126,6 +127,16 @@ func NewApplication() *Application { App.SetRoot(Root.Root, true).SetFocus(playlistNav.Table) InitNotifier() + updateRoutine() + + go func() { + for { + if Ui != nil && Ui.App != nil { + Ui.App.Draw() + time.Sleep(time.Second) + } + } + }() Ui = &Application{ App: App, diff --git a/ui/progress.go b/ui/progress.go new file mode 100644 index 0000000..2d2d290 --- /dev/null +++ b/ui/progress.go @@ -0,0 +1,141 @@ +package ui + +import ( + "fmt" + "strings" + "sync" + "time" + + "github.com/gdamore/tcell/v2" + "github.com/zmb3/spotify/v2" + + "github.com/aditya-K2/gspt/spt" + "github.com/aditya-K2/utils" + "github.com/rivo/tview" +) + +var ( + state *spotify.PlayerState + stateLock sync.Mutex +) + +// ProgressBar is a two-lined Box. First line is the BarTitle +// Second being the actual progress done. +// Use SetProgressFunc to provide the callback which provides the Fields each time the ProgressBar will be Drawn. +// The progressFunc must return (BarTitle, BarTopTitle, BarText, percentage) respectively +type ProgressBar struct { + *tview.Box + BarTitle string + BarText string + BarTopTitle string + progressFunc func() (BarTitle string, + BarTopTitle string, + BarText string, + percentage float64) +} + +func (self *ProgressBar) SetProgressFunc(pfunc func() (string, string, string, float64)) *ProgressBar { + self.progressFunc = pfunc + return self +} + +func NewProgressBar() *ProgressBar { + return &ProgressBar{ + Box: tview.NewBox(), + } +} + +func GetProgressGlyph(width, percentage float64, btext string) string { + q := "[black:white:b]" + var a string + a += strings.Repeat(" ", int(width)-len(btext)) + a = utils.InsertAt(a, btext, int(width/2)-10) + a = utils.InsertAt(a, "[-:-:-]", int(width*percentage/100)) + q += a + return q +} + +func (self *ProgressBar) Draw(screen tcell.Screen) { + var ( + OFFSET int = 1 + ) + self.Box.SetBorder(true) + self.Box.SetBackgroundColor(tcell.ColorDefault) + var percentage float64 + self.BarTitle, self.BarTopTitle, self.BarText, percentage = self.progressFunc() + self.DrawForSubclass(screen, self.Box) + self.Box.SetTitle(self.BarTopTitle) + self.Box.SetTitleAlign(tview.AlignRight) + x, y, _width, _ := self.Box.GetInnerRect() + tview.Print(screen, self.BarTitle, x+OFFSET, y, _width, tview.AlignLeft, tcell.ColorWhite) + tview.Print(screen, + GetProgressGlyph(float64(_width-OFFSET-1), + percentage, + self.BarText), + x, y+2, _width-OFFSET, tview.AlignRight, tcell.ColorWhite) +} + +func RefreshProgress() { + s, err := spt.GetPlayerState() + if err != nil { + SendNotification(err.Error()) + return + } + stateLock.Lock() + // TODO: imagePreview.RefreshState() + state = s + stateLock.Unlock() +} + +func RefreshProgressLocal() { + stateLock.Lock() + if state != nil { + if state.Item != nil && state.Playing { + if state.Item.Duration-state.Progress >= 1000 { + state.Progress += 1000 + } else { + state.Progress += state.Item.Duration - state.Progress + go func() { + RefreshProgress() + }() + } + } + } + stateLock.Unlock() +} + +func updateRoutine() { + RefreshProgress() + go func() { + localTicker := time.NewTicker(time.Second) + spotifyTicker := time.NewTicker(time.Second * 5) + for { + select { + case <-spotifyTicker.C: + { + RefreshProgress() + } + case <-localTicker.C: + { + RefreshProgressLocal() + } + } + } + }() +} + +func progressFunc() (string, string, string, float64) { + percentage := 0.0 + barTitle := " - " + barText := "---:---" + barTopTitle := "[]" + if state != nil { + barTopTitle = fmt.Sprintf("[Device: %s Shuffle: %t Repeat: %s]", state.Device.Name, state.ShuffleState, state.RepeatState) + if state.Item != nil { + barTitle = fmt.Sprintf("%s - %s", state.Item.Name, state.Item.Artists[0].Name) + barText = utils.StrTime(float64(state.Progress/1000)) + "/" + utils.StrTime(float64(state.Item.Duration/1000)) + percentage = (float64(state.Progress) / float64(state.Item.Duration)) * 100 + } + } + return barTitle, barTopTitle, barText, percentage +}