wait to set next track until near end of current. (still needs more testing/review)
This commit is contained in:
+51
-56
@@ -77,6 +77,11 @@ type playbackEngine struct {
|
|||||||
pendingPlayerChange bool
|
pendingPlayerChange bool
|
||||||
pendingPlayerChangeStatus player.Status
|
pendingPlayerChangeStatus player.Status
|
||||||
|
|
||||||
|
// Whether we need to set the next track on the Player
|
||||||
|
// before the current track completes (normally when 10 seconds remain
|
||||||
|
// in the time pos polling function)
|
||||||
|
needToSetNextTrack bool
|
||||||
|
|
||||||
// to pass to onSongChange listeners; clear once listeners have been called
|
// to pass to onSongChange listeners; clear once listeners have been called
|
||||||
lastScrobbled *mediaprovider.Track
|
lastScrobbled *mediaprovider.Track
|
||||||
scrobbleCfg *ScrobbleConfig
|
scrobbleCfg *ScrobbleConfig
|
||||||
@@ -135,7 +140,7 @@ func NewPlaybackEngine(
|
|||||||
func (p *playbackEngine) registerPlayerCallbacks(pl player.BasePlayer) {
|
func (p *playbackEngine) registerPlayerCallbacks(pl player.BasePlayer) {
|
||||||
pl.OnTrackChange(p.handleOnTrackChange)
|
pl.OnTrackChange(p.handleOnTrackChange)
|
||||||
pl.OnSeek(func() {
|
pl.OnSeek(func() {
|
||||||
p.doUpdateTimePos(true)
|
p.handleTimePosUpdate(true)
|
||||||
p.invokeNoArgCallbacks(p.onSeek)
|
p.invokeNoArgCallbacks(p.onSeek)
|
||||||
})
|
})
|
||||||
pl.OnStopped(p.handleOnStopped)
|
pl.OnStopped(p.handleOnStopped)
|
||||||
@@ -239,7 +244,10 @@ func (p *playbackEngine) NowPlayingIndex() int {
|
|||||||
func (p *playbackEngine) SetLoopMode(loopMode LoopMode) {
|
func (p *playbackEngine) SetLoopMode(loopMode LoopMode) {
|
||||||
p.loopMode = loopMode
|
p.loopMode = loopMode
|
||||||
if p.nowPlayingIdx >= 0 {
|
if p.nowPlayingIdx >= 0 {
|
||||||
p.setNextTrackBasedOnLoopMode(true)
|
// TODO - don't need when going from LoopNone to LoopAll
|
||||||
|
// if not on last track
|
||||||
|
p.needToSetNextTrack = true
|
||||||
|
//p.setNextTrackBasedOnLoopMode(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, cb := range p.onLoopModeChange {
|
for _, cb := range p.onLoopModeChange {
|
||||||
@@ -304,7 +312,7 @@ func (p *playbackEngine) SeekFwdBackN(n int) error {
|
|||||||
if idx == lastIdx && n > 0 {
|
if idx == lastIdx && n > 0 {
|
||||||
return nil // already on last track, nothing to seek next to
|
return nil // already on last track, nothing to seek next to
|
||||||
}
|
}
|
||||||
newIdx := minInt(lastIdx, maxInt(0, idx+n))
|
newIdx := min(lastIdx, max(0, idx+n))
|
||||||
return p.PlayTrackAt(newIdx)
|
return p.PlayTrackAt(newIdx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,7 +368,7 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu
|
|||||||
p.nowPlayingIdx = -1
|
p.nowPlayingIdx = -1
|
||||||
p.playQueue = nil
|
p.playQueue = nil
|
||||||
}
|
}
|
||||||
needToSetNext := len(items) > 0 && (insertQueueMode == InsertNext || (insertQueueMode == Append && p.nowPlayingIdx == len(p.playQueue)-1))
|
p.needToSetNextTrack = len(items) > 0 && (insertQueueMode == InsertNext || (insertQueueMode == Append && p.nowPlayingIdx == len(p.playQueue)-1))
|
||||||
|
|
||||||
if shuffle {
|
if shuffle {
|
||||||
rand.Shuffle(len(items), func(i, j int) { items[i], items[j] = items[j], items[i] })
|
rand.Shuffle(len(items), func(i, j int) { items[i], items[j] = items[j], items[i] })
|
||||||
@@ -372,9 +380,9 @@ func (p *playbackEngine) doLoaditems(items []mediaprovider.MediaItem, insertQueu
|
|||||||
}
|
}
|
||||||
p.playQueue = append(p.playQueue[:insertIdx], append(items, p.playQueue[insertIdx:]...)...)
|
p.playQueue = append(p.playQueue[:insertIdx], append(items, p.playQueue[insertIdx:]...)...)
|
||||||
|
|
||||||
if needToSetNext {
|
//if needToSetNext {
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
// p.setNextTrack(p.nowPlayingIdx + 1)
|
||||||
}
|
//}
|
||||||
|
|
||||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||||
return nil
|
return nil
|
||||||
@@ -386,7 +394,7 @@ func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, ins
|
|||||||
p.nowPlayingIdx = -1
|
p.nowPlayingIdx = -1
|
||||||
p.playQueue = nil
|
p.playQueue = nil
|
||||||
}
|
}
|
||||||
needToSetNext := insertMode == InsertNext || (insertMode == Append && p.nowPlayingIdx == len(p.playQueue)-1)
|
p.needToSetNextTrack = insertMode == InsertNext || (insertMode == Append && p.nowPlayingIdx == len(p.playQueue)-1)
|
||||||
insertIdx := len(p.playQueue)
|
insertIdx := len(p.playQueue)
|
||||||
if insertMode == InsertNext {
|
if insertMode == InsertNext {
|
||||||
insertIdx = p.nowPlayingIdx + 1
|
insertIdx = p.nowPlayingIdx + 1
|
||||||
@@ -398,9 +406,9 @@ func (p *playbackEngine) LoadRadioStation(radio *mediaprovider.RadioStation, ins
|
|||||||
copy(new[len(firstHalf)+1:], p.playQueue[insertIdx:])
|
copy(new[len(firstHalf)+1:], p.playQueue[insertIdx:])
|
||||||
p.playQueue = new
|
p.playQueue = new
|
||||||
|
|
||||||
if needToSetNext {
|
//if needToSetNext {
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
// p.setNextTrack(p.nowPlayingIdx + 1)
|
||||||
}
|
//}
|
||||||
|
|
||||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||||
}
|
}
|
||||||
@@ -460,11 +468,11 @@ func (p *playbackEngine) UpdatePlayQueue(items []mediaprovider.MediaItem) error
|
|||||||
if p.nowPlayingIdx >= 0 && newNowPlayingIdx == -1 {
|
if p.nowPlayingIdx >= 0 && newNowPlayingIdx == -1 {
|
||||||
return p.Stop()
|
return p.Stop()
|
||||||
}
|
}
|
||||||
needToUpdateNext := p.nowPlayingIdx >= 0
|
p.needToSetNextTrack = p.nowPlayingIdx >= 0
|
||||||
p.nowPlayingIdx = newNowPlayingIdx
|
p.nowPlayingIdx = newNowPlayingIdx
|
||||||
if needToUpdateNext {
|
//if needToUpdateNext {
|
||||||
p.setNextTrackAfterQueueUpdate()
|
// p.setNextTrackBasedOnLoopMode(false)
|
||||||
}
|
//}
|
||||||
|
|
||||||
p.invokeNoArgCallbacks(p.onQueueChange)
|
p.invokeNoArgCallbacks(p.onQueueChange)
|
||||||
return nil
|
return nil
|
||||||
@@ -510,7 +518,8 @@ func (p *playbackEngine) RemoveTracksFromQueue(idxs []int) {
|
|||||||
// when we receive new track event from player
|
// when we receive new track event from player
|
||||||
} else if isNextPlayingTrackremoved {
|
} else if isNextPlayingTrackremoved {
|
||||||
if newNowPlaying < len(newQueue)-1 {
|
if newNowPlaying < len(newQueue)-1 {
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
p.needToSetNextTrack = true
|
||||||
|
//p.setNextTrack(p.nowPlayingIdx + 1)
|
||||||
} else {
|
} else {
|
||||||
// no next track to play
|
// no next track to play
|
||||||
p.setNextTrack(-1)
|
p.setNextTrack(-1)
|
||||||
@@ -606,7 +615,7 @@ func (p *playbackEngine) handleOnTrackChange() {
|
|||||||
p.curTrackDuration = float64(nowPlaying.Metadata().Duration)
|
p.curTrackDuration = float64(nowPlaying.Metadata().Duration)
|
||||||
p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
|
p.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
|
||||||
p.invokeOnSongChangeCallbacks()
|
p.invokeOnSongChangeCallbacks()
|
||||||
p.doUpdateTimePos(false)
|
p.handleTimePosUpdate(false)
|
||||||
p.cacheNextTracks()
|
p.cacheNextTracks()
|
||||||
p.setNextTrackBasedOnLoopMode(false)
|
p.setNextTrackBasedOnLoopMode(false)
|
||||||
}
|
}
|
||||||
@@ -617,7 +626,7 @@ func (p *playbackEngine) handleOnStopped() {
|
|||||||
p.checkScrobble()
|
p.checkScrobble()
|
||||||
}
|
}
|
||||||
p.stopPollTimePos()
|
p.stopPollTimePos()
|
||||||
p.doUpdateTimePos(false)
|
p.handleTimePosUpdate(false)
|
||||||
p.invokeOnSongChangeCallbacks()
|
p.invokeOnSongChangeCallbacks()
|
||||||
p.invokeNoArgCallbacks(p.onStopped)
|
p.invokeNoArgCallbacks(p.onStopped)
|
||||||
p.alreadyScrobbled = false
|
p.alreadyScrobbled = false
|
||||||
@@ -646,32 +655,23 @@ func (p *playbackEngine) setNextTrackBasedOnLoopMode(onLoopModeChange bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) setNextTrackAfterQueueUpdate() {
|
|
||||||
switch p.loopMode {
|
|
||||||
case LoopNone:
|
|
||||||
if p.nowPlayingIdx < len(p.playQueue)-1 {
|
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
|
||||||
} else {
|
|
||||||
// need to erase next track
|
|
||||||
p.setNextTrack(-1)
|
|
||||||
}
|
|
||||||
case LoopOne:
|
|
||||||
p.setNextTrack(p.nowPlayingIdx)
|
|
||||||
case LoopAll:
|
|
||||||
if p.nowPlayingIdx >= len(p.playQueue)-1 {
|
|
||||||
p.setNextTrack(0)
|
|
||||||
} else {
|
|
||||||
p.setNextTrack(p.nowPlayingIdx + 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
|
func (p *playbackEngine) setTrack(idx int, next bool, startTime float64) error {
|
||||||
if urlP, ok := p.player.(player.URLPlayer); ok {
|
if urlP, ok := p.player.(player.URLPlayer); ok {
|
||||||
url := ""
|
url := ""
|
||||||
var meta mediaprovider.MediaItemMetadata
|
var meta mediaprovider.MediaItemMetadata
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
url = p.getMediaURLForIdx(idx)
|
item := p.playQueue[idx]
|
||||||
|
track, isTrack := item.(*mediaprovider.Track)
|
||||||
|
meta = item.Metadata()
|
||||||
|
if isTrack && p.audiocache != nil {
|
||||||
|
if filepath := p.audiocache.PathForCachedFile(track.ID); filepath != "" {
|
||||||
|
url = filepath
|
||||||
|
log.Println("playing file from cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if url == "" {
|
||||||
|
url = p.getMediaURLForIdx(idx)
|
||||||
|
}
|
||||||
if url == "" {
|
if url == "" {
|
||||||
return errors.New("no stream URL")
|
return errors.New("no stream URL")
|
||||||
}
|
}
|
||||||
@@ -815,7 +815,7 @@ func (p *playbackEngine) startPollTimePos() {
|
|||||||
pollingTick.Stop()
|
pollingTick.Stop()
|
||||||
return
|
return
|
||||||
case <-pollingTick.C:
|
case <-pollingTick.C:
|
||||||
p.doUpdateTimePos(false)
|
p.handleTimePosUpdate(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@@ -828,11 +828,20 @@ func (p *playbackEngine) stopPollTimePos() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *playbackEngine) doUpdateTimePos(seeked bool) {
|
func (p *playbackEngine) handleTimePosUpdate(seeked bool) {
|
||||||
|
s := p.PlaybackStatus()
|
||||||
|
var meta mediaprovider.MediaItemMetadata
|
||||||
|
if np := p.NowPlaying(); np != nil {
|
||||||
|
meta = np.Metadata()
|
||||||
|
}
|
||||||
|
isNearEnd := meta.Type != mediaprovider.MediaItemTypeRadioStation && s.TimePos > float64(meta.Duration)-10
|
||||||
|
if p.needToSetNextTrack && isNearEnd {
|
||||||
|
p.needToSetNextTrack = false
|
||||||
|
p.setNextTrackBasedOnLoopMode(false)
|
||||||
|
}
|
||||||
if p.callbacksDisabled {
|
if p.callbacksDisabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
s := p.PlaybackStatus()
|
|
||||||
if s.TimePos > p.latestTrackPosition {
|
if s.TimePos > p.latestTrackPosition {
|
||||||
p.latestTrackPosition = s.TimePos
|
p.latestTrackPosition = s.TimePos
|
||||||
}
|
}
|
||||||
@@ -845,17 +854,3 @@ func (p *playbackEngine) doUpdateTimePos(seeked bool) {
|
|||||||
cb(s.TimePos, duration, seeked)
|
cb(s.TimePos, duration, seeked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func minInt(a, b int) int {
|
|
||||||
if a < b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
func maxInt(a, b int) int {
|
|
||||||
if a > b {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -559,7 +559,13 @@ func (d *DLNAPlayer) handleRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new request to the target server
|
// if the url is a filepath for a local cached file, serve it
|
||||||
|
if info, err := os.Stat(url); err == nil && info.Size() > 0 {
|
||||||
|
http.ServeFile(w, r, url)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, proxy request to the music server
|
||||||
proxyReq, err := http.NewRequest(r.Method, url, r.Body)
|
proxyReq, err := http.NewRequest(r.Method, url, r.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|||||||
Reference in New Issue
Block a user