Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Properly handle multi-part http request in AirPlay video
This commit also adds the basics required to support AirPlay photo. Incidentally, properly handling multi-part data now allows to play videos from the iPhone "Photos" app.
  • Loading branch information
jyavenard committed Apr 24, 2013
1 parent 87e62f5 commit 7554135
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 17 deletions.
108 changes: 91 additions & 17 deletions mythtv/libs/libmythtv/AirPlay/mythairplayserver.cpp
Expand Up @@ -196,7 +196,8 @@ QByteArray DigestMd5Response(QString response, QString option,
class APHTTPRequest
{
public:
APHTTPRequest(QByteArray &data) : m_readPos(0), m_data(data)
APHTTPRequest(QByteArray& data) : m_readPos(0), m_data(data), m_size(0),
m_incomingPartial(false)
{
Process();
Check();
Expand All @@ -209,6 +210,12 @@ class APHTTPRequest
QMap<QByteArray,QByteArray>& GetHeaders(void)
{ return m_headers; }

void Append(QByteArray& data)
{
m_body.append(data);
Check();
}

QByteArray GetQueryValue(QByteArray key)
{
for (int i = 0; i < m_queries.size(); i++)
Expand All @@ -233,6 +240,11 @@ class APHTTPRequest
return result;
}

bool IsComplete(void)
{
return !m_incomingPartial;
}

private:
QByteArray GetLine(void)
{
Expand Down Expand Up @@ -277,32 +289,43 @@ class APHTTPRequest
if (m_headers.contains("Content-Length"))
{
int remaining = m_data.size() - m_readPos;
int size = m_headers["Content-Length"].toInt();
if (size > 0 && remaining > 0 && size <= remaining)
m_size = m_headers["Content-Length"].toInt();
if (m_size > 0 && remaining > 0)
{
m_body = m_data.mid(m_readPos, size);
m_readPos += size;
m_body = m_data.mid(m_readPos, m_size);
m_readPos += m_body.size();
}
}
}

void Check(void)
{
LOG(VB_GENERAL, LOG_DEBUG, LOC +
QString("HTTP Request:\n%1").arg(m_data.data()));
if (m_readPos == m_data.size())
if (!m_incomingPartial)
{
LOG(VB_GENERAL, LOG_DEBUG, LOC +
QString("HTTP Request:\n%1").arg(m_data.data()));
}
if (m_body.size() < m_size)
{
LOG(VB_PLAYBACK, LOG_DEBUG, LOC +
QString("AP HTTPRequest: Didn't read entire buffer."
"Left to receive: %1 (got %2 of %3) body=%4")
.arg(m_size-m_body.size()).arg(m_readPos).arg(m_size).arg(m_body.size()));
m_incomingPartial = true;
return;
LOG(VB_GENERAL, LOG_WARNING, LOC +
"AP HTTPRequest: Didn't read entire buffer.");
}
m_incomingPartial = false;
}

int m_readPos;
int m_readPos;
QByteArray m_data;
QByteArray m_method;
QByteArray m_uri;
QList<QPair<QByteArray, QByteArray> > m_queries;
QMap<QByteArray,QByteArray> m_headers;
QByteArray m_body;
int m_size;
bool m_incomingPartial;
};

bool MythAirplayServer::Create(void)
Expand Down Expand Up @@ -394,6 +417,13 @@ void MythAirplayServer::Teardown(void)
delete connection;
}
m_sockets.clear();

// remove all incoming buffers
foreach (APHTTPRequest* request, m_incoming)
{
delete request;
}
m_incoming.clear();
}

void MythAirplayServer::Start(void)
Expand Down Expand Up @@ -438,7 +468,7 @@ void MythAirplayServer::Start(void)
QByteArray type = "_airplay._tcp";
QByteArray txt;
txt.append(26); txt.append("deviceid="); txt.append(GetMacAddress());
txt.append(14); txt.append("features=0x219");
txt.append(14); txt.append("features=0x23b");
txt.append(16); txt.append("model=AppleTV2,1");
txt.append(14); txt.append("srcvers=101.28");

Expand Down Expand Up @@ -512,6 +542,12 @@ void MythAirplayServer::deleteConnection(QTcpSocket *socket)
}

socket->deleteLater();

if (m_incoming.contains(socket))
{
delete m_incoming[socket];
m_incoming.remove(socket);
}
}

void MythAirplayServer::read(void)
Expand All @@ -525,8 +561,24 @@ void MythAirplayServer::read(void)
.arg(socket->peerAddress().toString()).arg(socket->peerPort()));

QByteArray buf = socket->readAll();
APHTTPRequest request(buf);
HandleResponse(&request, socket);

if (!m_incoming.contains(socket))
{
APHTTPRequest *request = new APHTTPRequest(buf);
m_incoming.insert(socket, request);
}
else
{
m_incoming[socket]->Append(buf);
}
if (!m_incoming[socket]->IsComplete())
return;
HandleResponse(m_incoming[socket], socket);
if (m_incoming.contains(socket))
{
delete m_incoming[socket];
m_incoming.remove(socket);
}
}

QByteArray MythAirplayServer::StatusToString(int status)
Expand Down Expand Up @@ -732,8 +784,19 @@ void MythAirplayServer::HandleResponse(APHTTPRequest *req,
{
StopSession(session);
}
else if (req->GetURI() == "/photo" ||
req->GetURI() == "/slideshow-features")
else if (req->GetURI() == "/photo")
{
if (req->GetMethod() == "PUT")
{
// this may be received before playback starts...
bool png =
req->GetBody().size() > 3 && req->GetBody()[1] == 'P' &&
req->GetBody()[2] == 'N' && req->GetBody()[3] == 'G';
LOG(VB_GENERAL, LOG_INFO, LOC +
QString("Received %1 photo").arg(png ? "jpeg" : "png"));
}
}
else if (req->GetURI() == "/slideshow-features")
{
LOG(VB_GENERAL, LOG_INFO, LOC +
"Slideshow functionality not implemented.");
Expand Down Expand Up @@ -830,7 +893,8 @@ void MythAirplayServer::SendResponse(QTcpSocket *socket,
int status, QByteArray header,
QByteArray content_type, QString body)
{
if (!socket)
if (!socket || !m_incoming.contains(socket) ||
socket->state() != QAbstractSocket::ConnectedState)
return;
QTextStream response(socket);
response.setCodec("UTF-8");
Expand Down Expand Up @@ -1003,6 +1067,11 @@ void MythAirplayServer::DisconnectAllClients(const QByteArray &session)
socket->close();
m_sockets.removeOne(socket);
socket->deleteLater();
if (m_incoming.contains(socket))
{
delete m_incoming[socket];
m_incoming.remove(socket);
}
}
socket = it.value().controlSocket;
if (socket)
Expand All @@ -1011,6 +1080,11 @@ void MythAirplayServer::DisconnectAllClients(const QByteArray &session)
socket->close();
m_sockets.removeOne(socket);
socket->deleteLater();
if (m_incoming.contains(socket))
{
delete m_incoming[socket];
m_incoming.remove(socket);
}
}
it = m_connections.erase(it);
}
Expand Down
3 changes: 3 additions & 0 deletions mythtv/libs/libmythtv/AirPlay/mythairplayserver.h
Expand Up @@ -111,6 +111,9 @@ class MTV_PUBLIC MythAirplayServer : public ServerPool

//Authentication
QString m_nonce;

//Incoming data
QHash<QTcpSocket*, APHTTPRequest*> m_incoming;
};

#endif // MYTHAIRPLAYSERVER_H

2 comments on commit 7554135

@ciaranj
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work on adding this support, http://www.gossamer-threads.com/lists/mythtv/users/542761#542761 suggested you might be back-porting this to the 0.26 stream, is that still a thing, or should I just go compile from source ?

@jyavenard
Copy link
Member Author

@jyavenard jyavenard commented on 7554135 Apr 29, 2013 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.