show current player in casting menu, show error toasts on failures

This commit is contained in:
Drew Weymouth
2025-03-29 15:15:14 -07:00
parent 90ddfb87b8
commit 084aa6cdba
4 changed files with 62 additions and 19 deletions
+8 -2
View File
@@ -3,6 +3,7 @@ package backend
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"log" "log"
"math/rand" "math/rand"
"time" "time"
@@ -14,6 +15,8 @@ import (
"github.com/dweymouth/supersonic/sharedutil" "github.com/dweymouth/supersonic/sharedutil"
) )
// TODO: make thread-safe
var ( var (
ReplayGainNone = player.ReplayGainNone.String() ReplayGainNone = player.ReplayGainNone.String()
ReplayGainAlbum = player.ReplayGainAlbum.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 needToUnpause := false
stat := p.PlayerStatus() stat := p.PlayerStatus()
@@ -150,7 +153,9 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) {
case player.Stopped: case player.Stopped:
// nothing // nothing
case player.Playing: case player.Playing:
p.Pause() if err := p.Pause(); err != nil {
return fmt.Errorf("failed to pause: %v", err)
}
needToUnpause = true needToUnpause = true
fallthrough fallthrough
case player.Paused: case player.Paused:
@@ -179,6 +184,7 @@ func (p *playbackEngine) SetPlayer(pl player.BasePlayer) {
cb(vol) cb(vol)
} }
} }
return nil
} }
func (p *playbackEngine) PlayTrackAt(idx int) error { func (p *playbackEngine) PlayTrackAt(idx int) error {
+24 -10
View File
@@ -26,17 +26,19 @@ type PlaybackManager struct {
cmdQueue *playbackCommandQueue cmdQueue *playbackCommandQueue
cfg *AppConfig cfg *AppConfig
localPlayer player.BasePlayer localPlayer player.BasePlayer
remotePlayersLock sync.Mutex remotePlayersLock sync.Mutex
remotePlayers []remotePlayer remotePlayers []RemotePlaybackDevice
currentRemotePlayer *RemotePlaybackDevice
autoplay bool autoplay bool
lastPlayTime float64 lastPlayTime float64
} }
type remotePlayer struct { type RemotePlaybackDevice struct {
Name string Name string
URL string
Protocol string Protocol string
new func() (player.BasePlayer, error) 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) { func (p *PlaybackManager) scanRemotePlayers(ctx context.Context, waitSec int) {
devices, _ := device.SearchMediaRenderers(ctx, waitSec, services.AVTransport, services.RenderingControl) devices, _ := device.SearchMediaRenderers(ctx, waitSec, services.AVTransport, services.RenderingControl)
var discovered []remotePlayer var discovered []RemotePlaybackDevice
for _, d := range devices { for _, d := range devices {
p := remotePlayer{ p := RemotePlaybackDevice{
Name: d.FriendlyName, Name: d.FriendlyName,
URL: d.URL,
Protocol: "DLNA", Protocol: "DLNA",
new: func() (player.BasePlayer, error) { new: func() (player.BasePlayer, error) {
return dlna.NewDLNAPlayer(d) return dlna.NewDLNAPlayer(d)
@@ -124,16 +127,23 @@ func (p *PlaybackManager) scanRemotePlayers(ctx context.Context, waitSec int) {
p.remotePlayersLock.Unlock() p.remotePlayersLock.Unlock()
} }
func (p *PlaybackManager) RemotePlayers() []remotePlayer { func (p *PlaybackManager) RemotePlayers() []RemotePlaybackDevice {
p.remotePlayersLock.Lock() p.remotePlayersLock.Lock()
players := p.remotePlayers players := p.remotePlayers
p.remotePlayersLock.Unlock() p.remotePlayersLock.Unlock()
return players 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 { if rp == nil {
p.engine.SetPlayer(p.localPlayer) if err := p.engine.SetPlayer(p.localPlayer); err != nil {
return err
}
p.currentRemotePlayer = nil
return nil return nil
} }
@@ -141,7 +151,11 @@ func (p *PlaybackManager) SetRemotePlayer(rp *remotePlayer) error {
if err != nil { if err != nil {
return err return err
} }
p.engine.SetPlayer(player) if err := p.engine.SetPlayer(player); err != nil {
return err
}
p.currentRemotePlayer = rp
return nil return nil
} }
+8
View File
@@ -99,6 +99,14 @@ func NewDLNAPlayer(device *device.MediaRenderer) (*DLNAPlayer, error) {
return nil, err return nil, err
} }
rc.HTTPClient = cli 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{ return &DLNAPlayer{
avTransport: avt, avTransport: avt,
renderControl: rc, renderControl: rc,
+22 -7
View File
@@ -152,16 +152,31 @@ func (m *Controller) HaveModal() bool {
} }
func (m *Controller) ShowCastMenu() { func (m *Controller) ShowCastMenu() {
rp := m.App.PlaybackManager.CurrentRemotePlayer()
devices := m.App.PlaybackManager.RemotePlayers() devices := m.App.PlaybackManager.RemotePlayers()
menu := fyne.NewMenu("") local := fyne.NewMenuItem(lang.L("Local player"), func() {
menu.Items = append(menu.Items, fyne.NewMenuItem(lang.L("Local player"), func() { go func() {
m.App.PlaybackManager.SetRemotePlayer(nil) 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 { for _, d := range devices {
_d := d _d := d
menu.Items = append(menu.Items, fyne.NewMenuItem(d.Name, func() { item := fyne.NewMenuItem(d.Name, func() {
m.App.PlaybackManager.SetRemotePlayer(&_d) 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()) pop := widget.NewPopUpMenu(menu, m.MainWindow.Canvas())
canvasSize := m.MainWindow.Canvas().Size() canvasSize := m.MainWindow.Canvas().Size()