Skip to content

Commit 360391e

Browse files
authored
Merge pull request #744 from dweymouth/feature/lyrics-sidebar
Add lyrics viewer to sidebar
2 parents 35b2743 + 33eb081 commit 360391e

File tree

7 files changed

+211
-44
lines changed

7 files changed

+211
-44
lines changed

backend/app.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ var (
4444
type App struct {
4545
Config *Config
4646
ServerManager *ServerManager
47+
LyricsManager *LyricsManager
4748
ImageManager *ImageManager
4849
AudioCache *AudioCache
4950
PlaybackManager *PlaybackManager
@@ -52,7 +53,6 @@ type App struct {
5253
MPRISHandler *MPRISHandler
5354
WinSMTC *windows.SMTC
5455
ipcServer ipc.IPCServer
55-
LrcLibFetcher *LrcLibFetcher
5656

5757
// UI callbacks to be set in main
5858
OnReactivate func()
@@ -158,10 +158,12 @@ func StartupApp(appName, displayAppName, appVersion, appVersionTag, latestReleas
158158
a.ServerManager.SetPrefetchAlbumCoverCallback(func(coverID string) {
159159
_, _ = a.ImageManager.GetCoverThumbnail(coverID)
160160
})
161+
var fetch *LrcLibFetcher
161162
if a.Config.Application.EnableLrcLib {
162163
timeout := time.Duration(a.Config.Application.RequestTimeoutSeconds) * time.Second
163-
a.LrcLibFetcher = NewLrcLibFetcher(a.cacheDir, a.Config.Application.CustomLrcLibUrl, timeout)
164+
fetch = NewLrcLibFetcher(a.cacheDir, a.Config.Application.CustomLrcLibUrl, timeout)
164165
}
166+
a.LyricsManager = NewLyricsManager(a.ServerManager, fetch)
165167

166168
// Periodically scan for remote players
167169
go a.PlaybackManager.ScanRemotePlayers(a.bgrndCtx, true /*fastScan*/)

backend/config.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type AppConfig struct {
5959
EnableOSMediaPlayerAPIs bool
6060
ShowSidebar bool
6161
SidebarWidthFraction float64
62+
SidebarTab string
6263

6364
FontNormalTTF string
6465
FontBoldTTF string

backend/lyricsmanager.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package backend
2+
3+
import (
4+
"context"
5+
"log"
6+
"sync"
7+
8+
"github.com/dweymouth/supersonic/backend/mediaprovider"
9+
)
10+
11+
type LyricsManager struct {
12+
sm *ServerManager
13+
lrclib *LrcLibFetcher
14+
15+
// right now only one song can have lyrics being fetched
16+
// at any given time (b/c we only show lyrics for the currently playing song)
17+
lock sync.Mutex
18+
fetchInProgressID string
19+
fetchInProgressCancel context.CancelFunc
20+
cbs []func(string, *mediaprovider.Lyrics)
21+
}
22+
23+
func NewLyricsManager(sm *ServerManager, lrclib *LrcLibFetcher) *LyricsManager {
24+
return &LyricsManager{
25+
sm: sm,
26+
lrclib: lrclib,
27+
}
28+
}
29+
30+
func (lm *LyricsManager) FetchLyricsAsync(song *mediaprovider.Track, cb func(string, *mediaprovider.Lyrics)) {
31+
lm.lock.Lock()
32+
defer lm.lock.Unlock()
33+
34+
if lm.fetchInProgressID == song.ID {
35+
lm.cbs = append(lm.cbs, cb)
36+
return
37+
}
38+
lm.fetchInProgressID = song.ID
39+
40+
if lm.fetchInProgressCancel != nil {
41+
lm.fetchInProgressCancel()
42+
}
43+
lm.cbs = []func(string, *mediaprovider.Lyrics){cb}
44+
ctx, cancel := context.WithCancel(context.Background())
45+
lm.fetchInProgressCancel = cancel
46+
go lm.fetchLyrics(ctx, song, func(id string, lyrics *mediaprovider.Lyrics) {
47+
lm.lock.Lock()
48+
defer lm.lock.Unlock()
49+
lm.fetchInProgressID = ""
50+
lm.fetchInProgressCancel()
51+
for _, cb := range lm.cbs {
52+
cb(id, lyrics)
53+
}
54+
})
55+
}
56+
57+
func (lm *LyricsManager) fetchLyrics(ctx context.Context, song *mediaprovider.Track, cb func(string, *mediaprovider.Lyrics)) {
58+
var lyrics *mediaprovider.Lyrics
59+
var err error
60+
if lp, ok := lm.sm.Server.(mediaprovider.LyricsProvider); ok {
61+
if lyrics, err = lp.GetLyrics(song); err != nil {
62+
log.Printf("Error fetching lyrics: %v", err)
63+
}
64+
}
65+
if lyrics == nil && lm.lrclib != nil {
66+
lyrics, err = lm.lrclib.FetchLrcLibLyrics(song.Title, song.ArtistNames[0], song.Album, int(song.Duration.Seconds()))
67+
if err != nil {
68+
log.Println(err.Error())
69+
}
70+
}
71+
select {
72+
case <-ctx.Done():
73+
return
74+
default:
75+
cb(song.ID, lyrics)
76+
}
77+
}

ui/browsing/nowplayingpage.go

Lines changed: 8 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ type NowPlayingPage struct {
6161
container *fyne.Container
6262

6363
// cancel funcs for background fetch tasks
64-
lyricFetchCancel context.CancelFunc
6564
imageLoadCancel context.CancelFunc
6665
relatedFetchCancel context.CancelFunc
6766
}
@@ -72,27 +71,27 @@ type nowPlayingPageState struct {
7271
pool *util.WidgetPool
7372
sm *backend.ServerManager
7473
pm *backend.PlaybackManager
74+
lm *backend.LyricsManager
7575
im *backend.ImageManager
7676
mp mediaprovider.MediaProvider
7777
canRate bool
7878
canShare bool
79-
lrcFetch *backend.LrcLibFetcher
8079
}
8180

8281
func NewNowPlayingPage(
8382
conf *backend.NowPlayingPageConfig,
8483
contr *controller.Controller,
8584
pool *util.WidgetPool,
8685
sm *backend.ServerManager,
86+
lm *backend.LyricsManager,
8787
im *backend.ImageManager,
8888
pm *backend.PlaybackManager,
8989
mp mediaprovider.MediaProvider,
9090
canRate bool,
9191
canShare bool,
92-
lrcLibFetcher *backend.LrcLibFetcher,
9392
) *NowPlayingPage {
9493
state := nowPlayingPageState{
95-
conf: conf, contr: contr, pool: pool, sm: sm, im: im, pm: pm, mp: mp, canRate: canRate, canShare: canShare, lrcFetch: lrcLibFetcher,
94+
conf: conf, contr: contr, pool: pool, sm: sm, lm: lm, im: im, pm: pm, mp: mp, canRate: canRate, canShare: canShare,
9695
}
9796
if page, ok := pool.Obtain(util.WidgetTypeNowPlayingPage).(*NowPlayingPage); ok && page != nil {
9897
page.nowPlayingPageState = state
@@ -336,10 +335,6 @@ func (a *NowPlayingPage) onImageLoaded(img image.Image, err error) {
336335
}
337336

338337
func (a *NowPlayingPage) updateLyrics() {
339-
if a.lyricFetchCancel != nil {
340-
a.lyricFetchCancel()
341-
}
342-
343338
if a.nowPlayingID == a.curLyricsID {
344339
if a.nowPlayingID != "" {
345340
// just need to sync the current time
@@ -354,8 +349,6 @@ func (a *NowPlayingPage) updateLyrics() {
354349
return
355350
}
356351
a.curLyricsID = a.nowPlayingID
357-
ctx, cancel := context.WithCancel(context.Background())
358-
a.lyricFetchCancel = cancel
359352
a.lyricsLoading.Start()
360353
// set the widget to an empty (not nil) lyric during fetch
361354
// to keep it from showing "Lyrics not available"
@@ -365,27 +358,11 @@ func (a *NowPlayingPage) updateLyrics() {
365358
Lines: []mediaprovider.LyricLine{{Text: ""}},
366359
})
367360
tr, _ := a.nowPlaying.(*mediaprovider.Track)
368-
go a.fetchLyrics(ctx, tr)
369-
}
370361

371-
func (a *NowPlayingPage) fetchLyrics(ctx context.Context, song *mediaprovider.Track) {
372-
var lyrics *mediaprovider.Lyrics
373-
var err error
374-
if lp, ok := a.sm.Server.(mediaprovider.LyricsProvider); ok {
375-
if lyrics, err = lp.GetLyrics(song); err != nil {
376-
log.Printf("Error fetching lyrics: %v", err)
377-
}
378-
}
379-
if lyrics == nil && a.lrcFetch != nil {
380-
lyrics, err = a.lrcFetch.FetchLrcLibLyrics(song.Title, song.ArtistNames[0], song.Album, int(song.Duration.Seconds()))
381-
if err != nil {
382-
log.Println(err.Error())
362+
a.lm.FetchLyricsAsync(tr, func(id string, lyrics *mediaprovider.Lyrics) {
363+
if id != a.nowPlayingID {
364+
return
383365
}
384-
}
385-
select {
386-
case <-ctx.Done():
387-
return
388-
default:
389366
fyne.Do(func() {
390367
a.lyricsLoading.Stop()
391368
a.lyricsViewer.EnableTapToSeek()
@@ -395,7 +372,7 @@ func (a *NowPlayingPage) fetchLyrics(ctx context.Context, song *mediaprovider.Tr
395372
a.lyricsViewer.OnSeeked(a.lastPlayPos)
396373
}
397374
})
398-
}
375+
})
399376
}
400377

401378
func (a *NowPlayingPage) updateRelatedList() {
@@ -464,7 +441,7 @@ func (a *NowPlayingPage) Reload() {
464441
}
465442

466443
func (s *nowPlayingPageState) Restore() Page {
467-
return NewNowPlayingPage(s.conf, s.contr, s.pool, s.sm, s.im, s.pm, s.mp, s.canRate, s.canShare, s.lrcFetch)
444+
return NewNowPlayingPage(s.conf, s.contr, s.pool, s.sm, s.lm, s.im, s.pm, s.mp, s.canRate, s.canShare)
468445
}
469446

470447
var _ CanShowPlayTime = (*NowPlayingPage)(nil)

ui/browsing/router.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func (r Router) CreatePage(rte controller.Route) Page {
4848
case controller.Genres:
4949
return NewGenresPage(r.Controller, r.App.ServerManager.Server)
5050
case controller.NowPlaying:
51-
return NewNowPlayingPage(&r.App.Config.NowPlayingConfig, r.Controller, r.widgetPool, r.App.ServerManager, r.App.ImageManager, r.App.PlaybackManager, r.App.ServerManager.Server, canRate, canShare, r.App.LrcLibFetcher)
51+
return NewNowPlayingPage(&r.App.Config.NowPlayingConfig, r.Controller, r.widgetPool, r.App.ServerManager, r.App.LyricsManager, r.App.ImageManager, r.App.PlaybackManager, r.App.ServerManager.Server, canRate, canShare)
5252
case controller.Playlist:
5353
return NewPlaylistPage(rte.Arg, &r.App.Config.PlaylistPage, r.widgetPool, r.Controller, r.App.ServerManager, r.App.PlaybackManager, r.App.ImageManager)
5454
case controller.Playlists:

ui/mainwindow.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,10 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
6969
}
7070
m.Controller = controller.New(app, appVersion, m.Window)
7171
m.BrowsingPane = browsing.NewBrowsingPane(app.PlaybackManager, m.Controller, func() { m.Router.NavigateTo(m.StartupPage()) })
72-
m.Sidebar = NewSidebar(m.Controller, m.App.ImageManager)
72+
m.Sidebar = NewSidebar(m.Controller, m.App.PlaybackManager, m.App.ImageManager, m.App.LyricsManager)
73+
if m.App.Config.Application.SidebarTab == "Lyrics" {
74+
m.Sidebar.SetSelectedIndex(1)
75+
}
7376
m.ToastOverlay = NewToastOverlay()
7477
m.Router = browsing.NewRouter(app, m.Controller, m.BrowsingPane)
7578
goHomeFn := func() { m.Router.NavigateTo(m.StartupPage()) }
@@ -167,12 +170,12 @@ func NewMainWindow(fyneApp fyne.App, appName, displayAppName, appVersion string,
167170
func (m *MainWindow) UpdateOnTrackChange(item mediaprovider.MediaItem) {
168171
if item == nil {
169172
m.Window.SetTitle(res.DisplayName)
170-
m.Sidebar.SetNowPlaying("")
173+
m.Sidebar.SetNowPlaying(nil)
171174
return
172175
}
173176

174177
meta := item.Metadata()
175-
m.Sidebar.SetNowPlaying(meta.ID)
178+
m.Sidebar.SetNowPlaying(item)
176179
artistDisp := ""
177180
if tr, ok := item.(*mediaprovider.Track); ok {
178181
artistDisp = " – " + strings.Join(tr.ArtistNames, ", ")
@@ -533,6 +536,12 @@ func (m *MainWindow) SaveWindowSettings() {
533536
&m.App.Config.Application.WindowHeight)
534537
m.App.Config.Application.ShowSidebar = !m.Sidebar.Hidden
535538
m.App.Config.Application.SidebarWidthFraction = m.splitContainer.Offset
539+
switch m.Sidebar.SelectedIndex() {
540+
case 1:
541+
m.App.Config.Application.SidebarTab = "Lyrics"
542+
default:
543+
m.App.Config.Application.SidebarTab = "Play Queue"
544+
}
536545
}
537546

538547
// widget just so we can catch a tap event that doesn't land anywhere else

0 commit comments

Comments
 (0)