very WIP for DLNA casting
This commit is contained in:
+57
-49
@@ -11,16 +11,18 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"runtime"
|
"runtime"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/dweymouth/supersonic/backend/ipc"
|
"github.com/dweymouth/supersonic/backend/ipc"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/backend/player"
|
"github.com/dweymouth/supersonic/backend/player"
|
||||||
|
"github.com/dweymouth/supersonic/backend/player/dlna"
|
||||||
"github.com/dweymouth/supersonic/backend/player/mpv"
|
"github.com/dweymouth/supersonic/backend/player/mpv"
|
||||||
"github.com/dweymouth/supersonic/backend/util"
|
"github.com/dweymouth/supersonic/backend/util"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"github.com/supersonic-app/go-upnpcast/device"
|
||||||
|
"github.com/supersonic-app/go-upnpcast/services"
|
||||||
|
|
||||||
"github.com/20after4/configdir"
|
"github.com/20after4/configdir"
|
||||||
"github.com/zalando/go-keyring"
|
"github.com/zalando/go-keyring"
|
||||||
@@ -43,7 +45,7 @@ type App struct {
|
|||||||
ServerManager *ServerManager
|
ServerManager *ServerManager
|
||||||
ImageManager *ImageManager
|
ImageManager *ImageManager
|
||||||
PlaybackManager *PlaybackManager
|
PlaybackManager *PlaybackManager
|
||||||
LocalPlayer *mpv.Player
|
LocalPlayer player.BasePlayer
|
||||||
UpdateChecker UpdateChecker
|
UpdateChecker UpdateChecker
|
||||||
MPRISHandler *MPRISHandler
|
MPRISHandler *MPRISHandler
|
||||||
WinSMTC *SMTC
|
WinSMTC *SMTC
|
||||||
@@ -273,7 +275,11 @@ func (a *App) initMPV() error {
|
|||||||
if err := p.Init(c.InMemoryCacheSizeMB); err != nil {
|
if err := p.Init(c.InMemoryCacheSizeMB); err != nil {
|
||||||
return fmt.Errorf("failed to initialize mpv player: %s", err.Error())
|
return fmt.Errorf("failed to initialize mpv player: %s", err.Error())
|
||||||
}
|
}
|
||||||
a.LocalPlayer = p
|
// a.LocalPlayer = p
|
||||||
|
devices, _ := device.SearchMediaRenderers(context.Background(), 10, services.AVTransport)
|
||||||
|
if len(devices) > 0 {
|
||||||
|
a.LocalPlayer, _ = dlna.NewDLNAPlayer(devices[0])
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,55 +287,57 @@ func (a *App) setupMPV() error {
|
|||||||
a.Config.LocalPlayback.Volume = clamp(a.Config.LocalPlayback.Volume, 0, 100)
|
a.Config.LocalPlayback.Volume = clamp(a.Config.LocalPlayback.Volume, 0, 100)
|
||||||
a.LocalPlayer.SetVolume(a.Config.LocalPlayback.Volume)
|
a.LocalPlayer.SetVolume(a.Config.LocalPlayback.Volume)
|
||||||
|
|
||||||
devs, err := a.LocalPlayer.ListAudioDevices()
|
/*
|
||||||
if err != nil {
|
devs, err := a.LocalPlayer.ListAudioDevices()
|
||||||
return err
|
if err != nil {
|
||||||
}
|
return err
|
||||||
|
|
||||||
desiredDevice := a.Config.LocalPlayback.AudioDeviceName
|
|
||||||
var desiredDeviceAvailable bool
|
|
||||||
for _, dev := range devs {
|
|
||||||
if dev.Name == desiredDevice {
|
|
||||||
desiredDeviceAvailable = true
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if !desiredDeviceAvailable {
|
|
||||||
// The audio device the user has configured is not available.
|
|
||||||
// Use the default (autoselect) device but leave the setting unchanged,
|
|
||||||
// in case the device is later available on a subsequent run of the app
|
|
||||||
// (e.g. a USB audio device that is currently unplugged)
|
|
||||||
desiredDevice = "auto"
|
|
||||||
}
|
|
||||||
a.LocalPlayer.SetAudioDevice(desiredDevice)
|
|
||||||
|
|
||||||
rgainOpts := []string{ReplayGainNone, ReplayGainAlbum, ReplayGainTrack, ReplayGainAuto}
|
desiredDevice := a.Config.LocalPlayback.AudioDeviceName
|
||||||
if !slices.Contains(rgainOpts, a.Config.ReplayGain.Mode) {
|
var desiredDeviceAvailable bool
|
||||||
a.Config.ReplayGain.Mode = ReplayGainNone
|
for _, dev := range devs {
|
||||||
}
|
if dev.Name == desiredDevice {
|
||||||
mode := player.ReplayGainNone
|
desiredDeviceAvailable = true
|
||||||
switch a.Config.ReplayGain.Mode {
|
break
|
||||||
case ReplayGainAlbum:
|
}
|
||||||
mode = player.ReplayGainAlbum
|
}
|
||||||
case ReplayGainTrack:
|
if !desiredDeviceAvailable {
|
||||||
mode = player.ReplayGainTrack
|
// The audio device the user has configured is not available.
|
||||||
case ReplayGainAuto:
|
// Use the default (autoselect) device but leave the setting unchanged,
|
||||||
mode = player.ReplayGainTrack
|
// in case the device is later available on a subsequent run of the app
|
||||||
}
|
// (e.g. a USB audio device that is currently unplugged)
|
||||||
|
desiredDevice = "auto"
|
||||||
|
}
|
||||||
|
a.LocalPlayer.SetAudioDevice(desiredDevice)
|
||||||
|
|
||||||
a.LocalPlayer.SetReplayGainOptions(player.ReplayGainOptions{
|
rgainOpts := []string{ReplayGainNone, ReplayGainAlbum, ReplayGainTrack, ReplayGainAuto}
|
||||||
Mode: mode,
|
if !slices.Contains(rgainOpts, a.Config.ReplayGain.Mode) {
|
||||||
PreventClipping: a.Config.ReplayGain.PreventClipping,
|
a.Config.ReplayGain.Mode = ReplayGainNone
|
||||||
PreampGain: a.Config.ReplayGain.PreampGainDB,
|
}
|
||||||
})
|
mode := player.ReplayGainNone
|
||||||
a.LocalPlayer.SetAudioExclusive(a.Config.LocalPlayback.AudioExclusive)
|
switch a.Config.ReplayGain.Mode {
|
||||||
|
case ReplayGainAlbum:
|
||||||
|
mode = player.ReplayGainAlbum
|
||||||
|
case ReplayGainTrack:
|
||||||
|
mode = player.ReplayGainTrack
|
||||||
|
case ReplayGainAuto:
|
||||||
|
mode = player.ReplayGainTrack
|
||||||
|
}
|
||||||
|
|
||||||
eq := &mpv.ISO15BandEqualizer{
|
a.LocalPlayer.SetReplayGainOptions(player.ReplayGainOptions{
|
||||||
EQPreamp: a.Config.LocalPlayback.EqualizerPreamp,
|
Mode: mode,
|
||||||
Disabled: !a.Config.LocalPlayback.EqualizerEnabled,
|
PreventClipping: a.Config.ReplayGain.PreventClipping,
|
||||||
}
|
PreampGain: a.Config.ReplayGain.PreampGainDB,
|
||||||
copy(eq.BandGains[:], a.Config.LocalPlayback.GraphicEqualizerBands)
|
})
|
||||||
a.LocalPlayer.SetEqualizer(eq)
|
a.LocalPlayer.SetAudioExclusive(a.Config.LocalPlayback.AudioExclusive)
|
||||||
|
|
||||||
|
eq := &mpv.ISO15BandEqualizer{
|
||||||
|
EQPreamp: a.Config.LocalPlayback.EqualizerPreamp,
|
||||||
|
Disabled: !a.Config.LocalPlayback.EqualizerEnabled,
|
||||||
|
}
|
||||||
|
copy(eq.BandGains[:], a.Config.LocalPlayback.GraphicEqualizerBands)
|
||||||
|
a.LocalPlayer.SetEqualizer(eq)
|
||||||
|
*/
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -450,7 +458,7 @@ func (a *App) Shutdown() {
|
|||||||
a.PlaybackManager.DisableCallbacks()
|
a.PlaybackManager.DisableCallbacks()
|
||||||
a.PlaybackManager.Stop() // will trigger scrobble check
|
a.PlaybackManager.Stop() // will trigger scrobble check
|
||||||
a.cancel()
|
a.cancel()
|
||||||
a.LocalPlayer.Destroy()
|
//a.LocalPlayer.Destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SavePlayQueueIfEnabled() {
|
func (a *App) SavePlayQueueIfEnabled() {
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
package dlna
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/dweymouth/supersonic/backend/player"
|
||||||
|
"github.com/supersonic-app/go-upnpcast/device"
|
||||||
|
"github.com/supersonic-app/go-upnpcast/services/avtransport"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
stopped = 0
|
||||||
|
playing = 1
|
||||||
|
paused = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
var unimplemented = errors.New("unimplemented")
|
||||||
|
|
||||||
|
type DLNAPlayer struct {
|
||||||
|
player.BasePlayerCallbackImpl
|
||||||
|
|
||||||
|
avTransport *avtransport.Client
|
||||||
|
|
||||||
|
state int // stopped, playing, paused
|
||||||
|
seeking bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDLNAPlayer(device *device.MediaRenderer) (*DLNAPlayer, error) {
|
||||||
|
avt, err := device.AVTransportClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &DLNAPlayer{avTransport: avt}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) SetVolume(vol int) error {
|
||||||
|
return unimplemented
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) GetVolume() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) PlayFile(urlstr string) error {
|
||||||
|
ensureSetupProxies()
|
||||||
|
|
||||||
|
proxyURLLock.Lock()
|
||||||
|
dlnaProxyCurrent.url = urlstr
|
||||||
|
proxyURLLock.Unlock()
|
||||||
|
|
||||||
|
media := avtransport.MediaItem{
|
||||||
|
URL: "http://" + localIP + ":8080/current",
|
||||||
|
Title: "Supersonic media item",
|
||||||
|
}
|
||||||
|
log.Printf("URL %s", media.URL)
|
||||||
|
|
||||||
|
err := d.avTransport.SetAVTransportMedia(context.Background(), &media)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := d.avTransport.Play(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.state = playing
|
||||||
|
d.InvokeOnPlaying()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) SetNextFile(url string) error {
|
||||||
|
var media *avtransport.MediaItem
|
||||||
|
if url != "" {
|
||||||
|
ensureSetupProxies()
|
||||||
|
|
||||||
|
proxyURLLock.Lock()
|
||||||
|
dlnaProxyCurrent.url = url
|
||||||
|
proxyURLLock.Unlock()
|
||||||
|
|
||||||
|
media = &avtransport.MediaItem{
|
||||||
|
URL: "http://" + localIP + ":8080/next",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return d.avTransport.SetNextAVTransportMedia(context.Background(), media)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) Continue() error {
|
||||||
|
if err := d.avTransport.Play(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.state = playing
|
||||||
|
d.InvokeOnPlaying()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) Pause() error {
|
||||||
|
if err := d.avTransport.Pause(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.state = paused
|
||||||
|
d.InvokeOnPaused()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) Stop() error {
|
||||||
|
if err := d.avTransport.Pause(context.Background()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.state = stopped
|
||||||
|
d.InvokeOnStopped()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) SeekSeconds(secs float64) error {
|
||||||
|
d.seeking = true
|
||||||
|
if err := d.avTransport.Seek(context.Background(), int(secs)); err != nil {
|
||||||
|
d.seeking = false
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.seeking = false
|
||||||
|
d.InvokeOnSeek()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) IsSeeking() bool {
|
||||||
|
return d.seeking
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DLNAPlayer) GetStatus() player.Status {
|
||||||
|
state := player.Stopped
|
||||||
|
if d.state == playing {
|
||||||
|
state = player.Playing
|
||||||
|
} else if d.state == paused {
|
||||||
|
state = player.Paused
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO - the rest
|
||||||
|
|
||||||
|
return player.Status{
|
||||||
|
State: state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLocalIP() (string, error) {
|
||||||
|
interfaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, iface := range interfaces {
|
||||||
|
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
addrs, err := iface.Addrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, addr := range addrs {
|
||||||
|
ipnet, ok := addr.(*net.IPNet)
|
||||||
|
if ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
|
||||||
|
return ipnet.IP.String(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", fmt.Errorf("no suitable interface found")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
localIP string
|
||||||
|
proxyURLLock sync.Mutex
|
||||||
|
dlnaProxyCurrent proxy
|
||||||
|
dlnaProxyNext proxy
|
||||||
|
proxyActive atomic.Bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func ensureSetupProxies() {
|
||||||
|
if proxyActive.Swap(true) {
|
||||||
|
return // already active
|
||||||
|
}
|
||||||
|
|
||||||
|
localIP, _ = getLocalIP()
|
||||||
|
log.Println(localIP)
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/current", dlnaProxyCurrent.handleRequest)
|
||||||
|
mux.HandleFunc("/next", dlnaProxyNext.handleRequest)
|
||||||
|
go http.ListenAndServe(":8080", mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
type proxy struct {
|
||||||
|
url string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *proxy) handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Create a new request to the target server
|
||||||
|
proxyURLLock.Lock()
|
||||||
|
url := p.url
|
||||||
|
proxyURLLock.Unlock()
|
||||||
|
log.Println("Got request for " + url)
|
||||||
|
proxyReq, err := http.NewRequest(r.Method, url, r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy headers from the original request to the new request
|
||||||
|
proxyReq.Header = r.Header
|
||||||
|
|
||||||
|
// Create an HTTP client and send the request
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(proxyReq)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Copy headers from the response to the writer
|
||||||
|
for name, values := range resp.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
w.Header().Add(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the status code
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
|
||||||
|
// Copy the response body to the writer
|
||||||
|
_, err = io.Copy(w, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, "Error copying response body:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ require (
|
|||||||
github.com/quarckster/go-mpris-server v1.0.3
|
github.com/quarckster/go-mpris-server v1.0.3
|
||||||
github.com/supersonic-app/go-mpv v0.1.0
|
github.com/supersonic-app/go-mpv v0.1.0
|
||||||
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d
|
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d
|
||||||
|
github.com/supersonic-app/go-upnpcast v0.0.0-20250312000014-e4f7242a07ce
|
||||||
github.com/zalando/go-keyring v0.2.6
|
github.com/zalando/go-keyring v0.2.6
|
||||||
golang.org/x/net v0.25.0
|
golang.org/x/net v0.25.0
|
||||||
golang.org/x/sys v0.30.0
|
golang.org/x/sys v0.30.0
|
||||||
@@ -39,8 +40,10 @@ require (
|
|||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20240506104042-037f3cc74f2a // indirect
|
||||||
github.com/go-text/render v0.2.0 // indirect
|
github.com/go-text/render v0.2.0 // indirect
|
||||||
github.com/go-text/typesetting v0.2.1 // indirect
|
github.com/go-text/typesetting v0.2.1 // indirect
|
||||||
|
github.com/h2non/filetype v1.1.3 // indirect
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 // indirect
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||||
|
github.com/koron/go-ssdp v0.0.4 // indirect
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
@@ -53,4 +56,5 @@ require (
|
|||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/supersonic-app/go-upnpcast v0.0.0-20250312000014-e4f7242a07ce => ../go-upnpcast
|
||||||
replace fyne.io/fyne/v2 v2.6.0-beta1 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250308154116-c32a0de49bde
|
replace fyne.io/fyne/v2 v2.6.0-beta1 => github.com/dweymouth/fyne/v2 v2.3.0-rc1.0.20250308154116-c32a0de49bde
|
||||||
|
|||||||
@@ -57,10 +57,14 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
|
|||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
|
||||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||||
|
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
|
||||||
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||||
|
github.com/koron/go-ssdp v0.0.4 h1:1IDwrghSKYM7yLf7XCzbByg2sJ/JcNOZRXS2jczTwz0=
|
||||||
|
github.com/koron/go-ssdp v0.0.4/go.mod h1:oDXq+E5IL5q0U8uSBcoAXzTzInwy5lEgC91HoKtbmZk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
@@ -97,6 +101,8 @@ github.com/supersonic-app/go-mpv v0.1.0 h1:U+cCnLQxmpqx5mY6nMlC0J4uIdCCXUbAjpjS0
|
|||||||
github.com/supersonic-app/go-mpv v0.1.0/go.mod h1:1bQz6kBQumJopXEbkiqoLxIXLy7F7yWFBvknvpAtIC0=
|
github.com/supersonic-app/go-mpv v0.1.0/go.mod h1:1bQz6kBQumJopXEbkiqoLxIXLy7F7yWFBvknvpAtIC0=
|
||||||
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d h1:70+Nn7yh+cfeKqqXVTdpneFqXuvrBLyP7U6GVUsjTU4=
|
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d h1:70+Nn7yh+cfeKqqXVTdpneFqXuvrBLyP7U6GVUsjTU4=
|
||||||
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d/go.mod h1:D+OWPXeD9owcdcoXATv5YPBGWxxVvn5k98rt5B4wMc4=
|
github.com/supersonic-app/go-subsonic v0.0.0-20241224013245-9b2841f3711d/go.mod h1:D+OWPXeD9owcdcoXATv5YPBGWxxVvn5k98rt5B4wMc4=
|
||||||
|
github.com/supersonic-app/go-upnpcast v0.0.0-20250312000014-e4f7242a07ce h1:6R8/JRwRVrR9t2YPTgikS39lRf3gPp/MPcbx+lIf3rA=
|
||||||
|
github.com/supersonic-app/go-upnpcast v0.0.0-20250312000014-e4f7242a07ce/go.mod h1:Wscg4vEzF9x6i4ltL2Qkvx4VhzXfkBBl8uoG0YSAJMU=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||||
|
|||||||
+49
-48
@@ -16,8 +16,6 @@ import (
|
|||||||
fynetooltip "github.com/dweymouth/fyne-tooltip"
|
fynetooltip "github.com/dweymouth/fyne-tooltip"
|
||||||
"github.com/dweymouth/supersonic/backend"
|
"github.com/dweymouth/supersonic/backend"
|
||||||
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
"github.com/dweymouth/supersonic/backend/mediaprovider"
|
||||||
"github.com/dweymouth/supersonic/backend/player"
|
|
||||||
"github.com/dweymouth/supersonic/backend/player/mpv"
|
|
||||||
"github.com/dweymouth/supersonic/sharedutil"
|
"github.com/dweymouth/supersonic/sharedutil"
|
||||||
"github.com/dweymouth/supersonic/ui/dialogs"
|
"github.com/dweymouth/supersonic/ui/dialogs"
|
||||||
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
myTheme "github.com/dweymouth/supersonic/ui/theme"
|
||||||
@@ -270,53 +268,56 @@ func (c *Controller) ShowAboutDialog() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map[string]string) {
|
func (c *Controller) ShowSettingsDialog(themeUpdateCallbk func(), themeFiles map[string]string) {
|
||||||
devs, err := c.App.LocalPlayer.ListAudioDevices()
|
/*
|
||||||
if err != nil {
|
devs, err := c.App.LocalPlayer.ListAudioDevices()
|
||||||
log.Printf("error listing audio devices: %v", err)
|
if err != nil {
|
||||||
devs = []mpv.AudioDevice{{Name: "auto", Description: lang.L("Autoselect device")}}
|
log.Printf("error listing audio devices: %v", err)
|
||||||
}
|
devs = []mpv.AudioDevice{{Name: "auto", Description: lang.L("Autoselect device")}}
|
||||||
|
}
|
||||||
|
|
||||||
curPlayer := c.App.PlaybackManager.CurrentPlayer()
|
curPlayer := c.App.PlaybackManager.CurrentPlayer()
|
||||||
_, isReplayGainPlayer := curPlayer.(player.ReplayGainPlayer)
|
_, isReplayGainPlayer := curPlayer.(player.ReplayGainPlayer)
|
||||||
_, isEqualizerPlayer := curPlayer.(*mpv.Player)
|
_, isEqualizerPlayer := curPlayer.(*mpv.Player)
|
||||||
_, canSavePlayQueue := c.App.ServerManager.Server.(mediaprovider.CanSavePlayQueue)
|
_, canSavePlayQueue := c.App.ServerManager.Server.(mediaprovider.CanSavePlayQueue)
|
||||||
isLocalPlayer := isEqualizerPlayer
|
isLocalPlayer := isEqualizerPlayer
|
||||||
bands := c.App.LocalPlayer.Equalizer().BandFrequencies()
|
bands := c.App.LocalPlayer.Equalizer().BandFrequencies()
|
||||||
dlg := dialogs.NewSettingsDialog(c.App.Config,
|
|
||||||
devs, themeFiles, bands,
|
dlg := dialogs.NewSettingsDialog(c.App.Config,
|
||||||
c.App.ServerManager.Server.ClientDecidesScrobble(),
|
devs, themeFiles, bands,
|
||||||
isLocalPlayer, isReplayGainPlayer, isEqualizerPlayer, canSavePlayQueue,
|
c.App.ServerManager.Server.ClientDecidesScrobble(),
|
||||||
c.MainWindow)
|
isLocalPlayer, isReplayGainPlayer, isEqualizerPlayer, canSavePlayQueue,
|
||||||
dlg.OnReplayGainSettingsChanged = func() {
|
c.MainWindow)
|
||||||
c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain)
|
dlg.OnReplayGainSettingsChanged = func() {
|
||||||
}
|
c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain)
|
||||||
dlg.OnAudioExclusiveSettingChanged = func() {
|
}
|
||||||
c.App.LocalPlayer.SetAudioExclusive(c.App.Config.LocalPlayback.AudioExclusive)
|
dlg.OnAudioExclusiveSettingChanged = func() {
|
||||||
}
|
c.App.LocalPlayer.SetAudioExclusive(c.App.Config.LocalPlayback.AudioExclusive)
|
||||||
dlg.OnAudioDeviceSettingChanged = func() {
|
}
|
||||||
c.App.LocalPlayer.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName)
|
dlg.OnAudioDeviceSettingChanged = func() {
|
||||||
}
|
c.App.LocalPlayer.SetAudioDevice(c.App.Config.LocalPlayback.AudioDeviceName)
|
||||||
dlg.OnThemeSettingChanged = themeUpdateCallbk
|
}
|
||||||
dlg.OnEqualizerSettingsChanged = func() {
|
dlg.OnThemeSettingChanged = themeUpdateCallbk
|
||||||
// currently we only have one equalizer type
|
dlg.OnEqualizerSettingsChanged = func() {
|
||||||
eq := c.App.LocalPlayer.Equalizer().(*mpv.ISO15BandEqualizer)
|
// currently we only have one equalizer type
|
||||||
eq.Disabled = !c.App.Config.LocalPlayback.EqualizerEnabled
|
eq := c.App.LocalPlayer.Equalizer().(*mpv.ISO15BandEqualizer)
|
||||||
eq.EQPreamp = c.App.Config.LocalPlayback.EqualizerPreamp
|
eq.Disabled = !c.App.Config.LocalPlayback.EqualizerEnabled
|
||||||
copy(eq.BandGains[:], c.App.Config.LocalPlayback.GraphicEqualizerBands)
|
eq.EQPreamp = c.App.Config.LocalPlayback.EqualizerPreamp
|
||||||
c.App.LocalPlayer.SetEqualizer(eq)
|
copy(eq.BandGains[:], c.App.Config.LocalPlayback.GraphicEqualizerBands)
|
||||||
}
|
c.App.LocalPlayer.SetEqualizer(eq)
|
||||||
dlg.OnPageNeedsRefresh = c.RefreshPageFunc
|
}
|
||||||
pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas())
|
dlg.OnPageNeedsRefresh = c.RefreshPageFunc
|
||||||
fynetooltip.AddPopUpToolTipLayer(pop)
|
pop := widget.NewModalPopUp(dlg, c.MainWindow.Canvas())
|
||||||
dlg.OnDismiss = func() {
|
fynetooltip.AddPopUpToolTipLayer(pop)
|
||||||
pop.Hide()
|
dlg.OnDismiss = func() {
|
||||||
fynetooltip.DestroyPopUpToolTipLayer(pop)
|
pop.Hide()
|
||||||
c.doModalClosed()
|
fynetooltip.DestroyPopUpToolTipLayer(pop)
|
||||||
c.App.SaveConfigFile()
|
c.doModalClosed()
|
||||||
}
|
c.App.SaveConfigFile()
|
||||||
c.ClosePopUpOnEscape(pop)
|
}
|
||||||
c.haveModal = true
|
c.ClosePopUpOnEscape(pop)
|
||||||
pop.Show()
|
c.haveModal = true
|
||||||
|
pop.Show()
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) doModalClosed() {
|
func (c *Controller) doModalClosed() {
|
||||||
|
|||||||
@@ -71,13 +71,13 @@ func (c *Controller) stopVisualizationAnim() {
|
|||||||
if c.visualizationAnim != nil {
|
if c.visualizationAnim != nil {
|
||||||
c.visualizationAnim.Stop()
|
c.visualizationAnim.Stop()
|
||||||
c.visualizationAnim = nil
|
c.visualizationAnim = nil
|
||||||
c.App.LocalPlayer.SetPeaksEnabled(false)
|
// c.App.LocalPlayer.SetPeaksEnabled(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) startVisualizationAnim() {
|
func (c *Controller) startVisualizationAnim() {
|
||||||
if c.visualizationAnim == nil {
|
if c.visualizationAnim == nil {
|
||||||
c.App.LocalPlayer.SetPeaksEnabled(true)
|
//c.App.LocalPlayer.SetPeaksEnabled(true)
|
||||||
c.visualizationAnim = fyne.NewAnimation(
|
c.visualizationAnim = fyne.NewAnimation(
|
||||||
time.Duration(math.MaxInt64), /*until stopped*/
|
time.Duration(math.MaxInt64), /*until stopped*/
|
||||||
c.tickVisualizations)
|
c.tickVisualizations)
|
||||||
@@ -86,8 +86,8 @@ func (c *Controller) startVisualizationAnim() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) tickVisualizations(_ float32) {
|
func (c *Controller) tickVisualizations(_ float32) {
|
||||||
lP, rP, lRMS, rRMS := c.App.LocalPlayer.GetPeaks()
|
//lP, rP, lRMS, rRMS := c.App.LocalPlayer.GetPeaks()
|
||||||
if c.visualizationData.peakMeter != nil {
|
if c.visualizationData.peakMeter != nil {
|
||||||
c.visualizationData.peakMeter.UpdatePeaks(lP, rP, lRMS, rRMS)
|
//c.visualizationData.peakMeter.UpdatePeaks(lP, rP, lRMS, rRMS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user