forked from nstehr/bobcaygeon
/
dacp.go
102 lines (87 loc) · 2.98 KB
/
dacp.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package raop
import (
"context"
"fmt"
"log"
"net/http"
"time"
"github.com/grandcat/zeroconf"
)
// DacpClient used to perform DACP operations
type DacpClient struct {
dacpID string
activeRemote string
ipAddress string
port int
httpClient *http.Client
}
func newDacpClient(ipAddress string, port int, dacpID string, activeRemote string) *DacpClient {
return &DacpClient{ipAddress: ipAddress, port: port, dacpID: dacpID, activeRemote: activeRemote, httpClient: &http.Client{}}
}
func (d *DacpClient) Play() error {
return d.executeMethod("play")
}
func (d *DacpClient) Pause() error {
return d.executeMethod("pause")
}
func (d *DacpClient) PlayPause() error {
return d.executeMethod("playpause")
}
func (d *DacpClient) Stop() error {
return d.executeMethod("stop")
}
func (d *DacpClient) Next() error {
return d.executeMethod("nextitem")
}
func (d *DacpClient) executeMethod(method string) error {
req, err := http.NewRequest("GET", fmt.Sprintf("http://%s:%d/ctrl-int/1/%s", d.ipAddress, d.port, method), nil)
req.Header.Add("Active-Remote", d.activeRemote)
_, err = d.httpClient.Do(req)
return err
}
// DiscoverDacpClient will try to find the matching DACP client for stream operations
func DiscoverDacpClient(dacpID string, activeRemote string) *DacpClient {
serviceType := "_dacp._tcp"
resolver, err := zeroconf.NewResolver(nil)
if err != nil {
log.Fatalln("Failed to initialize resolver:", err.Error())
}
entries := make(chan *zeroconf.ServiceEntry)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(5))
defer cancel()
err = resolver.Browse(ctx, serviceType, "local", entries)
if err != nil {
log.Fatalln("Failed to browse:", err.Error())
}
log.Println("searching for daacp airplay client")
instanceName := fmt.Sprintf("iTunes_Ctrl_%s", dacpID)
var entry *zeroconf.ServiceEntry
foundEntry := make(chan *zeroconf.ServiceEntry)
// what we do is spin of a goroutine that will process the entries registered in
// mDNS for our service. As soon as we detect there is one with an IP4 address
// we send it off and cancel to stop the searching.
// there is an issue, https://github.com/grandcat/zeroconf/issues/27 where we
// could get an entry back without an IP4 addr, it will come in later as an update
// so we wait until we find the addr, or timeout
go func(results <-chan *zeroconf.ServiceEntry, foundEntry chan *zeroconf.ServiceEntry) {
for e := range results {
if (len(e.AddrIPv4)) > 0 && e.Instance == instanceName {
foundEntry <- e
cancel()
}
}
}(entries, foundEntry)
select {
// this should be ok, since we only expect one service of the _bobcaygeon_ type to be found
case entry = <-foundEntry:
log.Println("Found daacp airplay client")
case <-ctx.Done():
log.Println("dacp airplay client not found")
}
if entry == nil {
log.Println("no dacp client found, will not be able to control")
return nil
}
client := newDacpClient(entry.AddrIPv4[0].String(), entry.Port, dacpID, activeRemote)
return client
}