Merge branch 'main' into new-add-to-playlist
This commit is contained in:
@@ -98,7 +98,7 @@ type randomAlbumIter struct {
|
|||||||
// Once we start getting back too many already-returned albums,
|
// Once we start getting back too many already-returned albums,
|
||||||
// switch to requesting more albums from a deterministic sort order.
|
// switch to requesting more albums from a deterministic sort order.
|
||||||
deterministicFetcher AlbumFetchFn
|
deterministicFetcher AlbumFetchFn
|
||||||
ramdomFetcher AlbumFetchFn
|
randomFetcher AlbumFetchFn
|
||||||
phaseTwo bool
|
phaseTwo bool
|
||||||
offset int
|
offset int
|
||||||
done bool
|
done bool
|
||||||
@@ -109,7 +109,7 @@ func NewRandomAlbumIter(deterministicFetcher, randomFetcher AlbumFetchFn, filter
|
|||||||
filter: filter,
|
filter: filter,
|
||||||
prefetchCB: prefetchCoverCB,
|
prefetchCB: prefetchCoverCB,
|
||||||
deterministicFetcher: deterministicFetcher,
|
deterministicFetcher: deterministicFetcher,
|
||||||
ramdomFetcher: randomFetcher,
|
randomFetcher: randomFetcher,
|
||||||
albumIDSet: make(map[string]bool),
|
albumIDSet: make(map[string]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,13 +145,14 @@ func (r *randomAlbumIter) Next() *mediaprovider.Album {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
albums, err := r.ramdomFetcher(0 /*offset - doesn't matter for random*/, 25)
|
albums, err := r.randomFetcher(r.offset, 25)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
r.done = true
|
r.done = true
|
||||||
r.albumIDSet = nil
|
r.albumIDSet = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
r.offset += len(albums)
|
||||||
var hitCount int
|
var hitCount int
|
||||||
for _, album := range albums {
|
for _, album := range albums {
|
||||||
if _, ok := r.albumIDSet[album.ID]; !ok {
|
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 {
|
if successRatio := float64(hitCount) / float64(25); successRatio < 0.3 {
|
||||||
r.phaseTwo = true
|
r.phaseTwo = true
|
||||||
|
r.offset = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -354,6 +354,28 @@ func (j *jellyfinMediaProvider) RescanLibrary() error {
|
|||||||
return j.client.RefreshLibrary()
|
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 || (len(l.Lyrics) > 0 && l.Lyrics[0].Start > 0),
|
||||||
|
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 {
|
func toTrack(ch *jellyfin.Song) *mediaprovider.Track {
|
||||||
if ch == nil {
|
if ch == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -225,7 +225,11 @@ func (s *subsonicMediaProvider) newRandomIter(filter mediaprovider.AlbumFilter,
|
|||||||
return helpers.NewRandomAlbumIter(
|
return helpers.NewRandomAlbumIter(
|
||||||
s.fetchFnFromStandardSort("newest"),
|
s.fetchFnFromStandardSort("newest"),
|
||||||
makeFetchFn(func(offset, limit int) ([]*subsonic.AlbumID3, error) {
|
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)
|
filter, s.prefetchCoverCB)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ type playbackEngine struct {
|
|||||||
|
|
||||||
// registered callbacks
|
// registered callbacks
|
||||||
onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track)
|
onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track)
|
||||||
onPlayTimeUpdate []func(float64, float64)
|
onPlayTimeUpdate []func(float64, float64, bool)
|
||||||
onLoopModeChange []func(LoopMode)
|
onLoopModeChange []func(LoopMode)
|
||||||
onVolumeChange []func(int)
|
onVolumeChange []func(int)
|
||||||
onSeek []func()
|
onSeek []func()
|
||||||
@@ -92,7 +92,7 @@ func NewPlaybackEngine(
|
|||||||
}
|
}
|
||||||
p.OnTrackChange(pm.handleOnTrackChange)
|
p.OnTrackChange(pm.handleOnTrackChange)
|
||||||
p.OnSeek(func() {
|
p.OnSeek(func() {
|
||||||
pm.doUpdateTimePos()
|
pm.doUpdateTimePos(true)
|
||||||
pm.invokeNoArgCallbacks(pm.onSeek)
|
pm.invokeNoArgCallbacks(pm.onSeek)
|
||||||
})
|
})
|
||||||
p.OnStopped(pm.handleOnStopped)
|
p.OnStopped(pm.handleOnStopped)
|
||||||
@@ -239,7 +239,7 @@ func (p *playbackEngine) LoadTracks(tracks []*mediaprovider.Track, insertQueueMo
|
|||||||
func (p *playbackEngine) StopAndClearPlayQueue() {
|
func (p *playbackEngine) StopAndClearPlayQueue() {
|
||||||
changed := len(p.playQueue) > 0
|
changed := len(p.playQueue) > 0
|
||||||
p.player.Stop()
|
p.player.Stop()
|
||||||
p.doUpdateTimePos()
|
p.doUpdateTimePos(false)
|
||||||
p.playQueue = nil
|
p.playQueue = nil
|
||||||
p.nowPlayingIdx = -1
|
p.nowPlayingIdx = -1
|
||||||
if changed {
|
if changed {
|
||||||
@@ -399,7 +399,7 @@ func (p *playbackEngine) handleOnTrackChange() {
|
|||||||
p.curTrackTime = float64(p.playQueue[p.nowPlayingIdx].Duration)
|
p.curTrackTime = float64(p.playQueue[p.nowPlayingIdx].Duration)
|
||||||
p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
|
p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
|
||||||
p.invokeOnSongChangeCallbacks()
|
p.invokeOnSongChangeCallbacks()
|
||||||
p.doUpdateTimePos()
|
p.doUpdateTimePos(false)
|
||||||
p.setNextTrackBasedOnLoopMode(false)
|
p.setNextTrackBasedOnLoopMode(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,7 +407,7 @@ func (p *playbackEngine) handleOnStopped() {
|
|||||||
p.playTimeStopwatch.Stop()
|
p.playTimeStopwatch.Stop()
|
||||||
p.checkScrobble()
|
p.checkScrobble()
|
||||||
p.stopPollTimePos()
|
p.stopPollTimePos()
|
||||||
p.doUpdateTimePos()
|
p.doUpdateTimePos(false)
|
||||||
p.invokeOnSongChangeCallbacks()
|
p.invokeOnSongChangeCallbacks()
|
||||||
p.invokeNoArgCallbacks(p.onStopped)
|
p.invokeNoArgCallbacks(p.onStopped)
|
||||||
p.wasStopped = true
|
p.wasStopped = true
|
||||||
@@ -568,7 +568,7 @@ func (p *playbackEngine) startPollTimePos() {
|
|||||||
pollingTick.Stop()
|
pollingTick.Stop()
|
||||||
return
|
return
|
||||||
case <-pollingTick.C:
|
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 {
|
if p.callbacksDisabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -590,6 +590,6 @@ func (p *playbackEngine) doUpdateTimePos() {
|
|||||||
p.latestTrackPosition = s.TimePos
|
p.latestTrackPosition = s.TimePos
|
||||||
}
|
}
|
||||||
for _, cb := range p.onPlayTimeUpdate {
|
for _, cb := range p.onPlayTimeUpdate {
|
||||||
cb(s.TimePos, s.Duration)
|
cb(s.TimePos, s.Duration, seeked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.
|
// 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)
|
p.engine.onPlayTimeUpdate = append(p.engine.onPlayTimeUpdate, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
module github.com/dweymouth/supersonic
|
module github.com/dweymouth/supersonic
|
||||||
|
|
||||||
go 1.21
|
go 1.21.1
|
||||||
|
|
||||||
require (
|
require (
|
||||||
fyne.io/fyne/v2 v2.4.4
|
fyne.io/fyne/v2 v2.4.5
|
||||||
github.com/20after4/configdir v0.1.1
|
github.com/20after4/configdir v0.1.1
|
||||||
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
github.com/deluan/sanitize v0.0.0-20230310221930-6e18967d9fc1
|
||||||
github.com/dweymouth/go-jellyfin v0.0.0-20240330010648-fb02c0b3878e
|
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-mpv v0.0.0-20230406003141-7f1858e503ee
|
||||||
github.com/dweymouth/go-subsonic v0.0.0-20240417012336-798603e9f3a3
|
github.com/dweymouth/go-subsonic v0.0.0-20240417012336-798603e9f3a3
|
||||||
github.com/fsnotify/fsnotify v1.6.0
|
github.com/fsnotify/fsnotify v1.6.0
|
||||||
@@ -29,8 +30,8 @@ require (
|
|||||||
github.com/fyne-io/glfw-js v0.0.0-20220120001248-ee7290d23504 // 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/fyne-io/image v0.0.0-20220602074514-4956b0afb3d2 // indirect
|
||||||
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 // 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-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb // indirect
|
||||||
github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 // indirect
|
github.com/go-text/render v0.1.0 // indirect
|
||||||
github.com/go-text/typesetting v0.1.0 // indirect
|
github.com/go-text/typesetting v0.1.0 // indirect
|
||||||
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
github.com/gopherjs/gopherjs v1.17.2 // indirect
|
||||||
github.com/jsummers/gobmp v0.0.0-20151104160322-e2ba15ffa76e // 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
|
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
|
||||||
|
|||||||
@@ -69,10 +69,12 @@ 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/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 h1:mGvOb3zxl4vCLv+dbf7JA6CAaM2UH/AGP1KX4DsJmTI=
|
||||||
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-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 h1:HLnkaB2GRPMDaNgQ/HaEDKChU18UnLzJaq29dsfuGEA=
|
||||||
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20240418162613-9e56d7882130/go.mod h1:VyrxAOZ3NRZRWBvNIJbfqoKOG4DdbewoPk7ozqJKNPY=
|
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-20240517151952-5ceca61cb645 h1:KzqSaQwG3HsTZQlEtkp0BeUy9vmYZ0rq0B15qIPSiBs=
|
||||||
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/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 h1:ZGyJ6wp7CAfT31BugypcF/TPKEy2RrGR9JFq1JOjOpY=
|
||||||
github.com/dweymouth/go-mpv v0.0.0-20230406003141-7f1858e503ee/go.mod h1:Ov0ieN90M7i+0k3OxhA/g1dozGs+UcPHDsMKqPgRDk0=
|
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=
|
github.com/dweymouth/go-subsonic v0.0.0-20240417012336-798603e9f3a3 h1:DJwu4MrQ6cPJF+eqcuP0eGKelsaZbHa08xsRHrbJYc8=
|
||||||
@@ -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-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-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-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-20240306074159-ea2d69986ecb h1:S9I8pIVT5JHKDvmI1vQ0qs5fqxzUfhcZm/YbUC/8k1k=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20221017161538-93cebf72946b/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240306074159-ea2d69986ecb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8 h1:VkKnvzbvHqgEfm351rfr8Uclu5fnwq8HP2ximUzJsBM=
|
github.com/go-text/render v0.1.0 h1:osrmVDZNHuP1RSu3pNG7Z77Sd2xSbcb/xWytAj9kyVs=
|
||||||
github.com/go-text/render v0.0.0-20230619120952-35bccb6164b8/go.mod h1:h29xCucjNsDcYb7+0rJokxVwYAq+9kQ19WiFuBKkYtc=
|
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 h1:vioSaLPYcHwPEPLT7gsjCGDCoYSbljxoHJzMnKwVvHw=
|
||||||
github.com/go-text/typesetting v0.1.0/go.mod h1:d22AnmeKq/on0HNv73UFriMKc4Ez6EqZAofLhAzpSzI=
|
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-20240329101916-eee87fb235a3 h1:levTnuLLUmpavLGbJYLJA7fQnKeS7P1eCdAlM+vReXk=
|
||||||
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/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
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.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ func NewBottomPanel(pm *backend.PlaybackManager, contr *controller.Controller) *
|
|||||||
bp.ExtendBaseWidget(bp)
|
bp.ExtendBaseWidget(bp)
|
||||||
|
|
||||||
pm.OnSongChange(bp.onSongChange)
|
pm.OnSongChange(bp.onSongChange)
|
||||||
pm.OnPlayTimeUpdate(func(cur, total float64) {
|
pm.OnPlayTimeUpdate(func(cur, total float64, _ bool) {
|
||||||
if !pm.IsSeeking() {
|
if !pm.IsSeeking() {
|
||||||
bp.Controls.UpdatePlayTime(cur, total)
|
bp.Controls.UpdatePlayTime(cur, total)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ type CanShowNowPlaying interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CanShowPlayTime interface {
|
type CanShowPlayTime interface {
|
||||||
OnPlayTimeUpdate(curTime, totalTime float64)
|
OnPlayTimeUpdate(curTime, totalTime float64, seeked bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type CanShowPlayQueue interface {
|
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 {
|
if b.curPage == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if p, ok := b.curPage.(CanShowPlayTime); ok {
|
if p, ok := b.curPage.(CanShowPlayTime); ok {
|
||||||
p.OnPlayTimeUpdate(cur, total)
|
p.OnPlayTimeUpdate(cur, total, seeked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -241,8 +241,13 @@ func (s *nowPlayingPageState) Restore() Page {
|
|||||||
|
|
||||||
var _ CanShowPlayTime = (*NowPlayingPage)(nil)
|
var _ CanShowPlayTime = (*NowPlayingPage)(nil)
|
||||||
|
|
||||||
func (a *NowPlayingPage) OnPlayTimeUpdate(_, _ float64) {
|
func (a *NowPlayingPage) OnPlayTimeUpdate(curTime, _ float64, seeked bool) {
|
||||||
a.formatStatusLine()
|
a.formatStatusLine()
|
||||||
|
if seeked {
|
||||||
|
a.lyricsViewer.OnSeeked(curTime)
|
||||||
|
} else {
|
||||||
|
a.lyricsViewer.UpdatePlayPos(curTime)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ CanSelectAll = (*NowPlayingPage)(nil)
|
var _ CanSelectAll = (*NowPlayingPage)(nil)
|
||||||
|
|||||||
+74
-31
@@ -4,66 +4,109 @@ import (
|
|||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LyricsViewer struct {
|
type LyricsViewer struct {
|
||||||
widget.BaseWidget
|
widget.BaseWidget
|
||||||
|
|
||||||
noLyricsLabel widget.Label
|
noLyricsLabel widget.Label
|
||||||
unsyncedViewer *widget.RichText
|
viewer *fynelyrics.LyricsViewer
|
||||||
|
lyrics *mediaprovider.Lyrics
|
||||||
|
nextLyricLine int
|
||||||
|
lastPlayPos float64
|
||||||
|
|
||||||
container *container.Scroll
|
// keeps track if UpdatePlayPos has been called yet
|
||||||
currentView lyricView
|
// for the current lyrics
|
||||||
|
firstUpdate bool
|
||||||
|
|
||||||
|
container *fyne.Container
|
||||||
|
isEmpty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type lyricView int
|
|
||||||
|
|
||||||
const (
|
|
||||||
lyricViewEmpty lyricView = iota
|
|
||||||
lyricViewUnsynced
|
|
||||||
lyricViewSynced
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewLyricsViewer() *LyricsViewer {
|
func NewLyricsViewer() *LyricsViewer {
|
||||||
l := &LyricsViewer{noLyricsLabel: widget.Label{
|
l := &LyricsViewer{noLyricsLabel: widget.Label{
|
||||||
Text: "Lyrics not available",
|
Text: "Lyrics not available",
|
||||||
}}
|
}, isEmpty: true}
|
||||||
l.ExtendBaseWidget(l)
|
l.ExtendBaseWidget(l)
|
||||||
l.container = container.NewVScroll(&l.noLyricsLabel)
|
l.container = container.NewStack(&l.noLyricsLabel)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) {
|
func (l *LyricsViewer) SetLyrics(lyrics *mediaprovider.Lyrics) {
|
||||||
|
l.lyrics = lyrics
|
||||||
|
l.nextLyricLine = 0
|
||||||
|
l.firstUpdate = true
|
||||||
if lyrics == nil || len(lyrics.Lines) == 0 {
|
if lyrics == nil || len(lyrics.Lines) == 0 {
|
||||||
if l.currentView != lyricViewEmpty {
|
if !l.isEmpty {
|
||||||
l.container.Content = &l.noLyricsLabel
|
l.container.Objects[0] = &l.noLyricsLabel
|
||||||
l.currentView = lyricViewEmpty
|
l.isEmpty = true
|
||||||
l.Refresh()
|
l.Refresh()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if l.unsyncedViewer == nil {
|
if l.viewer == nil {
|
||||||
l.unsyncedViewer = widget.NewRichText()
|
l.viewer = fynelyrics.NewLyricsViewer()
|
||||||
l.unsyncedViewer.Wrapping = fyne.TextWrapWord
|
l.viewer.ActiveLyricPosition = fynelyrics.ActiveLyricPositionTopThird
|
||||||
}
|
}
|
||||||
l.unsyncedViewer.Segments = nil
|
lines := make([]string, len(lyrics.Lines))
|
||||||
for _, line := range lyrics.Lines {
|
for i, line := range lyrics.Lines {
|
||||||
ts := &widget.TextSegment{Text: line.Text}
|
lines[i] = 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)
|
|
||||||
}
|
}
|
||||||
l.unsyncedViewer.Refresh()
|
l.viewer.SetLyrics(lines, lyrics.Synced)
|
||||||
if l.currentView != lyricViewUnsynced {
|
if l.isEmpty {
|
||||||
l.container.Content = l.unsyncedViewer
|
l.container.Objects[0] = l.viewer
|
||||||
l.currentView = lyricViewUnsynced
|
l.isEmpty = false
|
||||||
l.Refresh()
|
l.Refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *LyricsViewer) UpdatePlayPos(timeSecs float64) {
|
||||||
|
if l.lyrics == nil || !l.lyrics.Synced {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if l.firstUpdate || timeSecs < l.lastPlayPos {
|
||||||
|
l.OnSeeked(timeSecs)
|
||||||
|
l.firstUpdate = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.lastPlayPos = timeSecs
|
||||||
|
// advance if needed
|
||||||
|
if l.lyrics.Lines[l.nextLyricLine].Start <= timeSecs {
|
||||||
|
l.viewer.NextLine()
|
||||||
|
if l.nextLyricLine < len(l.lyrics.Lines)-1 {
|
||||||
|
l.nextLyricLine++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LyricsViewer) OnSeeked(timeSecs float64) {
|
||||||
|
l.lastPlayPos = timeSecs
|
||||||
|
if l.lyrics == nil || !l.lyrics.Synced {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// find first line that starts after timeSecs
|
||||||
|
nextLine := -1
|
||||||
|
for i, l := range l.lyrics.Lines {
|
||||||
|
if l.Start > timeSecs {
|
||||||
|
nextLine = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextLine == -1 {
|
||||||
|
// last lyric
|
||||||
|
nextLine = len(l.lyrics.Lines)
|
||||||
|
l.nextLyricLine = nextLine - 1
|
||||||
|
} else {
|
||||||
|
l.nextLyricLine = nextLine
|
||||||
|
}
|
||||||
|
l.viewer.SetCurrentLine(nextLine /*one-indexed*/)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *LyricsViewer) CreateRenderer() fyne.WidgetRenderer {
|
func (l *LyricsViewer) CreateRenderer() fyne.WidgetRenderer {
|
||||||
return widget.NewSimpleRenderer(l.container)
|
return widget.NewSimpleRenderer(l.container)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user