Skip to content

Commit

Permalink
Properly handle multi-part http request in AirPlay video
Browse files Browse the repository at this point in the history
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.