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