very WIP for DLNA casting

This commit is contained in:
Drew Weymouth
2025-03-29 15:15:13 -07:00
parent 31c23490d1
commit 4f731833c0
6 changed files with 363 additions and 101 deletions
+243
View File
@@ -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)
}
}