1 /*
2  * Many concepts and protocol specification in this code are taken
3  * from Shairport, by James Laird.
4  *
5  *  Copyright (C) 2011-2018 Team Kodi
6  *  This file is part of Kodi - https://kodi.tv
7  *
8  *  SPDX-License-Identifier: GPL-2.0-or-later
9  *  See LICENSES/README.md for more information.
10  */
11 
12 #include "AirTunesServer.h"
13 
14 #include "Application.h"
15 #include "CompileInfo.h"
16 #include "FileItem.h"
17 #include "GUIInfoManager.h"
18 #include "ServiceBroker.h"
19 #include "URL.h"
20 #include "cores/VideoPlayer/DVDDemuxers/DVDDemuxBXA.h"
21 #include "filesystem/File.h"
22 #include "filesystem/PipeFile.h"
23 #include "guilib/GUIComponent.h"
24 #include "guilib/GUIWindowManager.h"
25 #include "input/Key.h"
26 #include "interfaces/AnnouncementManager.h"
27 #include "messaging/ApplicationMessenger.h"
28 #include "music/tags/MusicInfoTag.h"
29 #include "network/Network.h"
30 #include "network/Zeroconf.h"
31 #include "network/ZeroconfBrowser.h"
32 #include "network/dacp/dacp.h"
33 #include "settings/AdvancedSettings.h"
34 #include "settings/Settings.h"
35 #include "settings/SettingsComponent.h"
36 #include "utils/EndianSwap.h"
37 #include "utils/StringUtils.h"
38 #include "utils/SystemInfo.h"
39 #include "utils/Variant.h"
40 #include "utils/log.h"
41 
42 #include <map>
43 #include <string>
44 #include <utility>
45 
46 #if !defined(TARGET_WINDOWS)
47 #pragma GCC diagnostic ignored "-Wwrite-strings"
48 #endif
49 
50 #ifdef HAS_AIRPLAY
51 #include "network/AirPlayServer.h"
52 #endif
53 
54 #define TMP_COVERART_PATH_JPG "special://temp/airtunes_album_thumb.jpg"
55 #define TMP_COVERART_PATH_PNG "special://temp/airtunes_album_thumb.png"
56 #define ZEROCONF_DACP_SERVICE "_dacp._tcp"
57 
58 using namespace XFILE;
59 using namespace KODI::MESSAGING;
60 
61 CAirTunesServer *CAirTunesServer::ServerInstance = NULL;
62 std::string CAirTunesServer::m_macAddress;
63 std::string CAirTunesServer::m_metadata[3];
64 CCriticalSection CAirTunesServer::m_metadataLock;
65 bool CAirTunesServer::m_streamStarted = false;
66 CCriticalSection CAirTunesServer::m_dacpLock;
67 CDACP *CAirTunesServer::m_pDACP = NULL;
68 std::string CAirTunesServer::m_dacp_id;
69 std::string CAirTunesServer::m_active_remote_header;
70 CCriticalSection CAirTunesServer::m_actionQueueLock;
71 std::list<CAction> CAirTunesServer::m_actionQueue;
72 CEvent CAirTunesServer::m_processActions;
73 int CAirTunesServer::m_sampleRate = 44100;
74 
75 unsigned int CAirTunesServer::m_cachedStartTime = 0;
76 unsigned int CAirTunesServer::m_cachedEndTime = 0;
77 unsigned int CAirTunesServer::m_cachedCurrentTime = 0;
78 
79 
80 //parse daap metadata - thx to project MythTV
decodeDMAP(const char * buffer,unsigned int size)81 std::map<std::string, std::string> decodeDMAP(const char *buffer, unsigned int size)
82 {
83   std::map<std::string, std::string> result;
84   unsigned int offset = 8;
85   while (offset < size)
86   {
87     std::string tag;
88     tag.append(buffer + offset, 4);
89     offset += 4;
90     uint32_t length = Endian_SwapBE32(*(const uint32_t *)(buffer + offset));
91     offset += sizeof(uint32_t);
92     std::string content;
93     content.append(buffer + offset, length);//possible fixme - utf8?
94     offset += length;
95     result[tag] = content;
96   }
97   return result;
98 }
99 
ResetMetadata()100 void CAirTunesServer::ResetMetadata()
101 {
102   CSingleLock lock(m_metadataLock);
103 
104   XFILE::CFile::Delete(TMP_COVERART_PATH_JPG);
105   XFILE::CFile::Delete(TMP_COVERART_PATH_PNG);
106   RefreshCoverArt();
107 
108   m_metadata[0] = "";
109   m_metadata[1] = "AirPlay";
110   m_metadata[2] = "";
111   RefreshMetadata();
112 }
113 
RefreshMetadata()114 void CAirTunesServer::RefreshMetadata()
115 {
116   CSingleLock lock(m_metadataLock);
117   MUSIC_INFO::CMusicInfoTag tag;
118   CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
119   if (infoMgr.GetCurrentSongTag())
120     tag = *infoMgr.GetCurrentSongTag();
121   if (m_metadata[0].length())
122     tag.SetAlbum(m_metadata[0]);//album
123   if (m_metadata[1].length())
124     tag.SetTitle(m_metadata[1]);//title
125   if (m_metadata[2].length())
126     tag.SetArtist(m_metadata[2]);//artist
127 
128   CApplicationMessenger::GetInstance().PostMsg(TMSG_UPDATE_CURRENT_ITEM, 1, -1, static_cast<void*>(new CFileItem(tag)));
129 }
130 
RefreshCoverArt(const char * outputFilename)131 void CAirTunesServer::RefreshCoverArt(const char *outputFilename/* = NULL*/)
132 {
133   static std::string coverArtFile = TMP_COVERART_PATH_JPG;
134 
135   if (outputFilename != NULL)
136     coverArtFile = std::string(outputFilename);
137 
138   CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
139   CSingleLock lock(m_metadataLock);
140   //reset to empty before setting the new one
141   //else it won't get refreshed because the name didn't change
142   infoMgr.SetCurrentAlbumThumb("");
143   //update the ui
144   infoMgr.SetCurrentAlbumThumb(coverArtFile);
145   //update the ui
146   CGUIMessage msg(GUI_MSG_NOTIFY_ALL,0,0,GUI_MSG_REFRESH_THUMBS);
147   CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
148 }
149 
SetMetadataFromBuffer(const char * buffer,unsigned int size)150 void CAirTunesServer::SetMetadataFromBuffer(const char *buffer, unsigned int size)
151 {
152 
153   std::map<std::string, std::string> metadata = decodeDMAP(buffer, size);
154   CSingleLock lock(m_metadataLock);
155 
156   if(metadata["asal"].length())
157     m_metadata[0] = metadata["asal"];//album
158   if(metadata["minm"].length())
159     m_metadata[1] = metadata["minm"];//title
160   if(metadata["asar"].length())
161     m_metadata[2] = metadata["asar"];//artist
162 
163   RefreshMetadata();
164 }
165 
Announce(ANNOUNCEMENT::AnnouncementFlag flag,const std::string & sender,const std::string & message,const CVariant & data)166 void CAirTunesServer::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
167                                const std::string& sender,
168                                const std::string& message,
169                                const CVariant& data)
170 {
171   if ((flag & ANNOUNCEMENT::Player) &&
172       sender == ANNOUNCEMENT::CAnnouncementManager::ANNOUNCEMENT_SENDER)
173   {
174     if ((message == "OnPlay" || message == "OnResume") && m_streamStarted)
175     {
176       RefreshMetadata();
177       RefreshCoverArt();
178       CSingleLock lock(m_dacpLock);
179       if (m_pDACP)
180         m_pDACP->Play();
181     }
182 
183     if (message == "OnStop" && m_streamStarted)
184     {
185       CSingleLock lock(m_dacpLock);
186       if (m_pDACP)
187         m_pDACP->Stop();
188     }
189 
190     if (message == "OnPause" && m_streamStarted)
191     {
192       CSingleLock lock(m_dacpLock);
193       if (m_pDACP)
194         m_pDACP->Pause();
195     }
196   }
197 }
198 
EnableActionProcessing(bool enable)199 void CAirTunesServer::EnableActionProcessing(bool enable)
200 {
201   ServerInstance->RegisterActionListener(enable);
202 }
203 
OnAction(const CAction & action)204 bool CAirTunesServer::OnAction(const CAction &action)
205 {
206   switch(action.GetID())
207   {
208     case ACTION_NEXT_ITEM:
209     case ACTION_PREV_ITEM:
210     case ACTION_VOLUME_UP:
211     case ACTION_VOLUME_DOWN:
212     case ACTION_MUTE:
213     {
214       CSingleLock lock(m_actionQueueLock);
215       m_actionQueue.push_back(action);
216       m_processActions.Set();
217     }
218   }
219   return false;
220 }
221 
Process()222 void CAirTunesServer::Process()
223 {
224   m_bStop = false;
225   while(!m_bStop)
226   {
227     if (m_streamStarted)
228       SetupRemoteControl();// check for remote controls
229 
230     m_processActions.WaitMSec(1000);// timeout for being able to stop
231     std::list<CAction> currentActions;
232     {
233       CSingleLock lock(m_actionQueueLock);// copy and clear the source queue
234       currentActions.insert(currentActions.begin(), m_actionQueue.begin(), m_actionQueue.end());
235       m_actionQueue.clear();
236     }
237 
238     for (const auto& currentAction : currentActions)
239     {
240       CSingleLock lock(m_dacpLock);
241       if (m_pDACP)
242       {
243         switch(currentAction.GetID())
244         {
245           case ACTION_NEXT_ITEM:
246             m_pDACP->NextItem();
247             break;
248           case ACTION_PREV_ITEM:
249             m_pDACP->PrevItem();
250             break;
251           case ACTION_VOLUME_UP:
252             m_pDACP->VolumeUp();
253             break;
254           case ACTION_VOLUME_DOWN:
255             m_pDACP->VolumeDown();
256             break;
257           case ACTION_MUTE:
258             m_pDACP->ToggleMute();
259             break;
260         }
261       }
262     }
263   }
264 }
265 
IsJPEG(const char * buffer,unsigned int size)266 bool IsJPEG(const char *buffer, unsigned int size)
267 {
268   bool ret = false;
269   if (size < 2)
270     return false;
271 
272   //JPEG image files begin with FF D8 and end with FF D9.
273   // check for FF D8 big + little endian on start
274   if ((buffer[0] == (char)0xd8 && buffer[1] == (char)0xff) ||
275       (buffer[1] == (char)0xd8 && buffer[0] == (char)0xff))
276     ret = true;
277 
278   if (ret)
279   {
280     ret = false;
281     //check on FF D9 big + little endian on end
282     if ((buffer[size - 2] == (char)0xd9 && buffer[size - 1] == (char)0xff) ||
283        (buffer[size - 1] == (char)0xd9 && buffer[size - 2] == (char)0xff))
284         ret = true;
285   }
286 
287   return ret;
288 }
289 
SetCoverArtFromBuffer(const char * buffer,unsigned int size)290 void CAirTunesServer::SetCoverArtFromBuffer(const char *buffer, unsigned int size)
291 {
292   XFILE::CFile tmpFile;
293   std::string tmpFilename = TMP_COVERART_PATH_PNG;
294 
295   if(!size)
296     return;
297 
298   CSingleLock lock(m_metadataLock);
299 
300   if (IsJPEG(buffer, size))
301     tmpFilename = TMP_COVERART_PATH_JPG;
302 
303   if (tmpFile.OpenForWrite(tmpFilename, true))
304   {
305     int writtenBytes=0;
306     writtenBytes = tmpFile.Write(buffer, size);
307     tmpFile.Close();
308 
309     if (writtenBytes > 0)
310       RefreshCoverArt(tmpFilename.c_str());
311   }
312 }
313 
FreeDACPRemote()314 void CAirTunesServer::FreeDACPRemote()
315 {
316   CSingleLock lock(m_dacpLock);
317   if (m_pDACP)
318     delete m_pDACP;
319   m_pDACP = NULL;
320 }
321 
322 #define RSA_KEY " \
323 -----BEGIN RSA PRIVATE KEY-----\
324 MIIEpQIBAAKCAQEA59dE8qLieItsH1WgjrcFRKj6eUWqi+bGLOX1HL3U3GhC/j0Qg90u3sG/1CUt\
325 wC5vOYvfDmFI6oSFXi5ELabWJmT2dKHzBJKa3k9ok+8t9ucRqMd6DZHJ2YCCLlDRKSKv6kDqnw4U\
326 wPdpOMXziC/AMj3Z/lUVX1G7WSHCAWKf1zNS1eLvqr+boEjXuBOitnZ/bDzPHrTOZz0Dew0uowxf\
327 /+sG+NCK3eQJVxqcaJ/vEHKIVd2M+5qL71yJQ+87X6oV3eaYvt3zWZYD6z5vYTcrtij2VZ9Zmni/\
328 UAaHqn9JdsBWLUEpVviYnhimNVvYFZeCXg/IdTQ+x4IRdiXNv5hEewIDAQABAoIBAQDl8Axy9XfW\
329 BLmkzkEiqoSwF0PsmVrPzH9KsnwLGH+QZlvjWd8SWYGN7u1507HvhF5N3drJoVU3O14nDY4TFQAa\
330 LlJ9VM35AApXaLyY1ERrN7u9ALKd2LUwYhM7Km539O4yUFYikE2nIPscEsA5ltpxOgUGCY7b7ez5\
331 NtD6nL1ZKauw7aNXmVAvmJTcuPxWmoktF3gDJKK2wxZuNGcJE0uFQEG4Z3BrWP7yoNuSK3dii2jm\
332 lpPHr0O/KnPQtzI3eguhe0TwUem/eYSdyzMyVx/YpwkzwtYL3sR5k0o9rKQLtvLzfAqdBxBurciz\
333 aaA/L0HIgAmOit1GJA2saMxTVPNhAoGBAPfgv1oeZxgxmotiCcMXFEQEWflzhWYTsXrhUIuz5jFu\
334 a39GLS99ZEErhLdrwj8rDDViRVJ5skOp9zFvlYAHs0xh92ji1E7V/ysnKBfsMrPkk5KSKPrnjndM\
335 oPdevWnVkgJ5jxFuNgxkOLMuG9i53B4yMvDTCRiIPMQ++N2iLDaRAoGBAO9v//mU8eVkQaoANf0Z\
336 oMjW8CN4xwWA2cSEIHkd9AfFkftuv8oyLDCG3ZAf0vrhrrtkrfa7ef+AUb69DNggq4mHQAYBp7L+\
337 k5DKzJrKuO0r+R0YbY9pZD1+/g9dVt91d6LQNepUE/yY2PP5CNoFmjedpLHMOPFdVgqDzDFxU8hL\
338 AoGBANDrr7xAJbqBjHVwIzQ4To9pb4BNeqDndk5Qe7fT3+/H1njGaC0/rXE0Qb7q5ySgnsCb3DvA\
339 cJyRM9SJ7OKlGt0FMSdJD5KG0XPIpAVNwgpXXH5MDJg09KHeh0kXo+QA6viFBi21y340NonnEfdf\
340 54PX4ZGS/Xac1UK+pLkBB+zRAoGAf0AY3H3qKS2lMEI4bzEFoHeK3G895pDaK3TFBVmD7fV0Zhov\
341 17fegFPMwOII8MisYm9ZfT2Z0s5Ro3s5rkt+nvLAdfC/PYPKzTLalpGSwomSNYJcB9HNMlmhkGzc\
342 1JnLYT4iyUyx6pcZBmCd8bD0iwY/FzcgNDaUmbX9+XDvRA0CgYEAkE7pIPlE71qvfJQgoA9em0gI\
343 LAuE4Pu13aKiJnfft7hIjbK+5kyb3TysZvoyDnb3HOKvInK7vXbKuU4ISgxB2bB3HcYzQMGsz1qJ\
344 2gG0N5hvJpzwwhbhXqFKA4zaaSrw622wDniAK5MlIE0tIAKKP4yxNGjoD2QYjhBGuhvkWKY=\
345 -----END RSA PRIVATE KEY-----"
346 
audio_set_metadata(void * cls,void * session,const void * buffer,int buflen)347 void CAirTunesServer::AudioOutputFunctions::audio_set_metadata(void *cls, void *session, const void *buffer, int buflen)
348 {
349   CAirTunesServer::SetMetadataFromBuffer((const char *)buffer, buflen);
350 }
351 
audio_set_coverart(void * cls,void * session,const void * buffer,int buflen)352 void CAirTunesServer::AudioOutputFunctions::audio_set_coverart(void *cls, void *session, const void *buffer, int buflen)
353 {
354   CAirTunesServer::SetCoverArtFromBuffer((const char *)buffer, buflen);
355 }
356 
357 char session[]="Kodi-AirTunes";
358 
audio_init(void * cls,int bits,int channels,int samplerate)359 void* CAirTunesServer::AudioOutputFunctions::audio_init(void *cls, int bits, int channels, int samplerate)
360 {
361   XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
362   const CURL pathToUrl(XFILE::PipesManager::GetInstance().GetUniquePipeName());
363   pipe->OpenForWrite(pathToUrl);
364   pipe->SetOpenThreshold(300);
365 
366   Demux_BXA_FmtHeader header;
367   strncpy(header.fourcc, "BXA ", 4);
368   header.type = BXA_PACKET_TYPE_FMT_DEMUX;
369   header.bitsPerSample = bits;
370   header.channels = channels;
371   header.sampleRate = samplerate;
372   header.durationMs = 0;
373 
374   if (pipe->Write(&header, sizeof(header)) == 0)
375     return 0;
376 
377   CApplicationMessenger::GetInstance().SendMsg(TMSG_MEDIA_STOP);
378 
379   CFileItem *item = new CFileItem();
380   item->SetPath(pipe->GetName());
381   item->SetMimeType("audio/x-xbmc-pcm");
382   m_streamStarted = true;
383   m_sampleRate = samplerate;
384 
385   CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item));
386 
387   // Not all airplay streams will provide metadata (e.g. if using mirroring,
388   // no metadata will be sent).  If there *is* metadata, it will be received
389   // in a later call to audio_set_metadata/audio_set_coverart.
390   ResetMetadata();
391 
392   // browse for dacp services protocol which gives us the remote control service
393   CZeroconfBrowser::GetInstance()->Start();
394   CZeroconfBrowser::GetInstance()->AddServiceType(ZEROCONF_DACP_SERVICE);
395   CAirTunesServer::EnableActionProcessing(true);
396 
397   return session;//session
398 }
399 
audio_remote_control_id(void * cls,const char * dacp_id,const char * active_remote_header)400 void CAirTunesServer::AudioOutputFunctions::audio_remote_control_id(void *cls, const char *dacp_id, const char *active_remote_header)
401 {
402   if (dacp_id && active_remote_header)
403   {
404     m_dacp_id = dacp_id;
405     m_active_remote_header = active_remote_header;
406   }
407 }
408 
InformPlayerAboutPlayTimes()409 void CAirTunesServer::InformPlayerAboutPlayTimes()
410 {
411   if (m_cachedEndTime > 0)
412   {
413     unsigned int duration = m_cachedEndTime - m_cachedStartTime;
414     unsigned int position = m_cachedCurrentTime - m_cachedStartTime;
415     duration /= m_sampleRate;
416     position /= m_sampleRate;
417 
418     if (g_application.GetAppPlayer().IsPlaying())
419     {
420       g_application.GetAppPlayer().SetTime(position * 1000);
421       g_application.GetAppPlayer().SetTotalTime(duration * 1000);
422 
423       // reset play times now that we have informed the player
424       m_cachedEndTime = 0;
425       m_cachedCurrentTime = 0;
426       m_cachedStartTime = 0;
427 
428     }
429   }
430 }
431 
audio_set_progress(void * cls,void * session,unsigned int start,unsigned int curr,unsigned int end)432 void CAirTunesServer::AudioOutputFunctions::audio_set_progress(void *cls, void *session, unsigned int start, unsigned int curr, unsigned int end)
433 {
434   m_cachedStartTime = start;
435   m_cachedCurrentTime = curr;
436   m_cachedEndTime = end;
437   if (g_application.GetAppPlayer().IsPlaying())
438   {
439     // player is there - directly inform him about play times
440     InformPlayerAboutPlayTimes();
441   }
442 }
443 
SetupRemoteControl()444 void CAirTunesServer::SetupRemoteControl()
445 {
446   // check if we found the remote control service via zeroconf already or
447   // if no valid id and headers was received yet
448   if (m_dacp_id.empty() || m_active_remote_header.empty() || m_pDACP != NULL)
449     return;
450 
451   // check for the service matching m_dacp_id
452   std::vector<CZeroconfBrowser::ZeroconfService> services = CZeroconfBrowser::GetInstance()->GetFoundServices();
453   for (auto service : services )
454   {
455     if (StringUtils::EqualsNoCase(service.GetType(), std::string(ZEROCONF_DACP_SERVICE) + "."))
456     {
457 #define DACP_NAME_PREFIX "iTunes_Ctrl_"
458       // name has the form "iTunes_Ctrl_56B29BB6CB904862"
459       // were we are interested in the 56B29BB6CB904862 identifier
460       if (StringUtils::StartsWithNoCase(service.GetName(), DACP_NAME_PREFIX))
461       {
462         std::vector<std::string> tokens = StringUtils::Split(service.GetName(), DACP_NAME_PREFIX);
463         // if we found the service matching the given identifier
464         if (tokens.size() > 1 && tokens[1] == m_dacp_id)
465         {
466           // resolve the service and save it
467           CZeroconfBrowser::GetInstance()->ResolveService(service);
468           CSingleLock lock(m_dacpLock);
469           // recheck with lock hold
470           if (m_pDACP == NULL)
471           {
472             // we can control the client with this object now
473             m_pDACP = new CDACP(m_active_remote_header, service.GetIP(), service.GetPort());
474           }
475           break;
476         }
477       }
478     }
479   }
480 }
481 
audio_set_volume(void * cls,void * session,float volume)482 void  CAirTunesServer::AudioOutputFunctions::audio_set_volume(void *cls, void *session, float volume)
483 {
484   //volume from -30 - 0 - -144 means mute
485   float volPercent = volume < -30.0f ? 0 : 1 - volume/-30;
486 #ifdef HAS_AIRPLAY
487   CAirPlayServer::backupVolume();
488 #endif
489   if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SERVICES_AIRPLAYVOLUMECONTROL))
490     g_application.SetVolume(volPercent, false);//non-percent volume 0.0-1.0
491 }
492 
audio_process(void * cls,void * session,const void * buffer,int buflen)493 void  CAirTunesServer::AudioOutputFunctions::audio_process(void *cls, void *session, const void *buffer, int buflen)
494 {
495   XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
496   pipe->Write(buffer, buflen);
497 
498   // in case there are some play times cached that are not yet sent to the player - do it here
499   InformPlayerAboutPlayTimes();
500 }
501 
audio_destroy(void * cls,void * session)502 void  CAirTunesServer::AudioOutputFunctions::audio_destroy(void *cls, void *session)
503 {
504   XFILE::CPipeFile *pipe=(XFILE::CPipeFile *)cls;
505   pipe->SetEof();
506   pipe->Close();
507 
508   CAirTunesServer::FreeDACPRemote();
509   m_dacp_id.clear();
510   m_active_remote_header.clear();
511 
512   //fix airplay video for ios5 devices
513   //on ios5 when airplaying video
514   //the client first opens an airtunes stream
515   //while the movie is loading
516   //in that case we don't want to stop the player here
517   //because this would stop the airplaying video
518 #ifdef HAS_AIRPLAY
519   if (!CAirPlayServer::IsPlaying())
520 #endif
521   {
522     CApplicationMessenger::GetInstance().SendMsg(TMSG_MEDIA_STOP);
523     CLog::Log(LOGDEBUG, "AIRTUNES: AirPlay not running - stopping player");
524   }
525 
526   m_streamStarted = false;
527 
528   // no need to browse for dacp services while we don't receive
529   // any airtunes streams...
530   CZeroconfBrowser::GetInstance()->RemoveServiceType(ZEROCONF_DACP_SERVICE);
531   CZeroconfBrowser::GetInstance()->Stop();
532   CAirTunesServer::EnableActionProcessing(false);
533 }
534 
shairplay_log(void * cls,int level,const char * msg)535 void shairplay_log(void *cls, int level, const char *msg)
536 {
537   int xbmcLevel = LOGINFO;
538   if (!CServiceBroker::GetLogging().CanLogComponent(LOGAIRTUNES))
539     return;
540 
541   switch(level)
542   {
543     case RAOP_LOG_EMERG:    // system is unusable
544     case RAOP_LOG_ALERT:    // action must be taken immediately
545     case RAOP_LOG_CRIT:     // critical conditions
546       xbmcLevel = LOGFATAL;
547       break;
548     case RAOP_LOG_ERR:      // error conditions
549       xbmcLevel = LOGERROR;
550       break;
551     case RAOP_LOG_WARNING:  // warning conditions
552       xbmcLevel = LOGWARNING;
553       break;
554     case RAOP_LOG_NOTICE:   // normal but significant condition
555     case RAOP_LOG_INFO:     // informational
556       xbmcLevel = LOGINFO;
557       break;
558     case RAOP_LOG_DEBUG:    // debug-level messages
559       xbmcLevel = LOGDEBUG;
560       break;
561     default:
562       break;
563   }
564     CLog::Log(xbmcLevel, "AIRTUNES: %s", msg);
565 }
566 
StartServer(int port,bool nonlocal,bool usePassword,const std::string & password)567 bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const std::string &password/*=""*/)
568 {
569   bool success = false;
570   std::string pw = password;
571   CNetworkInterface *net = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
572   StopServer(true);
573 
574   if (net)
575   {
576     m_macAddress = net->GetMacAddress();
577     StringUtils::Replace(m_macAddress, ":","");
578     while (m_macAddress.size() < 12)
579     {
580       m_macAddress = '0' + m_macAddress;
581     }
582   }
583   else
584   {
585     m_macAddress = "000102030405";
586   }
587 
588   if (!usePassword)
589   {
590     pw.clear();
591   }
592 
593   ServerInstance = new CAirTunesServer(port, nonlocal);
594   if (ServerInstance->Initialize(pw))
595   {
596     success = true;
597     std::string appName = StringUtils::Format("%s@%s",
598                                              m_macAddress.c_str(),
599                                              CSysInfo::GetDeviceName().c_str());
600 
601     std::vector<std::pair<std::string, std::string> > txt;
602     txt.emplace_back("txtvers", "1");
603     txt.emplace_back("cn", "0,1");
604     txt.emplace_back("ch", "2");
605     txt.emplace_back("ek", "1");
606     txt.emplace_back("et", "0,1");
607     txt.emplace_back("sv", "false");
608     txt.emplace_back("tp", "UDP");
609     txt.emplace_back("sm", "false");
610     txt.emplace_back("ss", "16");
611     txt.emplace_back("sr", "44100");
612     txt.emplace_back("pw", usePassword ? "true" : "false");
613     txt.emplace_back("vn", "3");
614     txt.emplace_back("da", "true");
615     txt.emplace_back("md", "0,1,2");
616     txt.emplace_back("am", "Kodi,1");
617     txt.emplace_back("vs", "130.14");
618 
619     CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt);
620   }
621 
622   return success;
623 }
624 
StopServer(bool bWait)625 void CAirTunesServer::StopServer(bool bWait)
626 {
627   if (ServerInstance)
628   {
629     ServerInstance->Deinitialize();
630     if (bWait)
631     {
632       delete ServerInstance;
633       ServerInstance = NULL;
634     }
635 
636     CZeroconf::GetInstance()->RemoveService("servers.airtunes");
637   }
638 }
639 
IsRunning()640 bool CAirTunesServer::IsRunning()
641 {
642   if (ServerInstance == NULL)
643     return false;
644 
645   return ServerInstance->IsRAOPRunningInternal();
646 }
647 
IsRAOPRunningInternal()648 bool CAirTunesServer::IsRAOPRunningInternal()
649 {
650   if (m_pRaop)
651   {
652     return raop_is_running(m_pRaop) != 0;
653   }
654 
655   return false;
656 }
657 
CAirTunesServer(int port,bool nonlocal)658 CAirTunesServer::CAirTunesServer(int port, bool nonlocal)
659   : CThread("AirTunesActionThread")
660 {
661   m_port = port;
662   m_pPipe = new XFILE::CPipeFile;
663 }
664 
~CAirTunesServer()665 CAirTunesServer::~CAirTunesServer()
666 {
667   delete m_pPipe;
668 }
669 
RegisterActionListener(bool doRegister)670 void CAirTunesServer::RegisterActionListener(bool doRegister)
671 {
672   if (doRegister)
673   {
674     CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
675     g_application.RegisterActionListener(this);
676     ServerInstance->Create();
677   }
678   else
679   {
680     CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
681     g_application.UnregisterActionListener(this);
682     ServerInstance->StopThread(true);
683   }
684 }
685 
Initialize(const std::string & password)686 bool CAirTunesServer::Initialize(const std::string &password)
687 {
688   bool ret = false;
689 
690   Deinitialize();
691 
692   raop_callbacks_t ao = {};
693   ao.cls = m_pPipe;
694   ao.audio_init = AudioOutputFunctions::audio_init;
695   ao.audio_set_volume = AudioOutputFunctions::audio_set_volume;
696   ao.audio_set_metadata = AudioOutputFunctions::audio_set_metadata;
697   ao.audio_set_coverart = AudioOutputFunctions::audio_set_coverart;
698   ao.audio_process = AudioOutputFunctions::audio_process;
699   ao.audio_destroy = AudioOutputFunctions::audio_destroy;
700   ao.audio_remote_control_id = AudioOutputFunctions::audio_remote_control_id;
701   ao.audio_set_progress = AudioOutputFunctions::audio_set_progress;
702   m_pRaop = raop_init(1, &ao, RSA_KEY, nullptr); //1 - we handle one client at a time max
703 
704   if (m_pRaop)
705   {
706     char macAdr[6];
707     unsigned short port = (unsigned short)m_port;
708 
709     raop_set_log_level(m_pRaop, RAOP_LOG_WARNING);
710     if (CServiceBroker::GetLogging().CanLogComponent(LOGAIRTUNES))
711     {
712       raop_set_log_level(m_pRaop, RAOP_LOG_DEBUG);
713     }
714 
715     raop_set_log_callback(m_pRaop, shairplay_log, NULL);
716 
717     CNetworkInterface* net = CServiceBroker::GetNetwork().GetFirstConnectedInterface();
718 
719     if (net)
720     {
721       net->GetMacAddressRaw(macAdr);
722     }
723 
724     ret = raop_start(m_pRaop, &port, macAdr, 6, password.c_str()) >= 0;
725   }
726   return ret;
727 }
728 
Deinitialize()729 void CAirTunesServer::Deinitialize()
730 {
731   RegisterActionListener(false);
732 
733   if (m_pRaop)
734   {
735     raop_stop(m_pRaop);
736     raop_destroy(m_pRaop);
737     m_pRaop = nullptr;
738   }
739 }
740