11#include <QNetworkInterface>
12#include <QCoreApplication>
14#include <QCryptographicHash>
15#if QT_VERSION >= QT_VERSION_CHECK(6,0,0)
16#include <QStringConverter>
40#define LOC QString("AirPlay: ")
49static const QString
SERVER_INFO {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" \
50"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
51"<plist version=\"1.0\">\r\n"\
53"<key>deviceid</key>\r\n"\
54"<string>%1</string>\r\n"\
55"<key>features</key>\r\n"\
56"<integer>119</integer>\r\n"\
57"<key>model</key>\r\n"\
58"<string>MythTV,1</string>\r\n"\
59"<key>protovers</key>\r\n"\
60"<string>1.0</string>\r\n"\
61"<key>srcvers</key>\r\n"\
62"<string>%1</string>\r\n"\
66static const QString
EVENT_INFO {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r\n" \
67"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\r\n"\
68"<plist version=\"1.0\">\r\n"\
70"<key>category</key>\r\n"\
71"<string>video</string>\r\n"\
72"<key>state</key>\r\n"\
73"<string>%1</string>\r\n"\
77static const QString
PLAYBACK_INFO {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" \
78"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
79"<plist version=\"1.0\">\r\n"\
81"<key>duration</key>\r\n"\
83"<key>loadedTimeRanges</key>\r\n"\
86"\t\t\t<key>duration</key>\r\n"\
87"\t\t\t<real>%2</real>\r\n"\
88"\t\t\t<key>start</key>\r\n"\
89"\t\t\t<real>0.0</real>\r\n"\
92"<key>playbackBufferEmpty</key>\r\n"\
94"<key>playbackBufferFull</key>\r\n"\
96"<key>playbackLikelyToKeepUp</key>\r\n"\
98"<key>position</key>\r\n"\
100"<key>rate</key>\r\n"\
101"<real>%4</real>\r\n"\
102"<key>readyToPlay</key>\r\n"\
104"<key>seekableTimeRanges</key>\r\n"\
107"\t\t\t<key>duration</key>\r\n"\
108"\t\t\t<real>%1</real>\r\n"\
109"\t\t\t<key>start</key>\r\n"\
110"\t\t\t<real>0.0</real>\r\n"\
116static const QString
NOT_READY {
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n" \
117"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
118"<plist version=\"1.0\">\r\n"\
120"<key>readyToPlay</key>\r\n"\
127 QString key =
"AirPlayId";
129 int size =
id.size();
130 if (size == 12 &&
id.toUpper() ==
id)
149 std::array<uint32_t,4> nonceParts {
157 nonce = QString::number(nonceParts[0], 16).toUpper();
158 nonce += QString::number(nonceParts[1], 16).toUpper();
159 nonce += QString::number(nonceParts[2], 16).toUpper();
160 nonce += QString::number(nonceParts[3], 16).toUpper();
165 const QString& nonce,
const QString& password,
168 int authStart = response.indexOf(
"response=\"") + 10;
169 int authLength = response.indexOf(
"\"", authStart) - authStart;
170 auth = response.mid(authStart, authLength).toLatin1();
172 int uriStart = response.indexOf(
"uri=\"") + 5;
173 int uriLength = response.indexOf(
"\"", uriStart) - uriStart;
174 QByteArray uri = response.mid(uriStart, uriLength).toLatin1();
176 int userStart = response.indexOf(
"username=\"") + 10;
177 int userLength = response.indexOf(
"\"", userStart) - userStart;
178 QByteArray
user = response.mid(userStart, userLength).toLatin1();
180 int realmStart = response.indexOf(
"realm=\"") + 7;
181 int realmLength = response.indexOf(
"\"", realmStart) - realmStart;
182 QByteArray realm = response.mid(realmStart, realmLength).toLatin1();
184 QByteArray passwd = password.toLatin1();
186 QCryptographicHash hash(QCryptographicHash::Md5);
187 QByteArray colon(
":", 1);
192 hash.addData(passwd);
193 QByteArray ha1 = hash.result();
198 hash.addData(option.toLatin1());
201 QByteArray ha2 = hash.result().toHex();
207 hash.addData(nonce.toLatin1());
210 return hash.result().toHex();
240 &RequestQuery::first);
241 return (query !=
m_queries.cend()) ? query->second :
"";
246 QMap<QByteArray,QByteArray> result;
247 QList<QByteArray> lines =
m_body.split(
'\n');;
248 for (
const QByteArray& line : std::as_const(lines))
250 int index = line.indexOf(
":");
253 result.insert(line.left(index).trimmed(),
254 line.mid(index + 1).trimmed());
269 if (next < 0)
return {};
284 QList<QByteArray>
vals = line.split(
' ');
288 QUrl url = QUrl::fromEncoded(
vals[1].trimmed());
289 m_uri = url.path(QUrl::FullyEncoded).toLocal8Bit();
292 QList<QPair<QString, QString> > items =
293 QUrlQuery(url).queryItems(QUrl::FullyEncoded);
294 QList<QPair<QString, QString> >::ConstIterator it = items.constBegin();
295 for ( ; it != items.constEnd(); ++it)
296 m_queries << qMakePair(it->first.toLatin1(), it->second.toLatin1());
302 while (!(line =
GetLine()).isEmpty())
304 int index = line.indexOf(
":");
307 m_headers.insert(line.left(index).trimmed(),
308 line.mid(index + 1).trimmed());
313 if (
m_headers.contains(
"Content-Length"))
317 if (
m_size > 0 && remaining > 0)
329 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
330 QString(
"HTTP Request:\n%1").arg(
m_data.data()));
334 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
335 QString(
"AP HTTPRequest: Didn't read entire buffer."
336 "Left to receive: %1 (got %2 of %3) body=%4")
364 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create airplay thread.");
373 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create airplay object.");
390 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Created airplay objects.");
396 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Cleaning up.");
420 QMutexLocker locker(
m_lock);
438 for (QTcpSocket* connection : std::as_const(
m_sockets))
440 disconnect(connection,
nullptr,
nullptr,
nullptr);
455 QMutexLocker locker(
m_lock);
471 LOG(VB_GENERAL, LOG_ERR,
LOC +
472 "Failed to find a port for incoming connections.");
480 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to create Bonjour object.");
487 m_name += QString::number(multiple);
489 QByteArray name =
m_name.toUtf8();
492 QByteArray
type =
"_airplay._tcp";
494 txt.append(26); txt.append(
"deviceid="); txt.append(
GetMacAddress().toUtf8());
497 txt.append(13); txt.append(
"features=0xF7");
498 txt.append(14); txt.append(
"model=MythTV,1");
503 LOG(VB_GENERAL, LOG_ERR,
LOC +
"Failed to register service.");
530 QMutexLocker locker(
m_lock);
531 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"New connection from %1:%2")
532 .arg(client->peerAddress().toString()).arg(client->peerPort()));
536 connect(client, &QAbstractSocket::disconnected,
543 QMutexLocker locker(
m_lock);
544 auto *socket = qobject_cast<QTcpSocket *>(sender());
557 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Removing connection %1:%2")
558 .arg(socket->peerAddress().toString()).arg(socket->peerPort()));
565 if (it.value().m_reverseSocket == socket)
566 it.value().m_reverseSocket =
nullptr;
567 if (it.value().m_controlSocket == socket)
568 it.value().m_controlSocket =
nullptr;
569 if (!it.value().m_reverseSocket &&
570 !it.value().m_controlSocket)
572 if (!it.value().m_stopped)
581 if (!remove.isEmpty())
583 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"Removing session '%1'")
584 .arg(remove.data()));
588 tr(
"from %1").arg(socket->peerAddress().toString()));
594 socket->deleteLater();
605 QMutexLocker locker(
m_lock);
606 auto *socket = qobject_cast<QTcpSocket *>(sender());
610 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Read for %1:%2")
611 .arg(socket->peerAddress().toString()).arg(socket->peerPort()));
613 QByteArray buf = socket->readAll();
652 QHostAddress addr = socket->peerAddress();
657 QByteArray content_type;
659 if (req->
GetURI() !=
"/playback-info")
661 LOG(VB_GENERAL, LOG_INFO,
LOC +
662 QString(
"Method: %1 URI: %2")
667 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
668 QString(
"Method: %1 URI: %2")
675 if (!req->
GetHeaders().contains(
"X-Apple-Session-ID"))
677 LOG(VB_GENERAL, LOG_DEBUG,
LOC +
678 QString(
"No session ID in http request. "
679 "Connection from iTunes? Using IP %1").arg(addr.toString()));
683 session = req->
GetHeaders()[
"X-Apple-Session-ID"];
686 if (session.size() == 0)
689 session = addr.toString().toLatin1();
697 if (req->
GetURI() ==
"/reverse")
700 if (s != socket && s !=
nullptr)
702 LOG(VB_GENERAL, LOG_ERR,
LOC +
703 "Already have a different reverse socket for this connection.");
708 header =
"Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n";
709 SendResponse(socket, status, header, content_type, body);
714 if (s != socket && s !=
nullptr)
716 LOG(VB_GENERAL, LOG_ERR,
LOC +
717 "Already have a different control socket for this connection.");
731 tr(
"from %1").arg(socket->peerAddress().toString()));
737 double position = 0.0;
738 double duration = 0.0;
739 float playerspeed = 0.0F;
740 bool playing =
false;
749 if (playing && duration > 0.01 && position < 0.01)
773 header = QString(
"WWW-Authenticate: Digest realm=\"AirPlay\", "
774 "nonce=\"%1\"\r\n").arg(
m_nonce).toLatin1();
775 if (!req->
GetHeaders().contains(
"Authorization"))
778 header, content_type, body);
787 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AirPlay client authenticated");
791 LOG(VB_GENERAL, LOG_INFO,
LOC +
"AirPlay authentication failed");
793 header, content_type, body);
799 if (req->
GetURI() ==
"/server-info")
801 content_type =
"text/x-apple-plist+xml\r\n";
804 LOG(VB_GENERAL, LOG_INFO, body);
806 else if (req->
GetURI() ==
"/scrub")
812 auto intpos = (uint64_t)pos;
814 LOG(VB_GENERAL, LOG_INFO,
LOC +
815 QString(
"Scrub: (post) seek to %1").arg(intpos));
820 content_type =
"text/parameters\r\n";
821 body = QString(
"duration: %1\r\nposition: %2\r\n")
822 .arg(duration, 0,
'f', 6,
'0')
823 .arg(position, 0,
'f', 6,
'0');
825 LOG(VB_GENERAL, LOG_INFO,
LOC +
826 QString(
"Scrub: (get) returned %1 of %2")
827 .arg(position).arg(duration));
840 else if (req->
GetURI() ==
"/stop")
844 else if (req->
GetURI() ==
"/photo")
849 QImage image = QImage::fromData(req->
GetBody());
853 LOG(VB_GENERAL, LOG_INFO,
LOC +
854 QString(
"Received %1x%2 %3 photo")
855 .arg(image.width()).arg(image.height()).
856 arg(png ?
"jpeg" :
"png"));
873 else if (req->
GetURI() ==
"/slideshow-features")
875 LOG(VB_GENERAL, LOG_INFO,
LOC +
876 "Slideshow functionality not implemented.");
878 else if (req->
GetURI() ==
"/authorize")
880 LOG(VB_GENERAL, LOG_INFO,
LOC +
"Ignoring authorize request.");
882 else if ((req->
GetURI() ==
"/setProperty") ||
883 (req->
GetURI() ==
"/getProperty"))
887 else if (req->
GetURI() ==
"/rate")
894 if (playerspeed > 0.0F)
902 if (playerspeed < 1.0F)
911 else if (req->
GetURI() ==
"/play")
914 double start_pos = 0.0;
915 if (req->
GetHeaders().contains(
"Content-Type") &&
916 req->
GetHeaders()[
"Content-Type"] ==
"application/x-apple-binary-plist")
921 QVariant start = plist.
GetValue(
"Start-Position");
923 if (start.isValid() && start.canConvert<
double>())
924 start_pos = start.toDouble();
932 start_pos =
headers[
"Start-Position"].toDouble();
946 if (duration * start_pos >= .1)
954 LOG(VB_GENERAL, LOG_INFO,
LOC + QString(
"File: '%1' start_pos '%2'")
955 .arg(
file.data()).arg(start_pos));
957 else if (req->
GetURI() ==
"/playback-info")
959 content_type =
"text/x-apple-plist+xml\r\n";
969 body.replace(
"%1", QString(
"%1").arg(duration, 0,
'f', 6,
'0'));
970 body.replace(
"%2", QString(
"%1").arg(duration, 0,
'f', 6,
'0'));
971 body.replace(
"%3", QString(
"%1").arg(position, 0,
'f', 6,
'0'));
972 body.replace(
"%4", playerspeed > 0.0F ?
"1.0" :
"0.0");
973 LOG(VB_GENERAL, LOG_DEBUG, body);
978 SendResponse(socket, status, header, content_type, body);
982 uint16_t status,
const QByteArray& header,
983 const QByteArray& content_type,
const QString& body)
985 if (!socket || !
m_incoming.contains(socket) ||
986 socket->state() != QAbstractSocket::ConnectedState)
988 QTextStream response(socket);
989#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
990 response.setCodec(
"UTF-8");
992 response.setEncoding(QStringConverter::Utf8);
995 reply.append(
"HTTP/1.1 ");
996 reply.append(QString::number(status).toUtf8());
999 reply.append(
"\r\n");
1000 reply.append(
"DATE: ");
1002 reply.append(
" GMT\r\n");
1003 if (!header.isEmpty())
1004 reply.append(header);
1006 if (!body.isEmpty())
1008 reply.append(
"Content-Type: ");
1009 reply.append(content_type);
1010 reply.append(
"Content-Length: ");
1011 reply.append(QString::number(body.size()).toUtf8());
1015 reply.append(
"Content-Length: 0");
1017 reply.append(
"\r\n\r\n");
1019 if (!body.isEmpty())
1020 reply.append(body.toUtf8());
1025 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Send: %1 \n\n%2\n")
1026 .arg(socket->flush()).arg(reply.data()));
1050 QTextStream response(
m_connections[session].m_reverseSocket);
1051#if QT_VERSION < QT_VERSION_CHECK(6,0,0)
1052 response.setCodec(
"UTF-8");
1054 response.setEncoding(QStringConverter::Utf8);
1057 reply.append(
"POST /event HTTP/1.1\r\n");
1058 reply.append(
"Content-Type: text/x-apple-plist+xml\r\n");
1059 reply.append(
"Content-Length: ");
1060 reply.append(QString::number(body.size()).toUtf8());
1061 reply.append(
"\r\n");
1062 reply.append(
"x-apple-session-id: ");
1063 reply.append(session);
1064 reply.append(
"\r\n\r\n");
1065 if (!body.isEmpty())
1066 reply.append(body.toUtf8());
1071 LOG(VB_GENERAL, LOG_DEBUG,
LOC + QString(
"Send reverse: %1 \n\n%2\n")
1073 .arg(reply.data()));
1091 double &position,
double &duration,
1097 if (state.contains(
"state"))
1098 playing = state[
"state"].toString() !=
"idle";
1099 if (state.contains(
"playspeed"))
1100 speed = state[
"playspeed"].toFloat();
1101 if (state.contains(
"secondsplayed"))
1102 position = state[
"secondsplayed"].toDouble();
1103 if (state.contains(
"totalseconds"))
1104 duration = state[
"totalseconds"].toDouble();
1105 if (state.contains(
"pathname"))
1106 pathname = state[
"pathname"].toString();
1114 for (
int i = 1; i <=
id.size(); i++)
1116 res.append(
id[i-1]);
1117 if (i % 2 == 0 && i !=
id.size())
1140 double position = 0.0;
1141 double duration = 0.0;
1142 float playerspeed = 0.0F;
1143 bool playing =
false;
1160 QMutexLocker locker(
m_lock);
1161 QHash<QByteArray,AirplayConnection>::iterator it =
m_connections.begin();
1168 if (it.key() == session ||
1179 if (!(*it).m_stopped)
1186 socket->disconnect();
1189 socket->deleteLater();
1199 socket->disconnect();
1202 socket->deleteLater();
1219 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1220 QString(
"Sending ACTION_HANDLEMEDIA for %1")
1225 std::vector<CoreWaitInfo> sigs {
1229 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1230 QString(
"ACTION_HANDLEMEDIA completed"));
1237 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1238 QString(
"Sending ACTION_STOP for %1")
1241 auto* ke =
new QKeyEvent(QEvent::KeyPress, 0,
1245 std::vector<CoreWaitInfo> sigs {
1249 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1250 QString(
"ACTION_STOP completed"));
1254 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1255 QString(
"Playback not running, nothing to stop"));
1263 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1264 QString(
"Sending ACTION_SEEKABSOLUTE(%1) for %2")
1269 QStringList(QString::number(position)));
1272 std::vector<CoreWaitInfo> sigs {
1277 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1278 QString(
"ACTION_SEEKABSOLUTE completed"));
1282 LOG(VB_PLAYBACK, LOG_WARNING,
LOC +
1283 QString(
"Trying to seek when playback hasn't started"));
1291 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1292 QString(
"Sending ACTION_PAUSE for %1")
1295 auto* ke =
new QKeyEvent(QEvent::KeyPress, 0,
1299 std::vector<CoreWaitInfo> sigs {
1304 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1305 QString(
"ACTION_PAUSE completed"));
1309 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1310 QString(
"Playback not running, nothing to pause"));
1318 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1319 QString(
"Sending ACTION_PLAY for %1")
1322 auto* ke =
new QKeyEvent(QEvent::KeyPress, 0,
1326 std::vector<CoreWaitInfo> sigs {
1331 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1332 QString(
"ACTION_PLAY completed"));
1336 LOG(VB_PLAYBACK, LOG_DEBUG,
LOC +
1337 QString(
"Playback not running, nothing to unpause"));
1344 QHash<QByteArray,AirplayConnection>::iterator it =
m_connections.begin();
APHTTPRequest(QByteArray &data)
QMap< QByteArray, QByteArray > m_headers
bool IsComplete(void) const
QByteArray & GetMethod(void)
QMap< QByteArray, QByteArray > & GetHeaders(void)
QList< RequestQuery > m_queries
QByteArray & GetBody(void)
void Append(QByteArray &data)
QMap< QByteArray, QByteArray > GetHeadersFromBody(void)
QByteArray GetQueryValue(const QByteArray &key)
QByteArray & GetURI(void)
QTcpSocket * m_controlSocket
QTcpSocket * m_reverseSocket
bool ReAnnounceService(void)
bool Register(uint16_t port, const QByteArray &type, const QByteArray &name, const QByteArray &txt)
This is a wrapper around QThread that does several additional things.
bool isRunning(void) const
void start(QThread::Priority p=QThread::InheritPriority)
Tell MThread to start running the thread in the near future.
bool wait(std::chrono::milliseconds time=std::chrono::milliseconds::max())
Wait for the MThread to exit, with a maximum timeout.
void exit(int retcode=0)
Use this to exit from the thread if you are using a Qt event loop.
QThread * qthread(void)
Returns the thread, this will always return the same pointer no matter how often you restart the thre...
QHash< QByteArray, AirplayConnection > m_connections
void StopSession(const QByteArray &session)
void newAirplayConnection(QTcpSocket *client)
static void GetPlayerStatus(bool &playing, float &speed, double &position, double &duration, QString &pathname)
void StartPlayback(const QString &pathname)
QTimer * m_serviceRefresh
void HandleResponse(APHTTPRequest *req, QTcpSocket *socket)
static MythAirplayServer * gMythAirplayServer
void DisconnectAllClients(const QByteArray &session)
bool SendReverseEvent(QByteArray &session, AirplayEvent event)
QHash< QTcpSocket *, APHTTPRequest * > m_incoming
static QByteArray StatusToString(uint16_t status)
static void Cleanup(void)
static QString GetMacAddress()
QList< QTcpSocket * > m_sockets
static MThread * gMythAirplayServerThread
BonjourRegister * m_bonjour
void SendResponse(QTcpSocket *socket, uint16_t status, const QByteArray &header, const QByteArray &content_type, const QString &body)
void UnpausePlayback(void)
static QRecursiveMutex * gMythAirplayServerMutex
~MythAirplayServer(void) override
void SeekPosition(uint64_t position)
static QString eventToString(AirplayEvent event)
QVariant GetValue(const QString &Key)
QString GetHostName(void)
void SaveSetting(const QString &key, int newValue)
void TVPlaybackAborted(void)
QString GetSetting(const QString &key, const QString &defaultval="")
void SendSystemEvent(const QString &msg)
void TVPlaybackPaused(void)
void TVPlaybackSought(void)
void TVPlaybackStopped(void)
void TVPlaybackPlaying(void)
void WaitUntilSignals(std::vector< CoreWaitInfo > &sigs) const
Wait until any of the provided signals have been received.
void TVPlaybackStarted(void)
bool GetBoolSetting(const QString &key, bool defaultval=false)
This class is used as a container for messages.
void UnRegister(void *from, int id, bool closeimemdiately=false)
Unregister the client.
int Register(void *from)
An application can register in which case it will be assigned a reusable screen, which can be modifie...
bool Queue(const MythNotification ¬ification)
Queue a notification Queue() is thread-safe and can be called from anywhere.
void SetVisibility(VNMask nVisibility)
Define a bitmask of Visibility.
void SetId(int Id)
Contains the application registration id.
void SetFullScreen(bool FullScreen)
A notification may request to be displayed in full screen, this request may not be fullfilled should ...
void SetParent(void *Parent)
Contains the parent address. Required if id is set Id provided must match the parent address as provi...
VNMask GetVisibility() const
static void GetFreshState(QVariantMap &State)
int tryListeningPort(int baseport, int range=1)
tryListeningPort
void newConnection(QTcpSocket *)
static bool IsTVRunning()
Check whether media is currently playing.
static bool IsPaused()
Check whether playback is paused.
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
static const QString NOT_READY
QString AirPlayHardwareId()
QPair< QByteArray, QByteArray > RequestQuery
static const QString SERVER_INFO
static constexpr uint16_t HTTP_STATUS_UNAUTHORIZED
static constexpr const char * AIRPLAY_SERVER_VERSION_STR
static constexpr uint16_t HTTP_STATUS_OK
static constexpr uint16_t HTTP_STATUS_SWITCHING_PROTOCOLS
static const QString PLAYBACK_INFO
static constexpr uint16_t HTTP_STATUS_NOT_FOUND
QByteArray DigestMd5Response(const QString &response, const QString &option, const QString &nonce, const QString &password, QByteArray &auth)
QString GenerateNonce(void)
static const QString EVENT_INFO
static constexpr uint16_t HTTP_STATUS_NOT_IMPLEMENTED
static constexpr int AIRPLAY_PORT_RANGE
static constexpr size_t AIRPLAY_HARDWARE_ID_SIZE
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
MythNotificationCenter * GetNotificationCenter(void)
MythMainWindow * GetMythMainWindow(void)
Convenience inline random number generator functions.
static constexpr const char * ACTION_HANDLEMEDIA
QString toString(const QDateTime &raw_dt, uint format)
Returns formatted string representing the time.
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
uint32_t MythRandom()
generate 32 random bits
#define ACTION_SEEKABSOLUTE