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