-
Notifications
You must be signed in to change notification settings - Fork 1
/
player.go
167 lines (136 loc) · 3.96 KB
/
player.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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
package main
import (
"context"
"net"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"golang.org/x/text/encoding/charmap"
)
// player represents a squeezebox device
type player interface {
GetID() string
GetModel() string
GetName() string
Listener()
Heartbeat()
// Display the clock. Outputs framebuffers to the channel
DisplayClock() chan []byte
// Display the text, scrolling if needed. Outputs framebuffers to the channel
DisplayText(text string, ctx context.Context) chan []byte
Render(buf []byte)
Play(videoID string) (cancel func())
Stop()
SetVolume(level int)
GetVolume() int
Pause()
Unpause()
}
const (
AUDIO_PRELOAD = 10 // Seconds of audio to load before playing
VOLUME_INCREMENT = 5
IR_INTERVAL = 200 * time.Millisecond // Prevents duplicate IR commands from ruining our day
HEARTBEAT_INTERVAL = time.Second * 20 // Interval to request heartbeats at
)
// lastIR is global to prevent multiple players picking up the same signal
var lastIR time.Time
var metricConnectedPlayers = promauto.NewGauge(prometheus.GaugeOpts{
Name: "slimytm_connected_players",
Help: "The number of players currently connected",
})
var metricLoadTime = promauto.NewHistogram(prometheus.HistogramOpts{
Name: "slimytm_load_time_seconds",
Help: "How long it takes to go from the command for next song to playing audio",
Buckets: prometheus.LinearBuckets(0, 1.5, 15),
})
var metricPacketsTx = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "slimytm_packets_tx_total",
Help: "The total number of packets sent",
}, []string{"player"})
var metricPacketsRx = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "slimytm_packets_rx_total",
Help: "The total number of packets received",
}, []string{"player"})
func tcpListener() {
listener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 3483})
if err != nil {
logger.Panicw("unable to start tcp listener",
"port", 3483,
"err", err)
}
for {
conn, err := listener.AcceptTCP()
if err != nil {
logger.Errorw("unable to accept tcp connection",
"err", err)
continue
}
logger.Debug("received new tcp connection")
b := make([]byte, 1024)
_, err = conn.Read(b)
if err != nil {
logger.Errorw("unable to read from connection",
"err", err)
continue
}
if string(b[:4]) != "HELO" {
logger.DPanic("didn't receive a HELO")
continue
}
logger.Debug("squeezebox says HELO!")
var c player
queue := &Queue{
Buffer: new(audioBufferWrapper),
}
if b[8] == 2 {
c = &squeezebox1{conn: conn, Queue: queue, mac: net.HardwareAddr(b[10:16])}
} else if b[8] == 4 {
c = &squeezebox2{conn: conn, Queue: queue, mac: net.HardwareAddr(b[10:16])}
} else {
logger.Warnw("non-squeebox device tried to connect. pretending it is a sbox2")
c = &squeezebox2{conn: conn, Queue: queue, mac: net.HardwareAddr(b[10:16])}
// continue
}
logger.Infow("connected to a new squeezebox",
"assignedModel", c.GetModel(),
"firmware", b[9],
"mac", net.HardwareAddr(b[10:16]).String())
queue.Player = c
queues = append(queues, queue)
go c.Listener()
go c.Heartbeat()
go queue.Watch()
metricConnectedPlayers.Inc()
}
}
func udpListener() {
listener, err := net.ListenUDP("udp", &net.UDPAddr{Port: 3483})
if err != nil {
logger.Panicw("unable to start udp listener",
"port", 3483,
"err", err)
}
for {
b := make([]byte, 1024)
_, remote, err := listener.ReadFromUDP(b)
if err != nil {
logger.Errorw("unable to read udp packet",
"err", err)
return
}
if b[0] != 'd' {
logger.Info("received non-discovery request")
continue
}
logger.Debugw("responding to discovery request",
"from", remote.String())
encoder := charmap.ISO8859_10.NewEncoder()
resp, err := encoder.String("SlimYTM")
if err != nil {
logger.Panicw("unable to encode text",
"err", err)
}
resp = "D" + resp
listener.WriteTo(append([]byte(resp), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), remote)
}
}