mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-04-09 21:02:53 +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
|
qt_add_executable(applinux
|
||||||
main.cpp
|
main.cpp
|
||||||
logger.h
|
logger.h
|
||||||
mediacontroller.cpp
|
media/mediacontroller.cpp
|
||||||
mediacontroller.h
|
media/mediacontroller.h
|
||||||
airpods_packets.h
|
airpods_packets.h
|
||||||
trayiconmanager.cpp
|
trayiconmanager.cpp
|
||||||
trayiconmanager.h
|
trayiconmanager.h
|
||||||
@@ -32,6 +32,8 @@ qt_add_executable(applinux
|
|||||||
thirdparty/QR-Code-generator/qrcodegen.hpp
|
thirdparty/QR-Code-generator/qrcodegen.hpp
|
||||||
QRCodeImageProvider.hpp
|
QRCodeImageProvider.hpp
|
||||||
eardetection.hpp
|
eardetection.hpp
|
||||||
|
media/playerstatuswatcher.cpp
|
||||||
|
media/playerstatuswatcher.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(applinux
|
qt_add_qml_module(applinux
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
#include "airpods_packets.h"
|
#include "airpods_packets.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "mediacontroller.h"
|
#include "media/mediacontroller.h"
|
||||||
#include "trayiconmanager.h"
|
#include "trayiconmanager.h"
|
||||||
#include "enums.h"
|
#include "enums.h"
|
||||||
#include "battery.hpp"
|
#include "battery.hpp"
|
||||||
@@ -66,7 +66,6 @@ public:
|
|||||||
// Initialize MediaController and connect signals
|
// Initialize MediaController and connect signals
|
||||||
mediaController = new MediaController(this);
|
mediaController = new MediaController(this);
|
||||||
connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange);
|
connect(mediaController, &MediaController::mediaStateChanged, this, &AirPodsTrayApp::handleMediaStateChange);
|
||||||
mediaController->initializeMprisInterface();
|
|
||||||
mediaController->followMediaChanges();
|
mediaController->followMediaChanges();
|
||||||
|
|
||||||
monitor = new BluetoothMonitor(this);
|
monitor = new BluetoothMonitor(this);
|
||||||
@@ -795,13 +794,6 @@ public:
|
|||||||
process.waitForFinished();
|
process.waitForFinished();
|
||||||
QString output = process.readAllStandardOutput().trimmed();
|
QString output = process.readAllStandardOutput().trimmed();
|
||||||
LOG_INFO("Bluetoothctl output: " << output);
|
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;
|
QBluetoothLocalDevice localDevice;
|
||||||
const QList<QBluetoothAddress> connectedDevices = localDevice.connectedDevices();
|
const QList<QBluetoothAddress> connectedDevices = localDevice.connectedDevices();
|
||||||
@@ -816,31 +808,6 @@ public:
|
|||||||
LOG_WARN("AirPods not found among connected devices");
|
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() {
|
void initializeBluetooth() {
|
||||||
connectToPhone();
|
connectToPhone();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "mediacontroller.h"
|
#include "mediacontroller.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "eardetection.hpp"
|
#include "eardetection.hpp"
|
||||||
|
#include "playerstatuswatcher.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
@@ -9,34 +10,6 @@
|
|||||||
#include <QDBusConnectionInterface>
|
#include <QDBusConnectionInterface>
|
||||||
|
|
||||||
MediaController::MediaController(QObject *parent) : QObject(parent) {
|
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)
|
void MediaController::handleEarDetection(EarDetection *earDetection)
|
||||||
@@ -118,16 +91,14 @@ void MediaController::setEarDetectionBehavior(EarDetectionBehavior behavior)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MediaController::followMediaChanges() {
|
void MediaController::followMediaChanges() {
|
||||||
playerctlProcess = new QProcess(this);
|
playerStatusWatcher = new PlayerStatusWatcher("", this);
|
||||||
connect(playerctlProcess, &QProcess::readyReadStandardOutput, this,
|
connect(playerStatusWatcher, &PlayerStatusWatcher::playbackStatusChanged,
|
||||||
[this]() {
|
this, [this](const QString &status)
|
||||||
QString output =
|
{
|
||||||
playerctlProcess->readAllStandardOutput().trimmed();
|
LOG_DEBUG("Playback status changed: " << status);
|
||||||
LOG_DEBUG("Playerctl output: " << output);
|
MediaState state = mediaStateFromPlayerctlOutput(status);
|
||||||
MediaState state = mediaStateFromPlayerctlOutput(output);
|
|
||||||
emit mediaStateChanged(state);
|
emit mediaStateChanged(state);
|
||||||
});
|
});
|
||||||
playerctlProcess->start("playerctl", QStringList() << "--follow" << "status");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaController::isActiveOutputDeviceAirPods() {
|
bool MediaController::isActiveOutputDeviceAirPods() {
|
||||||
@@ -241,13 +212,6 @@ void MediaController::pause() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaController::~MediaController() {
|
MediaController::~MediaController() {
|
||||||
if (playerctlProcess) {
|
|
||||||
playerctlProcess->terminate();
|
|
||||||
if (!playerctlProcess->waitForFinished()) {
|
|
||||||
playerctlProcess->kill();
|
|
||||||
playerctlProcess->waitForFinished(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString MediaController::getAudioDeviceName()
|
QString MediaController::getAudioDeviceName()
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
class QProcess;
|
class QProcess;
|
||||||
class EarDetection;
|
class EarDetection;
|
||||||
|
class PlayerStatusWatcher;
|
||||||
|
|
||||||
class MediaController : public QObject
|
class MediaController : public QObject
|
||||||
{
|
{
|
||||||
@@ -29,7 +30,6 @@ public:
|
|||||||
explicit MediaController(QObject *parent = nullptr);
|
explicit MediaController(QObject *parent = nullptr);
|
||||||
~MediaController();
|
~MediaController();
|
||||||
|
|
||||||
void initializeMprisInterface();
|
|
||||||
void handleEarDetection(EarDetection*);
|
void handleEarDetection(EarDetection*);
|
||||||
void followMediaChanges();
|
void followMediaChanges();
|
||||||
bool isActiveOutputDeviceAirPods();
|
bool isActiveOutputDeviceAirPods();
|
||||||
@@ -50,13 +50,12 @@ private:
|
|||||||
MediaState mediaStateFromPlayerctlOutput(const QString &output);
|
MediaState mediaStateFromPlayerctlOutput(const QString &output);
|
||||||
QString getAudioDeviceName();
|
QString getAudioDeviceName();
|
||||||
|
|
||||||
QDBusInterface *mprisInterface = nullptr;
|
|
||||||
QProcess *playerctlProcess = nullptr;
|
|
||||||
bool wasPausedByApp = false;
|
bool wasPausedByApp = false;
|
||||||
int initialVolume = -1;
|
int initialVolume = -1;
|
||||||
QString connectedDeviceMacAddress;
|
QString connectedDeviceMacAddress;
|
||||||
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
|
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
|
||||||
QString m_deviceOutputName;
|
QString m_deviceOutputName;
|
||||||
|
PlayerStatusWatcher *playerStatusWatcher = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MEDIACONTROLLER_H
|
#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