Skip to content

Commit

Permalink
feat: prefetch next track
Browse files Browse the repository at this point in the history
  • Loading branch information
devgianlu committed May 18, 2024
1 parent 75ef7eb commit 51a1509
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 0 deletions.
50 changes: 50 additions & 0 deletions cmd/daemon/controls.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,48 @@ import (
"time"
)

func (p *AppPlayer) prefetchNext() {
next := p.state.tracks.PeekNext()
if next == nil {
return
}

nextId := librespot.SpotifyIdFromUri(next.Uri)
if p.secondaryStream != nil && p.secondaryStream.Is(nextId) {
return
}

log.Debugf("prefetching %s %s", nextId.Type(), nextId.Uri())

var err error
p.secondaryStream, err = p.player.NewStream(nextId, *p.app.cfg.Bitrate, 0)
if err != nil {
log.WithError(err).Warnf("failed prefetching stream for %s", nextId)
return
}

log.Infof("prefetched track \"%s\" (uri: %s, duration: %dms)",
p.primaryStream.Media.Name(), nextId.Uri(), p.primaryStream.Media.Duration())
}

func (p *AppPlayer) schedulePrefetchNext() {
if p.state.player.IsPaused || p.primaryStream == nil {
p.prefetchTimer.Reset(time.Duration(math.MaxInt64))
return
}

untilTrackEnd := time.Duration(p.primaryStream.Media.Duration()-int32(p.player.PositionMs())) * time.Millisecond
untilTrackEnd -= 30 * time.Second
if untilTrackEnd < 10*time.Second {
p.prefetchTimer.Reset(time.Duration(math.MaxInt64))

go p.prefetchNext()
} else {
p.prefetchTimer.Reset(untilTrackEnd)
log.Tracef("scheduling prefetch in %.0fs", untilTrackEnd.Seconds())
}
}

func (p *AppPlayer) handlePlayerEvent(ev *player.Event) {
switch ev.Type {
case player.EventTypePlaying:
Expand Down Expand Up @@ -185,6 +227,7 @@ func (p *AppPlayer) loadCurrentTrack(paused bool) error {
p.state.player.IsPlaying = true
p.state.player.IsBuffering = false
p.updateState()
p.schedulePrefetchNext()

p.app.server.Emit(&ApiEvent{
Type: ApiEventTypeMetadata,
Expand Down Expand Up @@ -253,13 +296,15 @@ func (p *AppPlayer) addToQueue(track *connectpb.ContextTrack) {
p.state.player.PrevTracks = p.state.tracks.PrevTracks()
p.state.player.NextTracks = p.state.tracks.NextTracks()
p.updateState()
p.schedulePrefetchNext()
}

func (p *AppPlayer) setQueue(prev []*connectpb.ContextTrack, next []*connectpb.ContextTrack) {
p.state.tracks.SetQueue(prev, next)
p.state.player.PrevTracks = p.state.tracks.PrevTracks()
p.state.player.NextTracks = p.state.tracks.NextTracks()
p.updateState()
p.schedulePrefetchNext()
}

func (p *AppPlayer) play() error {
Expand All @@ -283,6 +328,8 @@ func (p *AppPlayer) play() error {
p.state.player.PositionAsOfTimestamp = streamPos
p.state.player.IsPaused = false
p.updateState()
p.schedulePrefetchNext()

return nil
}

Expand All @@ -300,6 +347,8 @@ func (p *AppPlayer) pause() error {
p.state.player.PositionAsOfTimestamp = streamPos
p.state.player.IsPaused = true
p.updateState()
p.schedulePrefetchNext()

return nil
}

Expand All @@ -318,6 +367,7 @@ func (p *AppPlayer) seek(position int64) error {
p.state.player.Timestamp = time.Now().UnixMilli()
p.state.player.PositionAsOfTimestamp = position
p.updateState()
p.schedulePrefetchNext()

p.app.server.Emit(&ApiEvent{
Type: ApiEventTypeSeek,
Expand Down
4 changes: 4 additions & 0 deletions cmd/daemon/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"go-librespot/zeroconf"
"golang.org/x/exp/rand"
"gopkg.in/yaml.v3"
"math"
"os"
"strings"
"time"
Expand Down Expand Up @@ -74,6 +75,9 @@ func (app *App) newAppPlayer(creds any) (_ *AppPlayer, err error) {
countryCode: new(string),
}

// start a dummy timer for prefetching next media
appPlayer.prefetchTimer = time.AfterFunc(time.Duration(math.MaxInt64), appPlayer.prefetchNext)

if appPlayer.sess, err = session.NewSessionFromOptions(&session.Options{
DeviceType: app.deviceType,
DeviceId: app.deviceId,
Expand Down
5 changes: 5 additions & 0 deletions cmd/daemon/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"math"
"strings"
"sync"
"time"
)

type AppPlayer struct {
Expand All @@ -35,6 +36,8 @@ type AppPlayer struct {
state *State
primaryStream *player.Stream
secondaryStream *player.Stream

prefetchTimer *time.Timer
}

func (p *AppPlayer) handleAccesspointPacket(pktType ap.PacketType, payload []byte) error {
Expand Down Expand Up @@ -106,6 +109,8 @@ func (p *AppPlayer) handleDealerMessage(msg dealer.Message) error {
return fmt.Errorf("failed inactive state put: %w", err)
}

p.schedulePrefetchNext()

if p.app.cfg.ZeroconfEnabled {
p.logout <- p
}
Expand Down
13 changes: 13 additions & 0 deletions tracks/tracks.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@ func (tl *List) GoStart() bool {
return true
}

func (tl *List) PeekNext() *connectpb.ContextTrack {
if tl.playingQueue && len(tl.queue) > 1 {
return tl.queue[1]
}

iter := tl.tracks.iterHere()
if iter.next() {
return iter.get().item
}

return nil
}

func (tl *List) GoNext() bool {
if tl.playingQueue {
tl.queue = tl.queue[1:]
Expand Down

0 comments on commit 51a1509

Please sign in to comment.