summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/multimediawidgets/player/CMakeLists.txt2
-rw-r--r--examples/multimediawidgets/player/player.cpp2
-rw-r--r--examples/multimediawidgets/player/player.h2
-rw-r--r--examples/multimediawidgets/player/playlistmodel.cpp2
-rw-r--r--examples/multimediawidgets/player/qmediaplaylist.cpp689
-rw-r--r--examples/multimediawidgets/player/qmediaplaylist.h132
-rw-r--r--examples/multimediawidgets/player/qmediaplaylist_p.h148
-rw-r--r--examples/multimediawidgets/player/qplaylistfileparser.cpp641
-rw-r--r--examples/multimediawidgets/player/qplaylistfileparser_p.h116
9 files changed, 1731 insertions, 3 deletions
diff --git a/examples/multimediawidgets/player/CMakeLists.txt b/examples/multimediawidgets/player/CMakeLists.txt
index eb2b5a591..48be3d0c6 100644
--- a/examples/multimediawidgets/player/CMakeLists.txt
+++ b/examples/multimediawidgets/player/CMakeLists.txt
@@ -29,6 +29,8 @@ qt_add_executable(player
playercontrols.cpp playercontrols.h
playlistmodel.cpp playlistmodel.h
videowidget.cpp videowidget.h
+ qmediaplaylist.cpp qmediaplaylist.h qmediaplaylist_p.h
+ qplaylistfileparser.cpp qplaylistfileparser_p.h
)
set_target_properties(player PROPERTIES
WIN32_EXECUTABLE TRUE
diff --git a/examples/multimediawidgets/player/player.cpp b/examples/multimediawidgets/player/player.cpp
index 03420f3f1..7f374bae6 100644
--- a/examples/multimediawidgets/player/player.cpp
+++ b/examples/multimediawidgets/player/player.cpp
@@ -55,7 +55,7 @@
#include "histogramwidget.h"
#include "videowidget.h"
-#include <QMediaPlaylist>
+#include <qmediaplaylist.h>
#include <QMediaMetaData>
#include <QMediaDevices>
#include <QAudioDevice>
diff --git a/examples/multimediawidgets/player/player.h b/examples/multimediawidgets/player/player.h
index 1d723a343..5dc95cd58 100644
--- a/examples/multimediawidgets/player/player.h
+++ b/examples/multimediawidgets/player/player.h
@@ -53,7 +53,7 @@
#include <QWidget>
#include <QMediaPlayer>
-#include <QMediaPlaylist>
+#include <qmediaplaylist.h>
#include <QMediaMetaData>
QT_BEGIN_NAMESPACE
diff --git a/examples/multimediawidgets/player/playlistmodel.cpp b/examples/multimediawidgets/player/playlistmodel.cpp
index 676781b60..f6e683410 100644
--- a/examples/multimediawidgets/player/playlistmodel.cpp
+++ b/examples/multimediawidgets/player/playlistmodel.cpp
@@ -49,10 +49,10 @@
****************************************************************************/
#include "playlistmodel.h"
+#include <qmediaplaylist.h>
#include <QFileInfo>
#include <QUrl>
-#include <QMediaPlaylist>
PlaylistModel::PlaylistModel(QObject *parent)
: QAbstractItemModel(parent)
diff --git a/examples/multimediawidgets/player/qmediaplaylist.cpp b/examples/multimediawidgets/player/qmediaplaylist.cpp
new file mode 100644
index 000000000..d2fbe3753
--- /dev/null
+++ b/examples/multimediawidgets/player/qmediaplaylist.cpp
@@ -0,0 +1,689 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qmediaplaylist.h"
+#include "qmediaplaylist_p.h"
+#include "qplaylistfileparser_p.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qfile.h>
+#include <QtCore/qurl.h>
+#include <QtCore/qcoreevent.h>
+#include <QtCore/qcoreapplication.h>
+#include <QRandomGenerator>
+
+QT_BEGIN_NAMESPACE
+
+class QM3uPlaylistWriter
+{
+public:
+ QM3uPlaylistWriter(QIODevice *device)
+ :m_device(device), m_textStream(new QTextStream(m_device))
+ {
+ }
+
+ ~QM3uPlaylistWriter()
+ {
+ delete m_textStream;
+ }
+
+ bool writeItem(const QUrl& item)
+ {
+ *m_textStream << item.toString() << Qt::endl;
+ return true;
+ }
+
+private:
+ QIODevice *m_device;
+ QTextStream *m_textStream;
+};
+
+
+int QMediaPlaylistPrivate::nextPosition(int steps) const
+{
+ if (playlist.count() == 0)
+ return -1;
+
+ int next = currentPos + steps;
+
+ switch (playbackMode) {
+ case QMediaPlaylist::CurrentItemOnce:
+ return steps != 0 ? -1 : currentPos;
+ case QMediaPlaylist::CurrentItemInLoop:
+ return currentPos;
+ case QMediaPlaylist::Sequential:
+ if (next >= playlist.size())
+ next = -1;
+ break;
+ case QMediaPlaylist::Loop:
+ next %= playlist.count();
+ break;
+ }
+
+ return next;
+}
+
+int QMediaPlaylistPrivate::prevPosition(int steps) const
+{
+ if (playlist.count() == 0)
+ return -1;
+
+ int next = currentPos;
+ if (next < 0)
+ next = playlist.size();
+ next -= steps;
+
+ switch (playbackMode) {
+ case QMediaPlaylist::CurrentItemOnce:
+ return steps != 0 ? -1 : currentPos;
+ case QMediaPlaylist::CurrentItemInLoop:
+ return currentPos;
+ case QMediaPlaylist::Sequential:
+ if (next < 0)
+ next = -1;
+ break;
+ case QMediaPlaylist::Loop:
+ next %= playlist.size();
+ if (next < 0)
+ next += playlist.size();
+ break;
+ }
+
+ return next;
+}
+
+/*!
+ \class QMediaPlaylist
+ \inmodule QtMultimedia
+ \ingroup multimedia
+ \ingroup multimedia_playback
+
+
+ \brief The QMediaPlaylist class provides a list of media content to play.
+
+ QMediaPlaylist is intended to be used with other media objects,
+ like QMediaPlayer.
+
+ QMediaPlaylist allows to access the service intrinsic playlist functionality
+ if available, otherwise it provides the local memory playlist implementation.
+
+ \snippet multimedia-snippets/media.cpp Movie playlist
+
+ Depending on playlist source implementation, most of the playlist mutating
+ operations can be asynchronous.
+
+ QMediaPlayList currently supports M3U playlists (file extension .m3u and .m3u8).
+
+ \sa QUrl
+*/
+
+
+/*!
+ \enum QMediaPlaylist::PlaybackMode
+
+ The QMediaPlaylist::PlaybackMode describes the order items in playlist are played.
+
+ \value CurrentItemOnce The current item is played only once.
+
+ \value CurrentItemInLoop The current item is played repeatedly in a loop.
+
+ \value Sequential Playback starts from the current and moves through each successive item until the last is reached and then stops.
+ The next item is a null item when the last one is currently playing.
+
+ \value Loop Playback restarts at the first item after the last has finished playing.
+
+ \value Random Play items in random order.
+*/
+
+
+
+/*!
+ Create a new playlist object with the given \a parent.
+*/
+
+QMediaPlaylist::QMediaPlaylist(QObject *parent)
+ : QObject(parent)
+ , d_ptr(new QMediaPlaylistPrivate)
+{
+ Q_D(QMediaPlaylist);
+
+ d->q_ptr = this;
+}
+
+/*!
+ Destroys the playlist.
+ */
+
+QMediaPlaylist::~QMediaPlaylist()
+{
+ delete d_ptr;
+}
+
+/*!
+ \property QMediaPlaylist::playbackMode
+
+ This property defines the order that items in the playlist are played.
+
+ \sa QMediaPlaylist::PlaybackMode
+*/
+
+QMediaPlaylist::PlaybackMode QMediaPlaylist::playbackMode() const
+{
+ return d_func()->playbackMode;
+}
+
+void QMediaPlaylist::setPlaybackMode(QMediaPlaylist::PlaybackMode mode)
+{
+ Q_D(QMediaPlaylist);
+
+ if (mode == d->playbackMode)
+ return;
+
+ d->playbackMode = mode;
+
+ emit playbackModeChanged(mode);
+}
+
+/*!
+ Returns position of the current media content in the playlist.
+*/
+int QMediaPlaylist::currentIndex() const
+{
+ return d_func()->currentPos;
+}
+
+/*!
+ Returns the current media content.
+*/
+
+QUrl QMediaPlaylist::currentMedia() const
+{
+ Q_D(const QMediaPlaylist);
+ if (d->currentPos < 0 || d->currentPos >= d->playlist.size())
+ return QUrl();
+ return d_func()->playlist.at(d_func()->currentPos);
+}
+
+/*!
+ Returns the index of the item, which would be current after calling next()
+ \a steps times.
+
+ Returned value depends on the size of playlist, current position
+ and playback mode.
+
+ \sa QMediaPlaylist::playbackMode(), previousIndex()
+*/
+int QMediaPlaylist::nextIndex(int steps) const
+{
+ return d_func()->nextPosition(steps);
+}
+
+/*!
+ Returns the index of the item, which would be current after calling previous()
+ \a steps times.
+
+ \sa QMediaPlaylist::playbackMode(), nextIndex()
+*/
+
+int QMediaPlaylist::previousIndex(int steps) const
+{
+ return d_func()->prevPosition(steps);
+}
+
+
+/*!
+ Returns the number of items in the playlist.
+
+ \sa isEmpty()
+ */
+int QMediaPlaylist::mediaCount() const
+{
+ return d_func()->playlist.count();
+}
+
+/*!
+ Returns true if the playlist contains no items, otherwise returns false.
+
+ \sa mediaCount()
+ */
+bool QMediaPlaylist::isEmpty() const
+{
+ return mediaCount() == 0;
+}
+
+/*!
+ Returns the media content at \a index in the playlist.
+*/
+
+QUrl QMediaPlaylist::media(int index) const
+{
+ Q_D(const QMediaPlaylist);
+ if (index < 0 || index >= d->playlist.size())
+ return QUrl();
+ return d->playlist.at(index);
+}
+
+/*!
+ Append the media \a content to the playlist.
+
+ Returns true if the operation is successful, otherwise returns false.
+ */
+void QMediaPlaylist::addMedia(const QUrl &content)
+{
+ Q_D(QMediaPlaylist);
+ int pos = d->playlist.size();
+ emit mediaAboutToBeInserted(pos, pos);
+ d->playlist.append(content);
+ emit mediaInserted(pos, pos);
+}
+
+/*!
+ Append multiple media content \a items to the playlist.
+
+ Returns true if the operation is successful, otherwise returns false.
+ */
+void QMediaPlaylist::addMedia(const QList<QUrl> &items)
+{
+ if (!items.size())
+ return;
+
+ Q_D(QMediaPlaylist);
+ int first = d->playlist.size();
+ int last = first + items.size() - 1;
+ emit mediaAboutToBeInserted(first, last);
+ d_func()->playlist.append(items);
+ emit mediaInserted(first, last);
+}
+
+/*!
+ Insert the media \a content to the playlist at position \a pos.
+
+ Returns true if the operation is successful, otherwise returns false.
+*/
+
+bool QMediaPlaylist::insertMedia(int pos, const QUrl &content)
+{
+ Q_D(QMediaPlaylist);
+ pos = qBound(0, pos, d->playlist.size());
+ emit mediaAboutToBeInserted(pos, pos);
+ d->playlist.insert(pos, content);
+ emit mediaInserted(pos, pos);
+ return true;
+}
+
+/*!
+ Insert multiple media content \a items to the playlist at position \a pos.
+
+ Returns true if the operation is successful, otherwise returns false.
+*/
+
+bool QMediaPlaylist::insertMedia(int pos, const QList<QUrl> &items)
+{
+ if (!items.size())
+ return true;
+
+ Q_D(QMediaPlaylist);
+ pos = qBound(0, pos, d->playlist.size());
+ int last = pos + items.size() - 1;
+ emit mediaAboutToBeInserted(pos, last);
+ auto newList = d->playlist.mid(0, pos);
+ newList += items;
+ newList += d->playlist.mid(pos);
+ d->playlist = newList;
+ emit mediaInserted(pos, last);
+ return true;
+}
+
+/*!
+ Move the item from position \a from to position \a to.
+
+ Returns true if the operation is successful, otherwise false.
+
+ \since 5.7
+*/
+bool QMediaPlaylist::moveMedia(int from, int to)
+{
+ Q_D(QMediaPlaylist);
+ if (from < 0 || from > d->playlist.count() ||
+ to < 0 || to > d->playlist.count())
+ return false;
+
+ d->playlist.move(from, to);
+ emit mediaChanged(from, to);
+ return true;
+}
+
+/*!
+ Remove the item from the playlist at position \a pos.
+
+ Returns true if the operation is successful, otherwise return false.
+ */
+bool QMediaPlaylist::removeMedia(int pos)
+{
+ return removeMedia(pos, pos);
+}
+
+/*!
+ Remove items in the playlist from \a start to \a end inclusive.
+
+ Returns true if the operation is successful, otherwise return false.
+ */
+bool QMediaPlaylist::removeMedia(int start, int end)
+{
+ Q_D(QMediaPlaylist);
+ if (end < start || end < 0 || start >= d->playlist.count())
+ return false;
+ start = qBound(0, start, d->playlist.size() - 1);
+ end = qBound(0, end, d->playlist.size() - 1);
+
+ emit mediaAboutToBeRemoved(start, end);
+ d->playlist.remove(start, end - start + 1);
+ emit mediaRemoved(start, end);
+ return true;
+}
+
+/*!
+ Remove all the items from the playlist.
+
+ Returns true if the operation is successful, otherwise return false.
+ */
+void QMediaPlaylist::clear()
+{
+ Q_D(QMediaPlaylist);
+ int size = d->playlist.size();
+ emit mediaAboutToBeRemoved(0, size - 1);
+ d->playlist.clear();
+ emit mediaRemoved(0, size - 1);
+}
+
+/*!
+ Load playlist from \a location. If \a format is specified, it is used,
+ otherwise format is guessed from location name and data.
+
+ New items are appended to playlist.
+
+ QMediaPlaylist::loaded() signal is emitted if playlist was loaded successfully,
+ otherwise the playlist emits loadFailed().
+*/
+
+void QMediaPlaylist::load(const QUrl &location, const char *format)
+{
+ Q_D(QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ d->ensureParser();
+ d->parser->start(location, QString::fromUtf8(format));
+}
+
+/*!
+ Load playlist from QIODevice \a device. If \a format is specified, it is used,
+ otherwise format is guessed from device data.
+
+ New items are appended to playlist.
+
+ QMediaPlaylist::loaded() signal is emitted if playlist was loaded successfully,
+ otherwise the playlist emits loadFailed().
+*/
+void QMediaPlaylist::load(QIODevice *device, const char *format)
+{
+ Q_D(QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ d->ensureParser();
+ d->parser->start(device, QString::fromUtf8(format));
+}
+
+/*!
+ Save playlist to \a location. If \a format is specified, it is used,
+ otherwise format is guessed from location name.
+
+ Returns true if playlist was saved successfully, otherwise returns false.
+ */
+bool QMediaPlaylist::save(const QUrl &location, const char *format) const
+{
+ Q_D(const QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ if (!d->checkFormat(format))
+ return false;
+
+ QFile file(location.toLocalFile());
+
+ if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ d->error = AccessDeniedError;
+ d->errorString = tr("The file could not be accessed.");
+ return false;
+ }
+
+ return save(&file, format);
+}
+
+/*!
+ Save playlist to QIODevice \a device using format \a format.
+
+ Returns true if playlist was saved successfully, otherwise returns false.
+*/
+bool QMediaPlaylist::save(QIODevice *device, const char *format) const
+{
+ Q_D(const QMediaPlaylist);
+
+ d->error = NoError;
+ d->errorString.clear();
+
+ if (!d->checkFormat(format))
+ return false;
+
+ QM3uPlaylistWriter writer(device);
+ for (const auto &entry : d->playlist)
+ writer.writeItem(entry);
+ return true;
+}
+
+/*!
+ Returns the last error condition.
+*/
+QMediaPlaylist::Error QMediaPlaylist::error() const
+{
+ return d_func()->error;
+}
+
+/*!
+ Returns the string describing the last error condition.
+*/
+QString QMediaPlaylist::errorString() const
+{
+ return d_func()->errorString;
+}
+
+/*!
+ Shuffle items in the playlist.
+*/
+void QMediaPlaylist::shuffle()
+{
+ Q_D(QMediaPlaylist);
+ QList<QUrl> playlist;
+
+ // keep the current item when shuffling
+ QUrl current;
+ if (d->currentPos != -1)
+ current = d->playlist.takeAt(d->currentPos);
+
+ while (!d->playlist.isEmpty())
+ playlist.append(d->playlist.takeAt(QRandomGenerator::global()->bounded(int(d->playlist.size()))));
+
+ if (d->currentPos != -1)
+ playlist.insert(d->currentPos, current);
+ d->playlist = playlist;
+ emit mediaChanged(0, d->playlist.count());
+}
+
+
+/*!
+ Advance to the next media content in playlist.
+*/
+void QMediaPlaylist::next()
+{
+ Q_D(QMediaPlaylist);
+ d->currentPos = d->nextPosition(1);
+
+ emit currentIndexChanged(d->currentPos);
+ emit currentMediaChanged(currentMedia());
+}
+
+/*!
+ Return to the previous media content in playlist.
+*/
+void QMediaPlaylist::previous()
+{
+ Q_D(QMediaPlaylist);
+ d->currentPos = d->prevPosition(1);
+
+ emit currentIndexChanged(d->currentPos);
+ emit currentMediaChanged(currentMedia());
+}
+
+/*!
+ Activate media content from playlist at position \a playlistPosition.
+*/
+
+void QMediaPlaylist::setCurrentIndex(int playlistPosition)
+{
+ Q_D(QMediaPlaylist);
+ if (playlistPosition < 0 || playlistPosition >= d->playlist.size())
+ playlistPosition = -1;
+ d->currentPos = playlistPosition;
+
+ emit currentIndexChanged(d->currentPos);
+ emit currentMediaChanged(currentMedia());
+}
+
+/*!
+ \fn void QMediaPlaylist::mediaInserted(int start, int end)
+
+ This signal is emitted after media has been inserted into the playlist.
+ The new items are those between \a start and \a end inclusive.
+ */
+
+/*!
+ \fn void QMediaPlaylist::mediaRemoved(int start, int end)
+
+ This signal is emitted after media has been removed from the playlist.
+ The removed items are those between \a start and \a end inclusive.
+ */
+
+/*!
+ \fn void QMediaPlaylist::mediaChanged(int start, int end)
+
+ This signal is emitted after media has been changed in the playlist
+ between \a start and \a end positions inclusive.
+ */
+
+/*!
+ \fn void QMediaPlaylist::currentIndexChanged(int position)
+
+ Signal emitted when playlist position changed to \a position.
+*/
+
+/*!
+ \fn void QMediaPlaylist::playbackModeChanged(QMediaPlaylist::PlaybackMode mode)
+
+ Signal emitted when playback mode changed to \a mode.
+*/
+
+/*!
+ \fn void QMediaPlaylist::mediaAboutToBeInserted(int start, int end)
+
+ Signal emitted when items are to be inserted at \a start and ending at \a end.
+*/
+
+/*!
+ \fn void QMediaPlaylist::mediaAboutToBeRemoved(int start, int end)
+
+ Signal emitted when item are to be deleted at \a start and ending at \a end.
+*/
+
+/*!
+ \fn void QMediaPlaylist::currentMediaChanged(const QUrl &content)
+
+ Signal emitted when current media changes to \a content.
+*/
+
+/*!
+ \property QMediaPlaylist::currentIndex
+ \brief Current position.
+*/
+
+/*!
+ \property QMediaPlaylist::currentMedia
+ \brief Current media content.
+*/
+
+/*!
+ \fn QMediaPlaylist::loaded()
+
+ Signal emitted when playlist finished loading.
+*/
+
+/*!
+ \fn QMediaPlaylist::loadFailed()
+
+ Signal emitted if failed to load playlist.
+*/
+
+/*!
+ \enum QMediaPlaylist::Error
+
+ This enum describes the QMediaPlaylist error codes.
+
+ \value NoError No errors.
+ \value FormatError Format error.
+ \value FormatNotSupportedError Format not supported.
+ \value NetworkError Network error.
+ \value AccessDeniedError Access denied error.
+*/
+
+QT_END_NAMESPACE
+
+#include "moc_qmediaplaylist.cpp"
diff --git a/examples/multimediawidgets/player/qmediaplaylist.h b/examples/multimediawidgets/player/qmediaplaylist.h
new file mode 100644
index 000000000..163ef93fe
--- /dev/null
+++ b/examples/multimediawidgets/player/qmediaplaylist.h
@@ -0,0 +1,132 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMEDIAPLAYLIST_H
+#define QMEDIAPLAYLIST_H
+
+#include <QtCore/qobject.h>
+
+#include <QtMultimedia/qtmultimediaglobal.h>
+#include <QtMultimedia/qmediaenumdebug.h>
+
+
+QT_BEGIN_NAMESPACE
+
+class QMediaPlaylistPrivate;
+class QMediaPlaylist : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QMediaPlaylist::PlaybackMode playbackMode READ playbackMode WRITE setPlaybackMode NOTIFY playbackModeChanged)
+ Q_PROPERTY(QUrl currentMedia READ currentMedia NOTIFY currentMediaChanged)
+ Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
+
+public:
+ enum PlaybackMode { CurrentItemOnce, CurrentItemInLoop, Sequential, Loop };
+ Q_ENUM(PlaybackMode)
+ enum Error { NoError, FormatError, FormatNotSupportedError, NetworkError, AccessDeniedError };
+ Q_ENUM(Error)
+
+ explicit QMediaPlaylist(QObject *parent = nullptr);
+ virtual ~QMediaPlaylist();
+
+ PlaybackMode playbackMode() const;
+ void setPlaybackMode(PlaybackMode mode);
+
+ int currentIndex() const;
+ QUrl currentMedia() const;
+
+ int nextIndex(int steps = 1) const;
+ int previousIndex(int steps = 1) const;
+
+ QUrl media(int index) const;
+
+ int mediaCount() const;
+ bool isEmpty() const;
+
+ void addMedia(const QUrl &content);
+ void addMedia(const QList<QUrl> &items);
+ bool insertMedia(int index, const QUrl &content);
+ bool insertMedia(int index, const QList<QUrl> &items);
+ bool moveMedia(int from, int to);
+ bool removeMedia(int pos);
+ bool removeMedia(int start, int end);
+ void clear();
+
+ void load(const QUrl &location, const char *format = nullptr);
+ void load(QIODevice *device, const char *format = nullptr);
+
+ bool save(const QUrl &location, const char *format = nullptr) const;
+ bool save(QIODevice *device, const char *format) const;
+
+ Error error() const;
+ QString errorString() const;
+
+public Q_SLOTS:
+ void shuffle();
+
+ void next();
+ void previous();
+
+ void setCurrentIndex(int index);
+
+Q_SIGNALS:
+ void currentIndexChanged(int index);
+ void playbackModeChanged(QMediaPlaylist::PlaybackMode mode);
+ void currentMediaChanged(const QUrl&);
+
+ void mediaAboutToBeInserted(int start, int end);
+ void mediaInserted(int start, int end);
+ void mediaAboutToBeRemoved(int start, int end);
+ void mediaRemoved(int start, int end);
+ void mediaChanged(int start, int end);
+
+ void loaded();
+ void loadFailed();
+
+private:
+ QMediaPlaylistPrivate *d_ptr;
+ Q_DECLARE_PRIVATE(QMediaPlaylist)
+};
+
+QT_END_NAMESPACE
+
+Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, PlaybackMode)
+Q_MEDIA_ENUM_DEBUG(QMediaPlaylist, Error)
+
+#endif // QMEDIAPLAYLIST_H
diff --git a/examples/multimediawidgets/player/qmediaplaylist_p.h b/examples/multimediawidgets/player/qmediaplaylist_p.h
new file mode 100644
index 000000000..a09831377
--- /dev/null
+++ b/examples/multimediawidgets/player/qmediaplaylist_p.h
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#ifndef QMEDIAPLAYLIST_P_H
+#define QMEDIAPLAYLIST_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 "qmediaplaylist.h"
+#include "qplaylistfileparser_p.h"
+
+#include <QtCore/qdebug.h>
+
+#ifdef Q_MOC_RUN
+# pragma Q_MOC_EXPAND_MACROS
+#endif
+
+QT_BEGIN_NAMESPACE
+
+
+class QMediaPlaylistControl;
+
+class QMediaPlaylistPrivate
+{
+ Q_DECLARE_PUBLIC(QMediaPlaylist)
+public:
+ QMediaPlaylistPrivate()
+ : error(QMediaPlaylist::NoError)
+ {
+ }
+
+ virtual ~QMediaPlaylistPrivate()
+ {
+ if (parser)
+ delete parser;
+ }
+
+ void loadFailed(QMediaPlaylist::Error error, const QString &errorString)
+ {
+ this->error = error;
+ this->errorString = errorString;
+
+ emit q_ptr->loadFailed();
+ }
+
+ void loadFinished()
+ {
+ q_ptr->addMedia(parser->playlist);
+
+ emit q_ptr->loaded();
+ }
+
+ bool checkFormat(const char *format) const
+ {
+ QLatin1String f(format);
+ QPlaylistFileParser::FileType type = format ? QPlaylistFileParser::UNKNOWN : QPlaylistFileParser::M3U8;
+ if (format) {
+ if (f == QLatin1String("m3u") || f == QLatin1String("text/uri-list") ||
+ f == QLatin1String("audio/x-mpegurl") || f == QLatin1String("audio/mpegurl"))
+ type = QPlaylistFileParser::M3U;
+ else if (f == QLatin1String("m3u8") || f == QLatin1String("application/x-mpegURL") ||
+ f == QLatin1String("application/vnd.apple.mpegurl"))
+ type = QPlaylistFileParser::M3U8;
+ }
+
+ if (type == QPlaylistFileParser::UNKNOWN || type == QPlaylistFileParser::PLS) {
+ error = QMediaPlaylist::FormatNotSupportedError;
+ errorString = QMediaPlaylist::tr("This file format is not supported.");
+ return false;
+ }
+ return true;
+ }
+
+ void ensureParser()
+ {
+ if (parser)
+ return;
+
+ parser = new QPlaylistFileParser(q_ptr);
+ QObject::connect(parser, &QPlaylistFileParser::finished, [this]() { loadFinished(); });
+ QObject::connect(parser, &QPlaylistFileParser::error,
+ [this](QMediaPlaylist::Error err, const QString& errorMsg) { loadFailed(err, errorMsg); });
+ }
+
+ int nextPosition(int steps) const;
+ int prevPosition(int steps) const;
+
+ QList<QUrl> playlist;
+
+ int currentPos = -1;
+ QMediaPlaylist::PlaybackMode playbackMode = QMediaPlaylist::Sequential;
+
+ QPlaylistFileParser *parser = nullptr;
+ mutable QMediaPlaylist::Error error;
+ mutable QString errorString;
+
+ QMediaPlaylist *q_ptr;
+};
+
+QT_END_NAMESPACE
+
+
+#endif // QMEDIAPLAYLIST_P_H
diff --git a/examples/multimediawidgets/player/qplaylistfileparser.cpp b/examples/multimediawidgets/player/qplaylistfileparser.cpp
new file mode 100644
index 000000000..d684a9853
--- /dev/null
+++ b/examples/multimediawidgets/player/qplaylistfileparser.cpp
@@ -0,0 +1,641 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "qplaylistfileparser_p.h"
+#include <qfileinfo.h>
+#include <QtCore/QDebug>
+#include <QtCore/qiodevice.h>
+#include <QtCore/qpointer.h>
+#include <QtNetwork/QNetworkReply>
+#include <QtNetwork/QNetworkRequest>
+#include "qmediaplayer.h"
+#include "qmediametadata.h"
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+class ParserBase
+{
+public:
+ explicit ParserBase(QPlaylistFileParser *parent)
+ : m_parent(parent)
+ , m_aborted(false)
+ {
+ Q_ASSERT(m_parent);
+ }
+
+ bool parseLine(int lineIndex, const QString& line, const QUrl& root)
+ {
+ if (m_aborted)
+ return false;
+
+ const bool ok = parseLineImpl(lineIndex, line, root);
+ return ok && !m_aborted;
+ }
+
+ virtual void abort() { m_aborted = true; }
+ virtual ~ParserBase() = default;
+
+protected:
+ virtual bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) = 0;
+
+ static QUrl expandToFullPath(const QUrl &root, const QString &line)
+ {
+ // On Linux, backslashes are not converted to forward slashes :/
+ if (line.startsWith(QLatin1String("//")) || line.startsWith(QLatin1String("\\\\"))) {
+ // Network share paths are not resolved
+ return QUrl::fromLocalFile(line);
+ }
+
+ QUrl url(line);
+ if (url.scheme().isEmpty()) {
+ // Resolve it relative to root
+ if (root.isLocalFile())
+ return QUrl::fromUserInput(line, root.adjusted(QUrl::RemoveFilename).toLocalFile(), QUrl::AssumeLocalFile);
+ return root.resolved(url);
+ }
+ if (url.scheme().length() == 1)
+ // Assume it's a drive letter for a Windows path
+ url = QUrl::fromLocalFile(line);
+
+ return url;
+ }
+
+ void newItemFound(const QVariant& content) { Q_EMIT m_parent->newItem(content); }
+
+
+ QPlaylistFileParser *m_parent;
+ bool m_aborted;
+};
+
+class M3UParser : public ParserBase
+{
+public:
+ explicit M3UParser(QPlaylistFileParser *q)
+ : ParserBase(q)
+ , 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
+
+ */
+ bool parseLineImpl(int lineIndex, const QString& line, const QUrl& root) override
+ {
+ if (line[0] == u'#' ) {
+ if (m_extendedFormat) {
+ if (line.startsWith(QLatin1String("#EXTINF:"))) {
+ m_extraInfo.clear();
+ int artistStart = line.indexOf(QLatin1String(","), 8);
+ bool ok = false;
+ QStringView lineView { line };
+ int length = lineView.mid(8, artistStart < 8 ? -1 : artistStart - 8).trimmed().toInt(&ok);
+ if (ok && length > 0) {
+ //convert from second to milisecond
+ m_extraInfo[QMediaMetaData::Duration] = QVariant(length * 1000);
+ }
+ if (artistStart > 0) {
+ int titleStart = getSplitIndex(line, artistStart);
+ if (titleStart > artistStart) {
+ m_extraInfo[QMediaMetaData::Author] = lineView.mid(artistStart + 1,
+ titleStart - artistStart - 1).trimmed().toString().
+ replace(QLatin1String("--"), QLatin1String("-"));
+ m_extraInfo[QMediaMetaData::Title] = lineView.mid(titleStart + 1).trimmed().toString().
+ replace(QLatin1String("--"), QLatin1String("-"));
+ } else {
+ m_extraInfo[QMediaMetaData::Title] = lineView.mid(artistStart + 1).trimmed().toString().
+ replace(QLatin1String("--"), QLatin1String("-"));
+ }
+ }
+ }
+ } else if (lineIndex == 0 && line.startsWith(QLatin1String("#EXTM3U"))) {
+ m_extendedFormat = true;
+ }
+ } else {
+ QUrl url = expandToFullPath(root, line);
+ m_extraInfo[QMediaMetaData::Url] = url;
+ m_parent->playlist.append(url);
+ newItemFound(QVariant::fromValue(m_extraInfo));
+ m_extraInfo.clear();
+ }
+
+ return true;
+ }
+
+ 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] == u'-') {
+ if (i == line.length() - 1)
+ return i;
+ ++i;
+ if (buf[i] != u'-')
+ return i - 1;
+ }
+ }
+ return -1;
+ }
+
+private:
+ QMediaMetaData m_extraInfo;
+ bool m_extendedFormat;
+};
+
+class PLSParser : public ParserBase
+{
+public:
+ explicit PLSParser(QPlaylistFileParser *q)
+ : ParserBase(q)
+ {
+ }
+
+/*
+ *
+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
+*/
+ bool parseLineImpl(int, const QString &line, const QUrl &root) override
+ {
+ // We ignore everything but 'File' entries, since that's the only thing we care about.
+ if (!line.startsWith(QLatin1String("File")))
+ return true;
+
+ QString value = getValue(line);
+ if (value.isEmpty())
+ return true;
+
+ QUrl path = expandToFullPath(root, value);
+ m_parent->playlist.append(path);
+ newItemFound(path);
+
+ return true;
+ }
+
+ QString getValue(QStringView line) {
+ int start = line.indexOf(u'=');
+ if (start < 0)
+ return QString();
+ return line.mid(start + 1).trimmed().toString();
+ }
+};
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////////////
+
+class QPlaylistFileParserPrivate
+{
+ Q_DECLARE_PUBLIC(QPlaylistFileParser)
+public:
+ QPlaylistFileParserPrivate(QPlaylistFileParser *q)
+ : q_ptr(q)
+ , m_stream(nullptr)
+ , m_type(QPlaylistFileParser::UNKNOWN)
+ , m_scanIndex(0)
+ , m_lineIndex(-1)
+ , m_utf8(false)
+ , m_aborted(false)
+ {
+ }
+
+ void handleData();
+ void handleParserFinished();
+ void abort();
+ void reset();
+
+ QScopedPointer<QNetworkReply, QScopedPointerDeleteLater> m_source;
+ QScopedPointer<ParserBase> m_currentParser;
+ QByteArray m_buffer;
+ QUrl m_root;
+ QNetworkAccessManager m_mgr;
+ QString m_mimeType;
+ QPlaylistFileParser *q_ptr;
+ QPointer<QIODevice> m_stream;
+ QPlaylistFileParser::FileType m_type;
+ struct ParserJob
+ {
+ QIODevice *m_stream;
+ QUrl m_media;
+ QString m_mimeType;
+ [[nodiscard]] bool isValid() const { return m_stream || !m_media.isEmpty(); }
+ void reset() { m_stream = nullptr; m_media = QUrl(); m_mimeType = QString(); }
+ } m_pendingJob;
+ int m_scanIndex;
+ int m_lineIndex;
+ bool m_utf8;
+ bool m_aborted;
+
+private:
+ bool processLine(int startIndex, int length);
+};
+
+#define LINE_LIMIT 4096
+#define READ_LIMIT 64
+
+bool QPlaylistFileParserPrivate::processLine(int startIndex, int length)
+{
+ Q_Q(QPlaylistFileParser);
+ m_lineIndex++;
+
+ if (!m_currentParser) {
+ const QString urlString = m_root.toString();
+ const QString &suffix = !urlString.isEmpty() ? QFileInfo(urlString).suffix() : urlString;
+ QString mimeType;
+ if (m_source)
+ mimeType = m_source->header(QNetworkRequest::ContentTypeHeader).toString();
+ m_type = QPlaylistFileParser::findPlaylistType(suffix, !mimeType.isEmpty() ? mimeType : m_mimeType, m_buffer.constData(), quint32(m_buffer.size()));
+
+ switch (m_type) {
+ case QPlaylistFileParser::UNKNOWN:
+ emit q->error(QMediaPlaylist::FormatError,
+ QMediaPlaylist::tr("%1 playlist type is unknown").arg(m_root.toString()));
+ q->abort();
+ return false;
+ case QPlaylistFileParser::M3U:
+ m_currentParser.reset(new M3UParser(q));
+ break;
+ case QPlaylistFileParser::M3U8:
+ m_currentParser.reset(new M3UParser(q));
+ m_utf8 = true;
+ break;
+ case QPlaylistFileParser::PLS:
+ m_currentParser.reset(new PLSParser(q));
+ break;
+ }
+
+ Q_ASSERT(!m_currentParser.isNull());
+ }
+
+ 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 true;
+
+ Q_ASSERT(m_currentParser);
+ return m_currentParser->parseLine(m_lineIndex, line, m_root);
+}
+
+void QPlaylistFileParserPrivate::handleData()
+{
+ Q_Q(QPlaylistFileParser);
+ while (m_stream->bytesAvailable() && !m_aborted) {
+ int expectedBytes = qMin(READ_LIMIT, int(qMin(m_stream->bytesAvailable(),
+ qint64(LINE_LIMIT - m_buffer.size()))));
+ m_buffer.push_back(m_stream->read(expectedBytes));
+ int processedBytes = 0;
+ while (m_scanIndex < m_buffer.length() && !m_aborted) {
+ char s = m_buffer[m_scanIndex];
+ if (s == '\r' || s == '\n') {
+ int l = m_scanIndex - processedBytes;
+ if (l > 0) {
+ if (!processLine(processedBytes, l))
+ break;
+ }
+ processedBytes = m_scanIndex + 1;
+ if (!m_stream) {
+ //some error happened, so exit parsing
+ return;
+ }
+ }
+ m_scanIndex++;
+ }
+
+ if (m_aborted)
+ break;
+
+ if (m_buffer.length() - processedBytes >= LINE_LIMIT) {
+ emit q->error(QMediaPlaylist::FormatError, QMediaPlaylist::tr("invalid line in playlist file"));
+ q->abort();
+ break;
+ }
+
+ if (!m_stream->bytesAvailable() && (!m_source || !m_source->isFinished())) {
+ //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;
+ }
+
+ handleParserFinished();
+}
+
+QPlaylistFileParser::QPlaylistFileParser(QObject *parent)
+ : QObject(parent)
+ , d_ptr(new QPlaylistFileParserPrivate(this))
+{
+
+}
+
+QPlaylistFileParser::~QPlaylistFileParser() = default;
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findByMimeType(const QString &mime)
+{
+ if (mime == QLatin1String("text/uri-list") || mime == QLatin1String("audio/x-mpegurl") || mime == QLatin1String("audio/mpegurl"))
+ return QPlaylistFileParser::M3U;
+
+ if (mime == QLatin1String("application/x-mpegURL") || mime == QLatin1String("application/vnd.apple.mpegurl"))
+ return QPlaylistFileParser::M3U8;
+
+ if (mime == QLatin1String("audio/x-scpls"))
+ return QPlaylistFileParser::PLS;
+
+ return QPlaylistFileParser::UNKNOWN;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findBySuffixType(const QString &suffix)
+{
+ const QString &s = suffix.toLower();
+
+ if (s == QLatin1String("m3u"))
+ return QPlaylistFileParser::M3U;
+
+ if (s == QLatin1String("m3u8"))
+ return QPlaylistFileParser::M3U8;
+
+ if (s == QLatin1String("pls"))
+ return QPlaylistFileParser::PLS;
+
+ return QPlaylistFileParser::UNKNOWN;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findByDataHeader(const char *data, quint32 size)
+{
+ if (!data || size == 0)
+ return QPlaylistFileParser::UNKNOWN;
+
+ if (size >= 7 && strncmp(data, "#EXTM3U", 7) == 0)
+ return QPlaylistFileParser::M3U;
+
+ if (size >= 10 && strncmp(data, "[playlist]", 10) == 0)
+ return QPlaylistFileParser::PLS;
+
+ return QPlaylistFileParser::UNKNOWN;
+}
+
+QPlaylistFileParser::FileType QPlaylistFileParser::findPlaylistType(const QString& suffix,
+ const QString& mime,
+ const char *data,
+ quint32 size)
+{
+
+ FileType dataHeaderType = findByDataHeader(data, size);
+ if (dataHeaderType != UNKNOWN)
+ return dataHeaderType;
+
+ FileType mimeType = findByMimeType(mime);
+ if (mimeType != UNKNOWN)
+ return mimeType;
+
+ mimeType = findBySuffixType(mime);
+ if (mimeType != UNKNOWN)
+ return mimeType;
+
+ FileType suffixType = findBySuffixType(suffix);
+ if (suffixType != UNKNOWN)
+ return suffixType;
+
+ return UNKNOWN;
+}
+
+/*
+ * Delegating
+ */
+void QPlaylistFileParser::start(const QUrl &media, QIODevice *stream, const QString &mimeType)
+{
+ if (stream)
+ start(stream, mimeType);
+ else
+ start(media, mimeType);
+}
+
+void QPlaylistFileParser::start(QIODevice *stream, const QString &mimeType)
+{
+ Q_D(QPlaylistFileParser);
+ const bool validStream = stream ? (stream->isOpen() && stream->isReadable()) : false;
+
+ if (!validStream) {
+ Q_EMIT error(QMediaPlaylist::AccessDeniedError, QMediaPlaylist::tr("Invalid stream"));
+ return;
+ }
+
+ if (!d->m_currentParser.isNull()) {
+ abort();
+ d->m_pendingJob = { stream, QUrl(), mimeType };
+ return;
+ }
+
+ playlist.clear();
+ d->reset();
+ d->m_mimeType = mimeType;
+ d->m_stream = stream;
+ connect(d->m_stream, SIGNAL(readyRead()), this, SLOT(handleData()));
+ d->handleData();
+}
+
+void QPlaylistFileParser::start(const QUrl& request, const QString &mimeType)
+{
+ Q_D(QPlaylistFileParser);
+ const QUrl &url = request.url();
+
+ if (url.isLocalFile() && !QFile::exists(url.toLocalFile())) {
+ emit error(QMediaPlaylist::AccessDeniedError, QString(QMediaPlaylist::tr("%1 does not exist")).arg(url.toString()));
+ return;
+ }
+
+ if (!d->m_currentParser.isNull()) {
+ abort();
+ d->m_pendingJob = { nullptr, request, mimeType };
+ return;
+ }
+
+ d->reset();
+ d->m_root = url;
+ d->m_mimeType = mimeType;
+ d->m_source.reset(d->m_mgr.get(QNetworkRequest(request)));
+ d->m_stream = d->m_source.get();
+ connect(d->m_source.data(), SIGNAL(readyRead()), this, SLOT(handleData()));
+ connect(d->m_source.data(), SIGNAL(finished()), this, SLOT(handleData()));
+ connect(d->m_source.data(), SIGNAL(errorOccurred(QNetworkReply::NetworkError)), this, SLOT(handleError()));
+
+ if (url.isLocalFile())
+ d->handleData();
+}
+
+void QPlaylistFileParser::abort()
+{
+ Q_D(QPlaylistFileParser);
+ d->abort();
+
+ if (d->m_source)
+ d->m_source->disconnect();
+
+ if (d->m_stream)
+ disconnect(d->m_stream, SIGNAL(readyRead()), this, SLOT(handleData()));
+
+ playlist.clear();
+}
+
+void QPlaylistFileParser::handleData()
+{
+ Q_D(QPlaylistFileParser);
+ d->handleData();
+}
+
+void QPlaylistFileParserPrivate::handleParserFinished()
+{
+ Q_Q(QPlaylistFileParser);
+ const bool isParserValid = !m_currentParser.isNull();
+ if (!isParserValid && !m_aborted)
+ emit q->error(QMediaPlaylist::FormatNotSupportedError, QMediaPlaylist::tr("Empty file provided"));
+
+ if (isParserValid && !m_aborted) {
+ m_currentParser.reset();
+ emit q->finished();
+ }
+
+ if (!m_aborted)
+ q->abort();
+
+ if (!m_source.isNull())
+ m_source.reset();
+
+ if (m_pendingJob.isValid())
+ q->start(m_pendingJob.m_media, m_pendingJob.m_stream, m_pendingJob.m_mimeType);
+}
+
+void QPlaylistFileParserPrivate::abort()
+{
+ m_aborted = true;
+ if (!m_currentParser.isNull())
+ m_currentParser->abort();
+}
+
+void QPlaylistFileParserPrivate::reset()
+{
+ Q_ASSERT(m_currentParser.isNull());
+ Q_ASSERT(m_source.isNull());
+ m_buffer.clear();
+ m_root.clear();
+ m_mimeType.clear();
+ m_stream = nullptr;
+ m_type = QPlaylistFileParser::UNKNOWN;
+ m_scanIndex = 0;
+ m_lineIndex = -1;
+ m_utf8 = false;
+ m_aborted = false;
+ m_pendingJob.reset();
+}
+
+void QPlaylistFileParser::handleError()
+{
+ Q_D(QPlaylistFileParser);
+ const QString &errorString = d->m_source->errorString();
+ Q_EMIT error(QMediaPlaylist::NetworkError, errorString);
+ abort();
+}
+
+QT_END_NAMESPACE
diff --git a/examples/multimediawidgets/player/qplaylistfileparser_p.h b/examples/multimediawidgets/player/qplaylistfileparser_p.h
new file mode 100644
index 000000000..590472d24
--- /dev/null
+++ b/examples/multimediawidgets/player/qplaylistfileparser_p.h
@@ -0,0 +1,116 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 3 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL3 included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 3 requirements
+** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 2.0 or (at your option) the GNU General
+** Public license version 3 or any later version approved by the KDE Free
+** Qt Foundation. The licenses are as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-2.0.html and
+** https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $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 "qtmultimediaglobal.h"
+#include <QtCore/qobject.h>
+#include <qmediaplaylist.h>
+
+QT_BEGIN_NAMESPACE
+
+class QIODevice;
+class QUrl;
+class QNetworkRequest;
+
+class QPlaylistFileParserPrivate;
+
+class QPlaylistFileParser : public QObject
+{
+ Q_OBJECT
+public:
+ QPlaylistFileParser(QObject *parent = nullptr);
+ ~QPlaylistFileParser();
+
+ enum FileType
+ {
+ UNKNOWN,
+ M3U,
+ M3U8, // UTF-8 version of M3U
+ PLS
+ };
+
+ void start(const QUrl &media, QIODevice *stream = nullptr, const QString &mimeType = QString());
+ void start(const QUrl &request, const QString &mimeType = QString());
+ void start(QIODevice *stream, const QString &mimeType = QString());
+ void abort();
+
+ QList<QUrl> playlist;
+
+Q_SIGNALS:
+ void newItem(const QVariant& content);
+ void finished();
+ void error(QMediaPlaylist::Error err, const QString& errorMsg);
+
+private Q_SLOTS:
+ void handleData();
+ void handleError();
+
+private:
+
+ static FileType findByMimeType(const QString &mime);
+ static FileType findBySuffixType(const QString &suffix);
+ static FileType findByDataHeader(const char *data, quint32 size);
+ static FileType findPlaylistType(QIODevice *device,
+ const QString& mime);
+ static FileType findPlaylistType(const QString &suffix,
+ const QString& mime,
+ const char *data = nullptr,
+ quint32 size = 0);
+
+ Q_DISABLE_COPY(QPlaylistFileParser)
+ Q_DECLARE_PRIVATE(QPlaylistFileParser)
+ QScopedPointer<QPlaylistFileParserPrivate> d_ptr;
+};
+
+QT_END_NAMESPACE
+
+#endif // PLAYLISTFILEPARSER_P_H