chore: port to Qt 6

This commit is contained in:
Gary Wang 2022-10-05 14:02:46 +08:00
parent 95eddb3af0
commit 7744d3bf2d
16 changed files with 1731 additions and 207 deletions

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.5)
cmake_minimum_required(VERSION 3.12)
project(pineapple-music LANGUAGES CXX)
@ -10,10 +10,10 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt5 COMPONENTS Widgets Multimedia Network LinguistTools REQUIRED)
find_package(Qt6 6.4 COMPONENTS Widgets Multimedia Network LinguistTools REQUIRED)
find_package(PkgConfig)
if (PKG_CONFIG_FOUND)
@ -26,6 +26,9 @@ set (PMUSIC_CPP_FILES
seekableslider.cpp
playlistmodel.cpp
singleapplicationmanager.cpp
qt/qplaylistfileparser.cpp
qt/qmediaplaylist.cpp
)
set (PMUSIC_HEADER_FILES
@ -33,6 +36,10 @@ set (PMUSIC_HEADER_FILES
seekableslider.h
playlistmodel.h
singleapplicationmanager.h
qt/qplaylistfileparser_p.h
qt/qmediaplaylist.h
qt/qmediaplaylist_p.h
)
set (PMUSIC_UI_FILES
@ -45,7 +52,7 @@ set (EXE_NAME pmusic)
file (GLOB PMUSIC_TS_FILES languages/*.ts)
set (PMUSIC_CPP_FILES_FOR_I18N ${PMUSIC_CPP_FILES} ${PMUSIC_UI_FILES})
qt5_create_translation(PMUSIC_QM_FILES ${PMUSIC_CPP_FILES_FOR_I18N} ${PMUSIC_TS_FILES})
qt_create_translation(PMUSIC_QM_FILES ${PMUSIC_CPP_FILES_FOR_I18N} ${PMUSIC_TS_FILES})
add_executable(${EXE_NAME}
${PMUSIC_HEADER_FILES}
@ -68,7 +75,7 @@ if (NOT TagLib_FOUND)
endif ()
target_include_directories(${EXE_NAME} PRIVATE ${TagLib_INCLUDE_DIRS})
target_link_libraries(${EXE_NAME} PRIVATE Qt5::Widgets Qt5::Multimedia Qt5::Network ${TagLib_LINK_LIBRARIES})
target_link_libraries(${EXE_NAME} PRIVATE Qt::Widgets Qt::Multimedia Qt::Network ${TagLib_LINK_LIBRARIES})
# Extra build settings
if (WIN32)

View File

@ -19,7 +19,6 @@ ShadowPower 于2014/8/1 夜间
#include <cstring>
typedef unsigned char byte;
using namespace std;
namespace spFLAC {
//Flac元数据块头部结构体定义

View File

@ -19,7 +19,6 @@ ShadowPower 于2014/8/1 上午
#include <cstring>
typedef unsigned char byte;
using namespace std;
namespace spID3 {
//ID3v2标签头部结构体定义

View File

@ -5,16 +5,16 @@ environment:
PACKAGE_INSTALL_ROOT: C:\projects\pir
PKG_CONFIG_PATH: C:\projects\pir\lib\pkgconfig
matrix:
- build_name: mingw81_64_qt5_15_2
QTPATH: C:\Qt\5.15.2\mingw81_64
MINGW64: C:\Qt\Tools\mingw810_64
- build_name: mingw1120_64_qt6_4
QTPATH: C:\Qt\6.4\mingw_64
MINGW64: C:\Qt\Tools\mingw1120_64
install:
- mkdir %CMAKE_INSTALL_ROOT%
- mkdir %PACKAGE_INSTALL_ROOT%
- cd %APPVEYOR_BUILD_FOLDER%
- git submodule update --init --recursive
- set PATH=%PATH%;%CMAKE_INSTALL_ROOT%;%QTPATH%\bin;%MINGW32%\bin
- set PATH=%PATH%;%CMAKE_INSTALL_ROOT%;%QTPATH%\bin;%MINGW64%\bin
- set CC=%MINGW64%\bin\gcc.exe
- set CXX=%MINGW64%\bin\g++.exe
@ -40,8 +40,11 @@ build_script:
- cmake --build . --target install
# fixme: I don't know how to NOT make the binary installed to the ./bin/ folder...
- cd bin
- windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-angle --no-system-d3d-compiler .\pmusic.exe
- windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --compiler-runtime --no-system-d3d-compiler --multimedia .\pmusic.exe
- xcopy /s %PACKAGE_INSTALL_ROOT%\bin %cd%
# don't know why windeployqt doesn't copy the multimedia plugin dir...
- mkdir multimedia
- copy %QTPATH%\plugins\multimedia\windowsmediaplugin.dll multimedia\windowsmediaplugin.dll
# for debug..
- tree /f

View File

@ -4,61 +4,109 @@
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="106"/>
<source>Mono</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="108"/>
<source>Stereo</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="110"/>
<source>%1 Channels</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="238"/>
<source>Select songs to play</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="240"/>
<source>Audio Files</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="23"/>
<source>Pineapple Player</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="231"/>
<source>^</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="297"/>
<source>No song loaded...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="304"/>
<source>Drag and drop file to load</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="313"/>
<location filename="../mainwindow.ui" line="320"/>
<source>0:00</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="116"/>
<source>Sample Rate: %1 Hz</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="121"/>
<source>Bitrate: %1 Kbps</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="126"/>
<source>Channel Count: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QMediaPlaylist</name>
<message>
<location filename="../qt/qmediaplaylist.cpp" line="460"/>
<source>The file could not be accessed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="301"/>
<source>%1 playlist type is unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="362"/>
<source>invalid line in playlist file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="485"/>
<source>Invalid stream</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="509"/>
<source>%1 does not exist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="557"/>
<source>Empty file provided</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../main.cpp" line="28"/>
<source>File list.</source>
<translation type="unfinished"></translation>
</message>

View File

@ -4,61 +4,109 @@
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="106"/>
<source>Mono</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="108"/>
<source>Stereo</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="110"/>
<source>%1 Channels</source>
<translation>%1 </translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="238"/>
<source>Select songs to play</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="240"/>
<source>Audio Files</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="23"/>
<source>Pineapple Player</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="231"/>
<source>^</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="297"/>
<source>No song loaded...</source>
<translation>...</translation>
</message>
<message>
<location filename="../mainwindow.ui" line="304"/>
<source>Drag and drop file to load</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.ui" line="313"/>
<location filename="../mainwindow.ui" line="320"/>
<source>0:00</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="116"/>
<source>Sample Rate: %1 Hz</source>
<translation>: %1 Hz</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="121"/>
<source>Bitrate: %1 Kbps</source>
<translation>: %1 Kbps</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="126"/>
<source>Channel Count: %1</source>
<translation>: %1</translation>
</message>
</context>
<context>
<name>QMediaPlaylist</name>
<message>
<location filename="../qt/qmediaplaylist.cpp" line="460"/>
<source>The file could not be accessed.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="301"/>
<source>%1 playlist type is unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="362"/>
<source>invalid line in playlist file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="485"/>
<source>Invalid stream</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="509"/>
<source>%1 does not exist</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../qt/qplaylistfileparser.cpp" line="557"/>
<source>Empty file provided</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../main.cpp" line="28"/>
<source>File list.</source>
<translation></translation>
</message>

View File

@ -2,6 +2,7 @@
#include "./ui_mainwindow.h"
#include "playlistmodel.h"
#include "qt/qmediaplaylist.h"
#include "ID3v2Pic.h"
#include "FlacPic.h"
@ -13,7 +14,7 @@
#include <QPainter>
#include <QMediaPlayer>
#include <QMediaPlaylist>
#include <QAudioOutput>
#include <QPropertyAnimation>
#include <QFileDialog>
#include <QTime>
@ -27,10 +28,13 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_mediaPlayer(new QMediaPlayer(this))
, m_audioOutput(new QAudioOutput(this))
, m_playlistModel(new PlaylistModel(this))
{
ui->setupUi(this);
m_mediaPlayer->setAudioOutput(m_audioOutput);
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);
this->setAttribute(Qt::WA_TranslucentBackground, true);
@ -88,8 +92,7 @@ void MainWindow::loadPlaylistBySingleLocalFile(const QString &path)
currentFileIndex = 0;
}
QMediaPlaylist * playlist = createPlaylist(urlList);
playlist->setCurrentIndex(currentFileIndex);
createPlaylist(urlList);
}
void MainWindow::setAudioPropertyInfoForDisplay(int sampleRate, int bitrate, int channelCount, QString audioExt)
@ -246,22 +249,11 @@ void MainWindow::loadFile()
/*
* The returned QMediaPlaylist* ownership belongs to the internal QMediaPlayer instance.
*/
QMediaPlaylist *MainWindow::createPlaylist(QList<QUrl> urlList)
void MainWindow::createPlaylist(QList<QUrl> urlList)
{
QMediaPlaylist * oldPlaylist = m_mediaPlayer->playlist();
QMediaPlaylist * playlist = new QMediaPlaylist(m_mediaPlayer);
if (oldPlaylist) {
oldPlaylist->disconnect();
oldPlaylist->deleteLater();
}
for (const QUrl & url : urlList) {
bool succ = playlist->addMedia(QMediaContent(url));
if (!succ) {
qDebug("!!!!!!!!! break point time !!!!!!!!!");
}
}
QMediaPlaylist* playlist = m_playlistModel->playlist();
playlist->clear();
playlist->addMedia(urlList);
connect(playlist, &QMediaPlaylist::playbackModeChanged, this, [=](QMediaPlaylist::PlaybackMode mode) {
switch (mode) {
@ -274,19 +266,16 @@ QMediaPlaylist *MainWindow::createPlaylist(QList<QUrl> urlList)
case QMediaPlaylist::Sequential:
ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-normal.png"));
break;
case QMediaPlaylist::Random:
ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-shuffle.png"));
break;
// case QMediaPlaylist::Random:
// ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-shuffle.png"));
// break;
default:
break;
}
});
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
m_mediaPlayer->setPlaylist(playlist);
m_playlistModel->setPlaylist(playlist);
return playlist;
playlist->setCurrentIndex(0);
}
void MainWindow::centerWindow()
@ -314,8 +303,8 @@ void MainWindow::on_playBtn_clicked()
} else if (m_mediaPlayer->mediaStatus() == QMediaPlayer::InvalidMedia) {
ui->propLabel->setText("Error: InvalidMedia");
} else {
if (QList<QMediaPlayer::State> {QMediaPlayer::PausedState, QMediaPlayer::StoppedState}
.contains(m_mediaPlayer->state())) {
if (QList<QMediaPlayer::PlaybackState> {QMediaPlayer::PausedState, QMediaPlayer::StoppedState}
.contains(m_mediaPlayer->playbackState())) {
m_mediaPlayer->play();
} else {
m_mediaPlayer->pause();
@ -348,10 +337,10 @@ QList<QUrl> MainWindow::strlst2urllst(QStringList strlst)
void MainWindow::on_volumeSlider_valueChanged(int value)
{
if (m_mediaPlayer->isMuted()) {
m_mediaPlayer->setMuted(false);
if (m_audioOutput->isMuted()) {
m_audioOutput->setMuted(false);
}
m_mediaPlayer->setVolume(value);
m_audioOutput->setVolume(value);
}
void MainWindow::on_stopBtn_clicked()
@ -371,30 +360,30 @@ void MainWindow::on_prevBtn_clicked()
{
// QMediaPlaylist::previous() won't work when in CurrentItemInLoop playmode,
// and also works not as intended when in other playmode, so do it manually...
QMediaPlaylist * playlist = m_mediaPlayer->playlist();
QMediaPlaylist * playlist = m_playlistModel->playlist();
if (playlist) {
int index = playlist->currentIndex();
int count = playlist->mediaCount();
m_mediaPlayer->playlist()->setCurrentIndex(index == 0 ? count - 1 : index - 1);
playlist->setCurrentIndex(index == 0 ? count - 1 : index - 1);
}
}
void MainWindow::on_nextBtn_clicked()
{
// see also: MainWindow::on_prevBtn_clicked()
QMediaPlaylist * playlist = m_mediaPlayer->playlist();
QMediaPlaylist * playlist = m_playlistModel->playlist();
if (playlist) {
int index = playlist->currentIndex();
int count = playlist->mediaCount();
m_mediaPlayer->playlist()->setCurrentIndex(index == (count - 1) ? 0 : index + 1);
playlist->setCurrentIndex(index == (count - 1) ? 0 : index + 1);
}
}
void MainWindow::on_volumeBtn_clicked()
{
m_mediaPlayer->setMuted(!m_mediaPlayer->isMuted());
m_audioOutput->setMuted(!m_audioOutput->isMuted());
}
void MainWindow::on_minimumWindowBtn_clicked()
@ -424,13 +413,12 @@ void MainWindow::initUiAndAnimation()
void MainWindow::initConnections()
{
connect(m_mediaPlayer, &QMediaPlayer::currentMediaChanged, this, [=](const QMediaContent &media) {
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QUrl fileUrl = media.canonicalUrl();
#else
QUrl fileUrl = media.request().url();
#endif // QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
connect(m_playlistModel->playlist(), &QMediaPlaylist::currentIndexChanged, this, [=](int currentItem) {
bool isPlaying = m_mediaPlayer->playbackState() == QMediaPlayer::PlayingState;
m_mediaPlayer->setSource(m_playlistModel->playlist()->currentMedia());
if (isPlaying) m_mediaPlayer->play();
});
connect(m_playlistModel->playlist(), &QMediaPlaylist::currentMediaChanged, this, [=](const QUrl &fileUrl) {
ui->titleLabel->setText(fileUrl.fileName());
ui->titleLabel->setToolTip(fileUrl.fileName());
@ -489,7 +477,7 @@ void MainWindow::initConnections()
}
});
connect(m_mediaPlayer, &QMediaPlayer::mutedChanged, this, [=](bool muted) {
connect(m_audioOutput, &QAudioOutput::mutedChanged, this, [=](bool muted) {
if (muted) {
ui->volumeBtn->setIcon(QIcon(":/icons/icons/audio-volume-muted.png"));
} else {
@ -501,7 +489,7 @@ void MainWindow::initConnections()
ui->totalTimeLabel->setText(ms2str(dua));
});
connect(m_mediaPlayer, &QMediaPlayer::stateChanged, this, [=](QMediaPlayer::State newState) {
connect(m_mediaPlayer, &QMediaPlayer::playbackStateChanged, this, [=](QMediaPlayer::PlaybackState newState) {
switch (newState) {
case QMediaPlayer::PlayingState:
ui->playBtn->setIcon(QIcon(":/icons/icons/media-playback-pause.png"));
@ -513,23 +501,23 @@ void MainWindow::initConnections()
}
});
connect(m_mediaPlayer, &QMediaPlayer::volumeChanged, this, [=](int vol) {
connect(m_audioOutput, &QAudioOutput::volumeChanged, this, [=](int vol) {
ui->volumeSlider->setValue(vol);
});
connect(m_mediaPlayer, static_cast<void(QMediaPlayer::*)(QMediaPlayer::Error)>(&QMediaPlayer::error),
this, [=](QMediaPlayer::Error error) {
switch (error) {
default:
break;
}
qDebug("%s aaaaaaaaaaaaa", m_mediaPlayer->errorString().toUtf8().data());
});
// connect(m_mediaPlayer, static_cast<void(QMediaPlayer::*)(QMediaPlayer::Error)>(&QMediaPlayer::error),
// this, [=](QMediaPlayer::Error error) {
// switch (error) {
// default:
// break;
// }
// qDebug("%s aaaaaaaaaaaaa", m_mediaPlayer->errorString().toUtf8().data());
// });
}
void MainWindow::on_playbackModeBtn_clicked()
{
QMediaPlaylist * playlist = m_mediaPlayer->playlist();
QMediaPlaylist * playlist = m_playlistModel->playlist();
if (!playlist) return;
switch (playlist->playbackMode()) {
@ -540,11 +528,11 @@ void MainWindow::on_playbackModeBtn_clicked()
playlist->setPlaybackMode(QMediaPlaylist::Sequential);
break;
case QMediaPlaylist::Sequential:
playlist->setPlaybackMode(QMediaPlaylist::Random);
break;
case QMediaPlaylist::Random:
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
break;
// case QMediaPlaylist::Random:
// playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
// break;
default:
break;
}

View File

@ -8,7 +8,7 @@ QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
class QMediaPlayer;
class QMediaPlaylist;
class QAudioOutput;
class QPropertyAnimation;
QT_END_NAMESPACE
@ -40,7 +40,7 @@ protected:
void loadFile();
void centerWindow();
QMediaPlaylist *createPlaylist(QList<QUrl> urlList);
void createPlaylist(QList<QUrl> urlList);
private slots:
void on_playbackModeBtn_clicked();
@ -63,6 +63,7 @@ private:
Ui::MainWindow *ui;
QMediaPlayer *m_mediaPlayer;
QAudioOutput *m_audioOutput;
QPropertyAnimation *m_fadeOutAnimation;
PlaylistModel *m_playlistModel = nullptr; // TODO: move playback logic to player.cpp

View File

@ -1,67 +1,24 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** 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.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "playlistmodel.h"
#include "qt/qmediaplaylist.h"
#include <QFileInfo>
#include <QUrl>
#include <QMediaPlaylist>
PlaylistModel::PlaylistModel(QObject *parent)
: QAbstractItemModel(parent)
{
m_playlist.reset(new QMediaPlaylist);
connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems);
connect(m_playlist.data(), &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems);
connect(m_playlist.data(), &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems);
connect(m_playlist.data(), &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems);
connect(m_playlist.data(), &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems);
}
PlaylistModel::~PlaylistModel()
{
}
PlaylistModel::~PlaylistModel() = default;
int PlaylistModel::rowCount(const QModelIndex &parent) const
{
@ -94,11 +51,7 @@ QVariant PlaylistModel::data(const QModelIndex &index, int role) const
if (index.isValid() && role == Qt::DisplayRole) {
QVariant value = m_data[index];
if (!value.isValid() && index.column() == Title) {
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
QUrl location = m_playlist->media(index.row()).canonicalUrl();
#else
QUrl location = m_playlist->media(index.row()).request().url();
#endif
QUrl location = m_playlist->media(index.row());
return QFileInfo(location.path()).fileName();
}
@ -109,31 +62,7 @@ QVariant PlaylistModel::data(const QModelIndex &index, int role) const
QMediaPlaylist *PlaylistModel::playlist() const
{
return m_playlist;
}
void PlaylistModel::setPlaylist(QMediaPlaylist *playlist)
{
if (m_playlist) {
disconnect(m_playlist, &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems);
disconnect(m_playlist, &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems);
disconnect(m_playlist, &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems);
disconnect(m_playlist, &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems);
disconnect(m_playlist, &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems);
}
beginResetModel();
m_playlist = playlist;
if (m_playlist) {
connect(m_playlist, &QMediaPlaylist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems);
connect(m_playlist, &QMediaPlaylist::mediaInserted, this, &PlaylistModel::endInsertItems);
connect(m_playlist, &QMediaPlaylist::mediaAboutToBeRemoved, this, &PlaylistModel::beginRemoveItems);
connect(m_playlist, &QMediaPlaylist::mediaRemoved, this, &PlaylistModel::endRemoveItems);
connect(m_playlist, &QMediaPlaylist::mediaChanged, this, &PlaylistModel::changeItems);
}
endResetModel();
return m_playlist.data();
}
bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role)

View File

@ -1,59 +1,15 @@
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** 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.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
// Copyright (C) 2017 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef PLAYLISTMODEL_H
#define PLAYLISTMODEL_H
#include <QAbstractItemModel>
#include <QScopedPointer>
QT_BEGIN_NAMESPACE
class QMediaPlaylist;
QT_END_NAMESPACE
class PlaylistModel : public QAbstractItemModel
{
@ -67,7 +23,7 @@ public:
};
explicit PlaylistModel(QObject *parent = nullptr);
~PlaylistModel() override;
~PlaylistModel();
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
@ -78,7 +34,6 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QMediaPlaylist *playlist() const;
void setPlaylist(QMediaPlaylist *playlist);
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
@ -90,7 +45,7 @@ private slots:
void changeItems(int start, int end);
private:
QMediaPlaylist * m_playlist = nullptr;
QScopedPointer<QMediaPlaylist> m_playlist;
QMap<QModelIndex, QVariant> m_data;
};

653
qt/qmediaplaylist.cpp Normal file
View 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
View 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
View 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
View 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

View 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

View File

@ -1,5 +1,6 @@
#include "singleapplicationmanager.h"
#include <QVariant>
#include <QDataStream>
#include <QLocalServer>
#include <QLocalSocket>