feat: add playlist, recent files and MPRIS backends

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
triandClaude Opus 5.5 committed 2026-10-28 09:26:12 +07:00
1 parent 6dc9377de1
commit 54ad4eecf3
18 files changed
+2240 -4

No files matched your search

+162 -2
View File
@@ -9,6 +9,7 @@
#include <QSaveFile>
#include <QStandardPaths>
#include <algorithm>
#include <cmath>
#include <utility>
@@ -41,6 +42,13 @@ MpvItem::MpvItem(QQuickItem *parent)
observeProperty(QStringLiteral("pause"), MPV_FORMAT_FLAG);
observeProperty(QStringLiteral("track-list"), MPV_FORMAT_NODE);
observeProperty(QStringLiteral("sub-delay"), MPV_FORMAT_DOUBLE);
observeProperty(QStringLiteral("volume"), MPV_FORMAT_INT64);
observeProperty(QStringLiteral("volume-max"), MPV_FORMAT_INT64);
observeProperty(QStringLiteral("mute"), MPV_FORMAT_FLAG);
observeProperty(QStringLiteral("speed"), MPV_FORMAT_DOUBLE);
observeProperty(QStringLiteral("chapter-list"), MPV_FORMAT_NODE);
observeProperty(QStringLiteral("chapter"), MPV_FORMAT_INT64);
observeProperty(QStringLiteral("eof-reached"), MPV_FORMAT_FLAG);
// MpvController lives on a worker thread; queue its signals back to the GUI thread.
connect(mpvController(), &MpvController::propertyChanged, this, &MpvItem::onPropertyChanged, Qt::QueuedConnection);
@@ -132,6 +140,13 @@ void MpvItem::loadFile(const QUrl &url)
setSubtitleDelay(0.0);
}
void MpvItem::stop()
{
commandAsync(QStringList{QStringLiteral("stop")});
m_currentUrl.clear();
Q_EMIT currentUrlChanged();
}
void MpvItem::togglePause()
{
setPaused(!m_paused);
@@ -142,6 +157,21 @@ void MpvItem::seek(double seconds)
commandAsync(QStringList{QStringLiteral("seek"), QString::number(seconds), QStringLiteral("absolute")});
}
void MpvItem::seekBy(double seconds)
{
commandAsync(QStringList{QStringLiteral("seek"), QString::number(seconds), QStringLiteral("relative")});
}
void MpvItem::frameStep(bool forward)
{
commandAsync(QStringList{forward ? QStringLiteral("frame-step") : QStringLiteral("frame-back-step")});
}
void MpvItem::screenshot()
{
commandAsync(QStringList{QStringLiteral("screenshot")});
}
QVariantList MpvItem::subtitleTracks() const
{
return m_subtitleTracks;
@@ -186,6 +216,85 @@ void MpvItem::setSubtitleDelay(double seconds)
setPropertyAsync(QStringLiteral("secondary-sub-delay"), rounded);
}
QVariantList MpvItem::audioTracks() const
{
return m_audioTracks;
}
int MpvItem::audioId() const
{
return m_audioId;
}
void MpvItem::setAudioId(int id)
{
setPropertyAsync(QStringLiteral("aid"), trackIdValue(id));
}
int MpvItem::volume() const
{
return m_volume;
}
void MpvItem::setVolume(int volume)
{
volume = std::clamp(volume, 0, m_volumeMax);
// Like sub-delay: update right away so repeated steps build on each other. Always
// sent, since mpv may not have reported a volume from mpv.conf yet.
if (volume != m_volume) {
m_volume = volume;
Q_EMIT volumeChanged();
}
setPropertyAsync(QStringLiteral("volume"), volume);
}
int MpvItem::volumeMax() const
{
return m_volumeMax;
}
bool MpvItem::mute() const
{
return m_mute;
}
void MpvItem::setMute(bool mute)
{
setPropertyAsync(QStringLiteral("mute"), mute);
}
double MpvItem::speed() const
{
return m_speed;
}
void MpvItem::setSpeed(double speed)
{
const double rounded = std::round(std::clamp(speed, 0.1, 10.0) * 100.0) / 100.0;
if (rounded != m_speed) {
m_speed = rounded;
Q_EMIT speedChanged();
}
setPropertyAsync(QStringLiteral("speed"), rounded);
}
QVariantList MpvItem::chapters() const
{
return m_chapters;
}
int MpvItem::chapter() const
{
return m_chapter;
}
void MpvItem::setChapter(int chapter)
{
if (chapter >= 0 && chapter < m_chapters.size()) {
setPropertyAsync(QStringLiteral("chapter"), chapter);
}
}
void MpvItem::addSubtitleFile(const QUrl &url)
{
if (!url.isValid()) {
@@ -301,22 +410,69 @@ void MpvItem::onPropertyChanged(const QString &property, const QVariant &value)
} else if (property == u"sub-delay") {
m_subtitleDelay = value.toDouble();
Q_EMIT subtitleDelayChanged();
} else if (property == u"volume") {
m_volume = value.toInt();
Q_EMIT volumeChanged();
} else if (property == u"volume-max") {
m_volumeMax = value.toInt();
Q_EMIT volumeMaxChanged();
} else if (property == u"mute") {
m_mute = value.toBool();
Q_EMIT muteChanged();
} else if (property == u"speed") {
m_speed = value.toDouble();
Q_EMIT speedChanged();
} else if (property == u"chapter-list") {
QVariantList chapters;
for (const QVariant &entry : value.toList()) {
const QVariantMap chapter = entry.toMap();
chapters.append(QVariantMap{
{QStringLiteral("title"), chapter.value(QStringLiteral("title")).toString()},
{QStringLiteral("time"), chapter.value(QStringLiteral("time")).toDouble()},
});
}
m_chapters = chapters;
Q_EMIT chaptersChanged();
} else if (property == u"chapter") {
// Unavailable (no chapters) arrives as an invalid value.
m_chapter = value.isValid() ? value.toInt() : -1;
Q_EMIT chapterChanged();
} else if (property == u"eof-reached") {
if (value.toBool()) {
Q_EMIT endOfFile();
}
}
}
void MpvItem::updateTracks(const QVariantList &trackList)
{
QVariantList subtitles;
QVariantList audio;
int primary = 0;
int secondary = 0;
int audioId = 0;
for (const QVariant &entry : trackList) {
const QVariantMap track = entry.toMap();
if (track.value(QStringLiteral("type")).toString() != u"sub") {
const QString type = track.value(QStringLiteral("type")).toString();
const int id = track.value(QStringLiteral("id")).toInt();
if (type == u"audio") {
if (track.value(QStringLiteral("selected")).toBool()) {
audioId = id;
}
audio.append(QVariantMap{
{QStringLiteral("id"), id},
{QStringLiteral("title"), track.value(QStringLiteral("title")).toString()},
{QStringLiteral("lang"), track.value(QStringLiteral("lang")).toString()},
{QStringLiteral("codec"), track.value(QStringLiteral("codec")).toString()},
});
continue;
}
if (type != u"sub") {
continue;
}
const int id = track.value(QStringLiteral("id")).toInt();
if (track.value(QStringLiteral("selected")).toBool()) {
// main-selection is 0 for the primary subtitle and 1 for the secondary one.
if (track.value(QStringLiteral("main-selection")).toInt() == 0) {
@@ -346,6 +502,10 @@ void MpvItem::updateTracks(const QVariantList &trackList)
m_subtitleId = primary;
m_secondarySubtitleId = secondary;
Q_EMIT subtitleTracksChanged();
m_audioTracks = audio;
m_audioId = audioId;
Q_EMIT audioTracksChanged();
}
void MpvItem::setFileLoaded(bool loaded)