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