[Linux] Use DBus for following media playback change

This commit is contained in:
Tim Gromeyer
2025-06-08 18:27:26 +02:00
committed by Tim Gromeyer
parent 5754dbfb16
commit 38d6f8ceae
6 changed files with 85 additions and 82 deletions

View File

@@ -12,8 +12,8 @@ qt_standard_project_setup(REQUIRES 6.4)
qt_add_executable(applinux
main.cpp
logger.h
mediacontroller.cpp
mediacontroller.h
media/mediacontroller.cpp
media/mediacontroller.h
airpods_packets.h
trayiconmanager.cpp
trayiconmanager.h
@@ -32,6 +32,8 @@ qt_add_executable(applinux
thirdparty/QR-Code-generator/qrcodegen.hpp
QRCodeImageProvider.hpp
eardetection.hpp
media/playerstatuswatcher.cpp
media/playerstatuswatcher.h
)
qt_add_qml_module(applinux

View File

@@ -14,7 +14,7 @@
#include "airpods_packets.h"
#include "logger.h"
#include "mediacontroller.h"
#include "media/mediacontroller.h"
#include "trayiconmanager.h"
#include "enums.h"
#include "battery.hpp"
@@ -66,7 +66,6 @@ public:
// Initialize MediaController and connect signals
mediaController = new MediaController(this);
connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange);
mediaController->initializeMprisInterface();
mediaController->followMediaChanges();
monitor = new BluetoothMonitor(this);
@@ -795,13 +794,6 @@ public:
process.waitForFinished();
QString output = process.readAllStandardOutput().trimmed();
LOG_INFO("Bluetoothctl output: " << output);
if (output.contains("Connection successful")) {
LOG_INFO("Connection successful, proceeding with L2CAP connection");
QBluetoothAddress btAddress(m_deviceInfo->bluetoothAddress());
forceL2capConnection(btAddress);
} else {
LOG_ERROR("Connection failed, cannot proceed with L2CAP connection");
}
}
QBluetoothLocalDevice localDevice;
const QList<QBluetoothAddress> connectedDevices = localDevice.connectedDevices();
@@ -816,31 +808,6 @@ public:
LOG_WARN("AirPods not found among connected devices");
}
void forceL2capConnection(const QBluetoothAddress &address) {
LOG_INFO("Retrying L2CAP connection for up to 10 seconds...");
QBluetoothDeviceInfo device(address, "", 0);
QElapsedTimer timer;
timer.start();
while (timer.elapsed() < 10000) {
QProcess bcProcess;
bcProcess.start("bluetoothctl", QStringList() << "connect" << address.toString());
bcProcess.waitForFinished();
QString output = bcProcess.readAllStandardOutput().trimmed();
LOG_INFO("Bluetoothctl output: " << output);
if (output.contains("Connection successful")) {
connectToDevice(device);
QThread::sleep(1);
if (socket && socket->isOpen()) {
LOG_INFO("Successfully connected to device: " << address.toString());
return;
}
} else {
LOG_WARN("Connection attempt failed, retrying...");
}
}
LOG_ERROR("Failed to connect to device within 10 seconds: " << address.toString());
}
void initializeBluetooth() {
connectToPhone();

View File

@@ -1,6 +1,7 @@
#include "mediacontroller.h"
#include "logger.h"
#include "eardetection.hpp"
#include "playerstatuswatcher.h"
#include <QDebug>
#include <QProcess>
@@ -9,34 +10,6 @@
#include <QDBusConnectionInterface>
MediaController::MediaController(QObject *parent) : QObject(parent) {
// No additional initialization required here
}
void MediaController::initializeMprisInterface() {
QStringList services =
QDBusConnection::sessionBus().interface()->registeredServiceNames();
QString mprisService;
for (const QString &service : services) {
if (service.startsWith("org.mpris.MediaPlayer2.") &&
service != "org.mpris.MediaPlayer2") {
mprisService = service;
break;
}
}
if (!mprisService.isEmpty()) {
mprisInterface = new QDBusInterface(mprisService, "/org/mpris/MediaPlayer2",
"org.mpris.MediaPlayer2.Player",
QDBusConnection::sessionBus(), this);
if (!mprisInterface->isValid()) {
LOG_ERROR("Failed to initialize MPRIS interface for service: ") << mprisService;
} else {
LOG_INFO("Connected to MPRIS service: " << mprisService);
}
} else {
LOG_WARN("No active MPRIS media players found");
}
}
void MediaController::handleEarDetection(EarDetection *earDetection)
@@ -118,16 +91,14 @@ void MediaController::setEarDetectionBehavior(EarDetectionBehavior behavior)
}
void MediaController::followMediaChanges() {
playerctlProcess = new QProcess(this);
connect(playerctlProcess, &QProcess::readyReadStandardOutput, this,
[this]() {
QString output =
playerctlProcess->readAllStandardOutput().trimmed();
LOG_DEBUG("Playerctl output: " << output);
MediaState state = mediaStateFromPlayerctlOutput(output);
playerStatusWatcher = new PlayerStatusWatcher("", this);
connect(playerStatusWatcher, &PlayerStatusWatcher::playbackStatusChanged,
this, [this](const QString &status)
{
LOG_DEBUG("Playback status changed: " << status);
MediaState state = mediaStateFromPlayerctlOutput(status);
emit mediaStateChanged(state);
});
playerctlProcess->start("playerctl", QStringList() << "--follow" << "status");
}
bool MediaController::isActiveOutputDeviceAirPods() {
@@ -241,13 +212,6 @@ void MediaController::pause() {
}
MediaController::~MediaController() {
if (playerctlProcess) {
playerctlProcess->terminate();
if (!playerctlProcess->waitForFinished()) {
playerctlProcess->kill();
playerctlProcess->waitForFinished(1000);
}
}
}
QString MediaController::getAudioDeviceName()

View File

@@ -6,6 +6,7 @@
class QProcess;
class EarDetection;
class PlayerStatusWatcher;
class MediaController : public QObject
{
@@ -29,7 +30,6 @@ public:
explicit MediaController(QObject *parent = nullptr);
~MediaController();
void initializeMprisInterface();
void handleEarDetection(EarDetection*);
void followMediaChanges();
bool isActiveOutputDeviceAirPods();
@@ -50,13 +50,12 @@ private:
MediaState mediaStateFromPlayerctlOutput(const QString &output);
QString getAudioDeviceName();
QDBusInterface *mprisInterface = nullptr;
QProcess *playerctlProcess = nullptr;
bool wasPausedByApp = false;
int initialVolume = -1;
QString connectedDeviceMacAddress;
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
QString m_deviceOutputName;
PlayerStatusWatcher *playerStatusWatcher = nullptr;
};
#endif // MEDIACONTROLLER_H

View File

@@ -0,0 +1,47 @@
#include "playerstatuswatcher.h"
#include <QDBusConnection>
#include <QDBusPendingReply>
#include <QVariantMap>
#include <QDBusReply>
PlayerStatusWatcher::PlayerStatusWatcher(const QString &playerService, QObject *parent)
: QObject(parent),
m_playerService(playerService),
m_iface(new QDBusInterface(playerService, "/org/mpris/MediaPlayer2",
"org.mpris.MediaPlayer2.Player", QDBusConnection::sessionBus(), this)),
m_serviceWatcher(new QDBusServiceWatcher(playerService, QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForOwnerChange, this))
{
QDBusConnection::sessionBus().connect(
playerService, "/org/mpris/MediaPlayer2", "org.freedesktop.DBus.Properties",
"PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList))
);
connect(m_serviceWatcher, &QDBusServiceWatcher::serviceOwnerChanged,
this, &PlayerStatusWatcher::onServiceOwnerChanged);
updateStatus();
}
void PlayerStatusWatcher::onPropertiesChanged(const QString &interface,
const QVariantMap &changed,
const QStringList &)
{
if (interface == "org.mpris.MediaPlayer2.Player" && changed.contains("PlaybackStatus")) {
emit playbackStatusChanged(changed.value("PlaybackStatus").toString());
}
}
void PlayerStatusWatcher::updateStatus() {
QVariant reply = m_iface->property("PlaybackStatus");
if (reply.isValid()) {
emit playbackStatusChanged(reply.toString());
}
}
void PlayerStatusWatcher::onServiceOwnerChanged(const QString &name, const QString &, const QString &newOwner)
{
if (name == m_playerService && newOwner.isEmpty()) {
emit playbackStatusChanged(""); // player disappeared
} else if (name == m_playerService && !newOwner.isEmpty()) {
updateStatus(); // player appeared/reappeared
}
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include <QObject>
#include <QDBusInterface>
#include <QDBusServiceWatcher>
class PlayerStatusWatcher : public QObject {
Q_OBJECT
public:
explicit PlayerStatusWatcher(const QString &playerService, QObject *parent = nullptr);
signals:
void playbackStatusChanged(const QString &status);
private slots:
void onPropertiesChanged(const QString &interface, const QVariantMap &changed, const QStringList &);
void onServiceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner);
private:
void updateStatus();
QString m_playerService;
QDBusInterface *m_iface;
QDBusServiceWatcher *m_serviceWatcher;
};