self lookup mdns and audioClip
This commit is contained in:
1 parent
f619107517
commit
bec6edbb8c
137 files changed
+26610
-44
No files matched your search
+15
-13
@@ -11,31 +11,33 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Player interface {
|
||||
Play(uri *url.URL) error
|
||||
type PlayHandler interface {
|
||||
Play(*url.URL) error
|
||||
Watch(context.Context) error
|
||||
}
|
||||
|
||||
type RingHandler struct {
|
||||
lg *zap.Logger
|
||||
cfg *common.ConfigRing
|
||||
players []Player
|
||||
lg *zap.Logger
|
||||
cfg *common.ConfigRing
|
||||
playHandlers []PlayHandler
|
||||
}
|
||||
|
||||
func NewRingHandler(lg *zap.Logger, cfg *common.ConfigRing) (*RingHandler, error) {
|
||||
spl, err := sonos.NewSonosPlayers(lg.With(zap.String("context", "sonos")), cfg.SonosTarget)
|
||||
spl, err := sonos.NewSonosHandler(lg.With(zap.String("context", "sonos")), cfg.SonosTarget)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pl := make([]Player, 0, len(spl))
|
||||
for _, sp := range spl {
|
||||
pl = append(pl, sp)
|
||||
}
|
||||
|
||||
return &RingHandler{lg, cfg, pl}, nil
|
||||
return &RingHandler{lg, cfg, []PlayHandler{spl}}, nil
|
||||
}
|
||||
|
||||
func (h *RingHandler) Watch(ctx context.Context) error {
|
||||
for _, p := range h.playHandlers {
|
||||
if err := p.Watch(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
d, err := evdev.Open(h.cfg.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -61,7 +63,7 @@ func (h *RingHandler) Watch(ctx context.Context) error {
|
||||
e, err := d.ReadOne()
|
||||
if err == nil && e.Code == key && e.Value == 0 {
|
||||
tu := bu.JoinPath(h.cfg.JingleName)
|
||||
for _, p := range h.players {
|
||||
for _, p := range h.playHandlers {
|
||||
if err := p.Play(tu); err != nil {
|
||||
h.lg.Error("failed to play", zap.Error(err))
|
||||
}
|
||||
|
||||
+158
-30
@@ -1,56 +1,184 @@
|
||||
package sonos
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
so "github.com/szatmary/sonos"
|
||||
"github.com/google/uuid"
|
||||
"github.com/hashicorp/mdns"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type sonosInfo struct {
|
||||
Device struct {
|
||||
Id string `json:"id"`
|
||||
SerialNumber string `json:"serialNumber"`
|
||||
Model string `json:"model"`
|
||||
ModelDisplayName string `json:"modelDisplayName"`
|
||||
Color string `json:"color"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
ApiVersion string `json:"apiVersion"`
|
||||
MinApiVersion string `json:"minApiVersion"`
|
||||
Name string `json:"name"`
|
||||
WebsocketUrl string `json:"websocketUrl"`
|
||||
SoftwareVersion string `json:"softwareVersion"`
|
||||
HwVersion string `json:"hwVersion"`
|
||||
SwGen int `json:"swGen"`
|
||||
} `json:"device"`
|
||||
HouseholdId string `json:"householdId"`
|
||||
PlayerId string `json:"playerId"`
|
||||
GroupId string `json:"groupId"`
|
||||
WebsocketUrl string `json:"websocketUrl"`
|
||||
RestUrl string `json:"restUrl"`
|
||||
}
|
||||
|
||||
type sonosAudioClip struct {
|
||||
Name string `json:"name"`
|
||||
AppId string `json:"appId"`
|
||||
StreamUrl string `json:"streamUrl"`
|
||||
Volume int `json:"volume"`
|
||||
}
|
||||
|
||||
type SonosHandler struct {
|
||||
lg *zap.Logger
|
||||
target string
|
||||
players map[string]*SonosPlayer
|
||||
}
|
||||
|
||||
type SonosPlayer struct {
|
||||
lg *zap.Logger
|
||||
zp *so.ZonePlayer
|
||||
client *http.Client
|
||||
address *url.URL
|
||||
info sonosInfo
|
||||
}
|
||||
|
||||
func NewSonosPlayers(lg *zap.Logger, target string) ([]*SonosPlayer, error) {
|
||||
spl, err := SearchTargets(lg, target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewSonosPlayer(address *url.URL) (*SonosPlayer, error) {
|
||||
client := &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}}
|
||||
|
||||
return spl, nil
|
||||
return &SonosPlayer{client, address, sonosInfo{}}, nil
|
||||
}
|
||||
|
||||
func (p *SonosPlayer) Play(uri *url.URL) error {
|
||||
err := p.zp.SetAVTransportURI(uri.String())
|
||||
func NewSonosHandler(lg *zap.Logger, target string) (*SonosHandler, error) {
|
||||
return &SonosHandler{lg, target, make(map[string]*SonosPlayer)}, nil
|
||||
}
|
||||
|
||||
func (p *SonosPlayer) init() error {
|
||||
req, err := http.NewRequest(http.MethodGet, p.address.JoinPath("api/v1/players/local/info").String(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return p.zp.Play()
|
||||
// for some reason there must be a api key given
|
||||
// does not matter what
|
||||
req.Header.Add("X-Sonos-Api-Key", uuid.NewString())
|
||||
|
||||
res, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("received status %d", res.StatusCode)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(b, &p.info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func SearchTargets(lg *zap.Logger, target string) ([]*SonosPlayer, error) {
|
||||
players := make([]*SonosPlayer, 0)
|
||||
|
||||
son, err := so.NewSonos()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func (p *SonosPlayer) Play(uri *url.URL) error {
|
||||
sab := sonosAudioClip{
|
||||
Name: "Pull Bell",
|
||||
AppId: "com.acme.app",
|
||||
StreamUrl: uri.String(),
|
||||
Volume: 5,
|
||||
}
|
||||
defer son.Close()
|
||||
|
||||
found, _ := son.Search()
|
||||
to := time.After(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case <-to:
|
||||
return players, nil
|
||||
case zp := <-found:
|
||||
lg.Info("found player", zap.String("name", zp.RoomName()))
|
||||
if target == "-" || zp.RoomName() == target {
|
||||
players = append(players, &SonosPlayer{lg, zp})
|
||||
b, err := json.Marshal(sab)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, p.address.JoinPath(fmt.Sprintf("api/v1/players/%s/audioClip", p.info.PlayerId)).String(), bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// for some reason there must be a api key given
|
||||
// does not matter what
|
||||
req.Header.Add("X-Sonos-Api-Key", uuid.NewString())
|
||||
req.Header.Set("Content-Type", "application/json; charset=UTF-8")
|
||||
|
||||
res, err := p.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("received status %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *SonosHandler) Watch(ctx context.Context) error {
|
||||
// Make a channel for results and start listening
|
||||
entriesCh := make(chan *mdns.ServiceEntry, 4)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case e := <-entriesCh:
|
||||
h.lg.Info("found player", zap.String("name", e.Name), zap.String("address", e.AddrV4.String()))
|
||||
if _, has := h.players[e.Name]; !has {
|
||||
p, err := NewSonosPlayer(&url.URL{
|
||||
Scheme: "https",
|
||||
Host: net.JoinHostPort(e.AddrV4.String(), "1443"),
|
||||
})
|
||||
if err != nil {
|
||||
h.lg.Error("failed to create player", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
if err := p.init(); err != nil {
|
||||
h.lg.Error("failed to init player", zap.Error(err))
|
||||
continue
|
||||
}
|
||||
|
||||
h.players[e.Name] = p
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Start the lookup
|
||||
return mdns.Lookup("_sonos._tcp", entriesCh)
|
||||
}
|
||||
|
||||
func (h *SonosHandler) Play(uri *url.URL) error {
|
||||
for _, p := range h.players {
|
||||
if err := p.Play(uri); err != nil {
|
||||
h.lg.Error("failed to play", zap.String("address", p.address.String()), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user