int APW_Callback_SetAVTransportURI ( APW instance, void * session, char * uri, char * metadata) { LogFun(); CStdString userAgent="AppleCoreMedia/1.0.0.8F455 (AppleTV; U; CPU OS 4_3 like Mac OS X; de_de)", location = uri; CURL::Encode(userAgent); location += "|User-Agent=" + userAgent; CFileItem fileToPlay(location, false); fileToPlay.SetProperty("StartPercent", (0.0f)); g_application.getApplicationMessenger().MediaPlay(fileToPlay); CLog::Log(LOGDEBUG, "AIRPLAY: SetAVTransportURI with uri = %s", location.c_str()); return 0; }
int CAirPlayServer::CTCPClient::ProcessRequest( CStdString& responseHeader, CStdString& responseBody, CStdString& reverseHeader, CStdString& reverseBody, CStdString& sessionId) { CStdString method = m_httpParser->getMethod(); CStdString uri = m_httpParser->getUri(); CStdString queryString = m_httpParser->getQueryString(); CStdString body = m_httpParser->getBody(); CStdString contentType = m_httpParser->getValue("content-type"); sessionId = m_httpParser->getValue("x-apple-session-id"); CStdString authorization = m_httpParser->getValue("authorization"); int status = AIRPLAY_STATUS_OK; bool needAuth = false; if (ServerInstance->m_usePassword && !m_bAuthenticated) { needAuth = true; } int startQs = uri.Find('?'); if (startQs != -1) { uri = uri.Left(startQs); } // This is the socket which will be used for reverse HTTP // negotiate reverse HTTP via upgrade if (uri == "/reverse") { status = AIRPLAY_STATUS_SWITCHING_PROTOCOLS; responseHeader = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n"; } // The rate command is used to play/pause media. // A value argument should be supplied which indicates media should be played or paused. // 0.000000 => pause // 1.000000 => play else if (uri == "/rate") { const char* found = strstr(queryString.c_str(), "value="); int rate = found ? (int)(atof(found + strlen("value=")) + 0.5f) : 0; CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with rate %i", uri.c_str(), rate); if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (rate == 0) { if (g_application.m_pPlayer && g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused()) { CApplicationMessenger::Get().MediaPause(); ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PAUSED); } } else { if (g_application.m_pPlayer && g_application.m_pPlayer->IsPlaying() && g_application.m_pPlayer->IsPaused()) { CApplicationMessenger::Get().MediaPause(); ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PLAYING); } } } // The volume command is used to change playback volume. // A value argument should be supplied which indicates how loud we should get. // 0.000000 => silent // 1.000000 => loud else if (uri == "/volume") { const char* found = strstr(queryString.c_str(), "volume="); float volume = found ? (float)strtod(found + strlen("volume="), NULL) : 0; CLog::Log(LOGDEBUG, "AIRPLAY: got request %s with volume %f", uri.c_str(), volume); if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (volume >= 0 && volume <= 1) { int oldVolume = g_application.GetVolume(); volume *= 100; if(oldVolume != (int)volume) { g_application.SetVolume(volume); CApplicationMessenger::Get().ShowVolumeBar(oldVolume < volume); } } } // Contains a header like format in the request body which should contain a // Content-Location and optionally a Start-Position else if (uri == "/play") { CStdString location; float position = 0.0; m_lastEvent = EVENT_NONE; CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str()); if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (contentType == "application/x-apple-binary-plist") { CAirPlayServer::m_isPlaying++; if (m_pLibPlist->Load()) { m_pLibPlist->EnableDelayedUnload(false); const char* bodyChr = m_httpParser->getBody(); plist_t dict = NULL; m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict); if (m_pLibPlist->plist_dict_get_size(dict)) { plist_t tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Start-Position"); if (tmpNode) { double tmpDouble = 0; m_pLibPlist->plist_get_real_val(tmpNode, &tmpDouble); position = (float)tmpDouble; } tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Content-Location"); if (tmpNode) { char *tmpStr = NULL; m_pLibPlist->plist_get_string_val(tmpNode, &tmpStr); location=tmpStr; #ifdef TARGET_WINDOWS m_pLibPlist->plist_free_string_val(tmpStr); #else free(tmpStr); #endif } if (dict) { m_pLibPlist->plist_free(dict); } } else { CLog::Log(LOGERROR, "Error parsing plist"); } m_pLibPlist->Unload(); } } else { CAirPlayServer::m_isPlaying++; // Get URL to play int start = body.Find("Content-Location: "); if (start == -1) return AIRPLAY_STATUS_NOT_IMPLEMENTED; start += strlen("Content-Location: "); int end = body.Find('\n', start); location = body.Mid(start, end - start); start = body.Find("Start-Position"); if (start != -1) { start += strlen("Start-Position: "); int end = body.Find('\n', start); CStdString positionStr = body.Mid(start, end - start); position = (float)atof(positionStr.c_str()); } } if (status != AIRPLAY_STATUS_NEED_AUTH) { CStdString userAgent="AppleCoreMedia/1.0.0.8F455 (AppleTV; U; CPU OS 4_3 like Mac OS X; de_de)"; CURL::Encode(userAgent); location += "|User-Agent=" + userAgent; CFileItem fileToPlay(location, false); fileToPlay.SetProperty("StartPercent", position*100.0f); CApplicationMessenger::Get().MediaPlay(fileToPlay); ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PLAYING); } } // Used to perform seeking (POST request) and to retrieve current player position (GET request). // GET scrub seems to also set rate 1 - strange but true else if (uri == "/scrub") { if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (method == "GET") { CLog::Log(LOGDEBUG, "AIRPLAY: got GET request %s", uri.c_str()); if (g_application.m_pPlayer && g_application.m_pPlayer->GetTotalTime()) { float position = ((float) g_application.m_pPlayer->GetTime()) / 1000; responseBody.Format("duration: %d\r\nposition: %f", g_application.m_pPlayer->GetTotalTime() / 1000, position); } else { status = AIRPLAY_STATUS_METHOD_NOT_ALLOWED; } } else { const char* found = strstr(queryString.c_str(), "position="); if (found && g_application.m_pPlayer) { int64_t position = (int64_t) (atof(found + strlen("position=")) * 1000.0); g_application.m_pPlayer->SeekTime(position); CLog::Log(LOGDEBUG, "AIRPLAY: got POST request %s with pos %"PRId64, uri.c_str(), position); } } } // Sent when media playback should be stopped else if (uri == "/stop") { CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str()); if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else { if (IsPlaying()) //only stop player if we started him { CApplicationMessenger::Get().MediaStop(); CAirPlayServer::m_isPlaying--; } else //if we are not playing and get the stop request - we just wanna stop picture streaming { g_windowManager.PreviousWindow(); } ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_STOPPED); } } // RAW JPEG data is contained in the request body else if (uri == "/photo") { CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str()); if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (m_httpParser->getContentLength() > 0) { XFILE::CFile tmpFile; CStdString tmpFileName = "special://temp/airplay_photo.jpg"; if( m_httpParser->getContentLength() > 3 && m_httpParser->getBody()[1] == 'P' && m_httpParser->getBody()[2] == 'N' && m_httpParser->getBody()[3] == 'G') { tmpFileName = "special://temp/airplay_photo.png"; } if (tmpFile.OpenForWrite(tmpFileName, true)) { int writtenBytes=0; writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength()); tmpFile.Close(); if (writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength()) { CApplicationMessenger::Get().PictureShow(tmpFileName); } else { CLog::Log(LOGERROR,"AirPlayServer: Error writing tmpFile."); } } } } else if (uri == "/playback-info") { float position = 0.0f; float duration = 0.0f; float cachePosition = 0.0f; bool playing = false; CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str()); if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (g_application.m_pPlayer) { if (g_application.m_pPlayer->GetTotalTime()) { position = ((float) g_application.m_pPlayer->GetTime()) / 1000; duration = ((float) g_application.m_pPlayer->GetTotalTime()) / 1000; playing = g_application.m_pPlayer ? !g_application.m_pPlayer->IsPaused() : false; cachePosition = position + (duration * g_application.m_pPlayer->GetCachePercentage() / 100.0f); } responseBody.Format(PLAYBACK_INFO, duration, cachePosition, position, (playing ? 1 : 0), duration); responseHeader = "Content-Type: text/x-apple-plist+xml\r\n"; if (g_application.m_pPlayer->IsCaching()) { ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_LOADING); } else if (playing) { ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PLAYING); } else { ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PAUSED); } } else { responseBody.Format(PLAYBACK_INFO_NOT_READY, duration, cachePosition, position, (playing ? 1 : 0), duration); responseHeader = "Content-Type: text/x-apple-plist+xml\r\n"; ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_STOPPED); } } else if (uri == "/server-info") { CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str()); responseBody.Format(SERVER_INFO, g_application.getNetwork().GetFirstConnectedInterface()->GetMacAddress()); responseHeader = "Content-Type: text/x-apple-plist+xml\r\n"; } else if (uri == "/slideshow-features") { // Ignore for now. } else if (uri == "/authorize") { // DRM, ignore for now. } else if (uri == "/setProperty") { status = AIRPLAY_STATUS_NOT_FOUND; } else if (uri == "/getProperty") { status = AIRPLAY_STATUS_NOT_FOUND; } else if (uri == "200") //response OK from the event reverse message { status = AIRPLAY_STATUS_NO_RESPONSE_NEEDED; } else { CLog::Log(LOGERROR, "AIRPLAY Server: unhandled request [%s]\n", uri.c_str()); status = AIRPLAY_STATUS_NOT_IMPLEMENTED; } if (status == AIRPLAY_STATUS_NEED_AUTH) { ComposeAuthRequestAnswer(responseHeader, responseBody); } return status; }
int CAirPlayServer::CTCPClient::ProcessRequest( CStdString& responseHeader, CStdString& responseBody, CStdString& reverseHeader, CStdString& reverseBody, CStdString& sessionId) { CStdString method = m_httpParser->getMethod(); CStdString uri = m_httpParser->getUri(); CStdString queryString = m_httpParser->getQueryString(); CStdString body = m_httpParser->getBody(); CStdString contentType = m_httpParser->getValue("content-type"); sessionId = m_httpParser->getValue("x-apple-session-id"); CStdString authorization = m_httpParser->getValue("authorization"); int status = AIRPLAY_STATUS_OK; bool needAuth = false; if (ServerInstance->m_usePassword && !m_bAuthenticated) { needAuth = true; } int startQs = uri.Find('?'); if (startQs != -1) { uri = uri.Left(startQs); } // This is the socket which will be used for reverse HTTP // negotiate reverse HTTP via upgrade if (uri == "/reverse") { status = AIRPLAY_STATUS_SWITCHING_PROTOCOLS; responseHeader = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n"; } // The rate command is used to play/pause media. // A value argument should be supplied which indicates media should be played or paused. // 0.000000 => pause // 1.000000 => play else if (uri == "/rate") { const char* found = strstr(queryString.c_str(), "value="); int rate = found ? (int)(atof(found + strlen("value=")) + 0.5f) : 0; if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (rate == 0) { if (g_application.m_pPlayer && g_application.m_pPlayer->IsPlaying() && !g_application.m_pPlayer->IsPaused()) { g_application.getApplicationMessenger().MediaPause(); } } else { if (g_application.m_pPlayer && g_application.m_pPlayer->IsPlaying() && g_application.m_pPlayer->IsPaused()) { g_application.getApplicationMessenger().MediaPause(); } } } // Contains a header like format in the request body which should contain a // Content-Location and optionally a Start-Position else if (uri == "/play") { CStdString location; float position = 0.0; if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (contentType == "application/x-apple-binary-plist") { if (m_pLibPlist->Load()) { m_pLibPlist->EnableDelayedUnload(false); const char* bodyChr = m_httpParser->getBody(); plist_t dict = NULL; m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict); if (m_pLibPlist->plist_dict_get_size(dict)) { plist_t tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Start-Position"); if (tmpNode) { double tmpDouble = 0; m_pLibPlist->plist_get_real_val(tmpNode, &tmpDouble); position = tmpDouble; } tmpNode = m_pLibPlist->plist_dict_get_item(dict, "Content-Location"); if (tmpNode) { char *tmpStr = NULL; m_pLibPlist->plist_get_string_val(tmpNode, &tmpStr); location=tmpStr; free(tmpStr); } if (dict) { m_pLibPlist->plist_free(dict); } } else { CLog::Log(LOGERROR, "Error parsing plist"); } m_pLibPlist->Unload(); } } else { // Get URL to play int start = body.Find("Content-Location: "); if (start == -1) return AIRPLAY_STATUS_NOT_IMPLEMENTED; start += strlen("Content-Location: "); int end = body.Find('\n', start); location = body.Mid(start, end - start); start = body.Find("Start-Position"); if (start != -1) { start += strlen("Start-Position: "); int end = body.Find('\n', start); CStdString positionStr = body.Mid(start, end - start); position = atof(positionStr.c_str()); } } if (status != AIRPLAY_STATUS_NEED_AUTH) { CFileItem fileToPlay(location, false); fileToPlay.SetProperty("StartPercent", position*100.0f); g_application.getApplicationMessenger().MediaPlay(fileToPlay); } } // Used to perform seeking (POST request) and to retrieve current player position (GET request). else if (uri == "/scrub") { if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (method == "GET") { if (g_application.m_pPlayer && g_application.m_pPlayer->GetTotalTime()) { float position = ((float) g_application.m_pPlayer->GetTime()) / 1000; responseBody.Format("duration: %d\r\nposition: %f", g_application.m_pPlayer->GetTotalTime(), position); } } else { const char* found = strstr(queryString.c_str(), "position="); if (found && g_application.m_pPlayer) { __int64 position = (__int64) (atof(found + strlen("position=")) * 1000.0); g_application.m_pPlayer->SeekTime(position); } } } // Sent when media playback should be stopped else if (uri == "/stop") { if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else { g_application.getApplicationMessenger().MediaStop(); } } // RAW JPEG data is contained in the request body else if (uri == "/photo") { if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (m_httpParser->getContentLength() > 0) { XFILE::CFile tmpFile; if (tmpFile.OpenForWrite("special://temp/airplay_photo.jpg", true)) { int writtenBytes=0; writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength()); tmpFile.Close(); if (writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength()) { g_application.getApplicationMessenger().PictureShow("special://temp/airplay_photo.jpg"); } else { CLog::Log(LOGERROR,"AirPlayServer: Error writing tmpFile."); } } } } else if (uri == "/playback-info") { float position = 0.0f; float duration = 0.0f; float cacheDuration = 0.0f; bool playing = false; if (needAuth && !checkAuthorization(authorization, method, uri)) { status = AIRPLAY_STATUS_NEED_AUTH; } else if (g_application.m_pPlayer) { if (g_application.m_pPlayer->GetTotalTime()) { position = ((float) g_application.m_pPlayer->GetTime()) / 1000; duration = (float) g_application.m_pPlayer->GetTotalTime(); playing = g_application.m_pPlayer ? !g_application.m_pPlayer->IsPaused() : false; cacheDuration = (float) g_application.m_pPlayer->GetTotalTime() * g_application.GetCachePercentage()/100.0f; } responseBody.Format(PLAYBACK_INFO, duration, cacheDuration, position, (playing ? 1 : 0), duration); responseHeader = "Content-Type: text/x-apple-plist+xml\r\n"; if (g_application.m_pPlayer->IsCaching()) { ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_LOADING); } else if (playing) { ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PLAYING); } else { ComposeReverseEvent(reverseHeader, reverseBody, sessionId, EVENT_PAUSED); } } } else if (uri == "/server-info") { responseBody.Format(SERVER_INFO, g_application.getNetwork().GetFirstConnectedInterface()->GetMacAddress()); responseHeader = "Content-Type: text/x-apple-plist+xml\r\n"; } else if (uri == "/slideshow-features") { // Ignore for now. } else if (uri == "/authorize") { // DRM, ignore for now. } else if (uri == "200") //response OK from the event reverse message { status = AIRPLAY_STATUS_NO_RESPONSE_NEEDED; } else { CLog::Log(LOGERROR, "AIRPLAY Server: unhandled request [%s]\n", uri.c_str()); status = AIRPLAY_STATUS_NOT_IMPLEMENTED; } if (status == AIRPLAY_STATUS_NEED_AUTH) { ComposeAuthRequestAnswer(responseHeader, responseBody); } return status; }