add interface to decouple MPV player from PlaybackManager
This commit is contained in:
+7
-7
@@ -207,12 +207,12 @@ func (m *MPRISHandler) PlaybackStatus() (types.PlaybackStatus, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *MPRISHandler) LoopStatus() (types.LoopStatus, error) {
|
func (m *MPRISHandler) LoopStatus() (types.LoopStatus, error) {
|
||||||
switch m.pm.LoopMode() {
|
switch m.pm.GetLoopMode() {
|
||||||
case LoopModeAll:
|
case player.LoopAll:
|
||||||
return types.LoopStatusPlaylist, nil
|
return types.LoopStatusPlaylist, nil
|
||||||
case LoopModeOne:
|
case player.LoopOne:
|
||||||
return types.LoopStatusTrack, nil
|
return types.LoopStatusTrack, nil
|
||||||
case LoopModeNone:
|
case player.LoopNone:
|
||||||
return types.LoopStatusNone, nil
|
return types.LoopStatusNone, nil
|
||||||
}
|
}
|
||||||
return "", errors.New("unknown loop status")
|
return "", errors.New("unknown loop status")
|
||||||
@@ -221,11 +221,11 @@ func (m *MPRISHandler) LoopStatus() (types.LoopStatus, error) {
|
|||||||
func (m *MPRISHandler) SetLoopStatus(status types.LoopStatus) error {
|
func (m *MPRISHandler) SetLoopStatus(status types.LoopStatus) error {
|
||||||
switch status {
|
switch status {
|
||||||
case types.LoopStatusPlaylist:
|
case types.LoopStatusPlaylist:
|
||||||
return m.pm.SetLoopMode(LoopModeAll)
|
return m.pm.SetLoopMode(player.LoopAll)
|
||||||
case types.LoopStatusTrack:
|
case types.LoopStatusTrack:
|
||||||
return m.pm.SetLoopMode(LoopModeOne)
|
return m.pm.SetLoopMode(player.LoopOne)
|
||||||
case types.LoopStatusNone:
|
case types.LoopStatusNone:
|
||||||
return m.pm.SetLoopMode(LoopModeNone)
|
return m.pm.SetLoopMode(player.LoopNone)
|
||||||
}
|
}
|
||||||
return errors.New("unknown loop status")
|
return errors.New("unknown loop status")
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-35
@@ -2,14 +2,12 @@ package backend
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/backend/util"
|
"github.com/dweymouth/supersonic/backend/util"
|
||||||
"github.com/dweymouth/supersonic/player"
|
"github.com/dweymouth/supersonic/player"
|
||||||
"github.com/dweymouth/supersonic/player/mpv"
|
|
||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,14 +18,6 @@ var (
|
|||||||
ReplayGainAuto = "Auto"
|
ReplayGainAuto = "Auto"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoopMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
LoopModeNone LoopMode = LoopMode(player.LoopNone)
|
|
||||||
LoopModeAll LoopMode = LoopMode(player.LoopAll)
|
|
||||||
LoopModeOne LoopMode = LoopMode(player.LoopOne)
|
|
||||||
)
|
|
||||||
|
|
||||||
// A high-level Subsonic-aware playback backend.
|
// A high-level Subsonic-aware playback backend.
|
||||||
// Manages loading tracks into the Player queue,
|
// Manages loading tracks into the Player queue,
|
||||||
// sending callbacks on play time updates and track changes.
|
// sending callbacks on play time updates and track changes.
|
||||||
@@ -36,7 +26,7 @@ type PlaybackManager struct {
|
|||||||
cancelPollPos context.CancelFunc
|
cancelPollPos context.CancelFunc
|
||||||
pollingTick *time.Ticker
|
pollingTick *time.Ticker
|
||||||
sm *ServerManager
|
sm *ServerManager
|
||||||
player *mpv.Player
|
player player.BasePlayer
|
||||||
|
|
||||||
playTimeStopwatch util.Stopwatch
|
playTimeStopwatch util.Stopwatch
|
||||||
curTrackTime float64
|
curTrackTime float64
|
||||||
@@ -44,7 +34,7 @@ type PlaybackManager struct {
|
|||||||
callbacksDisabled bool
|
callbacksDisabled bool
|
||||||
|
|
||||||
playQueue []*mediaprovider.Track
|
playQueue []*mediaprovider.Track
|
||||||
nowPlayingIdx int64
|
nowPlayingIdx int
|
||||||
|
|
||||||
// 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
|
||||||
@@ -55,7 +45,7 @@ type PlaybackManager struct {
|
|||||||
// registered callbacks
|
// registered callbacks
|
||||||
onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track)
|
onSongChange []func(nowPlaying, justScrobbledIfAny *mediaprovider.Track)
|
||||||
onPlayTimeUpdate []func(float64, float64)
|
onPlayTimeUpdate []func(float64, float64)
|
||||||
onLoopModeChange []func(LoopMode)
|
onLoopModeChange []func(player.LoopMode)
|
||||||
onVolumeChange []func(int)
|
onVolumeChange []func(int)
|
||||||
onSeek []func()
|
onSeek []func()
|
||||||
onPaused []func()
|
onPaused []func()
|
||||||
@@ -66,7 +56,7 @@ type PlaybackManager struct {
|
|||||||
func NewPlaybackManager(
|
func NewPlaybackManager(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
s *ServerManager,
|
s *ServerManager,
|
||||||
p *mpv.Player,
|
p player.BasePlayer,
|
||||||
scrobbleCfg *ScrobbleConfig,
|
scrobbleCfg *ScrobbleConfig,
|
||||||
transcodeCfg *TranscodingConfig,
|
transcodeCfg *TranscodingConfig,
|
||||||
) *PlaybackManager {
|
) *PlaybackManager {
|
||||||
@@ -79,8 +69,8 @@ func NewPlaybackManager(
|
|||||||
scrobbleCfg: scrobbleCfg,
|
scrobbleCfg: scrobbleCfg,
|
||||||
transcodeCfg: transcodeCfg,
|
transcodeCfg: transcodeCfg,
|
||||||
}
|
}
|
||||||
p.OnTrackChange(func(tracknum int64) {
|
p.OnTrackChange(func(tracknum int) {
|
||||||
if tracknum >= int64(len(pm.playQueue)) {
|
if tracknum >= len(pm.playQueue) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pm.checkScrobble() // scrobble the previous song if needed
|
pm.checkScrobble() // scrobble the previous song if needed
|
||||||
@@ -165,7 +155,7 @@ func (p *PlaybackManager) OnPlayTimeUpdate(cb func(float64, float64)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Registers a callback that is notified whenever the loop mode changes.
|
// Registers a callback that is notified whenever the loop mode changes.
|
||||||
func (p *PlaybackManager) OnLoopModeChange(cb func(LoopMode)) {
|
func (p *PlaybackManager) OnLoopModeChange(cb func(player.LoopMode)) {
|
||||||
p.onLoopModeChange = append(p.onLoopModeChange, cb)
|
p.onLoopModeChange = append(p.onLoopModeChange, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,11 +213,18 @@ func (p *PlaybackManager) LoadTracks(tracks []*mediaprovider.Track, appendToQueu
|
|||||||
util.ShuffleSlice(nums)
|
util.ShuffleSlice(nums)
|
||||||
}
|
}
|
||||||
for _, i := range nums {
|
for _, i := range nums {
|
||||||
url, err := p.sm.Server.GetStreamURL(tracks[i].ID, p.transcodeCfg.ForceRawFile)
|
|
||||||
if err != nil {
|
if urlP, ok := p.player.(player.URLPlayer); ok {
|
||||||
return err
|
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.TrackIDPlayer); ok {
|
||||||
|
trP.AppendTrack(tracks[i].ID)
|
||||||
|
} else {
|
||||||
|
panic("unsupported player type")
|
||||||
}
|
}
|
||||||
p.player.AppendFile(url)
|
|
||||||
// ensure a deep copy of the track info so that we can maintain our own state
|
// 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
|
// (tracking play count increases, favorite, and rating) without messing up
|
||||||
// other views' track models
|
// other views' track models
|
||||||
@@ -245,7 +242,7 @@ func (p *PlaybackManager) PlayAlbum(albumID string, firstTrack int, shuffle bool
|
|||||||
p.SetReplayGainMode(player.ReplayGainAlbum)
|
p.SetReplayGainMode(player.ReplayGainAlbum)
|
||||||
}
|
}
|
||||||
if firstTrack <= 0 {
|
if firstTrack <= 0 {
|
||||||
return p.player.PlayFromBeginning()
|
return p.player.PlayTrackAt(0)
|
||||||
}
|
}
|
||||||
return p.player.PlayTrackAt(firstTrack)
|
return p.player.PlayTrackAt(firstTrack)
|
||||||
}
|
}
|
||||||
@@ -258,7 +255,7 @@ func (p *PlaybackManager) PlayPlaylist(playlistID string, firstTrack int, shuffl
|
|||||||
p.SetReplayGainMode(player.ReplayGainTrack)
|
p.SetReplayGainMode(player.ReplayGainTrack)
|
||||||
}
|
}
|
||||||
if firstTrack <= 0 {
|
if firstTrack <= 0 {
|
||||||
return p.player.PlayFromBeginning()
|
return p.player.PlayTrackAt(0)
|
||||||
}
|
}
|
||||||
return p.player.PlayTrackAt(firstTrack)
|
return p.player.PlayTrackAt(firstTrack)
|
||||||
}
|
}
|
||||||
@@ -276,7 +273,7 @@ func (p *PlaybackManager) PlayTrack(trackID string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) PlayFromBeginning() error {
|
func (p *PlaybackManager) PlayFromBeginning() error {
|
||||||
return p.player.PlayFromBeginning()
|
return p.player.PlayTrackAt(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) PlayTrackAt(idx int) error {
|
func (p *PlaybackManager) PlayTrackAt(idx int) error {
|
||||||
@@ -374,6 +371,12 @@ func (p *PlaybackManager) StopAndClearPlayQueue() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) SetReplayGainOptions(config ReplayGainConfig) {
|
func (p *PlaybackManager) SetReplayGainOptions(config ReplayGainConfig) {
|
||||||
|
rGainPlayer, ok := p.player.(player.ReplayGainPlayer)
|
||||||
|
if !ok {
|
||||||
|
log.Println("Error: player doesn't support ReplayGain")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
p.replayGainCfg = config
|
p.replayGainCfg = config
|
||||||
mode := player.ReplayGainNone
|
mode := player.ReplayGainNone
|
||||||
switch config.Mode {
|
switch config.Mode {
|
||||||
@@ -388,7 +391,7 @@ func (p *PlaybackManager) SetReplayGainOptions(config ReplayGainConfig) {
|
|||||||
if config.Mode == ReplayGainAuto {
|
if config.Mode == ReplayGainAuto {
|
||||||
mode = player.ReplayGainTrack
|
mode = player.ReplayGainTrack
|
||||||
}
|
}
|
||||||
p.player.SetReplayGainOptions(player.ReplayGainOptions{
|
rGainPlayer.SetReplayGainOptions(player.ReplayGainOptions{
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
PreventClipping: config.PreventClipping,
|
PreventClipping: config.PreventClipping,
|
||||||
PreampGain: config.PreampGainDB,
|
PreampGain: config.PreampGainDB,
|
||||||
@@ -396,7 +399,12 @@ func (p *PlaybackManager) SetReplayGainOptions(config ReplayGainConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) SetReplayGainMode(mode player.ReplayGainMode) {
|
func (p *PlaybackManager) SetReplayGainMode(mode player.ReplayGainMode) {
|
||||||
p.player.SetReplayGainOptions(player.ReplayGainOptions{
|
rGainPlayer, ok := p.player.(player.ReplayGainPlayer)
|
||||||
|
if !ok {
|
||||||
|
log.Println("Error: player doesn't support ReplayGain")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rGainPlayer.SetReplayGainOptions(player.ReplayGainOptions{
|
||||||
PreventClipping: p.replayGainCfg.PreventClipping,
|
PreventClipping: p.replayGainCfg.PreventClipping,
|
||||||
PreampGain: p.replayGainCfg.PreampGainDB,
|
PreampGain: p.replayGainCfg.PreampGainDB,
|
||||||
Mode: mode,
|
Mode: mode,
|
||||||
@@ -406,18 +414,29 @@ func (p *PlaybackManager) SetReplayGainMode(mode player.ReplayGainMode) {
|
|||||||
// Changes the loop mode of the player to the next one.
|
// Changes the loop mode of the player to the next one.
|
||||||
// Useful for toggling UI elements, to change modes without knowing the current player mode.
|
// Useful for toggling UI elements, to change modes without knowing the current player mode.
|
||||||
func (p *PlaybackManager) SetNextLoopMode() error {
|
func (p *PlaybackManager) SetNextLoopMode() error {
|
||||||
if err := p.player.SetNextLoopMode(); err != nil {
|
var err error
|
||||||
return err
|
switch p.GetLoopMode() {
|
||||||
|
case player.LoopNone:
|
||||||
|
err = p.SetLoopMode(player.LoopAll)
|
||||||
|
case player.LoopAll:
|
||||||
|
err = p.SetLoopMode(player.LoopOne)
|
||||||
|
case player.LoopOne:
|
||||||
|
err = p.SetLoopMode(player.LoopNone)
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for _, cb := range p.onLoopModeChange {
|
for _, cb := range p.onLoopModeChange {
|
||||||
cb(LoopMode(p.player.GetLoopMode()))
|
cb(p.player.GetLoopMode())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) SetLoopMode(loopMode LoopMode) error {
|
func (p *PlaybackManager) SetLoopMode(loopMode player.LoopMode) error {
|
||||||
if err := p.player.SetLoopMode(player.LoopMode(loopMode)); err != nil {
|
if err := p.player.SetLoopMode(player.LoopMode(loopMode)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -429,8 +448,8 @@ func (p *PlaybackManager) SetLoopMode(loopMode LoopMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) LoopMode() LoopMode {
|
func (p *PlaybackManager) GetLoopMode() player.LoopMode {
|
||||||
return LoopMode(p.player.GetLoopMode())
|
return p.player.GetLoopMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) PlayerStatus() player.Status {
|
func (p *PlaybackManager) PlayerStatus() player.Status {
|
||||||
@@ -457,12 +476,15 @@ func (p *PlaybackManager) SeekNext() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) SeekBackOrPrevious() error {
|
func (p *PlaybackManager) SeekBackOrPrevious() error {
|
||||||
return p.player.SeekBackOrPrevious()
|
if p.player.GetStatus().TimePos > 3 {
|
||||||
|
return p.player.SeekSeconds(0)
|
||||||
|
}
|
||||||
|
return p.player.SeekPrevious()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek to given absolute position in the current track by seconds.
|
// Seek to given absolute position in the current track by seconds.
|
||||||
func (p *PlaybackManager) SeekSeconds(sec float64) error {
|
func (p *PlaybackManager) SeekSeconds(sec float64) error {
|
||||||
return p.player.Seek(fmt.Sprintf("%0.2f", sec), mpv.SeekAbsolute)
|
return p.player.SeekSeconds(sec)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seek to a fractional position in the current track [0..1]
|
// Seek to a fractional position in the current track [0..1]
|
||||||
@@ -472,7 +494,8 @@ func (p *PlaybackManager) SeekFraction(fraction float64) error {
|
|||||||
} else if fraction > 1 {
|
} else if fraction > 1 {
|
||||||
fraction = 1
|
fraction = 1
|
||||||
}
|
}
|
||||||
return p.player.Seek(fmt.Sprintf("%0.1f", fraction*100), mpv.SeekAbsolutePercent)
|
target := p.curTrackTime * fraction
|
||||||
|
return p.player.SeekSeconds(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PlaybackManager) Stop() error {
|
func (p *PlaybackManager) Stop() error {
|
||||||
|
|||||||
+13
-55
@@ -15,16 +15,6 @@ import (
|
|||||||
// Error returned by many Player functions if called before the player has not been initialized.
|
// Error returned by many Player functions if called before the player has not been initialized.
|
||||||
var ErrUnitialized error = errors.New("mpv player uninitialized")
|
var ErrUnitialized error = errors.New("mpv player uninitialized")
|
||||||
|
|
||||||
// Argument to Seek function (SeekAbsolute, SeekRelative, SeekAbsolutePercent, SeekRelativePercent).
|
|
||||||
type SeekMode int
|
|
||||||
|
|
||||||
const (
|
|
||||||
SeekAbsolute SeekMode = iota
|
|
||||||
SeekRelative
|
|
||||||
SeekAbsolutePercent
|
|
||||||
SeekRelativePercent
|
|
||||||
)
|
|
||||||
|
|
||||||
// Information about a specific audio device.
|
// Information about a specific audio device.
|
||||||
// Returned by ListAudioDevices.
|
// Returned by ListAudioDevices.
|
||||||
type AudioDevice struct {
|
type AudioDevice struct {
|
||||||
@@ -56,6 +46,8 @@ type MediaInfo struct {
|
|||||||
Bitrate int
|
Bitrate int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var _ player.URLPlayer = (*Player)(nil)
|
||||||
|
|
||||||
// Player encapsulates the mpv instance and provides functions
|
// Player encapsulates the mpv instance and provides functions
|
||||||
// to control it and to check its status.
|
// to control it and to check its status.
|
||||||
type Player struct {
|
type Player struct {
|
||||||
@@ -80,7 +72,7 @@ type Player struct {
|
|||||||
onStopped []func()
|
onStopped []func()
|
||||||
onPlaying []func()
|
onPlaying []func()
|
||||||
onSeek []func()
|
onSeek []func()
|
||||||
onTrackChange []func(int64)
|
onTrackChange []func(int)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a new player.
|
// Returns a new player.
|
||||||
@@ -203,30 +195,25 @@ func (p *Player) ClearPlayQueue() error {
|
|||||||
|
|
||||||
// Seeks within the currently playing track.
|
// Seeks within the currently playing track.
|
||||||
// See MPV seek command documentation for more details.
|
// See MPV seek command documentation for more details.
|
||||||
func (p *Player) Seek(target string, mode SeekMode) error {
|
func (p *Player) SeekSeconds(secs float64) error {
|
||||||
if !p.initialized {
|
if !p.initialized {
|
||||||
return ErrUnitialized
|
return ErrUnitialized
|
||||||
}
|
}
|
||||||
|
target := fmt.Sprintf("%0.1f", secs)
|
||||||
p.seeking = true
|
p.seeking = true
|
||||||
err := p.mpv.Command([]string{"seek", target, mode.String()})
|
err := p.mpv.Command([]string{"seek", target, "absolute"})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Seeks to the beginning of the current track if:
|
// Seeks to the beginning of the previous track,
|
||||||
// - The current track is the first track in the play queue, or
|
// or if no previous track, seeks to the beginning of the current track.
|
||||||
// - The current time is more than 3 seconds past the beginning of the track.
|
func (p *Player) SeekPrevious() error {
|
||||||
//
|
|
||||||
// Else seeks to the beginning of the previous track.
|
|
||||||
func (p *Player) SeekBackOrPrevious() error {
|
|
||||||
if !p.initialized {
|
if !p.initialized {
|
||||||
return ErrUnitialized
|
return ErrUnitialized
|
||||||
}
|
}
|
||||||
|
|
||||||
if pos, err := p.getInt64Property("time-pos"); err == nil && pos > 3 {
|
|
||||||
return p.Seek("0", SeekAbsolutePercent)
|
|
||||||
}
|
|
||||||
if pos, err := p.getInt64Property("playlist-pos"); err == nil && pos == 0 {
|
if pos, err := p.getInt64Property("playlist-pos"); err == nil && pos == 0 {
|
||||||
return p.Seek("0", SeekAbsolutePercent)
|
return p.SeekSeconds(0)
|
||||||
}
|
}
|
||||||
return p.mpv.Command([]string{"playlist-prev"})
|
return p.mpv.Command([]string{"playlist-prev"})
|
||||||
}
|
}
|
||||||
@@ -445,21 +432,6 @@ func (p *Player) SetLoopMode(mode player.LoopMode) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Change the loop mode of the player to the next one.
|
|
||||||
// Useful for toggling UI elements, to change modes without knowing the current player mode.
|
|
||||||
func (p *Player) SetNextLoopMode() error {
|
|
||||||
switch p.loopMode {
|
|
||||||
case player.LoopNone:
|
|
||||||
return p.SetLoopMode(player.LoopAll)
|
|
||||||
case player.LoopAll:
|
|
||||||
return p.SetLoopMode(player.LoopOne)
|
|
||||||
case player.LoopOne:
|
|
||||||
return p.SetLoopMode(player.LoopNone)
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the current status of the player.
|
// Get the current status of the player.
|
||||||
func (p *Player) GetStatus() player.Status {
|
func (p *Player) GetStatus() player.Status {
|
||||||
if !p.initialized {
|
if !p.initialized {
|
||||||
@@ -475,7 +447,7 @@ func (p *Player) GetStatus() player.Status {
|
|||||||
p.status.Duration = dur.(float64)
|
p.status.Duration = dur.(float64)
|
||||||
}
|
}
|
||||||
if playpos, err := p.getInt64Property("playlist-pos"); err == nil {
|
if playpos, err := p.getInt64Property("playlist-pos"); err == nil {
|
||||||
p.status.PlaylistPos = playpos
|
p.status.PlaylistPos = int(playpos)
|
||||||
}
|
}
|
||||||
return p.status
|
return p.status
|
||||||
}
|
}
|
||||||
@@ -586,7 +558,7 @@ func (p *Player) OnSeek(cb func()) {
|
|||||||
// Registers a callback which is invoked when the currently playing track changes,
|
// Registers a callback which is invoked when the currently playing track changes,
|
||||||
// or when playback begins at any time from the Stopped state.
|
// or when playback begins at any time from the Stopped state.
|
||||||
// Callback is invoked with the index of the currently playing track (zero-based).
|
// Callback is invoked with the index of the currently playing track (zero-based).
|
||||||
func (p *Player) OnTrackChange(cb func(int64)) {
|
func (p *Player) OnTrackChange(cb func(int)) {
|
||||||
p.onTrackChange = append(p.onTrackChange, cb)
|
p.onTrackChange = append(p.onTrackChange, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -657,7 +629,7 @@ func (p *Player) eventHandler(ctx context.Context) {
|
|||||||
if pos, err := p.getInt64Property("playlist-pos"); err == nil {
|
if pos, err := p.getInt64Property("playlist-pos"); err == nil {
|
||||||
p.curPlaylistPos = pos
|
p.curPlaylistPos = pos
|
||||||
for _, cb := range p.onTrackChange {
|
for _, cb := range p.onTrackChange {
|
||||||
cb(pos)
|
cb(int(pos))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case mpv.EVENT_IDLE:
|
case mpv.EVENT_IDLE:
|
||||||
@@ -668,17 +640,3 @@ func (p *Player) eventHandler(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SeekMode) String() string {
|
|
||||||
switch s {
|
|
||||||
case SeekAbsolute:
|
|
||||||
return "absolute"
|
|
||||||
case SeekRelative:
|
|
||||||
return "relative"
|
|
||||||
case SeekAbsolutePercent:
|
|
||||||
return "absolute-percent"
|
|
||||||
case SeekRelativePercent:
|
|
||||||
return "relative-percent"
|
|
||||||
}
|
|
||||||
return "UNKNOWN_SEEK_MODE"
|
|
||||||
}
|
|
||||||
|
|||||||
+48
-1
@@ -1,5 +1,52 @@
|
|||||||
package player
|
package player
|
||||||
|
|
||||||
|
type URLPlayer interface {
|
||||||
|
BasePlayer
|
||||||
|
AppendFile(url string) error
|
||||||
|
PlayFile(url string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackIDPlayer interface {
|
||||||
|
BasePlayer
|
||||||
|
AppendTrack(trackID string) error
|
||||||
|
PlayTrack(trackID string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type BasePlayer interface {
|
||||||
|
// Transport
|
||||||
|
PlayTrackAt(idx int) error
|
||||||
|
Continue() error
|
||||||
|
Pause() error
|
||||||
|
PlayPause() 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 ReplayGainPlayer interface {
|
||||||
|
SetReplayGainOptions(ReplayGainOptions) error
|
||||||
|
}
|
||||||
|
|
||||||
// The playback state (Stopped, Paused, or Playing).
|
// The playback state (Stopped, Paused, or Playing).
|
||||||
type State int
|
type State int
|
||||||
|
|
||||||
@@ -15,7 +62,7 @@ type Status struct {
|
|||||||
State State
|
State State
|
||||||
TimePos float64
|
TimePos float64
|
||||||
Duration float64
|
Duration float64
|
||||||
PlaylistPos int64
|
PlaylistPos int
|
||||||
}
|
}
|
||||||
|
|
||||||
// The playback loop mode (LoopNone, LoopAll, LoopOne).
|
// The playback loop mode (LoopNone, LoopAll, LoopOne).
|
||||||
|
|||||||
+4
-4
@@ -162,13 +162,13 @@ func (m *MainWindow) SetupSystemTrayMenu(appName string, fyneApp fyne.App) {
|
|||||||
if desk, ok := fyneApp.(desktop.App); ok {
|
if desk, ok := fyneApp.(desktop.App); ok {
|
||||||
menu := fyne.NewMenu(appName,
|
menu := fyne.NewMenu(appName,
|
||||||
fyne.NewMenuItem("Play/Pause", func() {
|
fyne.NewMenuItem("Play/Pause", func() {
|
||||||
_ = m.App.Player.PlayPause()
|
_ = m.App.PlaybackManager.PlayPause()
|
||||||
}),
|
}),
|
||||||
fyne.NewMenuItem("Previous", func() {
|
fyne.NewMenuItem("Previous", func() {
|
||||||
_ = m.App.Player.SeekBackOrPrevious()
|
_ = m.App.PlaybackManager.SeekBackOrPrevious()
|
||||||
}),
|
}),
|
||||||
fyne.NewMenuItem("Next", func() {
|
fyne.NewMenuItem("Next", func() {
|
||||||
_ = m.App.Player.SeekNext()
|
_ = m.App.PlaybackManager.SeekNext()
|
||||||
}),
|
}),
|
||||||
fyne.NewMenuItemSeparator(),
|
fyne.NewMenuItemSeparator(),
|
||||||
fyne.NewMenuItem("Volume +10%", func() {
|
fyne.NewMenuItem("Volume +10%", func() {
|
||||||
@@ -298,7 +298,7 @@ func (m *MainWindow) addShortcuts() {
|
|||||||
case fyne.KeyEscape:
|
case fyne.KeyEscape:
|
||||||
m.Controller.CloseEscapablePopUp()
|
m.Controller.CloseEscapablePopUp()
|
||||||
case fyne.KeySpace:
|
case fyne.KeySpace:
|
||||||
m.App.Player.PlayPause()
|
m.App.PlaybackManager.PlayPause()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"fyne.io/fyne/v2/widget"
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/player"
|
||||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
"github.com/dweymouth/supersonic/ui/util"
|
"github.com/dweymouth/supersonic/ui/util"
|
||||||
)
|
)
|
||||||
@@ -67,15 +67,15 @@ func (a *AuxControls) OnChangeLoopMode(f func()) {
|
|||||||
a.loop.OnTapped = f
|
a.loop.OnTapped = f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuxControls) SetLoopMode(mode backend.LoopMode) {
|
func (a *AuxControls) SetLoopMode(mode player.LoopMode) {
|
||||||
switch mode {
|
switch mode {
|
||||||
case backend.LoopModeAll:
|
case player.LoopAll:
|
||||||
a.loop.Importance = widget.HighImportance
|
a.loop.Importance = widget.HighImportance
|
||||||
a.loop.Icon = myTheme.RepeatIcon
|
a.loop.Icon = myTheme.RepeatIcon
|
||||||
case backend.LoopModeOne:
|
case player.LoopOne:
|
||||||
a.loop.Importance = widget.HighImportance
|
a.loop.Importance = widget.HighImportance
|
||||||
a.loop.Icon = myTheme.RepeatOneIcon
|
a.loop.Icon = myTheme.RepeatOneIcon
|
||||||
case backend.LoopModeNone:
|
case player.LoopNone:
|
||||||
a.loop.Importance = widget.MediumImportance
|
a.loop.Importance = widget.MediumImportance
|
||||||
a.loop.Icon = myTheme.RepeatIcon
|
a.loop.Icon = myTheme.RepeatIcon
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user