11#include <QDBusServiceWatcher>
12#include <QDBusConnectionInterface>
13#include <QDBusMetaType>
14#include <QDBusArgument>
24static QVariant unwrapVariant(
const QVariant &var) {
25 if (var.userType() == qMetaTypeId<QDBusVariant>()) {
26 return unwrapVariant(var.value<QDBusVariant>().variant());
29 if (var.userType() == qMetaTypeId<QDBusArgument>()) {
30 QDBusArgument arg = var.value<QDBusArgument>();
32 if (arg.currentType() == QDBusArgument::VariantType) {
35 return unwrapVariant(inner);
38 if (arg.currentType() == QDBusArgument::MapType) {
41 for (
auto it = map.begin(); it != map.end(); ++it)
42 it.value() = unwrapVariant(it.value());
46 if (arg.currentType() == QDBusArgument::ArrayType) {
49 for (QVariant &v : list)
55 if (var.typeId() == QMetaType::QVariantList) {
56 QVariantList list = var.toList();
57 for (QVariant &v : list)
67 qDBusRegisterMetaType<QVariantMap>();
68 qDBusRegisterMetaType<QList<QVariant>>();
71 auto *watcher =
new QDBusServiceWatcher(
72 "org.mpris.MediaPlayer2*",
73 QDBusConnection::sessionBus(),
74 QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
78 connect(watcher, &QDBusServiceWatcher::serviceRegistered,
79 this, &BluetoothManager::connectToService);
81 connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
82 this, [
this](
const QString &service){
83 if (service == m_currentService) {
85 m_title =
"Déconnecté";
92 m_currentService.clear();
98 QTimer::singleShot(300,
this, &BluetoothManager::findActivePlayer);
105void BluetoothManager::findActivePlayer() {
106 QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
109 const QStringList services = bus->registeredServiceNames();
114 for (
const QString &service : services) {
115 if (service.startsWith(
"org.mpris.MediaPlayer2.") &&
116 !service.contains(
"mpris-proxy") &&
117 !service.contains(
"Bluetooth_Player")) {
118 connectToService(service);
125 for (
const QString &service : services) {
126 if (service.startsWith(
"org.mpris.MediaPlayer2.") &&
127 !service.endsWith(
".mpris-proxy")) {
128 connectToService(service);
134void BluetoothManager::connectToService(
const QString &serviceName) {
135 if (serviceName.isEmpty())
return;
136 if (serviceName == m_currentService && m_playerInterface)
return;
138 qDebug() <<
" Connexion au lecteur :" << serviceName;
139 m_currentService = serviceName;
143 QDBusConnection::sessionBus().disconnect(QString(),
"/org/mpris/MediaPlayer2",
144 "org.freedesktop.DBus.Properties",
"PropertiesChanged",
145 this, SLOT(handleDBusSignal(QDBusMessage)));
147 delete m_playerInterface;
150 m_playerInterface =
new QDBusInterface(
152 "/org/mpris/MediaPlayer2",
153 "org.mpris.MediaPlayer2.Player",
154 QDBusConnection::sessionBus(),
159 QDBusConnection::sessionBus().connect(
161 "/org/mpris/MediaPlayer2",
162 "org.freedesktop.DBus.Properties",
165 SLOT(handleDBusSignal(QDBusMessage))
170 updatePlaybackStatus();
174void BluetoothManager::handleDBusSignal(
const QDBusMessage &msg) {
176 const QList<QVariant> args = msg.arguments();
177 if (args.size() < 2)
return;
179 const QString iface = args.at(0).toString();
180 if (iface !=
"org.mpris.MediaPlayer2.Player")
return;
182 const QVariantMap changed = unwrapVariant(args.at(1)).toMap();
184 if (changed.contains(
"Metadata")) {
185 parseMetadataMap(unwrapVariant(changed.value(
"Metadata")).toMap());
188 if (changed.contains(
"PlaybackStatus")) {
189 m_isPlaying = (unwrapVariant(changed.value(
"PlaybackStatus")).toString() ==
"Playing");
197 if (changed.contains(
"Position")) {
198 const qint64 posUs = unwrapVariant(changed.value(
"Position")).toLongLong();
199 m_positionMs = posUs / 1000;
204void BluetoothManager::updateMetadata() {
205 if (m_currentService.isEmpty())
return;
207 QDBusMessage msg = QDBusMessage::createMethodCall(
209 "/org/mpris/MediaPlayer2",
210 "org.freedesktop.DBus.Properties",
213 msg <<
"org.mpris.MediaPlayer2.Player" <<
"Metadata";
215 QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
216 if (reply.type() == QDBusMessage::ReplyMessage && !reply.arguments().isEmpty()) {
217 parseMetadataMap(unwrapVariant(reply.arguments().first()).toMap());
221void BluetoothManager::updatePlaybackStatus() {
222 if (m_currentService.isEmpty())
return;
224 QDBusMessage msg = QDBusMessage::createMethodCall(
226 "/org/mpris/MediaPlayer2",
227 "org.freedesktop.DBus.Properties",
230 msg <<
"org.mpris.MediaPlayer2.Player" <<
"PlaybackStatus";
232 QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
233 if (reply.type() == QDBusMessage::ReplyMessage && !reply.arguments().isEmpty()) {
234 m_isPlaying = (unwrapVariant(reply.arguments().first()).toString() ==
"Playing");
239void BluetoothManager::updatePosition() {
240 if (m_currentService.isEmpty())
return;
242 QDBusMessage msg = QDBusMessage::createMethodCall(
244 "/org/mpris/MediaPlayer2",
245 "org.freedesktop.DBus.Properties",
248 msg <<
"org.mpris.MediaPlayer2.Player" <<
"Position";
250 QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
251 if (reply.type() == QDBusMessage::ReplyMessage && !reply.arguments().isEmpty()) {
254 const qint64 posUs = unwrapVariant(reply.arguments().first()).toLongLong();
255 m_positionMs = posUs / 1000;
260void BluetoothManager::parseMetadataMap(
const QVariantMap &metadata) {
261 const QString newTitle = unwrapVariant(metadata.value(
"xesam:title")).toString();
262 const QString newAlbum = unwrapVariant(metadata.value(
"xesam:album")).toString();
265 const QVariant artistVar = unwrapVariant(metadata.value(
"xesam:artist"));
268 if (artistVar.canConvert<QStringList>()) {
269 newArtist = artistVar.toStringList().join(
", ");
270 }
else if (artistVar.typeId() == QMetaType::QVariantList) {
272 for (
const QVariant &it : artistVar.toList())
273 tmp << it.toString();
274 newArtist = tmp.join(
", ");
276 newArtist = artistVar.toString();
279 const qint64 lenUs = unwrapVariant(metadata.value(
"mpris:length")).toLongLong();
280 const qint64 newDurationMs = (lenUs > 0) ? (lenUs / 1000) : 0;
282 bool changed =
false;
285 if (!newTitle.isEmpty() && newTitle != m_title) { m_title = newTitle; changed =
true; }
286 if (newArtist != m_artist) { m_artist = newArtist; changed =
true; }
287 if (newAlbum != m_album) { m_album = newAlbum; changed =
true; }
288 if (newDurationMs != m_durationMs) { m_durationMs = newDurationMs; changed =
true; }
291 qDebug() <<
"🎵" << m_title <<
"-" << m_artist <<
"(" << m_album <<
")";
Rôle architectural : Interface de contrôle média Bluetooth via MPRIS/DBus.
void positionChanged()
Émis lorsque la position de lecture avance.
void togglePlay()
Bascule entre Lecture et Pause.
void next()
Passe à la piste suivante.
void previous()
Revient à la piste précédente.
BluetoothManager(QObject *parent=nullptr)
Constructeur du gestionnaire Bluetooth/Média.
void statusChanged()
Émis lorsque l'état de lecture change (Play -> Pause).
void metadataChanged()
Émis lorsque la chanson, l'artiste ou l'album change.