2 * Many concepts and protocol specification in this code are taken
3 * from Airplayer. https://github.com/PascalW/Airplayer
7 * This Program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2.1, or (at your option)
12 * This Program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with XBMC; see the file COPYING. If not, see
19 * <http://www.gnu.org/licenses/>.
23 #include "network/Network.h"
24 #include "AirPlayServer.h"
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include "DllLibPlist.h"
31 #include "utils/log.h"
32 #include "utils/URIUtils.h"
33 #include "utils/StringUtils.h"
34 #include "threads/SingleLock.h"
35 #include "filesystem/File.h"
37 #include "Application.h"
38 #include "ApplicationMessenger.h"
39 #include "utils/md5.h"
40 #include "utils/Variant.h"
41 #include "settings/Settings.h"
42 #include "guilib/Key.h"
44 #include "cores/IPlayer.h"
45 #include "interfaces/AnnouncementManager.h"
47 using namespace ANNOUNCEMENT;
50 #define close closesocket
53 #define RECEIVEBUFFER 1024
55 #define AIRPLAY_STATUS_OK 200
56 #define AIRPLAY_STATUS_SWITCHING_PROTOCOLS 101
57 #define AIRPLAY_STATUS_NEED_AUTH 401
58 #define AIRPLAY_STATUS_NOT_FOUND 404
59 #define AIRPLAY_STATUS_METHOD_NOT_ALLOWED 405
60 #define AIRPLAY_STATUS_NOT_IMPLEMENTED 501
61 #define AIRPLAY_STATUS_NO_RESPONSE_NEEDED 1000
63 CAirPlayServer *CAirPlayServer::ServerInstance = NULL;
64 int CAirPlayServer::m_isPlaying = 0;
67 #define EVENT_PLAYING 0
68 #define EVENT_PAUSED 1
69 #define EVENT_LOADING 2
70 #define EVENT_STOPPED 3
71 const char *eventStrings[] = {"playing", "paused", "loading", "stopped"};
73 #define PLAYBACK_INFO "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
74 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
75 "<plist version=\"1.0\">\r\n"\
77 "<key>duration</key>\r\n"\
78 "<real>%f</real>\r\n"\
79 "<key>loadedTimeRanges</key>\r\n"\
82 "\t\t\t<key>duration</key>\r\n"\
83 "\t\t\t<real>%f</real>\r\n"\
84 "\t\t\t<key>start</key>\r\n"\
85 "\t\t\t<real>0.0</real>\r\n"\
88 "<key>playbackBufferEmpty</key>\r\n"\
90 "<key>playbackBufferFull</key>\r\n"\
92 "<key>playbackLikelyToKeepUp</key>\r\n"\
94 "<key>position</key>\r\n"\
95 "<real>%f</real>\r\n"\
96 "<key>rate</key>\r\n"\
97 "<real>%d</real>\r\n"\
98 "<key>readyToPlay</key>\r\n"\
100 "<key>seekableTimeRanges</key>\r\n"\
103 "\t\t\t<key>duration</key>\r\n"\
104 "\t\t\t<real>%f</real>\r\n"\
105 "\t\t\t<key>start</key>\r\n"\
106 "\t\t\t<real>0.0</real>\r\n"\
112 #define PLAYBACK_INFO_NOT_READY "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
113 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
114 "<plist version=\"1.0\">\r\n"\
116 "<key>readyToPlay</key>\r\n"\
121 #define SERVER_INFO "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
122 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
123 "<plist version=\"1.0\">\r\n"\
125 "<key>deviceid</key>\r\n"\
126 "<string>%s</string>\r\n"\
127 "<key>features</key>\r\n"\
128 "<integer>119</integer>\r\n"\
129 "<key>model</key>\r\n"\
130 "<string>Xbmc,1</string>\r\n"\
131 "<key>protovers</key>\r\n"\
132 "<string>1.0</string>\r\n"\
133 "<key>srcvers</key>\r\n"\
134 "<string>"AIRPLAY_SERVER_VERSION_STR"</string>\r\n"\
138 #define EVENT_INFO "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r\n"\
139 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\r\n"\
140 "<plist version=\"1.0\">\r\n"\
142 "<key>category</key>\r\n"\
143 "<string>video</string>\r\n"\
144 "<key>sessionID</key>\r\n"\
145 "<integer>%d</integer>\r\n"\
146 "<key>state</key>\r\n"\
147 "<string>%s</string>\r\n"\
151 #define AUTH_REALM "AirPlay"
152 #define AUTH_REQUIRED "WWW-Authenticate: Digest realm=\"" AUTH_REALM "\", nonce=\"%s\"\r\n"
154 void CAirPlayServer::Announce(AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
156 if ( (flag & Player) && strcmp(sender, "xbmc") == 0 && ServerInstance)
158 if (strcmp(message, "OnStop") == 0)
161 ServerInstance->AnnounceToClients(EVENT_STOPPED);
163 else if (strcmp(message, "OnPlay") == 0)
165 ServerInstance->AnnounceToClients(EVENT_PLAYING);
167 else if (strcmp(message, "OnPause") == 0)
169 ServerInstance->AnnounceToClients(EVENT_PAUSED);
174 bool CAirPlayServer::StartServer(int port, bool nonlocal)
178 ServerInstance = new CAirPlayServer(port, nonlocal);
179 if (ServerInstance->Initialize())
181 ServerInstance->Create();
188 bool CAirPlayServer::SetCredentials(bool usePassword, const CStdString& password)
194 ret = ServerInstance->SetInternalCredentials(usePassword, password);
199 bool CAirPlayServer::SetInternalCredentials(bool usePassword, const CStdString& password)
201 m_usePassword = usePassword;
202 m_password = password;
206 void CAirPlayServer::StopServer(bool bWait)
210 ServerInstance->StopThread(bWait);
213 delete ServerInstance;
214 ServerInstance = NULL;
219 bool CAirPlayServer::IsRunning()
221 if (ServerInstance == NULL)
224 return ((CThread*)ServerInstance)->IsRunning();
227 void CAirPlayServer::AnnounceToClients(int state)
229 CSingleLock lock (m_connectionLock);
231 std::vector<CTCPClient>::iterator it;
232 for (it = m_connections.begin(); it != m_connections.end(); it++)
234 CStdString reverseHeader;
235 CStdString reverseBody;
237 int reverseSocket = INVALID_SOCKET;
238 it->ComposeReverseEvent(reverseHeader, reverseBody, state);
240 // Send event status per reverse http socket (play, loading, paused)
241 // if we have a reverse header and a reverse socket
242 if (reverseHeader.size() > 0 && m_reverseSockets.find(it->m_sessionId) != m_reverseSockets.end())
244 //search the reverse socket to this sessionid
245 response = StringUtils::Format("POST /event HTTP/1.1\r\n");
246 reverseSocket = m_reverseSockets[it->m_sessionId]; //that is our reverse socket
247 response += reverseHeader;
251 if (reverseBody.size() > 0)
253 response += reverseBody;
256 // don't send it to the connection object
257 // the reverse socket itself belongs to
258 if (reverseSocket != INVALID_SOCKET && reverseSocket != it->m_socket)
260 send(reverseSocket, response.c_str(), response.size(), 0);//send the event status on the eventSocket
265 CAirPlayServer::CAirPlayServer(int port, bool nonlocal) : CThread("AirPlayServer")
268 m_nonlocal = nonlocal;
269 m_ServerSocket = INVALID_SOCKET;
270 m_usePassword = false;
272 CAnnouncementManager::AddAnnouncer(this);
275 CAirPlayServer::~CAirPlayServer()
277 CAnnouncementManager::RemoveAnnouncer(this);
280 void CAirPlayServer::Process()
283 static int sessionCounter = 0;
289 struct timeval to = {1, 0};
292 FD_SET(m_ServerSocket, &rfds);
293 max_fd = m_ServerSocket;
295 for (unsigned int i = 0; i < m_connections.size(); i++)
297 FD_SET(m_connections[i].m_socket, &rfds);
298 if (m_connections[i].m_socket > max_fd)
299 max_fd = m_connections[i].m_socket;
302 int res = select(max_fd+1, &rfds, NULL, NULL, &to);
305 CLog::Log(LOGERROR, "AIRPLAY Server: Select failed");
311 for (int i = m_connections.size() - 1; i >= 0; i--)
313 int socket = m_connections[i].m_socket;
314 if (FD_ISSET(socket, &rfds))
316 char buffer[RECEIVEBUFFER] = {};
318 nread = recv(socket, (char*)&buffer, RECEIVEBUFFER, 0);
321 CStdString sessionId;
322 m_connections[i].PushBuffer(this, buffer, nread, sessionId, m_reverseSockets);
326 CSingleLock lock (m_connectionLock);
327 CLog::Log(LOGINFO, "AIRPLAY Server: Disconnection detected");
328 m_connections[i].Disconnect();
329 m_connections.erase(m_connections.begin() + i);
334 if (FD_ISSET(m_ServerSocket, &rfds))
336 CLog::Log(LOGDEBUG, "AIRPLAY Server: New connection detected");
337 CTCPClient newconnection;
338 newconnection.m_socket = accept(m_ServerSocket, (struct sockaddr*) &newconnection.m_cliaddr, &newconnection.m_addrlen);
340 newconnection.m_sessionCounter = sessionCounter;
342 if (newconnection.m_socket == INVALID_SOCKET)
344 CLog::Log(LOGERROR, "AIRPLAY Server: Accept of new connection failed: %d", errno);
354 CSingleLock lock (m_connectionLock);
355 CLog::Log(LOGINFO, "AIRPLAY Server: New connection added");
356 m_connections.push_back(newconnection);
365 bool CAirPlayServer::Initialize()
369 if ((m_ServerSocket = CreateTCPServerSocket(m_port, !m_nonlocal, 10, "AIRPLAY")) == INVALID_SOCKET)
372 CLog::Log(LOGINFO, "AIRPLAY Server: Successfully initialized");
376 void CAirPlayServer::Deinitialize()
378 CSingleLock lock (m_connectionLock);
379 for (unsigned int i = 0; i < m_connections.size(); i++)
380 m_connections[i].Disconnect();
382 m_connections.clear();
383 m_reverseSockets.clear();
385 if (m_ServerSocket != INVALID_SOCKET)
387 shutdown(m_ServerSocket, SHUT_RDWR);
388 close(m_ServerSocket);
389 m_ServerSocket = INVALID_SOCKET;
393 CAirPlayServer::CTCPClient::CTCPClient()
395 m_socket = INVALID_SOCKET;
396 m_httpParser = new HttpParser();
398 m_addrlen = sizeof(struct sockaddr_storage);
399 m_pLibPlist = new DllLibPlist();
401 m_bAuthenticated = false;
402 m_lastEvent = EVENT_NONE;
405 CAirPlayServer::CTCPClient::CTCPClient(const CTCPClient& client)
408 m_httpParser = new HttpParser();
409 m_pLibPlist = new DllLibPlist();
412 CAirPlayServer::CTCPClient::~CTCPClient()
414 if (m_pLibPlist->IsLoaded())
416 m_pLibPlist->Unload();
422 CAirPlayServer::CTCPClient& CAirPlayServer::CTCPClient::operator=(const CTCPClient& client)
425 m_httpParser = new HttpParser();
426 m_pLibPlist = new DllLibPlist();
430 void CAirPlayServer::CTCPClient::PushBuffer(CAirPlayServer *host, const char *buffer,
431 int length, CStdString &sessionId, std::map<CStdString,
432 int> &reverseSockets)
434 HttpParser::status_t status = m_httpParser->addBytes(buffer, length);
436 if (status == HttpParser::Done)
439 CStdString responseHeader;
440 CStdString responseBody;
441 int status = ProcessRequest(responseHeader, responseBody);
442 sessionId = m_sessionId;
443 CStdString statusMsg = "OK";
447 case AIRPLAY_STATUS_NOT_IMPLEMENTED:
448 statusMsg = "Not Implemented";
450 case AIRPLAY_STATUS_SWITCHING_PROTOCOLS:
451 statusMsg = "Switching Protocols";
452 reverseSockets[sessionId] = m_socket;//save this socket as reverse http socket for this sessionid
454 case AIRPLAY_STATUS_NEED_AUTH:
455 statusMsg = "Unauthorized";
457 case AIRPLAY_STATUS_NOT_FOUND:
458 statusMsg = "Not Found";
460 case AIRPLAY_STATUS_METHOD_NOT_ALLOWED:
461 statusMsg = "Method Not Allowed";
465 // Prepare the response
467 const time_t ltime = time(NULL);
468 char *date = asctime(gmtime(<ime)); //Fri, 17 Dec 2010 11:18:01 GMT;
469 date[strlen(date) - 1] = '\0'; // remove \n
470 response = StringUtils::Format("HTTP/1.1 %d %s\nDate: %s\r\n", status, statusMsg.c_str(), date);
471 if (responseHeader.size() > 0)
473 response += responseHeader;
476 if (responseBody.size() > 0)
478 response = StringUtils::Format("%sContent-Length: %d\r\n", response.c_str(), responseBody.size());
482 if (responseBody.size() > 0)
484 response += responseBody;
488 //don't send response on AIRPLAY_STATUS_NO_RESPONSE_NEEDED
489 if (status != AIRPLAY_STATUS_NO_RESPONSE_NEEDED)
491 send(m_socket, response.c_str(), response.size(), 0);
493 // We need a new parser...
495 m_httpParser = new HttpParser;
499 void CAirPlayServer::CTCPClient::Disconnect()
501 if (m_socket != INVALID_SOCKET)
503 CSingleLock lock (m_critSection);
504 shutdown(m_socket, SHUT_RDWR);
506 m_socket = INVALID_SOCKET;
512 void CAirPlayServer::CTCPClient::Copy(const CTCPClient& client)
514 m_socket = client.m_socket;
515 m_cliaddr = client.m_cliaddr;
516 m_addrlen = client.m_addrlen;
517 m_httpParser = client.m_httpParser;
518 m_authNonce = client.m_authNonce;
519 m_bAuthenticated = client.m_bAuthenticated;
520 m_sessionCounter = client.m_sessionCounter;
524 void CAirPlayServer::CTCPClient::ComposeReverseEvent( CStdString& reverseHeader,
525 CStdString& reverseBody,
529 if ( m_lastEvent != state )
537 reverseBody = StringUtils::Format(EVENT_INFO, m_sessionCounter, eventStrings[state]);
538 CLog::Log(LOGDEBUG, "AIRPLAY: sending event: %s", eventStrings[state]);
541 reverseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
542 reverseHeader = StringUtils::Format("%sContent-Length: %d\r\n",reverseHeader.c_str(), reverseBody.size());
543 reverseHeader = StringUtils::Format("%sx-apple-session-id: %s\r\n",reverseHeader.c_str(), m_sessionId.c_str());
548 void CAirPlayServer::CTCPClient::ComposeAuthRequestAnswer(CStdString& responseHeader, CStdString& responseBody)
550 int16_t random=rand();
551 CStdString randomStr = StringUtils::Format("%i", random);
552 m_authNonce=XBMC::XBMC_MD5::GetMD5(randomStr);
553 responseHeader = StringUtils::Format(AUTH_REQUIRED, m_authNonce.c_str());
554 responseBody.clear();
559 CStdString calcResponse(const CStdString& username,
560 const CStdString& password,
561 const CStdString& realm,
562 const CStdString& method,
563 const CStdString& digestUri,
564 const CStdString& nonce)
570 HA1 = XBMC::XBMC_MD5::GetMD5(username + ":" + realm + ":" + password);
571 HA2 = XBMC::XBMC_MD5::GetMD5(method + ":" + digestUri);
572 response = XBMC::XBMC_MD5::GetMD5(HA1.ToLower() + ":" + nonce + ":" + HA2.ToLower());
573 return response.ToLower();
577 //from a string field1="value1", field2="value2" it parses the value to a field
578 CStdString getFieldFromString(const CStdString &str, const char* field)
581 CStdStringArray tmpAr1;
582 CStdStringArray tmpAr2;
584 StringUtils::SplitString(str, ",", tmpAr1);
586 for(unsigned int i = 0;i<tmpAr1.size();i++)
588 if (tmpAr1[i].Find(field) != -1)
590 if (StringUtils::SplitString(tmpAr1[i], "=", tmpAr2) == 2)
592 tmpAr2[1].Remove('\"');//remove quotes
600 bool CAirPlayServer::CTCPClient::checkAuthorization(const CStdString& authStr,
601 const CStdString& method,
602 const CStdString& uri)
604 bool authValid = true;
611 //first get username - we allow all usernames for airplay (usually it is AirPlay)
612 username = getFieldFromString(authStr, "username");
613 if (username.empty())
621 if (getFieldFromString(authStr, "realm") != AUTH_REALM)
630 if (getFieldFromString(authStr, "nonce") != m_authNonce)
639 if (getFieldFromString(authStr, "uri") != uri)
645 //last check response
648 CStdString realm = AUTH_REALM;
649 CStdString ourResponse = calcResponse(username, ServerInstance->m_password, realm, method, uri, m_authNonce);
650 CStdString theirResponse = getFieldFromString(authStr, "response");
651 if (!theirResponse.Equals(ourResponse, false))
654 CLog::Log(LOGDEBUG,"AirAuth: response mismatch - our: %s theirs: %s",ourResponse.c_str(), theirResponse.c_str());
658 CLog::Log(LOGDEBUG, "AirAuth: successfull authentication from AirPlay client");
661 m_bAuthenticated = authValid;
662 return m_bAuthenticated;
665 void CAirPlayServer::backupVolume()
667 if (ServerInstance->m_origVolume == -1)
668 ServerInstance->m_origVolume = (int)g_application.GetVolume();
671 void CAirPlayServer::restoreVolume()
673 if (ServerInstance->m_origVolume != -1 && CSettings::Get().GetBool("services.airplayvolumecontrol"))
675 g_application.SetVolume((float)ServerInstance->m_origVolume);
676 ServerInstance->m_origVolume = -1;
680 int CAirPlayServer::CTCPClient::ProcessRequest( CStdString& responseHeader,
681 CStdString& responseBody)
683 CStdString method = m_httpParser->getMethod();
684 CStdString uri = m_httpParser->getUri();
685 CStdString queryString = m_httpParser->getQueryString();
686 CStdString body = m_httpParser->getBody();
687 CStdString contentType = m_httpParser->getValue("content-type");
688 m_sessionId = m_httpParser->getValue("x-apple-session-id");
689 CStdString authorization = m_httpParser->getValue("authorization");
690 int status = AIRPLAY_STATUS_OK;
691 bool needAuth = false;
693 if (m_sessionId.IsEmpty())
694 m_sessionId = "00000000-0000-0000-0000-000000000000";
696 if (ServerInstance->m_usePassword && !m_bAuthenticated)
701 int startQs = uri.Find('?');
704 uri = uri.Left(startQs);
707 // This is the socket which will be used for reverse HTTP
708 // negotiate reverse HTTP via upgrade
709 if (uri == "/reverse")
711 status = AIRPLAY_STATUS_SWITCHING_PROTOCOLS;
712 responseHeader = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n";
715 // The rate command is used to play/pause media.
716 // A value argument should be supplied which indicates media should be played or paused.
719 else if (uri == "/rate")
721 const char* found = strstr(queryString.c_str(), "value=");
722 int rate = found ? (int)(atof(found + strlen("value=")) + 0.5f) : 0;
724 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with rate %i", uri.c_str(), rate);
726 if (needAuth && !checkAuthorization(authorization, method, uri))
728 status = AIRPLAY_STATUS_NEED_AUTH;
732 if (g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused())
734 CApplicationMessenger::Get().MediaPause();
739 if (g_application.m_pPlayer->IsPausedPlayback())
741 CApplicationMessenger::Get().MediaPause();
746 // The volume command is used to change playback volume.
747 // A value argument should be supplied which indicates how loud we should get.
748 // 0.000000 => silent
750 else if (uri == "/volume")
752 const char* found = strstr(queryString.c_str(), "volume=");
753 float volume = found ? (float)strtod(found + strlen("volume="), NULL) : 0;
755 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with volume %f", uri.c_str(), volume);
757 if (needAuth && !checkAuthorization(authorization, method, uri))
759 status = AIRPLAY_STATUS_NEED_AUTH;
761 else if (volume >= 0 && volume <= 1)
763 float oldVolume = g_application.GetVolume();
765 if(oldVolume != volume && CSettings::Get().GetBool("services.airplayvolumecontrol"))
768 g_application.SetVolume(volume);
769 CApplicationMessenger::Get().ShowVolumeBar(oldVolume < volume);
775 // Contains a header like format in the request body which should contain a
776 // Content-Location and optionally a Start-Position
777 else if (uri == "/play")
780 float position = 0.0;
781 m_lastEvent = EVENT_NONE;
783 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
785 if (needAuth && !checkAuthorization(authorization, method, uri))
787 status = AIRPLAY_STATUS_NEED_AUTH;
789 else if (contentType == "application/x-apple-binary-plist")
791 CAirPlayServer::m_isPlaying++;
793 if (m_pLibPlist->Load())
795 m_pLibPlist->EnableDelayedUnload(false);
797 const char* bodyChr = m_httpParser->getBody();
800 m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict);
802 if (m_pLibPlist->plist_dict_get_size(dict))
804 plist_t tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Start-Position");
807 double tmpDouble = 0;
808 m_pLibPlist->plist_get_real_val(tmpNode, &tmpDouble);
809 position = (float)tmpDouble;
812 tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Content-Location");
816 m_pLibPlist->plist_get_string_val(tmpNode, &tmpStr);
818 #ifdef TARGET_WINDOWS
819 m_pLibPlist->plist_free_string_val(tmpStr);
827 m_pLibPlist->plist_free(dict);
832 CLog::Log(LOGERROR, "Error parsing plist");
834 m_pLibPlist->Unload();
839 CAirPlayServer::m_isPlaying++;
841 int start = body.Find("Content-Location: ");
843 return AIRPLAY_STATUS_NOT_IMPLEMENTED;
844 start += strlen("Content-Location: ");
845 int end = body.Find('\n', start);
846 location = body.Mid(start, end - start);
848 start = body.Find("Start-Position");
851 start += strlen("Start-Position: ");
852 int end = body.Find('\n', start);
853 CStdString positionStr = body.Mid(start, end - start);
854 position = (float)atof(positionStr.c_str());
858 if (status != AIRPLAY_STATUS_NEED_AUTH)
860 CStdString userAgent="AppleCoreMedia/1.0.0.8F455 (AppleTV; U; CPU OS 4_3 like Mac OS X; de_de)";
861 CURL::Encode(userAgent);
862 location += "|User-Agent=" + userAgent;
864 CFileItem fileToPlay(location, false);
865 fileToPlay.SetProperty("StartPercent", position*100.0f);
866 ServerInstance->AnnounceToClients(EVENT_LOADING);
867 // froce to internal dvdplayer cause it is the only
868 // one who will work well with airplay
869 g_application.m_eForcedNextPlayer = EPC_DVDPLAYER;
870 CApplicationMessenger::Get().MediaPlay(fileToPlay);
874 // Used to perform seeking (POST request) and to retrieve current player position (GET request).
875 // GET scrub seems to also set rate 1 - strange but true
876 else if (uri == "/scrub")
878 if (needAuth && !checkAuthorization(authorization, method, uri))
880 status = AIRPLAY_STATUS_NEED_AUTH;
882 else if (method == "GET")
884 CLog::Log(LOGDEBUG, "AIRPLAY: got GET request %s", uri.c_str());
886 if (g_application.m_pPlayer->GetTotalTime())
888 float position = ((float) g_application.m_pPlayer->GetTime()) / 1000;
889 responseBody = StringUtils::Format("duration: %.6f\r\nposition: %.6f\r\n", (float)g_application.m_pPlayer->GetTotalTime() / 1000, position);
893 status = AIRPLAY_STATUS_METHOD_NOT_ALLOWED;
898 const char* found = strstr(queryString.c_str(), "position=");
900 if (found && g_application.m_pPlayer->HasPlayer())
902 int64_t position = (int64_t) (atof(found + strlen("position=")) * 1000.0);
903 g_application.m_pPlayer->SeekTime(position);
904 CLog::Log(LOGDEBUG, "AIRPLAY: got POST request %s with pos %"PRId64, uri.c_str(), position);
909 // Sent when media playback should be stopped
910 else if (uri == "/stop")
912 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
913 if (needAuth && !checkAuthorization(authorization, method, uri))
915 status = AIRPLAY_STATUS_NEED_AUTH;
919 if (IsPlaying()) //only stop player if we started him
921 CApplicationMessenger::Get().MediaStop();
922 CAirPlayServer::m_isPlaying--;
924 else //if we are not playing and get the stop request - we just wanna stop picture streaming
926 CApplicationMessenger::Get().SendAction(ACTION_PREVIOUS_MENU);
931 // RAW JPEG data is contained in the request body
932 else if (uri == "/photo")
934 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
935 if (needAuth && !checkAuthorization(authorization, method, uri))
937 status = AIRPLAY_STATUS_NEED_AUTH;
939 else if (m_httpParser->getContentLength() > 0)
941 XFILE::CFile tmpFile;
942 CStdString tmpFileName = "special://temp/airplay_photo.jpg";
944 if( m_httpParser->getContentLength() > 3 &&
945 m_httpParser->getBody()[1] == 'P' &&
946 m_httpParser->getBody()[2] == 'N' &&
947 m_httpParser->getBody()[3] == 'G')
949 tmpFileName = "special://temp/airplay_photo.png";
952 if (tmpFile.OpenForWrite(tmpFileName, true))
955 writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength());
958 if (writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength())
960 CApplicationMessenger::Get().PictureShow(tmpFileName);
964 CLog::Log(LOGERROR,"AirPlayServer: Error writing tmpFile.");
970 else if (uri == "/playback-info")
972 float position = 0.0f;
973 float duration = 0.0f;
974 float cachePosition = 0.0f;
975 bool playing = false;
977 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
979 if (needAuth && !checkAuthorization(authorization, method, uri))
981 status = AIRPLAY_STATUS_NEED_AUTH;
983 else if (g_application.m_pPlayer->HasPlayer())
985 if (g_application.m_pPlayer->GetTotalTime())
987 position = ((float) g_application.m_pPlayer->GetTime()) / 1000;
988 duration = ((float) g_application.m_pPlayer->GetTotalTime()) / 1000;
989 playing = !g_application.m_pPlayer->IsPaused();
990 cachePosition = position + (duration * g_application.m_pPlayer->GetCachePercentage() / 100.0f);
993 responseBody = StringUtils::Format(PLAYBACK_INFO, duration, cachePosition, position, (playing ? 1 : 0), duration);
994 responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
996 if (g_application.m_pPlayer->IsCaching())
998 CAirPlayServer::ServerInstance->AnnounceToClients(EVENT_LOADING);
1003 responseBody = StringUtils::Format(PLAYBACK_INFO_NOT_READY, duration, cachePosition, position, (playing ? 1 : 0), duration);
1004 responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
1008 else if (uri == "/server-info")
1010 CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
1011 responseBody = StringUtils::Format(SERVER_INFO, g_application.getNetwork().GetFirstConnectedInterface()->GetMacAddress().c_str());
1012 responseHeader = "Content-Type: text/x-apple-plist+xml\r\n";
1015 else if (uri == "/slideshow-features")
1020 else if (uri == "/authorize")
1022 // DRM, ignore for now.
1025 else if (uri == "/setProperty")
1027 status = AIRPLAY_STATUS_NOT_FOUND;
1030 else if (uri == "/getProperty")
1032 status = AIRPLAY_STATUS_NOT_FOUND;
1035 else if (uri == "200") //response OK from the event reverse message
1037 status = AIRPLAY_STATUS_NO_RESPONSE_NEEDED;
1041 CLog::Log(LOGERROR, "AIRPLAY Server: unhandled request [%s]\n", uri.c_str());
1042 status = AIRPLAY_STATUS_NOT_IMPLEMENTED;
1045 if (status == AIRPLAY_STATUS_NEED_AUTH)
1047 ComposeAuthRequestAnswer(responseHeader, responseBody);