MythTV  master
mythairplayserver.cpp
Go to the documentation of this file.
1 // TODO
2 // locking ?
3 // race on startup?
4 // http date format and locale
5 
6 #include <chrono>
7 #include <vector>
8 
9 #include <QTcpSocket>
10 #include <QNetworkInterface>
11 #include <QCoreApplication>
12 #include <QKeyEvent>
13 #include <QCryptographicHash>
14 #include <QTimer>
15 #include <QUrlQuery>
16 
17 #include "mthread.h"
18 #include "mythdate.h"
19 #include "mythlogging.h"
20 #include "mythcorecontext.h"
21 #include "mythuiactions.h"
22 #include "mythuistatetracker.h"
23 #include "mythbinaryplist.h"
24 #include "tv_play.h"
25 #include "mythmainwindow.h"
26 #include "tv_actions.h"
27 
28 #include "bonjourregister.h"
29 #include "mythairplayserver.h"
30 
33 QMutex* MythAirplayServer::gMythAirplayServerMutex = new QMutex(QMutex::Recursive);
34 
35 #define LOC QString("AirPlay: ")
36 
37 #define HTTP_STATUS_OK 200
38 #define HTTP_STATUS_SWITCHING_PROTOCOLS 101
39 #define HTTP_STATUS_NOT_IMPLEMENTED 501
40 #define HTTP_STATUS_UNAUTHORIZED 401
41 #define HTTP_STATUS_NOT_FOUND 404
42 
43 #define AIRPLAY_SERVER_VERSION_STR "115.2"
44 #define SERVER_INFO QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
45 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
46 "<plist version=\"1.0\">\r\n"\
47 "<dict>\r\n"\
48 "<key>deviceid</key>\r\n"\
49 "<string>%1</string>\r\n"\
50 "<key>features</key>\r\n"\
51 "<integer>119</integer>\r\n"\
52 "<key>model</key>\r\n"\
53 "<string>MythTV,1</string>\r\n"\
54 "<key>protovers</key>\r\n"\
55 "<string>1.0</string>\r\n"\
56 "<key>srcvers</key>\r\n"\
57 "<string>" AIRPLAY_SERVER_VERSION_STR "</string>\r\n"\
58 "</dict>\r\n"\
59 "</plist>\r\n")
60 
61 #define EVENT_INFO QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r\n"\
62 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\r\n"\
63 "<plist version=\"1.0\">\r\n"\
64 "<dict>\r\n"\
65 "<key>category</key>\r\n"\
66 "<string>video</string>\r\n"\
67 "<key>state</key>\r\n"\
68 "<string>%1</string>\r\n"\
69 "</dict>\r\n"\
70 "</plist>\r\n")
71 
72 #define PLAYBACK_INFO QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
73 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
74 "<plist version=\"1.0\">\r\n"\
75 "<dict>\r\n"\
76 "<key>duration</key>\r\n"\
77 "<real>%1</real>\r\n"\
78 "<key>loadedTimeRanges</key>\r\n"\
79 "<array>\r\n"\
80 "\t\t<dict>\r\n"\
81 "\t\t\t<key>duration</key>\r\n"\
82 "\t\t\t<real>%2</real>\r\n"\
83 "\t\t\t<key>start</key>\r\n"\
84 "\t\t\t<real>0.0</real>\r\n"\
85 "\t\t</dict>\r\n"\
86 "</array>\r\n"\
87 "<key>playbackBufferEmpty</key>\r\n"\
88 "<true/>\r\n"\
89 "<key>playbackBufferFull</key>\r\n"\
90 "<false/>\r\n"\
91 "<key>playbackLikelyToKeepUp</key>\r\n"\
92 "<true/>\r\n"\
93 "<key>position</key>\r\n"\
94 "<real>%3</real>\r\n"\
95 "<key>rate</key>\r\n"\
96 "<real>%4</real>\r\n"\
97 "<key>readyToPlay</key>\r\n"\
98 "<true/>\r\n"\
99 "<key>seekableTimeRanges</key>\r\n"\
100 "<array>\r\n"\
101 "\t\t<dict>\r\n"\
102 "\t\t\t<key>duration</key>\r\n"\
103 "\t\t\t<real>%1</real>\r\n"\
104 "\t\t\t<key>start</key>\r\n"\
105 "\t\t\t<real>0.0</real>\r\n"\
106 "\t\t</dict>\r\n"\
107 "</array>\r\n"\
108 "</dict>\r\n"\
109 "</plist>\r\n")
110 
111 #define NOT_READY QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
112 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
113 "<plist version=\"1.0\">\r\n"\
114 "<dict>\r\n"\
115 "<key>readyToPlay</key>\r\n"\
116 "<false/>\r\n"\
117 "</dict>\r\n"\
118 "</plist>\r\n")
119 
121 {
122  QString key = "AirPlayId";
123  QString id = gCoreContext->GetSetting(key);
124  int size = id.size();
125  if (size == 12 && id.toUpper() == id)
126  return id;
127  if (size != 12)
128  {
129  QByteArray ba;
130  for (int i = 0; i < AIRPLAY_HARDWARE_ID_SIZE; i++)
131  ba.append((MythRandom() % 80) + 33);
132  id = ba.toHex();
133  }
134  id = id.toUpper();
135 
136  gCoreContext->SaveSetting(key, id);
137  return id;
138 }
139 
140 QString GenerateNonce(void)
141 {
142 #if QT_VERSION >= QT_VERSION_CHECK(5,10,0)
143  auto *randgen = QRandomGenerator::global();
144  std::array<uint32_t,4> nonceParts {
145  randgen->generate(), randgen->generate(),
146  randgen->generate(), randgen->generate() };
147 #else
148  std::srand(std::time(nullptr));
149  std::array<int32_t,4> nonceParts {
150  std::rand(), std::rand(), std::rand(), std::rand() };
151 #endif
152 
153  QString nonce;
154  nonce = QString::number(nonceParts[0], 16).toUpper();
155  nonce += QString::number(nonceParts[1], 16).toUpper();
156  nonce += QString::number(nonceParts[2], 16).toUpper();
157  nonce += QString::number(nonceParts[3], 16).toUpper();
158  return nonce;
159 }
160 
161 QByteArray DigestMd5Response(const QString& response, const QString& option,
162  const QString& nonce, const QString& password,
163  QByteArray &auth)
164 {
165  int authStart = response.indexOf("response=\"") + 10;
166  int authLength = response.indexOf("\"", authStart) - authStart;
167  auth = response.mid(authStart, authLength).toLatin1();
168 
169  int uriStart = response.indexOf("uri=\"") + 5;
170  int uriLength = response.indexOf("\"", uriStart) - uriStart;
171  QByteArray uri = response.mid(uriStart, uriLength).toLatin1();
172 
173  int userStart = response.indexOf("username=\"") + 10;
174  int userLength = response.indexOf("\"", userStart) - userStart;
175  QByteArray user = response.mid(userStart, userLength).toLatin1();
176 
177  int realmStart = response.indexOf("realm=\"") + 7;
178  int realmLength = response.indexOf("\"", realmStart) - realmStart;
179  QByteArray realm = response.mid(realmStart, realmLength).toLatin1();
180 
181  QByteArray passwd = password.toLatin1();
182 
183  QCryptographicHash hash(QCryptographicHash::Md5);
184  hash.addData(user);
185  hash.addData(":", 1);
186  hash.addData(realm);
187  hash.addData(":", 1);
188  hash.addData(passwd);
189  QByteArray ha1 = hash.result();
190  ha1 = ha1.toHex();
191 
192  // calculate H(A2)
193  hash.reset();
194  hash.addData(option.toLatin1());
195  hash.addData(":", 1);
196  hash.addData(uri);
197  QByteArray ha2 = hash.result().toHex();
198 
199  // calculate response
200  hash.reset();
201  hash.addData(ha1);
202  hash.addData(":", 1);
203  hash.addData(nonce.toLatin1());
204  hash.addData(":", 1);
205  hash.addData(ha2);
206  return hash.result().toHex();
207 }
208 
210 {
211  public:
212  explicit APHTTPRequest(QByteArray& data) : m_data(data)
213  {
214  Process();
215  Check();
216  }
217  ~APHTTPRequest() = default;
218 
219  QByteArray& GetMethod(void) { return m_method; }
220  QByteArray& GetURI(void) { return m_uri; }
221  QByteArray& GetBody(void) { return m_body; }
222  QMap<QByteArray,QByteArray>& GetHeaders(void)
223  { return m_headers; }
224 
225  void Append(QByteArray& data)
226  {
227  m_body.append(data);
228  Check();
229  }
230 
231  QByteArray GetQueryValue(const QByteArray& key)
232  {
233  auto samekey = [key](const auto& query) { return query.first == key; };;
234  auto query = std::find_if(m_queries.cbegin(), m_queries.cend(), samekey);
235  return (query != m_queries.cend()) ? query->second : "";
236  }
237 
238  QMap<QByteArray,QByteArray> GetHeadersFromBody(void)
239  {
240  QMap<QByteArray,QByteArray> result;
241  QList<QByteArray> lines = m_body.split('\n');;
242  for (const QByteArray& line : qAsConst(lines))
243  {
244  int index = line.indexOf(":");
245  if (index > 0)
246  {
247  result.insert(line.left(index).trimmed(),
248  line.mid(index + 1).trimmed());
249  }
250  }
251  return result;
252  }
253 
254  bool IsComplete(void) const
255  {
256  return !m_incomingPartial;
257  }
258 
259  private:
260  QByteArray GetLine(void)
261  {
262  int next = m_data.indexOf("\r\n", m_readPos);
263  if (next < 0) return QByteArray();
264  QByteArray line = m_data.mid(m_readPos, next - m_readPos);
265  m_readPos = next + 2;
266  return line;
267  }
268 
269  void Process(void)
270  {
271  if (m_data.isEmpty())
272  return;
273 
274  // request line
275  QByteArray line = GetLine();
276  if (line.isEmpty())
277  return;
278  QList<QByteArray> vals = line.split(' ');
279  if (vals.size() < 3)
280  return;
281  m_method = vals[0].trimmed();
282  QUrl url = QUrl::fromEncoded(vals[1].trimmed());
283  m_uri = url.path(QUrl::FullyEncoded).toLocal8Bit();
284  m_queries.clear();
285  {
286  QList<QPair<QString, QString> > items =
287  QUrlQuery(url).queryItems(QUrl::FullyEncoded);
288  QList<QPair<QString, QString> >::ConstIterator it = items.constBegin();
289  for ( ; it != items.constEnd(); ++it)
290  m_queries << qMakePair(it->first.toLatin1(), it->second.toLatin1());
291  }
292  if (m_method.isEmpty() || m_uri.isEmpty())
293  return;
294 
295  // headers
296  while (!(line = GetLine()).isEmpty())
297  {
298  int index = line.indexOf(":");
299  if (index > 0)
300  {
301  m_headers.insert(line.left(index).trimmed(),
302  line.mid(index + 1).trimmed());
303  }
304  }
305 
306  // body?
307  if (m_headers.contains("Content-Length"))
308  {
309  int remaining = m_data.size() - m_readPos;
310  m_size = m_headers["Content-Length"].toInt();
311  if (m_size > 0 && remaining > 0)
312  {
313  m_body = m_data.mid(m_readPos, m_size);
314  m_readPos += m_body.size();
315  }
316  }
317  }
318 
319  void Check(void)
320  {
321  if (!m_incomingPartial)
322  {
323  LOG(VB_GENERAL, LOG_DEBUG, LOC +
324  QString("HTTP Request:\n%1").arg(m_data.data()));
325  }
326  if (m_body.size() < m_size)
327  {
328  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
329  QString("AP HTTPRequest: Didn't read entire buffer."
330  "Left to receive: %1 (got %2 of %3) body=%4")
331  .arg(m_size-m_body.size()).arg(m_readPos).arg(m_size).arg(m_body.size()));
332  m_incomingPartial = true;
333  return;
334  }
335  m_incomingPartial = false;
336  }
337 
338  int m_readPos {0};
339  QByteArray m_data;
340  QByteArray m_method;
341  QByteArray m_uri;
342  QList<QPair<QByteArray, QByteArray> > m_queries;
343  QMap<QByteArray,QByteArray> m_headers;
344  QByteArray m_body;
345  int m_size {0};
346  bool m_incomingPartial {false};
347 };
348 
350 {
351  QMutexLocker locker(gMythAirplayServerMutex);
352 
353  // create the server thread
355  gMythAirplayServerThread = new MThread("AirplayServer");
357  {
358  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create airplay thread.");
359  return false;
360  }
361 
362  // create the server object
363  if (!gMythAirplayServer)
365  if (!gMythAirplayServer)
366  {
367  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create airplay object.");
368  return false;
369  }
370 
371  // start the thread
373  {
375  QObject::connect(
376  gMythAirplayServerThread->qthread(), &QThread::started,
378  QObject::connect(
379  gMythAirplayServerThread->qthread(), &QThread::finished,
381  gMythAirplayServerThread->start(QThread::LowestPriority);
382  }
383 
384  LOG(VB_GENERAL, LOG_INFO, LOC + "Created airplay objects.");
385  return true;
386 }
387 
389 {
390  LOG(VB_GENERAL, LOG_INFO, LOC + "Cleaning up.");
391 
392  QMutexLocker locker(gMythAirplayServerMutex);
394  {
397  }
399  gMythAirplayServerThread = nullptr;
400 
401  delete gMythAirplayServer;
402  gMythAirplayServer = nullptr;
403 }
404 
405 
407 {
408  delete m_lock;
409  m_lock = nullptr;
410 }
411 
413 {
414  QMutexLocker locker(m_lock);
415 
416  // invalidate
417  m_valid = false;
418 
419  // stop Bonjour Service Updater
420  if (m_serviceRefresh)
421  {
422  m_serviceRefresh->stop();
423  delete m_serviceRefresh;
424  m_serviceRefresh = nullptr;
425  }
426 
427  // disconnect from mDNS
428  delete m_bonjour;
429  m_bonjour = nullptr;
430 
431  // disconnect connections
432  for (QTcpSocket* connection : qAsConst(m_sockets))
433  {
434  disconnect(connection, nullptr, nullptr, nullptr);
435  delete connection;
436  }
437  m_sockets.clear();
438 
439  // remove all incoming buffers
440  for (APHTTPRequest* request : qAsConst(m_incoming))
441  {
442  delete request;
443  }
444  m_incoming.clear();
445 }
446 
448 {
449  QMutexLocker locker(m_lock);
450 
451  // already started?
452  if (m_valid)
453  return;
454 
455  // join the dots
456  connect(this, &ServerPool::newConnection,
458 
459  // start listening for connections
460  // try a few ports in case the default is in use
461  int baseport = m_setupPort;
463  if (m_setupPort < 0)
464  {
465  LOG(VB_GENERAL, LOG_ERR, LOC +
466  "Failed to find a port for incoming connections.");
467  }
468  else
469  {
470  // announce service
471  m_bonjour = new BonjourRegister(this);
472  if (!m_bonjour)
473  {
474  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Bonjour object.");
475  return;
476  }
477 
478  // give each frontend a unique name
479  int multiple = m_setupPort - baseport;
480  if (multiple > 0)
481  m_name += QString::number(multiple);
482 
483  QByteArray name = m_name.toUtf8();
484  name.append(" on ");
485  name.append(gCoreContext->GetHostName());
486  QByteArray type = "_airplay._tcp";
487  QByteArray txt;
488  txt.append(26); txt.append("deviceid="); txt.append(GetMacAddress());
489  // supposed to be: 0: video, 1:Phone, 3: Volume Control, 4: HLS
490  // 9: Audio, 10: ? (but important without it it fails) 11: Audio redundant
491  txt.append(13); txt.append("features=0xF7");
492  txt.append(14); txt.append("model=MythTV,1");
493  txt.append(13); txt.append("srcvers=" AIRPLAY_SERVER_VERSION_STR);
494 
495  if (!m_bonjour->Register(m_setupPort, type, name, txt))
496  {
497  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register service.");
498  return;
499  }
500  if (!m_serviceRefresh)
501  {
502  m_serviceRefresh = new QTimer();
504  }
505  // Will force a Bonjour refresh in two seconds
506  m_serviceRefresh->start(2s);
507  }
508  m_valid = true;
509 }
510 
512 {
514  m_serviceRefresh->start(10s);
515 }
516 
518 {
519  Teardown();
520 }
521 
523 {
524  QMutexLocker locker(m_lock);
525  LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection from %1:%2")
526  .arg(client->peerAddress().toString()).arg(client->peerPort()));
527 
528  gCoreContext->SendSystemEvent(QString("AIRPLAY_NEW_CONNECTION"));
529  m_sockets.append(client);
530  connect(client, &QAbstractSocket::disconnected,
531  this, qOverload<>(&MythAirplayServer::deleteConnection));
532  connect(client, &QIODevice::readyRead, this, &MythAirplayServer::read);
533 }
534 
536 {
537  QMutexLocker locker(m_lock);
538  auto *socket = qobject_cast<QTcpSocket *>(sender());
539  if (!socket)
540  return;
541 
542  if (!m_sockets.contains(socket))
543  return;
544 
545  deleteConnection(socket);
546 }
547 
548 void MythAirplayServer::deleteConnection(QTcpSocket *socket)
549 {
550  // must have lock
551  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing connection %1:%2")
552  .arg(socket->peerAddress().toString()).arg(socket->peerPort()));
553  gCoreContext->SendSystemEvent(QString("AIRPLAY_DELETE_CONNECTION"));
554  m_sockets.removeOne(socket);
555 
556  QByteArray remove;
557  QMutableHashIterator<QByteArray,AirplayConnection> it(m_connections);
558  while (it.hasNext())
559  {
560  it.next();
561  if (it.value().m_reverseSocket == socket)
562  it.value().m_reverseSocket = nullptr;
563  if (it.value().m_controlSocket == socket)
564  it.value().m_controlSocket = nullptr;
565  if (!it.value().m_reverseSocket &&
566  !it.value().m_controlSocket)
567  {
568  if (!it.value().m_stopped)
569  {
570  StopSession(it.key());
571  }
572  remove = it.key();
573  break;
574  }
575  }
576 
577  if (!remove.isEmpty())
578  {
579  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing session '%1'")
580  .arg(remove.data()));
581  m_connections.remove(remove);
582 
583  MythNotification n(tr("Client disconnected"), tr("AirPlay"),
584  tr("from %1").arg(socket->peerAddress().toString()));
585  // Don't show it during playback
588  }
589 
590  socket->deleteLater();
591 
592  if (m_incoming.contains(socket))
593  {
594  delete m_incoming[socket];
595  m_incoming.remove(socket);
596  }
597 }
598 
600 {
601  QMutexLocker locker(m_lock);
602  auto *socket = qobject_cast<QTcpSocket *>(sender());
603  if (!socket)
604  return;
605 
606  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Read for %1:%2")
607  .arg(socket->peerAddress().toString()).arg(socket->peerPort()));
608 
609  QByteArray buf = socket->readAll();
610 
611  if (!m_incoming.contains(socket))
612  {
613  auto *request = new APHTTPRequest(buf);
614  m_incoming.insert(socket, request);
615  }
616  else
617  {
618  m_incoming[socket]->Append(buf);
619  }
620  if (!m_incoming[socket]->IsComplete())
621  return;
622  HandleResponse(m_incoming[socket], socket);
623  if (m_incoming.contains(socket))
624  {
625  delete m_incoming[socket];
626  m_incoming.remove(socket);
627  }
628 }
629 
630 QByteArray MythAirplayServer::StatusToString(int status)
631 {
632  switch (status)
633  {
634  case HTTP_STATUS_OK: return "OK";
635  case HTTP_STATUS_SWITCHING_PROTOCOLS: return "Switching Protocols";
636  case HTTP_STATUS_NOT_IMPLEMENTED: return "Not Implemented";
637  case HTTP_STATUS_UNAUTHORIZED: return "Unauthorized";
638  case HTTP_STATUS_NOT_FOUND: return "Not Found";
639  }
640  return "";
641 }
642 
644  QTcpSocket *socket)
645 {
646  if (!socket)
647  return;
648  QHostAddress addr = socket->peerAddress();
649  QByteArray session;
650  QByteArray header;
651  QString body;
652  int status = HTTP_STATUS_OK;
653  QByteArray content_type;
654 
655  if (req->GetURI() != "/playback-info")
656  {
657  LOG(VB_GENERAL, LOG_INFO, LOC +
658  QString("Method: %1 URI: %2")
659  .arg(req->GetMethod().data()).arg(req->GetURI().data()));
660  }
661  else
662  {
663  LOG(VB_GENERAL, LOG_DEBUG, LOC +
664  QString("Method: %1 URI: %2")
665  .arg(req->GetMethod().data()).arg(req->GetURI().data()));
666  }
667 
668  if (req->GetURI() == "200" || req->GetMethod().startsWith("HTTP"))
669  return;
670 
671  if (!req->GetHeaders().contains("X-Apple-Session-ID"))
672  {
673  LOG(VB_GENERAL, LOG_DEBUG, LOC +
674  QString("No session ID in http request. "
675  "Connection from iTunes? Using IP %1").arg(addr.toString()));
676  }
677  else
678  {
679  session = req->GetHeaders()["X-Apple-Session-ID"];
680  }
681 
682  if (session.size() == 0)
683  {
684  // No session ID, use IP address instead
685  session = addr.toString().toLatin1();
686  }
687  if (!m_connections.contains(session))
688  {
689  AirplayConnection apcon;
690  m_connections.insert(session, apcon);
691  }
692 
693  if (req->GetURI() == "/reverse")
694  {
695  QTcpSocket *s = m_connections[session].m_reverseSocket;
696  if (s != socket && s != nullptr)
697  {
698  LOG(VB_GENERAL, LOG_ERR, LOC +
699  "Already have a different reverse socket for this connection.");
700  return;
701  }
702  m_connections[session].m_reverseSocket = socket;
704  header = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n";
705  SendResponse(socket, status, header, content_type, body);
706  return;
707  }
708 
709  QTcpSocket *s = m_connections[session].m_controlSocket;
710  if (s != socket && s != nullptr)
711  {
712  LOG(VB_GENERAL, LOG_ERR, LOC +
713  "Already have a different control socket for this connection.");
714  return;
715  }
716  m_connections[session].m_controlSocket = socket;
717 
718  if (m_connections[session].m_controlSocket != nullptr &&
719  m_connections[session].m_reverseSocket != nullptr &&
720  !m_connections[session].m_initialized)
721  {
722  // Got a full connection, disconnect any other clients
723  DisconnectAllClients(session);
724  m_connections[session].m_initialized = true;
725 
726  MythNotification n(tr("New Connection"), tr("AirPlay"),
727  tr("from %1").arg(socket->peerAddress().toString()));
728  // Don't show it during playback
731  }
732 
733  double position = 0.0F;
734  double duration = 0.0F;
735  float playerspeed = 0.0F;
736  bool playing = false;
737  QString pathname;
738  GetPlayerStatus(playing, playerspeed, position, duration, pathname);
739 
740  if (playing && pathname != m_pathname)
741  {
742  // not ours
743  playing = false;
744  }
745  if (playing && duration > 0.01 && position < 0.01)
746  {
747  // Assume playback hasn't started yet, get saved position
748  position = m_connections[session].m_position;
749  }
750  if (!playing && m_connections[session].m_was_playing)
751  {
752  // playback got interrupted, notify client to stop
753  if (SendReverseEvent(session, AP_EVENT_STOPPED))
754  {
755  m_connections[session].m_was_playing = false;
756  }
757  }
758  else
759  {
760  m_connections[session].m_was_playing = playing;
761  }
762 
763  if (gCoreContext->GetBoolSetting("AirPlayPasswordEnabled", false))
764  {
765  if (m_nonce.isEmpty())
766  {
768  }
769  header = QString("WWW-Authenticate: Digest realm=\"AirPlay\", "
770  "nonce=\"%1\"\r\n").arg(m_nonce).toLatin1();
771  if (!req->GetHeaders().contains("Authorization"))
772  {
774  header, content_type, body);
775  return;
776  }
777 
778  QByteArray auth;
779  if (DigestMd5Response(req->GetHeaders()["Authorization"], req->GetMethod(), m_nonce,
780  gCoreContext->GetSetting("AirPlayPassword"),
781  auth) == auth)
782  {
783  LOG(VB_GENERAL, LOG_INFO, LOC + "AirPlay client authenticated");
784  }
785  else
786  {
787  LOG(VB_GENERAL, LOG_INFO, LOC + "AirPlay authentication failed");
789  header, content_type, body);
790  return;
791  }
792  header = "";
793  }
794 
795  if (req->GetURI() == "/server-info")
796  {
797  content_type = "text/x-apple-plist+xml\r\n";
798  body = SERVER_INFO;
799  body.replace("%1", GetMacAddress());
800  LOG(VB_GENERAL, LOG_INFO, body);
801  }
802  else if (req->GetURI() == "/scrub")
803  {
804  double pos = req->GetQueryValue("position").toDouble();
805  if (req->GetMethod() == "POST")
806  {
807  // this may be received before playback starts...
808  auto intpos = (uint64_t)pos;
809  m_connections[session].m_position = pos;
810  LOG(VB_GENERAL, LOG_INFO, LOC +
811  QString("Scrub: (post) seek to %1").arg(intpos));
812  SeekPosition(intpos);
813  }
814  else if (req->GetMethod() == "GET")
815  {
816  content_type = "text/parameters\r\n";
817  body = QString("duration: %1\r\nposition: %2\r\n")
818  .arg(duration, 0, 'f', 6, '0')
819  .arg(position, 0, 'f', 6, '0');
820 
821  LOG(VB_GENERAL, LOG_INFO, LOC +
822  QString("Scrub: (get) returned %1 of %2")
823  .arg(position).arg(duration));
824 
825  /*
826  if (playing && playerspeed < 1.0F)
827  {
828  SendReverseEvent(session, AP_EVENT_PLAYING);
829  QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
830  Qt::NoModifier, ACTION_PLAY);
831  qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
832  }
833  */
834  }
835  }
836  else if (req->GetURI() == "/stop")
837  {
838  StopSession(session);
839  }
840  else if (req->GetURI() == "/photo")
841  {
842  if (req->GetMethod() == "PUT")
843  {
844  // this may be received before playback starts...
845  QImage image = QImage::fromData(req->GetBody());
846  bool png =
847  req->GetBody().size() > 3 && req->GetBody()[1] == 'P' &&
848  req->GetBody()[2] == 'N' && req->GetBody()[3] == 'G';
849  LOG(VB_GENERAL, LOG_INFO, LOC +
850  QString("Received %1x%2 %3 photo")
851  .arg(image.width()).arg(image.height()).
852  arg(png ? "jpeg" : "png"));
853 
854  if (m_connections[session].m_notificationid < 0)
855  {
856  m_connections[session].m_notificationid =
858  }
859  // send full screen display notification
861  n.SetId(m_connections[session].m_notificationid);
862  n.SetParent(this);
863  n.SetFullScreen(true);
865  // This is a photo session
866  m_connections[session].m_photos = true;
867  }
868  }
869  else if (req->GetURI() == "/slideshow-features")
870  {
871  LOG(VB_GENERAL, LOG_INFO, LOC +
872  "Slideshow functionality not implemented.");
873  }
874  else if (req->GetURI() == "/authorize")
875  {
876  LOG(VB_GENERAL, LOG_INFO, LOC + "Ignoring authorize request.");
877  }
878  else if ((req->GetURI() == "/setProperty") ||
879  (req->GetURI() == "/getProperty"))
880  {
881  status = HTTP_STATUS_NOT_FOUND;
882  }
883  else if (req->GetURI() == "/rate")
884  {
885  float rate = req->GetQueryValue("value").toFloat();
886  m_connections[session].m_speed = rate;
887 
888  if (rate < 1.0F)
889  {
890  if (playerspeed > 0.0F)
891  {
892  PausePlayback();
893  }
895  }
896  else
897  {
898  if (playerspeed < 1.0F)
899  {
900  UnpausePlayback();
901  }
903  // If there's any photos left displayed, hide them
904  HideAllPhotos();
905  }
906  }
907  else if (req->GetURI() == "/play")
908  {
909  QByteArray file;
910  double start_pos = 0.0F;
911  if (req->GetHeaders().contains("Content-Type") &&
912  req->GetHeaders()["Content-Type"] == "application/x-apple-binary-plist")
913  {
914  MythBinaryPList plist(req->GetBody());
915  LOG(VB_GENERAL, LOG_DEBUG, LOC + plist.ToString());
916 
917  QVariant start = plist.GetValue("Start-Position");
918  QVariant content = plist.GetValue("Content-Location");
919  if (start.isValid() && start.canConvert<double>())
920  start_pos = start.toDouble();
921  if (content.isValid() && content.canConvert<QByteArray>())
922  file = content.toByteArray();
923  }
924  else
925  {
926  QMap<QByteArray,QByteArray> headers = req->GetHeadersFromBody();
927  file = headers["Content-Location"];
928  start_pos = headers["Start-Position"].toDouble();
929  }
930 
931  if (!file.isEmpty())
932  {
933  m_pathname = QUrl::fromPercentEncoding(file);
935  GetPlayerStatus(playing, playerspeed, position, duration, pathname);
936  m_connections[session].m_url = QUrl(m_pathname);
937  m_connections[session].m_position = start_pos * duration;
938  if (TV::IsTVRunning())
939  {
940  HideAllPhotos();
941  }
942  if (duration * start_pos >= .1)
943  {
944  // not point seeking so close to the beginning
945  SeekPosition(duration * start_pos);
946  }
947  }
948 
950  LOG(VB_GENERAL, LOG_INFO, LOC + QString("File: '%1' start_pos '%2'")
951  .arg(file.data()).arg(start_pos));
952  }
953  else if (req->GetURI() == "/playback-info")
954  {
955  content_type = "text/x-apple-plist+xml\r\n";
956 
957  if (!playing)
958  {
959  body = NOT_READY;
961  }
962  else
963  {
964  body = PLAYBACK_INFO;
965  body.replace("%1", QString("%1").arg(duration, 0, 'f', 6, '0'));
966  body.replace("%2", QString("%1").arg(duration, 0, 'f', 6, '0')); // cached
967  body.replace("%3", QString("%1").arg(position, 0, 'f', 6, '0'));
968  body.replace("%4", playerspeed > 0.0F ? "1.0" : "0.0");
969  LOG(VB_GENERAL, LOG_DEBUG, body);
970  SendReverseEvent(session, playerspeed > 0.0F ? AP_EVENT_PLAYING :
972  }
973  }
974  SendResponse(socket, status, header, content_type, body);
975 }
976 
977 void MythAirplayServer::SendResponse(QTcpSocket *socket,
978  int status, const QByteArray& header,
979  const QByteArray& content_type, const QString& body)
980 {
981  if (!socket || !m_incoming.contains(socket) ||
982  socket->state() != QAbstractSocket::ConnectedState)
983  return;
984  QTextStream response(socket);
985  response.setCodec("UTF-8");
986  QByteArray reply;
987  reply.append("HTTP/1.1 ");
988  reply.append(QString::number(status));
989  reply.append(" ");
990  reply.append(StatusToString(status));
991  reply.append("\r\n");
992  reply.append("DATE: ");
993  reply.append(MythDate::current().toString("ddd, d MMM yyyy hh:mm:ss"));
994  reply.append(" GMT\r\n");
995  if (!header.isEmpty())
996  reply.append(header);
997 
998  if (!body.isEmpty())
999  {
1000  reply.append("Content-Type: ");
1001  reply.append(content_type);
1002  reply.append("Content-Length: ");
1003  reply.append(QString::number(body.size()));
1004  }
1005  else
1006  {
1007  reply.append("Content-Length: 0");
1008  }
1009  reply.append("\r\n\r\n");
1010 
1011  if (!body.isEmpty())
1012  reply.append(body);
1013 
1014  response << reply;
1015  response.flush();
1016 
1017  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Send: %1 \n\n%2\n")
1018  .arg(socket->flush()).arg(reply.data()));
1019 }
1020 
1021 bool MythAirplayServer::SendReverseEvent(QByteArray &session,
1022  AirplayEvent event)
1023 {
1024  if (!m_connections.contains(session))
1025  return false;
1026  if (m_connections[session].m_lastEvent == event)
1027  return false;
1028  if (!m_connections[session].m_reverseSocket)
1029  return false;
1030 
1031  QString body;
1032  if (AP_EVENT_PLAYING == event ||
1033  AP_EVENT_LOADING == event ||
1034  AP_EVENT_PAUSED == event ||
1035  AP_EVENT_STOPPED == event)
1036  {
1037  body = EVENT_INFO;
1038  body.replace("%1", eventToString(event));
1039  }
1040 
1041  m_connections[session].m_lastEvent = event;
1042  QTextStream response(m_connections[session].m_reverseSocket);
1043  response.setCodec("UTF-8");
1044  QByteArray reply;
1045  reply.append("POST /event HTTP/1.1\r\n");
1046  reply.append("Content-Type: text/x-apple-plist+xml\r\n");
1047  reply.append("Content-Length: ");
1048  reply.append(QString::number(body.size()));
1049  reply.append("\r\n");
1050  reply.append("x-apple-session-id: ");
1051  reply.append(session);
1052  reply.append("\r\n\r\n");
1053  if (!body.isEmpty())
1054  reply.append(body);
1055 
1056  response << reply;
1057  response.flush();
1058 
1059  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Send reverse: %1 \n\n%2\n")
1060  .arg(m_connections[session].m_reverseSocket->flush())
1061  .arg(reply.data()));
1062  return true;
1063 }
1064 
1066 {
1067  switch (event)
1068  {
1069  case AP_EVENT_PLAYING: return "playing";
1070  case AP_EVENT_PAUSED: return "paused";
1071  case AP_EVENT_LOADING: return "loading";
1072  case AP_EVENT_STOPPED: return "stopped";
1073  case AP_EVENT_NONE: return "none";
1074  default: return "";
1075  }
1076 }
1077 
1078 void MythAirplayServer::GetPlayerStatus(bool &playing, float &speed,
1079  double &position, double &duration,
1080  QString &pathname)
1081 {
1082  QVariantMap state;
1084 
1085  if (state.contains("state"))
1086  playing = state["state"].toString() != "idle";
1087  if (state.contains("playspeed"))
1088  speed = state["playspeed"].toFloat();
1089  if (state.contains("secondsplayed"))
1090  position = state["secondsplayed"].toDouble();
1091  if (state.contains("totalseconds"))
1092  duration = state["totalseconds"].toDouble();
1093  if (state.contains("pathname"))
1094  pathname = state["pathname"].toString();
1095 }
1096 
1098 {
1099  QString id = AirPlayHardwareId();
1100 
1101  QString res;
1102  for (int i = 1; i <= id.size(); i++)
1103  {
1104  res.append(id[i-1]);
1105  if (i % 2 == 0 && i != id.size())
1106  {
1107  res.append(':');
1108  }
1109  }
1110  return res;
1111 }
1112 
1113 void MythAirplayServer::StopSession(const QByteArray &session)
1114 {
1115  AirplayConnection& cnx = m_connections[session];
1116 
1117  if (cnx.m_photos)
1118  {
1119  if (cnx.m_notificationid > 0)
1120  {
1121  // close any photos that could be displayed
1123  cnx.m_notificationid = -1;
1124  }
1125  return;
1126  }
1127  cnx.m_stopped = true;
1128  double position = 0.0F;
1129  double duration = 0.0F;
1130  float playerspeed = 0.0F;
1131  bool playing = false;
1132  QString pathname;
1133  GetPlayerStatus(playing, playerspeed, position, duration, pathname);
1134  if (pathname != m_pathname)
1135  {
1136  // not ours
1137  return;
1138  }
1139  if (!playing)
1140  {
1141  return;
1142  }
1143  StopPlayback();
1144 }
1145 
1146 void MythAirplayServer::DisconnectAllClients(const QByteArray &session)
1147 {
1148  QMutexLocker locker(m_lock);
1149  QHash<QByteArray,AirplayConnection>::iterator it = m_connections.begin();
1150  AirplayConnection& current_cnx = m_connections[session];
1151 
1152  while (it != m_connections.end())
1153  {
1154  AirplayConnection& cnx = it.value();
1155 
1156  if (it.key() == session ||
1157  (current_cnx.m_reverseSocket && cnx.m_reverseSocket &&
1158  current_cnx.m_reverseSocket->peerAddress() == cnx.m_reverseSocket->peerAddress()) ||
1159  (current_cnx.m_controlSocket && cnx.m_controlSocket &&
1160  current_cnx.m_controlSocket->peerAddress() == cnx.m_controlSocket->peerAddress()))
1161  {
1162  // ignore if the connection is the currently active one or
1163  // from the same IP address
1164  ++it;
1165  continue;
1166  }
1167  if (!(*it).m_stopped)
1168  {
1169  StopSession(it.key());
1170  }
1171  QTcpSocket *socket = cnx.m_reverseSocket;
1172  if (socket)
1173  {
1174  socket->disconnect();
1175  socket->close();
1176  m_sockets.removeOne(socket);
1177  socket->deleteLater();
1178  if (m_incoming.contains(socket))
1179  {
1180  delete m_incoming[socket];
1181  m_incoming.remove(socket);
1182  }
1183  }
1184  socket = cnx.m_controlSocket;
1185  if (socket)
1186  {
1187  socket->disconnect();
1188  socket->close();
1189  m_sockets.removeOne(socket);
1190  socket->deleteLater();
1191  if (m_incoming.contains(socket))
1192  {
1193  delete m_incoming[socket];
1194  m_incoming.remove(socket);
1195  }
1196  }
1197  it = m_connections.erase(it);
1198  }
1199 }
1200 
1201 void MythAirplayServer::StartPlayback(const QString &pathname)
1202 {
1203  if (TV::IsTVRunning())
1204  {
1205  StopPlayback();
1206  }
1207  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1208  QString("Sending ACTION_HANDLEMEDIA for %1")
1209  .arg(pathname));
1210  auto* me = new MythEvent(ACTION_HANDLEMEDIA, QStringList(pathname));
1211  qApp->postEvent(GetMythMainWindow(), me);
1212  // Wait until we receive that the play has started
1213  std::vector<CoreWaitInfo> sigs {
1214  { "TVPlaybackStarted", &MythCoreContext::TVPlaybackStarted },
1215  { "TVPlaybackAborted", &MythCoreContext::TVPlaybackAborted } };
1217  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1218  QString("ACTION_HANDLEMEDIA completed"));
1219 }
1220 
1222 {
1223  if (TV::IsTVRunning())
1224  {
1225  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1226  QString("Sending ACTION_STOP for %1")
1227  .arg(m_pathname));
1228 
1229  auto* ke = new QKeyEvent(QEvent::KeyPress, 0,
1230  Qt::NoModifier, ACTION_STOP);
1231  qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
1232  // Wait until we receive that playback has stopped
1233  std::vector<CoreWaitInfo> sigs {
1234  { "TVPlaybackStopped", &MythCoreContext::TVPlaybackStopped },
1235  { "TVPlaybackAborted", &MythCoreContext::TVPlaybackAborted } };
1237  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1238  QString("ACTION_STOP completed"));
1239  }
1240  else
1241  {
1242  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1243  QString("Playback not running, nothing to stop"));
1244  }
1245 }
1246 
1247 void MythAirplayServer::SeekPosition(uint64_t position)
1248 {
1249  if (TV::IsTVRunning())
1250  {
1251  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1252  QString("Sending ACTION_SEEKABSOLUTE(%1) for %2")
1253  .arg(position)
1254  .arg(m_pathname));
1255 
1256  auto* me = new MythEvent(ACTION_SEEKABSOLUTE,
1257  QStringList(QString::number(position)));
1258  qApp->postEvent(GetMythMainWindow(), me);
1259  // Wait until we receive that the seek has completed
1260  std::vector<CoreWaitInfo> sigs {
1261  { "TVPlaybackSought", qOverload<>(&MythCoreContext::TVPlaybackSought) },
1262  { "TVPlaybackStopped", &MythCoreContext::TVPlaybackStopped },
1263  { "TVPlaybackAborted", &MythCoreContext::TVPlaybackAborted } };
1265  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1266  QString("ACTION_SEEKABSOLUTE completed"));
1267  }
1268  else
1269  {
1270  LOG(VB_PLAYBACK, LOG_WARNING, LOC +
1271  QString("Trying to seek when playback hasn't started"));
1272  }
1273 }
1274 
1276 {
1277  if (TV::IsTVRunning() && !TV::IsPaused())
1278  {
1279  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1280  QString("Sending ACTION_PAUSE for %1")
1281  .arg(m_pathname));
1282 
1283  auto* ke = new QKeyEvent(QEvent::KeyPress, 0,
1284  Qt::NoModifier, ACTION_PAUSE);
1285  qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
1286  // Wait until we receive that playback has stopped
1287  std::vector<CoreWaitInfo> sigs {
1288  { "TVPlaybackPaused", &MythCoreContext::TVPlaybackPaused },
1289  { "TVPlaybackStopped", &MythCoreContext::TVPlaybackStopped },
1290  { "TVPlaybackAborted", &MythCoreContext::TVPlaybackAborted } };
1292  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1293  QString("ACTION_PAUSE completed"));
1294  }
1295  else
1296  {
1297  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1298  QString("Playback not running, nothing to pause"));
1299  }
1300 }
1301 
1303 {
1304  if (TV::IsTVRunning())
1305  {
1306  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1307  QString("Sending ACTION_PLAY for %1")
1308  .arg(m_pathname));
1309 
1310  auto* ke = new QKeyEvent(QEvent::KeyPress, 0,
1311  Qt::NoModifier, ACTION_PLAY);
1312  qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
1313  // Wait until we receive that playback has stopped
1314  std::vector<CoreWaitInfo> sigs {
1315  { "TVPlaybackPlaying", &MythCoreContext::TVPlaybackPlaying },
1316  { "TVPlaybackStopped", &MythCoreContext::TVPlaybackStopped },
1317  { "TVPlaybackAborted", &MythCoreContext::TVPlaybackAborted } };
1319  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1320  QString("ACTION_PLAY completed"));
1321  }
1322  else
1323  {
1324  LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
1325  QString("Playback not running, nothing to unpause"));
1326  }
1327 }
1328 
1330 {
1331  // playback has started, dismiss any currently displayed photo
1332  QHash<QByteArray,AirplayConnection>::iterator it = m_connections.begin();
1333 
1334  while (it != m_connections.end())
1335  {
1336  AirplayConnection& cnx = it.value();
1337 
1338  if (cnx.m_photos)
1339  {
1340  cnx.UnRegister();
1341  }
1342  ++it;
1343  }
1344 }
ACTION_PLAY
#define ACTION_PLAY
Definition: tv_actions.h:30
AIRPLAY_SERVER_VERSION_STR
#define AIRPLAY_SERVER_VERSION_STR
Definition: mythairplayserver.cpp:43
AP_EVENT_PLAYING
@ AP_EVENT_PLAYING
Definition: mythairplayserver.h:28
DigestMd5Response
QByteArray DigestMd5Response(const QString &response, const QString &option, const QString &nonce, const QString &password, QByteArray &auth)
Definition: mythairplayserver.cpp:161
APHTTPRequest::GetHeaders
QMap< QByteArray, QByteArray > & GetHeaders(void)
Definition: mythairplayserver.cpp:222
MythAirplayServer::Teardown
void Teardown(void)
Definition: mythairplayserver.cpp:412
MythAirplayServer::SeekPosition
void SeekPosition(uint64_t position)
Definition: mythairplayserver.cpp:1247
MThread::start
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
Definition: mthread.cpp:286
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:103
MythAirplayServer::GetPlayerStatus
static void GetPlayerStatus(bool &playing, float &speed, double &position, double &duration, QString &pathname)
Definition: mythairplayserver.cpp:1078
APHTTPRequest::m_size
int m_size
Definition: mythairplayserver.cpp:345
MythAirplayServer
Definition: mythairplayserver.h:67
HTTP_STATUS_OK
#define HTTP_STATUS_OK
Definition: mythairplayserver.cpp:37
musicbrainzngs.musicbrainz.auth
def auth(u, p)
Definition: musicbrainz.py:292
AirplayConnection::m_notificationid
int m_notificationid
Definition: mythairplayserver.h:62
MythAirplayServer::Create
static bool Create(void)
Definition: mythairplayserver.cpp:349
MythCoreContext::TVPlaybackSought
void TVPlaybackSought(void)
MythNotificationCenter::Register
int Register(void *from)
An application can register in which case it will be assigned a reusable screen, which can be modifie...
Definition: mythnotificationcenter.cpp:1370
MythAirplayServer::m_incoming
QHash< QTcpSocket *, APHTTPRequest * > m_incoming
Definition: mythairplayserver.h:132
APHTTPRequest::m_queries
QList< QPair< QByteArray, QByteArray > > m_queries
Definition: mythairplayserver.cpp:342
MThread::wait
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
Definition: mthread.cpp:303
MythAirplayServer::StopPlayback
void StopPlayback(void)
Definition: mythairplayserver.cpp:1221
MythBinaryPList
Definition: mythbinaryplist.h:11
APHTTPRequest::IsComplete
bool IsComplete(void) const
Definition: mythairplayserver.cpp:254
GenerateNonce
QString GenerateNonce(void)
Definition: mythairplayserver.cpp:140
build_compdb.content
content
Definition: build_compdb.py:38
musicbrainzngs.musicbrainz.user
string user
Definition: musicbrainz.py:287
MythAirplayServer::m_valid
bool m_valid
Definition: mythairplayserver.h:121
MythAirplayServer::m_setupPort
int m_setupPort
Definition: mythairplayserver.h:123
AIRPLAY_HARDWARE_ID_SIZE
#define AIRPLAY_HARDWARE_ID_SIZE
Definition: mythairplayserver.h:18
MythEvent
This class is used as a container for messages.
Definition: mythevent.h:16
BonjourRegister::Register
bool Register(uint16_t port, const QByteArray &type, const QByteArray &name, const QByteArray &txt)
Definition: bonjourregister.cpp:41
AirplayConnection::m_stopped
bool m_stopped
Definition: mythairplayserver.h:57
arg
arg(title).arg(filename).arg(doDelete))
ServerPool::tryListeningPort
int tryListeningPort(int baseport, int range=1)
tryListeningPort
Definition: serverpool.cpp:721
MythNotification
Definition: mythnotification.h:29
AIRPLAY_PORT_RANGE
#define AIRPLAY_PORT_RANGE
Definition: mythairplayserver.h:17
MythAirplayServer::HideAllPhotos
void HideAllPhotos(void)
Definition: mythairplayserver.cpp:1329
MythAirplayServer::eventToString
static QString eventToString(AirplayEvent event)
Definition: mythairplayserver.cpp:1065
MythAirplayServer::HandleResponse
void HandleResponse(APHTTPRequest *req, QTcpSocket *socket)
Definition: mythairplayserver.cpp:643
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
BonjourRegister
Definition: bonjourregister.h:11
APHTTPRequest::GetBody
QByteArray & GetBody(void)
Definition: mythairplayserver.cpp:221
MythUIStateTracker::GetFreshState
static void GetFreshState(QVariantMap &State)
Definition: mythuistatetracker.cpp:41
vals
static float * vals
Definition: tentacle3d.cpp:18
MythAirplayServer::UnpausePlayback
void UnpausePlayback(void)
Definition: mythairplayserver.cpp:1302
MythAirplayServer::MythAirplayServer
MythAirplayServer()
Definition: mythairplayserver.h:77
build_compdb.file
file
Definition: build_compdb.py:55
MythNotification::SetVisibility
void SetVisibility(VNMask nVisibility)
Define a bitmask of Visibility.
Definition: mythnotification.cpp:154
MythBinaryPList::GetValue
QVariant GetValue(const QString &Key)
Definition: mythbinaryplist.cpp:112
MythAirplayServer::Cleanup
static void Cleanup(void)
Definition: mythairplayserver.cpp:388
AP_EVENT_PAUSED
@ AP_EVENT_PAUSED
Definition: mythairplayserver.h:29
MythImageNotification
Definition: mythnotification.h:126
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
ServerPool::newConnection
void newConnection(QTcpSocket *)
mythairplayserver.h
APHTTPRequest::m_method
QByteArray m_method
Definition: mythairplayserver.cpp:340
MythAirplayServer::read
void read(void)
Definition: mythairplayserver.cpp:599
MythAirplayServer::~MythAirplayServer
~MythAirplayServer(void) override
Definition: mythairplayserver.cpp:406
APHTTPRequest::APHTTPRequest
APHTTPRequest(QByteArray &data)
Definition: mythairplayserver.cpp:212
MythAirplayServer::PausePlayback
void PausePlayback(void)
Definition: mythairplayserver.cpp:1275
APHTTPRequest::GetLine
QByteArray GetLine(void)
Definition: mythairplayserver.cpp:260
toString
QString toString(MarkTypes type)
Definition: programtypes.cpp:26
MythAirplayServer::m_connections
QHash< QByteArray, AirplayConnection > m_connections
Definition: mythairplayserver.h:125
MythNotification::kPlayback
@ kPlayback
Definition: mythnotification.h:75
mythdate.h
mythlogging.h
TV::IsPaused
static bool IsPaused()
Check whether playback is paused.
Definition: tv_play.cpp:4759
MythNotification::SetFullScreen
void SetFullScreen(bool FullScreen)
A notification may request to be displayed in full screen, this request may not be fullfilled should ...
Definition: mythnotification.cpp:107
MythAirplayServer::StartPlayback
void StartPlayback(const QString &pathname)
Definition: mythairplayserver.cpp:1201
MythAirplayServer::m_lock
QMutex * m_lock
Definition: mythairplayserver.h:122
tv_actions.h
ACTION_HANDLEMEDIA
#define ACTION_HANDLEMEDIA
Definition: mythuiactions.h:21
MythCoreContext::SendSystemEvent
void SendSystemEvent(const QString &msg)
Definition: mythcorecontext.cpp:1553
MSqlQuery::first
bool first(void)
Wrap QSqlQuery::first() so we can display the query results.
Definition: mythdbcon.cpp:793
MythCoreContext::WaitUntilSignals
void WaitUntilSignals(std::vector< CoreWaitInfo > &sigs) const
Wait until any of the provided signals have been received.
Definition: mythcorecontext.cpp:1895
ACTION_SEEKABSOLUTE
#define ACTION_SEEKABSOLUTE
Definition: tv_actions.h:40
MythAirplayServer::Stop
void Stop()
Definition: mythairplayserver.cpp:517
AirPlayHardwareId
QString AirPlayHardwareId()
Definition: mythairplayserver.cpp:120
AirplayConnection::m_controlSocket
QTcpSocket * m_controlSocket
Definition: mythairplayserver.h:50
MythNotification::SetParent
void SetParent(void *Parent)
Contains the parent address. Required if id is set Id provided must match the parent address as provi...
Definition: mythnotification.cpp:98
APHTTPRequest::m_body
QByteArray m_body
Definition: mythairplayserver.cpp:344
bonjourregister.h
NOT_READY
#define NOT_READY
Definition: mythairplayserver.cpp:111
APHTTPRequest::m_uri
QByteArray m_uri
Definition: mythairplayserver.cpp:341
ACTION_PAUSE
#define ACTION_PAUSE
Definition: tv_actions.h:15
MythCoreContext::TVPlaybackPaused
void TVPlaybackPaused(void)
APHTTPRequest::Append
void Append(QByteArray &data)
Definition: mythairplayserver.cpp:225
AirplayConnection::m_photos
bool m_photos
Definition: mythairplayserver.h:60
MythCoreContext::TVPlaybackPlaying
void TVPlaybackPlaying(void)
APHTTPRequest::GetQueryValue
QByteArray GetQueryValue(const QByteArray &key)
Definition: mythairplayserver.cpp:231
MThread::qthread
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
Definition: mthread.cpp:236
APHTTPRequest::GetHeadersFromBody
QMap< QByteArray, QByteArray > GetHeadersFromBody(void)
Definition: mythairplayserver.cpp:238
MythAirplayServer::deleteConnection
void deleteConnection()
Definition: mythairplayserver.cpp:535
ACTION_STOP
#define ACTION_STOP
Definition: tv_actions.h:8
MythAirplayServer::GetMacAddress
static QString GetMacAddress()
Definition: mythairplayserver.cpp:1097
HTTP_STATUS_UNAUTHORIZED
#define HTTP_STATUS_UNAUTHORIZED
Definition: mythairplayserver.cpp:40
MythNotification::New
static Type New
Definition: mythnotification.h:32
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
APHTTPRequest::m_headers
QMap< QByteArray, QByteArray > m_headers
Definition: mythairplayserver.cpp:343
mythuistatetracker.h
HTTP_STATUS_NOT_FOUND
#define HTTP_STATUS_NOT_FOUND
Definition: mythairplayserver.cpp:41
MythAirplayServer::m_bonjour
BonjourRegister * m_bonjour
Definition: mythairplayserver.h:120
APHTTPRequest::m_incomingPartial
bool m_incomingPartial
Definition: mythairplayserver.cpp:346
MythAirplayServer::m_sockets
QList< QTcpSocket * > m_sockets
Definition: mythairplayserver.h:124
MythAirplayServer::SendResponse
void SendResponse(QTcpSocket *socket, int status, const QByteArray &header, const QByteArray &content_type, const QString &body)
Definition: mythairplayserver.cpp:977
APHTTPRequest
Definition: mythairplayserver.cpp:209
TV::IsTVRunning
static bool IsTVRunning()
Check whether media is currently playing.
Definition: tv_play.cpp:151
MythCoreContext::TVPlaybackAborted
void TVPlaybackAborted(void)
AP_EVENT_LOADING
@ AP_EVENT_LOADING
Definition: mythairplayserver.h:30
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:926
MythAirplayServer::SendReverseEvent
bool SendReverseEvent(QByteArray &session, AirplayEvent event)
Definition: mythairplayserver.cpp:1021
MythRandom
MBASE_PUBLIC uint32_t MythRandom()
Definition: mythmiscutil.h:24
APHTTPRequest::m_readPos
int m_readPos
Definition: mythairplayserver.cpp:338
APHTTPRequest::Process
void Process(void)
Definition: mythairplayserver.cpp:269
MythBinaryPList::ToString
QString ToString()
Definition: mythbinaryplist.cpp:124
AirplayConnection
Definition: mythairplayserver.h:34
BonjourRegister::ReAnnounceService
bool ReAnnounceService(void)
Definition: bonjourregister.cpp:143
MythAirplayServer::gMythAirplayServerThread
static MThread * gMythAirplayServerThread
Definition: mythairplayserver.h:116
MythAirplayServer::StopSession
void StopSession(const QByteArray &session)
Definition: mythairplayserver.cpp:1113
mythcorecontext.h
MythCoreContext::TVPlaybackStarted
void TVPlaybackStarted(void)
PLAYBACK_INFO
#define PLAYBACK_INFO
Definition: mythairplayserver.cpp:72
APHTTPRequest::GetMethod
QByteArray & GetMethod(void)
Definition: mythairplayserver.cpp:219
EVENT_INFO
#define EVENT_INFO
Definition: mythairplayserver.cpp:61
GetNotificationCenter
MythNotificationCenter * GetNotificationCenter(void)
Definition: mythmainwindow.cpp:124
MythNotificationCenter::UnRegister
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
Definition: mythnotificationcenter.cpp:1375
MThread
This is a wrapper around QThread that does several additional things.
Definition: mthread.h:49
APHTTPRequest::m_data
QByteArray m_data
Definition: mythairplayserver.cpp:339
AP_EVENT_NONE
@ AP_EVENT_NONE
Definition: mythairplayserver.h:27
APHTTPRequest::GetURI
QByteArray & GetURI(void)
Definition: mythairplayserver.cpp:220
mthread.h
APHTTPRequest::~APHTTPRequest
~APHTTPRequest()=default
MThread::exit
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
Definition: mthread.cpp:281
GetMythMainWindow
MythMainWindow * GetMythMainWindow(void)
Definition: mythmainwindow.cpp:104
MThread::isRunning
bool isRunning(void) const
Definition: mthread.cpp:266
AirplayConnection::UnRegister
void UnRegister(void)
Definition: mythairplayserver.h:42
MythAirplayServer::DisconnectAllClients
void DisconnectAllClients(const QByteArray &session)
Definition: mythairplayserver.cpp:1146
MythAirplayServer::newAirplayConnection
void newAirplayConnection(QTcpSocket *client)
Definition: mythairplayserver.cpp:522
MythNotification::SetId
void SetId(int Id)
Contains the application registration id.
Definition: mythnotification.cpp:85
APHTTPRequest::Check
void Check(void)
Definition: mythairplayserver.cpp:319
MythAirplayServer::Start
void Start()
Definition: mythairplayserver.cpp:447
MythCoreContext::GetHostName
QString GetHostName(void)
Definition: mythcorecontext.cpp:858
mythuiactions.h
MythAirplayServer::StatusToString
static QByteArray StatusToString(int status)
Definition: mythairplayserver.cpp:630
HTTP_STATUS_NOT_IMPLEMENTED
#define HTTP_STATUS_NOT_IMPLEMENTED
Definition: mythairplayserver.cpp:39
MythAirplayServer::gMythAirplayServerMutex
static QMutex * gMythAirplayServerMutex
Definition: mythairplayserver.h:115
MythCoreContext::TVPlaybackStopped
void TVPlaybackStopped(void)
MythCoreContext::SaveSetting
void SaveSetting(const QString &key, int newValue)
Definition: mythcorecontext.cpp:901
AirplayEvent
AirplayEvent
Definition: mythairplayserver.h:25
LOC
#define LOC
Definition: mythairplayserver.cpp:35
MythAirplayServer::m_nonce
QString m_nonce
Definition: mythairplayserver.h:129
mythmainwindow.h
AirplayConnection::m_reverseSocket
QTcpSocket * m_reverseSocket
Definition: mythairplayserver.h:51
query
MSqlQuery query(MSqlQuery::InitCon())
MythAirplayServer::m_name
QString m_name
Definition: mythairplayserver.h:119
MythAirplayServer::gMythAirplayServer
static MythAirplayServer * gMythAirplayServer
Definition: mythairplayserver.h:114
MythNotification::GetVisibility
VNMask GetVisibility() const
Definition: mythnotification.h:104
HTTP_STATUS_SWITCHING_PROTOCOLS
#define HTTP_STATUS_SWITCHING_PROTOCOLS
Definition: mythairplayserver.cpp:38
AP_EVENT_STOPPED
@ AP_EVENT_STOPPED
Definition: mythairplayserver.h:31
SERVER_INFO
#define SERVER_INFO
Definition: mythairplayserver.cpp:44
MythAirplayServer::m_serviceRefresh
QTimer * m_serviceRefresh
Definition: mythairplayserver.h:135
MythCoreContext::GetSetting
QString GetSetting(const QString &key, const QString &defaultval="")
Definition: mythcorecontext.cpp:918
MythAirplayServer::m_pathname
QString m_pathname
Definition: mythairplayserver.h:126
MythAirplayServer::timeout
void timeout(void)
Definition: mythairplayserver.cpp:511
tv_play.h
MythNotificationCenter::Queue
bool Queue(const MythNotification &notification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
Definition: mythnotificationcenter.cpp:1351
mythbinaryplist.h