From 2ebea05283a2ddad831d5039a49817cefb7c7625 Mon Sep 17 00:00:00 2001 From: Lev Zelenskiy Date: Fri, 9 Mar 2012 09:49:30 +1000 Subject: New playlist parsers to support network playlists. Added new playlist parsers for M3U and PLS. Added QNetworkMediaPlaylistProvider. Change-Id: I4c64018e44b8ae2401d6846a0c3c326d8c2ca5cc Reviewed-by: Dmytro Poplavskiy --- src/multimedia/playback/playlistfileparser.cpp | 595 +++++++++++++++++++++ src/multimedia/playback/playlistfileparser_p.h | 112 ++++ .../playback/qmedianetworkplaylistprovider.cpp | 265 +++++++++ .../playback/qmedianetworkplaylistprovider_p.h | 103 ++++ 4 files changed, 1075 insertions(+) create mode 100644 src/multimedia/playback/playlistfileparser.cpp create mode 100644 src/multimedia/playback/playlistfileparser_p.h create mode 100644 src/multimedia/playback/qmedianetworkplaylistprovider.cpp create mode 100644 src/multimedia/playback/qmedianetworkplaylistprovider_p.h diff --git a/src/multimedia/playback/playlistfileparser.cpp b/src/multimedia/playback/playlistfileparser.cpp new file mode 100644 index 000000000..c7e3cea17 --- /dev/null +++ b/src/multimedia/playback/playlistfileparser.cpp @@ -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 +#include +#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 index 000000000..4a609ee25 --- /dev/null +++ b/src/multimedia/playback/playlistfileparser_p.h @@ -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 + +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 index 000000000..f7cc342fe --- /dev/null +++ b/src/multimedia/playback/qmedianetworkplaylistprovider.cpp @@ -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 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 &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 &items) +{ + Q_D(QMediaNetworkPlaylistProvider); + + if (items.isEmpty()) + return true; + + const int last = pos+items.count()-1; + + emit mediaAboutToBeInserted(pos, last); + for (int i=0; iresources.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 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 index 000000000..a90201d8d --- /dev/null +++ b/src/multimedia/playback/qmedianetworkplaylistprovider_p.h @@ -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 &items); + virtual bool insertMedia(int pos, const QMediaContent &content); + virtual bool insertMedia(int pos, const QList &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 -- cgit v1.2.3