add scrobbling of played tracks
This commit is contained in:
@@ -2,12 +2,19 @@ package backend
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strconv"
|
||||
"supersonic/backend/util"
|
||||
"supersonic/player"
|
||||
"time"
|
||||
|
||||
subsonic "github.com/dweymouth/go-subsonic"
|
||||
)
|
||||
|
||||
const (
|
||||
ScrobbleThreshold = 0.6
|
||||
)
|
||||
|
||||
type PlaybackManager struct {
|
||||
ctx context.Context
|
||||
cancelPollPos context.CancelFunc
|
||||
@@ -15,6 +22,9 @@ type PlaybackManager struct {
|
||||
sm *ServerManager
|
||||
player *player.Player
|
||||
|
||||
playTimeStopwatch util.Stopwatch
|
||||
curTrackTime float64
|
||||
|
||||
playQueue []*subsonic.Child
|
||||
nowPlayingIdx int64
|
||||
onSongChange []func(*subsonic.Child)
|
||||
@@ -27,11 +37,20 @@ func NewPlaybackManager(ctx context.Context, s *ServerManager, p *player.Player)
|
||||
sm: s,
|
||||
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) {
|
||||
if tracknum >= int64(len(pm.playQueue)) {
|
||||
return
|
||||
}
|
||||
pm.checkScrobble(pm.playTimeStopwatch.Elapsed())
|
||||
pm.playTimeStopwatch.Reset()
|
||||
if pm.player.GetStatus().State == player.Playing {
|
||||
pm.playTimeStopwatch.Start()
|
||||
}
|
||||
pm.nowPlayingIdx = tracknum
|
||||
pm.curTrackTime = float64(pm.playQueue[pm.nowPlayingIdx].Duration)
|
||||
for _, cb := range pm.onSongChange {
|
||||
cb(pm.NowPlaying())
|
||||
}
|
||||
@@ -41,15 +60,20 @@ func NewPlaybackManager(ctx context.Context, s *ServerManager, p *player.Player)
|
||||
pm.doUpdateTimePos()
|
||||
})
|
||||
p.OnStopped(func() {
|
||||
pm.playTimeStopwatch.Stop()
|
||||
pm.checkScrobble(pm.playTimeStopwatch.Elapsed())
|
||||
pm.playTimeStopwatch.Reset()
|
||||
pm.stopPollTimePos()
|
||||
for _, cb := range pm.onSongChange {
|
||||
cb(nil)
|
||||
}
|
||||
})
|
||||
p.OnPaused(func() {
|
||||
pm.playTimeStopwatch.Stop()
|
||||
pm.stopPollTimePos()
|
||||
})
|
||||
p.OnPlaying(func() {
|
||||
pm.playTimeStopwatch.Start()
|
||||
pm.startPollTimePos()
|
||||
})
|
||||
|
||||
@@ -107,6 +131,17 @@ func (p *PlaybackManager) PlayAlbum(albumID string) error {
|
||||
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() {
|
||||
ctx, cancel := context.WithCancel(p.ctx)
|
||||
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