00001
00002
00003
00004
00005
00006 #include <QTcpSocket>
00007 #include <QNetworkInterface>
00008 #include <QCoreApplication>
00009 #include <QKeyEvent>
00010
00011 #include "mthread.h"
00012 #include "mythlogging.h"
00013 #include "mythcorecontext.h"
00014 #include "mythuiactions.h"
00015 #include "mythmainwindow.h"
00016 #include "mythuistatetracker.h"
00017 #include "plist.h"
00018 #include "tv_play.h"
00019
00020 #include "bonjourregister.h"
00021 #include "mythairplayserver.h"
00022 #include "mythraopdevice.h"
00023
00024 MythAirplayServer* MythAirplayServer::gMythAirplayServer = NULL;
00025 MThread* MythAirplayServer::gMythAirplayServerThread = NULL;
00026 QMutex* MythAirplayServer::gMythAirplayServerMutex = new QMutex(QMutex::Recursive);
00027
00028 #define LOC QString("AirPlay: ")
00029
00030 #define HTTP_STATUS_OK 200
00031 #define HTTP_STATUS_SWITCHING_PROTOCOLS 101
00032 #define HTTP_STATUS_NOT_IMPLEMENTED 501
00033
00034 #define AIRPLAY_SERVER_VERSION_STR ""
00035 #define SERVER_INFO QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
00036 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
00037 "<plist version=\"1.0\">\r\n"\
00038 "<dict>\r\n"\
00039 "<key>deviceid</key>\r\n"\
00040 "<string>%1</string>\r\n"\
00041 "<key>features</key>\r\n"\
00042 "<integer>119</integer>\r\n"\
00043 "<key>model</key>\r\n"\
00044 "<string>AppleTV2,1</string>\r\n"\
00045 "<key>protovers</key>\r\n"\
00046 "<string>1.0</string>\r\n"\
00047 "<key>srcvers</key>\r\n"\
00048 "<string>101.28</string>\r\n"\
00049 "</dict>\r\n"\
00050 "</plist>\r\n")
00051
00052 #define EVENT_INFO QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\r\n"\
00053 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n\r\n"\
00054 "<plist version=\"1.0\">\r\n"\
00055 "<dict>\r\n"\
00056 "<key>category</key>\r\n"\
00057 "<string>video</string>\r\n"\
00058 "<key>state</key>\r\n"\
00059 "<string>%1</string>\r\n"\
00060 "</dict>\r\n"\
00061 "</plist>\r\n")
00062
00063 #define PLAYBACK_INFO QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
00064 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
00065 "<plist version=\"1.0\">\r\n"\
00066 "<dict>\r\n"\
00067 "<key>duration</key>\r\n"\
00068 "<real>%1</real>\r\n"\
00069 "<key>loadedTimeRanges</key>\r\n"\
00070 "<array>\r\n"\
00071 "\t\t<dict>\r\n"\
00072 "\t\t\t<key>duration</key>\r\n"\
00073 "\t\t\t<real>%2</real>\r\n"\
00074 "\t\t\t<key>start</key>\r\n"\
00075 "\t\t\t<real>0.0</real>\r\n"\
00076 "\t\t</dict>\r\n"\
00077 "</array>\r\n"\
00078 "<key>playbackBufferEmpty</key>\r\n"\
00079 "<true/>\r\n"\
00080 "<key>playbackBufferFull</key>\r\n"\
00081 "<false/>\r\n"\
00082 "<key>playbackLikelyToKeepUp</key>\r\n"\
00083 "<true/>\r\n"\
00084 "<key>position</key>\r\n"\
00085 "<real>%3</real>\r\n"\
00086 "<key>rate</key>\r\n"\
00087 "<real>%4</real>\r\n"\
00088 "<key>readyToPlay</key>\r\n"\
00089 "<true/>\r\n"\
00090 "<key>seekableTimeRanges</key>\r\n"\
00091 "<array>\r\n"\
00092 "\t\t<dict>\r\n"\
00093 "\t\t\t<key>duration</key>\r\n"\
00094 "\t\t\t<real>%1</real>\r\n"\
00095 "\t\t\t<key>start</key>\r\n"\
00096 "\t\t\t<real>0.0</real>\r\n"\
00097 "\t\t</dict>\r\n"\
00098 "</array>\r\n"\
00099 "</dict>\r\n"\
00100 "</plist>\r\n")
00101
00102 #define NOT_READY QString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"\
00103 "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\r\n"\
00104 "<plist version=\"1.0\">\r\n"\
00105 "<dict>\r\n"\
00106 "<key>readyToPlay</key>\r\n"\
00107 "<false/>\r\n"\
00108 "</dict>\r\n"\
00109 "</plist>\r\n")
00110
00111 class APHTTPRequest
00112 {
00113 public:
00114 APHTTPRequest(QByteArray &data) : m_readPos(0), m_data(data)
00115 {
00116 Process();
00117 Check();
00118 }
00119 ~APHTTPRequest() { }
00120
00121 QByteArray& GetMethod(void) { return m_method; }
00122 QByteArray& GetURI(void) { return m_uri; }
00123 QByteArray& GetBody(void) { return m_body; }
00124 QMap<QByteArray,QByteArray>& GetHeaders(void)
00125 { return m_headers; }
00126
00127 QByteArray GetQueryValue(QByteArray key)
00128 {
00129 for (int i = 0; i < m_queries.size(); i++)
00130 if (m_queries[i].first == key)
00131 return m_queries[i].second;
00132 return "";
00133 }
00134
00135 QMap<QByteArray,QByteArray> GetHeadersFromBody(void)
00136 {
00137 QMap<QByteArray,QByteArray> result;
00138 QList<QByteArray> lines = m_body.split('\n');;
00139 foreach (QByteArray line, lines)
00140 {
00141 int index = line.indexOf(":");
00142 if (index > 0)
00143 {
00144 result.insert(line.left(index).trimmed(),
00145 line.mid(index + 1).trimmed());
00146 }
00147 }
00148 return result;
00149 }
00150
00151 private:
00152 QByteArray GetLine(void)
00153 {
00154 int next = m_data.indexOf("\r\n", m_readPos);
00155 if (next < 0) return QByteArray();
00156 QByteArray line = m_data.mid(m_readPos, next - m_readPos);
00157 m_readPos = next + 2;
00158 return line;
00159 }
00160
00161 void Process(void)
00162 {
00163 if (!m_data.size())
00164 return;
00165
00166
00167 QByteArray line = GetLine();
00168 if (line.isEmpty())
00169 return;
00170 QList<QByteArray> vals = line.split(' ');
00171 if (vals.size() < 3)
00172 return;
00173 m_method = vals[0].trimmed();
00174 QUrl url = QUrl::fromEncoded(vals[1].trimmed());
00175 m_uri = url.encodedPath();
00176 m_queries = url.encodedQueryItems();
00177 if (m_method.isEmpty() || m_uri.isEmpty())
00178 return;
00179
00180
00181 while (!(line = GetLine()).isEmpty())
00182 {
00183 int index = line.indexOf(":");
00184 if (index > 0)
00185 {
00186 m_headers.insert(line.left(index).trimmed(),
00187 line.mid(index + 1).trimmed());
00188 }
00189 }
00190
00191
00192 if (m_headers.contains("Content-Length"))
00193 {
00194 int remaining = m_data.size() - m_readPos;
00195 int size = m_headers["Content-Length"].toInt();
00196 if (size > 0 && remaining > 0 && size <= remaining)
00197 {
00198 m_body = m_data.mid(m_readPos, size);
00199 m_readPos += size;
00200 }
00201 }
00202 }
00203
00204 void Check(void)
00205 {
00206 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00207 QString("HTTP Request:\n%1").arg(m_data.data()));
00208 if (m_readPos == m_data.size())
00209 return;
00210 LOG(VB_GENERAL, LOG_WARNING, LOC +
00211 "AP HTTPRequest: Didn't read entire buffer.");
00212 }
00213
00214 int m_readPos;
00215 QByteArray m_data;
00216 QByteArray m_method;
00217 QByteArray m_uri;
00218 QList<QPair<QByteArray, QByteArray> > m_queries;
00219 QMap<QByteArray,QByteArray> m_headers;
00220 QByteArray m_body;
00221 };
00222
00223 bool MythAirplayServer::Create(void)
00224 {
00225 QMutexLocker locker(gMythAirplayServerMutex);
00226
00227
00228 if (!gMythAirplayServerThread)
00229 gMythAirplayServerThread = new MThread("AirplayServer");
00230 if (!gMythAirplayServerThread)
00231 {
00232 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create airplay thread.");
00233 return false;
00234 }
00235
00236
00237 if (!gMythAirplayServer)
00238 gMythAirplayServer = new MythAirplayServer();
00239 if (!gMythAirplayServer)
00240 {
00241 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create airplay object.");
00242 return false;
00243 }
00244
00245
00246 if (!gMythAirplayServerThread->isRunning())
00247 {
00248 gMythAirplayServer->moveToThread(gMythAirplayServerThread->qthread());
00249 QObject::connect(
00250 gMythAirplayServerThread->qthread(), SIGNAL(started()),
00251 gMythAirplayServer, SLOT(Start()));
00252 gMythAirplayServerThread->start(QThread::LowestPriority);
00253 }
00254
00255 LOG(VB_GENERAL, LOG_INFO, LOC + "Created airplay objects.");
00256 return true;
00257 }
00258
00259 void MythAirplayServer::Cleanup(void)
00260 {
00261 LOG(VB_GENERAL, LOG_INFO, LOC + "Cleaning up.");
00262
00263 if (gMythAirplayServer)
00264 gMythAirplayServer->Teardown();
00265
00266 QMutexLocker locker(gMythAirplayServerMutex);
00267 if (gMythAirplayServerThread)
00268 {
00269 gMythAirplayServerThread->exit();
00270 gMythAirplayServerThread->wait();
00271 }
00272 delete gMythAirplayServerThread;
00273 gMythAirplayServerThread = NULL;
00274
00275 delete gMythAirplayServer;
00276 gMythAirplayServer = NULL;
00277 }
00278
00279
00280 MythAirplayServer::MythAirplayServer()
00281 : ServerPool(), m_name(QString("MythTV")), m_bonjour(NULL), m_valid(false),
00282 m_lock(new QMutex(QMutex::Recursive)), m_setupPort(5100)
00283 {
00284 }
00285
00286 MythAirplayServer::~MythAirplayServer()
00287 {
00288 Teardown();
00289
00290 delete m_lock;
00291 m_lock = NULL;
00292 }
00293
00294 void MythAirplayServer::Teardown(void)
00295 {
00296 QMutexLocker locker(m_lock);
00297
00298
00299 m_valid = false;
00300
00301
00302 delete m_bonjour;
00303 m_bonjour = NULL;
00304
00305
00306 foreach (QTcpSocket* connection, m_sockets)
00307 {
00308 disconnect(connection, 0, 0, 0);
00309 delete connection;
00310 }
00311 m_sockets.clear();
00312 }
00313
00314 void MythAirplayServer::Start(void)
00315 {
00316 QMutexLocker locker(m_lock);
00317
00318
00319 if (m_valid)
00320 return;
00321
00322
00323 connect(this, SIGNAL(newConnection(QTcpSocket*)),
00324 this, SLOT(newConnection(QTcpSocket*)));
00325
00326
00327
00328 int baseport = m_setupPort;
00329 m_setupPort = tryListeningPort(m_setupPort, AIRPLAY_PORT_RANGE);
00330 if (m_setupPort < 0)
00331 {
00332 LOG(VB_GENERAL, LOG_ERR, LOC +
00333 "Failed to find a port for incoming connections.");
00334 }
00335 else
00336 {
00337
00338 m_bonjour = new BonjourRegister(this);
00339 if (!m_bonjour)
00340 {
00341 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create Bonjour object.");
00342 return;
00343 }
00344
00345
00346 int multiple = m_setupPort - baseport;
00347 if (multiple > 0)
00348 m_name += QString::number(multiple);
00349
00350 QByteArray name = m_name.toUtf8();
00351 name.append(" on ");
00352 name.append(gCoreContext->GetHostName());
00353 QByteArray type = "_airplay._tcp";
00354 QByteArray txt;
00355 txt.append(26); txt.append("deviceid="); txt.append(GetMacAddress());
00356 txt.append(14); txt.append("features=0x219");
00357 txt.append(16); txt.append("model=AppleTV2,1");
00358 txt.append(14); txt.append("srcvers=101.28");
00359
00360 if (!m_bonjour->Register(m_setupPort, type, name, txt))
00361 {
00362 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to register service.");
00363 return;
00364 }
00365 }
00366 m_valid = true;
00367 return;
00368 }
00369
00370 void MythAirplayServer::newConnection(QTcpSocket *client)
00371 {
00372 QMutexLocker locker(m_lock);
00373 LOG(VB_GENERAL, LOG_INFO, LOC + QString("New connection from %1:%2")
00374 .arg(client->peerAddress().toString()).arg(client->peerPort()));
00375
00376 m_sockets.append(client);
00377 connect(client, SIGNAL(disconnected()), this, SLOT(deleteConnection()));
00378 connect(client, SIGNAL(readyRead()), this, SLOT(read()));
00379 }
00380
00381 void MythAirplayServer::deleteConnection(void)
00382 {
00383 QMutexLocker locker(m_lock);
00384 QTcpSocket *socket = (QTcpSocket *)sender();
00385 if (!socket)
00386 return;
00387
00388 if (!m_sockets.contains(socket))
00389 return;
00390
00391 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing connection %1:%2")
00392 .arg(socket->peerAddress().toString()).arg(socket->peerPort()));
00393 m_sockets.removeOne(socket);
00394
00395 QByteArray remove;
00396 QMutableHashIterator<QByteArray,AirplayConnection> it(m_connections);
00397 while (it.hasNext())
00398 {
00399 it.next();
00400 if (it.value().reverseSocket == socket)
00401 it.value().reverseSocket = NULL;
00402 if (it.value().controlSocket == socket)
00403 it.value().controlSocket = NULL;
00404 if (!it.value().reverseSocket &&
00405 !it.value().controlSocket &&
00406 it.value().stopped)
00407 {
00408 remove = it.key();
00409 }
00410 }
00411
00412 if (!remove.isEmpty())
00413 {
00414 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Removing session '%1'")
00415 .arg(remove.data()));
00416 m_connections.remove(remove);
00417 }
00418
00419 socket->deleteLater();
00420 }
00421
00422 void MythAirplayServer::read(void)
00423 {
00424 QMutexLocker locker(m_lock);
00425 QTcpSocket *socket = (QTcpSocket *)sender();
00426 if (!socket)
00427 return;
00428
00429 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Read for %1:%2")
00430 .arg(socket->peerAddress().toString()).arg(socket->peerPort()));
00431
00432 QByteArray buf = socket->readAll();
00433 APHTTPRequest request(buf);
00434 HandleResponse(&request, socket);
00435 }
00436
00437 QByteArray MythAirplayServer::StatusToString(int status)
00438 {
00439 switch (status)
00440 {
00441 case HTTP_STATUS_OK: return "OK";
00442 case HTTP_STATUS_SWITCHING_PROTOCOLS: return "Switching Protocols";
00443 case HTTP_STATUS_NOT_IMPLEMENTED: return "Not Implemented";
00444 }
00445 return "";
00446 }
00447
00448 void MythAirplayServer::HandleResponse(APHTTPRequest *req,
00449 QTcpSocket *socket)
00450 {
00451 if (!socket)
00452 return;
00453 QHostAddress addr = socket->peerAddress();
00454 QByteArray session;
00455 QByteArray header;
00456 QString body;
00457 int status = HTTP_STATUS_OK;
00458 QByteArray content_type;
00459
00460 if (req->GetURI() != "/playback-info")
00461 {
00462 LOG(VB_GENERAL, LOG_INFO, LOC +
00463 QString("Method: %1 URI: %2")
00464 .arg(req->GetMethod().data()).arg(req->GetURI().data()));
00465 }
00466 else
00467 {
00468 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00469 QString("Method: %1 URI: %2")
00470 .arg(req->GetMethod().data()).arg(req->GetURI().data()));
00471 }
00472
00473 if (req->GetURI() == "200" || req->GetMethod().startsWith("HTTP"))
00474 return;
00475
00476 if (!req->GetHeaders().contains("X-Apple-Session-ID"))
00477 {
00478 LOG(VB_GENERAL, LOG_DEBUG, LOC +
00479 QString("No session ID in http request. "
00480 "Connection from iTunes? Using IP %1").arg(addr.toString()));
00481 }
00482 else
00483 {
00484 session = req->GetHeaders()["X-Apple-Session-ID"];
00485 }
00486
00487 if (session.size() == 0)
00488 {
00489
00490 session = addr.toString().toAscii();
00491 }
00492 if (!m_connections.contains(session))
00493 {
00494 AirplayConnection apcon;
00495 m_connections.insert(session, apcon);
00496 }
00497
00498 if (req->GetURI() == "/reverse")
00499 {
00500 QTcpSocket *s = m_connections[session].reverseSocket;
00501 if (s != socket && s != NULL)
00502 {
00503 LOG(VB_GENERAL, LOG_ERR, LOC +
00504 "Already have a different reverse socket for this connection.");
00505 return;
00506 }
00507 m_connections[session].reverseSocket = socket;
00508 status = HTTP_STATUS_SWITCHING_PROTOCOLS;
00509 header = "Upgrade: PTTH/1.0\r\nConnection: Upgrade\r\n";
00510 SendResponse(socket, status, header, content_type, body);
00511 return;
00512 }
00513
00514 QTcpSocket *s = m_connections[session].controlSocket;
00515 if (s != socket && s != NULL)
00516 {
00517 LOG(VB_GENERAL, LOG_ERR, LOC +
00518 "Already have a different control socket for this connection.");
00519 return;
00520 }
00521 m_connections[session].controlSocket = socket;
00522
00523 double position = 0.0f;
00524 double duration = 0.0f;
00525 float playerspeed = 0.0f;
00526 bool playing = false;
00527 GetPlayerStatus(playing, playerspeed, position, duration);
00528
00529 if (duration > 0.01f && playing)
00530 {
00531 if (m_connections[session].initial_position > 0.0f)
00532 {
00533 position = duration * m_connections[session].initial_position;
00534 MythEvent* me = new MythEvent(ACTION_SEEKABSOLUTE,
00535 QStringList(QString::number((uint64_t)position)));
00536 qApp->postEvent(GetMythMainWindow(), me);
00537 m_connections[session].position = position;
00538 m_connections[session].initial_position = -1.0f;
00539 }
00540 else if (position < .01f)
00541 {
00542
00543 position = m_connections[session].position;
00544 }
00545 }
00546 if (!playing && m_connections[session].was_playing)
00547 {
00548
00549 if (SendReverseEvent(session, AP_EVENT_STOPPED))
00550 {
00551 m_connections[session].was_playing = false;
00552 }
00553 }
00554 else
00555 {
00556 m_connections[session].was_playing = playing;
00557 }
00558
00559 if (req->GetURI() == "/server-info")
00560 {
00561 content_type = "text/x-apple-plist+xml\r\n";
00562 body = SERVER_INFO;
00563 body.replace("%1", GetMacAddress());
00564 LOG(VB_GENERAL, LOG_INFO, body);
00565 }
00566 else if (req->GetURI() == "/scrub")
00567 {
00568 double pos = req->GetQueryValue("position").toDouble();
00569 if (req->GetMethod() == "POST")
00570 {
00571
00572 uint64_t intpos = (uint64_t)pos;
00573 m_connections[session].position = pos;
00574 LOG(VB_GENERAL, LOG_INFO, LOC +
00575 QString("Scrub: (post) seek to %1").arg(intpos));
00576 MythEvent* me = new MythEvent(ACTION_SEEKABSOLUTE,
00577 QStringList(QString::number(intpos)));
00578 qApp->postEvent(GetMythMainWindow(), me);
00579 }
00580 else if (req->GetMethod() == "GET")
00581 {
00582 content_type = "text/parameters\r\n";
00583 body = QString("duration: %1\r\nposition: %2\r\n")
00584 .arg((double)duration, 0, 'f', 6, '0')
00585 .arg((double)position, 0, 'f', 6, '0');
00586
00587 LOG(VB_GENERAL, LOG_INFO, LOC +
00588 QString("Scrub: (get) returned %1 of %2")
00589 .arg(position).arg(duration));
00590
00591
00592
00593
00594
00595
00596
00597
00598
00599
00600 }
00601 }
00602 else if (req->GetURI() == "/stop")
00603 {
00604 m_connections[session].stopped = true;
00605 QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
00606 Qt::NoModifier, ACTION_STOP);
00607 qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
00608 }
00609 else if (req->GetURI() == "/photo" ||
00610 req->GetURI() == "/slideshow-features")
00611 {
00612 LOG(VB_GENERAL, LOG_INFO, LOC +
00613 "Slideshow functionality not implemented.");
00614 }
00615 else if (req->GetURI() == "/authorize")
00616 {
00617 LOG(VB_GENERAL, LOG_INFO, LOC + "Ignoring authorize request.");
00618 }
00619 else if (req->GetURI() == "/rate")
00620 {
00621 float rate = req->GetQueryValue("value").toFloat();
00622 m_connections[session].speed = rate;
00623
00624 if (rate < 1.0f)
00625 {
00626 SendReverseEvent(session, AP_EVENT_PAUSED);
00627 if (playerspeed > 0.0f)
00628 {
00629 QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
00630 Qt::NoModifier, ACTION_PAUSE);
00631 qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
00632 }
00633 }
00634 else
00635 {
00636 SendReverseEvent(session, AP_EVENT_PLAYING);
00637 if (playerspeed < 1.0f)
00638 {
00639 QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0,
00640 Qt::NoModifier, ACTION_PLAY);
00641 qApp->postEvent(GetMythMainWindow(), (QEvent*)ke);
00642 }
00643 }
00644 }
00645 else if (req->GetURI() == "/play")
00646 {
00647 QByteArray file;
00648 double start_pos = 0.0f;
00649 if (req->GetHeaders().contains("Content-Type") &&
00650 req->GetHeaders()["Content-Type"] == "application/x-apple-binary-plist")
00651 {
00652 PList plist(req->GetBody());
00653 LOG(VB_GENERAL, LOG_DEBUG, LOC + plist.ToString());
00654
00655 QVariant start = plist.GetValue("Start-Position");
00656 QVariant content = plist.GetValue("Content-Location");
00657 if (start.isValid() && start.canConvert<double>())
00658 start_pos = start.toDouble();
00659 if (content.isValid() && content.canConvert<QByteArray>())
00660 file = content.toByteArray();
00661 }
00662 else
00663 {
00664 QMap<QByteArray,QByteArray> headers = req->GetHeadersFromBody();
00665 file = headers["Content-Location"];
00666 start_pos = headers["Start-Position"].toDouble();
00667 }
00668
00669 if (!TV::IsTVRunning())
00670 {
00671 if (!file.isEmpty())
00672 {
00673 m_connections[session].url = QUrl(file);
00674 m_connections[session].position = 0.0f;
00675 m_connections[session].initial_position = start_pos;
00676
00677 MythEvent* me = new MythEvent(ACTION_HANDLEMEDIA,
00678 QStringList(file));
00679 qApp->postEvent(GetMythMainWindow(), me);
00680 }
00681 }
00682 else
00683 {
00684 LOG(VB_GENERAL, LOG_WARNING, LOC +
00685 "Ignoring playback - something else is playing.");
00686 }
00687
00688 SendReverseEvent(session, AP_EVENT_PLAYING);
00689 LOG(VB_GENERAL, LOG_INFO, LOC + QString("File: '%1' start_pos '%2'")
00690 .arg(file.data()).arg(start_pos));
00691 }
00692 else if (req->GetURI() == "/playback-info")
00693 {
00694 content_type = "text/x-apple-plist+xml\r\n";
00695
00696 if (!playing)
00697 {
00698 body = NOT_READY;
00699 SendReverseEvent(session, AP_EVENT_LOADING);
00700 }
00701 else
00702 {
00703 body = PLAYBACK_INFO;
00704 body.replace("%1", QString("%1").arg((double)duration, 0, 'f', 6, '0'));
00705 body.replace("%2", QString("%1").arg((double)duration, 0, 'f', 6, '0'));
00706 body.replace("%3", QString("%1").arg((double)position, 0, 'f', 6, '0'));
00707 body.replace("%4", playerspeed > 0.0f ? "1.0" : "0.0");
00708 LOG(VB_GENERAL, LOG_DEBUG, body);
00709 SendReverseEvent(session, playerspeed > 0.0f ? AP_EVENT_PLAYING :
00710 AP_EVENT_PAUSED);
00711 }
00712 }
00713 SendResponse(socket, status, header, content_type, body);
00714 }
00715
00716 void MythAirplayServer::SendResponse(QTcpSocket *socket,
00717 int status, QByteArray header,
00718 QByteArray content_type, QString body)
00719 {
00720 QTextStream response(socket);
00721 response.setCodec("UTF-8");
00722 QByteArray reply;
00723 reply.append("HTTP/1.1 ");
00724 reply.append(QString::number(status));
00725 reply.append(" ");
00726 reply.append(StatusToString(status));
00727 reply.append("\r\n");
00728 reply.append("DATE: ");
00729 reply.append(QDateTime::currentDateTime().toString("ddd, d MMM yyyy hh:mm:ss"));
00730 reply.append(" GMT\r\n");
00731 if (header.size())
00732 reply.append(header);
00733
00734 if (body.size())
00735 {
00736 reply.append("Content-Type: ");
00737 reply.append(content_type);
00738 reply.append("Content-Length: ");
00739 reply.append(QString::number(body.size()));
00740 reply.append("\r\n");
00741 }
00742
00743 reply.append("\r\n");
00744
00745 if (body.size())
00746 reply.append(body);
00747
00748 response << reply;
00749 response.flush();
00750
00751 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Send: %1 \n\n%2\n")
00752 .arg(socket->flush()).arg(reply.data()));
00753 }
00754
00755 bool MythAirplayServer::SendReverseEvent(QByteArray &session,
00756 AirplayEvent event)
00757 {
00758 if (!m_connections.contains(session))
00759 return false;
00760 if (m_connections[session].lastEvent == event)
00761 return false;
00762 if (!m_connections[session].reverseSocket)
00763 return false;
00764
00765 QString body;
00766 if (AP_EVENT_PLAYING == event ||
00767 AP_EVENT_LOADING == event ||
00768 AP_EVENT_PAUSED == event ||
00769 AP_EVENT_STOPPED == event)
00770 {
00771 body = EVENT_INFO;
00772 body.replace("%1", eventToString(event));
00773 }
00774
00775 m_connections[session].lastEvent = event;
00776 QTextStream response(m_connections[session].reverseSocket);
00777 response.setCodec("UTF-8");
00778 QByteArray reply;
00779 reply.append("POST /event HTTP/1.1\r\n");
00780 reply.append("Content-Type: text/x-apple-plist+xml\r\n");
00781 reply.append("Content-Length: ");
00782 reply.append(QString::number(body.size()));
00783 reply.append("\r\n");
00784 reply.append("x-apple-session-id: ");
00785 reply.append(session);
00786 reply.append("\r\n\r\n");
00787 if (body.size())
00788 reply.append(body);
00789
00790 response << reply;
00791 response.flush();
00792
00793 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Send reverse: %1 \n\n%2\n")
00794 .arg(m_connections[session].reverseSocket->flush())
00795 .arg(reply.data()));
00796 return true;
00797 }
00798
00799 QString MythAirplayServer::eventToString(AirplayEvent event)
00800 {
00801 switch (event)
00802 {
00803 case AP_EVENT_PLAYING: return "playing";
00804 case AP_EVENT_PAUSED: return "paused";
00805 case AP_EVENT_LOADING: return "loading";
00806 case AP_EVENT_STOPPED: return "stopped";
00807 }
00808 return "";
00809 }
00810
00811 void MythAirplayServer::GetPlayerStatus(bool &playing, float &speed,
00812 double &position, double &duration)
00813 {
00814 QVariantMap state;
00815 MythUIStateTracker::GetFreshState(state);
00816 if (state.contains("state"))
00817 playing = state["state"].toString() != "idle";
00818 if (state.contains("playspeed"))
00819 speed = state["playspeed"].toFloat();
00820 if (state.contains("secondsplayed"))
00821 position = state["secondsplayed"].toDouble();
00822 if (state.contains("totalseconds"))
00823 duration = state["totalseconds"].toDouble();
00824 }
00825
00826 QString MythAirplayServer::GetMacAddress()
00827 {
00828 QString id = MythRAOPDevice::HardwareId();
00829
00830 QString res;
00831 for (int i = 1; i <= id.size(); i++)
00832 {
00833 res.append(id[i-1]);
00834 if (i % 2 == 0 && i != id.size())
00835 {
00836 res.append(':');
00837 }
00838 }
00839 return res;
00840 }