From eb9d750f59161f2bf0a55b4e75b56e65e6f7b432 Mon Sep 17 00:00:00 2001 From: Martin Szulecki Date: Mon, 27 Dec 2010 16:07:43 +0100 Subject: Initial commit of the sources --- AirPlayService.py | 232 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 AirPlayService.py (limited to 'AirPlayService.py') 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() + -- cgit v1.1-32-gdbae