Beginning of reworked PlaybackManager - PARTIAL

This commit is contained in:
Drew Weymouth
2023-12-22 15:50:39 -08:00
parent 0cd58cb51b
commit d9eca8d807
3 changed files with 93 additions and 219 deletions
+71 -46
View File
@@ -64,25 +64,26 @@ func NewPlaybackManager(
// clamp to 99% to avoid any possible rounding issues
scrobbleCfg.ThresholdPercent = clamp(scrobbleCfg.ThresholdPercent, 0, 99)
pm := &PlaybackManager{
ctx: ctx,
sm: s,
player: p,
scrobbleCfg: scrobbleCfg,
transcodeCfg: transcodeCfg,
ctx: ctx,
sm: s,
player: p,
scrobbleCfg: scrobbleCfg,
transcodeCfg: transcodeCfg,
nowPlayingIdx: -1,
}
p.OnTrackChange(func(tracknum int) {
if tracknum >= len(pm.playQueue) {
return
}
p.OnTrackChange(func() {
pm.checkScrobble() // scrobble the previous song if needed
if pm.player.GetStatus().State == player.Playing {
pm.playTimeStopwatch.Start()
}
pm.nowPlayingIdx = tracknum
pm.nowPlayingIdx++
pm.curTrackTime = float64(pm.playQueue[pm.nowPlayingIdx].Duration)
pm.sendNowPlayingScrobble() // Must come before invokeOnChangeCallbacks b/c track may immediately be scrobbled
pm.invokeOnSongChangeCallbacks()
pm.doUpdateTimePos()
if pm.nowPlayingIdx < len(pm.playQueue)-1 {
pm.setTrack(pm.nowPlayingIdx+1, true)
}
})
p.OnSeek(func() {
pm.doUpdateTimePos()
@@ -95,6 +96,7 @@ func NewPlaybackManager(
pm.doUpdateTimePos()
pm.invokeOnSongChangeCallbacks()
pm.invokeNoArgCallbacks(pm.onStopped)
pm.nowPlayingIdx = -1
})
p.OnPaused(func() {
pm.playTimeStopwatch.Stop()
@@ -205,32 +207,24 @@ func (p *PlaybackManager) LoadPlaylist(playlistID string, appendToQueue bool, sh
func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, appendToQueue, shuffle bool) error {
if !appendToQueue {
p.player.Stop()
p.nowPlayingIdx = 0
p.nowPlayingIdx = -1
p.playQueue = nil
}
nums := util.Range(len(tracks))
if shuffle {
util.ShuffleSlice(nums)
}
needToSetNext := appendToQueue && len(tracks) > 0 && p.nowPlayingIdx == len(p.playQueue)-1
for _, i := range nums {
if urlP, ok := p.player.(player.URLPlayer); ok {
url, err := p.sm.Server.GetStreamURL(tracks[i].ID, p.transcodeCfg.ForceRawFile)
if err != nil {
return err
}
urlP.AppendFile(url)
} else if trP, ok := p.player.(player.TrackPlayer); ok {
trP.AppendTrack(tracks[i])
} else {
panic("unsupported player type")
}
// ensure a deep copy of the track info so that we can maintain our own state
// (tracking play count increases, favorite, and rating) without messing up
// other views' track models
tr := *tracks[i]
p.playQueue = append(p.playQueue, &tr)
}
if needToSetNext {
p.setTrack(p.nowPlayingIdx+1, true)
}
return nil
}
@@ -241,7 +235,7 @@ func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int, shuffle bool
if p.replayGainCfg.Mode == ReplayGainAuto {
p.SetReplayGainMode(player.ReplayGainAlbum)
}
return p.player.PlayTrackAt(firstTrack)
return p.PlayTrackAt(firstTrack)
}
func (p *PlaybackManager) PlayPlaylist(playlistID string, firstTrack int, shuffle bool) error {
@@ -251,7 +245,7 @@ func (p *PlaybackManager) PlayPlaylist(playlistID string, firstTrack int, shuffl
if p.replayGainCfg.Mode == ReplayGainAuto {
p.SetReplayGainMode(player.ReplayGainTrack)
}
return p.player.PlayTrackAt(firstTrack)
return p.PlayTrackAt(firstTrack)
}
func (p *PlaybackManager) PlayTrack(trackID string) error {
@@ -267,11 +261,15 @@ func (p *PlaybackManager) PlayTrack(trackID string) error {
}
func (p *PlaybackManager) PlayFromBeginning() error {
return p.player.PlayTrackAt(0)
return p.PlayTrackAt(0)
}
func (p *PlaybackManager) PlayTrackAt(idx int) error {
return p.player.PlayTrackAt(idx)
if idx < 0 || idx >= len(p.playQueue) {
return errors.New("track index out of range")
}
p.nowPlayingIdx = idx - 1
return p.setTrack(idx, false)
}
func (p *PlaybackManager) PlayRandomSongs(genreName string) {
@@ -325,7 +323,7 @@ func (p *PlaybackManager) OnTrackRatingChanged(id string, rating int) {
func (p *PlaybackManager) RemoveTracksFromQueue(trackIDs []string) {
newQueue := make([]*mediaprovider.Track, 0, len(p.playQueue)-len(trackIDs))
rmCount := 0
//rmCount := 0
idSet := sharedutil.ToSet(trackIDs)
isPlayingTrackRemoved := false
for i, tr := range p.playQueue {
@@ -336,13 +334,14 @@ func (p *PlaybackManager) RemoveTracksFromQueue(trackIDs []string) {
// If we are removing the currently playing track, we need to scrobble it
p.checkScrobble()
}
if err := p.player.RemoveTrackAt(i - rmCount); err == nil {
rmCount++
} else {
log.Printf("error removing track: %v", err.Error())
// did not remove this track
newQueue = append(newQueue, tr)
}
// if err := p.player.RemoveTrackAt(i - rmCount); err == nil {
// rmCount++
//} else {
var err error
log.Printf("error removing track: %v", err.Error())
// did not remove this track
newQueue = append(newQueue, tr)
//}
} else {
// not removing this track
newQueue = append(newQueue, tr)
@@ -359,9 +358,9 @@ func (p *PlaybackManager) RemoveTracksFromQueue(trackIDs []string) {
// Stop playback and clear the play queue.
func (p *PlaybackManager) StopAndClearPlayQueue() {
p.player.Stop()
p.player.ClearPlayQueue()
p.doUpdateTimePos()
p.playQueue = nil
p.nowPlayingIdx = -1
}
func (p *PlaybackManager) SetReplayGainOptions(config ReplayGainConfig) {
@@ -423,17 +422,17 @@ func (p *PlaybackManager) SetNextLoopMode() error {
if err != nil {
return err
}
for _, cb := range p.onLoopModeChange {
cb(p.player.GetLoopMode())
}
//for _, cb := range p.onLoopModeChange {
// cb(p.player.GetLoopMode())
//}
return nil
}
func (p *PlaybackManager) SetLoopMode(loopMode player.LoopMode) error {
if err := p.player.SetLoopMode(player.LoopMode(loopMode)); err != nil {
return err
}
//if err := p.player.SetLoopMode(player.LoopMode(loopMode)); err != nil {
// return err
//}
for _, cb := range p.onLoopModeChange {
cb(loopMode)
@@ -443,7 +442,8 @@ func (p *PlaybackManager) SetLoopMode(loopMode player.LoopMode) error {
}
func (p *PlaybackManager) GetLoopMode() player.LoopMode {
return p.player.GetLoopMode()
return 0
//return p.player.GetLoopMode()
}
func (p *PlaybackManager) PlayerStatus() player.Status {
@@ -466,14 +466,17 @@ func (p *PlaybackManager) Volume() int {
}
func (p *PlaybackManager) SeekNext() error {
return p.player.SeekNext()
if p.CurrentPlayer().GetStatus().State == player.Stopped {
return nil
}
return p.PlayTrackAt(p.nowPlayingIdx + 1)
}
func (p *PlaybackManager) SeekBackOrPrevious() error {
if p.player.GetStatus().TimePos > 3 {
if p.nowPlayingIdx == 0 || p.player.GetStatus().TimePos > 3 {
return p.player.SeekSeconds(0)
}
return p.player.SeekPrevious()
return p.PlayTrackAt(p.nowPlayingIdx - 1)
}
// Seek to given absolute position in the current track by seconds.
@@ -501,6 +504,9 @@ func (p *PlaybackManager) Pause() error {
}
func (p *PlaybackManager) Continue() error {
if p.player.GetStatus().State == player.Stopped {
return p.PlayFromBeginning()
}
return p.player.Continue()
}
@@ -516,6 +522,25 @@ func (p *PlaybackManager) PlayPause() error {
return errors.New("unreached - invalid player state")
}
func (p *PlaybackManager) setTrack(idx int, next bool) error {
if urlP, ok := p.player.(player.URLPlayer); ok {
url, err := p.sm.Server.GetStreamURL(p.playQueue[idx].ID, p.transcodeCfg.ForceRawFile)
if err != nil {
return err
}
if next {
return urlP.SetNextFile(url)
}
return urlP.PlayFile(url)
} else if trP, ok := p.player.(player.TrackPlayer); ok {
if next {
return trP.SetNextTrack(p.playQueue[idx])
}
return trP.PlayTrack(p.playQueue[idx])
}
panic("Unsupported player type")
}
// call BEFORE updating p.nowPlayingIdx
func (p *PlaybackManager) checkScrobble() {
if !p.scrobbleCfg.Enabled || len(p.playQueue) == 0 || p.nowPlayingIdx < 0 {
+18 -129
View File
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"log"
"math"
"strconv"
@@ -58,9 +57,9 @@ type Player struct {
haveRGainOpts bool
audioExclusive bool
status player.Status
loopMode player.LoopMode
seeking bool
curPlaylistPos int64
lenPlaylist int64
prePausedState player.State
clientName string
equalizer Equalizer
@@ -72,7 +71,7 @@ type Player struct {
onStopped []func()
onPlaying []func()
onSeek []func()
onTrackChange []func(int)
onTrackChange []func()
}
// Returns a new player.
@@ -133,38 +132,19 @@ func (p *Player) Init(maxCacheMB int) error {
return nil
}
// Appends the given file to the play queue.
// Note that the Player API does not provide methods to read
// the play queue. Clients are expected to maintain their own play queue model.
func (p *Player) AppendFile(url string) error {
log.Printf("Adding playback URL: %s", url)
if !p.initialized {
return ErrUnitialized
}
return p.mpv.Command([]string{"loadfile", url, "append"})
}
// Plays the specified file, clearing the previous play queue, if any.
func (p *Player) PlayFile(url string) error {
log.Printf("Adding playback URL: %s", url)
if !p.initialized {
return ErrUnitialized
}
err := p.mpv.Command([]string{"loadfile", url, "replace"})
if err == nil {
p.setState(player.Playing)
p.lenPlaylist = 1
}
return err
}
// Removes the item at the given index from the internal playqueue.
func (p *Player) RemoveTrackAt(idx int) error {
if !p.initialized {
return ErrUnitialized
}
return p.mpv.Command([]string{"playlist-remove", strconv.Itoa(idx)})
}
// Stops playback and clears the play queue.
func (p *Player) Stop() error {
if !p.initialized {
@@ -180,30 +160,24 @@ func (p *Player) Stop() error {
}
}
if err == nil {
p.lenPlaylist = 0
p.setState(player.Stopped)
}
return err
}
// Clears the play queue, except for the currently playing file.
func (p *Player) ClearPlayQueue() error {
if !p.initialized {
return ErrUnitialized
func (p *Player) SetNextFile(url string) error {
if p.lenPlaylist > p.curPlaylistPos+1 {
if err := p.mpv.Command([]string{"playlist-remove", strconv.Itoa(int(p.curPlaylistPos) + 1)}); err != nil {
return err
}
p.lenPlaylist--
}
return p.mpv.Command([]string{"playlist-clear"})
}
func (p *Player) SetFile(url string) error {
if !p.initialized {
return ErrUnitialized
err := p.mpv.Command([]string{"loadfile", url, "append"})
if err == nil {
p.lenPlaylist++
}
if err := p.mpv.Command([]string{"stop"}); err != nil {
return err
}
if err := p.mpv.Command([]string{"playlist-clear"}); err != nil {
return err
}
return p.mpv.Command([]string{"loadfile", url, "append"})
return err
}
// Seeks within the currently playing track.
@@ -218,27 +192,6 @@ func (p *Player) SeekSeconds(secs float64) error {
return err
}
// Seeks to the beginning of the previous track,
// or if no previous track, seeks to the beginning of the current track.
func (p *Player) SeekPrevious() error {
if !p.initialized {
return ErrUnitialized
}
if pos, err := p.getInt64Property("playlist-pos"); err == nil && pos == 0 {
return p.SeekSeconds(0)
}
return p.mpv.Command([]string{"playlist-prev"})
}
// Seeks to the next track in the play queue, if any.
func (p *Player) SeekNext() error {
if !p.initialized {
return ErrUnitialized
}
return p.mpv.Command([]string{"playlist-next"})
}
// Sets the volume of the player (0-100).
// Unlike most Player functions, SetVolume can be called before Init,
// to set the initial volume of the player on startup.
@@ -325,22 +278,6 @@ func (p *Player) setPaused(paused bool) error {
return err
}
// Start playback from the specified track index in the play queue.
func (p *Player) PlayTrackAt(idx int) error {
// check if we have anything to play
if c, err := p.getInt64Property("playlist-count"); err == nil && c <= int64(idx) {
return nil
}
err := p.mpv.Command([]string{"playlist-play-index", strconv.Itoa(idx)})
if p.GetStatus().State == player.Paused {
err = p.setPaused(false)
}
if err == nil {
p.setState(player.Playing)
}
return err
}
// Pause playback and update the player state
func (p *Player) Pause() error {
if p.status.State != player.Playing {
@@ -362,57 +299,11 @@ func (p *Player) Continue() error {
p.setState(p.prePausedState)
}
return err
} else if p.status.State == player.Stopped {
return p.PlayTrackAt(0)
}
return nil
}
// Get the loop mode of the player.
func (p *Player) GetLoopMode() player.LoopMode {
return p.loopMode
}
// Set the loop mode of the player.
func (p *Player) SetLoopMode(mode player.LoopMode) error {
if !p.initialized {
return ErrUnitialized
}
// Return early if player is already in specified mode
if mode == p.loopMode {
return nil
}
switch mode {
case player.LoopNone:
if err := p.mpv.SetOptionString("loop-playlist", "no"); err != nil {
return err
}
if err := p.mpv.SetOptionString("loop-file", "no"); err != nil {
return err
}
case player.LoopAll:
if err := p.mpv.SetOptionString("loop-playlist", "inf"); err != nil {
return err
}
if err := p.mpv.SetOptionString("loop-file", "no"); err != nil {
return err
}
case player.LoopOne:
if err := p.mpv.SetOptionString("loop-playlist", "no"); err != nil {
return err
}
if err := p.mpv.SetOptionString("loop-file", "inf"); err != nil {
return err
}
}
p.loopMode = mode
return nil
}
// Get the current status of the player.
func (p *Player) GetStatus() player.Status {
if !p.initialized {
@@ -539,7 +430,7 @@ func (p *Player) OnSeek(cb func()) {
// Registers a callback which is invoked when the currently playing track changes,
// or when playback begins at any time from the Stopped state.
// Callback is invoked with the index of the currently playing track (zero-based).
func (p *Player) OnTrackChange(cb func(int)) {
func (p *Player) OnTrackChange(cb func()) {
p.onTrackChange = append(p.onTrackChange, cb)
}
@@ -600,6 +491,7 @@ func (p *Player) eventHandler(ctx context.Context) {
cb()
}
case mpv.EVENT_FILE_LOADED:
p.curPlaylistPos, _ = p.getInt64Property("playlist-pos")
if p.status.State == player.Paused {
// seek while paused switches to a new file
// mpv does not fire seek event in this case
@@ -607,11 +499,8 @@ func (p *Player) eventHandler(ctx context.Context) {
cb()
}
}
if pos, err := p.getInt64Property("playlist-pos"); err == nil {
p.curPlaylistPos = pos
for _, cb := range p.onTrackChange {
cb(int(pos))
}
for _, cb := range p.onTrackChange {
cb()
}
case mpv.EVENT_IDLE:
p.status.Duration = 0
+4 -44
View File
@@ -4,57 +4,17 @@ import "github.com/dweymouth/supersonic/backend/mediaprovider"
type URLPlayer interface {
BasePlayer
AppendFile(url string) error
}
type URLPlayerNew interface {
BasePlayer
SetFile(url string)
SetNextFile(url string)
PlayFile(url string) error
SetNextFile(url string) error
}
type TrackPlayer interface {
BasePlayer
AppendTrack(track *mediaprovider.Track) error
}
type TrackPlayerNew interface {
BasePlayer
SetTrack(track *mediaprovider.Track) error
PlayTrack(track *mediaprovider.Track) error
SetNextTrack(track *mediaprovider.Track) error
}
type BasePlayer interface {
// Transport
PlayTrackAt(idx int) error
Continue() error
Pause() error
Stop() error
SeekPrevious() error
SeekNext() error
SeekSeconds(secs float64) error
IsSeeking() bool
SetVolume(int) error
GetVolume() int
GetStatus() Status
ClearPlayQueue() error
RemoveTrackAt(idx int) error
SetLoopMode(LoopMode) error
GetLoopMode() LoopMode
// Event API
OnPaused(func())
OnStopped(func())
OnPlaying(func())
OnSeek(func())
OnTrackChange(func(int))
}
type BasePlayerNew interface {
Continue() error
Pause() error
Stop() error
@@ -72,7 +32,7 @@ type BasePlayerNew interface {
OnStopped(func())
OnPlaying(func())
OnSeek(func())
OnTrackChange(func(int))
OnTrackChange(func())
}
type ReplayGainPlayer interface {