add scrobbling of played tracks
This commit is contained in:
@@ -2,12 +2,19 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
|
"strconv"
|
||||||
|
"supersonic/backend/util"
|
||||||
"supersonic/player"
|
"supersonic/player"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
subsonic "github.com/dweymouth/go-subsonic"
|
subsonic "github.com/dweymouth/go-subsonic"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScrobbleThreshold = 0.6
|
||||||
|
)
|
||||||
|
|
||||||
type PlaybackManager struct {
|
type PlaybackManager struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelPollPos context.CancelFunc
|
cancelPollPos context.CancelFunc
|
||||||
@@ -15,6 +22,9 @@ type PlaybackManager struct {
|
|||||||
sm *ServerManager
|
sm *ServerManager
|
||||||
player *player.Player
|
player *player.Player
|
||||||
|
|
||||||
|
playTimeStopwatch util.Stopwatch
|
||||||
|
curTrackTime float64
|
||||||
|
|
||||||
playQueue []*subsonic.Child
|
playQueue []*subsonic.Child
|
||||||
nowPlayingIdx int64
|
nowPlayingIdx int64
|
||||||
onSongChange []func(*subsonic.Child)
|
onSongChange []func(*subsonic.Child)
|
||||||
@@ -27,11 +37,20 @@ func NewPlaybackManager(ctx context.Context, s *ServerManager, p *player.Player)
|
|||||||
sm: s,
|
sm: s,
|
||||||
player: p,
|
player: p,
|
||||||
}
|
}
|
||||||
|
// TODO: we get some spurious OnTrackChange callbacks from the player,
|
||||||
|
// especially when loading/playing a new album,
|
||||||
|
// but for now they're pretty much harmless. Investigate later.
|
||||||
p.OnTrackChange(func(tracknum int64) {
|
p.OnTrackChange(func(tracknum int64) {
|
||||||
if tracknum >= int64(len(pm.playQueue)) {
|
if tracknum >= int64(len(pm.playQueue)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
pm.checkScrobble(pm.playTimeStopwatch.Elapsed())
|
||||||
|
pm.playTimeStopwatch.Reset()
|
||||||
|
if pm.player.GetStatus().State == player.Playing {
|
||||||
|
pm.playTimeStopwatch.Start()
|
||||||
|
}
|
||||||
pm.nowPlayingIdx = tracknum
|
pm.nowPlayingIdx = tracknum
|
||||||
|
pm.curTrackTime = float64(pm.playQueue[pm.nowPlayingIdx].Duration)
|
||||||
for _, cb := range pm.onSongChange {
|
for _, cb := range pm.onSongChange {
|
||||||
cb(pm.NowPlaying())
|
cb(pm.NowPlaying())
|
||||||
}
|
}
|
||||||
@@ -41,15 +60,20 @@ func NewPlaybackManager(ctx context.Context, s *ServerManager, p *player.Player)
|
|||||||
pm.doUpdateTimePos()
|
pm.doUpdateTimePos()
|
||||||
})
|
})
|
||||||
p.OnStopped(func() {
|
p.OnStopped(func() {
|
||||||
|
pm.playTimeStopwatch.Stop()
|
||||||
|
pm.checkScrobble(pm.playTimeStopwatch.Elapsed())
|
||||||
|
pm.playTimeStopwatch.Reset()
|
||||||
pm.stopPollTimePos()
|
pm.stopPollTimePos()
|
||||||
for _, cb := range pm.onSongChange {
|
for _, cb := range pm.onSongChange {
|
||||||
cb(nil)
|
cb(nil)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
p.OnPaused(func() {
|
p.OnPaused(func() {
|
||||||
|
pm.playTimeStopwatch.Stop()
|
||||||
pm.stopPollTimePos()
|
pm.stopPollTimePos()
|
||||||
})
|
})
|
||||||
p.OnPlaying(func() {
|
p.OnPlaying(func() {
|
||||||
|
pm.playTimeStopwatch.Start()
|
||||||
pm.startPollTimePos()
|
pm.startPollTimePos()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -107,6 +131,17 @@ func (p *PlaybackManager) PlayAlbum(albumID string) error {
|
|||||||
return p.player.PlayFromBeginning()
|
return p.player.PlayFromBeginning()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PlaybackManager) checkScrobble(playDur time.Duration) {
|
||||||
|
if playDur.Seconds() < 0.1 || p.curTrackTime < 0.1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
song := p.playQueue[p.nowPlayingIdx]
|
||||||
|
if playDur.Seconds()/p.curTrackTime > ScrobbleThreshold {
|
||||||
|
log.Printf("Scrobbling %q", song.Title)
|
||||||
|
p.sm.Server.Scrobble(song.ID, map[string]string{"time": strconv.FormatInt(time.Now().Unix()*1000, 10)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) startPollTimePos() {
|
func (p *PlaybackManager) startPollTimePos() {
|
||||||
ctx, cancel := context.WithCancel(p.ctx)
|
ctx, cancel := context.WithCancel(p.ctx)
|
||||||
p.cancelPollPos = cancel
|
p.cancelPollPos = cancel
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Stopwatch struct {
|
||||||
|
running bool
|
||||||
|
started time.Time
|
||||||
|
elapsed time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stopwatch) Start() {
|
||||||
|
if s.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.started = time.Now()
|
||||||
|
s.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stopwatch) Stop() {
|
||||||
|
if !s.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.elapsed += time.Since(s.started)
|
||||||
|
s.running = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stopwatch) Elapsed() time.Duration {
|
||||||
|
e := s.elapsed
|
||||||
|
if s.running {
|
||||||
|
e += time.Since(s.started)
|
||||||
|
}
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stopwatch) Reset() {
|
||||||
|
s.running = false
|
||||||
|
s.elapsed = time.Duration(0)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user