-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathserver.go
177 lines (168 loc) · 4.34 KB
/
server.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
168
169
170
171
172
173
174
175
176
177
// Package onair provides a server and client for managing music playback,
// metadata storage and display. It currently supports shairport-sync as a
// playback source.
package onair
import (
"bufio"
"log"
"net"
"net/textproto"
"os"
"os/signal"
"strings"
"syscall"
)
// Track is the common model for an album track.
type Track struct {
Artist string
Album string
Name string
Composer string
Genre string
ID uint64
Time uint32
}
// Server manages track flow and control commands.
type Server struct {
port int
tracks chan Track
source TrackSource
sink TrackSink
control PlaybackControl
}
// TrackSource provides an interface playback sources need to implement. As
// tracks are played, they should be pushed into the provided channel. When
// playbacks stops, they should push a blank Track.
type TrackSource interface {
// RegisterTrackOutChan supplies the source a Track output channel
RegisterTrackOutChan(chan<- Track)
}
// TrackSink provides an interface track sinks need to implement. Sinks can
// pull tracks from the provided channel and store or render them as
// appropriate. When playback stops, sinks will receive a blank track.
type TrackSink interface {
// RegisterTrackInChan supplies the sink a Track input channel
RegisterTrackInChan(<-chan Track)
}
// PlaybackControl provides an interface for source specific playback
// controllers to implement.
type PlaybackControl interface {
// Displays the currently playing track
Display() string
// Play starts playback.
Play()
// Pause pauses playback.
Pause()
// Next plays the next next item in the playlist.
Next()
// Previous plays the previous item in the playlist.
Previous()
// Stop playback.
Stop()
// FastForward begins fast forward, PlayResume() should be called to return to playback.
FastForward()
// Rewind begins rewinding, PlayResume() should be called to return to playback.
Rewind()
// PlayResume is called after a FastForward() or Rewind() call to resume playback.
PlayResume()
// TogglePause toggles pause state.
TogglePause()
// ToggleMute toggles mute state.
ToggleMute()
// Shuffle the tracks in a playlist.
Shuffle()
// VolumeUp increases the volume.
VolumeUp()
// VolumeDown decreases the volume.
VolumeDown()
}
// NewServer returns a configured Server.
func NewServer(port int, source TrackSource, sink TrackSink, control PlaybackControl) Server {
s := Server{
port: port,
source: source,
sink: sink,
control: control,
tracks: make(chan Track, 0),
}
source.RegisterTrackOutChan(s.tracks)
sink.RegisterTrackInChan(s.tracks)
return s
}
// Listen for control commands. This method blocks until a termination signal
// is received.
func (me *Server) Listen() {
address := net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: me.port}
listener, err := net.ListenTCP("tcp", &address)
defer listener.Close()
if err != nil {
if strings.Index(err.Error(), "in use") == -1 {
panic(err)
}
log.Printf("Already listening")
return
}
log.Printf("Listening on port %d", me.port)
go func() {
for {
conn, err := listener.AcceptTCP()
if err != nil {
if strings.Index(err.Error(), "closed network connection") == -1 {
log.Printf("Error accept: %s", err.Error())
}
return
}
log.Printf("Connected: %v", conn)
go me.handleConnection(conn)
}
}()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
<-sigs
}
func (me *Server) handleConnection(conn *net.TCPConn) {
r := bufio.NewReader(conn)
tr := textproto.NewReader(r)
w := bufio.NewWriter(conn)
tw := textproto.NewWriter(w)
defer conn.Close()
for {
cmd, err := tr.ReadLine()
if err != nil {
break
}
switch cmd {
case "display":
np := me.control.Display()
tw.PrintfLine("%s", np)
case "play":
me.control.Play()
case "pause":
me.control.Pause()
case "nextitem":
me.control.Next()
case "previtem":
me.control.Previous()
case "stop":
me.control.Stop()
case "beginff":
me.control.FastForward()
case "beginrew":
me.control.Rewind()
case "playresume":
me.control.PlayResume()
case "playpause":
me.control.TogglePause()
case "mutetoggle":
me.control.ToggleMute()
case "shuffle_songs":
me.control.Shuffle()
case "volumedown":
me.control.VolumeUp()
case "volumeup":
me.control.VolumeDown()
default:
log.Printf("Bad command: '%s'", cmd)
}
}
}