chore: port to Qt 6
This commit is contained in:
parent
95eddb3af0
commit
7744d3bf2d
|
@ -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)
|
||||
|
|
|
@ -19,7 +19,6 @@ ShadowPower 于2014/8/1 夜间
|
|||
#include <cstring>
|
||||
|
||||
typedef unsigned char byte;
|
||||
using namespace std;
|
||||
|
||||
namespace spFLAC {
|
||||
//Flac元数据块头部结构体定义
|
||||
|
|
|
@ -19,7 +19,6 @@ ShadowPower 于2014/8/1 上午
|
|||
#include <cstring>
|
||||
|
||||
typedef unsigned char byte;
|
||||
using namespace std;
|
||||
|
||||
namespace spID3 {
|
||||
//ID3v2标签头部结构体定义
|
||||
|
|
13
appveyor.yml
13
appveyor.yml
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
102
mainwindow.cpp
102
mainwindow.cpp
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
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
|
|
@ -1,5 +1,6 @@
|
|||
#include "singleapplicationmanager.h"
|
||||
|
||||
#include <QVariant>
|
||||
#include <QDataStream>
|
||||
#include <QLocalServer>
|
||||
#include <QLocalSocket>
|
||||
|
|
Loading…
Reference in New Issue
Block a user