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