Files
go2tv/devices/devices.go
2025-02-24 21:44:43 +02:00

159 lines
3.1 KiB
Go

package devices
import (
"context"
"fmt"
"net"
"sort"
"github.com/alexballas/go-ssdp"
"github.com/alexballas/go2tv/soapcalls"
"github.com/pkg/errors"
)
var (
ErrNoDeviceAvailable = errors.New("loadSSDPservices: No available Media Renderers")
ErrDeviceNotAvailable = errors.New("devicePicker: Requested device not available")
ErrSomethingWentWrong = errors.New("devicePicker: Something went terribly wrong")
)
// LoadSSDPservices returns a map with all the devices that support the
// AVTransport service.
func LoadSSDPservices(delay int) (map[string]string, error) {
// Reset device list every time we call this.
urlList := make(map[string]string)
port := 1900
var (
address *net.UDPAddr
tries int
)
for address == nil && tries < 100 {
addr := net.UDPAddr{
Port: port,
IP: net.ParseIP("0.0.0.0"),
}
if err := checkInterfacesForPort(port); err != nil {
port++
tries++
continue
}
address = &addr
}
var addrString string
if address != nil {
addrString = address.String()
}
list, err := ssdp.Search(ssdp.All, delay, addrString)
if err != nil {
return nil, fmt.Errorf("LoadSSDPservices search error: %w", err)
}
for _, srv := range list {
// We only care about the AVTransport services for basic actions
// (stop,play,pause). If we need support other functionalities
// like volume control we need to use the RenderingControl service.
if srv.Type == "urn:schemas-upnp-org:service:AVTransport:1" {
friendlyName, err := soapcalls.GetFriendlyName(context.Background(), srv.Location)
if err != nil {
continue
}
urlList[srv.Location] = friendlyName
}
}
deviceList := make(map[string]string)
dupNames := make(map[string]int)
for loc, fn := range urlList {
_, exists := dupNames[fn]
dupNames[fn]++
if exists {
fn = fn + " (" + loc + ")"
}
deviceList[fn] = loc
}
for fn, c := range dupNames {
if c > 1 {
loc := deviceList[fn]
delete(deviceList, fn)
fn = fn + " (" + loc + ")"
deviceList[fn] = loc
}
}
if len(deviceList) > 0 {
return deviceList, nil
}
return nil, ErrNoDeviceAvailable
}
// DevicePicker will pick the nth device from the devices input map.
func DevicePicker(devices map[string]string, n int) (string, error) {
if n > len(devices) || len(devices) == 0 || n <= 0 {
return "", ErrDeviceNotAvailable
}
var keys []string
for k := range devices {
keys = append(keys, k)
}
sort.Strings(keys)
for q, k := range keys {
if n == q+1 {
return devices[k], nil
}
}
return "", ErrSomethingWentWrong
}
func checkInterfacesForPort(port int) error {
interfaces, err := net.Interfaces()
if err != nil {
return err
}
for _, iface := range interfaces {
addrs, err := iface.Addrs()
if err != nil {
return err
}
for _, addr := range addrs {
ip, _, _ := net.ParseCIDR(addr.String())
if ip.IsLoopback() || ip.To4() == nil {
continue
}
udpAddr := net.UDPAddr{
Port: port,
IP: ip,
}
udpConn, err := net.ListenUDP("udp4", &udpAddr)
if err != nil {
return err
}
udpConn.Close()
return nil
}
}
return nil
}