New playlist parsers to support network playlists.
authorLev Zelenskiy <lev.zelenskiy@nokia.com>
Thu, 8 Mar 2012 23:49:30 +0000 (09:49 +1000)
committerQt by Nokia <qt-info@nokia.com>
Wed, 21 Mar 2012 04:43:20 +0000 (05:43 +0100)
Added new playlist parsers for M3U and PLS.
Added QNetworkMediaPlaylistProvider.

Change-Id: I4c64018e44b8ae2401d6846a0c3c326d8c2ca5cc
Reviewed-by: Dmytro Poplavskiy <dmytro.poplavskiy@nokia.com>
src/multimedia/playback/playlistfileparser.cpp [new file with mode: 0644]
src/multimedia/playback/playlistfileparser_p.h [new file with mode: 0644]
src/multimedia/playback/qmedianetworkplaylistprovider.cpp [new file with mode: 0644]
src/multimedia/playback/qmedianetworkplaylistprovider_p.h [new file with mode: 0644]

diff --git a/src/multimedia/playback/playlistfileparser.cpp b/src/multimedia/playback/playlistfileparser.cpp
new file mode 100644 (file)
index 0000000..c7e3cea
--- /dev/null
@@ -0,0 +1,595 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "playlistfileparser_p.h"
+#include <qfileinfo.h>
+#include <QtNetwork/QNetworkReply>
+#include "qmediaobject_p.h"
+#include "qtmedianamespace.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+class ParserBase : public QObject
+{
+    Q_OBJECT
+public:
+    ParserBase(QObject *parent)
+        : QObject(parent)
+    {
+    }
+
+    virtual void parseLine(int lineIndex, const QString& line, const QUrl& root) = 0;
+
+Q_SIGNALS:
+    void newItem(const QVariant& content);
+    void finished();
+    void error(QPlaylistFileParser::ParserError err, const QString& errorMsg);
+};
+
+class M3UParser : public ParserBase
+{
+public:
+    M3UParser(QObject *parent)
+        : ParserBase(parent)
+        , m_extendedFormat(false)
+    {
+    }
+
+
+    /*
+     *
+    Extended M3U directives
+
+    #EXTM3U - header - must be first line of file
+    #EXTINF - extra info - length (seconds), title
+    #EXTINF - extra info - length (seconds), artist '-' title
+
+    Example
+
+    #EXTM3U
+    #EXTINF:123, Sample artist - Sample title
+    C:\Documents and Settings\I\My Music\Sample.mp3
+    #EXTINF:321,Example Artist - Example title
+    C:\Documents and Settings\I\My Music\Greatest Hits\Example.ogg
+
+     */
+    void parseLine(int lineIndex, const QString& line, const QUrl& root)
+    {
+        if (line[0] == '#' ) {
+            if (m_extendedFormat) {
+                if (line.startsWith(QLatin1String("#EXTINF:"))) {
+                    m_extraInfo.clear();
+                    int artistStart = line.indexOf(QLatin1String(","), 8);
+                    bool ok = false;
+                    int length = line.mid(8, artistStart < 8 ? -1 : artistStart - 8).trimmed().toInt(&ok);
+                    if (ok && length > 0) {
+                        //convert from second to milisecond
+                        m_extraInfo[QtMultimedia::MetaData::Duration] = QVariant(length * 1000);
+                    }
+                    if (artistStart > 0) {
+                        int titleStart = getSplitIndex(line, artistStart);
+                        if (titleStart > artistStart) {
+                            m_extraInfo[QtMultimedia::MetaData::Author] = line.mid(artistStart + 1,
+                                                             titleStart - artistStart - 1).trimmed().
+                                                             replace(QLatin1String("--"), QLatin1String("-"));
+                            m_extraInfo[QtMultimedia::MetaData::Title] = line.mid(titleStart + 1).trimmed().
+                                                   replace(QLatin1String("--"), QLatin1String("-"));
+                        } else {
+                            m_extraInfo[QtMultimedia::MetaData::Title] = line.mid(artistStart + 1).trimmed().
+                                                   replace(QLatin1String("--"), QLatin1String("-"));
+                        }
+                    }
+                }
+            } else if (lineIndex == 0 && line.startsWith(QLatin1String("#EXTM3U"))) {
+                m_extendedFormat = true;
+            }
+        } else {
+            m_extraInfo[QLatin1String("url")] = expandToFullPath(root, line);
+            emit newItem(QVariant(m_extraInfo));
+            m_extraInfo.clear();
+        }
+    }
+
+    int getSplitIndex(const QString& line, int startPos)
+    {
+        if (startPos < 0)
+            startPos = 0;
+        const QChar* buf = line.data();
+        for (int i = startPos; i < line.length(); ++i) {
+            if (buf[i] == '-') {
+                if (i == line.length() - 1)
+                    return i;
+                ++i;
+                if (buf[i] != '-')
+                    return i - 1;
+            }
+        }
+        return -1;
+    }
+
+    QUrl expandToFullPath(const QUrl& root, const QString& line)
+    {
+        QUrl url(line);
+        if (url.isRelative()) {
+            if (root.isLocalFile())
+                return root.resolved(QUrl::fromLocalFile(line));
+            else
+                return root.resolved(url);
+        }
+
+        return url;
+    }
+
+private:
+    bool            m_extendedFormat;
+    QVariantMap     m_extraInfo;
+};
+
+class PLSParser : public ParserBase
+{
+public:
+    PLSParser(QObject *parent)
+        : ParserBase(parent)
+        , m_state(Header)
+        , m_count(0)
+        , m_readFlags(0)
+    {
+    }
+
+    enum ReadFlags
+    {
+        FileRead = 0x1,
+        TitleRead = 0x2,
+        LengthRead = 0x4,
+        All = FileRead | TitleRead | LengthRead
+    };
+
+    enum State
+    {
+        Header,
+        Track,
+        Footer
+    };
+
+/*
+ *
+The format is essentially that of an INI file structured as follows:
+
+Header
+
+    * [playlist] : This tag indicates that it is a Playlist File
+
+Track Entry
+Assuming track entry #X
+
+    * FileX : Variable defining location of stream.
+    * TitleX : Defines track title.
+    * LengthX : Length in seconds of track. Value of -1 indicates indefinite.
+
+Footer
+
+    * NumberOfEntries : This variable indicates the number of tracks.
+    * Version : Playlist version. Currently only a value of 2 is valid.
+
+[playlist]
+
+File1=Alternative\everclear - SMFTA.mp3
+
+Title1=Everclear - So Much For The Afterglow
+
+Length1=233
+
+File2=http://www.site.com:8000/listen.pls
+
+Title2=My Cool Stream
+
+Length5=-1
+
+NumberOfEntries=2
+
+Version=2
+*/
+    inline bool containsFlag(const ReadFlags& flag)
+    {
+        return (m_readFlags & int(flag)) == flag;
+    }
+
+    inline void setFlag(const ReadFlags& flag)
+    {
+        m_readFlags |= int(flag);
+    }
+
+    void parseLine(int lineIndex, const QString& line, const QUrl&)
+    {
+        switch (m_state) {
+        case Header:
+            if (line == QLatin1String("[playlist]")) {
+                m_state = Track;
+                setCount(1);
+            }
+            break;
+        case Track:
+            if (!containsFlag(FileRead) && line.startsWith(m_fileName)) {
+                m_item[QLatin1String("url")] = getValue(lineIndex, line);
+                setFlag(FileRead);
+            } else if (!containsFlag(TitleRead) && line.startsWith(m_titleName)) {
+                m_item[QtMultimedia::MetaData::Title] = getValue(lineIndex, line);
+                setFlag(TitleRead);
+            } else if (!containsFlag(LengthRead) && line.startsWith(m_lengthName)) {
+                //convert from seconds to miliseconds
+                int length = getValue(lineIndex, line).toInt();
+                if (length > 0)
+                    m_item[QtMultimedia::MetaData::Duration] = length * 1000;
+                setFlag(LengthRead);
+            } else if (line.startsWith(QLatin1String("NumberOfEntries"))) {
+                m_state = Footer;
+                int entries = getValue(lineIndex, line).toInt();
+                int count = m_readFlags == 0 ? (m_count - 1) : m_count;
+                if (entries != count) {
+                    emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing pls: %1, expected count = %2")).
+                               arg(line, QString::number(count)));
+                }
+                break;
+            }
+            if (m_readFlags == int(All)) {
+                emit newItem(m_item);
+                setCount(m_count + 1);
+            }
+            break;
+        case Footer:
+            if (line.startsWith(QLatin1String("Version"))) {
+                int version = getValue(lineIndex, line).toInt();
+                if (version != 2)
+                    emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing pls at line[%1], expected version = 2")).arg(line));
+            }
+            break;
+        }
+    }
+
+    QString getValue(int lineIndex, const QString& line) {
+        int start = line.indexOf('=');
+        if (start < 0) {
+            emit error(QPlaylistFileParser::FormatError, QString(tr("Error parsing pls at line[%1]:%2")).arg(QString::number(lineIndex), line));
+            return QString();
+        }
+        return line.mid(start + 1).trimmed();
+    }
+
+    void setCount(int count) {
+        m_count = count;
+        m_fileName = QString(tr("File%1")).arg(count);
+        m_titleName = QString(tr("Title%1")).arg(count);
+        m_lengthName = QString(tr("Length%1")).arg(count);
+        m_item.clear();
+        m_readFlags = 0;
+    }
+
+private:
+    State m_state;
+    int  m_count;
+    QString m_titleName;
+    QString m_fileName;
+    QString m_lengthName;
+    QVariantMap m_item;
+    int m_readFlags;
+};
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+class QPlaylistFileParserPrivate : public QObject
+{
+    Q_OBJECT
+    Q_DECLARE_NON_CONST_PUBLIC(QPlaylistFileParser)
+public:
+    QPlaylistFileParserPrivate()
+        : m_source(0)
+        , m_scanIndex(0)
+        , m_utf8(false)
+        , m_lineIndex(-1)
+        , m_type(QPlaylistFileParser::UNKNOWN)
+        , m_currentParser(0)
+    {
+    }
+
+    void _q_handleData();
+    void _q_handleError();
+    void _q_handleParserError(QPlaylistFileParser::ParserError err, const QString& errorMsg);
+    void _q_handleParserFinished();
+
+    QNetworkReply  *m_source;
+    QByteArray      m_buffer;
+    int             m_scanIndex;
+    QUrl            m_root;
+    bool            m_utf8;
+    int             m_lineIndex;
+    QPlaylistFileParser::FileType m_type;
+    ParserBase     *m_currentParser;
+    QNetworkAccessManager m_mgr;
+
+    QPlaylistFileParser *q_ptr;
+
+private:
+    void processLine(int startIndex, int length);
+};
+
+#define LINE_LIMIT  4096
+#define READ_LIMIT  64
+
+void QPlaylistFileParserPrivate::processLine(int startIndex, int length)
+{
+    Q_Q(QPlaylistFileParser);
+    m_lineIndex++;
+
+    if (!m_currentParser) {
+        Q_ASSERT(!m_currentParser);
+
+        QString mimeType = m_source->header(QNetworkRequest::ContentTypeHeader).toString();
+        m_type = QPlaylistFileParser::findPlaylistType(m_root.toString(), mimeType, m_buffer.constData(), m_buffer.size());
+
+        switch (m_type) {
+        case QPlaylistFileParser::UNKNOWN:
+            emit q->error(QPlaylistFileParser::FormatError, QString(tr("%1 playlist type is unknown")).arg(m_root.toString()));
+            q->stop();
+            return;
+        case QPlaylistFileParser::M3U:
+            m_currentParser = new M3UParser(this);
+            break;
+        case QPlaylistFileParser::M3U8:
+            m_currentParser = new M3UParser(this);
+            m_utf8 = true;
+            break;
+        case QPlaylistFileParser::PLS:
+            m_currentParser = new PLSParser(this);
+            break;
+        }
+        Q_ASSERT(m_currentParser);
+        connect(m_currentParser, SIGNAL(newItem(QVariant)), q, SIGNAL(newItem(QVariant)));
+        connect(m_currentParser, SIGNAL(finished()), q, SLOT(_q_handleParserFinished()));
+        connect(m_currentParser, SIGNAL(error(QPlaylistFileParser::ParserError, QString)),
+                q, SLOT(_q_handleParserError(QPlaylistFileParser::ParserError, QString)));
+    }
+
+    QString line;
+
+    if (m_utf8) {
+        line = QString::fromUtf8(m_buffer.constData() + startIndex, length).trimmed();
+    } else {
+        line = QString::fromLatin1(m_buffer.constData() + startIndex, length).trimmed();
+    }
+    if (line.isEmpty())
+        return;
+
+    Q_ASSERT(m_currentParser);
+    m_currentParser->parseLine(m_lineIndex, line, m_root);
+}
+
+void QPlaylistFileParserPrivate::_q_handleData()
+{
+    Q_Q(QPlaylistFileParser);
+    while (m_source->bytesAvailable()) {
+        int expectedBytes = qMin(READ_LIMIT, int(qMin(m_source->bytesAvailable(),
+                                                      qint64(LINE_LIMIT - m_buffer.size()))));
+        m_buffer.push_back(m_source->read(expectedBytes));
+        int processedBytes = 0;
+        while (m_scanIndex < m_buffer.length()) {
+            char s = m_buffer[m_scanIndex];
+            if (s == '\r' || s == '\n') {
+                int l = m_scanIndex - processedBytes;
+                if (l > 0)
+                    processLine(processedBytes, l);
+                processedBytes = m_scanIndex + 1;
+                if (!m_source) {
+                    //some error happened, so exit parsing
+                    return;
+                }
+            }
+            m_scanIndex++;
+        }
+
+        if (m_buffer.length() - processedBytes >= LINE_LIMIT) {
+            qWarning() << "error parsing playlist["<< m_root << "] with line content >= 4096 bytes.";
+            emit q->error(QPlaylistFileParser::FormatError, tr("invalid line in playlist file"));
+            q->stop();
+            return;
+        }
+
+        if (m_source->isFinished() && !m_source->bytesAvailable()) {
+            //last line
+            processLine(processedBytes, -1);
+            break;
+        }
+
+        Q_ASSERT(m_buffer.length() == m_scanIndex);
+        if (processedBytes == 0)
+            continue;
+
+        int copyLength = m_buffer.length() - processedBytes;
+        if (copyLength > 0) {
+            Q_ASSERT(copyLength <= READ_LIMIT);
+            m_buffer = m_buffer.right(copyLength);
+        } else {
+            m_buffer.clear();
+        }
+        m_scanIndex = 0;
+    }
+
+    if (m_source->isFinished()) {
+        _q_handleParserFinished();
+    }
+}
+
+void QPlaylistFileParserPrivate::_q_handleError()
+{
+    Q_Q(QPlaylistFileParser);
+    emit q->error(QPlaylistFileParser::NetworkError, m_source->errorString());
+    q->stop();
+}
+
+void QPlaylistFileParserPrivate::_q_handleParserError(QPlaylistFileParser::ParserError err, const QString& errorMsg)
+{
+    Q_Q(QPlaylistFileParser);
+    emit q->error(err, errorMsg);
+}
+
+void QPlaylistFileParserPrivate::_q_handleParserFinished()
+{
+    Q_Q(QPlaylistFileParser);
+    bool isParserValid = (m_currentParser != 0);
+    if (!isParserValid)
+        emit q->error(QPlaylistFileParser::FormatNotSupportedError, tr("Empty file provided"));
+
+    q->stop();
+
+    if (isParserValid)
+        emit q->finished();
+}
+
+
+QPlaylistFileParser::QPlaylistFileParser(QObject *parent)
+    :QObject(parent), d_ptr(new QPlaylistFileParserPrivate)
+{
+    d_func()->q_ptr = this;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findPlaylistType(const QString& uri, const QString& mime, const void *data, quint32 size)
+{
+    if (!data || !size)
+        return UNKNOWN;
+
+    FileType uriType = UNKNOWN;
+    QString suffix = QFileInfo(uri).suffix().toLower();
+    if (suffix == QLatin1String("m3u"))
+        uriType = M3U;
+    else if (suffix == QLatin1String("m3u8"))
+        uriType = M3U8;
+    else if (suffix == QLatin1String("pls"))
+        uriType = PLS;
+
+    FileType mimeType = UNKNOWN;
+    if (mime == QLatin1String("text/uri-list") || mime == QLatin1String("audio/x-mpegurl") || mime == QLatin1String("audio/mpegurl"))
+        mimeType = QPlaylistFileParser::M3U;
+    else if (mime == QLatin1String("application/x-mpegURL") || mime == QLatin1String("application/vnd.apple.mpegurl"))
+        mimeType = QPlaylistFileParser::M3U8;
+    else if (mime == QLatin1String("audio/x-scpls"))
+        mimeType = QPlaylistFileParser::PLS;
+
+    FileType bufferType = UNKNOWN;
+    if (size >= 7 && strncmp((const char*)data, "#EXTM3U", 7) == 0)
+        bufferType = M3U;
+    else if (size >= 10 && strncmp((const char*)data, "[playlist]", 10) == 0)
+        bufferType = PLS;
+    else {
+        // Make sure every line is a text string
+        quint32 n;
+        for (n = 0; n < size; n++)
+            if (!QChar(QLatin1Char(((const char*)data)[n])).isPrint())
+                break;
+        if (n == size)
+            bufferType = M3U;
+    }
+
+    if (bufferType == M3U && (uriType == M3U8 || mimeType == M3U8))
+        bufferType = M3U8;
+
+    if (bufferType != UNKNOWN)
+        return bufferType;
+
+    if (uriType != UNKNOWN)
+        return uriType;
+
+    if (mimeType != UNKNOWN)
+        return mimeType;
+
+    return UNKNOWN;
+}
+
+void QPlaylistFileParser::start(const QUrl& url, bool utf8)
+{
+    Q_D(QPlaylistFileParser);
+    stop();
+
+    d->m_type = UNKNOWN;
+    d->m_utf8 = utf8;
+    d->m_root = url;
+
+    if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) {
+        emit error(NetworkError, QString(tr("%1 does not exist")).arg(url.toString()));
+        return;
+    }
+
+    d->m_source = d->m_mgr.get(QNetworkRequest(url));
+
+    connect(d->m_source, SIGNAL(readyRead()), this, SLOT(_q_handleData()));
+    connect(d->m_source, SIGNAL(finished()), this, SLOT(_q_handleData()));
+    connect(d->m_source, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(_q_handleError()));
+
+    d->_q_handleData();
+}
+
+void QPlaylistFileParser::stop()
+{
+    Q_D(QPlaylistFileParser);
+    if (d->m_currentParser) {
+        disconnect(d->m_currentParser, SIGNAL(newItem(QVariant)), this, SIGNAL(newItem(QVariant)));
+        disconnect(d->m_currentParser, SIGNAL(finished()), this, SLOT(_q_handleParserFinished()));
+        disconnect(d->m_currentParser, SIGNAL(error(QPlaylistFileParser::ParserError, QString)),
+                   this, SLOT(_q_handleParserError(QPlaylistFileParser::ParserError, QString)));
+        d->m_currentParser->deleteLater();
+        d->m_currentParser = 0;
+    }
+
+    d->m_buffer.clear();
+    d->m_scanIndex = 0;
+    d->m_lineIndex = -1;
+    if (d->m_source) {
+        disconnect(d->m_source, SIGNAL(readyRead()), this, SLOT(_q_handleData()));
+        disconnect(d->m_source, SIGNAL(finished()), this, SLOT(_q_handleData()));
+        disconnect(d->m_source, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(_q_handleError()));
+        d->m_source->deleteLater();
+        d->m_source = 0;
+    }
+}
+
+#include "moc_playlistfileparser_p.cpp"
+#include "playlistfileparser.moc"
+QT_END_NAMESPACE
diff --git a/src/multimedia/playback/playlistfileparser_p.h b/src/multimedia/playback/playlistfileparser_p.h
new file mode 100644 (file)
index 0000000..4a609ee
--- /dev/null
@@ -0,0 +1,112 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef PLAYLISTFILEPARSER_P_H
+#define PLAYLISTFILEPARSER_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtNetwork>
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+class QPlaylistFileParserPrivate;
+
+class Q_MULTIMEDIA_EXPORT QPlaylistFileParser : public QObject
+{
+    Q_OBJECT
+public:
+    QPlaylistFileParser(QObject *parent = 0);
+
+    enum FileType
+    {
+        UNKNOWN,
+        M3U,
+        M3U8, // UTF-8 version of M3U
+        PLS
+    };
+
+    enum ParserError
+    {
+        NoError,
+        FormatError,
+        FormatNotSupportedError,
+        NetworkError
+    };
+
+    static FileType findPlaylistType(const QString& uri, const QString& mime, const void *data, quint32 size);
+
+    void start(const QUrl& url, bool utf8 = false);
+    void stop();
+
+Q_SIGNALS:
+    void newItem(const QVariant& content);
+    void finished();
+    void error(QPlaylistFileParser::ParserError err, const QString& errorMsg);
+
+protected:
+    QPlaylistFileParserPrivate *d_ptr;
+
+private:
+    Q_DISABLE_COPY(QPlaylistFileParser)
+    Q_DECLARE_PRIVATE(QPlaylistFileParser)
+    Q_PRIVATE_SLOT(d_func(), void _q_handleData())
+    Q_PRIVATE_SLOT(d_func(), void _q_handleError())
+    Q_PRIVATE_SLOT(d_func(), void _q_handleParserError(QPlaylistFileParser::ParserError err, const QString& errorMsg))
+    Q_PRIVATE_SLOT(d_func(), void _q_handleParserFinished())
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+#endif // PLAYLISTFILEPARSER_P_H
diff --git a/src/multimedia/playback/qmedianetworkplaylistprovider.cpp b/src/multimedia/playback/qmedianetworkplaylistprovider.cpp
new file mode 100644 (file)
index 0000000..f7cc342
--- /dev/null
@@ -0,0 +1,265 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmedianetworkplaylistprovider_p.h"
+#include "qmediaplaylistprovider_p.h"
+#include "qmediacontent.h"
+#include "qmediaobject_p.h"
+#include "playlistfileparser_p.h"
+
+QT_BEGIN_NAMESPACE
+
+class QMediaNetworkPlaylistProviderPrivate: public QMediaPlaylistProviderPrivate
+{
+    Q_DECLARE_NON_CONST_PUBLIC(QMediaNetworkPlaylistProvider)
+public:
+    bool load(const QUrl &location);
+
+    QPlaylistFileParser parser;
+    QList<QMediaContent> resources;
+
+    void _q_handleParserError(QPlaylistFileParser::ParserError err, const QString &);
+    void _q_handleNewItem(const QVariant& content);
+
+    QMediaNetworkPlaylistProvider *q_ptr;
+};
+
+bool QMediaNetworkPlaylistProviderPrivate::load(const QUrl &location)
+{
+    parser.stop();
+    parser.start(location, false);
+
+    return true;
+}
+
+void QMediaNetworkPlaylistProviderPrivate::_q_handleParserError(QPlaylistFileParser::ParserError err, const QString &errorMessage)
+{
+    Q_Q(QMediaNetworkPlaylistProvider);
+
+    QMediaPlaylist::Error playlistError = QMediaPlaylist::NoError;
+
+    switch ((QPlaylistFileParser::ParserError)err) {
+    case QPlaylistFileParser::NoError:
+        return;
+    case QPlaylistFileParser::FormatError:
+        playlistError = QMediaPlaylist::FormatError;
+        break;
+    case QPlaylistFileParser::FormatNotSupportedError:
+        playlistError = QMediaPlaylist::FormatNotSupportedError;
+        break;
+    case QPlaylistFileParser::NetworkError:
+        playlistError = QMediaPlaylist::NetworkError;
+        break;
+    }
+
+    emit q->loadFailed(playlistError, errorMessage);
+}
+
+void QMediaNetworkPlaylistProviderPrivate::_q_handleNewItem(const QVariant& content)
+{
+    Q_Q(QMediaNetworkPlaylistProvider);
+
+    QUrl url;
+    if (content.type() == QVariant::Url) {
+        url = content.toUrl();
+    } else if (content.type() == QVariant::Map) {
+        url = content.toMap()[QLatin1String("url")].toUrl();
+    } else {
+        return;
+    }
+
+    q->addMedia(QMediaContent(url));
+}
+
+QMediaNetworkPlaylistProvider::QMediaNetworkPlaylistProvider(QObject *parent)
+    :QMediaPlaylistProvider(*new QMediaNetworkPlaylistProviderPrivate, parent)
+{
+    d_func()->q_ptr = this;
+    connect(&d_func()->parser, SIGNAL(newItem(const QVariant&)),
+            this, SLOT(_q_handleNewItem(const QVariant&)));
+    connect(&d_func()->parser, SIGNAL(finished()), this, SIGNAL(loaded()));
+    connect(&d_func()->parser, SIGNAL(error(QPlaylistFileParser::ParserError, const QString &)),
+            this, SLOT(_q_handleParserError(QPlaylistFileParser::ParserError, const QString &)));
+}
+
+QMediaNetworkPlaylistProvider::~QMediaNetworkPlaylistProvider()
+{
+}
+
+bool QMediaNetworkPlaylistProvider::isReadOnly() const
+{
+    return false;
+}
+
+bool QMediaNetworkPlaylistProvider::load(const QUrl &location, const char *format)
+{
+    Q_UNUSED(format);
+    return d_func()->load(location);
+}
+
+int QMediaNetworkPlaylistProvider::mediaCount() const
+{
+    return d_func()->resources.size();
+}
+
+QMediaContent QMediaNetworkPlaylistProvider::media(int pos) const
+{
+    return d_func()->resources.value(pos);
+}
+
+bool QMediaNetworkPlaylistProvider::addMedia(const QMediaContent &content)
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+
+    int pos = d->resources.count();
+
+    emit mediaAboutToBeInserted(pos, pos);
+    d->resources.append(content);
+    emit mediaInserted(pos, pos);
+
+    return true;
+}
+
+bool QMediaNetworkPlaylistProvider::addMedia(const QList<QMediaContent> &items)
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+
+    if (items.isEmpty())
+        return true;
+
+    int pos = d->resources.count();
+    int end = pos+items.count()-1;
+
+    emit mediaAboutToBeInserted(pos, end);
+    d->resources.append(items);
+    emit mediaInserted(pos, end);
+
+    return true;
+}
+
+
+bool QMediaNetworkPlaylistProvider::insertMedia(int pos, const QMediaContent &content)
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+
+    emit mediaAboutToBeInserted(pos, pos);
+    d->resources.insert(pos, content);
+    emit mediaInserted(pos,pos);
+
+    return true;
+}
+
+bool QMediaNetworkPlaylistProvider::insertMedia(int pos, const QList<QMediaContent> &items)
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+
+    if (items.isEmpty())
+        return true;
+
+    const int last = pos+items.count()-1;
+
+    emit mediaAboutToBeInserted(pos, last);
+    for (int i=0; i<items.count(); i++)
+        d->resources.insert(pos+i, items.at(i));
+    emit mediaInserted(pos, last);
+
+    return true;
+}
+
+bool QMediaNetworkPlaylistProvider::removeMedia(int fromPos, int toPos)
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+
+    Q_ASSERT(fromPos >= 0);
+    Q_ASSERT(fromPos <= toPos);
+    Q_ASSERT(toPos < mediaCount());
+
+    emit mediaAboutToBeRemoved(fromPos, toPos);
+    d->resources.erase(d->resources.begin()+fromPos, d->resources.begin()+toPos+1);
+    emit mediaRemoved(fromPos, toPos);
+
+    return true;
+}
+
+bool QMediaNetworkPlaylistProvider::removeMedia(int pos)
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+
+    emit mediaAboutToBeRemoved(pos, pos);
+    d->resources.removeAt(pos);
+    emit mediaRemoved(pos, pos);
+
+    return true;
+}
+
+bool QMediaNetworkPlaylistProvider::clear()
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+    if (!d->resources.isEmpty()) {
+        int lastPos = mediaCount()-1;
+        emit mediaAboutToBeRemoved(0, lastPos);
+        d->resources.clear();
+        emit mediaRemoved(0, lastPos);
+    }
+
+    return true;
+}
+
+void QMediaNetworkPlaylistProvider::shuffle()
+{
+    Q_D(QMediaNetworkPlaylistProvider);
+    if (!d->resources.isEmpty()) {
+        QList<QMediaContent> resources;
+
+        while (!d->resources.isEmpty()) {
+            resources.append(d->resources.takeAt(qrand() % d->resources.size()));
+        }
+
+        d->resources = resources;
+        emit mediaChanged(0, mediaCount()-1);
+    }
+
+}
+
+#include "moc_qmedianetworkplaylistprovider_p.cpp"
+
+QT_END_NAMESPACE
+
diff --git a/src/multimedia/playback/qmedianetworkplaylistprovider_p.h b/src/multimedia/playback/qmedianetworkplaylistprovider_p.h
new file mode 100644 (file)
index 0000000..a90201d
--- /dev/null
@@ -0,0 +1,103 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** GNU Lesser General Public License Usage
+** This file may be used under the terms of the GNU Lesser General Public
+** License version 2.1 as published by the Free Software Foundation and
+** appearing in the file LICENSE.LGPL included in the packaging of this
+** file. Please review the following information to ensure the GNU Lesser
+** General Public License version 2.1 requirements will be met:
+** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Nokia gives you certain additional
+** rights. These rights are described in the Nokia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU General
+** Public License version 3.0 as published by the Free Software Foundation
+** and appearing in the file LICENSE.GPL included in the packaging of this
+** file. Please review the following information to ensure the GNU General
+** Public License version 3.0 requirements will be met:
+** http://www.gnu.org/copyleft/gpl.html.
+**
+** Other Usage
+** Alternatively, this file may be used in accordance with the terms and
+** conditions contained in a signed written agreement between you and Nokia.
+**
+**
+**
+**
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMEDIANETWORKPAYLISTPROVIDER_P_H
+#define QMEDIANETWORKPAYLISTPROVIDER_P_H
+
+//
+//  W A R N I N G
+//  -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include "qmediaplaylistprovider_p.h"
+
+QT_BEGIN_HEADER
+
+QT_BEGIN_NAMESPACE
+
+QT_MODULE(Multimedia)
+
+
+class QMediaNetworkPlaylistProviderPrivate;
+class Q_MULTIMEDIA_EXPORT QMediaNetworkPlaylistProvider : public QMediaPlaylistProvider
+{
+    Q_OBJECT
+public:
+    QMediaNetworkPlaylistProvider(QObject *parent=0);
+    virtual ~QMediaNetworkPlaylistProvider();
+
+    virtual bool load(const QUrl &location, const char *format = 0);
+
+    virtual int mediaCount() const;
+    virtual QMediaContent media(int pos) const;
+
+    virtual bool isReadOnly() const;
+
+    virtual bool addMedia(const QMediaContent &content);
+    virtual bool addMedia(const QList<QMediaContent> &items);
+    virtual bool insertMedia(int pos, const QMediaContent &content);
+    virtual bool insertMedia(int pos, const QList<QMediaContent> &items);
+    virtual bool removeMedia(int pos);
+    virtual bool removeMedia(int start, int end);
+    virtual bool clear();
+
+public Q_SLOTS:
+    virtual void shuffle();
+
+private:
+    Q_DISABLE_COPY(QMediaNetworkPlaylistProvider)
+    Q_DECLARE_PRIVATE(QMediaNetworkPlaylistProvider)
+    Q_PRIVATE_SLOT(d_func(), void _q_handleParserError(QPlaylistFileParser::ParserError err, const QString &))
+    Q_PRIVATE_SLOT(d_func(), void _q_handleNewItem(const QVariant& content))
+};
+
+QT_END_NAMESPACE
+
+QT_END_HEADER
+
+
+#endif // QMEDIANETWORKPAYLISTSOURCE_P_H