sync playback time when seeking and starting new tracks
This commit is contained in:
@@ -41,6 +41,8 @@ type proxyMapEntry struct {
|
||||
type DLNAPlayer struct {
|
||||
player.BasePlayerCallbackImpl
|
||||
|
||||
destroyed bool
|
||||
|
||||
avTransport *avtransport.Client
|
||||
renderControl *renderingcontrol.Client
|
||||
|
||||
@@ -51,8 +53,13 @@ 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
|
||||
stopwatch util.Stopwatch
|
||||
// how long the track has been playing since last time sync
|
||||
stopwatch util.Stopwatch
|
||||
|
||||
proxyServer *http.Server
|
||||
proxyActive atomic.Bool
|
||||
@@ -139,10 +146,22 @@ 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)
|
||||
d.sendSeekCmd(startTime)
|
||||
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)
|
||||
@@ -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()
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user