-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathhomecast.go
113 lines (95 loc) · 2.87 KB
/
homecast.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
103
104
105
106
107
108
109
110
111
112
113
package homecast
import (
"context"
"fmt"
"log"
"net/url"
"strings"
"github.com/barnybug/go-cast"
"github.com/barnybug/go-cast/controllers"
castnet "github.com/barnybug/go-cast/net"
"github.com/micro/mdns"
)
const (
googleCastServiceName = "_googlecast._tcp"
googleHomeModelInfo = "md=Google Home"
)
// CastDevice is cast-able device contains cast client
type CastDevice struct {
*mdns.ServiceEntry
client *cast.Client
}
// Connect connects required services to cast
func (g *CastDevice) Connect(ctx context.Context) error {
return g.client.Connect(ctx)
}
// Close calls client's close func
func (g *CastDevice) Close() {
g.client.Close()
}
// Speak speaks given text on cast device
func (g *CastDevice) Speak(ctx context.Context, text, lang string) error {
url, err := tts(text, lang)
if err != nil {
return err
}
return g.Play(ctx, url)
}
// Play plays media contents on cast device
func (g *CastDevice) Play(ctx context.Context, url *url.URL) error {
conn := castnet.NewConnection()
if err := conn.Connect(ctx, g.AddrV4, g.Port); err != nil {
return err
}
defer conn.Close()
status, err := g.client.Receiver().LaunchApp(ctx, cast.AppMedia)
if err != nil {
return err
}
app := status.GetSessionByAppId(cast.AppMedia)
cc := controllers.NewConnectionController(conn, g.client.Events, cast.DefaultSender, *app.TransportId)
if err := cc.Start(ctx); err != nil {
return err
}
media := controllers.NewMediaController(conn, g.client.Events, cast.DefaultSender, *app.TransportId)
if err := media.Start(ctx); err != nil {
return err
}
mediaItem := controllers.MediaItem{
ContentId: url.String(),
ContentType: "audio/mp3",
StreamType: "BUFFERED",
}
log.Printf("[INFO] Load media: content_id=%s", mediaItem.ContentId)
_, err = media.LoadMedia(ctx, mediaItem, 0, true, nil)
return err
}
// LookupAndConnect retrieves cast-able google home devices
func LookupAndConnect(ctx context.Context) []*CastDevice {
entriesCh := make(chan *mdns.ServiceEntry, 4)
results := make([]*CastDevice, 0, 4)
go func() {
for entry := range entriesCh {
log.Printf("[INFO] ServiceEntry detected: [%s:%d]%s", entry.AddrV4, entry.Port, entry.Name)
for _, field := range entry.InfoFields {
if strings.HasPrefix(field, googleHomeModelInfo) {
client := cast.NewClient(entry.AddrV4, entry.Port)
if err := client.Connect(ctx); err != nil {
log.Printf("[ERROR] Failed to connect: %s", err)
continue
}
results = append(results, &CastDevice{entry, client})
}
}
}
}()
mdns.Lookup(googleCastServiceName, entriesCh)
close(entriesCh)
return results
}
// tts provides text-to-speech sound url.
// NOTE: it seems to be unofficial.
func tts(text, lang string) (*url.URL, error) {
base := "https://translate.google.com/translate_tts?client=tw-ob&ie=UTF-8&q=%s&tl=%s"
return url.Parse(fmt.Sprintf(base, url.QueryEscape(text), url.QueryEscape(lang)))
}