feat: complete QML shell migration and ship 0.0.8 Arch package

Replace the Widgets main shell with AppShellHost + QML routes, add a QML
independent player window, and remove the legacy player/page Widgets stack.
Also wire settings cache/proxy actions, fix quit-time engine teardown, improve
list/home scroll performance, and update Arch packaging to 0.0.8.
This commit is contained in:
dela
2026-07-27 16:41:18 +08:00
parent e2357aaa32
commit 2ee3a234a6
251 changed files with 40121 additions and 50423 deletions
+63 -34
View File
@@ -6,7 +6,7 @@
#include <config/configstore.h>
#include <config/config_keys.h>
#include <models/media/playbackinfo.h>
#include "../components/playerwindow.h"
#include "../quick/independentplayerhost.h"
#include "../components/moderntoast.h"
#include "../utils/logredactionutils.h"
#include "../utils/playerpreferenceutils.h"
@@ -39,16 +39,21 @@ void PlaybackManager::init(QEmbyCore* core)
m_core = core;
}
void PlaybackManager::setEmbeddedHostAvailable(bool available)
{
m_embeddedHostAvailable = available;
}
void PlaybackManager::startPlayback(const QString& mediaId, const QString& title,
const QString& streamUrl, long long startPositionTicks,
const QVariant& extraData)
{
if (!m_core) return;
if (m_independentWindow) {
m_independentWindow->close();
// Drop any existing independent window before starting a new session
// (embedded, external, or a replacement independent play).
if (m_independentHost) {
m_independentHost->close();
}
qDebug() << "[PlaybackManager] ===== startPlayback =====";
@@ -88,6 +93,13 @@ void PlaybackManager::startPlayback(const QString& mediaId, const QString& title
// No embedded host: fall back to QML IndependentPlayerHost.
if (!m_embeddedHostAvailable) {
qDebug() << "[PlaybackManager] → Mode: Independent window (no embedded host)";
launchIndependentWindow(mediaId, title, streamUrl, startPositionTicks, extraData);
return;
}
qDebug() << "[PlaybackManager] → Mode: Embedded player";
Q_EMIT requestEmbeddedPlay(mediaId, title, streamUrl, startPositionTicks, extraData);
}
@@ -98,22 +110,25 @@ void PlaybackManager::startInternalPlayback(const QString& mediaId, const QStrin
{
if (!m_core) return;
if (m_independentWindow) {
m_independentWindow->close();
if (m_independentHost) {
m_independentHost->close();
}
qDebug() << "[PlaybackManager] ===== startInternalPlayback =====";
qDebug() << "[PlaybackManager] mediaId:" << mediaId << "title:" << title;
qDebug() << "[PlaybackManager] streamUrl:" << LogRedactionUtils::url(streamUrl);
bool independent = ConfigStore::instance()->get<bool>(ConfigKeys::PlayerIndependentWindow, false);
if (independent) {
qDebug() << "[PlaybackManager] → Mode: Independent window (internal)";
launchIndependentWindow(mediaId, title, streamUrl, startPositionTicks, extraData);
return;
}
if (!m_embeddedHostAvailable) {
qDebug() << "[PlaybackManager] → Mode: Independent window (internal, no embedded host)";
launchIndependentWindow(mediaId, title, streamUrl, startPositionTicks, extraData);
return;
}
qDebug() << "[PlaybackManager] → Mode: Embedded player (internal)";
Q_EMIT requestEmbeddedPlay(mediaId, title, streamUrl, startPositionTicks, extraData);
}
@@ -127,6 +142,35 @@ void PlaybackManager::stopExternalPlayer()
cleanupExternalPlayerState();
}
bool PlaybackManager::stopActivePlaybackForQuit()
{
bool stopped = false;
// Independent QML player: close() deactivates PlayerViewModel (STOP report)
// then destroys the secondary engine. Host emits closed → playbackFinished.
if (m_independentHost) {
m_independentHost->close();
stopped = true;
}
// External player process: kill it and flush the final stop report. This
// mirrors the process-exit handler's reportPlaybackStopped, but synchronously
// on the quit path where we cannot wait for the async exit signal.
if (m_extProcess && m_extProcess->state() != QProcess::NotRunning) {
long long finalTicks = m_ipc ? m_ipc->currentPositionTicks() : 0;
if (m_core && m_core->mediaService() && !m_currentMediaId.isEmpty() &&
!m_currentPlaySessionId.isEmpty()) {
m_core->mediaService()->reportPlaybackStopped(
m_currentMediaId, m_currentMediaSourceId, finalTicks,
m_currentPlaySessionId);
}
stopExternalPlayer();
stopped = true;
}
return stopped;
}
void PlaybackManager::cleanupExternalPlayerState()
{
if (m_progressTimer) {
@@ -640,31 +684,16 @@ void PlaybackManager::launchIndependentWindow(const QString& mediaId, const QStr
qDebug() << "[PlaybackManager] startPositionTicks:" << startPositionTicks
<< "(" << ticksToTimeString(startPositionTicks) << ")";
if (m_independentWindow) {
qDebug() << "[PlaybackManager] Reusing existing independent window";
m_independentWindow->playMedia(mediaId, title, streamUrl, startPositionTicks, extraData);
m_independentWindow->raise();
m_independentWindow->activateWindow();
return;
if (!m_independentHost) {
qDebug() << "[PlaybackManager] Creating IndependentPlayerHost";
m_independentHost = new IndependentPlayerHost(m_core, this);
connect(m_independentHost, &IndependentPlayerHost::closed, this, [this]() {
qDebug() << "[PlaybackManager] Independent player window closed";
Q_EMIT playbackFinished();
});
} else {
qDebug() << "[PlaybackManager] Reusing IndependentPlayerHost";
}
qDebug() << "[PlaybackManager] Creating new independent window";
auto* playerWindow = new PlayerWindow(m_core);
playerWindow->setAttribute(Qt::WA_DeleteOnClose, true);
playerWindow->setMinimumSize(960, 540);
playerWindow->resize(1280, 720);
connect(playerWindow, &QObject::destroyed, this, [this]() {
m_independentWindow = nullptr;
qDebug() << "[PlaybackManager] Independent window destroyed";
Q_EMIT playbackFinished();
});
m_independentWindow = playerWindow;
playerWindow->show();
playerWindow->playMedia(mediaId, title, streamUrl, startPositionTicks, extraData);
m_independentHost->play(mediaId, title, streamUrl, startPositionTicks, extraData);
}
+18 -29
View File
@@ -9,48 +9,47 @@
class QEmbyCore;
class QProcess;
class PlayerWindow;
class IndependentPlayerHost;
class QTimer;
struct MediaSourceInfo;
class PlaybackManager : public QObject
{
Q_OBJECT
public:
static PlaybackManager* instance();
void init(QEmbyCore* core);
// Whether an in-shell embedded player host exists to receive
// requestEmbeddedPlay (AppShellHost + PlayerRouteController). When false,
// the embedded branch falls back to IndependentPlayerHost (QML window).
void setEmbeddedHostAvailable(bool available);
void startPlayback(const QString& mediaId, const QString& title,
const QString& streamUrl, long long startPositionTicks,
const QVariant& extraData = QVariant());
void startExternalPlayback(const QString& playerPath,
const QString& mediaId, const QString& title,
const QString& streamUrl, long long startPositionTicks,
const QVariant& extraData = QVariant());
void startInternalPlayback(const QString& mediaId, const QString& title,
const QString& streamUrl, long long startPositionTicks,
const QVariant& extraData = QVariant());
void stopExternalPlayer();
// Stop independent QML player window and/or external process before quit.
// Returns true if something was stopped (caller may defer quit briefly).
bool stopActivePlaybackForQuit();
signals:
void requestEmbeddedPlay(const QString& mediaId, const QString& title,
const QString& streamUrl, long long startPositionTicks,
const QVariant& extraData);
void playbackFinished();
private:
@@ -66,11 +65,8 @@ private:
const QString& streamUrl, long long startPositionTicks,
const QVariant& extraData);
static PlayerType identifyPlayerType(const QString& playerPath);
static QStringList buildPlayerArgs(PlayerType type,
long long startPositionTicks,
int audioStreamIndex,
@@ -78,34 +74,27 @@ private:
bool subtitleDisabled,
const MediaSourceInfo* sourceInfo);
static QString ticksToTimeString(long long ticks);
static double ticksToSeconds(long long ticks);
static int computeRelativeStreamIndex(const MediaSourceInfo& source,
const QString& streamType,
int globalIndex);
static QString generateIpcEndpoint(PlayerType type);
void cleanupExternalPlayerState();
QEmbyCore* m_core = nullptr;
QPointer<QProcess> m_extProcess;
QPointer<PlayerWindow> m_independentWindow;
bool m_embeddedHostAvailable = true;
QPointer<QProcess> m_extProcess;
QPointer<IndependentPlayerHost> m_independentHost;
ExternalPlayerIPC* m_ipc = nullptr;
QTimer* m_progressTimer = nullptr;
QString m_currentMediaId;
QString m_currentMediaSourceId;
QString m_currentPlaySessionId;
ExternalPlayerIPC* m_ipc = nullptr;
QTimer* m_progressTimer = nullptr;
QString m_currentMediaId;
QString m_currentMediaSourceId;
QString m_currentPlaySessionId;
};
#endif