diff --git a/ChangeLog b/ChangeLog index dd2967eaa..46b9cc49f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -107,8 +107,9 @@ will list each track as a separate entry in the artists and albums views. 68. When loading a stream into the playqueue, show a status message at the bottom - allowing the loading to be cancelled. -69. If stream is not a cantata stream, and the total time is known, then enable +69. If stream is not a audiocd stream, and the total time is known, then enable the position slider. +70. Allow seeking in cantata HTTP streams. 1.0.3 ----- diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index b11466a38..6400469df 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -1820,7 +1820,7 @@ void MainWindow::updateCurrentSong(const Song &song) if (current.time<5 && MPDStatus::self()->songId()==current.id && MPDStatus::self()->timeTotal()>5) { current.time=MPDStatus::self()->timeTotal(); } - positionSlider->setEnabled(-1!=current.id && (!currentIsStream() || (!current.isCantataStream() && !current.isCdda() && current.time>5))); + positionSlider->setEnabled(-1!=current.id && (!currentIsStream() || (!current.isCdda() && current.time>5))); coverWidget->update(current); if (current.isStream() && !current.isCantataStream() && !current.isCdda()) { diff --git a/http/httpsocket.cpp b/http/httpsocket.cpp index 1248f92cd..a4b122e4c 100644 --- a/http/httpsocket.cpp +++ b/http/httpsocket.cpp @@ -128,12 +128,19 @@ static QString detectMimeType(const QString &file) return QString(); } -static void writeMimeType(const QString &mimeType, QTcpSocket *socket) +static void writeMimeType(const QString &mimeType, QTcpSocket *socket, qint64 size=0) { if (!mimeType.isEmpty()) { QTextStream os(socket); os.setAutoDetectUnicode(true); - os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n"; + if (size>0) { + os << "HTTP/1.0 200 OK" + << "\r\nAccept-Ranges: bytes" + << "\r\nContent-Length: " << QString::number(size) + << "\r\nContent-Type: " << mimeType << "\r\n\r\n"; + } else { + os << "HTTP/1.0 200 OK\r\nContent-Type: " << mimeType << "\r\n\r\n"; + } } } @@ -149,6 +156,67 @@ static QHostAddress getAddress(const QNetworkInterface &iface) return QHostAddress(); } +static int getSep(const QByteArray &a, int pos) +{ + for (int i=pos+1; i split(const QByteArray &a) +{ + QList rv; + int lastPos=-1; + for (;;) { + int pos=getSep(a, lastPos); + + if (pos==(lastPos+1)) { + lastPos++; + } else if (pos>-1) { + lastPos++; + rv.append(a.mid(lastPos, pos-lastPos)); + lastPos=pos; + } else { + lastPos++; + rv.append(a.mid(lastPos)); + break; + } + } + return rv; +} + +static bool isFromMpd(const QStringList ¶ms) +{ + foreach (const QString &str, params) { + if (str.startsWith("User-Agent:") && str.contains("Music Player Daemon")) { + return true; + } + } + return false; +} + +static void getRange(const QStringList ¶ms, qint64 &from, qint64 &to) +{ + foreach (const QString &str, params) { + if (str.startsWith("Range:")) { + int start=str.indexOf("bytes="); + if (start>0) { + QStringList range=str.mid(start+6).split("-", QString::SkipEmptyParts); + if (1==range.length()) { + from=range.at(0).toLongLong(); + } else if (2==range.length()) { + from=range.at(0).toLongLong(); + to=range.at(1).toLongLong(); + } + } + break; + } + } +} + HttpSocket::HttpSocket(const QString &iface, quint16 port) : QTcpServer(0) , cfgInterface(iface) @@ -235,38 +303,6 @@ void HttpSocket::incomingConnection(int socket) s->setSocketDescriptor(socket); } -int getSep(const QByteArray &a, int pos) -{ - for (int i=pos+1; i split(const QByteArray &a) -{ - QList rv; - int lastPos=-1; - for (;;) { - int pos=getSep(a, lastPos); - - if (pos==(lastPos+1)) { - lastPos++; - } else if (pos>-1) { - lastPos++; - rv.append(a.mid(lastPos, pos-lastPos)); - lastPos=pos; - } else { - lastPos++; - rv.append(a.mid(lastPos)); - break; - } - } - return rv; -} - void HttpSocket::readClient() { if (terminated) { @@ -277,6 +313,12 @@ void HttpSocket::readClient() if (socket->canReadLine()) { QList tokens = split(socket->readLine()); // QRegExp("[ \r\n][ \r\n]*")); if (tokens.length()>=2 && "GET"==tokens[0]) { + QStringList params = QString(socket->readAll()).split(QRegExp("[\r\n][\r\n]*")); + + if (!isFromMpd(params)) { + return; + } + #if QT_VERSION < 0x050000 QUrl url(QUrl::fromEncoded(tokens[1])); QUrl &q=url; @@ -365,37 +407,60 @@ void HttpSocket::readClient() QFile f(url.path()); if (f.open(QIODevice::ReadOnly)) { - writeMimeType(detectMimeType(url.path()), socket); - ok=true; - static const int constChunkSize=8192; - char buffer[constChunkSize]; + qint64 from=0; + qint64 to=0; qint64 totalBytes = f.size(); + + getRange(params, from, to); + writeMimeType(detectMimeType(url.path()), socket, totalBytes); + ok=true; + static const int constChunkSize=32768; + char buffer[constChunkSize]; qint64 readPos = 0; qint64 bytesRead = 0; bool stop=false; - do { - bytesRead = f.read(buffer, constChunkSize); - readPos+=bytesRead; - if (bytesRead<0 || terminated) { - break; + if (0!=from) { + if (!f.seek(from)) { + ok=false; } + bytesRead+=from; + } - qint64 writePos=0; + if (0!=to && to>from && to!=totalBytes) { + totalBytes-=(totalBytes-to); + } + + if (ok) { do { - qint64 bytesWritten = socket->write(&buffer[writePos], bytesRead - writePos); - if (terminated || -1==bytesWritten) { - stop=true; + bytesRead = f.read(buffer, constChunkSize); + readPos+=bytesRead; + if (bytesRead<0 || terminated) { break; } - socket->flush(); - writePos+=bytesWritten; - } while (writePoswrite(&buffer[writePos], bytesRead - writePos); + if (terminated || -1==bytesWritten) { + stop=true; + break; + } + socket->flush(); + writePos+=bytesWritten; + } while (writePosstate()) { + socket->waitForBytesWritten(); + } + if (QAbstractSocket::ConnectedState!=socket->state()) { + break; + } + } while ((readPos+bytesRead)