summaryrefslogtreecommitdiffstats
path: root/AirPlayService.py
diff options
context:
space:
mode:
Diffstat (limited to 'AirPlayService.py')
-rw-r--r--AirPlayService.py232
1 files changed, 232 insertions, 0 deletions
diff --git a/AirPlayService.py b/AirPlayService.py
new file mode 100644
index 0000000..dee6dde
--- /dev/null
+++ b/AirPlayService.py
@@ -0,0 +1,232 @@
1# -*- coding: utf-8 -*-
2
3import asyncore
4import platform
5import socket
6import threading
7import time
8from datetime import datetime, date
9from urlparse import urlparse
10from ZeroconfService import ZeroconfService
11
12__all__ = ["BaseAirPlayRequest", "AirPlayService", "AirPlayProtocolHandler"]
13
14class BaseAirPlayRequest(object):
15 def read_from_socket(self, socket):
16 data = socket.recv(1024)
17 if not data:
18 return False
19
20 # we split the message into HTTP headers and content body
21 message = data.split("\r\n\r\n", 1)
22 headers = message[0]
23 headerlines = headers.splitlines()
24
25 # parse request headers
26 command = headerlines[0].split()
27 self.type = command[0]
28 self.uri = command[1]
29 self.version = command[2]
30 del headerlines[0]
31 self.headers = self.parse_headers(headerlines)
32
33 # parse any uri query parameters
34 self.params = None
35 if (self.uri.find('?')):
36 url = urlparse(self.uri)
37 if (url[4] is not ""):
38 self.params = dict([part.split('=') for part in url[4].split('&')])
39 self.uri = url[2]
40
41 # parse message body
42 if (int(self.headers['Content-Length']) > 0):
43 self.body = message[1]
44 # read more data if we have to
45 if len(self.body) < int(self.headers['Content-Length']):
46 while 1:
47 data = socket.recv(8192)
48 if not data:
49 break
50 self.body = self.body + data
51
52 return True
53
54 def parse_headers(self, lines):
55 headers = {}
56 for line in lines:
57 if line:
58 name, value = line.split(": ", 1)
59 headers[name.strip()] = value.strip()
60 return headers
61
62class AirPlayProtocolHandler(asyncore.dispatcher_with_send):
63 def __init__(self, socket, service):
64 asyncore.dispatcher_with_send.__init__(self, socket)
65 self.service = service
66
67 def handle_read(self):
68 # read from the socket and parse a HTTP request
69 request = BaseAirPlayRequest()
70 if (not request.read_from_socket(self)):
71 return
72
73 answer = ""
74
75 # process the request and run the appropriate callback
76 if (request.uri.find('/play')>-1):
77 parsedbody = request.parse_headers(request.body.splitlines())
78 self.service.play(parsedbody['Content-Location'], float(parsedbody['Start-Position']))
79 answer = self.create_request()
80 elif (request.uri.find('/stop')>-1):
81 self.service.stop(request.headers)
82 answer = self.create_request()
83 elif (request.uri.find('/scrub')>-1):
84 if request.type == 'GET':
85 d, p = self.service.get_scrub()
86 content = "duration: " + str(float(d))
87 content += "\nposition: " + str(float(p))
88 answer = self.create_request(200, content)
89 elif request.type == 'POST':
90 self.service.set_scrub(float(request.params['position']))
91 answer = self.create_request()
92 elif (request.uri.find('/reverse')>-1):
93 self.service.reverse(request.headers)
94 answer = self.create_request(101)
95 elif (request.type == 'POST' and request.uri.find('/rate')>-1):
96 self.service.rate(float(request.params['value']))
97 answer = self.create_request()
98 elif (request.type == 'PUT' and request.uri.find('/photo')>-1):
99 self.photo(request.body, request.headers['X-Apple-Transition'])
100 answer = self.create_request()
101 else:
102 print "ERROR: AirPlay - Unable to handle request \"%s\"" % (request.uri)
103 answer = self.create_request(404)
104
105 if(answer is not ""):
106 self.send(answer)
107
108 def getDateTime(self):
109 today = datetime.now()
110 datestr = today.strftime("%a, %d %b %Y %H:%M:%S")
111 return datestr+" GMT"
112
113 def create_request(self, status = 200, body = ""):
114 clength = len(bytes(body))
115 if (status == 200):
116 answer = "HTTP/1.1 200 OK"
117 elif (status == 404):
118 answer = "HTTP/1.1 404 Not Found"
119 elif (status == 101):
120 answer = "HTTP/1.1 101 Switching Protocols"
121 answer += "\nUpgrade: PTTH/1.0"
122 answer += "\nConnection: Upgrade"
123 answer += "\nDate: " + self.getDateTime()
124 answer += "\nContent-Length: " + str(clength)
125 answer +="\n\n"
126 answer += body
127 return answer
128
129 def get_scrub(self):
130 return False
131
132 def set_scrub(self, position):
133 return False
134
135 def play(self, location, position):
136 return False
137
138 def stop(self, info):
139 return False
140
141 def reverse(self, info):
142 return True
143
144 def photo(self, data, transition):
145 return False
146
147 def rate(self, speed):
148 return False
149
150 def volume(self, info):
151 return False
152
153 def authorize(self, info):
154 return False
155
156 def event(self, info):
157 return False
158
159class AsyncoreThread(threading.Thread):
160 def __init__(self, timeout=30.0, use_poll=0,map=None):
161 self.flag = True
162 self.timeout = 30.0
163 self.use_poll = use_poll
164 self.map = map
165 threading.Thread.__init__(self, None, None, 'asyncore thread')
166
167 def run(self):
168 self.loop()
169
170 def loop(self):
171 if self.map is None:
172 self.map = asyncore.socket_map
173
174 if self.use_poll:
175 if hasattr(select, 'poll'):
176 poll_fun = asyncore.poll3
177 else:
178 poll_fun = asyncore.poll2
179 else:
180 poll_fun = asyncore.poll
181
182 while self.map and self.flag:
183 poll_fun(self.timeout,self.map)
184
185 def end(self):
186 self.flag=False
187 self.map=None
188
189class AirPlayService(asyncore.dispatcher):
190 def __init__(self, name=None, host="0.0.0.0", port=22555):
191 print "AirPlayService running"
192
193 # create socket server
194 asyncore.dispatcher.__init__(self)
195 self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
196 self.set_reuse_addr()
197 self.bind((host, port))
198 self.listen(5)
199 self.remote_clients = []
200
201 # create avahi service
202 if (name is None):
203 name = "Airplay Service on " + platform.node()
204 self.zeroconf_service = ZeroconfService(name, port=port, stype="_airplay._tcp", text=["Name="+name])
205
206 # publish avahi service
207 self.zeroconf_service.publish()
208
209 # do this so we do not block the main thread
210 self.thread = AsyncoreThread(timeout=30)
211 self.thread.is_finished = False
212 self.thread.start()
213
214 def handle_accept(self):
215 pair = self.accept()
216 if pair is None:
217 pass
218 else:
219 sock, addr = pair
220 self.remote_clients.append(AirPlayProtocolHandler(sock, self))
221
222 def handle_close(self):
223 self.close()
224
225 def __del__(self):
226 self.thread.end()
227 self.close()
228 del self.thread
229
230 # unpublish avahi service
231 self.zeroconf_service.unpublish()
232