aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xlanguage/English/strings.po14
-rw-r--r--system/settings/settings.xml8
-rw-r--r--xbmc/network/AirPlayServer.cpp194
-rw-r--r--xbmc/network/AirTunesServer.cpp2
-rw-r--r--xbmc/network/DllLibPlist.h12
-rw-r--r--xbmc/network/NetworkServices.cpp15
6 files changed, 199 insertions, 46 deletions
diff --git a/language/English/strings.po b/language/English/strings.po
index bb225cc21d..b34820606f 100755
--- a/language/English/strings.po
+++ b/language/English/strings.po
@@ -3761,9 +3761,13 @@ msgctxt "#1260"
msgid "Announce these services to other systems via Zeroconf"
msgstr ""
-#empty strings from id 1261 to 1268
+#empty strings from id 1261 to 1267
#: system/settings/settings.xml
+msgctxt "#1268"
+msgid "iOS 8 compatibility mode"
+msgstr ""
+
msgctxt "#1269"
msgid "Allow volume control"
msgstr ""
@@ -15670,7 +15674,13 @@ msgctxt "#36548"
msgid "Limits resolution of GUI to save memory. Does not affect video playback. Requires restart."
msgstr ""
-#empty strings from id 36549 to 36599
+#. Description of setting "Services -> AirPlay -> iOS 8 compatibility mode" with label #1268
+#: system/settings/settings.xml
+msgctxt "#36549"
+msgid "Use iOS8 compatible AirPlay support. If you have trouble with older iOS devices detecting Kodi as a valid target try switching this off. This option takes effect on the next restart of Kodi only!"
+msgstr ""
+
+#empty strings from id 36550 to 36599
#reserved strings 365XX
#. Description of settings category "Music -> Library" with label #14022
diff --git a/system/settings/settings.xml b/system/settings/settings.xml
index a5aa0cd6eb..d8736c3a8d 100644
--- a/system/settings/settings.xml
+++ b/system/settings/settings.xml
@@ -2202,6 +2202,14 @@
<hidden>true</hidden>
</control>
</setting>
+ <setting id="services.airplayios8compat" type="boolean" parent="services.airplay" label="1268" help="36549">
+ <level>3</level>
+ <default>true</default>
+ <dependencies>
+ <dependency type="enable" setting="services.airplay">true</dependency>
+ </dependencies>
+ <control type="toggle" />
+ </setting>
</group>
</category>
<category id="smb" label="1200" help="36346">
diff --git a/xbmc/network/AirPlayServer.cpp b/xbmc/network/AirPlayServer.cpp
index 09f7bd4523..f9e04a0d0a 100644
--- a/xbmc/network/AirPlayServer.cpp
+++ b/xbmc/network/AirPlayServer.cpp
@@ -33,6 +33,7 @@
#include "utils/StringUtils.h"
#include "threads/SingleLock.h"
#include "filesystem/File.h"
+#include "filesystem/Directory.h"
#include "FileItem.h"
#include "Application.h"
#include "ApplicationMessenger.h"
@@ -61,6 +62,7 @@ using namespace std;
#define AIRPLAY_STATUS_NEED_AUTH 401
#define AIRPLAY_STATUS_NOT_FOUND 404
#define AIRPLAY_STATUS_METHOD_NOT_ALLOWED 405
+#define AIRPLAY_STATUS_PRECONDITION_FAILED 412
#define AIRPLAY_STATUS_NOT_IMPLEMENTED 501
#define AIRPLAY_STATUS_NO_RESPONSE_NEEDED 1000
@@ -219,9 +221,34 @@ bool CAirPlayServer::SetInternalCredentials(bool usePassword, const std::string&
return true;
}
+void ClearPhotoAssetCache()
+{
+ CLog::Log(LOGINFO, "AIRPLAY: Cleaning up photoassetcache");
+ // remove all cached photos
+ CFileItemList items;
+ XFILE::CDirectory::GetDirectory("special://temp/", items);
+
+ for (int i = 0; i < items.Size(); ++i)
+ {
+ CFileItemPtr pItem = items[i];
+ if (!pItem->m_bIsFolder)
+ {
+ if (StringUtils::StartsWithNoCase(pItem->GetLabel(), "airplayasset") &&
+ (StringUtils::EndsWithNoCase(pItem->GetLabel(), ".jpg") ||
+ StringUtils::EndsWithNoCase(pItem->GetLabel(), ".png") ))
+ {
+ XFILE::CFile::Delete(pItem->GetPath());
+ }
+ }
+ }
+}
+
void CAirPlayServer::StopServer(bool bWait)
{
CSingleLock lock(ServerInstanceLock);
+ //clean up the photo cache temp folder
+ ClearPhotoAssetCache();
+
if (ServerInstance)
{
ServerInstance->StopThread(bWait);
@@ -495,6 +522,9 @@ void CAirPlayServer::CTCPClient::PushBuffer(CAirPlayServer *host, const char *bu
case AIRPLAY_STATUS_METHOD_NOT_ALLOWED:
statusMsg = "Method Not Allowed";
break;
+ case AIRPLAY_STATUS_PRECONDITION_FAILED:
+ statusMsg = "Precondition Failed";
+ break;
}
// Prepare the response
@@ -711,6 +741,29 @@ void CAirPlayServer::restoreVolume()
}
}
+void dumpPlist(DllLibPlist *pLibPlist, plist_t *dict)
+{
+ char *plist = NULL;
+ uint32_t len = 0;
+ pLibPlist->plist_to_xml(*dict,&plist, &len);
+ CLog::Log(LOGDEBUG, "AIRPLAY-DUMP: %s", plist);
+
+}
+
+std::string getStringFromPlist(DllLibPlist *pLibPlist,plist_t node)
+{
+ std::string ret;
+ char *tmpStr = NULL;
+ pLibPlist->plist_get_string_val(node, &tmpStr);
+ ret = tmpStr;
+#ifdef TARGET_WINDOWS
+ pLibPlist->plist_free_string_val(tmpStr);
+#else
+ free(tmpStr);
+#endif
+ return ret;
+}
+
int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
std::string& responseBody)
{
@@ -721,6 +774,9 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
std::string contentType = m_httpParser->getValue("content-type") ? m_httpParser->getValue("content-type") : "";
m_sessionId = m_httpParser->getValue("x-apple-session-id") ? m_httpParser->getValue("x-apple-session-id") : "";
std::string authorization = m_httpParser->getValue("authorization") ? m_httpParser->getValue("authorization") : "";
+ std::string photoAction = m_httpParser->getValue("x-apple-assetaction") ? m_httpParser->getValue("x-apple-assetaction") : "";
+ std::string photoCacheId = m_httpParser->getValue("x-apple-assetkey") ? m_httpParser->getValue("x-apple-assetkey") : "";
+
int status = AIRPLAY_STATUS_OK;
bool needAuth = false;
@@ -812,6 +868,7 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
{
std::string location;
float position = 0.0;
+ bool startPlayback = true;
m_lastEvent = EVENT_NONE;
CLog::Log(LOGDEBUG, "AIRPLAY: got request %s", uri.c_str());
@@ -820,7 +877,7 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
{
status = AIRPLAY_STATUS_NEED_AUTH;
}
- else if (contentType == "application/x-apple-binary-plist")
+ else
{
CAirPlayServer::m_isPlaying++;
@@ -831,7 +888,11 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
const char* bodyChr = m_httpParser->getBody();
plist_t dict = NULL;
- m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict);
+ if (contentType == "application/x-apple-binary-plist")
+ m_pLibPlist->plist_from_bin(bodyChr, m_httpParser->getContentLength(), &dict);
+ else
+ m_pLibPlist->plist_from_xml(bodyChr, m_httpParser->getContentLength(), &dict);
+
if (m_pLibPlist->plist_dict_get_size(dict))
{
@@ -846,14 +907,36 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
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
+ location = getStringFromPlist(m_pLibPlist, tmpNode);
+ tmpNode = NULL;
+ }
+
+ tmpNode = m_pLibPlist->plist_dict_get_item(dict, "rate");
+ if (tmpNode)
+ {
+ double rate = 0;
+ m_pLibPlist->plist_get_real_val(tmpNode, &rate);
+ if (rate == 0.0)
+ {
+ startPlayback = false;
+ }
+ tmpNode = NULL;
+ }
+
+ // in newer protocol versions the location is given
+ // via host and path where host is ip:port and path is /path/file.mov
+ if (location.empty())
+ tmpNode = m_pLibPlist->plist_dict_get_item(dict, "host");
+ if (tmpNode)
+ {
+ location = "http://";
+ location += getStringFromPlist(m_pLibPlist, tmpNode);
+
+ tmpNode = m_pLibPlist->plist_dict_get_item(dict, "path");
+ if (tmpNode)
+ {
+ location += getStringFromPlist(m_pLibPlist, tmpNode);
+ }
}
if (dict)
@@ -868,28 +951,6 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
m_pLibPlist->Unload();
}
}
- else
- {
- CAirPlayServer::m_isPlaying++;
- // Get URL to play
- std::string contentLocation = "Content-Location: ";
- size_t start = body.find(contentLocation);
- if (start == std::string::npos)
- return AIRPLAY_STATUS_NOT_IMPLEMENTED;
- start += contentLocation.size();
- int end = body.find('\n', start);
- location = body.substr(start, end - start);
-
- std::string startPosition = "Start-Position: ";
- start = body.find(startPosition);
- if (start != std::string::npos)
- {
- start += startPosition.size();
- int end = body.find('\n', start);
- std::string positionStr = body.substr(start, end - start);
- position = (float)atof(positionStr.c_str());
- }
- }
if (status != AIRPLAY_STATUS_NEED_AUTH)
{
@@ -903,6 +964,13 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
// one who will work well with airplay
g_application.m_eForcedNextPlayer = EPC_DVDPLAYER;
CApplicationMessenger::Get().MediaPlay(fileToPlay);
+
+ // allow starting the player paused in ios8 mode (needed by camera roll app)
+ if (CSettings::Get().GetBool("services.airplayios8compat") && !startPlayback)
+ {
+ CApplicationMessenger::Get().MediaPause();
+ g_application.m_pPlayer->SeekPercentage(position * 100.0f);
+ }
}
}
@@ -961,6 +1029,7 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
CApplicationMessenger::Get().SendAction(ACTION_PREVIOUS_MENU);
}
}
+ ClearPhotoAssetCache();
}
// RAW JPEG data is contained in the request body
@@ -971,28 +1040,64 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
{
status = AIRPLAY_STATUS_NEED_AUTH;
}
- else if (m_httpParser->getContentLength() > 0)
+ else if (m_httpParser->getContentLength() > 0 || photoAction == "displayCached")
{
XFILE::CFile tmpFile;
- std::string tmpFileName = "special://temp/airplay_photo.jpg";
+ std::string tmpFileName = "special://temp/airplayasset";
+ bool showPhoto = true;
+ bool receivePhoto = true;
- if( m_httpParser->getContentLength() > 3 &&
+
+ if (photoAction == "cacheOnly")
+ showPhoto = false;
+ else if (photoAction == "displayCached")
+ {
+ receivePhoto = false;
+ if (photoCacheId.length())
+ CLog::Log(LOGDEBUG, "AIRPLAY: Trying to show from cache asset: %s", photoCacheId.c_str());
+ }
+
+ if (photoCacheId.length())
+ tmpFileName += photoCacheId;
+ else
+ tmpFileName += "airplay_photo";
+
+ if( receivePhoto && 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";
+ tmpFileName += ".png";
+ }
+ else
+ {
+ tmpFileName += ".jpg";
}
- if (tmpFile.OpenForWrite(tmpFileName, true))
+ int writtenBytes=0;
+ if (receivePhoto)
{
- int writtenBytes=0;
- writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength());
- tmpFile.Close();
+ if (tmpFile.OpenForWrite(tmpFileName, true))
+ {
+ writtenBytes = tmpFile.Write(m_httpParser->getBody(), m_httpParser->getContentLength());
+ tmpFile.Close();
+ }
+ if (photoCacheId.length())
+ CLog::Log(LOGDEBUG, "AIRPLAY: Cached asset: %s", photoCacheId.c_str());
+ }
- if (writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength())
+ if (showPhoto)
+ {
+ if ((writtenBytes > 0 && (unsigned int)writtenBytes == m_httpParser->getContentLength()) || !receivePhoto)
{
- CApplicationMessenger::Get().PictureShow(tmpFileName);
+ if (!receivePhoto && !XFILE::CFile::Exists(tmpFileName))
+ {
+ status = AIRPLAY_STATUS_PRECONDITION_FAILED; //image not found in the cache
+ if (photoCacheId.length())
+ CLog::Log(LOGWARNING, "AIRPLAY: Asset %s not found in our cache.", photoCacheId.c_str());
+ }
+ else
+ CApplicationMessenger::Get().PictureShow(tmpFileName);
}
else
{
@@ -1065,6 +1170,11 @@ int CAirPlayServer::CTCPClient::ProcessRequest( std::string& responseHeader,
else if (uri == "/getProperty")
{
status = AIRPLAY_STATUS_NOT_FOUND;
+ }
+
+ else if (uri == "/fp-setup")
+ {
+ status = AIRPLAY_STATUS_PRECONDITION_FAILED;
}
else if (uri == "200") //response OK from the event reverse message
diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp
index 4f29b48b21..4f52e2d872 100644
--- a/xbmc/network/AirTunesServer.cpp
+++ b/xbmc/network/AirTunesServer.cpp
@@ -389,9 +389,9 @@ bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, con
txt.push_back(std::make_pair("pw", usePassword?"true":"false"));
txt.push_back(std::make_pair("vn", "3"));
txt.push_back(std::make_pair("da", "true"));
- txt.push_back(std::make_pair("vs", "130.14"));
txt.push_back(std::make_pair("md", "0,1,2"));
txt.push_back(std::make_pair("am", "Xbmc,1"));
+ txt.push_back(std::make_pair("vs", "130.14"));
CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt);
}
diff --git a/xbmc/network/DllLibPlist.h b/xbmc/network/DllLibPlist.h
index 94511ac28f..a6aa7c424c 100644
--- a/xbmc/network/DllLibPlist.h
+++ b/xbmc/network/DllLibPlist.h
@@ -30,6 +30,7 @@ public:
virtual ~DllLibPlistInterface() {}
virtual void plist_from_bin (const char *plist_bin, uint32_t length, plist_t * plist )=0;
+ virtual void plist_from_xml (const char *plist_xml, uint32_t length, plist_t * plist )=0;
virtual plist_t plist_new_dict (void )=0;
virtual uint32_t plist_dict_get_size (plist_t node )=0;
virtual void plist_get_string_val (plist_t node, char **val )=0;
@@ -39,6 +40,9 @@ public:
#ifdef TARGET_WINDOWS
virtual void plist_free_string_val (char *val )=0;
#endif
+ virtual void plist_to_xml (plist_t plist, char **plist_xml, uint32_t * length)=0;
+ virtual void plist_dict_new_iter (plist_t node, plist_dict_iter *iter )=0;
+ virtual void plist_dict_next_item (plist_t node, plist_dict_iter iter, char **key, plist_t *val) = 0;
};
@@ -50,8 +54,12 @@ class DllLibPlist : public DllDynamic, DllLibPlistInterface
DEFINE_METHOD1(void, plist_free, (plist_t p1))
DEFINE_METHOD2(void, plist_get_string_val, (plist_t p1, char **p2))
DEFINE_METHOD2(void, plist_get_real_val, (plist_t p1, double *p2))
+ DEFINE_METHOD2(void, plist_dict_new_iter, (plist_t p1, plist_dict_iter* p2))
DEFINE_METHOD2(plist_t, plist_dict_get_item, (plist_t p1, const char* p2))
DEFINE_METHOD3(void, plist_from_bin, (const char *p1, uint32_t p2, plist_t *p3))
+ DEFINE_METHOD3(void, plist_from_xml, (const char *p1, uint32_t p2, plist_t *p3))
+ DEFINE_METHOD3(void, plist_to_xml, (plist_t p1, char **p2, uint32_t *p3));
+ DEFINE_METHOD4(void, plist_dict_next_item, (plist_t p1, plist_dict_iter p2, char **p3, plist_t *p4))
#ifdef TARGET_WINDOWS
DEFINE_METHOD1(void, plist_free_string_val, (char *p1))
#endif
@@ -62,9 +70,13 @@ class DllLibPlist : public DllDynamic, DllLibPlistInterface
RESOLVE_METHOD_RENAME(plist_free, plist_free)
RESOLVE_METHOD_RENAME(plist_dict_get_size, plist_dict_get_size)
RESOLVE_METHOD_RENAME(plist_from_bin, plist_from_bin)
+ RESOLVE_METHOD_RENAME(plist_from_xml, plist_from_xml)
RESOLVE_METHOD_RENAME(plist_get_real_val, plist_get_real_val)
RESOLVE_METHOD_RENAME(plist_get_string_val, plist_get_string_val)
RESOLVE_METHOD_RENAME(plist_dict_get_item, plist_dict_get_item)
+ RESOLVE_METHOD_RENAME(plist_dict_new_iter, plist_dict_new_iter)
+ RESOLVE_METHOD_RENAME(plist_dict_next_item, plist_dict_next_item)
+ RESOLVE_METHOD_RENAME(plist_to_xml, plist_to_xml)
#ifdef TARGET_WINDOWS
RESOLVE_METHOD_RENAME(plist_free_string_val, plist_free_string_val)
#endif
diff --git a/xbmc/network/NetworkServices.cpp b/xbmc/network/NetworkServices.cpp
index 3dc2a27099..519c27ece4 100644
--- a/xbmc/network/NetworkServices.cpp
+++ b/xbmc/network/NetworkServices.cpp
@@ -530,9 +530,22 @@ bool CNetworkServices::StartAirPlayServer()
std::vector<std::pair<std::string, std::string> > txt;
CNetworkInterface* iface = g_application.getNetwork().GetFirstConnectedInterface();
txt.push_back(make_pair("deviceid", iface != NULL ? iface->GetMacAddress() : "FF:FF:FF:FF:FF:F2"));
- txt.push_back(make_pair("features", "0x77"));
txt.push_back(make_pair("model", "Xbmc,1"));
txt.push_back(make_pair("srcvers", AIRPLAY_SERVER_VERSION_STR));
+
+ if (CSettings::Get().GetBool("services.airplayios8compat"))
+ {
+ // for ios8 clients we need to announce mirroring support
+ // else we won't get video urls anymore.
+ // We also announce photo caching support (as it seems faster and
+ // we have implemented it anyways).
+ txt.push_back(make_pair("features", "0x20F7"));
+ }
+ else
+ {
+ txt.push_back(make_pair("features", "0x77"));
+ }
+
CZeroconf::GetInstance()->PublishService("servers.airplay", "_airplay._tcp", g_infoManager.GetLabel(SYSTEM_FRIENDLY_NAME), g_advancedSettings.m_airPlayPort, txt);
#endif // HAS_ZEROCONF