00001
00002
00003
00004
00005
00006
00007
00008 #include <assert.h>
00009 #include <algorithm>
00010
00011
00012 #include <QApplication>
00013 #include <QRegExp>
00014 #include <QAbstractSocket>
00015 #include <QTimer>
00016
00017
00018 #include <mythcorecontext.h>
00019 #include <mcodecs.h>
00020 #include <mythversion.h>
00021
00022
00023 #include "metadata.h"
00024 #include "shoutcast.h"
00025
00026
00027
00028
00029 #define MAX_ALLOWED_HEADER_SIZE 1024 * 4
00030 #define MAX_ALLOWED_META_SIZE 1024 * 100
00031 #define MAX_REDIRECTS 3
00032 #define PREBUFFER_SECS 5
00033 #define ICE_UDP_PORT 6000
00034
00035
00036
00037 const char* ShoutCastIODevice::stateString (const State &s)
00038 {
00039 #define TO_STRING(a) case a: return #a
00040 switch (s) {
00041 TO_STRING (NOT_CONNECTED);
00042 TO_STRING (RESOLVING);
00043 TO_STRING (CONNECTING);
00044 TO_STRING (CANT_RESOLVE);
00045 TO_STRING (CANT_CONNECT);
00046 TO_STRING (CONNECTED);
00047 TO_STRING (WRITING_HEADER);
00048 TO_STRING (READING_HEADER);
00049 TO_STRING (PLAYING);
00050 TO_STRING (STREAMING);
00051 TO_STRING (STREAMING_META);
00052 TO_STRING (STOPPED);
00053 default:
00054 return "unknown state";
00055 }
00056 #undef TO_STRING
00057 }
00058
00059
00060
00061 class ShoutCastRequest
00062 {
00063 public:
00064 ShoutCastRequest(void) { }
00065 ShoutCastRequest(const QUrl &url) { setUrl(url); }
00066 ~ShoutCastRequest(void) { }
00067 const char *data(void) { return m_data.data(); }
00068 uint size(void) { return m_data.size(); }
00069
00070 private:
00071 void setUrl(const QUrl &url)
00072 {
00073 QString hdr;
00074 hdr = QString("GET %1 HTTP/1.1\r\n"
00075 "Host: %2\r\n"
00076 "User-Agent: MythMusic/%3\r\n"
00077 "Accept: */*\r\n")
00078 .arg(url.path()).arg(url.host().arg(MYTH_BINARY_VERSION));
00079 if (!url.userName().isEmpty() && !url.password().isEmpty())
00080 {
00081 QString authstring = url.userName() + ":" + url.password();
00082 QString auth = QCodecs::base64Encode(authstring.toLocal8Bit());
00083
00084 hdr += "Authorization: Basic " + auth;
00085 }
00086
00087 hdr += QString("TE: trailers\r\n"
00088 "Icy-Metadata: 1\r\n"
00089 "\r\n");
00090
00091 m_data = hdr.toAscii();
00092 }
00093
00094 QByteArray m_data;
00095 };
00096
00097 class IceCastRequest
00098 {
00099 public:
00100 IceCastRequest(void) { }
00101 IceCastRequest(const QUrl &url) { setUrl(url); }
00102 ~IceCastRequest(void) { }
00103 const char *data(void) { return m_data.data(); }
00104 uint size(void) { return m_data.size(); }
00105
00106 private:
00107 void setUrl(const QUrl &url)
00108 {
00109 QString hdr;
00110 hdr = QString("GET %1 HTTP/1.1\r\n"
00111 "Host: %2\r\n"
00112 "User-Agent: MythMusic/%3\r\n"
00113 "Accept: */*\r\n")
00114 .arg(url.path()).arg(url.host()).arg(MYTH_BINARY_VERSION);
00115 if (!url.userName().isEmpty() && !url.password().isEmpty())
00116 {
00117 QString authstring = url.userName() + ":" + url.password();
00118 QString auth = QCodecs::base64Encode(authstring.toLocal8Bit());
00119
00120 hdr += "Authorization: Basic " + auth;
00121 }
00122
00123 hdr += QString("TE: trailers\r\n"
00124 "x-audiocast-udpport: %1\r\n"
00125 "\r\n").arg(ICE_UDP_PORT);
00126
00127 m_data = hdr.toAscii();
00128 }
00129
00130 QByteArray m_data;
00131 };
00132
00133
00134
00135
00136 class ShoutCastResponse
00137 {
00138 public:
00139 ShoutCastResponse(void) { }
00140 ~ShoutCastResponse(void) { }
00141
00142 int getMetaint(void)
00143 {
00144 if (m_data.contains("icy-metaint"))
00145 return getInt("icy-metaint");
00146 else
00147 return -1;
00148 }
00149 int getBitrate(void) { return getInt("icy-br"); }
00150 QString getGenre(void) { return getString("icy-genre"); }
00151 QString getName(void) { return getString("icy-name"); }
00152 int getStatus(void) { return getInt("status"); }
00153 bool isICY(void) { return QString(m_data["protocol"]).left(3) == "ICY"; }
00154 QString getContent(void) { return getString("content-type"); }
00155 QString getLocation(void) { return getString("location"); }
00156
00157 QString getString(const QString &key) { return m_data[key]; }
00158 int getInt(const QString &key) { return m_data[key].toInt(); }
00159
00160 int fillResponse(const char *data, int len);
00161
00162 private:
00163 QMap<QString, QString> m_data;
00164 };
00165
00169 int ShoutCastResponse::fillResponse(const char *s, int l)
00170 {
00171 QByteArray d(s, l);
00172 int result = 0;
00173
00174 for (;;)
00175 {
00176 int pos = d.indexOf("\r");
00177
00178 if (pos <= 0)
00179 break;
00180
00181
00182 QByteArray snip(d.data(), pos + 1);
00183 d.remove(0, pos + 2);
00184 result += pos + 2;
00185
00186 if (snip.left(4) == "ICY ")
00187 {
00188 int space = snip.indexOf(' ');
00189 m_data["protocol"] = "ICY";
00190 QString tmp = snip.mid(space).simplified();
00191 int second_space = tmp.indexOf(' ');
00192 if (second_space > 0)
00193 m_data["status"] = tmp.left(second_space);
00194 else
00195 m_data["status"] = tmp;
00196 }
00197 else if (snip.left(7) == "HTTP/1.")
00198 {
00199 int space = snip.indexOf(' ');
00200 m_data["protocol"] = snip.left(space);
00201 QString tmp = snip.mid(space).simplified();
00202 int second_space = tmp.indexOf(' ');
00203 if (second_space > 0)
00204 m_data["status"] = tmp.left(second_space);
00205 else
00206 m_data["status"] = tmp;
00207 }
00208 else if (snip.left(9).toLower() == "location:")
00209 {
00210 m_data["location"] = snip.mid(9).trimmed();
00211 }
00212 else if (snip.left(13).toLower() == "content-type:")
00213 {
00214 m_data["content-type"] = snip.mid(13).trimmed();
00215 }
00216 else if (snip.left(4) == "icy-")
00217 {
00218 int pos = snip.indexOf(':');
00219 QString key = snip.left(pos);
00220 m_data[key.toAscii()] = snip.mid(pos+1).trimmed();
00221 }
00222 }
00223
00224 return result;
00225 }
00226
00227
00228
00229 class ShoutCastMetaParser
00230 {
00231 public:
00232 ShoutCastMetaParser(void) :
00233 m_meta_artist_pos(-1), m_meta_title_pos(-1), m_meta_album_pos(-1) { }
00234 ~ShoutCastMetaParser(void) { }
00235
00236 void setMetaFormat(const QString &metaformat);
00237 ShoutCastMetaMap parseMeta(QString meta);
00238
00239 private:
00240 QString m_meta_format;
00241 int m_meta_artist_pos;
00242 int m_meta_title_pos;
00243 int m_meta_album_pos;
00244 };
00245
00246 void ShoutCastMetaParser::setMetaFormat(const QString &metaformat)
00247 {
00248
00249
00250
00251
00252
00253
00254
00255 m_meta_format = metaformat;
00256
00257 m_meta_artist_pos = 0;
00258 m_meta_title_pos = 0;
00259 m_meta_album_pos = 0;
00260
00261 int assign_index = 1;
00262 int pos = 0;
00263
00264 pos = m_meta_format.indexOf("%", pos);
00265 while (pos >= 0)
00266 {
00267 pos++;
00268 QChar ch = m_meta_format.at(pos);
00269
00270 if (ch == '%')
00271 {
00272 pos++;
00273 }
00274 else if (ch == 'r' || ch == 'a' || ch == 'b' || ch == 't')
00275 {
00276 if (ch == 'a')
00277 m_meta_artist_pos = assign_index;
00278
00279 if (ch == 'b')
00280 m_meta_album_pos = assign_index;
00281
00282 if (ch == 't')
00283 m_meta_title_pos = assign_index;
00284
00285 assign_index++;
00286 }
00287 else
00288 LOG(VB_GENERAL, LOG_ERR,
00289 QString("ShoutCastMetaParser: malformed metaformat '%1'")
00290 .arg(m_meta_format));
00291
00292 pos = m_meta_format.indexOf("%", pos);
00293 }
00294
00295 m_meta_format.replace("%a", "(.*)");
00296 m_meta_format.replace("%t", "(.*)");
00297 m_meta_format.replace("%b", "(.*)");
00298 m_meta_format.replace("%r", "(.*)");
00299 m_meta_format.replace("%%", "%");
00300 }
00301
00302 ShoutCastMetaMap ShoutCastMetaParser::parseMeta(QString meta)
00303 {
00304 QByteArray metastring(meta.toLocal8Bit());
00305 ShoutCastMetaMap result;
00306 int title_begin_pos = metastring.indexOf("StreamTitle='");
00307 int title_end_pos;
00308
00309 if (title_begin_pos >= 0)
00310 {
00311 title_begin_pos += 13;
00312 title_end_pos = metastring.indexOf("';", title_begin_pos);
00313 QByteArray title = metastring.mid(title_begin_pos,
00314 title_end_pos - title_begin_pos);
00315 QRegExp rx;
00316 rx.setPattern(m_meta_format);
00317 if (rx.indexIn(title) != -1)
00318 {
00319 LOG(VB_PLAYBACK, LOG_INFO, QString("ShoutCast: Meta : '%1'")
00320 .arg(meta));
00321 LOG(VB_PLAYBACK, LOG_INFO,
00322 QString("ShoutCast: Parsed as: '%1' by '%2'")
00323 .arg(rx.cap(m_meta_title_pos))
00324 .arg(rx.cap(m_meta_artist_pos)));
00325
00326 if (m_meta_title_pos > 0)
00327 result["title"] = rx.cap(m_meta_title_pos);
00328
00329 if (m_meta_artist_pos > 0)
00330 result["artist"] = rx.cap(m_meta_artist_pos);
00331
00332 if (m_meta_album_pos > 0)
00333 result["album"] = rx.cap(m_meta_album_pos);
00334 }
00335 }
00336
00337 return result;
00338 }
00339
00340
00341
00342 ShoutCastIODevice::ShoutCastIODevice(void)
00343 : m_redirects (0),
00344 m_scratchpad_pos (0),
00345 m_state (NOT_CONNECTED)
00346 {
00347 m_socket = new QTcpSocket;
00348 m_response = new ShoutCastResponse;
00349
00350 connect(m_socket, SIGNAL(hostFound()), SLOT(socketHostFound()));
00351 connect(m_socket, SIGNAL(connected()), SLOT(socketConnected()));
00352 connect(m_socket, SIGNAL(disconnected()), SLOT(socketConnectionClosed()));
00353 connect(m_socket, SIGNAL(readyRead()), SLOT(socketReadyRead()));
00354 connect(m_socket, SIGNAL(error(QAbstractSocket::SocketError)),
00355 SLOT(socketError(QAbstractSocket::SocketError)));
00356
00357 switchToState(NOT_CONNECTED);
00358
00359 setOpenMode(ReadWrite);
00360 }
00361
00362 ShoutCastIODevice::~ShoutCastIODevice(void)
00363 {
00364 delete m_response;
00365 m_socket->close();
00366 m_socket->disconnect(this);
00367 m_socket->deleteLater();
00368 m_socket = NULL;
00369 }
00370
00371 void ShoutCastIODevice::connectToUrl(const QUrl &url)
00372 {
00373 m_url = url;
00374 switchToState (RESOLVING);
00375 setOpenMode(ReadWrite);
00376 open(ReadWrite);
00377 return m_socket->connectToHost(m_url.host(), m_url.port() == -1 ? 80 : m_url.port());
00378 }
00379
00380 void ShoutCastIODevice::close(void)
00381 {
00382 return m_socket->close();
00383 }
00384
00385 bool ShoutCastIODevice::flush(void)
00386 {
00387 return m_socket->flush();
00388 }
00389
00390 qint64 ShoutCastIODevice::size(void) const
00391 {
00392 return m_buffer->readBufAvail();
00393 }
00394
00395 qint64 ShoutCastIODevice::readData(char *data, qint64 maxlen)
00396 {
00397
00398
00399
00400
00401 socketReadyRead();
00402
00403 if (m_buffer->readBufAvail() == 0)
00404 {
00405 LOG(VB_PLAYBACK, LOG_ERR, "ShoutCastIODevice: No data in buffer!!");
00406 switchToState(STOPPED);
00407 return -1;
00408 }
00409
00410 if (m_state == STREAMING_META && parseMeta())
00411 switchToState(STREAMING);
00412
00413 if (m_state == STREAMING)
00414 {
00415 if (m_bytesTillNextMeta > 0)
00416 {
00417
00418 if (maxlen > m_bytesTillNextMeta)
00419 maxlen = m_bytesTillNextMeta;
00420
00421 maxlen = m_buffer->read(data, maxlen);
00422
00423 m_bytesTillNextMeta -= maxlen;
00424
00425 if (m_bytesTillNextMeta == 0)
00426 switchToState(STREAMING_META);
00427 }
00428 else
00429 maxlen = m_buffer->read(data, maxlen);
00430 }
00431
00432 if (m_state != STOPPED)
00433 LOG(VB_NETWORK, LOG_INFO,
00434 QString("ShoutCastIODevice: %1 kb in buffer, btnm=%2/%3 "
00435 "state=%4, len=%5")
00436 .arg(m_buffer->readBufAvail() / 1024, 4)
00437 .arg(m_bytesTillNextMeta, 4)
00438 .arg(m_response->getMetaint())
00439 .arg(stateString (m_state))
00440 .arg(maxlen));
00441 else
00442 LOG(VB_NETWORK, LOG_INFO, QString("ShoutCastIODevice: stopped"));
00443
00444 return maxlen;
00445 }
00446
00447 qint64 ShoutCastIODevice::writeData(const char *data, qint64 sz)
00448 {
00449 return m_socket->write(data, sz);
00450 }
00451
00452 qint64 ShoutCastIODevice::bytesAvailable(void) const
00453 {
00454 return m_buffer->readBufAvail();
00455 }
00456
00457 void ShoutCastIODevice::socketHostFound(void)
00458 {
00459 LOG(VB_NETWORK, LOG_INFO, "ShoutCastIODevice: Host Found");
00460 switchToState(CONNECTING);
00461 }
00462
00463 void ShoutCastIODevice::socketConnected(void)
00464 {
00465 LOG(VB_NETWORK, LOG_INFO, "ShoutCastIODevice: Connected");
00466 switchToState(CONNECTED);
00467
00468 ShoutCastRequest request(m_url);
00469 qint64 written = m_socket->write(request.data(), request.size());
00470 LOG(VB_NETWORK, LOG_INFO,
00471 QString("ShoutCastIODevice: Sending Request, %1 of %2 bytes")
00472 .arg(written).arg(request.size()));
00473
00474 if (written != request.size())
00475 {
00476 LOG(VB_NETWORK, LOG_INFO, "ShoutCastIODevice: buffering write");
00477 m_scratchpad = QByteArray(request.data() + written, request.size() - written);
00478 m_scratchpad_pos = 0;
00479 connect(m_socket, SIGNAL (bytesWritten(qint64)), SLOT(socketBytesWritten(qint64)));
00480 switchToState(WRITING_HEADER);
00481 }
00482 else
00483 switchToState(READING_HEADER);
00484
00485 m_started = false;
00486 m_bytesDownloaded = 0;
00487 m_bytesTillNextMeta = 0;
00488 m_response_gotten = false;
00489 }
00490
00491 void ShoutCastIODevice::socketConnectionClosed(void)
00492 {
00493 LOG(VB_NETWORK, LOG_INFO, "ShoutCastIODevice: Connection Closed");
00494 switchToState(STOPPED);
00495 }
00496
00497 void ShoutCastIODevice::socketReadyRead(void)
00498 {
00499
00500 int available = DecoderIOFactory::DefaultBufferSize - m_buffer->readBufAvail();
00501
00502 QByteArray data = m_socket->read(available);
00503
00504 m_bytesDownloaded += data.size();
00505 m_buffer->write(data);
00506
00507 if (!m_started && m_bytesDownloaded > DecoderIOFactory::DefaultPrebufferSize)
00508 {
00509 m_socket->setReadBufferSize(DecoderIOFactory::DefaultPrebufferSize);
00510 m_started = true;
00511 }
00512
00513
00514 if (m_state == READING_HEADER && m_buffer->readBufAvail() >= MAX_ALLOWED_HEADER_SIZE)
00515 {
00516 if (parseHeader())
00517 {
00518 if (m_response->getStatus() == 200)
00519 {
00520 switchToState(PLAYING);
00521
00522 m_response_gotten = true;
00523
00524 m_bytesTillNextMeta = m_response->getMetaint();
00525
00526 switchToState(STREAMING);
00527 }
00528 else if (m_response->getStatus() == 302 || m_response->getStatus() == 301)
00529 {
00530 if (++m_redirects > MAX_REDIRECTS)
00531 {
00532 LOG(VB_NETWORK, LOG_ERR, QString("Too many redirects"));
00533 switchToState(STOPPED);
00534 }
00535 else
00536 {
00537 LOG(VB_NETWORK, LOG_INFO, QString("Redirect to %1").arg(m_response->getLocation()));
00538 m_socket->close();
00539 connectToUrl(m_url);
00540 return;
00541 }
00542 }
00543 else
00544 {
00545 LOG(VB_NETWORK, LOG_ERR, QString("Unknown response status %1")
00546 .arg(m_response->getStatus()));
00547 switchToState(STOPPED);
00548 }
00549 }
00550 }
00551 }
00552
00553 void ShoutCastIODevice::socketBytesWritten(qint64)
00554 {
00555 qint64 written = m_socket->write(m_scratchpad.data() + m_scratchpad_pos,
00556 m_scratchpad.size() - m_scratchpad_pos);
00557 LOG(VB_NETWORK, LOG_INFO,
00558 QString("ShoutCastIO: %1 bytes written").arg(written));
00559
00560 m_scratchpad_pos += written;
00561 if (m_scratchpad_pos == m_scratchpad.size())
00562 {
00563 m_scratchpad.truncate(0);
00564 disconnect (m_socket, SIGNAL(bytesWritten(qint64)), this, 0);
00565 switchToState(READING_HEADER);
00566 }
00567 }
00568
00569 void ShoutCastIODevice::socketError(QAbstractSocket::SocketError error)
00570 {
00571 switch (error)
00572 {
00573 case QAbstractSocket::ConnectionRefusedError:
00574 LOG(VB_NETWORK, LOG_ERR,
00575 "ShoutCastIODevice: Error Connection Refused");
00576 switchToState(CANT_CONNECT);
00577 break;
00578 case QAbstractSocket::RemoteHostClosedError:
00579 LOG(VB_NETWORK, LOG_ERR,
00580 "ShoutCastIODevice: Error Remote Host Closed The Connection");
00581 switchToState(CANT_CONNECT);
00582 break;
00583 case QAbstractSocket::HostNotFoundError:
00584 LOG(VB_NETWORK, LOG_ERR,
00585 "ShoutCastIODevice: Error Host Not Found");
00586 switchToState(CANT_RESOLVE);
00587 break;
00588 case QAbstractSocket::SocketTimeoutError:
00589 LOG(VB_NETWORK, LOG_ERR, "ShoutCastIODevice: Error Socket Timeout");
00590 switchToState(STOPPED);
00591 break;
00592 default:
00593 LOG(VB_NETWORK, LOG_ERR,
00594 QString("ShoutCastIODevice: Got socket error '%1'")
00595 .arg(errorString()));
00596 switchToState(STOPPED);
00597 break;
00598 }
00599 }
00600
00601 void ShoutCastIODevice::switchToState(const State &state)
00602 {
00603 switch (state)
00604 {
00605 case PLAYING:
00606 LOG(VB_PLAYBACK, LOG_INFO, QString("Playing %1 (%2) at %3 kbps")
00607 .arg(m_response->getName())
00608 .arg(m_response->getGenre())
00609 .arg(m_response->getBitrate()));
00610 break;
00611 case STREAMING:
00612 if (m_state == STREAMING_META)
00613 m_bytesTillNextMeta = m_response->getMetaint();
00614 break;
00615 case STOPPED:
00616 m_socket->close();
00617 break;
00618 default:
00619 break;
00620 }
00621
00622 m_state = state;
00623 emit changedState(m_state);
00624 }
00625
00626 bool ShoutCastIODevice::parseHeader(void)
00627 {
00628 int available = min(4096, (int)m_buffer->readBufAvail());
00629 QByteArray data;
00630 m_buffer->read(data, available, false);
00631 int consumed = m_response->fillResponse(data.data(), data.size());
00632
00633 LOG(VB_NETWORK, LOG_INFO,
00634 QString("ShoutCastIODevice: Receiving header, %1 bytes").arg(consumed));
00635 {
00636 QString tmp;
00637 tmp = QString::fromAscii(data.data(), consumed);
00638 LOG(VB_NETWORK, LOG_INFO,
00639 QString("ShoutCastIODevice: Receiving header\n%1").arg(tmp));
00640 }
00641
00642 m_buffer->remove(0, consumed);
00643
00644 if (m_buffer->readBufAvail() >= 2)
00645 {
00646 data.clear();
00647 m_buffer->read(data, 2, false);
00648 if (data.size() == 2 && data[0] == '\r' && data[1] == '\n')
00649 {
00650 m_buffer->remove(0, 2);
00651 return true;
00652 }
00653 }
00654
00655 return false;
00656 }
00657
00658 bool ShoutCastIODevice::getResponse(ShoutCastResponse &response)
00659 {
00660 if (!m_response_gotten)
00661 return false;
00662
00663 response = *m_response;
00664 return true;
00665 }
00666
00667 bool ShoutCastIODevice::parseMeta(void)
00668 {
00669 QByteArray data;
00670 m_buffer->read(data, 1);
00671 unsigned char ch = data[0];
00672
00673 qint64 meta_size = 16 * ch;
00674 if (meta_size == 0)
00675 return true;
00676
00677
00678 if (meta_size > MAX_ALLOWED_META_SIZE)
00679 {
00680 LOG(VB_PLAYBACK, LOG_ERR,
00681 QString("ShoutCastIODevice: Error in stream, got a meta size of %1")
00682 .arg(meta_size));
00683 switchToState(STOPPED);
00684 return false;
00685 }
00686
00687 LOG(VB_NETWORK, LOG_INFO,
00688 QString("ShoutCastIODevice: Reading %1 bytes of meta").arg(meta_size));
00689
00690
00691 data.clear();
00692 m_buffer->read(data, meta_size);
00693
00694
00695 if (meta_size > data.size())
00696 {
00697 LOG(VB_PLAYBACK, LOG_ERR,
00698 QString("ShoutCastIODevice: Not enough data, we have %1, but the "
00699 "metadata size is %1")
00700 .arg(data.size()).arg(meta_size));
00701 switchToState(STOPPED);
00702 return false;
00703 }
00704
00705 QString metadata_string(data);
00706
00707
00708 if (m_last_metadata == metadata_string)
00709 return true;
00710
00711 m_last_metadata = metadata_string;
00712 emit meta(metadata_string);
00713
00714 return true;
00715 }
00716
00717
00718
00719 DecoderIOFactoryShoutCast::DecoderIOFactoryShoutCast(DecoderHandler *parent)
00720 : DecoderIOFactory(parent), m_timer(NULL), m_input(NULL)
00721 {
00722 m_timer = new QTimer(this);
00723 }
00724
00725 DecoderIOFactoryShoutCast::~DecoderIOFactoryShoutCast(void)
00726 {
00727 closeIODevice();
00728 }
00729
00730 QIODevice* DecoderIOFactoryShoutCast::takeInput(void)
00731 {
00732 QIODevice *result = m_input;
00733
00734 return result;
00735 }
00736
00737 void DecoderIOFactoryShoutCast::makeIODevice(void)
00738 {
00739 closeIODevice();
00740
00741 m_input = new ShoutCastIODevice();
00742
00743 qRegisterMetaType<ShoutCastIODevice::State>("ShoutCastIODevice::State");
00744 connect(m_input, SIGNAL(meta(const QString&)),
00745 this, SLOT(shoutcastMeta(const QString&)));
00746 connect(m_input, SIGNAL(changedState(ShoutCastIODevice::State)),
00747 this, SLOT(shoutcastChangedState(ShoutCastIODevice::State)));
00748 }
00749
00750 void DecoderIOFactoryShoutCast::closeIODevice(void)
00751 {
00752 if (m_input)
00753 {
00754 m_input->disconnect();
00755 if (m_input->isOpen())
00756 {
00757 m_input->close();
00758 }
00759 m_input->deleteLater();
00760 m_input = NULL;
00761 }
00762 }
00763
00764 void DecoderIOFactoryShoutCast::start(void)
00765 {
00766 LOG(VB_PLAYBACK, LOG_INFO,
00767 QString("DecoderIOFactoryShoutCast %1").arg(getUrl().toString()));
00768 doOperationStart("Connecting");
00769
00770 makeIODevice();
00771 m_input->connectToUrl(getUrl());
00772 }
00773
00774 void DecoderIOFactoryShoutCast::stop(void)
00775 {
00776 if (m_timer)
00777 m_timer->disconnect();
00778
00779 doOperationStop();
00780
00781 Metadata mdata(getMetadata());
00782 mdata.setTitle("Stopped");
00783 mdata.setArtist("");
00784 mdata.setLength(-1);
00785 DecoderHandlerEvent ev(DecoderHandlerEvent::Meta, mdata);
00786 dispatch(ev);
00787 }
00788
00789 void DecoderIOFactoryShoutCast::periodicallyCheckResponse(void)
00790 {
00791 int res = checkResponseOK();
00792
00793 if (res == 0)
00794 {
00795 ShoutCastResponse response;
00796 m_input->getResponse(response);
00797 m_prebuffer = PREBUFFER_SECS * response.getBitrate() * 125;
00798 LOG(VB_NETWORK, LOG_INFO,
00799 QString("kbps is %1, prebuffering %2 secs = %3 kb")
00800 .arg(response.getBitrate()).arg(PREBUFFER_SECS)
00801 .arg(m_prebuffer/1024));
00802 m_timer->stop();
00803 m_timer->disconnect();
00804 connect(m_timer, SIGNAL(timeout()), this, SLOT(periodicallyCheckBuffered()));
00805 m_timer->start(500);
00806 }
00807 else if (res < 0)
00808 {
00809 m_timer->stop();
00810 doFailed("Cannot parse this stream");
00811 }
00812 }
00813
00814 void DecoderIOFactoryShoutCast::periodicallyCheckBuffered(void)
00815 {
00816 LOG(VB_NETWORK, LOG_INFO,
00817 QString("DecoderIOFactoryShoutCast: prebuffered %1/%2KB")
00818 .arg(m_input->bytesAvailable()/1024).arg(m_prebuffer/1024));
00819
00820 if (m_input->bytesAvailable() < m_prebuffer || m_input->bytesAvailable() == 0)
00821 return;
00822
00823 ShoutCastResponse response;
00824 m_input->getResponse(response);
00825 LOG(VB_PLAYBACK, LOG_INFO,
00826 QString("contents '%1'").arg(response.getContent()));
00827 if (response.getContent() == "audio/mpeg")
00828 doConnectDecoder("create-mp3-decoder.mp3");
00829 else if (response.getContent() == "audio/aacp")
00830 doConnectDecoder("create-aac-decoder.m4a");
00831 else if (response.getContent() == "application/ogg")
00832 doConnectDecoder("create-ogg-decoder.ogg");
00833 else if (response.getContent() == "audio/aac")
00834 doConnectDecoder("create-aac-decoder.aac");
00835 else
00836 {
00837 doFailed(QObject::tr("Unsupported content type for ShoutCast stream: %1")
00838 .arg (response.getContent()));
00839 }
00840
00841 m_timer->disconnect();
00842 m_timer->stop();
00843 }
00844
00845 void DecoderIOFactoryShoutCast::shoutcastMeta(const QString &metadata)
00846 {
00847 LOG(VB_PLAYBACK, LOG_INFO,
00848 QString("DecoderIOFactoryShoutCast: metadata changed - %1")
00849 .arg(metadata));
00850 ShoutCastMetaParser parser;
00851
00852
00853 parser.setMetaFormat("%a - %t");
00854
00855 ShoutCastMetaMap meta_map = parser.parseMeta(metadata);
00856
00857 Metadata mdata = getMetadata();
00858 mdata.setTitle(meta_map["title"]);
00859 mdata.setArtist(meta_map["artist"]);
00860 mdata.setAlbum(getMetadata().Album());
00861 mdata.setLength(-1);
00862
00863 DecoderHandlerEvent ev(DecoderHandlerEvent::Meta, mdata);
00864 dispatch(ev);
00865 }
00866
00867 void DecoderIOFactoryShoutCast::shoutcastChangedState(ShoutCastIODevice::State state)
00868 {
00869 LOG(VB_PLAYBACK, LOG_INFO, QString("ShoutCast changed state to %1")
00870 .arg(ShoutCastIODevice::stateString(state)));
00871
00872 if (state == ShoutCastIODevice::RESOLVING)
00873 doOperationStart("Finding radio");
00874
00875 if (state == ShoutCastIODevice::CANT_RESOLVE)
00876 doFailed(QObject::tr("Cannot find radio.\nCheck the URL is correct."));
00877
00878 if (state == ShoutCastIODevice::CONNECTING)
00879 doOperationStart("Connecting to radio");
00880
00881 if (state == ShoutCastIODevice::CANT_CONNECT)
00882 doFailed(QObject::tr("Cannot connect to radio.\nCheck the URL is correct."));
00883
00884 if (state == ShoutCastIODevice::CONNECTED)
00885 {
00886 doOperationStart("Connected to radio");
00887 m_timer->stop();
00888 m_timer->disconnect();
00889 connect(m_timer, SIGNAL(timeout()),
00890 this, SLOT(periodicallyCheckResponse()));
00891 m_timer->start(300);
00892 }
00893
00894 if (state == ShoutCastIODevice::PLAYING)
00895 {
00896 doOperationStart("Buffering");
00897 }
00898
00899 if (state == ShoutCastIODevice::STOPPED)
00900 stop();
00901 }
00902
00903 int DecoderIOFactoryShoutCast::checkResponseOK()
00904 {
00905 ShoutCastResponse response;
00906
00907 if (!m_input->getResponse(response))
00908 return 1;
00909
00910 if (!response.isICY() && response.getStatus() == 302 &&
00911 !response.getLocation().isEmpty())
00912 {
00913
00914 setUrl(response.getLocation());
00915 start();
00916 return 1;
00917 }
00918
00919 if (response.getStatus() != 200)
00920 return -1;
00921
00922
00923
00924
00925 return 0;
00926 }