From 220d523f88fba99e4bab98401c09564d65d2bad3 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 May 2024 18:23:51 -0700 Subject: [PATCH 01/11] switch to fyne-lyrics viewer --- go.mod | 11 ++++---- go.sum | 14 ++++++----- ui/widgets/lyricsviewer.go | 51 +++++++++++++++----------------------- 3 files changed, 34 insertions(+), 42 deletions(-) diff --git a/go.mod b/go.mod index 6b4d79d..1502ea4 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/dweymouth/supersonic -go 1.21 +go 1.21.1 require ( - fyne.io/fyne/v2 v2.4.4 + fyne.io/fyne/v2 v2.4.5 github.com/20after4/configdir v0.1.1 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e @@ -24,13 +24,14 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dweymouth/fyne-lyrics v0.0.0-20240517003322-3c47c48b1751 // indirect github.com/fredbi/uri v1.0.0 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect github.com/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b // indirect - github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb // indirect + github.com/go-text/render v0.1.0 // indirect github.com/go-text/typesetting v0.1.0 // indirect github.com/gopherjs/gopherjs v1.17.2 // indirect github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // indirect @@ -47,4 +48,4 @@ require ( honnef.co/go/js/dom v0.0.0-20210725211120-f030747120f2 // indirect ) -replace fyne.io/fyne/v2 v2.4.4 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130 +replace fyne.io/fyne/v2 v2.4.5 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130 diff --git a/go.sum b/go.sum index 26a1982..fb86551 100644 --- a/go.sum +++ b/go.sum @@ -69,6 +69,8 @@ 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/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= +github.com/dweymouth/fyne-lyrics v0.0.0-20240517003322-3c47c48b1751 h1:siUJZlGbTAzpIUDGDkzM3KD5g1oOHsJRUeGEW/hDHnw= +github.com/dweymouth/fyne-lyrics v0.0.0-20240517003322-3c47c48b1751/go.mod h1:wfA5DWb0Udzq4K7MkzK+EzrM7xAyGyUpNO2HbWBPNvs= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130 h1:HLnkaB2GRPMDaNgQ/HaEDKChU18UnLzJaq29dsfuGEA= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130/go.mod h1:VyrxAOZ3NRZRWBvNIJbfqoKOG4DdbewoPk7ozqJKNPY= github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e h1:89N7tfmGPA3kB3JPhm0UYbc2fjIMUgHPryj9jeuaeTg= @@ -103,14 +105,14 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b h1:GgabKamyOYguHqHjSkDACcgoPIz3w0Dis/zJ1wyHHHU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM= -github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb h1:S9I8pIVT5JHKDvmI1vQ0qs5fqxzUfhcZm/YbUC/8k1k= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-text/render v0.1.0 h1:osrmVDZNHuP1RSu3pNG7Z77Sd2xSbcb/xWytAj9kyVs= +github.com/go-text/render v0.1.0/go.mod h1:jqEuNMenrmj6QRnkdpeaP0oKGFLDNhDkVKwGjsWWYU4= github.com/go-text/typesetting v0.1.0 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw= github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI= -github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04 h1:zBx+p/W2aQYtNuyZNcTfinWvXBQwYtDfme051PR/lAY= -github.com/go-text/typesetting-utils v0.0.0-20231211103740-d9332ae51f04/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= +github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3 h1:levTnuLLUmpavLGbJYLJA7fQnKeS7P1eCdAlM+vReXk= +github.com/go-text/typesetting-utils v0.0.0-20240329101916-eee87fb235a3/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index 81a2a01..1456dc8 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -4,62 +4,51 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/widget" + fynelyrics "github.com/dweymouth/fyne-lyrics" "github.com/dweymouth/supersonic/backend/mediaprovider" ) type LyricsViewer struct { widget.BaseWidget - noLyricsLabel widget.Label - unsyncedViewer *widget.RichText + noLyricsLabel widget.Label + viewer *fynelyrics.LyricsViewer - container *container.Scroll - currentView lyricView + container *fyne.Container + isEmpty bool } -type lyricView int - -const ( - lyricViewEmpty lyricView = iota - lyricViewUnsynced - lyricViewSynced -) - func NewLyricsViewer() *LyricsViewer { l := &LyricsViewer{noLyricsLabel: widget.Label{ Text: "Lyrics not available", - }} + }, isEmpty: true} l.ExtendBaseWidget(l) - l.container = container.NewVScroll(&l.noLyricsLabel) + l.container = container.NewStack(&l.noLyricsLabel) return l } func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) { if lyrics == nil || len(lyrics.Lines) == 0 { - if l.currentView != lyricViewEmpty { - l.container.Content = &l.noLyricsLabel - l.currentView = lyricViewEmpty + if !l.isEmpty { + l.container.Objects[0] = &l.noLyricsLabel + l.isEmpty = true l.Refresh() } return } - if l.unsyncedViewer == nil { - l.unsyncedViewer = widget.NewRichText() - l.unsyncedViewer.Wrapping = fyne.TextWrapWord + if l.viewer == nil { + l.viewer = fynelyrics.NewLyricsViewer() + l.viewer.ActiveLyricPosition = fynelyrics.ActiveLyricPositionTopThird } - l.unsyncedViewer.Segments = nil - for _, line := range lyrics.Lines { - ts := &widget.TextSegment{Text: line.Text} - ts.Style.Alignment = fyne.TextAlignCenter - ts.Style.SizeName = widget.RichTextStyleSubHeading.SizeName - ts.Style.Inline = false - l.unsyncedViewer.Segments = append(l.unsyncedViewer.Segments, ts) + lines := make([]string, len(lyrics.Lines)) + for i, line := range lyrics.Lines { + lines[i] = line.Text } - l.unsyncedViewer.Refresh() - if l.currentView != lyricViewUnsynced { - l.container.Content = l.unsyncedViewer - l.currentView = lyricViewUnsynced + l.viewer.SetLyrics(lines, false /*synced*/) + if l.isEmpty { + l.container.Objects[0] = l.viewer + l.isEmpty = false l.Refresh() } } From e8d751bc2ad147da019f510384b6ae10c8ab7c25 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 May 2024 18:32:13 -0700 Subject: [PATCH 02/11] first draft of synced lyrics integration, missing seek support --- ui/browsing/nowplayingpage.go | 3 ++- ui/widgets/lyricsviewer.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index 3cc6d28..c3a9ea5 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -241,8 +241,9 @@ func (s *nowPlayingPageState) Restore() Page { var _ CanShowPlayTime = (*NowPlayingPage)(nil) -func (a *NowPlayingPage) OnPlayTimeUpdate(_, _ float64) { +func (a *NowPlayingPage) OnPlayTimeUpdate(curTime, _ float64) { a.formatStatusLine() + a.lyricsViewer.UpdatePlayPos(curTime) } var _ CanSelectAll = (*NowPlayingPage)(nil) diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index 1456dc8..21d277a 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -13,6 +13,8 @@ type LyricsViewer struct { noLyricsLabel widget.Label viewer *fynelyrics.LyricsViewer + lyrics *mediaprovider.Lyrics + nextLyricLine int container *fyne.Container isEmpty bool @@ -28,6 +30,8 @@ func NewLyricsViewer() *LyricsViewer { } func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) { + l.lyrics = lyrics + l.nextLyricLine = 0 if lyrics == nil || len(lyrics.Lines) == 0 { if !l.isEmpty { l.container.Objects[0] = &l.noLyricsLabel @@ -53,6 +57,33 @@ func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) { } } +func (l *LyricsViewer) UpdatePlayPos(timeSecs float64) { + if l.lyrics == nil || !l.lyrics.Synced { + return + } + // advance if needed + if l.lyrics.Lines[l.nextLyricLine].Start <= timeSecs { + l.viewer.NextLine() + if l.nextLyricLine < len(l.lyrics.Lines) { + l.nextLyricLine++ + } + } +} + +func (l *LyricsViewer) OnSeeked(timeSecs float64) { + if l.lyrics == nil || !l.lyrics.Synced { + return + } + curLine := 0 + for i, l := range l.lyrics.Lines { + if l.Start < timeSecs { + curLine = i + 1 + break + } + } + l.viewer.SetCurrentLine(curLine) +} + func (l *LyricsViewer) CreateRenderer() fyne.WidgetRenderer { return widget.NewSimpleRenderer(l.container) } From d156bf02e0ececb14cce5c48057ff4bcf7dab43c Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 May 2024 18:33:59 -0700 Subject: [PATCH 03/11] whoops missed synced param --- ui/widgets/lyricsviewer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index 21d277a..7d7c09f 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -49,7 +49,7 @@ func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) { for i, line := range lyrics.Lines { lines[i] = line.Text } - l.viewer.SetLyrics(lines, false /*synced*/) + l.viewer.SetLyrics(lines, lyrics.Synced) if l.isEmpty { l.container.Objects[0] = l.viewer l.isEmpty = false From e7b2b821f3ee77017d42224768e9608c1d6012d1 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Thu, 16 May 2024 19:22:08 -0700 Subject: [PATCH 04/11] hook up seek for synced lyrics --- backend/playbackengine.go | 16 ++++++++-------- backend/playbackmanager.go | 2 +- ui/bottompanel.go | 2 +- ui/browsing/browsingpane.go | 6 +++--- ui/browsing/nowplayingpage.go | 8 ++++++-- ui/widgets/lyricsviewer.go | 7 +++++++ 6 files changed, 26 insertions(+), 15 deletions(-) diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 3cb61ac..3a089d4 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -61,7 +61,7 @@ type playbackEngine struct { // registered callbacks onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track) - onPlayTimeUpdate []func(float64, float64) + onPlayTimeUpdate []func(float64, float64, bool) onLoopModeChange []func(LoopMode) onVolumeChange []func(int) onSeek []func() @@ -92,7 +92,7 @@ func NewPlaybackEngine( } p.OnTrackChange(pm.handleOnTrackChange) p.OnSeek(func() { - pm.doUpdateTimePos() + pm.doUpdateTimePos(true) pm.invokeNoArgCallbacks(pm.onSeek) }) p.OnStopped(pm.handleOnStopped) @@ -239,7 +239,7 @@ func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMo func (p *playbackEngine) StopAndClearPlayQueue() { changed := len(p.playQueue) > 0 p.player.Stop() - p.doUpdateTimePos() + p.doUpdateTimePos(false) p.playQueue = nil p.nowPlayingIdx = -1 if changed { @@ -399,7 +399,7 @@ func (p *playbackEngine) handleOnTrackChange() { p.curTrackTime = float64(p.playQueue[p.nowPlayingIdx].Duration) p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled p.invokeOnSongChangeCallbacks() - p.doUpdateTimePos() + p.doUpdateTimePos(false) p.setNextTrackBasedOnLoopMode(false) } @@ -407,7 +407,7 @@ func (p *playbackEngine) handleOnStopped() { p.playTimeStopwatch.Stop() p.checkScrobble() p.stopPollTimePos() - p.doUpdateTimePos() + p.doUpdateTimePos(false) p.invokeOnSongChangeCallbacks() p.invokeNoArgCallbacks(p.onStopped) p.wasStopped = true @@ -568,7 +568,7 @@ func (p *playbackEngine) startPollTimePos() { pollingTick.Stop() return case <-pollingTick.C: - p.doUpdateTimePos() + p.doUpdateTimePos(false) } } }() @@ -581,7 +581,7 @@ func (p *playbackEngine) stopPollTimePos() { } } -func (p *playbackEngine) doUpdateTimePos() { +func (p *playbackEngine) doUpdateTimePos(seeked bool) { if p.callbacksDisabled { return } @@ -590,6 +590,6 @@ func (p *playbackEngine) doUpdateTimePos() { p.latestTrackPosition = s.TimePos } for _, cb := range p.onPlayTimeUpdate { - cb(s.TimePos, s.Duration) + cb(s.TimePos, s.Duration, seeked) } } diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 4f577c2..2fdab18 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -60,7 +60,7 @@ func (p *PlaybackManager) OnSongChange(cb func(nowPlaying *mediaprovider.Track, } // Registers a callback that is notified whenever the play time should be updated. -func (p *PlaybackManager) OnPlayTimeUpdate(cb func(float64, float64)) { +func (p *PlaybackManager) OnPlayTimeUpdate(cb func(curTime float64, totalTime float64, seeked bool)) { p.engine.onPlayTimeUpdate = append(p.engine.onPlayTimeUpdate, cb) } diff --git a/ui/bottompanel.go b/ui/bottompanel.go index 1f2b9c1..7af0d53 100644 --- a/ui/bottompanel.go +++ b/ui/bottompanel.go @@ -35,7 +35,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, contr *controller.Controller) * bp.ExtendBaseWidget(bp) pm.OnSongChange(bp.onSongChange) - pm.OnPlayTimeUpdate(func(cur, total float64) { + pm.OnPlayTimeUpdate(func(cur, total float64, _ bool) { if !pm.IsSeeking() { bp.Controls.UpdatePlayTime(cur, total) } diff --git a/ui/browsing/browsingpane.go b/ui/browsing/browsingpane.go index 15098ce..347941c 100644 --- a/ui/browsing/browsingpane.go +++ b/ui/browsing/browsingpane.go @@ -46,7 +46,7 @@ type CanShowNowPlaying interface { } type CanShowPlayTime interface { - OnPlayTimeUpdate(curTime, totalTime float64) + OnPlayTimeUpdate(curTime, totalTime float64, seeked bool) } type CanShowPlayQueue interface { @@ -222,12 +222,12 @@ func (b *BrowsingPane) onSongChange(song, lastScrobbledIfAny *mediaprovider.Trac } } -func (b *BrowsingPane) onPlayTimeUpdate(cur, total float64) { +func (b *BrowsingPane) onPlayTimeUpdate(cur, total float64, seeked bool) { if b.curPage == nil { return } if p, ok := b.curPage.(CanShowPlayTime); ok { - p.OnPlayTimeUpdate(cur, total) + p.OnPlayTimeUpdate(cur, total, seeked) } } diff --git a/ui/browsing/nowplayingpage.go b/ui/browsing/nowplayingpage.go index c3a9ea5..7c2f95a 100644 --- a/ui/browsing/nowplayingpage.go +++ b/ui/browsing/nowplayingpage.go @@ -241,9 +241,13 @@ func (s *nowPlayingPageState) Restore() Page { var _ CanShowPlayTime = (*NowPlayingPage)(nil) -func (a *NowPlayingPage) OnPlayTimeUpdate(curTime, _ float64) { +func (a *NowPlayingPage) OnPlayTimeUpdate(curTime, _ float64, seeked bool) { a.formatStatusLine() - a.lyricsViewer.UpdatePlayPos(curTime) + if seeked { + a.lyricsViewer.OnSeeked(curTime) + } else { + a.lyricsViewer.UpdatePlayPos(curTime) + } } var _ CanSelectAll = (*NowPlayingPage)(nil) diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index 7d7c09f..4df1796 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -15,6 +15,7 @@ type LyricsViewer struct { viewer *fynelyrics.LyricsViewer lyrics *mediaprovider.Lyrics nextLyricLine int + lastPlayPos float64 container *fyne.Container isEmpty bool @@ -61,6 +62,11 @@ func (l *LyricsViewer) UpdatePlayPos(timeSecs float64) { if l.lyrics == nil || !l.lyrics.Synced { return } + if timeSecs < l.lastPlayPos { + l.OnSeeked(timeSecs) + return + } + l.lastPlayPos = timeSecs // advance if needed if l.lyrics.Lines[l.nextLyricLine].Start <= timeSecs { l.viewer.NextLine() @@ -71,6 +77,7 @@ func (l *LyricsViewer) UpdatePlayPos(timeSecs float64) { } func (l *LyricsViewer) OnSeeked(timeSecs float64) { + l.lastPlayPos = timeSecs if l.lyrics == nil || !l.lyrics.Synced { return } From 3273f45594e52d8a7148dc3c2cc132ae4177a07d Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 17 May 2024 08:38:43 -0700 Subject: [PATCH 05/11] hook up with Jellyfin lyrics --- .../jellyfin/jellyfinmediaprovider.go | 22 +++++++++++++++++++ go.mod | 2 +- go.sum | 2 ++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go index 882c885..4db015e 100644 --- a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -354,6 +354,28 @@ func (j *jellyfinMediaProvider) RescanLibrary() error { return j.client.RefreshLibrary() } +var _ mediaprovider.LyricsProvider = (*jellyfinMediaProvider)(nil) + +func (j *jellyfinMediaProvider) GetLyrics(tr *mediaprovider.Track) (*mediaprovider.Lyrics, error) { + l, err := j.client.GetLyrics(tr.ID) + if err != nil { + return nil, err + } + return &mediaprovider.Lyrics{ + Title: l.Metadata.Title, + Artist: l.Metadata.Artist, + Synced: l.Metadata.IsSynced, + Lines: sharedutil.MapSlice(l.Lyrics, toLyricLine), + }, nil +} + +func toLyricLine(ll jellyfin.LyricLine) mediaprovider.LyricLine { + return mediaprovider.LyricLine{ + Text: ll.Text, + Start: float64(ll.Start) / float64(runTimeTicksPerSecond), + } +} + func toTrack(ch *jellyfin.Song) *mediaprovider.Track { if ch == nil { return nil diff --git a/go.mod b/go.mod index 1502ea4..f6f0ec6 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( fyne.io/fyne/v2 v2.4.5 github.com/20after4/configdir v0.1.1 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 - github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e + github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee github.com/dweymouth/go-subsonic v0.0.0-20240417012336-798603e9f3a3 github.com/fsnotify/fsnotify v1.6.0 diff --git a/go.sum b/go.sum index fb86551..a498058 100644 --- a/go.sum +++ b/go.sum @@ -75,6 +75,8 @@ github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130 h1:HLnkaB2 github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130/go.mod h1:VyrxAOZ3NRZRWBvNIJbfqoKOG4DdbewoPk7ozqJKNPY= github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e h1:89N7tfmGPA3kB3JPhm0UYbc2fjIMUgHPryj9jeuaeTg= github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= +github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs= +github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0= github.com/dweymouth/go-subsonic v0.0.0-20240417012336-798603e9f3a3 h1:DJwu4MrQ6cPJF+eqcuP0eGKelsaZbHa08xsRHrbJYc8= From 2e62bdf01d4be4c454fb6cbf49b60e2ec928c315 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 May 2024 16:15:57 -0700 Subject: [PATCH 06/11] fix lyrics seeking bug --- ui/widgets/lyricsviewer.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index 4df1796..9354aaf 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -81,14 +81,16 @@ func (l *LyricsViewer) OnSeeked(timeSecs float64) { if l.lyrics == nil || !l.lyrics.Synced { return } - curLine := 0 + + nextLine := 0 // first line that starts after timeSecs for i, l := range l.lyrics.Lines { - if l.Start < timeSecs { - curLine = i + 1 + if l.Start > timeSecs { + nextLine = i break } } - l.viewer.SetCurrentLine(curLine) + l.nextLyricLine = nextLine + l.viewer.SetCurrentLine(nextLine /*one-indexed*/) } func (l *LyricsViewer) CreateRenderer() fyne.WidgetRenderer { From d8ebc17566ed79fd4e8684fc8dc0b7200f4c5cfe Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 May 2024 16:26:36 -0700 Subject: [PATCH 07/11] make sure to jump straight to correct lyric for the first time update --- ui/widgets/lyricsviewer.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index 9354aaf..ca410c3 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -17,6 +17,10 @@ type LyricsViewer struct { nextLyricLine int lastPlayPos float64 + // keeps track if UpdatePlayPos has been called yet + // for the current lyrics + firstUpdate bool + container *fyne.Container isEmpty bool } @@ -33,6 +37,7 @@ func NewLyricsViewer() *LyricsViewer { func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) { l.lyrics = lyrics l.nextLyricLine = 0 + l.firstUpdate = true if lyrics == nil || len(lyrics.Lines) == 0 { if !l.isEmpty { l.container.Objects[0] = &l.noLyricsLabel @@ -62,8 +67,9 @@ func (l *LyricsViewer) UpdatePlayPos(timeSecs float64) { if l.lyrics == nil || !l.lyrics.Synced { return } - if timeSecs < l.lastPlayPos { + if l.firstUpdate || timeSecs < l.lastPlayPos { l.OnSeeked(timeSecs) + l.firstUpdate = false return } l.lastPlayPos = timeSecs From e4854abb20fd257742aac63e57aff68ca70fae83 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 May 2024 16:37:32 -0700 Subject: [PATCH 08/11] infer synced lyrics for Jellyfin even if metadata missing --- backend/mediaprovider/jellyfin/jellyfinmediaprovider.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go index 4db015e..9386052 100644 --- a/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go +++ b/backend/mediaprovider/jellyfin/jellyfinmediaprovider.go @@ -364,7 +364,7 @@ func (j *jellyfinMediaProvider) GetLyrics(tr *mediaprovider.Track) (*mediaprovid return &mediaprovider.Lyrics{ Title: l.Metadata.Title, Artist: l.Metadata.Artist, - Synced: l.Metadata.IsSynced, + Synced: l.Metadata.IsSynced || (len(l.Lyrics) > 0 && l.Lyrics[0].Start > 0), Lines: sharedutil.MapSlice(l.Lyrics, toLyricLine), }, nil } From 4946b0172615bec12d8fa4700e8c69fc31cb0b03 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 May 2024 17:28:10 -0700 Subject: [PATCH 09/11] update fyne-lyrics for bug fix --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f6f0ec6..a15dd67 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( fyne.io/fyne/v2 v2.4.5 github.com/20after4/configdir v0.1.1 github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 + github.com/dweymouth/fyne-lyrics v0.0.0-20240519002116-250ccc6f4a5f github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee github.com/dweymouth/go-subsonic v0.0.0-20240417012336-798603e9f3a3 @@ -24,7 +25,6 @@ require ( github.com/alessio/shellescape v1.4.1 // indirect github.com/danieljoos/wincred v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dweymouth/fyne-lyrics v0.0.0-20240517003322-3c47c48b1751 // indirect github.com/fredbi/uri v1.0.0 // indirect github.com/fyne-io/gl-js v0.0.0-20220119005834-d2da28d9ccfe // indirect github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // indirect diff --git a/go.sum b/go.sum index a498058..75f73e7 100644 --- a/go.sum +++ b/go.sum @@ -69,12 +69,10 @@ 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/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI= github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E= -github.com/dweymouth/fyne-lyrics v0.0.0-20240517003322-3c47c48b1751 h1:siUJZlGbTAzpIUDGDkzM3KD5g1oOHsJRUeGEW/hDHnw= -github.com/dweymouth/fyne-lyrics v0.0.0-20240517003322-3c47c48b1751/go.mod h1:wfA5DWb0Udzq4K7MkzK+EzrM7xAyGyUpNO2HbWBPNvs= +github.com/dweymouth/fyne-lyrics v0.0.0-20240519002116-250ccc6f4a5f h1://6ChT8JiE/Ar4CNApFE7QzzbGrjKnSMZCfWcWViRpg= +github.com/dweymouth/fyne-lyrics v0.0.0-20240519002116-250ccc6f4a5f/go.mod h1:wfA5DWb0Udzq4K7MkzK+EzrM7xAyGyUpNO2HbWBPNvs= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130 h1:HLnkaB2GRPMDaNgQ/HaEDKChU18UnLzJaq29dsfuGEA= github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130/go.mod h1:VyrxAOZ3NRZRWBvNIJbfqoKOG4DdbewoPk7ozqJKNPY= -github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e h1:89N7tfmGPA3kB3JPhm0UYbc2fjIMUgHPryj9jeuaeTg= -github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs= github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645/go.mod h1:fcUagHBaQnt06GmBAllNE0J4O/7064zXRWdqnTTtVjI= github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY= From b4b079478aede0c1f661ff768f8e7a3a6d75c10f Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Sat, 18 May 2024 17:42:08 -0700 Subject: [PATCH 10/11] fix crashing out of range after last lyric --- ui/widgets/lyricsviewer.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/ui/widgets/lyricsviewer.go b/ui/widgets/lyricsviewer.go index ca410c3..54adf50 100644 --- a/ui/widgets/lyricsviewer.go +++ b/ui/widgets/lyricsviewer.go @@ -76,7 +76,7 @@ func (l *LyricsViewer) UpdatePlayPos(timeSecs float64) { // advance if needed if l.lyrics.Lines[l.nextLyricLine].Start <= timeSecs { l.viewer.NextLine() - if l.nextLyricLine < len(l.lyrics.Lines) { + if l.nextLyricLine < len(l.lyrics.Lines)-1 { l.nextLyricLine++ } } @@ -88,14 +88,22 @@ func (l *LyricsViewer) OnSeeked(timeSecs float64) { return } - nextLine := 0 // first line that starts after timeSecs + // find first line that starts after timeSecs + nextLine := -1 for i, l := range l.lyrics.Lines { if l.Start > timeSecs { nextLine = i break } } - l.nextLyricLine = nextLine + + if nextLine == -1 { + // last lyric + nextLine = len(l.lyrics.Lines) + l.nextLyricLine = nextLine - 1 + } else { + l.nextLyricLine = nextLine + } l.viewer.SetCurrentLine(nextLine /*one-indexed*/) } From ce647c27b9eb24ac0bb60c78273847a5436e266e Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Mon, 20 May 2024 18:17:41 -0700 Subject: [PATCH 11/11] Fix #387: pass offset to subsonic random album fetch call --- backend/mediaprovider/helpers/iterators.go | 8 +++++--- backend/mediaprovider/subsonic/albumiterator.go | 6 +++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/backend/mediaprovider/helpers/iterators.go b/backend/mediaprovider/helpers/iterators.go index e65379d..a8f631c 100644 --- a/backend/mediaprovider/helpers/iterators.go +++ b/backend/mediaprovider/helpers/iterators.go @@ -98,7 +98,7 @@ type randomAlbumIter struct { // Once we start getting back too many already-returned albums, // switch to requesting more albums from a deterministic sort order. deterministicFetcher AlbumFetchFn - ramdomFetcher AlbumFetchFn + randomFetcher AlbumFetchFn phaseTwo bool offset int done bool @@ -109,7 +109,7 @@ func NewRandomAlbumIter(deterministicFetcher, randomFetcher AlbumFetchFn, filter filter: filter, prefetchCB: prefetchCoverCB, deterministicFetcher: deterministicFetcher, - ramdomFetcher: randomFetcher, + randomFetcher: randomFetcher, albumIDSet: make(map[string]bool), } } @@ -145,13 +145,14 @@ func (r *randomAlbumIter) Next() *mediaprovider.Album { } } } else { - albums, err := r.ramdomFetcher(0 /*offset - doesn't matter for random*/, 25) + albums, err := r.randomFetcher(r.offset, 25) if err != nil { log.Println(err) r.done = true r.albumIDSet = nil return nil } + r.offset += len(albums) var hitCount int for _, album := range albums { if _, ok := r.albumIDSet[album.ID]; !ok { @@ -169,6 +170,7 @@ func (r *randomAlbumIter) Next() *mediaprovider.Album { } if successRatio := float64(hitCount) / float64(25); successRatio < 0.3 { r.phaseTwo = true + r.offset = 0 } } } diff --git a/backend/mediaprovider/subsonic/albumiterator.go b/backend/mediaprovider/subsonic/albumiterator.go index 5637460..6c0b846 100644 --- a/backend/mediaprovider/subsonic/albumiterator.go +++ b/backend/mediaprovider/subsonic/albumiterator.go @@ -225,7 +225,11 @@ func (s *subsonicMediaProvider) newRandomIter(filter mediaprovider.AlbumFilter, return helpers.NewRandomAlbumIter( s.fetchFnFromStandardSort("newest"), makeFetchFn(func(offset, limit int) ([]*subsonic.AlbumID3, error) { - return s.client.GetAlbumList2("random", map[string]string{"size": strconv.Itoa(limit)}) + args := map[string]string{ + "size": strconv.Itoa(limit), + "offset": strconv.Itoa(offset), + } + return s.client.GetAlbumList2("random", args) }), filter, s.prefetchCoverCB) }