Closes #519: Add click-to-seek to lyrics viewer
This commit is contained in:
@@ -9,7 +9,6 @@ require (
|
|||||||
github.com/cenkalti/dominantcolor v1.0.3
|
github.com/cenkalti/dominantcolor v1.0.3
|
||||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c
|
||||||
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64
|
|
||||||
github.com/dweymouth/fyne-tooltip v0.3.0
|
github.com/dweymouth/fyne-tooltip v0.3.0
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20250531151636-29591764f0a0
|
github.com/dweymouth/go-jellyfin v0.0.0-20250531151636-29591764f0a0
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
@@ -17,6 +16,7 @@ require (
|
|||||||
github.com/hashicorp/go-retryablehttp v0.7.7
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8
|
github.com/pelletier/go-toml/v2 v2.0.8
|
||||||
github.com/quarckster/go-mpris-server v1.0.3
|
github.com/quarckster/go-mpris-server v1.0.3
|
||||||
|
github.com/supersonic-app/fyne-lyrics v0.0.0-20250614151306-b1880a70a410
|
||||||
github.com/supersonic-app/go-mpv v0.1.0
|
github.com/supersonic-app/go-mpv v0.1.0
|
||||||
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d
|
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d
|
||||||
github.com/supersonic-app/go-upnpcast v0.0.0-20250330154256-b957204209a5
|
github.com/supersonic-app/go-upnpcast v0.0.0-20250330154256-b957204209a5
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1 h1:mGvOb3zxl4vCLv+
|
|||||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E=
|
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1/go.mod h1:ZNCLJfehvEf34B7BbLKjgpsL9lyW7q938w/GY1XgV4E=
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c h1:UXwfQ1CzSe7l8CSPr/Fyu0LViv0oRUDy3SEUafnSxdQ=
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c h1:UXwfQ1CzSe7l8CSPr/Fyu0LViv0oRUDy3SEUafnSxdQ=
|
||||||
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c/go.mod h1:Idgzr4LYzve8IPHF1stLO1bdGQZnDKt/bT9WfJflCGw=
|
github.com/dweymouth/fyne-advanced-list v0.0.0-20250211191927-58ea85eec72c/go.mod h1:Idgzr4LYzve8IPHF1stLO1bdGQZnDKt/bT9WfJflCGw=
|
||||||
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64 h1:RUIrnGY034rDMlcOui/daurwX5b+52KdUKhH9aXaDSg=
|
|
||||||
github.com/dweymouth/fyne-lyrics v0.0.0-20240528234907-15eee7ce5e64/go.mod h1:3YrjFDHMlhCsSZ/OvmJCxWm9QHSgOVWZBxnraZz9Z7c=
|
|
||||||
github.com/dweymouth/fyne-tooltip v0.3.0 h1:NKCyTkh9NtvnTsiHtTOtaJzRDOFYP8AckQ2tyhOh6JY=
|
github.com/dweymouth/fyne-tooltip v0.3.0 h1:NKCyTkh9NtvnTsiHtTOtaJzRDOFYP8AckQ2tyhOh6JY=
|
||||||
github.com/dweymouth/fyne-tooltip v0.3.0/go.mod h1:m04ShLW/Tp6LXrNieTumApvNgo7YSB+wi+jZTN+kDBU=
|
github.com/dweymouth/fyne-tooltip v0.3.0/go.mod h1:m04ShLW/Tp6LXrNieTumApvNgo7YSB+wi+jZTN+kDBU=
|
||||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250406173843-f5cb002423a5 h1:/gxlRSmndtF8QVxV3oFjlozm2clkMlLBlfiKFtehH+M=
|
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250406173843-f5cb002423a5 h1:/gxlRSmndtF8QVxV3oFjlozm2clkMlLBlfiKFtehH+M=
|
||||||
@@ -115,6 +113,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||||||
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
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/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/supersonic-app/fyne-lyrics v0.0.0-20250614151306-b1880a70a410 h1:wuDHCo6SU6r0KMm0EuI5FGVJCuLK4hcuPcMKmln8oCA=
|
||||||
|
github.com/supersonic-app/fyne-lyrics v0.0.0-20250614151306-b1880a70a410/go.mod h1:XvGBPgXWkKrK+5OsoVkTcTZmwZac1Sj6miEn8q6uA20=
|
||||||
github.com/supersonic-app/go-mpv v0.1.0 h1:U+cCnLQxmpqx5mY6nMlC0J4uIdCCXUbAjpjS04XkFu8=
|
github.com/supersonic-app/go-mpv v0.1.0 h1:U+cCnLQxmpqx5mY6nMlC0J4uIdCCXUbAjpjS04XkFu8=
|
||||||
github.com/supersonic-app/go-mpv v0.1.0/go.mod h1:1bQz6kBQumJopXEbkiqoLxIXLy7F7yWFBvknvpAtIC0=
|
github.com/supersonic-app/go-mpv v0.1.0/go.mod h1:1bQz6kBQumJopXEbkiqoLxIXLy7F7yWFBvknvpAtIC0=
|
||||||
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d h1:70+Nn7yh+cfeKqqXVTdpneFqXuvrBLyP7U6GVUsjTU4=
|
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d h1:70+Nn7yh+cfeKqqXVTdpneFqXuvrBLyP7U6GVUsjTU4=
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ type NowPlayingPage struct {
|
|||||||
// volatile state
|
// volatile state
|
||||||
nowPlaying mediaprovider.MediaItem
|
nowPlaying mediaprovider.MediaItem
|
||||||
nowPlayingID string
|
nowPlayingID string
|
||||||
|
curLyrics *mediaprovider.Lyrics
|
||||||
curLyricsID string // id of track currently shown in lyrics
|
curLyricsID string // id of track currently shown in lyrics
|
||||||
curRelatedID string // id of track currrently used to populate related list
|
curRelatedID string // id of track currrently used to populate related list
|
||||||
totalTime float64
|
totalTime float64
|
||||||
@@ -177,13 +178,20 @@ func NewNowPlayingPage(
|
|||||||
a.relatedList.OnShowTrackInfo = func(track *mediaprovider.Track) {
|
a.relatedList.OnShowTrackInfo = func(track *mediaprovider.Track) {
|
||||||
a.contr.ShowTrackInfoDialog(track)
|
a.contr.ShowTrackInfoDialog(track)
|
||||||
}
|
}
|
||||||
a.lyricsViewer = widgets.NewLyricsViewer()
|
a.lyricsViewer = widgets.NewLyricsViewer(a.onSeekToLyricLine)
|
||||||
a.statusLabel = widget.NewLabel(lang.L("Stopped"))
|
a.statusLabel = widget.NewLabel(lang.L("Stopped"))
|
||||||
|
|
||||||
a.Reload()
|
a.Reload()
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *NowPlayingPage) onSeekToLyricLine(lineNum int) {
|
||||||
|
if a.curLyrics != nil && len(a.curLyrics.Lines) > lineNum-1 {
|
||||||
|
time := a.curLyrics.Lines[lineNum-1].Start
|
||||||
|
a.pm.SeekSeconds(time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
|
func (a *NowPlayingPage) CreateRenderer() fyne.WidgetRenderer {
|
||||||
if a.container == nil {
|
if a.container == nil {
|
||||||
initialTab := 0
|
initialTab := 0
|
||||||
@@ -341,6 +349,7 @@ func (a *NowPlayingPage) updateLyrics() {
|
|||||||
}
|
}
|
||||||
if a.nowPlaying == nil || a.nowPlaying.Metadata().Type == mediaprovider.MediaItemTypeRadioStation {
|
if a.nowPlaying == nil || a.nowPlaying.Metadata().Type == mediaprovider.MediaItemTypeRadioStation {
|
||||||
a.lyricsViewer.SetLyrics(nil)
|
a.lyricsViewer.SetLyrics(nil)
|
||||||
|
a.curLyrics = nil
|
||||||
a.curLyricsID = ""
|
a.curLyricsID = ""
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -350,6 +359,7 @@ func (a *NowPlayingPage) updateLyrics() {
|
|||||||
a.lyricsLoading.Start()
|
a.lyricsLoading.Start()
|
||||||
// set the widget to an empty (not nil) lyric during fetch
|
// set the widget to an empty (not nil) lyric during fetch
|
||||||
// to keep it from showing "Lyrics not available"
|
// to keep it from showing "Lyrics not available"
|
||||||
|
a.lyricsViewer.DisableTapToSeek()
|
||||||
a.lyricsViewer.SetLyrics(&mediaprovider.Lyrics{Synced: true,
|
a.lyricsViewer.SetLyrics(&mediaprovider.Lyrics{Synced: true,
|
||||||
Lines: []mediaprovider.LyricLine{{Text: ""}}})
|
Lines: []mediaprovider.LyricLine{{Text: ""}}})
|
||||||
tr, _ := a.nowPlaying.(*mediaprovider.Track)
|
tr, _ := a.nowPlaying.(*mediaprovider.Track)
|
||||||
@@ -376,7 +386,9 @@ func (a *NowPlayingPage) fetchLyrics(ctx context.Context, song *mediaprovider.Tr
|
|||||||
default:
|
default:
|
||||||
fyne.Do(func() {
|
fyne.Do(func() {
|
||||||
a.lyricsLoading.Stop()
|
a.lyricsLoading.Stop()
|
||||||
|
a.lyricsViewer.EnableTapToSeek()
|
||||||
a.lyricsViewer.SetLyrics(lyrics)
|
a.lyricsViewer.SetLyrics(lyrics)
|
||||||
|
a.curLyrics = lyrics
|
||||||
if lyrics != nil {
|
if lyrics != nil {
|
||||||
a.lyricsViewer.OnSeeked(a.lastPlayPos)
|
a.lyricsViewer.OnSeeked(a.lastPlayPos)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/lang"
|
"fyne.io/fyne/v2/lang"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
fynelyrics "github.com/dweymouth/fyne-lyrics"
|
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/ui/theme"
|
"github.com/dweymouth/supersonic/ui/theme"
|
||||||
|
fynelyrics "github.com/supersonic-app/fyne-lyrics"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LyricsViewer struct {
|
type LyricsViewer struct {
|
||||||
@@ -23,21 +23,36 @@ type LyricsViewer struct {
|
|||||||
// for the current lyrics
|
// for the current lyrics
|
||||||
firstUpdate bool
|
firstUpdate bool
|
||||||
|
|
||||||
|
onSeekToLine func(int)
|
||||||
|
|
||||||
container *fyne.Container
|
container *fyne.Container
|
||||||
isEmpty bool
|
isEmpty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLyricsViewer() *LyricsViewer {
|
func NewLyricsViewer(onSeekToLine func(int)) *LyricsViewer {
|
||||||
l := &LyricsViewer{
|
l := &LyricsViewer{
|
||||||
noLyricsMsg: container.NewCenter(NewInfoMessage(
|
noLyricsMsg: container.NewCenter(NewInfoMessage(
|
||||||
lang.L("Lyrics not available"), "")),
|
lang.L("Lyrics not available"), "")),
|
||||||
isEmpty: true,
|
isEmpty: true,
|
||||||
|
onSeekToLine: onSeekToLine,
|
||||||
}
|
}
|
||||||
l.ExtendBaseWidget(l)
|
l.ExtendBaseWidget(l)
|
||||||
l.container = container.NewStack(l.noLyricsMsg)
|
l.container = container.NewStack(l.noLyricsMsg)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LyricsViewer) DisableTapToSeek() {
|
||||||
|
if l.viewer != nil {
|
||||||
|
l.viewer.OnLyricTapped = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LyricsViewer) EnableTapToSeek() {
|
||||||
|
if l.viewer != nil {
|
||||||
|
l.viewer.OnLyricTapped = l.onSeekToLine
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) {
|
func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) {
|
||||||
l.lyrics = lyrics
|
l.lyrics = lyrics
|
||||||
l.nextLyricLine = 0
|
l.nextLyricLine = 0
|
||||||
@@ -55,6 +70,7 @@ func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) {
|
|||||||
l.viewer = fynelyrics.NewLyricsViewer()
|
l.viewer = fynelyrics.NewLyricsViewer()
|
||||||
l.viewer.ActiveLyricPosition = fynelyrics.ActiveLyricPositionUpperMiddle
|
l.viewer.ActiveLyricPosition = fynelyrics.ActiveLyricPositionUpperMiddle
|
||||||
l.viewer.InactiveLyricColorName = theme.ColorNameInactiveLyric
|
l.viewer.InactiveLyricColorName = theme.ColorNameInactiveLyric
|
||||||
|
l.viewer.OnLyricTapped = l.onSeekToLine
|
||||||
}
|
}
|
||||||
lines := make([]string, len(lyrics.Lines))
|
lines := make([]string, len(lyrics.Lines))
|
||||||
for i, line := range lyrics.Lines {
|
for i, line := range lyrics.Lines {
|
||||||
|
|||||||
Reference in New Issue
Block a user