diff --git a/packages/mediacenter/xbmc/patches/xbmc-11.0.1-999.012-airtunes_implementation_for_windows_using_libshairplay.patch b/packages/mediacenter/xbmc/patches/xbmc-11.0.1-999.012-airtunes_implementation_for_windows_using_libshairplay.patch new file mode 100644 index 0000000000..9ea05b6281 --- /dev/null +++ b/packages/mediacenter/xbmc/patches/xbmc-11.0.1-999.012-airtunes_implementation_for_windows_using_libshairplay.patch @@ -0,0 +1,527 @@ +From b73018af2ae69c7cfad0a4461d169a49c7d0dfbf Mon Sep 17 00:00:00 2001 +From: Memphiz +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 ++#include ++ + + 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 decodeDMAP(const char *buffer, unsigned int size) ++{ ++ std::map 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 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 + #include + #include +@@ -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 + diff --git a/packages/mediacenter/xbmc/patches/xbmc-11.0.1-999.013-airtunes-add_dmap_metadata_parsing.patch b/packages/mediacenter/xbmc/patches/xbmc-11.0.1-999.013-airtunes-add_dmap_metadata_parsing.patch new file mode 100644 index 0000000000..44bd4b83a9 --- /dev/null +++ b/packages/mediacenter/xbmc/patches/xbmc-11.0.1-999.013-airtunes-add_dmap_metadata_parsing.patch @@ -0,0 +1,64 @@ +From b7fb4615609c684a98dc1cc27906aaa0f117837f Mon Sep 17 00:00:00 2001 +From: Memphiz +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 + #include + +- + 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 + diff --git a/packages/mediacenter/xbmc/patches/xbmc-pvr-11.0.1-999.011-airtunes_reapply_lost_fix.patch b/packages/mediacenter/xbmc/patches/xbmc-pvr-11.0.1-999.011-airtunes_reapply_lost_fix.patch new file mode 100644 index 0000000000..4885fe33e9 --- /dev/null +++ b/packages/mediacenter/xbmc/patches/xbmc-pvr-11.0.1-999.011-airtunes_reapply_lost_fix.patch @@ -0,0 +1,31 @@ +From ab175ba28508445f6aff57386a8ce04b58a86f60 Mon Sep 17 00:00:00 2001 +From: Memphiz +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 + diff --git a/packages/mediacenter/xbmc/patches/xbmc-pvr-11.0.1-999.012-airtunes_implementation_for_windows_using_libshairplay.patch b/packages/mediacenter/xbmc/patches/xbmc-pvr-11.0.1-999.012-airtunes_implementation_for_windows_using_libshairplay.patch new file mode 100644 index 0000000000..9ea05b6281 --- /dev/null +++ b/packages/mediacenter/xbmc/patches/xbmc-pvr-11.0.1-999.012-airtunes_implementation_for_windows_using_libshairplay.patch @@ -0,0 +1,527 @@ +From b73018af2ae69c7cfad0a4461d169a49c7d0dfbf Mon Sep 17 00:00:00 2001 +From: Memphiz +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 ++#include ++ + + 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 decodeDMAP(const char *buffer, unsigned int size) ++{ ++ std::map 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 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 + #include + #include +@@ -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 +