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)
|
project(pineapple-music LANGUAGES CXX)
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ set(CMAKE_AUTOUIC ON)
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
set(CMAKE_CXX_STANDARD 11)
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
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)
|
find_package(PkgConfig)
|
||||||
|
|
||||||
if (PKG_CONFIG_FOUND)
|
if (PKG_CONFIG_FOUND)
|
||||||
|
@ -26,6 +26,9 @@ set (PMUSIC_CPP_FILES
|
||||||
seekableslider.cpp
|
seekableslider.cpp
|
||||||
playlistmodel.cpp
|
playlistmodel.cpp
|
||||||
singleapplicationmanager.cpp
|
singleapplicationmanager.cpp
|
||||||
|
|
||||||
|
qt/qplaylistfileparser.cpp
|
||||||
|
qt/qmediaplaylist.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set (PMUSIC_HEADER_FILES
|
set (PMUSIC_HEADER_FILES
|
||||||
|
@ -33,6 +36,10 @@ set (PMUSIC_HEADER_FILES
|
||||||
seekableslider.h
|
seekableslider.h
|
||||||
playlistmodel.h
|
playlistmodel.h
|
||||||
singleapplicationmanager.h
|
singleapplicationmanager.h
|
||||||
|
|
||||||
|
qt/qplaylistfileparser_p.h
|
||||||
|
qt/qmediaplaylist.h
|
||||||
|
qt/qmediaplaylist_p.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set (PMUSIC_UI_FILES
|
set (PMUSIC_UI_FILES
|
||||||
|
@ -45,7 +52,7 @@ set (EXE_NAME pmusic)
|
||||||
file (GLOB PMUSIC_TS_FILES languages/*.ts)
|
file (GLOB PMUSIC_TS_FILES languages/*.ts)
|
||||||
set (PMUSIC_CPP_FILES_FOR_I18N ${PMUSIC_CPP_FILES} ${PMUSIC_UI_FILES})
|
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}
|
add_executable(${EXE_NAME}
|
||||||
${PMUSIC_HEADER_FILES}
|
${PMUSIC_HEADER_FILES}
|
||||||
|
@ -68,7 +75,7 @@ if (NOT TagLib_FOUND)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
target_include_directories(${EXE_NAME} PRIVATE ${TagLib_INCLUDE_DIRS})
|
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
|
# Extra build settings
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
|
|
|
@ -19,7 +19,6 @@ ShadowPower 于2014/8/1 夜间
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
typedef unsigned char byte;
|
typedef unsigned char byte;
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace spFLAC {
|
namespace spFLAC {
|
||||||
//Flac元数据块头部结构体定义
|
//Flac元数据块头部结构体定义
|
||||||
|
|
|
@ -19,7 +19,6 @@ ShadowPower 于2014/8/1 上午
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
typedef unsigned char byte;
|
typedef unsigned char byte;
|
||||||
using namespace std;
|
|
||||||
|
|
||||||
namespace spID3 {
|
namespace spID3 {
|
||||||
//ID3v2标签头部结构体定义
|
//ID3v2标签头部结构体定义
|
||||||
|
|
15
appveyor.yml
15
appveyor.yml
|
@ -5,16 +5,16 @@ environment:
|
||||||
PACKAGE_INSTALL_ROOT: C:\projects\pir
|
PACKAGE_INSTALL_ROOT: C:\projects\pir
|
||||||
PKG_CONFIG_PATH: C:\projects\pir\lib\pkgconfig
|
PKG_CONFIG_PATH: C:\projects\pir\lib\pkgconfig
|
||||||
matrix:
|
matrix:
|
||||||
- build_name: mingw81_64_qt5_15_2
|
- build_name: mingw1120_64_qt6_4
|
||||||
QTPATH: C:\Qt\5.15.2\mingw81_64
|
QTPATH: C:\Qt\6.4\mingw_64
|
||||||
MINGW64: C:\Qt\Tools\mingw810_64
|
MINGW64: C:\Qt\Tools\mingw1120_64
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- mkdir %CMAKE_INSTALL_ROOT%
|
- mkdir %CMAKE_INSTALL_ROOT%
|
||||||
- mkdir %PACKAGE_INSTALL_ROOT%
|
- mkdir %PACKAGE_INSTALL_ROOT%
|
||||||
- cd %APPVEYOR_BUILD_FOLDER%
|
- cd %APPVEYOR_BUILD_FOLDER%
|
||||||
- git submodule update --init --recursive
|
- 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 CC=%MINGW64%\bin\gcc.exe
|
||||||
- set CXX=%MINGW64%\bin\g++.exe
|
- set CXX=%MINGW64%\bin\g++.exe
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ build_script:
|
||||||
# build taglib
|
# build taglib
|
||||||
- cd 3rdparty
|
- cd 3rdparty
|
||||||
- git clone -q https://github.com/taglib/taglib.git
|
- git clone -q https://github.com/taglib/taglib.git
|
||||||
- cd taglib
|
- cd taglib
|
||||||
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%PACKAGE_INSTALL_ROOT% -DBUILD_SHARED_LIBS=ON
|
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%PACKAGE_INSTALL_ROOT% -DBUILD_SHARED_LIBS=ON
|
||||||
- cmake --build .
|
- cmake --build .
|
||||||
- cmake --build . --target install
|
- cmake --build . --target install
|
||||||
|
@ -40,8 +40,11 @@ build_script:
|
||||||
- cmake --build . --target install
|
- cmake --build . --target install
|
||||||
# fixme: I don't know how to NOT make the binary installed to the ./bin/ folder...
|
# fixme: I don't know how to NOT make the binary installed to the ./bin/ folder...
|
||||||
- cd bin
|
- 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%
|
- 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..
|
# for debug..
|
||||||
- tree /f
|
- tree /f
|
||||||
|
|
||||||
|
|
|
@ -4,61 +4,109 @@
|
||||||
<context>
|
<context>
|
||||||
<name>MainWindow</name>
|
<name>MainWindow</name>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="106"/>
|
||||||
<source>Mono</source>
|
<source>Mono</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="108"/>
|
||||||
<source>Stereo</source>
|
<source>Stereo</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="110"/>
|
||||||
<source>%1 Channels</source>
|
<source>%1 Channels</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="238"/>
|
||||||
<source>Select songs to play</source>
|
<source>Select songs to play</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="240"/>
|
||||||
<source>Audio Files</source>
|
<source>Audio Files</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="23"/>
|
||||||
<source>Pineapple Player</source>
|
<source>Pineapple Player</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="231"/>
|
||||||
<source>^</source>
|
<source>^</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="297"/>
|
||||||
<source>No song loaded...</source>
|
<source>No song loaded...</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="304"/>
|
||||||
<source>Drag and drop file to load</source>
|
<source>Drag and drop file to load</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="313"/>
|
||||||
|
<location filename="../mainwindow.ui" line="320"/>
|
||||||
<source>0:00</source>
|
<source>0:00</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="116"/>
|
||||||
<source>Sample Rate: %1 Hz</source>
|
<source>Sample Rate: %1 Hz</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="121"/>
|
||||||
<source>Bitrate: %1 Kbps</source>
|
<source>Bitrate: %1 Kbps</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="126"/>
|
||||||
<source>Channel Count: %1</source>
|
<source>Channel Count: %1</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</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>
|
<context>
|
||||||
<name>main</name>
|
<name>main</name>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../main.cpp" line="28"/>
|
||||||
<source>File list.</source>
|
<source>File list.</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
|
|
|
@ -4,61 +4,109 @@
|
||||||
<context>
|
<context>
|
||||||
<name>MainWindow</name>
|
<name>MainWindow</name>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="106"/>
|
||||||
<source>Mono</source>
|
<source>Mono</source>
|
||||||
<translation>单声道</translation>
|
<translation>单声道</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="108"/>
|
||||||
<source>Stereo</source>
|
<source>Stereo</source>
|
||||||
<translation>立体声</translation>
|
<translation>立体声</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="110"/>
|
||||||
<source>%1 Channels</source>
|
<source>%1 Channels</source>
|
||||||
<translation>%1 声道</translation>
|
<translation>%1 声道</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="238"/>
|
||||||
<source>Select songs to play</source>
|
<source>Select songs to play</source>
|
||||||
<translation>选择要播放的曲目</translation>
|
<translation>选择要播放的曲目</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="240"/>
|
||||||
<source>Audio Files</source>
|
<source>Audio Files</source>
|
||||||
<translation>音频文件</translation>
|
<translation>音频文件</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="23"/>
|
||||||
<source>Pineapple Player</source>
|
<source>Pineapple Player</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="231"/>
|
||||||
<source>^</source>
|
<source>^</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="297"/>
|
||||||
<source>No song loaded...</source>
|
<source>No song loaded...</source>
|
||||||
<translation>未加载曲目...</translation>
|
<translation>未加载曲目...</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="304"/>
|
||||||
<source>Drag and drop file to load</source>
|
<source>Drag and drop file to load</source>
|
||||||
<translation>拖放文件来播放</translation>
|
<translation>拖放文件来播放</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.ui" line="313"/>
|
||||||
|
<location filename="../mainwindow.ui" line="320"/>
|
||||||
<source>0:00</source>
|
<source>0:00</source>
|
||||||
<translation type="unfinished"></translation>
|
<translation type="unfinished"></translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="116"/>
|
||||||
<source>Sample Rate: %1 Hz</source>
|
<source>Sample Rate: %1 Hz</source>
|
||||||
<translation>采样率: %1 Hz</translation>
|
<translation>采样率: %1 Hz</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="121"/>
|
||||||
<source>Bitrate: %1 Kbps</source>
|
<source>Bitrate: %1 Kbps</source>
|
||||||
<translation>比特率: %1 Kbps</translation>
|
<translation>比特率: %1 Kbps</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../mainwindow.cpp" line="126"/>
|
||||||
<source>Channel Count: %1</source>
|
<source>Channel Count: %1</source>
|
||||||
<translation>声道数: %1</translation>
|
<translation>声道数: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</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>
|
<context>
|
||||||
<name>main</name>
|
<name>main</name>
|
||||||
<message>
|
<message>
|
||||||
|
<location filename="../main.cpp" line="28"/>
|
||||||
<source>File list.</source>
|
<source>File list.</source>
|
||||||
<translation>文件列表</translation>
|
<translation>文件列表</translation>
|
||||||
</message>
|
</message>
|
||||||
|
|
102
mainwindow.cpp
102
mainwindow.cpp
|
@ -2,6 +2,7 @@
|
||||||
#include "./ui_mainwindow.h"
|
#include "./ui_mainwindow.h"
|
||||||
|
|
||||||
#include "playlistmodel.h"
|
#include "playlistmodel.h"
|
||||||
|
#include "qt/qmediaplaylist.h"
|
||||||
|
|
||||||
#include "ID3v2Pic.h"
|
#include "ID3v2Pic.h"
|
||||||
#include "FlacPic.h"
|
#include "FlacPic.h"
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
#include <QMediaPlaylist>
|
#include <QAudioOutput>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QTime>
|
#include <QTime>
|
||||||
|
@ -27,10 +28,13 @@ MainWindow::MainWindow(QWidget *parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
, ui(new Ui::MainWindow)
|
, ui(new Ui::MainWindow)
|
||||||
, m_mediaPlayer(new QMediaPlayer(this))
|
, m_mediaPlayer(new QMediaPlayer(this))
|
||||||
|
, m_audioOutput(new QAudioOutput(this))
|
||||||
, m_playlistModel(new PlaylistModel(this))
|
, m_playlistModel(new PlaylistModel(this))
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
m_mediaPlayer->setAudioOutput(m_audioOutput);
|
||||||
|
|
||||||
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);
|
this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);
|
||||||
this->setAttribute(Qt::WA_TranslucentBackground, true);
|
this->setAttribute(Qt::WA_TranslucentBackground, true);
|
||||||
|
|
||||||
|
@ -88,8 +92,7 @@ void MainWindow::loadPlaylistBySingleLocalFile(const QString &path)
|
||||||
currentFileIndex = 0;
|
currentFileIndex = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
QMediaPlaylist * playlist = createPlaylist(urlList);
|
createPlaylist(urlList);
|
||||||
playlist->setCurrentIndex(currentFileIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setAudioPropertyInfoForDisplay(int sampleRate, int bitrate, int channelCount, QString audioExt)
|
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.
|
* 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 = m_playlistModel->playlist();
|
||||||
QMediaPlaylist * playlist = new QMediaPlaylist(m_mediaPlayer);
|
playlist->clear();
|
||||||
|
playlist->addMedia(urlList);
|
||||||
if (oldPlaylist) {
|
|
||||||
oldPlaylist->disconnect();
|
|
||||||
oldPlaylist->deleteLater();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const QUrl & url : urlList) {
|
|
||||||
bool succ = playlist->addMedia(QMediaContent(url));
|
|
||||||
if (!succ) {
|
|
||||||
qDebug("!!!!!!!!! break point time !!!!!!!!!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(playlist, &QMediaPlaylist::playbackModeChanged, this, [=](QMediaPlaylist::PlaybackMode mode) {
|
connect(playlist, &QMediaPlaylist::playbackModeChanged, this, [=](QMediaPlaylist::PlaybackMode mode) {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
@ -274,19 +266,16 @@ QMediaPlaylist *MainWindow::createPlaylist(QList<QUrl> urlList)
|
||||||
case QMediaPlaylist::Sequential:
|
case QMediaPlaylist::Sequential:
|
||||||
ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-normal.png"));
|
ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-normal.png"));
|
||||||
break;
|
break;
|
||||||
case QMediaPlaylist::Random:
|
// case QMediaPlaylist::Random:
|
||||||
ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-shuffle.png"));
|
// ui->playbackModeBtn->setIcon(QIcon(":/icons/icons/media-playlist-shuffle.png"));
|
||||||
break;
|
// break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
|
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
|
||||||
m_mediaPlayer->setPlaylist(playlist);
|
playlist->setCurrentIndex(0);
|
||||||
m_playlistModel->setPlaylist(playlist);
|
|
||||||
|
|
||||||
return playlist;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::centerWindow()
|
void MainWindow::centerWindow()
|
||||||
|
@ -314,8 +303,8 @@ void MainWindow::on_playBtn_clicked()
|
||||||
} else if (m_mediaPlayer->mediaStatus() == QMediaPlayer::InvalidMedia) {
|
} else if (m_mediaPlayer->mediaStatus() == QMediaPlayer::InvalidMedia) {
|
||||||
ui->propLabel->setText("Error: InvalidMedia");
|
ui->propLabel->setText("Error: InvalidMedia");
|
||||||
} else {
|
} else {
|
||||||
if (QList<QMediaPlayer::State> {QMediaPlayer::PausedState, QMediaPlayer::StoppedState}
|
if (QList<QMediaPlayer::PlaybackState> {QMediaPlayer::PausedState, QMediaPlayer::StoppedState}
|
||||||
.contains(m_mediaPlayer->state())) {
|
.contains(m_mediaPlayer->playbackState())) {
|
||||||
m_mediaPlayer->play();
|
m_mediaPlayer->play();
|
||||||
} else {
|
} else {
|
||||||
m_mediaPlayer->pause();
|
m_mediaPlayer->pause();
|
||||||
|
@ -348,10 +337,10 @@ QList<QUrl> MainWindow::strlst2urllst(QStringList strlst)
|
||||||
|
|
||||||
void MainWindow::on_volumeSlider_valueChanged(int value)
|
void MainWindow::on_volumeSlider_valueChanged(int value)
|
||||||
{
|
{
|
||||||
if (m_mediaPlayer->isMuted()) {
|
if (m_audioOutput->isMuted()) {
|
||||||
m_mediaPlayer->setMuted(false);
|
m_audioOutput->setMuted(false);
|
||||||
}
|
}
|
||||||
m_mediaPlayer->setVolume(value);
|
m_audioOutput->setVolume(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_stopBtn_clicked()
|
void MainWindow::on_stopBtn_clicked()
|
||||||
|
@ -371,30 +360,30 @@ void MainWindow::on_prevBtn_clicked()
|
||||||
{
|
{
|
||||||
// QMediaPlaylist::previous() won't work when in CurrentItemInLoop playmode,
|
// QMediaPlaylist::previous() won't work when in CurrentItemInLoop playmode,
|
||||||
// and also works not as intended when in other playmode, so do it manually...
|
// 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) {
|
if (playlist) {
|
||||||
int index = playlist->currentIndex();
|
int index = playlist->currentIndex();
|
||||||
int count = playlist->mediaCount();
|
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()
|
void MainWindow::on_nextBtn_clicked()
|
||||||
{
|
{
|
||||||
// see also: MainWindow::on_prevBtn_clicked()
|
// see also: MainWindow::on_prevBtn_clicked()
|
||||||
QMediaPlaylist * playlist = m_mediaPlayer->playlist();
|
QMediaPlaylist * playlist = m_playlistModel->playlist();
|
||||||
if (playlist) {
|
if (playlist) {
|
||||||
int index = playlist->currentIndex();
|
int index = playlist->currentIndex();
|
||||||
int count = playlist->mediaCount();
|
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()
|
void MainWindow::on_volumeBtn_clicked()
|
||||||
{
|
{
|
||||||
m_mediaPlayer->setMuted(!m_mediaPlayer->isMuted());
|
m_audioOutput->setMuted(!m_audioOutput->isMuted());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_minimumWindowBtn_clicked()
|
void MainWindow::on_minimumWindowBtn_clicked()
|
||||||
|
@ -424,13 +413,12 @@ void MainWindow::initUiAndAnimation()
|
||||||
|
|
||||||
void MainWindow::initConnections()
|
void MainWindow::initConnections()
|
||||||
{
|
{
|
||||||
connect(m_mediaPlayer, &QMediaPlayer::currentMediaChanged, this, [=](const QMediaContent &media) {
|
connect(m_playlistModel->playlist(), &QMediaPlaylist::currentIndexChanged, this, [=](int currentItem) {
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
bool isPlaying = m_mediaPlayer->playbackState() == QMediaPlayer::PlayingState;
|
||||||
QUrl fileUrl = media.canonicalUrl();
|
m_mediaPlayer->setSource(m_playlistModel->playlist()->currentMedia());
|
||||||
#else
|
if (isPlaying) m_mediaPlayer->play();
|
||||||
QUrl fileUrl = media.request().url();
|
});
|
||||||
#endif // QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
connect(m_playlistModel->playlist(), &QMediaPlaylist::currentMediaChanged, this, [=](const QUrl &fileUrl) {
|
||||||
|
|
||||||
ui->titleLabel->setText(fileUrl.fileName());
|
ui->titleLabel->setText(fileUrl.fileName());
|
||||||
ui->titleLabel->setToolTip(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) {
|
if (muted) {
|
||||||
ui->volumeBtn->setIcon(QIcon(":/icons/icons/audio-volume-muted.png"));
|
ui->volumeBtn->setIcon(QIcon(":/icons/icons/audio-volume-muted.png"));
|
||||||
} else {
|
} else {
|
||||||
|
@ -501,7 +489,7 @@ void MainWindow::initConnections()
|
||||||
ui->totalTimeLabel->setText(ms2str(dua));
|
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) {
|
switch (newState) {
|
||||||
case QMediaPlayer::PlayingState:
|
case QMediaPlayer::PlayingState:
|
||||||
ui->playBtn->setIcon(QIcon(":/icons/icons/media-playback-pause.png"));
|
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);
|
ui->volumeSlider->setValue(vol);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_mediaPlayer, static_cast<void(QMediaPlayer::*)(QMediaPlayer::Error)>(&QMediaPlayer::error),
|
// connect(m_mediaPlayer, static_cast<void(QMediaPlayer::*)(QMediaPlayer::Error)>(&QMediaPlayer::error),
|
||||||
this, [=](QMediaPlayer::Error error) {
|
// this, [=](QMediaPlayer::Error error) {
|
||||||
switch (error) {
|
// switch (error) {
|
||||||
default:
|
// default:
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
qDebug("%s aaaaaaaaaaaaa", m_mediaPlayer->errorString().toUtf8().data());
|
// qDebug("%s aaaaaaaaaaaaa", m_mediaPlayer->errorString().toUtf8().data());
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::on_playbackModeBtn_clicked()
|
void MainWindow::on_playbackModeBtn_clicked()
|
||||||
{
|
{
|
||||||
QMediaPlaylist * playlist = m_mediaPlayer->playlist();
|
QMediaPlaylist * playlist = m_playlistModel->playlist();
|
||||||
if (!playlist) return;
|
if (!playlist) return;
|
||||||
|
|
||||||
switch (playlist->playbackMode()) {
|
switch (playlist->playbackMode()) {
|
||||||
|
@ -540,11 +528,11 @@ void MainWindow::on_playbackModeBtn_clicked()
|
||||||
playlist->setPlaybackMode(QMediaPlaylist::Sequential);
|
playlist->setPlaybackMode(QMediaPlaylist::Sequential);
|
||||||
break;
|
break;
|
||||||
case QMediaPlaylist::Sequential:
|
case QMediaPlaylist::Sequential:
|
||||||
playlist->setPlaybackMode(QMediaPlaylist::Random);
|
|
||||||
break;
|
|
||||||
case QMediaPlaylist::Random:
|
|
||||||
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
|
playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
|
||||||
break;
|
break;
|
||||||
|
// case QMediaPlaylist::Random:
|
||||||
|
// playlist->setPlaybackMode(QMediaPlaylist::CurrentItemInLoop);
|
||||||
|
// break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ QT_BEGIN_NAMESPACE
|
||||||
namespace Ui { class MainWindow; }
|
namespace Ui { class MainWindow; }
|
||||||
|
|
||||||
class QMediaPlayer;
|
class QMediaPlayer;
|
||||||
class QMediaPlaylist;
|
class QAudioOutput;
|
||||||
class QPropertyAnimation;
|
class QPropertyAnimation;
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ protected:
|
||||||
|
|
||||||
void loadFile();
|
void loadFile();
|
||||||
void centerWindow();
|
void centerWindow();
|
||||||
QMediaPlaylist *createPlaylist(QList<QUrl> urlList);
|
void createPlaylist(QList<QUrl> urlList);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void on_playbackModeBtn_clicked();
|
void on_playbackModeBtn_clicked();
|
||||||
|
@ -63,6 +63,7 @@ private:
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
|
||||||
QMediaPlayer *m_mediaPlayer;
|
QMediaPlayer *m_mediaPlayer;
|
||||||
|
QAudioOutput *m_audioOutput;
|
||||||
QPropertyAnimation *m_fadeOutAnimation;
|
QPropertyAnimation *m_fadeOutAnimation;
|
||||||
PlaylistModel *m_playlistModel = nullptr; // TODO: move playback logic to player.cpp
|
PlaylistModel *m_playlistModel = nullptr; // TODO: move playback logic to player.cpp
|
||||||
|
|
||||||
|
|
|
@ -1,67 +1,24 @@
|
||||||
/****************************************************************************
|
// Copyright (C) 2017 The Qt Company Ltd.
|
||||||
**
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
** 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$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#include "playlistmodel.h"
|
#include "playlistmodel.h"
|
||||||
|
#include "qt/qmediaplaylist.h"
|
||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
#include <QMediaPlaylist>
|
|
||||||
|
|
||||||
PlaylistModel::PlaylistModel(QObject *parent)
|
PlaylistModel::PlaylistModel(QObject *parent)
|
||||||
: QAbstractItemModel(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
|
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) {
|
if (index.isValid() && role == Qt::DisplayRole) {
|
||||||
QVariant value = m_data[index];
|
QVariant value = m_data[index];
|
||||||
if (!value.isValid() && index.column() == Title) {
|
if (!value.isValid() && index.column() == Title) {
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
QUrl location = m_playlist->media(index.row());
|
||||||
QUrl location = m_playlist->media(index.row()).canonicalUrl();
|
|
||||||
#else
|
|
||||||
QUrl location = m_playlist->media(index.row()).request().url();
|
|
||||||
#endif
|
|
||||||
return QFileInfo(location.path()).fileName();
|
return QFileInfo(location.path()).fileName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,31 +62,7 @@ QVariant PlaylistModel::data(const QModelIndex &index, int role) const
|
||||||
|
|
||||||
QMediaPlaylist *PlaylistModel::playlist() const
|
QMediaPlaylist *PlaylistModel::playlist() const
|
||||||
{
|
{
|
||||||
return m_playlist;
|
return m_playlist.data();
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
bool PlaylistModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||||
|
|
|
@ -1,59 +1,15 @@
|
||||||
/****************************************************************************
|
// Copyright (C) 2017 The Qt Company Ltd.
|
||||||
**
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||||
** 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$
|
|
||||||
**
|
|
||||||
****************************************************************************/
|
|
||||||
|
|
||||||
#ifndef PLAYLISTMODEL_H
|
#ifndef PLAYLISTMODEL_H
|
||||||
#define PLAYLISTMODEL_H
|
#define PLAYLISTMODEL_H
|
||||||
|
|
||||||
#include <QAbstractItemModel>
|
#include <QAbstractItemModel>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
|
||||||
|
QT_BEGIN_NAMESPACE
|
||||||
class QMediaPlaylist;
|
class QMediaPlaylist;
|
||||||
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
class PlaylistModel : public QAbstractItemModel
|
class PlaylistModel : public QAbstractItemModel
|
||||||
{
|
{
|
||||||
|
@ -67,7 +23,7 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit PlaylistModel(QObject *parent = nullptr);
|
explicit PlaylistModel(QObject *parent = nullptr);
|
||||||
~PlaylistModel() override;
|
~PlaylistModel();
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
int columnCount(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;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
QMediaPlaylist *playlist() const;
|
QMediaPlaylist *playlist() const;
|
||||||
void setPlaylist(QMediaPlaylist *playlist);
|
|
||||||
|
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override;
|
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);
|
void changeItems(int start, int end);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMediaPlaylist * m_playlist = nullptr;
|
QScopedPointer<QMediaPlaylist> m_playlist;
|
||||||
QMap<QModelIndex, QVariant> m_data;
|
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 "singleapplicationmanager.h"
|
||||||
|
|
||||||
|
#include <QVariant>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QLocalServer>
|
#include <QLocalServer>
|
||||||
#include <QLocalSocket>
|
#include <QLocalSocket>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user