diff options
author | Lars Knoll <lars.knoll@qt.io> | 2021-06-18 08:29:27 +0200 |
---|---|---|
committer | Lars Knoll <lars.knoll@qt.io> | 2021-06-18 15:28:13 +0200 |
commit | 5773f7214c7430a98dea3974c0597cb3ee0ea7f5 (patch) | |
tree | 4ef63d6f66e96a34e44bbab9685489e53b480f33 /src/multimedia/playback/qplaylistfileparser.cpp | |
parent | 435d7a1dd3ad96ea64045a320fffad8908e1c2cf (diff) |
Remove QMediaPlaylist from QtMultimedia for now
The API needs a redesign. As the class is not used anywhere in
Qt Multimedia, move it to the player example for now. For 6.3,
we should have a good look at how to best implement playlist
support again.
Change-Id: I5a225b69e2cd0f5c88531fdcee80c99f34255efd
Reviewed-by: Doris Verria <doris.verria@qt.io>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'src/multimedia/playback/qplaylistfileparser.cpp')
-rw-r--r-- | src/multimedia/playback/qplaylistfileparser.cpp | 641 |
1 files changed, 0 insertions, 641 deletions
diff --git a/src/multimedia/playback/qplaylistfileparser.cpp b/src/multimedia/playback/qplaylistfileparser.cpp deleted file mode 100644 index d684a9853..000000000 --- a/src/multimedia/playback/qplaylistfileparser.cpp +++ /dev/null @@ -1,641 +0,0 @@ -/**************************************************************************** -** -** 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 |