Compare commits

..

7 Commits

Author SHA1 Message Date
a3bc60c977 feat: remember volume 2024-10-21 00:40:34 +08:00
cf90e2d70c feat: initial fft spectrum visualization support
The final goal is actually still clone ShadowPlayer's spectrum
visualization, tho.
2024-10-20 00:21:54 +08:00
64c75263bf buildsystem: use taglib 2's CMake support 2024-10-16 00:47:29 +08:00
5092f9bafc chore: better qt-only msvc build support
* more info in song prop label when qt-only
* editorconfig for msvc (and other IDEs)
* one more audio format
2024-10-15 22:46:35 +08:00
044bb34390 chore(CI): bump msvc build to use Qt 6.8.0 2024-10-12 20:48:08 +08:00
7192c8a1b7 refactor: option to use QTextCodec from Qt5compat 2024-09-29 00:13:42 +08:00
c9fc9346b7 chore: switch to use KCodecs 2024-09-28 16:59:05 +08:00
11 changed files with 342 additions and 61 deletions

7
.editorconfig Normal file
View File

@ -0,0 +1,7 @@
[*]
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

View File

@ -7,9 +7,11 @@ jobs:
strategy:
matrix:
vs: ['2022']
msvc_arch: ['x64']
qt_ver: ['6.7.3']
include:
- qt_ver: '6.8.0'
vs: '2022'
aqt_arch: 'win64_msvc2022_64'
msvc_arch: 'x64'
runs-on: windows-2022
@ -18,9 +20,9 @@ jobs:
- name: Install Qt
uses: jurplel/install-qt-action@v4
with:
arch: 'win64_msvc2019_64'
arch: ${{ matrix.aqt_arch }}
version: ${{ matrix.qt_ver }}
modules: 'qtmultimedia'
modules: 'qtmultimedia qt5compat'
- name: Build
shell: cmd
run: |
@ -33,30 +35,31 @@ jobs:
:: ------ dep ------
set CMAKE_PREFIX_PATH=%PWD%/dependencies_bin
mkdir dependencies_src
:: ===== uchardet =====
echo ::group::build uchardet
git clone -q https://gitlab.freedesktop.org/BLumia/uchardet.git --branch msvc dependencies_src/uchardet
cmake .\dependencies_src\uchardet -Bbuild_dependencies/uchardet -DBUILD_BINARY=OFF -DCMAKE_INSTALL_PREFIX="dependencies_bin" || goto :error
cmake --build build_dependencies/uchardet --config Release --target=install -j || goto :error
echo ::endgroup::
:: ===== pkg-config =====
choco install pkgconfiglite
set PKG_CONFIG_PATH=%PWD%/dependencies_bin/lib/pkgconfig
:: ===== ECM =====
git clone -q https://invent.kde.org/frameworks/extra-cmake-modules.git dependencies_src/extra-cmake-modules
cmake .\dependencies_src\extra-cmake-modules -Bbuild_dependencies/extra-cmake-modules -DCMAKE_INSTALL_PREFIX="dependencies_bin" -DBUILD_TESTING=OFF || goto :error
cmake --build build_dependencies/extra-cmake-modules --config Release --target=install || goto :error
:: ===== Gperf (required by KCodecs) =====
choco install gperf
:: ===== KCodecs =====
git clone -q https://invent.kde.org/frameworks/kcodecs.git dependencies_src/kcodecs
cmake .\dependencies_src\kcodecs -Bbuild_dependencies/kcodecs -DCMAKE_INSTALL_PREFIX="dependencies_bin" -DBUILD_TESTING=OFF || goto :error
cmake --build build_dependencies/kcodecs --config Release --target=install || goto :error
:: ===== taglib =====
git clone --recurse-submodules -q https://github.com/taglib/taglib.git dependencies_src/taglib
cmake .\dependencies_src\taglib -Bbuild_dependencies/taglib -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="dependencies_bin" || goto :error
cmake --build build_dependencies/taglib --config Release --target=install -j || goto :error
:: ------ app ------
cmake -Bbuild . -DCMAKE_INSTALL_PREFIX="%PWD%\build\" || goto :error
cmake -Bbuild . -DUSE_QTEXTCODEC=ON -DCMAKE_INSTALL_PREFIX="%PWD%\build\" || goto :error
cmake --build build --config Release -j || goto :error
cmake --build build --config Release --target=install
cmake --build build --config Release --target=install || goto :error
:: ------ pkg ------
windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-system-d3d-compiler --no-system-dxc-compiler --multimedia --skip-plugin-types tls,networkinformation build\bin\pmusic.exe
windeployqt --verbose=2 --no-quick-import --no-translations --no-opengl-sw --no-system-d3d-compiler --no-system-dxc-compiler --multimedia --core5compat --skip-plugin-types tls,networkinformation build\bin\pmusic.exe
robocopy ./dependencies_bin/bin build/bin *.dll
if ErrorLevel 8 (exit /B 1)
copy LICENSE build/bin/
exit /B 0
- uses: actions/upload-artifact@v4
with:
name: "windows-msvc${{ matrix.vs }}-qt${{ matrix.qt_ver }}-cmake-package"
name: "${{ matrix.aqt_arch }}-qt${{ matrix.qt_ver }}-cmake-package"
path: build/bin/*

View File

@ -4,6 +4,7 @@ project(pineapple-music LANGUAGES CXX)
include (GNUInstallDirs)
include (FeatureSummary)
include (FetchContent)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -14,13 +15,26 @@ set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.6 COMPONENTS Widgets Multimedia Network LinguistTools REQUIRED)
find_package(uchardet)
find_package(PkgConfig)
option(USE_QTEXTCODEC "Use QTextCodec instead of QStringConverter, in case Qt is not built with ICU" OFF)
if (PKG_CONFIG_FOUND)
pkg_check_modules(TagLib taglib IMPORTED_TARGET)
endif ()
find_package(Qt6 6.6 COMPONENTS Widgets Multimedia Network LinguistTools REQUIRED)
find_package(TagLib 2.0.0)
find_package(KF6Codecs 6.1.0)
FetchContent_Declare(
kissfft
GIT_REPOSITORY https://github.com/mborgerding/kissfft.git
GIT_TAG f5f2a3b2f2cd02bf80639adb12cbeed125bdf420
)
set(KISSFFT_PKGCONFIG OFF CACHE BOOL "dep(kissfft): pkgconfig support")
set(KISSFFT_STATIC ON CACHE BOOL "dep(kissfft): static linking")
set(KISSFFT_TEST OFF CACHE BOOL "dep(kissfft): enable testing")
set(KISSFFT_TOOLS OFF CACHE BOOL "dep(kissfft): build tools")
FetchContent_MakeAvailable(kissfft)
if (USE_QTEXTCODEC)
find_package(Qt6 6.6 COMPONENTS Core5Compat REQUIRED)
endif()
set (PMUSIC_CPP_FILES
main.cpp
@ -30,6 +44,7 @@ set (PMUSIC_CPP_FILES
singleapplicationmanager.cpp
lrcbar.cpp
lyricsmanager.cpp
fftspectrum.cpp
)
set (PMUSIC_HEADER_FILES
@ -39,6 +54,7 @@ set (PMUSIC_HEADER_FILES
singleapplicationmanager.h
lrcbar.h
lyricsmanager.h
fftspectrum.h
)
set (PMUSIC_UI_FILES
@ -70,16 +86,20 @@ endif ()
if (NOT TagLib_FOUND)
target_compile_definitions(${EXE_NAME} PRIVATE NO_TAGLIB=1)
else ()
target_link_libraries(${EXE_NAME} PRIVATE PkgConfig::TagLib)
target_link_libraries(${EXE_NAME} PRIVATE TagLib::tag)
endif ()
if (NOT uchardet_FOUND)
target_compile_definitions(${EXE_NAME} PRIVATE NO_UCHARDET=1)
else ()
target_link_libraries (${EXE_NAME} PRIVATE uchardet::libuchardet)
if (TARGET KF6::Codecs)
target_compile_definitions(${EXE_NAME} PRIVATE HAVE_KCODECS=1)
target_link_libraries (${EXE_NAME} PRIVATE KF6::Codecs)
endif ()
target_link_libraries(${EXE_NAME} PRIVATE Qt::Widgets Qt::Multimedia Qt::Network)
target_link_libraries(${EXE_NAME} PRIVATE Qt::Widgets Qt::Multimedia Qt::Network kissfft::kissfft)
if (USE_QTEXTCODEC)
target_compile_definitions(${EXE_NAME} PRIVATE USE_QTEXTCODEC=1)
target_link_libraries(${EXE_NAME} PRIVATE Qt6::Core5Compat)
endif()
# Install settings
if (WIN32)

View File

@ -2,12 +2,23 @@
Since **I** just need a simple player which *just works* right now, so I did many things in a dirty way. Don't feel so weird if you saw something I did in this project is using a bad approach.
### Feature Notice
### Features
We have the following features:
- [Sidecar](https://en.wikipedia.org/wiki/Sidecar_file) lyrics file (`.lrc`) support with an optional desktop lyrics bar widget
- Auto-load all audio files in the same folder of the file that you attempted to play, into a playlist
But these features are not available, some of them are TBD and others are not planned:
- File format support will be limited by the [FFmpeg version that Qt 6 uses](https://doc.qt.io/qt-6/qtmultimedia-attribution-ffmpeg.html).
- ...which if you use Qt's official binary, only contains the LGPLv2.1+ part. (already good enough, tho)
- No music library management support and there won't be one!
- It'll auto-load music files in the same folder of the file that you attempted to play, so organize your music files on a folder-basis.
- Limited system integration:
- No [SMTC](https://learn.microsoft.com/en-us/uwp/api/windows.media.systemmediatransportcontrols) support under Windows for now
- No [MPRIS](https://www.freedesktop.org/wiki/Specifications/mpris-spec/) support under Linux desktop for now
- No "playback progress on taskbar icon" and "taskbar thumbnail buttons" support whatever on Windows or Linux desktop for now
- Limited lyrics (`.lrc`) loading support:
- Currently no `.tlrc` (for translated lyrics) or `.rlrc` (for romanized lyrics) support.
- Multi-line lyrics and duplicated timestamps are not supported
@ -20,7 +31,7 @@ Current state, we need:
- `cmake` as the build system.
- `qt6` with `qt6-multimedia` since we use it for playback.
- `taglib` to get the audio file properties.
- `pkg-config` to find the installed taglib.
- `kissfft` for FFT support (will be downloaded at configure-time by `cmake`).
Then we can build it with any proper c++ compiler like g++ or msvc.

View File

@ -3,7 +3,6 @@ image:
environment:
CMAKE_INSTALL_ROOT: C:\projects\cmake
PACKAGE_INSTALL_ROOT: C:\projects\pir
PKG_CONFIG_PATH: C:\projects\pir\lib\pkgconfig
matrix:
- build_name: mingw1120_64_qt6_7
QTPATH: C:\Qt\6.7\mingw_64
@ -22,12 +21,19 @@ build_script:
# prepare
- mkdir 3rdparty
- choco install ninja
- choco install pkgconfiglite
- choco install gperf
- cd 3rdparty
# build uchardet
- git clone -q https://gitlab.freedesktop.org/uchardet/uchardet.git
- cd uchardet
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%PACKAGE_INSTALL_ROOT% -DBUILD_BINARY=OFF
# install ECM
- git clone -q https://invent.kde.org/frameworks/extra-cmake-modules.git
- git rev-parse HEAD
- cd extra-cmake-modules
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%PACKAGE_INSTALL_ROOT% -DBUILD_TESTING=OFF
- cmake --build . --target install
- cd %APPVEYOR_BUILD_FOLDER%
# build kcodecs
- git clone -q https://invent.kde.org/frameworks/kcodecs.git
- cd kcodecs
- cmake -G "Ninja" . -DCMAKE_INSTALL_PREFIX=%PACKAGE_INSTALL_ROOT% -DBUILD_TESTING=OFF
- cmake --build . --target install
- cd %APPVEYOR_BUILD_FOLDER%
# build taglib

123
fftspectrum.cpp Normal file
View File

@ -0,0 +1,123 @@
// SPDX-FileCopyrightText: 2024 Gary Wang <git@blumia.net>
//
// SPDX-License-Identifier: MIT
#include "fftspectrum.h"
#include <QAudioBuffer>
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
#include <QAudioBufferOutput>
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
#include <QPainter>
#include <kissfft.hh>
FFTSpectrum::FFTSpectrum(QWidget* parent)
: QWidget(parent)
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
, m_audioBufferOutput(new QAudioBufferOutput(this))
#endif // #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
connect(this, &FFTSpectrum::mediaPlayerChanged, this, [=]() {
if (m_mediaPlayer) {
m_mediaPlayer->setAudioBufferOutput(m_audioBufferOutput);
}
});
connect(m_audioBufferOutput, &QAudioBufferOutput::audioBufferReceived, this, [=](const QAudioBuffer& buffer) {
const QAudioFormat& fmt = buffer.format();
const QAudioFormat::SampleFormat sampleFormat = fmt.sampleFormat();
QAudioFormat::ChannelConfig channelConfig = fmt.channelConfig();
const QFlags supportedChannelConfig({ QAudioFormat::ChannelConfigMono, QAudioFormat::ChannelConfigStereo });
const int frameCount = buffer.frameCount();
kissfft<float> fft(frameCount, false);
std::vector<kissfft<float>::cpx_t> samples(frameCount);
std::vector<kissfft<float>::cpx_t> mass(frameCount);
if (sampleFormat == QAudioFormat::Int16 && supportedChannelConfig.testFlag(channelConfig)) {
if (channelConfig == QAudioFormat::ChannelConfigMono) {
const QAudioBuffer::S16M* data = buffer.constData<QAudioBuffer::S16M>();
for (int i = 0; i < frameCount; ++i) {
samples[i].real(data[i].value(QAudioFormat::FrontCenter) / float(32768));
samples[i].imag(0);
}
} else {
const QAudioBuffer::S16S* data = buffer.constData<QAudioBuffer::S16S>();
for (int i = 0; i < frameCount; ++i) {
samples[i].real(data[i].value(QAudioFormat::FrontLeft) / float(32768));
samples[i].imag(0);
}
}
} else if (sampleFormat == QAudioFormat::Int32 && supportedChannelConfig.testFlag(channelConfig)) {
if (channelConfig == QAudioFormat::ChannelConfigMono) {
const QAudioBuffer::S32M* data = buffer.constData<QAudioBuffer::S32M>();
for (int i = 0; i < frameCount; ++i) {
samples[i].real(data[i].value(QAudioFormat::FrontCenter) / float(2147483647));
samples[i].imag(0);
}
} else {
const QAudioBuffer::S32S* data = buffer.constData<QAudioBuffer::S32S>();
for (int i = 0; i < frameCount; ++i) {
samples[i].real(data[i].value(QAudioFormat::FrontLeft) / float(2147483647));
samples[i].imag(0);
}
}
} else if (sampleFormat == QAudioFormat::Float && supportedChannelConfig.testFlag(channelConfig)) {
if (channelConfig == QAudioFormat::ChannelConfigMono) {
const QAudioBuffer::F32M* data = buffer.constData<QAudioBuffer::F32M>();
for (int i = 0; i < frameCount; ++i) {
samples[i].real(data[i].value(QAudioFormat::FrontCenter));
samples[i].imag(0);
}
} else {
const QAudioBuffer::F32S* data = buffer.constData<QAudioBuffer::F32S>();
for (int i = 0; i < frameCount; ++i) {
samples[i].real(data[i].value(QAudioFormat::FrontLeft));
samples[i].imag(0);
}
}
} else {
qWarning() << "Unsupported format or channel config:" << sampleFormat << channelConfig;
return;
}
fft.transform(samples.data(), mass.data());
m_freq.resize(frameCount);
for (int i = 0; i < frameCount; i++) {
m_freq[i] = sqrt(mass[i].real() * mass[i].real() + mass[i].imag() * mass[i].imag());
}
for (auto& val : m_freq) {
val = log10(val + 1);
}
update();
});
#endif // QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
resize(490, 420);
}
FFTSpectrum::~FFTSpectrum()
{
}
void FFTSpectrum::setMediaPlayer(QMediaPlayer* player)
{
m_mediaPlayer = player;
emit mediaPlayerChanged();
}
void FFTSpectrum::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
if (!m_freq.empty()) {
int width = this->width();
int height = this->height();
int barWidth = std::max(1ULL, width / m_freq.size());
for (int i = 0; i < m_freq.size(); i++) {
int barHeight = static_cast<int>(sqrt(m_freq[i]) * height * 0.5);
painter.fillRect(i * barWidth, height - barHeight, barWidth, barHeight, QColor(70, 130, 180, (int)(140 * m_freq[i]) + 90));
}
}
}

31
fftspectrum.h Normal file
View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2024 Gary Wang <git@blumia.net>
//
// SPDX-License-Identifier: MIT
#pragma once
#include <QWidget>
#include <QMediaPlayer>
class QAudioBufferOutput;
class FFTSpectrum : public QWidget
{
Q_OBJECT
Q_PROPERTY(QMediaPlayer * mediaPlayer MEMBER m_mediaPlayer WRITE setMediaPlayer NOTIFY mediaPlayerChanged)
public:
explicit FFTSpectrum(QWidget* parent);
~FFTSpectrum();
void setMediaPlayer(QMediaPlayer* player);
signals:
void mediaPlayerChanged();
protected:
void paintEvent(QPaintEvent* e) override;
private:
QMediaPlayer* m_mediaPlayer = nullptr;
QAudioBufferOutput* m_audioBufferOutput = nullptr;
std::vector<float> m_freq;
};

View File

@ -9,8 +9,14 @@
#include <QRegularExpression>
#include <QStringConverter>
#ifndef NO_UCHARDET
#include <uchardet/uchardet.h>
#ifdef HAVE_KCODECS
#include <KCharsets>
#include <KCodecs>
#include <KEncodingProber>
#endif
#ifdef USE_QTEXTCODEC
#include <QTextCodec>
#endif
Q_LOGGING_CATEGORY(lcLyrics, "pmusic.lyrics")
@ -44,24 +50,38 @@ bool LyricsManager::loadLyrics(QString filepath)
return false;
}
QByteArray fileContent(file.readAll());
#ifndef NO_UCHARDET
uchardet_t handle = uchardet_new();
uchardet_handle_data(handle, fileContent.data(), fileContent.length());
uchardet_data_end(handle);
const char* encoding = uchardet_get_charset(handle);
qCDebug(lcLyrics) << "Detected encoding:" << (encoding == NULL ? "unknown" : encoding);
QStringList lines;
if (QStringConverter::availableCodecs().contains(QString(encoding))) {
#ifdef HAVE_KCODECS
KEncodingProber prober(KEncodingProber::Universal);
prober.feed(fileContent);
QByteArray encoding(prober.encoding());
qCDebug(lcLyrics) << "Detected encoding:" << QString(encoding) << "with confidence" << prober.confidence();
#ifdef USE_QTEXTCODEC
qCDebug(lcLyrics) << "QTextCodec is used instead of QStringConverter.";
QTextCodec *codec = QTextCodec::codecForName(encoding);
if (codec) {
lines = codec->toUnicode(fileContent).split('\n');
} else {
lines = QString(fileContent).split('\n');
qCDebug(lcLyrics) << "No codec for the detected encoding. Available codecs are:" << QTextCodec::availableCodecs();
qCDebug(lcLyrics) << "KCodecs offers these encodings:" << KCharsets::charsets()->availableEncodingNames();
}
#else // NOT USE_QTEXTCODEC
auto toUtf16 = QStringDecoder(encoding);
// Don't use `QStringConverter::availableCodecs().contains(QString(encoding))` here, since the charset
// encoding name might not match, e.g. GB18030 (from availableCodecs) != gb18030 (from KEncodingProber)
if (toUtf16.isValid()) {
QString decodedResult = toUtf16(fileContent);
lines = decodedResult.split('\n');
} else {
qCDebug(lcLyrics) << "No codec for the detected encoding. Available codecs are:" << QStringConverter::availableCodecs();
qCDebug(lcLyrics) << "KCodecs offers these encodings:" << KCharsets::charsets()->availableEncodingNames();
lines = QString(fileContent).split('\n');
}
uchardet_delete(handle);
#else
QStringList lines = QString(fileContent).split('\n');
#endif
#endif // USE_QTEXTCODEC
#else // NOT HAVE_KCODECS
lines = QString(fileContent).split('\n');
#endif // HAVE_KCODECS
file.close();
// parse lyrics timestamp

View File

@ -6,6 +6,7 @@
#include "./ui_mainwindow.h"
#include "playlistmanager.h"
#include "fftspectrum.h"
#include "lrcbar.h"
// taglib
@ -31,6 +32,7 @@
#include <QAudioDevice>
#include <QMessageBox>
#include <QStringBuilder>
#include <QSettings>
constexpr QSize miniSize(490, 160);
constexpr QSize fullSize(490, 420);
@ -41,23 +43,28 @@ MainWindow::MainWindow(QWidget *parent)
, m_mediaDevices(new QMediaDevices(this))
, m_mediaPlayer(new QMediaPlayer(this))
, m_audioOutput(new QAudioOutput(this))
, m_fftSpectrum(new FFTSpectrum(this))
, m_lrcbar(new LrcBar(nullptr))
, m_playlistManager(new PlaylistManager(this))
{
ui->setupUi(this);
m_playlistManager->setAutoLoadFilterSuffixes({
"*.mp3", "*.wav", "*.aiff", "*.ape", "*.flac", "*.ogg", "*.oga", "*.mpga", "*.aac"
"*.mp3", "*.wav", "*.aiff", "*.ape", "*.flac", "*.ogg", "*.oga", "*.mpga", "*.aac", "*.tta"
});
m_fftSpectrum->setMediaPlayer(m_mediaPlayer);
m_mediaPlayer->setAudioOutput(m_audioOutput);
m_mediaPlayer->setLoops(QMediaPlayer::Infinite);
ui->playlistView->setModel(m_playlistManager->model());
ui->actionHelp->setShortcut(QKeySequence::HelpContents);
addAction(ui->actionHelp);
ui->actionOpen->setShortcut(QKeySequence::Open);
addAction(ui->actionOpen);
setWindowFlags(Qt::FramelessWindowHint | Qt::WindowSystemMenuHint | Qt::WindowMinimizeButtonHint);
setAttribute(Qt::WA_TranslucentBackground, true);
loadConfig();
loadSkinData();
initConnections();
initUiAndAnimation();
@ -67,6 +74,7 @@ MainWindow::MainWindow(QWidget *parent)
MainWindow::~MainWindow()
{
saveConfig();
delete m_lrcbar;
delete ui;
}
@ -438,6 +446,8 @@ void MainWindow::initConnections()
setAudioMetadataForDisplay(metadata.stringValue(QMediaMetaData::Title),
metadata.stringValue(QMediaMetaData::Author),
metadata.stringValue(QMediaMetaData::AlbumTitle));
setAudioPropertyInfoForDisplay(-1, metadata.value(QMediaMetaData::AudioBitRate).toInt() / 1000,
-1, metadata.stringValue(QMediaMetaData::FileFormat));
#endif // NO_TAGLIB
QVariant coverArt(metadata.value(QMediaMetaData::ThumbnailImage));
if (!coverArt.isNull()) {
@ -526,9 +536,27 @@ void MainWindow::initConnections()
});
}
void MainWindow::loadConfig()
{
QDir configDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
QSettings settings(configDir.filePath("settings.ini"), QSettings::IniFormat);
ui->volumeSlider->setValue(settings.value("volume", 100).toInt());
}
void MainWindow::saveConfig()
{
QDir configDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
if (!configDir.exists()) {
configDir.mkpath(".");
}
QSettings settings(configDir.filePath("settings.ini"), QSettings::IniFormat);
settings.setValue("volume", ui->volumeSlider->value());
}
void MainWindow::loadSkinData()
{
QFile file(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + "/skin.dat");
QDir configDir(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation));
QFile file(configDir.filePath("skin.dat"));
bool canOpen = file.open(QIODevice::ReadOnly);
if (!canOpen) return;
QDataStream stream(&file);
@ -604,6 +632,10 @@ void MainWindow::on_lrcBtn_clicked()
}
}
void MainWindow::on_actionOpen_triggered()
{
loadFile();
}
void MainWindow::on_actionHelp_triggered()
{
@ -616,12 +648,16 @@ void MainWindow::on_actionHelp_triggered()
"\n\n" %
tr("Based on the following free software libraries:") %
"\n\n" %
QStringLiteral("- [Qt](https://www.qt.io/) %1\n").arg(QT_VERSION_STR) %
QStringLiteral("- [Qt](https://www.qt.io/) %1 with the following module(s):\n").arg(QT_VERSION_STR) %
QStringLiteral(" - multimedia\n") %
#ifdef USE_QTEXTCODEC
QStringLiteral(" - core5compat\n") %
#endif
#ifndef NO_TAGLIB
QStringLiteral("- [TagLib](https://github.com/taglib/taglib)\n") %
#endif // NO_TAGLIB
#ifndef NO_UCHARDET
QStringLiteral("- [uchardet](https://www.freedesktop.org/wiki/Software/uchardet/)\n") %
#ifdef HAVE_KCODECS
QStringLiteral("- [KCodecs](https://invent.kde.org/frameworks/kcodecs)\n") %
#endif // NO_TAGLIB
"\n"
"[Source Code](https://github.com/BLumia/pineapple-music)\n"

View File

@ -17,6 +17,7 @@ class QAudioOutput;
class QPropertyAnimation;
QT_END_NAMESPACE
class FFTSpectrum;
class LrcBar;
class PlaylistManager;
class MainWindow : public QMainWindow
@ -75,6 +76,7 @@ private slots:
void on_playListBtn_clicked();
void on_playlistView_activated(const QModelIndex &index);
void on_lrcBtn_clicked();
void on_actionOpen_triggered();
void on_actionHelp_triggered();
signals:
@ -92,6 +94,7 @@ private:
QMediaDevices *m_mediaDevices;
QMediaPlayer *m_mediaPlayer;
QAudioOutput *m_audioOutput;
FFTSpectrum* m_fftSpectrum;
LrcBar *m_lrcbar;
QPropertyAnimation *m_fadeOutAnimation;
PlaylistManager *m_playlistManager;
@ -99,6 +102,8 @@ private:
void initUiAndAnimation();
void initConnections();
void loadConfig();
void saveConfig();
void loadSkinData();
void saveSkinData();

View File

@ -72,6 +72,7 @@ QSlider::add-page:vertical {
/****** PushButton ******/
QPushButton {
color: white;
border: 0px solid grey;
background-color: transparent;
}
@ -106,6 +107,7 @@ QLabel#coverLabel {
/****** ListView ******/
QListView {
color: white;
background: rgba(0, 0, 0, 50);
}</string>
</property>
@ -659,7 +661,10 @@ QListView {
</widget>
<action name="actionHelp">
<property name="text">
<string>help</string>
<string>Help</string>
</property>
<property name="toolTip">
<string>Help</string>
</property>
<property name="shortcut">
<string>F1</string>
@ -668,6 +673,20 @@ QListView {
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
<action name="actionOpen">
<property name="icon">
<iconset theme="document-open"/>
</property>
<property name="text">
<string>Open</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
<property name="menuRole">
<enum>QAction::MenuRole::NoRole</enum>
</property>
</action>
</widget>
<customwidgets>
<customwidget>