|
MythTV
0.26-pre
|
00001 // TODO 00002 // locking ? 00003 // race on startup? 00004 // http date format and locale 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 // request line 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 // headers 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 // body? 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 // create the server thread 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 // create the server object 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 // start the thread 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 // invalidate 00299 m_valid = false; 00300 00301 // disconnect from mDNS 00302 delete m_bonjour; 00303 m_bonjour = NULL; 00304 00305 // disconnect connections 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 // already started? 00319 if (m_valid) 00320 return; 00321 00322 // join the dots 00323 connect(this, SIGNAL(newConnection(QTcpSocket*)), 00324 this, SLOT(newConnection(QTcpSocket*))); 00325 00326 // start listening for connections 00327 // try a few ports in case the default is in use 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 // announce service 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 // give each frontend a unique name 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 // No session ID, use IP address instead 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 // set initial position if it was set at start of playback. 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 // Assume playback hasn't started yet, get saved position 00543 position = m_connections[session].position; 00544 } 00545 } 00546 if (!playing && m_connections[session].was_playing) 00547 { 00548 // playback got interrupted, notify client to stop 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 // this may be received before playback starts... 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 if (playing && playerspeed < 1.0f) 00593 { 00594 SendReverseEvent(session, AP_EVENT_PLAYING); 00595 QKeyEvent* ke = new QKeyEvent(QEvent::KeyPress, 0, 00596 Qt::NoModifier, ACTION_PLAY); 00597 qApp->postEvent(GetMythMainWindow(), (QEvent*)ke); 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')); // cached 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 }
1.7.6.1