diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/multimediawidgets/player/CMakeLists.txt | 2 | ||||
-rw-r--r-- | examples/multimediawidgets/player/player.cpp | 2 | ||||
-rw-r--r-- | examples/multimediawidgets/player/player.h | 2 | ||||
-rw-r--r-- | examples/multimediawidgets/player/playlistmodel.cpp | 2 | ||||
-rw-r--r-- | examples/multimediawidgets/player/qmediaplaylist.cpp | 689 | ||||
-rw-r--r-- | examples/multimediawidgets/player/qmediaplaylist.h | 132 | ||||
-rw-r--r-- | examples/multimediawidgets/player/qmediaplaylist_p.h | 148 | ||||
-rw-r--r-- | examples/multimediawidgets/player/qplaylistfileparser.cpp | 641 | ||||
-rw-r--r-- | examples/multimediawidgets/player/qplaylistfileparser_p.h | 116 |
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 |