sync playback time when seeking and starting new tracks

This commit is contained in:
Drew Weymouth
2025-03-30 13:08:18 -07:00
parent 447519c413
commit 2701bb3f7f
5 changed files with 75 additions and 15 deletions
+52 -1
View File
@@ -41,6 +41,8 @@ type proxyMapEntry struct {
type DLNAPlayer struct {
player.BasePlayerCallbackImpl
destroyed bool
avTransport *avtransport.Client
renderControl *renderingcontrol.Client
@@ -51,7 +53,12 @@ type DLNAPlayer struct {
curTrackMeta mediaprovider.MediaItemMetadata
nextTrackMeta mediaprovider.MediaItemMetadata
// if true, report playback time 00:00
// pending time sync with player after beginning playback
pendingPlayStart bool
// start playback position in seconds of the last seek/time sync
lastStartTime int
// how long the track has been playing since last time sync
stopwatch util.Stopwatch
proxyServer *http.Server
@@ -139,11 +146,23 @@ func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadat
if err := d.playAVTransportMedia(&media); err != nil {
return err
}
d.pendingPlayStart = true
if startTime > 0 {
// TODO: do something better than this!!
time.Sleep(2 * time.Second)
if !d.destroyed {
d.sendSeekCmd(startTime)
}
d.pendingPlayStart = false
} else {
go func() {
time.Sleep(2 * time.Second)
if !d.destroyed {
d.syncPlaybackTime()
}
d.pendingPlayStart = false
}()
}
d.state = playing
remainingDur := meta.Duration - int(startTime)
d.setTrackChangeTimer(time.Duration(remainingDur) * time.Second)
@@ -278,6 +297,13 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error {
}
d.InvokeOnSeek()
go func() {
time.Sleep(4 * time.Second)
if !d.destroyed {
d.syncPlaybackTime()
}
}()
return nil
}
@@ -303,9 +329,13 @@ func (d *DLNAPlayer) GetStatus() player.Status {
state = player.Paused
}
var timePos float64
if !d.pendingPlayStart {
timePos = d.curPlayPos().Seconds()
}
return player.Status{
State: state,
TimePos: d.curPlayPos().Seconds(),
TimePos: timePos,
Duration: float64(d.curTrackMeta.Duration),
}
}
@@ -315,11 +345,25 @@ func (d *DLNAPlayer) curPlayPos() time.Duration {
}
func (d *DLNAPlayer) Destroy() {
d.destroyed = true
if d.proxyServer != nil {
go d.proxyServer.Shutdown(context.Background())
}
}
func (d *DLNAPlayer) syncPlaybackTime() {
start := time.Now()
if pos, err := d.avTransport.GetPositionInfo(context.Background()); err == nil {
d.lastStartTime = int(pos.RelTime.Seconds() + (time.Since(start) / 2).Seconds())
d.stopwatch.Reset()
if d.state == playing {
d.stopwatch.Start()
}
d.setTrackChangeTimer(time.Duration(d.curTrackMeta.Duration-d.lastStartTime) * time.Second)
d.InvokeOnSeek()
}
}
func (d *DLNAPlayer) ensureSetupProxy() error {
if d.proxyActive.Swap(true) {
return nil // already active
@@ -422,6 +466,13 @@ func (d *DLNAPlayer) handleOnTrackChange() {
d.stopwatch.Start()
d.setTrackChangeTimer(nextTrackChange)
d.InvokeOnTrackChange()
go func() {
time.Sleep(5 * time.Second)
if !d.destroyed {
d.syncPlaybackTime()
}
}()
}
}
+5 -5
View File
@@ -1,6 +1,6 @@
module github.com/dweymouth/supersonic
go 1.21
go 1.21.9
require (
fyne.io/fyne/v2 v2.6.0-beta1
@@ -14,13 +14,14 @@ require (
github.com/dweymouth/go-jellyfin v0.0.0-20240517151952-5ceca61cb645
github.com/godbus/dbus/v5 v5.1.0
github.com/google/uuid v1.3.0
github.com/hashicorp/go-retryablehttp v0.7.7
github.com/pelletier/go-toml/v2 v2.0.8
github.com/quarckster/go-mpris-server v1.0.3
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-upnpcast v0.0.0-20250312000014-e4f7242a07ce
github.com/supersonic-app/go-upnpcast v0.0.0-20250330154256-b957204209a5
github.com/zalando/go-keyring v0.2.6
golang.org/x/net v0.25.0
golang.org/x/net v0.33.0
golang.org/x/sys v0.30.0
golang.org/x/text v0.22.0
)
@@ -42,10 +43,9 @@ require (
github.com/go-text/typesetting v0.2.1 // indirect
github.com/h2non/filetype v1.1.3 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
github.com/koron/go-ssdp v0.0.4 // indirect
github.com/koron/go-ssdp v0.0.5 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
+14 -6
View File
@@ -27,6 +27,8 @@ github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250308154116-c32a0de49bde h1:vSJQkJW
github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250308154116-c32a0de49bde/go.mod h1:ON11afuS9jVyN+nfEtITv3jX1H+JD+Z50gxCiEC1zLw=
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/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
@@ -61,16 +63,22 @@ github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
github.com/koron/go-ssdp v0.0.5 h1:E1iSMxIs4WqxTbIBLtmNBeOOC+1sCIXQeqTWVnpmwhk=
github.com/koron/go-ssdp v0.0.5/go.mod h1:Qm59B7hpKpDqfyRNWRNr00jGwLdXjDyZh6y7rH6VS0w=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
@@ -105,8 +113,8 @@ github.com/supersonic-app/go-mpv v0.1.0 h1:U+cCnLQxmpqx5mY6nMlC0J4uIdCCXUbAjpjS0
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/go.mod h1:D+OWPXeD9owcdcoXATv5YPBGWxxVvn5k98rt5B4wMc4=
github.com/supersonic-app/go-upnpcast v0.0.0-20250312000014-e4f7242a07ce h1:6R8/JRwRVrR9t2YPTgikS39lRf3gPp/MPcbx+lIf3rA=
github.com/supersonic-app/go-upnpcast v0.0.0-20250312000014-e4f7242a07ce/go.mod h1:Wscg4vEzF9x6i4ltL2Qkvx4VhzXfkBBl8uoG0YSAJMU=
github.com/supersonic-app/go-upnpcast v0.0.0-20250330154256-b957204209a5 h1:aoUJKPFD/ZrNZjK6fl2Xhazwsttx42esKxhSSzEE3Bo=
github.com/supersonic-app/go-upnpcast v0.0.0-20250330154256-b957204209a5/go.mod h1:ibt19zDV5/vvF14jHJpTv3AOorq1EbmrMAubxnuvR5Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
@@ -123,8 +131,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+1
View File
@@ -207,6 +207,7 @@
"Testing connection": "Testing connection",
"The request timed out": "The request timed out",
"Theme": "Theme",
"This computer": "This computer",
"Time": "Time",
"Title": "Title",
"Title (A-Z)": "Title (A-Z)",
+1 -1
View File
@@ -154,7 +154,7 @@ func (m *Controller) HaveModal() bool {
func (m *Controller) ShowCastMenu(onPendingPlayerChange func()) {
rp := m.App.PlaybackManager.CurrentRemotePlayer()
devices := m.App.PlaybackManager.RemotePlayers()
local := fyne.NewMenuItem(lang.L("Local player"), func() {
local := fyne.NewMenuItem(lang.L("This computer"), func() {
onPendingPlayerChange()
go func() {
if err := m.App.PlaybackManager.SetRemotePlayer(nil); err != nil {