| @@ -0,0 +1,527 @@ | ||
| From b73018af2ae69c7cfad0a4461d169a49c7d0dfbf Mon Sep 17 00:00:00 2001 | ||
| From: Memphiz <memphis@machzwo.de> | ||
| Date: Tue, 15 May 2012 19:12:07 +0200 | ||
| Subject: [PATCH] [airtunes] - implementation for windows using libshairplay | ||
|
|
||
| --- | ||
| xbmc/network/AirTunesServer.cpp | 330 +++++++++++++++++++++++++++++++++++++-- | ||
| xbmc/network/AirTunesServer.h | 23 +++ | ||
| 2 files changed, 343 insertions(+), 10 deletions(-) | ||
|
|
||
| diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp | ||
| index a60ad09..e14da90 100644 | ||
| --- a/xbmc/network/AirTunesServer.cpp | ||
| +++ b/xbmc/network/AirTunesServer.cpp | ||
| @@ -17,7 +17,9 @@ | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| */ | ||
|
|
||
| +#if !defined(TARGET_WINDOWS) | ||
| #pragma GCC diagnostic ignored "-Wwrite-strings" | ||
| +#endif | ||
|
|
||
| #include "AirTunesServer.h" | ||
|
|
||
| @@ -37,15 +39,243 @@ | ||
| #include "music/tags/MusicInfoTag.h" | ||
| #include "FileItem.h" | ||
| #include "GUIInfoManager.h" | ||
| +#include "guilib/GUIWindowManager.h" | ||
| #include "utils/Variant.h" | ||
| #include "settings/AdvancedSettings.h" | ||
| +#include "utils/EndianSwap.h" | ||
| + | ||
| +#include <map> | ||
| +#include <string> | ||
| + | ||
|
|
||
| using namespace XFILE; | ||
|
|
||
| +#if defined(TARGET_WINDOWS) | ||
| +DllLibShairplay *CAirTunesServer::m_pLibShairplay = NULL; | ||
| +#else | ||
| DllLibShairport *CAirTunesServer::m_pLibShairport = NULL; | ||
| +#endif | ||
| CAirTunesServer *CAirTunesServer::ServerInstance = NULL; | ||
| CStdString CAirTunesServer::m_macAddress; | ||
|
|
||
| +//parse daap metadata - thx to project MythTV | ||
| +std::map<std::string, std::string> decodeDMAP(const char *buffer, unsigned int size) | ||
| +{ | ||
| + std::map<std::string, std::string> result; | ||
| + unsigned int offset = 8; | ||
| + while (offset < size) | ||
| + { | ||
| + std::string tag; | ||
| + tag.append(buffer + offset, 4); | ||
| + offset += 4; | ||
| + uint32_t length = Endian_SwapBE32(*(uint32_t *)(buffer + offset)); | ||
| + offset += sizeof(uint32_t); | ||
| + std::string content; | ||
| + content.append(buffer + offset, length);//possible fixme - utf8? | ||
| + offset += length; | ||
| + result[tag] = content; | ||
| + } | ||
| + return result; | ||
| +} | ||
| + | ||
| +void CAirTunesServer::SetMetadataFromBuffer(const char *buffer, unsigned int size) | ||
| +{ | ||
| + MUSIC_INFO::CMusicInfoTag tag; | ||
| + std::map<std::string, std::string> metadata = decodeDMAP(buffer, size); | ||
| + if(metadata["asal"].length()) | ||
| + tag.SetAlbum(metadata["asal"]);//album | ||
| + if(metadata["minm"].length()) | ||
| + tag.SetTitle(metadata["minm"]);//title | ||
| + if(metadata["asar"].length()) | ||
| + tag.SetArtist(metadata["asar"]);//artist | ||
| + g_infoManager.SetCurrentSongTag(tag); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::SetCoverArtFromBuffer(const char *buffer, unsigned int size) | ||
| +{ | ||
| + XFILE::CFile tmpFile; | ||
| + const char *tmpFileName = "special://temp/airtunes_album_thumb.jpg"; | ||
| + | ||
| + if(!size) | ||
| + return; | ||
| + | ||
| + if (tmpFile.OpenForWrite(tmpFileName, true)) | ||
| + { | ||
| + int writtenBytes=0; | ||
| + writtenBytes = tmpFile.Write(buffer, size); | ||
| + tmpFile.Close(); | ||
| + | ||
| + if(writtenBytes) | ||
| + { | ||
| + //reset to empty before setting the new one | ||
| + //else it won't get refreshed because the name didn't change | ||
| + g_infoManager.SetCurrentAlbumThumb(""); | ||
| + g_infoManager.SetCurrentAlbumThumb(tmpFileName); | ||
| + //update the ui | ||
| + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_REFRESH_THUMBS); | ||
| + g_windowManager.SendThreadMessage(msg); | ||
| + } | ||
| + } | ||
| +} | ||
| + | ||
| +#if defined(TARGET_WINDOWS) | ||
| +#define RSA_KEY " \ | ||
| +-----BEGIN RSA PRIVATE KEY-----\ | ||
| +MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\ | ||
| +wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\ | ||
| +wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\ | ||
| +/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\ | ||
| +UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\ | ||
| +BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\ | ||
| +LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\ | ||
| +NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\ | ||
| +lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\ | ||
| +aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\ | ||
| +a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\ | ||
| +oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\ | ||
| +oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\ | ||
| +k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\ | ||
| +AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\ | ||
| +cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\ | ||
| +54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\ | ||
| +17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\ | ||
| +1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\ | ||
| +LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\ | ||
| +2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\ | ||
| +-----END RSA PRIVATE KEY-----" | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_set_metadata(void *cls, void *session, const void *buffer, int buflen) | ||
| +{ | ||
| + CAirTunesServer::SetMetadataFromBuffer((char *)buffer, buflen); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_set_coverart(void *cls, void *session, const void *buffer, int buflen) | ||
| +{ | ||
| + CAirTunesServer::SetCoverArtFromBuffer((char *)buffer, buflen); | ||
| +} | ||
| + | ||
| +void* CAirTunesServer::AudioOutputFunctions::audio_init(void *cls, int bits, int channels, int samplerate) | ||
| +{ | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName()); | ||
| + pipe->SetOpenThreashold(300); | ||
| + | ||
| + BXA_FmtHeader header; | ||
| + strncpy(header.fourcc, "BXA ", 4); | ||
| + header.type = BXA_PACKET_TYPE_FMT; | ||
| + header.bitsPerSample = bits; | ||
| + header.channels = channels; | ||
| + header.sampleRate = samplerate; | ||
| + header.durationMs = 0; | ||
| + | ||
| + if (pipe->Write(&header, sizeof(header)) == 0) | ||
| + return 0; | ||
| + | ||
| + ThreadMessage tMsg = { TMSG_MEDIA_STOP }; | ||
| + g_application.getApplicationMessenger().SendMessage(tMsg, true); | ||
| + | ||
| + CFileItem item; | ||
| + item.SetPath(pipe->GetName()); | ||
| + item.SetMimeType("audio/x-xbmc-pcm"); | ||
| + | ||
| + ThreadMessage tMsg2 = { TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VISUALISATION, 0 }; | ||
| + g_application.getApplicationMessenger().SendMessage(tMsg2, true); | ||
| + | ||
| + g_application.getApplicationMessenger().PlayFile(item); | ||
| + | ||
| + return "XBMC-AirTunes";//session | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_set_volume(void *cls, void *session, float volume) | ||
| +{ | ||
| + //volume from -144 - 0 | ||
| + float volPercent = 1 - volume/-144; | ||
| + g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0 | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_process(void *cls, void *session, const void *buffer, int buflen) | ||
| +{ | ||
| + #define NUM_OF_BYTES 64 | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + int sentBytes = 0; | ||
| + unsigned char buf[NUM_OF_BYTES]; | ||
| + | ||
| + while (sentBytes < buflen) | ||
| + { | ||
| + int n = (buflen - sentBytes < NUM_OF_BYTES ? buflen - sentBytes : NUM_OF_BYTES); | ||
| + memcpy(buf, (char*) buffer + sentBytes, n); | ||
| + | ||
| + if (pipe->Write(buf, n) == 0) | ||
| + return; | ||
| + | ||
| + sentBytes += n; | ||
| + } | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_flush(void *cls, void *session) | ||
| +{ | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + pipe->Flush(); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_destroy(void *cls, void *session) | ||
| +{ | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + pipe->SetEof(); | ||
| + pipe->Close(); | ||
| + | ||
| + //fix airplay video for ios5 devices | ||
| + //on ios5 when airplaying video | ||
| + //the client first opens an airtunes stream | ||
| + //while the movie is loading | ||
| + //in that case we don't want to stop the player here | ||
| + //because this would stop the airplaying video | ||
| +#ifdef HAS_AIRPLAY | ||
| + if (!CAirPlayServer::IsPlaying()) | ||
| +#endif | ||
| + { | ||
| + ThreadMessage tMsg = { TMSG_MEDIA_STOP }; | ||
| + g_application.getApplicationMessenger().SendMessage(tMsg, true); | ||
| + CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player"); | ||
| + } | ||
| +} | ||
| + | ||
| +void shairplay_log(int level, const char *msg) | ||
| +{ | ||
| + int xbmcLevel = LOGINFO; | ||
| + | ||
| + switch(level) | ||
| + { | ||
| + case RAOP_LOG_EMERG: // system is unusable | ||
| + xbmcLevel = LOGFATAL; | ||
| + break; | ||
| + case RAOP_LOG_ALERT: // action must be taken immediately | ||
| + case RAOP_LOG_CRIT: // critical conditions | ||
| + xbmcLevel = LOGSEVERE; | ||
| + break; | ||
| + case RAOP_LOG_ERR: // error conditions | ||
| + xbmcLevel = LOGERROR; | ||
| + break; | ||
| + case RAOP_LOG_WARNING: // warning conditions | ||
| + xbmcLevel = LOGWARNING; | ||
| + break; | ||
| + case RAOP_LOG_NOTICE: // normal but significant condition | ||
| + xbmcLevel = LOGNOTICE; | ||
| + break; | ||
| + case RAOP_LOG_INFO: // informational | ||
| + xbmcLevel = LOGINFO; | ||
| + break; | ||
| + case RAOP_LOG_DEBUG: // debug-level messages | ||
| + xbmcLevel = LOGDEBUG; | ||
| + break; | ||
| + default: | ||
| + break; | ||
| + } | ||
| + CLog::Log(xbmcLevel, "AIRTUNES: %s", msg); | ||
| +} | ||
| + | ||
| +#else | ||
| + | ||
| struct ao_device_xbmc | ||
| { | ||
| XFILE::CPipeFile *pipe; | ||
| @@ -214,6 +444,17 @@ char* CAirTunesServer::AudioOutputFunctions::ao_get_option(ao_option *options, c | ||
| return NULL; | ||
| } | ||
|
|
||
| +int shairport_log(const char* msg, size_t msgSize) | ||
| +{ | ||
| + if( g_advancedSettings.m_logEnableAirtunes) | ||
| + { | ||
| + CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg); | ||
| + } | ||
| + return 1; | ||
| +} | ||
| + | ||
| +#endif | ||
| + | ||
| bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password/*=""*/) | ||
| { | ||
| bool success = false; | ||
| @@ -243,7 +484,9 @@ bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, con | ||
| ServerInstance = new CAirTunesServer(port, nonlocal); | ||
| if (ServerInstance->Initialize(password)) | ||
| { | ||
| +#ifndef TARGET_WINDOWS | ||
| ServerInstance->Create(); | ||
| +#endif | ||
| success = true; | ||
| } | ||
|
|
||
| @@ -264,6 +507,9 @@ bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, con | ||
| txt["sr"] = "44100"; | ||
| txt["pw"] = "false"; | ||
| txt["vn"] = "3"; | ||
| + txt["da"] = "true"; | ||
| + txt["vs"] = "130.14"; | ||
| + txt["md"] = "0,1,2"; | ||
| txt["txtvers"] = "1"; | ||
|
|
||
| CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt); | ||
| @@ -276,10 +522,12 @@ void CAirTunesServer::StopServer(bool bWait) | ||
| { | ||
| if (ServerInstance) | ||
| { | ||
| +#if !defined(TARGET_WINDOWS) | ||
| if (m_pLibShairport->IsLoaded()) | ||
| { | ||
| m_pLibShairport->shairport_exit(); | ||
| } | ||
| +#endif | ||
| ServerInstance->StopThread(bWait); | ||
| ServerInstance->Deinitialize(); | ||
| if (bWait) | ||
| @@ -295,47 +543,98 @@ void CAirTunesServer::StopServer(bool bWait) | ||
| CAirTunesServer::CAirTunesServer(int port, bool nonlocal) : CThread("AirTunesServer") | ||
| { | ||
| m_port = port; | ||
| +#if defined(TARGET_WINDOWS) | ||
| + m_pLibShairplay = new DllLibShairplay(); | ||
| + m_pPipe = new XFILE::CPipeFile; | ||
| +#else | ||
| m_pLibShairport = new DllLibShairport(); | ||
| +#endif | ||
| } | ||
|
|
||
| CAirTunesServer::~CAirTunesServer() | ||
| { | ||
| +#if defined(TARGET_WINDOWS) | ||
| + if (m_pLibShairplay->IsLoaded()) | ||
| + { | ||
| + m_pLibShairplay->Unload(); | ||
| + } | ||
| + delete m_pLibShairplay; | ||
| + delete m_pPipe; | ||
| +#else | ||
| if (m_pLibShairport->IsLoaded()) | ||
| { | ||
| m_pLibShairport->Unload(); | ||
| } | ||
| delete m_pLibShairport; | ||
| +#endif | ||
| } | ||
|
|
||
| void CAirTunesServer::Process() | ||
| { | ||
| m_bStop = false; | ||
|
|
||
| +#if !defined(TARGET_WINDOWS) | ||
| while (!m_bStop && m_pLibShairport->shairport_is_running()) | ||
| { | ||
| m_pLibShairport->shairport_loop(); | ||
| } | ||
| +#endif | ||
| } | ||
|
|
||
| -int shairport_log(const char* msg, size_t msgSize) | ||
| +bool CAirTunesServer::Initialize(const CStdString &password) | ||
| { | ||
| - if( g_advancedSettings.m_logEnableAirtunes) | ||
| + bool ret = false; | ||
| + | ||
| + Deinitialize(); | ||
| + | ||
| +#if defined(TARGET_WINDOWS) | ||
| + if (m_pLibShairplay->Load()) | ||
| { | ||
| - CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg); | ||
| + | ||
| + raop_callbacks_t ao; | ||
| + ao.cls = m_pPipe; | ||
| + ao.audio_init = AudioOutputFunctions::audio_init; | ||
| + ao.audio_set_volume = AudioOutputFunctions::audio_set_volume; | ||
| + ao.audio_set_metadata = AudioOutputFunctions::audio_set_metadata; | ||
| + ao.audio_set_coverart = AudioOutputFunctions::audio_set_coverart; | ||
| + ao.audio_process = AudioOutputFunctions::audio_process; | ||
| + ao.audio_flush = AudioOutputFunctions::audio_flush; | ||
| + ao.audio_destroy = AudioOutputFunctions::audio_destroy; | ||
| + m_pLibShairplay->EnableDelayedUnload(false); | ||
| + m_pRaop = m_pLibShairplay->raop_init(1, &ao, RSA_KEY);//1 - we handle one client at a time max | ||
| + ret = m_pRaop != NULL; | ||
| + | ||
| + if(ret) | ||
| + { | ||
| + char macAdr[6]; | ||
| + unsigned short port = (unsigned short)m_port; | ||
| + | ||
| + m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_WARNING); | ||
| + if(g_advancedSettings.m_logEnableAirtunes) | ||
| + { | ||
| + m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_DEBUG); | ||
| + } | ||
| + | ||
| + m_pLibShairplay->raop_set_log_callback(m_pRaop, shairplay_log); | ||
| + | ||
| + CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface(); | ||
| + | ||
| + if (net) | ||
| + { | ||
| + net->GetMacAddressRaw(macAdr); | ||
| + } | ||
| + | ||
| + ret = m_pLibShairplay->raop_start(m_pRaop, &port, macAdr, 6, password.c_str()) >= 0; | ||
| + } | ||
| } | ||
| - return 1; | ||
| -} | ||
|
|
||
| -bool CAirTunesServer::Initialize(const CStdString &password) | ||
| -{ | ||
| - bool ret = false; | ||
| +#else | ||
| + | ||
| int numArgs = 3; | ||
| CStdString hwStr; | ||
| CStdString pwStr; | ||
| CStdString portStr; | ||
|
|
||
| - Deinitialize(); | ||
| - | ||
| hwStr.Format("--mac=%s", m_macAddress.c_str()); | ||
| pwStr.Format("--password=%s",password.c_str()); | ||
| portStr.Format("--server_port=%d",m_port); | ||
| @@ -368,16 +667,27 @@ bool CAirTunesServer::Initialize(const CStdString &password) | ||
| m_pLibShairport->shairport_main(numArgs, argv); | ||
| ret = true; | ||
| } | ||
| +#endif | ||
| return ret; | ||
| } | ||
|
|
||
| void CAirTunesServer::Deinitialize() | ||
| { | ||
| +#if defined(TARGET_WINDOWS) | ||
| + if (m_pLibShairplay && m_pLibShairplay->IsLoaded()) | ||
| + { | ||
| + m_pLibShairplay->raop_stop(m_pRaop); | ||
| + m_pLibShairplay->raop_destroy(m_pRaop); | ||
| + m_pLibShairplay->Unload(); | ||
| + } | ||
| +#else | ||
| if (m_pLibShairport && m_pLibShairport->IsLoaded()) | ||
| { | ||
| m_pLibShairport->shairport_exit(); | ||
| m_pLibShairport->Unload(); | ||
| } | ||
| +#endif | ||
| } | ||
|
|
||
| #endif | ||
| + | ||
| diff --git a/xbmc/network/AirTunesServer.h b/xbmc/network/AirTunesServer.h | ||
| index da893df..4a30e0a 100644 | ||
| --- a/xbmc/network/AirTunesServer.h | ||
| +++ b/xbmc/network/AirTunesServer.h | ||
| @@ -26,7 +26,11 @@ | ||
|
|
||
| #ifdef HAS_AIRTUNES | ||
|
|
||
| +#if defined(TARGET_WINDOWS) | ||
| +#include "DllLibShairplay.h" | ||
| +#else | ||
| #include "DllLibShairport.h" | ||
| +#endif | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
| #include <netinet/in.h> | ||
| @@ -41,11 +45,14 @@ | ||
|
|
||
| class DllLibShairport; | ||
|
|
||
| + | ||
| class CAirTunesServer : public CThread | ||
| { | ||
| public: | ||
| static bool StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password=""); | ||
| static void StopServer(bool bWait); | ||
| + static void SetMetadataFromBuffer(const char *buffer, unsigned int size); | ||
| + static void SetCoverArtFromBuffer(const char *buffer, unsigned int size); | ||
|
|
||
| protected: | ||
| void Process(); | ||
| @@ -57,13 +64,28 @@ class CAirTunesServer : public CThread | ||
| void Deinitialize(); | ||
|
|
||
| int m_port; | ||
| +#if defined(TARGET_WINDOWS) | ||
| + static DllLibShairplay *m_pLibShairplay;//the lib | ||
| + raop_t *m_pRaop; | ||
| + XFILE::CPipeFile *m_pPipe; | ||
| +#else | ||
| static DllLibShairport *m_pLibShairport;//the lib | ||
| +#endif | ||
| static CAirTunesServer *ServerInstance; | ||
| static CStdString m_macAddress; | ||
|
|
||
| class AudioOutputFunctions | ||
| { | ||
| public: | ||
| +#if defined(TARGET_WINDOWS) | ||
| + static void* audio_init(void *cls, int bits, int channels, int samplerate); | ||
| + static void audio_set_volume(void *cls, void *session, float volume); | ||
| + static void audio_set_metadata(void *cls, void *session, const void *buffer, int buflen); | ||
| + static void audio_set_coverart(void *cls, void *session, const void *buffer, int buflen); | ||
| + static void audio_process(void *cls, void *session, const void *buffer, int buflen); | ||
| + static void audio_flush(void *cls, void *session); | ||
| + static void audio_destroy(void *cls, void *session); | ||
| +#else | ||
| static void ao_initialize(void); | ||
| static int ao_play(ao_device *device, char *output_samples, uint32_t num_bytes); | ||
| static int ao_default_driver_id(void); | ||
| @@ -74,6 +96,7 @@ class CAirTunesServer : public CThread | ||
| static int ao_append_option(ao_option **options, const char *key, const char *value); | ||
| static void ao_free_options(ao_option *options); | ||
| static char* ao_get_option(ao_option *options, const char* key); | ||
| +#endif | ||
| }; | ||
| }; | ||
|
|
||
| -- | ||
| 1.7.10 | ||
|
|
| @@ -0,0 +1,64 @@ | ||
| From b7fb4615609c684a98dc1cc27906aaa0f117837f Mon Sep 17 00:00:00 2001 | ||
| From: Memphiz <memphis@machzwo.de> | ||
| Date: Wed, 9 May 2012 18:53:45 +0200 | ||
| Subject: [PATCH] [airtunes] - add dmap metadata parsing | ||
|
|
||
| --- | ||
| xbmc/network/AirTunesServer.cpp | 13 ++++++++++++- | ||
| xbmc/network/AirTunesServer.h | 2 ++ | ||
| 2 files changed, 14 insertions(+), 1 deletion(-) | ||
|
|
||
| diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp | ||
| index e14da90..2ad097e 100644 | ||
| --- a/xbmc/network/AirTunesServer.cpp | ||
| +++ b/xbmc/network/AirTunesServer.cpp | ||
| @@ -47,7 +47,6 @@ | ||
| #include <map> | ||
| #include <string> | ||
|
|
||
| - | ||
| using namespace XFILE; | ||
|
|
||
| #if defined(TARGET_WINDOWS) | ||
| @@ -390,6 +389,16 @@ int CAirTunesServer::AudioOutputFunctions::ao_close(ao_device *device) | ||
| return 0; | ||
| } | ||
|
|
||
| +void CAirTunesServer::AudioOutputFunctions::ao_set_metadata(const char *buffer, unsigned int size) | ||
| +{ | ||
| + CAirTunesServer::SetMetadataFromBuffer(buffer, size); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::ao_set_metadata_coverart(const char *buffer, unsigned int size) | ||
| +{ | ||
| + CAirTunesServer::SetCoverArtFromBuffer(buffer, size); | ||
| +} | ||
| + | ||
| /* -- Device Setup/Playback/Teardown -- */ | ||
| int CAirTunesServer::AudioOutputFunctions::ao_append_option(ao_option **options, const char *key, const char *value) | ||
| { | ||
| @@ -658,6 +667,8 @@ bool CAirTunesServer::Initialize(const CStdString &password) | ||
| ao.ao_append_option = AudioOutputFunctions::ao_append_option; | ||
| ao.ao_free_options = AudioOutputFunctions::ao_free_options; | ||
| ao.ao_get_option = AudioOutputFunctions::ao_get_option; | ||
| + ao.ao_set_metadata = AudioOutputFunctions::ao_set_metadata; | ||
| + ao.ao_set_metadata_coverart = AudioOutputFunctions::ao_set_metadata_coverart; | ||
| struct printfPtr funcPtr; | ||
| funcPtr.extprintf = shairport_log; | ||
|
|
||
| diff --git a/xbmc/network/AirTunesServer.h b/xbmc/network/AirTunesServer.h | ||
| index 4a30e0a..0a4ce5a 100644 | ||
| --- a/xbmc/network/AirTunesServer.h | ||
| +++ b/xbmc/network/AirTunesServer.h | ||
| @@ -96,6 +96,8 @@ class CAirTunesServer : public CThread | ||
| static int ao_append_option(ao_option **options, const char *key, const char *value); | ||
| static void ao_free_options(ao_option *options); | ||
| static char* ao_get_option(ao_option *options, const char* key); | ||
| + static void ao_set_metadata(const char *buffer, unsigned int size); | ||
| + static void ao_set_metadata_coverart(const char *buffer, unsigned int size); | ||
| #endif | ||
| }; | ||
| }; | ||
| -- | ||
| 1.7.10 | ||
|
|
| @@ -0,0 +1,31 @@ | ||
| From ab175ba28508445f6aff57386a8ce04b58a86f60 Mon Sep 17 00:00:00 2001 | ||
| From: Memphiz <memphis@machzwo.de> | ||
| Date: Fri, 11 May 2012 19:56:37 +0200 | ||
| Subject: [PATCH] [fix] - reapply fix for airtunes with AE which was lost | ||
| during merge | ||
|
|
||
| --- | ||
| xbmc/network/AirTunesServer.cpp | 4 ++-- | ||
| 1 file changed, 2 insertions(+), 2 deletions(-) | ||
|
|
||
| diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp | ||
| index 285a0a6..a60ad09 100644 | ||
| --- a/xbmc/network/AirTunesServer.cpp | ||
| +++ b/xbmc/network/AirTunesServer.cpp | ||
| @@ -125,11 +125,11 @@ ao_device* CAirTunesServer::AudioOutputFunctions::ao_open_live(int driver_id, ao | ||
| if (ao_get_option(option, "name")) | ||
| item.GetMusicInfoTag()->SetTitle(ao_get_option(option, "name")); | ||
|
|
||
| - g_application.getApplicationMessenger().PlayFile(item); | ||
| - | ||
| ThreadMessage tMsg2 = { TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VISUALISATION, 0 }; | ||
| g_application.getApplicationMessenger().SendMessage(tMsg2, true); | ||
|
|
||
| + g_application.getApplicationMessenger().PlayFile(item); | ||
| + | ||
| return (ao_device*) device; | ||
| } | ||
|
|
||
| -- | ||
| 1.7.10 | ||
|
|
| @@ -0,0 +1,527 @@ | ||
| From b73018af2ae69c7cfad0a4461d169a49c7d0dfbf Mon Sep 17 00:00:00 2001 | ||
| From: Memphiz <memphis@machzwo.de> | ||
| Date: Tue, 15 May 2012 19:12:07 +0200 | ||
| Subject: [PATCH] [airtunes] - implementation for windows using libshairplay | ||
|
|
||
| --- | ||
| xbmc/network/AirTunesServer.cpp | 330 +++++++++++++++++++++++++++++++++++++-- | ||
| xbmc/network/AirTunesServer.h | 23 +++ | ||
| 2 files changed, 343 insertions(+), 10 deletions(-) | ||
|
|
||
| diff --git a/xbmc/network/AirTunesServer.cpp b/xbmc/network/AirTunesServer.cpp | ||
| index a60ad09..e14da90 100644 | ||
| --- a/xbmc/network/AirTunesServer.cpp | ||
| +++ b/xbmc/network/AirTunesServer.cpp | ||
| @@ -17,7 +17,9 @@ | ||
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
| */ | ||
|
|
||
| +#if !defined(TARGET_WINDOWS) | ||
| #pragma GCC diagnostic ignored "-Wwrite-strings" | ||
| +#endif | ||
|
|
||
| #include "AirTunesServer.h" | ||
|
|
||
| @@ -37,15 +39,243 @@ | ||
| #include "music/tags/MusicInfoTag.h" | ||
| #include "FileItem.h" | ||
| #include "GUIInfoManager.h" | ||
| +#include "guilib/GUIWindowManager.h" | ||
| #include "utils/Variant.h" | ||
| #include "settings/AdvancedSettings.h" | ||
| +#include "utils/EndianSwap.h" | ||
| + | ||
| +#include <map> | ||
| +#include <string> | ||
| + | ||
|
|
||
| using namespace XFILE; | ||
|
|
||
| +#if defined(TARGET_WINDOWS) | ||
| +DllLibShairplay *CAirTunesServer::m_pLibShairplay = NULL; | ||
| +#else | ||
| DllLibShairport *CAirTunesServer::m_pLibShairport = NULL; | ||
| +#endif | ||
| CAirTunesServer *CAirTunesServer::ServerInstance = NULL; | ||
| CStdString CAirTunesServer::m_macAddress; | ||
|
|
||
| +//parse daap metadata - thx to project MythTV | ||
| +std::map<std::string, std::string> decodeDMAP(const char *buffer, unsigned int size) | ||
| +{ | ||
| + std::map<std::string, std::string> result; | ||
| + unsigned int offset = 8; | ||
| + while (offset < size) | ||
| + { | ||
| + std::string tag; | ||
| + tag.append(buffer + offset, 4); | ||
| + offset += 4; | ||
| + uint32_t length = Endian_SwapBE32(*(uint32_t *)(buffer + offset)); | ||
| + offset += sizeof(uint32_t); | ||
| + std::string content; | ||
| + content.append(buffer + offset, length);//possible fixme - utf8? | ||
| + offset += length; | ||
| + result[tag] = content; | ||
| + } | ||
| + return result; | ||
| +} | ||
| + | ||
| +void CAirTunesServer::SetMetadataFromBuffer(const char *buffer, unsigned int size) | ||
| +{ | ||
| + MUSIC_INFO::CMusicInfoTag tag; | ||
| + std::map<std::string, std::string> metadata = decodeDMAP(buffer, size); | ||
| + if(metadata["asal"].length()) | ||
| + tag.SetAlbum(metadata["asal"]);//album | ||
| + if(metadata["minm"].length()) | ||
| + tag.SetTitle(metadata["minm"]);//title | ||
| + if(metadata["asar"].length()) | ||
| + tag.SetArtist(metadata["asar"]);//artist | ||
| + g_infoManager.SetCurrentSongTag(tag); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::SetCoverArtFromBuffer(const char *buffer, unsigned int size) | ||
| +{ | ||
| + XFILE::CFile tmpFile; | ||
| + const char *tmpFileName = "special://temp/airtunes_album_thumb.jpg"; | ||
| + | ||
| + if(!size) | ||
| + return; | ||
| + | ||
| + if (tmpFile.OpenForWrite(tmpFileName, true)) | ||
| + { | ||
| + int writtenBytes=0; | ||
| + writtenBytes = tmpFile.Write(buffer, size); | ||
| + tmpFile.Close(); | ||
| + | ||
| + if(writtenBytes) | ||
| + { | ||
| + //reset to empty before setting the new one | ||
| + //else it won't get refreshed because the name didn't change | ||
| + g_infoManager.SetCurrentAlbumThumb(""); | ||
| + g_infoManager.SetCurrentAlbumThumb(tmpFileName); | ||
| + //update the ui | ||
| + CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_REFRESH_THUMBS); | ||
| + g_windowManager.SendThreadMessage(msg); | ||
| + } | ||
| + } | ||
| +} | ||
| + | ||
| +#if defined(TARGET_WINDOWS) | ||
| +#define RSA_KEY " \ | ||
| +-----BEGIN RSA PRIVATE KEY-----\ | ||
| +MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\ | ||
| +wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\ | ||
| +wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\ | ||
| +/+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\ | ||
| +UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\ | ||
| +BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\ | ||
| +LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\ | ||
| +NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\ | ||
| +lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\ | ||
| +aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\ | ||
| +a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\ | ||
| +oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\ | ||
| +oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\ | ||
| +k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\ | ||
| +AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\ | ||
| +cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\ | ||
| +54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\ | ||
| +17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\ | ||
| +1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\ | ||
| +LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\ | ||
| +2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\ | ||
| +-----END RSA PRIVATE KEY-----" | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_set_metadata(void *cls, void *session, const void *buffer, int buflen) | ||
| +{ | ||
| + CAirTunesServer::SetMetadataFromBuffer((char *)buffer, buflen); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_set_coverart(void *cls, void *session, const void *buffer, int buflen) | ||
| +{ | ||
| + CAirTunesServer::SetCoverArtFromBuffer((char *)buffer, buflen); | ||
| +} | ||
| + | ||
| +void* CAirTunesServer::AudioOutputFunctions::audio_init(void *cls, int bits, int channels, int samplerate) | ||
| +{ | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName()); | ||
| + pipe->SetOpenThreashold(300); | ||
| + | ||
| + BXA_FmtHeader header; | ||
| + strncpy(header.fourcc, "BXA ", 4); | ||
| + header.type = BXA_PACKET_TYPE_FMT; | ||
| + header.bitsPerSample = bits; | ||
| + header.channels = channels; | ||
| + header.sampleRate = samplerate; | ||
| + header.durationMs = 0; | ||
| + | ||
| + if (pipe->Write(&header, sizeof(header)) == 0) | ||
| + return 0; | ||
| + | ||
| + ThreadMessage tMsg = { TMSG_MEDIA_STOP }; | ||
| + g_application.getApplicationMessenger().SendMessage(tMsg, true); | ||
| + | ||
| + CFileItem item; | ||
| + item.SetPath(pipe->GetName()); | ||
| + item.SetMimeType("audio/x-xbmc-pcm"); | ||
| + | ||
| + ThreadMessage tMsg2 = { TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VISUALISATION, 0 }; | ||
| + g_application.getApplicationMessenger().SendMessage(tMsg2, true); | ||
| + | ||
| + g_application.getApplicationMessenger().PlayFile(item); | ||
| + | ||
| + return "XBMC-AirTunes";//session | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_set_volume(void *cls, void *session, float volume) | ||
| +{ | ||
| + //volume from -144 - 0 | ||
| + float volPercent = 1 - volume/-144; | ||
| + g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0 | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_process(void *cls, void *session, const void *buffer, int buflen) | ||
| +{ | ||
| + #define NUM_OF_BYTES 64 | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + int sentBytes = 0; | ||
| + unsigned char buf[NUM_OF_BYTES]; | ||
| + | ||
| + while (sentBytes < buflen) | ||
| + { | ||
| + int n = (buflen - sentBytes < NUM_OF_BYTES ? buflen - sentBytes : NUM_OF_BYTES); | ||
| + memcpy(buf, (char*) buffer + sentBytes, n); | ||
| + | ||
| + if (pipe->Write(buf, n) == 0) | ||
| + return; | ||
| + | ||
| + sentBytes += n; | ||
| + } | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_flush(void *cls, void *session) | ||
| +{ | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + pipe->Flush(); | ||
| +} | ||
| + | ||
| +void CAirTunesServer::AudioOutputFunctions::audio_destroy(void *cls, void *session) | ||
| +{ | ||
| + XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls; | ||
| + pipe->SetEof(); | ||
| + pipe->Close(); | ||
| + | ||
| + //fix airplay video for ios5 devices | ||
| + //on ios5 when airplaying video | ||
| + //the client first opens an airtunes stream | ||
| + //while the movie is loading | ||
| + //in that case we don't want to stop the player here | ||
| + //because this would stop the airplaying video | ||
| +#ifdef HAS_AIRPLAY | ||
| + if (!CAirPlayServer::IsPlaying()) | ||
| +#endif | ||
| + { | ||
| + ThreadMessage tMsg = { TMSG_MEDIA_STOP }; | ||
| + g_application.getApplicationMessenger().SendMessage(tMsg, true); | ||
| + CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player"); | ||
| + } | ||
| +} | ||
| + | ||
| +void shairplay_log(int level, const char *msg) | ||
| +{ | ||
| + int xbmcLevel = LOGINFO; | ||
| + | ||
| + switch(level) | ||
| + { | ||
| + case RAOP_LOG_EMERG: // system is unusable | ||
| + xbmcLevel = LOGFATAL; | ||
| + break; | ||
| + case RAOP_LOG_ALERT: // action must be taken immediately | ||
| + case RAOP_LOG_CRIT: // critical conditions | ||
| + xbmcLevel = LOGSEVERE; | ||
| + break; | ||
| + case RAOP_LOG_ERR: // error conditions | ||
| + xbmcLevel = LOGERROR; | ||
| + break; | ||
| + case RAOP_LOG_WARNING: // warning conditions | ||
| + xbmcLevel = LOGWARNING; | ||
| + break; | ||
| + case RAOP_LOG_NOTICE: // normal but significant condition | ||
| + xbmcLevel = LOGNOTICE; | ||
| + break; | ||
| + case RAOP_LOG_INFO: // informational | ||
| + xbmcLevel = LOGINFO; | ||
| + break; | ||
| + case RAOP_LOG_DEBUG: // debug-level messages | ||
| + xbmcLevel = LOGDEBUG; | ||
| + break; | ||
| + default: | ||
| + break; | ||
| + } | ||
| + CLog::Log(xbmcLevel, "AIRTUNES: %s", msg); | ||
| +} | ||
| + | ||
| +#else | ||
| + | ||
| struct ao_device_xbmc | ||
| { | ||
| XFILE::CPipeFile *pipe; | ||
| @@ -214,6 +444,17 @@ char* CAirTunesServer::AudioOutputFunctions::ao_get_option(ao_option *options, c | ||
| return NULL; | ||
| } | ||
|
|
||
| +int shairport_log(const char* msg, size_t msgSize) | ||
| +{ | ||
| + if( g_advancedSettings.m_logEnableAirtunes) | ||
| + { | ||
| + CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg); | ||
| + } | ||
| + return 1; | ||
| +} | ||
| + | ||
| +#endif | ||
| + | ||
| bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password/*=""*/) | ||
| { | ||
| bool success = false; | ||
| @@ -243,7 +484,9 @@ bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, con | ||
| ServerInstance = new CAirTunesServer(port, nonlocal); | ||
| if (ServerInstance->Initialize(password)) | ||
| { | ||
| +#ifndef TARGET_WINDOWS | ||
| ServerInstance->Create(); | ||
| +#endif | ||
| success = true; | ||
| } | ||
|
|
||
| @@ -264,6 +507,9 @@ bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, con | ||
| txt["sr"] = "44100"; | ||
| txt["pw"] = "false"; | ||
| txt["vn"] = "3"; | ||
| + txt["da"] = "true"; | ||
| + txt["vs"] = "130.14"; | ||
| + txt["md"] = "0,1,2"; | ||
| txt["txtvers"] = "1"; | ||
|
|
||
| CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt); | ||
| @@ -276,10 +522,12 @@ void CAirTunesServer::StopServer(bool bWait) | ||
| { | ||
| if (ServerInstance) | ||
| { | ||
| +#if !defined(TARGET_WINDOWS) | ||
| if (m_pLibShairport->IsLoaded()) | ||
| { | ||
| m_pLibShairport->shairport_exit(); | ||
| } | ||
| +#endif | ||
| ServerInstance->StopThread(bWait); | ||
| ServerInstance->Deinitialize(); | ||
| if (bWait) | ||
| @@ -295,47 +543,98 @@ void CAirTunesServer::StopServer(bool bWait) | ||
| CAirTunesServer::CAirTunesServer(int port, bool nonlocal) : CThread("AirTunesServer") | ||
| { | ||
| m_port = port; | ||
| +#if defined(TARGET_WINDOWS) | ||
| + m_pLibShairplay = new DllLibShairplay(); | ||
| + m_pPipe = new XFILE::CPipeFile; | ||
| +#else | ||
| m_pLibShairport = new DllLibShairport(); | ||
| +#endif | ||
| } | ||
|
|
||
| CAirTunesServer::~CAirTunesServer() | ||
| { | ||
| +#if defined(TARGET_WINDOWS) | ||
| + if (m_pLibShairplay->IsLoaded()) | ||
| + { | ||
| + m_pLibShairplay->Unload(); | ||
| + } | ||
| + delete m_pLibShairplay; | ||
| + delete m_pPipe; | ||
| +#else | ||
| if (m_pLibShairport->IsLoaded()) | ||
| { | ||
| m_pLibShairport->Unload(); | ||
| } | ||
| delete m_pLibShairport; | ||
| +#endif | ||
| } | ||
|
|
||
| void CAirTunesServer::Process() | ||
| { | ||
| m_bStop = false; | ||
|
|
||
| +#if !defined(TARGET_WINDOWS) | ||
| while (!m_bStop && m_pLibShairport->shairport_is_running()) | ||
| { | ||
| m_pLibShairport->shairport_loop(); | ||
| } | ||
| +#endif | ||
| } | ||
|
|
||
| -int shairport_log(const char* msg, size_t msgSize) | ||
| +bool CAirTunesServer::Initialize(const CStdString &password) | ||
| { | ||
| - if( g_advancedSettings.m_logEnableAirtunes) | ||
| + bool ret = false; | ||
| + | ||
| + Deinitialize(); | ||
| + | ||
| +#if defined(TARGET_WINDOWS) | ||
| + if (m_pLibShairplay->Load()) | ||
| { | ||
| - CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg); | ||
| + | ||
| + raop_callbacks_t ao; | ||
| + ao.cls = m_pPipe; | ||
| + ao.audio_init = AudioOutputFunctions::audio_init; | ||
| + ao.audio_set_volume = AudioOutputFunctions::audio_set_volume; | ||
| + ao.audio_set_metadata = AudioOutputFunctions::audio_set_metadata; | ||
| + ao.audio_set_coverart = AudioOutputFunctions::audio_set_coverart; | ||
| + ao.audio_process = AudioOutputFunctions::audio_process; | ||
| + ao.audio_flush = AudioOutputFunctions::audio_flush; | ||
| + ao.audio_destroy = AudioOutputFunctions::audio_destroy; | ||
| + m_pLibShairplay->EnableDelayedUnload(false); | ||
| + m_pRaop = m_pLibShairplay->raop_init(1, &ao, RSA_KEY);//1 - we handle one client at a time max | ||
| + ret = m_pRaop != NULL; | ||
| + | ||
| + if(ret) | ||
| + { | ||
| + char macAdr[6]; | ||
| + unsigned short port = (unsigned short)m_port; | ||
| + | ||
| + m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_WARNING); | ||
| + if(g_advancedSettings.m_logEnableAirtunes) | ||
| + { | ||
| + m_pLibShairplay->raop_set_log_level(m_pRaop, RAOP_LOG_DEBUG); | ||
| + } | ||
| + | ||
| + m_pLibShairplay->raop_set_log_callback(m_pRaop, shairplay_log); | ||
| + | ||
| + CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface(); | ||
| + | ||
| + if (net) | ||
| + { | ||
| + net->GetMacAddressRaw(macAdr); | ||
| + } | ||
| + | ||
| + ret = m_pLibShairplay->raop_start(m_pRaop, &port, macAdr, 6, password.c_str()) >= 0; | ||
| + } | ||
| } | ||
| - return 1; | ||
| -} | ||
|
|
||
| -bool CAirTunesServer::Initialize(const CStdString &password) | ||
| -{ | ||
| - bool ret = false; | ||
| +#else | ||
| + | ||
| int numArgs = 3; | ||
| CStdString hwStr; | ||
| CStdString pwStr; | ||
| CStdString portStr; | ||
|
|
||
| - Deinitialize(); | ||
| - | ||
| hwStr.Format("--mac=%s", m_macAddress.c_str()); | ||
| pwStr.Format("--password=%s",password.c_str()); | ||
| portStr.Format("--server_port=%d",m_port); | ||
| @@ -368,16 +667,27 @@ bool CAirTunesServer::Initialize(const CStdString &password) | ||
| m_pLibShairport->shairport_main(numArgs, argv); | ||
| ret = true; | ||
| } | ||
| +#endif | ||
| return ret; | ||
| } | ||
|
|
||
| void CAirTunesServer::Deinitialize() | ||
| { | ||
| +#if defined(TARGET_WINDOWS) | ||
| + if (m_pLibShairplay && m_pLibShairplay->IsLoaded()) | ||
| + { | ||
| + m_pLibShairplay->raop_stop(m_pRaop); | ||
| + m_pLibShairplay->raop_destroy(m_pRaop); | ||
| + m_pLibShairplay->Unload(); | ||
| + } | ||
| +#else | ||
| if (m_pLibShairport && m_pLibShairport->IsLoaded()) | ||
| { | ||
| m_pLibShairport->shairport_exit(); | ||
| m_pLibShairport->Unload(); | ||
| } | ||
| +#endif | ||
| } | ||
|
|
||
| #endif | ||
| + | ||
| diff --git a/xbmc/network/AirTunesServer.h b/xbmc/network/AirTunesServer.h | ||
| index da893df..4a30e0a 100644 | ||
| --- a/xbmc/network/AirTunesServer.h | ||
| +++ b/xbmc/network/AirTunesServer.h | ||
| @@ -26,7 +26,11 @@ | ||
|
|
||
| #ifdef HAS_AIRTUNES | ||
|
|
||
| +#if defined(TARGET_WINDOWS) | ||
| +#include "DllLibShairplay.h" | ||
| +#else | ||
| #include "DllLibShairport.h" | ||
| +#endif | ||
| #include <sys/types.h> | ||
| #include <sys/socket.h> | ||
| #include <netinet/in.h> | ||
| @@ -41,11 +45,14 @@ | ||
|
|
||
| class DllLibShairport; | ||
|
|
||
| + | ||
| class CAirTunesServer : public CThread | ||
| { | ||
| public: | ||
| static bool StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password=""); | ||
| static void StopServer(bool bWait); | ||
| + static void SetMetadataFromBuffer(const char *buffer, unsigned int size); | ||
| + static void SetCoverArtFromBuffer(const char *buffer, unsigned int size); | ||
|
|
||
| protected: | ||
| void Process(); | ||
| @@ -57,13 +64,28 @@ class CAirTunesServer : public CThread | ||
| void Deinitialize(); | ||
|
|
||
| int m_port; | ||
| +#if defined(TARGET_WINDOWS) | ||
| + static DllLibShairplay *m_pLibShairplay;//the lib | ||
| + raop_t *m_pRaop; | ||
| + XFILE::CPipeFile *m_pPipe; | ||
| +#else | ||
| static DllLibShairport *m_pLibShairport;//the lib | ||
| +#endif | ||
| static CAirTunesServer *ServerInstance; | ||
| static CStdString m_macAddress; | ||
|
|
||
| class AudioOutputFunctions | ||
| { | ||
| public: | ||
| +#if defined(TARGET_WINDOWS) | ||
| + static void* audio_init(void *cls, int bits, int channels, int samplerate); | ||
| + static void audio_set_volume(void *cls, void *session, float volume); | ||
| + static void audio_set_metadata(void *cls, void *session, const void *buffer, int buflen); | ||
| + static void audio_set_coverart(void *cls, void *session, const void *buffer, int buflen); | ||
| + static void audio_process(void *cls, void *session, const void *buffer, int buflen); | ||
| + static void audio_flush(void *cls, void *session); | ||
| + static void audio_destroy(void *cls, void *session); | ||
| +#else | ||
| static void ao_initialize(void); | ||
| static int ao_play(ao_device *device, char *output_samples, uint32_t num_bytes); | ||
| static int ao_default_driver_id(void); | ||
| @@ -74,6 +96,7 @@ class CAirTunesServer : public CThread | ||
| static int ao_append_option(ao_option **options, const char *key, const char *value); | ||
| static void ao_free_options(ao_option *options); | ||
| static char* ao_get_option(ao_option *options, const char* key); | ||
| +#endif | ||
| }; | ||
| }; | ||
|
|
||
| -- | ||
| 1.7.10 | ||
|
|