only call IPlayerCallback::OnPlayBackSpeedChanged if the speed has actually changed
[vuplus_xbmc] / xbmc / network / AirTunesServer.cpp
1 /*
2  * Many concepts and protocol specification in this code are taken
3  * from Shairport, by James Laird.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2.1 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18  */
19
20 #pragma GCC diagnostic ignored "-Wwrite-strings"
21
22 #include "AirTunesServer.h"
23
24 #ifdef HAS_AIRTUNES
25
26 #include "utils/log.h"
27 #include "utils/StdString.h"
28 #include "network/Zeroconf.h"
29 #include "ApplicationMessenger.h"
30 #include "filesystem/FilePipe.h"
31 #include "Application.h"
32 #include "cores/paplayer/BXAcodec.h"
33 #include "music/tags/MusicInfoTag.h"
34 #include "FileItem.h"
35 #include "utils/Variant.h"
36 #include "settings/AdvancedSettings.h"
37
38 using namespace XFILE;
39
40 DllLibShairport *CAirTunesServer::m_pLibShairport = NULL;
41 CAirTunesServer *CAirTunesServer::ServerInstance = NULL;
42 CStdString CAirTunesServer::m_macAddress;
43
44 struct ao_device_xbmc
45 {
46   XFILE::CFilePipe *pipe;
47 };
48
49 //audio output interface
50 void CAirTunesServer::AudioOutputFunctions::ao_initialize(void)
51 {
52 }
53
54 int CAirTunesServer::AudioOutputFunctions::ao_play(ao_device *device, char *output_samples, uint32_t num_bytes)
55 {
56   if (!device)
57     return 0;
58
59   /*if (num_bytes && g_application.m_pPlayer)
60     g_application.m_pPlayer->SetCaching(CACHESTATE_NONE);*///TODO
61
62   ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
63
64 #define NUM_OF_BYTES 64
65
66   unsigned int sentBytes = 0;
67   unsigned char buf[NUM_OF_BYTES];
68   while (sentBytes < num_bytes)
69   {
70     int n = (num_bytes - sentBytes < NUM_OF_BYTES ? num_bytes - sentBytes : NUM_OF_BYTES);
71     memcpy(buf, (char*) output_samples + sentBytes, n);
72
73     if (device_xbmc->pipe->Write(buf, n) == 0)
74       return 0;
75
76     sentBytes += n;
77   }
78
79   return 1;
80 }
81
82 int CAirTunesServer::AudioOutputFunctions::ao_default_driver_id(void)
83 {
84   return 0;
85 }
86
87 ao_device* CAirTunesServer::AudioOutputFunctions::ao_open_live(int driver_id, ao_sample_format *format,
88     ao_option *option)
89 {
90   ao_device_xbmc* device = new ao_device_xbmc();
91
92   device->pipe = new XFILE::CFilePipe;
93   device->pipe->OpenForWrite(XFILE::PipesManager::GetInstance().GetUniquePipeName());
94   device->pipe->SetOpenThreashold(300);
95
96   BXA_FmtHeader header;
97   strncpy(header.fourcc, "BXA ", 4);
98   header.type = BXA_PACKET_TYPE_FMT;
99   header.bitsPerSample = format->bits;
100   header.channels = format->channels;
101   header.sampleRate = format->rate;
102   header.durationMs = 0;
103
104   if (device->pipe->Write(&header, sizeof(header)) == 0)
105     return 0;
106
107   ThreadMessage tMsg = { TMSG_MEDIA_STOP };
108   g_application.getApplicationMessenger().SendMessage(tMsg, true);
109
110   CFileItem item;
111   item.SetPath(device->pipe->GetName());
112   item.SetMimeType("audio/x-xbmc-pcm");
113   item.SetProperty("isradio", true);
114   item.SetProperty("no-skip", true);
115   item.SetProperty("no-pause", true);
116
117   if (ao_get_option(option, "artist"))
118     item.GetMusicInfoTag()->SetArtist(ao_get_option(option, "artist"));
119
120   if (ao_get_option(option, "album"))
121     item.GetMusicInfoTag()->SetAlbum(ao_get_option(option, "album"));
122
123   if (ao_get_option(option, "name"))
124     item.GetMusicInfoTag()->SetTitle(ao_get_option(option, "name"));
125
126   g_application.getApplicationMessenger().PlayFile(item);
127
128   ThreadMessage tMsg2 = { TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VISUALISATION, 0 };
129   g_application.getApplicationMessenger().SendMessage(tMsg2, true);
130
131   return (ao_device*) device;
132 }
133
134 int CAirTunesServer::AudioOutputFunctions::ao_close(ao_device *device)
135 {
136   ao_device_xbmc* device_xbmc = (ao_device_xbmc*) device;
137   device_xbmc->pipe->SetEof();
138   device_xbmc->pipe->Close();
139   delete device_xbmc->pipe;
140
141   ThreadMessage tMsg = { TMSG_MEDIA_STOP };
142   g_application.getApplicationMessenger().SendMessage(tMsg, true);
143
144   delete device_xbmc;
145
146   return 0;
147 }
148
149 /* -- Device Setup/Playback/Teardown -- */
150 int CAirTunesServer::AudioOutputFunctions::ao_append_option(ao_option **options, const char *key, const char *value)
151 {
152   ao_option *op, *list;
153
154   op = (ao_option*) calloc(1,sizeof(ao_option));
155   if (op == NULL) return 0;
156
157   op->key = strdup(key);
158   op->value = strdup(value?value:"");
159   op->next = NULL;
160
161   if ((list = *options) != NULL)
162   {
163     list = *options;
164     while (list->next != NULL)
165       list = list->next;
166     list->next = op;
167   }
168   else
169   {
170     *options = op;
171   }
172
173   return 1;
174 }
175
176 void CAirTunesServer::AudioOutputFunctions::ao_free_options(ao_option *options)
177 {
178   ao_option *rest;
179
180   while (options != NULL)
181   {
182     rest = options->next;
183     free(options->key);
184     free(options->value);
185     free(options);
186     options = rest;
187   }
188 }
189
190 char* CAirTunesServer::AudioOutputFunctions::ao_get_option(ao_option *options, const char* key)
191 {
192
193   while (options != NULL)
194   {
195     if (strcmp(options->key, key) == 0)
196       return options->value;
197     options = options->next;
198   }
199
200   return NULL;
201 }
202
203 bool CAirTunesServer::StartServer(int port, bool nonlocal, bool usePassword, const CStdString &password/*=""*/)
204 {
205   bool success = false;
206   CStdString pw = password;
207   CNetworkInterface *net = g_application.getNetwork().GetFirstConnectedInterface();
208   StopServer(true);
209
210   if (net)
211   {
212     m_macAddress = net->GetMacAddress();
213     m_macAddress.Replace(":","");
214     while (m_macAddress.size() < 12)
215     {
216       m_macAddress = CStdString("0") + m_macAddress;
217     }
218   }
219   else
220   {
221     m_macAddress = "000102030405";
222   }
223
224   if (!usePassword)
225   {
226     pw.Empty();
227   }
228
229   ServerInstance = new CAirTunesServer(port, nonlocal);
230   if (ServerInstance->Initialize(password))
231   {
232     ServerInstance->Create();
233     success = true;
234   }
235
236   if (success)
237   {
238     CStdString appName;
239     appName.Format("%s@XBMC", m_macAddress.c_str());
240
241     std::map<std::string, std::string> txt;
242     txt["cn"] = "0,1";
243     txt["ch"] = "2";
244     txt["ek"] = "1";
245     txt["et"] = "0,1";
246     txt["sv"] = "false";
247     txt["tp"] = "UDP";
248     txt["sm"] = "false";
249     txt["ss"] = "16";
250     txt["sr"] = "44100";
251     txt["pw"] = "false";
252     txt["vn"] = "3";
253     txt["txtvers"] = "1";
254
255     CZeroconf::GetInstance()->PublishService("servers.airtunes", "_raop._tcp", appName, port, txt);
256   }
257
258   return success;
259 }
260
261 void CAirTunesServer::StopServer(bool bWait)
262 {
263   if (ServerInstance)
264   {
265     if (m_pLibShairport->IsLoaded())
266     {
267       m_pLibShairport->shairport_exit();
268     }
269     ServerInstance->StopThread(bWait);
270     ServerInstance->Deinitialize();
271     if (bWait)
272     {
273       delete ServerInstance;
274       ServerInstance = NULL;
275     }
276
277     CZeroconf::GetInstance()->RemoveService("servers.airtunes");
278   }
279 }
280
281 CAirTunesServer::CAirTunesServer(int port, bool nonlocal)
282 {
283   m_port = port;
284   m_pLibShairport = new DllLibShairport();
285 }
286
287 CAirTunesServer::~CAirTunesServer()
288 {
289   if (m_pLibShairport->IsLoaded())
290   {
291     m_pLibShairport->Unload();
292   }
293   delete m_pLibShairport;
294 }
295
296 void CAirTunesServer::Process()
297 {
298   m_bStop = false;
299
300   while (!m_bStop && m_pLibShairport->shairport_is_running())
301   {
302     m_pLibShairport->shairport_loop();
303   }
304 }
305
306 int shairport_log(const char* msg, size_t msgSize)
307 {
308   if( g_advancedSettings.m_logEnableAirtunes)
309   {
310     CLog::Log(LOGDEBUG, "AIRTUNES: %s", msg);
311   }
312   return 1;
313 }
314
315 bool CAirTunesServer::Initialize(const CStdString &password)
316 {
317   bool ret = false;
318   int numArgs = 3;
319   CStdString hwStr;
320   CStdString pwStr;
321
322   Deinitialize();
323
324   hwStr.Format("--mac=%s", m_macAddress.c_str());
325   pwStr.Format("--password=%s",password.c_str());
326
327   if (!password.empty())
328   {
329     numArgs++;
330   }
331
332   char *argv[] = { "--apname=XBMC", "--server_port=5000", (char*) hwStr.c_str(), (char *)pwStr.c_str(), NULL };
333
334   if (m_pLibShairport->Load())
335   {
336
337     struct AudioOutput ao;
338     ao.ao_initialize = AudioOutputFunctions::ao_initialize;
339     ao.ao_play = AudioOutputFunctions::ao_play;
340     ao.ao_default_driver_id = AudioOutputFunctions::ao_default_driver_id;
341     ao.ao_open_live = AudioOutputFunctions::ao_open_live;
342     ao.ao_close = AudioOutputFunctions::ao_close;
343     ao.ao_append_option = AudioOutputFunctions::ao_append_option;
344     ao.ao_free_options = AudioOutputFunctions::ao_free_options;
345     ao.ao_get_option = AudioOutputFunctions::ao_get_option;
346     struct printfPtr funcPtr;
347     funcPtr.extprintf = shairport_log;
348
349     m_pLibShairport->EnableDelayedUnload(false);
350     m_pLibShairport->shairport_set_ao(&ao);
351     m_pLibShairport->shairport_set_printf(&funcPtr);
352     m_pLibShairport->shairport_main(numArgs, argv);
353     ret = true;
354   }
355   return ret;
356 }
357
358 void CAirTunesServer::Deinitialize()
359 {
360   if (m_pLibShairport && m_pLibShairport->IsLoaded())
361   {
362     m_pLibShairport->shairport_exit();
363     m_pLibShairport->Unload();
364   }
365 }
366
367 #endif