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