mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-01-28 22:01:50 +00:00
[Linux] Use DBus for following media playback change
This commit is contained in:
committed by
Tim Gromeyer
parent
5754dbfb16
commit
38d6f8ceae
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
47
linux/media/playerstatuswatcher.cpp
Normal file
47
linux/media/playerstatuswatcher.cpp
Normal 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
|
||||
}
|
||||
}
|
||||
24
linux/media/playerstatuswatcher.h
Normal file
24
linux/media/playerstatuswatcher.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user