chore: port to Qt 6
This commit is contained in:
653
qt/qmediaplaylist.cpp
Normal file
653
qt/qmediaplaylist.cpp
Normal file
@ -0,0 +1,653 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#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"
|
96
qt/qmediaplaylist.h
Normal file
96
qt/qmediaplaylist.h
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#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
|
112
qt/qmediaplaylist_p.h
Normal file
112
qt/qmediaplaylist_p.h
Normal file
@ -0,0 +1,112 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#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
|
605
qt/qplaylistfileparser.cpp
Normal file
605
qt/qplaylistfileparser.cpp
Normal file
@ -0,0 +1,605 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#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
|
80
qt/qplaylistfileparser_p.h
Normal file
80
qt/qplaylistfileparser_p.h
Normal file
@ -0,0 +1,80 @@
|
||||
// Copyright (C) 2016 The Qt Company Ltd.
|
||||
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
||||
|
||||
#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 "qmediaplaylist.h"
|
||||
#include <QtCore/qobject.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
|
Reference in New Issue
Block a user