diff --git a/backend/playbackengine.go b/backend/playbackengine.go index 6dbd9e8..2ed3eb8 100644 --- a/backend/playbackengine.go +++ b/backend/playbackengine.go @@ -3,6 +3,7 @@ package backend import ( "context" "errors" + "fmt" "log" "math/rand" "time" @@ -14,6 +15,8 @@ import ( "github.com/dweymouth/supersonic/sharedutil" ) +// TODO: make thread-safe + var ( ReplayGainNone = player.ReplayGainNone.String() ReplayGainAlbum = player.ReplayGainAlbum.String() @@ -138,7 +141,7 @@ func (p *playbackEngine) registerPlayerCallbacks(pl player.BasePlayer) { }) } -func (p *playbackEngine) SetPlayer(pl player.BasePlayer) { +func (p *playbackEngine) SetPlayer(pl player.BasePlayer) error { needToUnpause := false stat := p.PlayerStatus() @@ -150,7 +153,9 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) { case player.Stopped: // nothing case player.Playing: - p.Pause() + if err := p.Pause(); err != nil { + return fmt.Errorf("failed to pause: %v", err) + } needToUnpause = true fallthrough case player.Paused: @@ -179,6 +184,7 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) { cb(vol) } } + return nil } func (p *playbackEngine) PlayTrackAt(idx int) error { diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 4d27e5f..057ea8a 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -26,17 +26,19 @@ type PlaybackManager struct { cmdQueue *playbackCommandQueue cfg *AppConfig - localPlayer player.BasePlayer - remotePlayersLock sync.Mutex - remotePlayers []remotePlayer + localPlayer player.BasePlayer + remotePlayersLock sync.Mutex + remotePlayers []RemotePlaybackDevice + currentRemotePlayer *RemotePlaybackDevice autoplay bool lastPlayTime float64 } -type remotePlayer struct { +type RemotePlaybackDevice struct { Name string + URL string Protocol string new func() (player.BasePlayer, error) } @@ -107,10 +109,11 @@ func (p *PlaybackManager) ScanRemotePlayers(ctx context.Context, fastScan bool) func (p *PlaybackManager) scanRemotePlayers(ctx context.Context, waitSec int) { devices, _ := device.SearchMediaRenderers(ctx, waitSec, services.AVTransport, services.RenderingControl) - var discovered []remotePlayer + var discovered []RemotePlaybackDevice for _, d := range devices { - p := remotePlayer{ + p := RemotePlaybackDevice{ Name: d.FriendlyName, + URL: d.URL, Protocol: "DLNA", new: func() (player.BasePlayer, error) { return dlna.NewDLNAPlayer(d) @@ -124,16 +127,23 @@ func (p *PlaybackManager) scanRemotePlayers(ctx context.Context, waitSec int) { p.remotePlayersLock.Unlock() } -func (p *PlaybackManager) RemotePlayers() []remotePlayer { +func (p *PlaybackManager) RemotePlayers() []RemotePlaybackDevice { p.remotePlayersLock.Lock() players := p.remotePlayers p.remotePlayersLock.Unlock() return players } -func (p *PlaybackManager) SetRemotePlayer(rp *remotePlayer) error { +func (p *PlaybackManager) CurrentRemotePlayer() *RemotePlaybackDevice { + return p.currentRemotePlayer +} + +func (p *PlaybackManager) SetRemotePlayer(rp *RemotePlaybackDevice) error { if rp == nil { - p.engine.SetPlayer(p.localPlayer) + if err := p.engine.SetPlayer(p.localPlayer); err != nil { + return err + } + p.currentRemotePlayer = nil return nil } @@ -141,7 +151,11 @@ func (p *PlaybackManager) SetRemotePlayer(rp *remotePlayer) error { if err != nil { return err } - p.engine.SetPlayer(player) + if err := p.engine.SetPlayer(player); err != nil { + return err + } + + p.currentRemotePlayer = rp return nil } diff --git a/backend/player/dlna/dlnaplayer.go b/backend/player/dlna/dlnaplayer.go index 778cf37..cb28591 100644 --- a/backend/player/dlna/dlnaplayer.go +++ b/backend/player/dlna/dlnaplayer.go @@ -99,6 +99,14 @@ func NewDLNAPlayer(device *device.MediaRenderer) (*DLNAPlayer, error) { return nil, err } rc.HTTPClient = cli + + // ping to test connectivity + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if _, err := avt.GetTransportInfo(ctx); err != nil { + return nil, fmt.Errorf("failed to connect to %s", device.FriendlyName) + } + return &DLNAPlayer{ avTransport: avt, renderControl: rc, diff --git a/ui/controller/controller.go b/ui/controller/controller.go index c9fff4b..5bd0a44 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -152,16 +152,31 @@ func (m *Controller) HaveModal() bool { } func (m *Controller) ShowCastMenu() { + rp := m.App.PlaybackManager.CurrentRemotePlayer() devices := m.App.PlaybackManager.RemotePlayers() - menu := fyne.NewMenu("") - menu.Items = append(menu.Items, fyne.NewMenuItem(lang.L("Local player"), func() { - m.App.PlaybackManager.SetRemotePlayer(nil) - })) + local := fyne.NewMenuItem(lang.L("Local player"), func() { + go func() { + if err := m.App.PlaybackManager.SetRemotePlayer(nil); err != nil { + fyne.Do(func() { m.ToastProvider.ShowErrorToast("Failed to disconnect from remote player") }) + } + }() + }) + local.Icon = theme.ComputerIcon() + local.Checked = rp == nil + + menu := fyne.NewMenu("", local) for _, d := range devices { _d := d - menu.Items = append(menu.Items, fyne.NewMenuItem(d.Name, func() { - m.App.PlaybackManager.SetRemotePlayer(&_d) - })) + item := fyne.NewMenuItem(d.Name, func() { + go func() { + if err := m.App.PlaybackManager.SetRemotePlayer(&_d); err != nil { + fyne.Do(func() { m.ToastProvider.ShowErrorToast("Failed to connect to " + _d.Name) }) + } + }() + }) + item.Icon = myTheme.CastIcon + item.Checked = rp != nil && _d.URL == rp.URL + menu.Items = append(menu.Items, item) } pop := widget.NewPopUpMenu(menu, m.MainWindow.Canvas()) canvasSize := m.MainWindow.Canvas().Size()