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