Skip to content

Commit 85ab5ba

Browse files
committed
extract out track change timer from DLNA player, some more work on jukebox
1 parent 8b73765 commit 85ab5ba

File tree

3 files changed

+122
-64
lines changed

3 files changed

+122
-64
lines changed
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package common
2+
3+
import (
4+
"sync/atomic"
5+
"time"
6+
)
7+
8+
type TrackChangeTimer struct {
9+
timerActive atomic.Bool
10+
timer *time.Timer
11+
resetChan chan (time.Duration)
12+
13+
onHandleTrackChange func()
14+
}
15+
16+
func NewTrackChangeTimer(onHandleTrackChange func()) TrackChangeTimer {
17+
return TrackChangeTimer{
18+
resetChan: make(chan time.Duration),
19+
onHandleTrackChange: onHandleTrackChange,
20+
}
21+
}
22+
23+
func (d *TrackChangeTimer) Reset(dur time.Duration) {
24+
if d.timerActive.Swap(true) {
25+
// was active
26+
d.resetChan <- dur
27+
return
28+
}
29+
if dur == 0 {
30+
d.timerActive.Store(false)
31+
return
32+
}
33+
34+
d.timer = time.NewTimer(dur)
35+
go func() {
36+
for {
37+
select {
38+
case dur := <-d.resetChan:
39+
if dur == 0 {
40+
d.timerActive.Store(false)
41+
if !d.timer.Stop() {
42+
select {
43+
case <-d.timer.C:
44+
default:
45+
}
46+
}
47+
d.timer = nil
48+
return
49+
}
50+
// reset the timer
51+
if !d.timer.Stop() {
52+
select {
53+
case <-d.timer.C:
54+
default:
55+
}
56+
}
57+
d.timer.Reset(dur)
58+
case <-d.timer.C:
59+
d.timerActive.Store(false)
60+
d.timer = nil
61+
d.onHandleTrackChange()
62+
return
63+
}
64+
}
65+
}()
66+
}

backend/player/dlna/dlnaplayer.go

Lines changed: 15 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818

1919
"github.com/dweymouth/supersonic/backend/mediaprovider"
2020
"github.com/dweymouth/supersonic/backend/player"
21+
"github.com/dweymouth/supersonic/backend/player/common"
2122
"github.com/dweymouth/supersonic/backend/util"
2223
"github.com/hashicorp/go-retryablehttp"
2324
"github.com/supersonic-app/go-upnpcast/device"
@@ -83,9 +84,7 @@ type DLNAPlayer struct {
8384
failedToSetNext bool
8485
unsetNextMediaItem *avtransport.MediaItem
8586

86-
timerActive atomic.Bool
87-
timer *time.Timer
88-
resetChan chan (time.Duration)
87+
trackChangeTimer common.TrackChangeTimer
8988
}
9089

9190
func NewDLNAPlayer(device *device.MediaRenderer) (*DLNAPlayer, error) {
@@ -113,11 +112,13 @@ func NewDLNAPlayer(device *device.MediaRenderer) (*DLNAPlayer, error) {
113112
return nil, fmt.Errorf("failed to connect to %s", device.FriendlyName)
114113
}
115114

116-
return &DLNAPlayer{
115+
d := &DLNAPlayer{
117116
avTransport: avt,
118117
renderControl: rc,
119-
resetChan: make(chan time.Duration),
120-
}, nil
118+
}
119+
d.trackChangeTimer = common.NewTrackChangeTimer(d.handleOnTrackChange)
120+
121+
return d, nil
121122
}
122123

123124
func (d *DLNAPlayer) SetVolume(vol int) error {
@@ -182,7 +183,7 @@ func (d *DLNAPlayer) PlayFile(urlstr string, meta mediaprovider.MediaItemMetadat
182183
}
183184
d.state = playing
184185
remainingDur := meta.Duration - time.Duration(startTime)*time.Second
185-
d.setTrackChangeTimer(remainingDur)
186+
d.trackChangeTimer.Reset(remainingDur)
186187
d.stopwatch.Reset()
187188
d.stopwatch.Start()
188189
d.lastStartTime = int(startTime)
@@ -271,7 +272,7 @@ func (d *DLNAPlayer) Continue() error {
271272
nextTrackChange := d.curTrackMeta.Duration - d.curPlayPos()
272273
d.metaLock.Unlock()
273274
d.state = playing
274-
d.setTrackChangeTimer(nextTrackChange)
275+
d.trackChangeTimer.Reset(nextTrackChange)
275276
d.stopwatch.Start()
276277
d.InvokeOnPlaying()
277278
return nil
@@ -288,7 +289,7 @@ func (d *DLNAPlayer) Pause() error {
288289
if err := d.avTransport.Pause(ctx); err != nil {
289290
return err
290291
}
291-
d.setTrackChangeTimer(0)
292+
d.trackChangeTimer.Reset(0)
292293
d.stopwatch.Stop()
293294
d.state = paused
294295
d.InvokeOnPaused()
@@ -322,7 +323,7 @@ func (d *DLNAPlayer) Stop(force bool) error {
322323
}
323324
fallthrough
324325
case paused:
325-
d.setTrackChangeTimer(0)
326+
d.trackChangeTimer.Reset(0)
326327
d.stopwatch.Reset()
327328
d.lastStartTime = 0
328329
d.state = stopped
@@ -354,7 +355,7 @@ func (d *DLNAPlayer) SeekSeconds(secs float64) error {
354355
d.metaLock.Lock()
355356
nextTrackChange := d.curTrackMeta.Duration - time.Duration(secs)*time.Second
356357
d.metaLock.Unlock()
357-
d.setTrackChangeTimer(nextTrackChange)
358+
d.trackChangeTimer.Reset(nextTrackChange)
358359
d.stopwatch.Start()
359360
}
360361

@@ -410,7 +411,7 @@ func (d *DLNAPlayer) curPlayPos() time.Duration {
410411

411412
func (d *DLNAPlayer) Destroy() {
412413
d.destroyed = true
413-
d.setTrackChangeTimer(0)
414+
d.trackChangeTimer.Reset(0)
414415
if d.cancelRequest != nil {
415416
d.cancelRequest()
416417
}
@@ -431,7 +432,7 @@ func (d *DLNAPlayer) syncPlaybackTime() {
431432
if d.state == playing {
432433
d.stopwatch.Start()
433434
}
434-
d.setTrackChangeTimer(d.curTrackMeta.Duration - time.Duration(d.lastStartTime)*time.Second)
435+
d.trackChangeTimer.Reset(d.curTrackMeta.Duration - time.Duration(d.lastStartTime)*time.Second)
435436
d.InvokeOnSeek()
436437
}
437438
}
@@ -461,51 +462,6 @@ func (d *DLNAPlayer) ensureSetupProxy() error {
461462
return nil
462463
}
463464

464-
func (d *DLNAPlayer) setTrackChangeTimer(dur time.Duration) {
465-
if d.timerActive.Swap(true) {
466-
// was active
467-
d.resetChan <- dur
468-
return
469-
}
470-
if dur == 0 {
471-
d.timerActive.Store(false)
472-
return
473-
}
474-
475-
d.timer = time.NewTimer(dur)
476-
go func() {
477-
for {
478-
select {
479-
case dur := <-d.resetChan:
480-
if dur == 0 {
481-
d.timerActive.Store(false)
482-
if !d.timer.Stop() {
483-
select {
484-
case <-d.timer.C:
485-
default:
486-
}
487-
}
488-
d.timer = nil
489-
return
490-
}
491-
// reset the timer
492-
if !d.timer.Stop() {
493-
select {
494-
case <-d.timer.C:
495-
default:
496-
}
497-
}
498-
d.timer.Reset(dur)
499-
case <-d.timer.C:
500-
d.timerActive.Store(false)
501-
d.timer = nil
502-
d.handleOnTrackChange()
503-
return
504-
}
505-
}
506-
}()
507-
}
508-
509465
func (d *DLNAPlayer) handleOnTrackChange() {
510466
stopping := false
511467
d.metaLock.Lock()
@@ -536,7 +492,7 @@ func (d *DLNAPlayer) handleOnTrackChange() {
536492
d.lastStartTime = 0
537493
d.stopwatch.Reset()
538494
d.stopwatch.Start()
539-
d.setTrackChangeTimer(nextTrackChange)
495+
d.trackChangeTimer.Reset(nextTrackChange)
540496
d.InvokeOnTrackChange()
541497

542498
go func() {

backend/player/jukebox/jukeboxplayer.go

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55

66
"github.com/dweymouth/supersonic/backend/mediaprovider"
77
"github.com/dweymouth/supersonic/backend/player"
8+
"github.com/dweymouth/supersonic/backend/player/common"
9+
"github.com/dweymouth/supersonic/backend/util"
810
)
911

1012
const (
@@ -22,13 +24,26 @@ type JukeboxPlayer struct {
2224
volume int
2325
seeking bool
2426

27+
// start playback position in seconds of the last seek/time sync
28+
lastStartTime int
29+
// how long the track has been playing since last time sync
30+
stopwatch util.Stopwatch
31+
32+
trackChangeTimer common.TrackChangeTimer
33+
2534
curTrack int
2635
queueLength int
2736
curTrackDuration float64
2837
startTrackTime float64
2938
startedAtUnixMilli int64
3039
}
3140

41+
func NewJukeboxPlayer(provider mediaprovider.JukeboxProvider) (*JukeboxPlayer, error) {
42+
j := &JukeboxPlayer{provider: provider}
43+
j.trackChangeTimer = common.NewTrackChangeTimer(j.handleOnTrackChange)
44+
return j, nil
45+
}
46+
3247
func (j *JukeboxPlayer) SetVolume(vol int) error {
3348
if err := j.provider.JukeboxSetVolume(vol); err != nil {
3449
return err
@@ -125,21 +140,42 @@ func (j *JukeboxPlayer) IsSeeking() bool {
125140

126141
func (j *JukeboxPlayer) GetStatus() player.Status {
127142
state := player.Stopped
128-
if j.state == playing {
143+
switch j.state {
144+
case playing:
129145
state = player.Playing
130-
} else if j.state == paused {
146+
case paused:
131147
state = player.Paused
132148
}
133149

134-
// TODO - the rest
135-
136150
return player.Status{
137-
State: state,
151+
State: state,
152+
TimePos: j.curPlayPos().Seconds(),
153+
//Duration: TODO
138154
}
139155
}
140156

157+
func (j *JukeboxPlayer) curPlayPos() time.Duration {
158+
return time.Duration(j.lastStartTime)*time.Second + j.stopwatch.Elapsed()
159+
}
160+
141161
func (j *JukeboxPlayer) Destroy() {}
142162

163+
func (j *JukeboxPlayer) handleOnTrackChange() {
164+
stopping := false
165+
// TODO: is playing next track?
166+
167+
if stopping {
168+
j.lastStartTime = 0
169+
j.stopwatch.Reset()
170+
} else {
171+
j.lastStartTime = 0
172+
j.stopwatch.Reset()
173+
j.stopwatch.Start()
174+
j.trackChangeTimer.Reset(0 /*TODO: next track time*/)
175+
j.InvokeOnTrackChange()
176+
}
177+
}
178+
143179
func (j *JukeboxPlayer) startAndUpdateTime() error {
144180
beforeStart := time.Now()
145181
if err := j.provider.JukeboxStart(); err != nil {

0 commit comments

Comments
 (0)