summaryrefslogtreecommitdiffstats
path: root/AirPlayService.py
diff options
context:
space:
mode:
authorGravatar Martin Szulecki2010-12-27 16:07:43 +0100
committerGravatar Martin Szulecki2010-12-27 16:07:43 +0100
commiteb9d750f59161f2bf0a55b4e75b56e65e6f7b432 (patch)
tree975a0e589eb2a1e2e1bcc7b253ffaa115b193ae3 /AirPlayService.py
downloadtotem-plugin-airplay-eb9d750f59161f2bf0a55b4e75b56e65e6f7b432.tar.gz
totem-plugin-airplay-eb9d750f59161f2bf0a55b4e75b56e65e6f7b432.tar.bz2
Initial commit of the sources
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 @@
+# -*- coding: utf-8 -*-
+
+import asyncore
+import platform
+import socket
+import threading
+import time
+from datetime import datetime, date
+from urlparse import urlparse
+from ZeroconfService import ZeroconfService
+
+__all__ = ["BaseAirPlayRequest", "AirPlayService", "AirPlayProtocolHandler"]
+
+class BaseAirPlayRequest(object):
+ def read_from_socket(self, socket):
+ data = socket.recv(1024)
+ if not data:
+ return False
+
+ # we split the message into HTTP headers and content body
+ message = data.split("\r\n\r\n", 1)
+ headers = message[0]
+ headerlines = headers.splitlines()
+
+ # parse request headers
+ command = headerlines[0].split()
+ self.type = command[0]
+ self.uri = command[1]
+ self.version = command[2]
+ del headerlines[0]
+ self.headers = self.parse_headers(headerlines)
+
+ # parse any uri query parameters
+ self.params = None
+ if (self.uri.find('?')):
+ url = urlparse(self.uri)
+ if (url[4] is not ""):
+ self.params = dict([part.split('=') for part in url[4].split('&')])
+ self.uri = url[2]
+
+ # parse message body
+ if (int(self.headers['Content-Length']) > 0):
+ self.body = message[1]
+ # read more data if we have to
+ if len(self.body) < int(self.headers['Content-Length']):
+ while 1:
+ data = socket.recv(8192)
+ if not data:
+ break
+ self.body = self.body + data
+
+ return True
+
+ def parse_headers(self, lines):
+ headers = {}
+ for line in lines:
+ if line:
+ name, value = line.split(": ", 1)
+ headers[name.strip()] = value.strip()
+ return headers
+
+class AirPlayProtocolHandler(asyncore.dispatcher_with_send):
+ def __init__(self, socket, service):
+ asyncore.dispatcher_with_send.__init__(self, socket)
+ self.service = service
+
+ def handle_read(self):
+ # read from the socket and parse a HTTP request
+ request = BaseAirPlayRequest()
+ if (not request.read_from_socket(self)):
+ return
+
+ answer = ""
+
+ # process the request and run the appropriate callback
+ if (request.uri.find('/play')>-1):
+ parsedbody = request.parse_headers(request.body.splitlines())
+ self.service.play(parsedbody['Content-Location'], float(parsedbody['Start-Position']))
+ answer = self.create_request()
+ elif (request.uri.find('/stop')>-1):
+ self.service.stop(request.headers)
+ answer = self.create_request()
+ elif (request.uri.find('/scrub')>-1):
+ if request.type == 'GET':
+ d, p = self.service.get_scrub()
+ content = "duration: " + str(float(d))
+ content += "\nposition: " + str(float(p))
+ answer = self.create_request(200, content)
+ elif request.type == 'POST':
+ self.service.set_scrub(float(request.params['position']))
+ answer = self.create_request()
+ elif (request.uri.find('/reverse')>-1):
+ self.service.reverse(request.headers)
+ answer = self.create_request(101)
+ elif (request.type == 'POST' and request.uri.find('/rate')>-1):
+ self.service.rate(float(request.params['value']))
+ answer = self.create_request()
+ elif (request.type == 'PUT' and request.uri.find('/photo')>-1):
+ self.photo(request.body, request.headers['X-Apple-Transition'])
+ answer = self.create_request()
+ else:
+ print "ERROR: AirPlay - Unable to handle request \"%s\"" % (request.uri)
+ answer = self.create_request(404)
+
+ if(answer is not ""):
+ self.send(answer)
+
+ def getDateTime(self):
+ today = datetime.now()
+ datestr = today.strftime("%a, %d %b %Y %H:%M:%S")
+ return datestr+" GMT"
+
+ def create_request(self, status = 200, body = ""):
+ clength = len(bytes(body))
+ if (status == 200):
+ answer = "HTTP/1.1 200 OK"
+ elif (status == 404):
+ answer = "HTTP/1.1 404 Not Found"
+ elif (status == 101):
+ answer = "HTTP/1.1 101 Switching Protocols"
+ answer += "\nUpgrade: PTTH/1.0"
+ answer += "\nConnection: Upgrade"
+ answer += "\nDate: " + self.getDateTime()
+ answer += "\nContent-Length: " + str(clength)
+ answer +="\n\n"
+ answer += body
+ return answer
+
+ def get_scrub(self):
+ return False
+
+ def set_scrub(self, position):
+ return False
+
+ def play(self, location, position):
+ return False
+
+ def stop(self, info):
+ return False
+
+ def reverse(self, info):
+ return True
+
+ def photo(self, data, transition):
+ return False
+
+ def rate(self, speed):
+ return False
+
+ def volume(self, info):
+ return False
+
+ def authorize(self, info):
+ return False
+
+ def event(self, info):
+ return False
+
+class AsyncoreThread(threading.Thread):
+ def __init__(self, timeout=30.0, use_poll=0,map=None):
+ self.flag = True
+ self.timeout = 30.0
+ self.use_poll = use_poll
+ self.map = map
+ threading.Thread.__init__(self, None, None, 'asyncore thread')
+
+ def run(self):
+ self.loop()
+
+ def loop(self):
+ if self.map is None:
+ self.map = asyncore.socket_map
+
+ if self.use_poll:
+ if hasattr(select, 'poll'):
+ poll_fun = asyncore.poll3
+ else:
+ poll_fun = asyncore.poll2
+ else:
+ poll_fun = asyncore.poll
+
+ while self.map and self.flag:
+ poll_fun(self.timeout,self.map)
+
+ def end(self):
+ self.flag=False
+ self.map=None
+
+class AirPlayService(asyncore.dispatcher):
+ def __init__(self, name=None, host="0.0.0.0", port=22555):
+ print "AirPlayService running"
+
+ # create socket server
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind((host, port))
+ self.listen(5)
+ self.remote_clients = []
+
+ # create avahi service
+ if (name is None):
+ name = "Airplay Service on " + platform.node()
+ self.zeroconf_service = ZeroconfService(name, port=port, stype="_airplay._tcp", text=["Name="+name])
+
+ # publish avahi service
+ self.zeroconf_service.publish()
+
+ # do this so we do not block the main thread
+ self.thread = AsyncoreThread(timeout=30)
+ self.thread.is_finished = False
+ self.thread.start()
+
+ def handle_accept(self):
+ pair = self.accept()
+ if pair is None:
+ pass
+ else:
+ sock, addr = pair
+ self.remote_clients.append(AirPlayProtocolHandler(sock, self))
+
+ def handle_close(self):
+ self.close()
+
+ def __del__(self):
+ self.thread.end()
+ self.close()
+ del self.thread
+
+ # unpublish avahi service
+ self.zeroconf_service.unpublish()
+