InterfaceGPS 0.1.0
Interface embarquée Qt pour navigation, multimédia, caméra et télémétrie
Chargement...
Recherche...
Aucune correspondance
bluetoothmanager.cpp
Aller à la documentation de ce fichier.
1
9#include "bluetoothmanager.h"
10#include <QDebug>
11#include <QDBusServiceWatcher>
12#include <QDBusConnectionInterface>
13#include <QDBusMetaType>
14#include <QDBusArgument>
15#include <QTimer>
16
24static QVariant unwrapVariant(const QVariant &var) {
25 if (var.userType() == qMetaTypeId<QDBusVariant>()) {
26 return unwrapVariant(var.value<QDBusVariant>().variant());
27 }
28
29 if (var.userType() == qMetaTypeId<QDBusArgument>()) {
30 QDBusArgument arg = var.value<QDBusArgument>();
31
32 if (arg.currentType() == QDBusArgument::VariantType) {
33 QVariant inner;
34 arg >> inner;
35 return unwrapVariant(inner);
36 }
37
38 if (arg.currentType() == QDBusArgument::MapType) {
39 QVariantMap map;
40 arg >> map;
41 for (auto it = map.begin(); it != map.end(); ++it)
42 it.value() = unwrapVariant(it.value());
43 return map;
44 }
45
46 if (arg.currentType() == QDBusArgument::ArrayType) {
47 QVariantList list;
48 arg >> list;
49 for (QVariant &v : list)
50 v = unwrapVariant(v);
51 return list;
52 }
53 }
54
55 if (var.typeId() == QMetaType::QVariantList) {
56 QVariantList list = var.toList();
57 for (QVariant &v : list)
58 v = unwrapVariant(v);
59 return list;
60 }
61
62 return var;
63}
64
65BluetoothManager::BluetoothManager(QObject *parent) : QObject(parent) {
66 // Enregistrement des types complexes requis par le système QtDBus
67 qDBusRegisterMetaType<QVariantMap>();
68 qDBusRegisterMetaType<QList<QVariant>>();
69
70 // Création d'un Watcher pour être alerté dès qu'un lecteur multimédia s'allume ou s'éteint
71 auto *watcher = new QDBusServiceWatcher(
72 "org.mpris.MediaPlayer2*",
73 QDBusConnection::sessionBus(),
74 QDBusServiceWatcher::WatchForRegistration | QDBusServiceWatcher::WatchForUnregistration,
75 this
76 );
77
78 connect(watcher, &QDBusServiceWatcher::serviceRegistered,
79 this, &BluetoothManager::connectToService);
80
81 connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
82 this, [this](const QString &service){
83 if (service == m_currentService) {
84 // Si le lecteur actuel se déconnecte, on réinitialise l'interface
85 m_title = "Déconnecté";
86 m_artist = "";
87 m_album = "";
88 m_isPlaying = false;
89 m_positionMs = 0;
90 m_durationMs = 0;
91
92 m_currentService.clear();
93 emit metadataChanged();
94 emit statusChanged();
95 emit positionChanged();
96
97 // On patiente un instant puis on cherche si un autre lecteur a pris le relais
98 QTimer::singleShot(300, this, &BluetoothManager::findActivePlayer);
99 }
100 });
101
102 findActivePlayer();
103}
104
105void BluetoothManager::findActivePlayer() {
106 QDBusConnectionInterface *bus = QDBusConnection::sessionBus().interface();
107 if (!bus) return;
108
109 const QStringList services = bus->registeredServiceNames();
110
111 // Priorité 1 : On cherche un "vrai" lecteur multimédia actif.
112 // Certains wrappers MPRIS (comme mpris-proxy) exposent des services éphémères ;
113 // ce filtre réduit les bascules de lecteur intempestives qui dégradent l'UX sur des reconnexions rapides.
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);
119 return;
120 }
121 }
122
123 // Priorité 2 (Repli) : Si aucun vrai lecteur n'est trouvé, on accepte les proxy Bluetooth.
124 // On préfère un lecteur potentiellement imparfait plutôt qu'une UI vide.
125 for (const QString &service : services) {
126 if (service.startsWith("org.mpris.MediaPlayer2.") &&
127 !service.endsWith(".mpris-proxy")) {
128 connectToService(service);
129 return;
130 }
131 }
132}
133
134void BluetoothManager::connectToService(const QString &serviceName) {
135 if (serviceName.isEmpty()) return;
136 if (serviceName == m_currentService && m_playerInterface) return;
137
138 qDebug() << " Connexion au lecteur :" << serviceName;
139 m_currentService = serviceName;
140
141 // Une seule subscription PropertiesChanged est conservée pour éviter les doublons de notifications
142 // (on nettoie l'ancienne écoute avant d'en créer une nouvelle).
143 QDBusConnection::sessionBus().disconnect(QString(), "/org/mpris/MediaPlayer2",
144 "org.freedesktop.DBus.Properties", "PropertiesChanged",
145 this, SLOT(handleDBusSignal(QDBusMessage)));
146
147 delete m_playerInterface;
148
149 // Création de l'interface qui nous permet d'envoyer des commandes (Play/Pause)
150 m_playerInterface = new QDBusInterface(
151 serviceName,
152 "/org/mpris/MediaPlayer2",
153 "org.mpris.MediaPlayer2.Player",
154 QDBusConnection::sessionBus(),
155 this
156 );
157
158 // Connexion pour écouter les changements asynchrones (ex: l'utilisateur a changé de musique sur son téléphone)
159 QDBusConnection::sessionBus().connect(
160 serviceName,
161 "/org/mpris/MediaPlayer2",
162 "org.freedesktop.DBus.Properties",
163 "PropertiesChanged",
164 this,
165 SLOT(handleDBusSignal(QDBusMessage))
166 );
167
168 // Initialisation immédiate de l'état (récupération des données courantes)
169 updateMetadata();
170 updatePlaybackStatus();
171 updatePosition();
172}
173
174void BluetoothManager::handleDBusSignal(const QDBusMessage &msg) {
175 // Un seul point d'entrée capte Metadata/PlaybackStatus/Position pour garder une synchronisation atomique.
176 const QList<QVariant> args = msg.arguments();
177 if (args.size() < 2) return;
178
179 const QString iface = args.at(0).toString();
180 if (iface != "org.mpris.MediaPlayer2.Player") return;
181
182 const QVariantMap changed = unwrapVariant(args.at(1)).toMap();
183
184 if (changed.contains("Metadata")) {
185 parseMetadataMap(unwrapVariant(changed.value("Metadata")).toMap());
186 }
187
188 if (changed.contains("PlaybackStatus")) {
189 m_isPlaying = (unwrapVariant(changed.value("PlaybackStatus")).toString() == "Playing");
190 emit statusChanged();
191
192 if (m_isPlaying) {
193 updatePosition();
194 }
195 }
196
197 if (changed.contains("Position")) {
198 const qint64 posUs = unwrapVariant(changed.value("Position")).toLongLong();
199 m_positionMs = posUs / 1000;
200 emit positionChanged();
201 }
202}
203
204void BluetoothManager::updateMetadata() {
205 if (m_currentService.isEmpty()) return;
206
207 QDBusMessage msg = QDBusMessage::createMethodCall(
208 m_currentService,
209 "/org/mpris/MediaPlayer2",
210 "org.freedesktop.DBus.Properties",
211 "Get"
212 );
213 msg << "org.mpris.MediaPlayer2.Player" << "Metadata";
214
215 QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
216 if (reply.type() == QDBusMessage::ReplyMessage && !reply.arguments().isEmpty()) {
217 parseMetadataMap(unwrapVariant(reply.arguments().first()).toMap());
218 }
219}
220
221void BluetoothManager::updatePlaybackStatus() {
222 if (m_currentService.isEmpty()) return;
223
224 QDBusMessage msg = QDBusMessage::createMethodCall(
225 m_currentService,
226 "/org/mpris/MediaPlayer2",
227 "org.freedesktop.DBus.Properties",
228 "Get"
229 );
230 msg << "org.mpris.MediaPlayer2.Player" << "PlaybackStatus";
231
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");
235 emit statusChanged();
236 }
237}
238
239void BluetoothManager::updatePosition() {
240 if (m_currentService.isEmpty()) return;
241
242 QDBusMessage msg = QDBusMessage::createMethodCall(
243 m_currentService,
244 "/org/mpris/MediaPlayer2",
245 "org.freedesktop.DBus.Properties",
246 "Get"
247 );
248 msg << "org.mpris.MediaPlayer2.Player" << "Position";
249
250 QDBusMessage reply = QDBusConnection::sessionBus().call(msg);
251 if (reply.type() == QDBusMessage::ReplyMessage && !reply.arguments().isEmpty()) {
252 // La position est fournie en microsecondes par MPRIS,
253 // on la convertit en millisecondes pour notre binding QML.
254 const qint64 posUs = unwrapVariant(reply.arguments().first()).toLongLong();
255 m_positionMs = posUs / 1000;
256 emit positionChanged();
257 }
258}
259
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();
263
264 QString newArtist;
265 const QVariant artistVar = unwrapVariant(metadata.value("xesam:artist"));
266
267 // L'artiste peut être un simple String ou une Liste de Strings (feat.)
268 if (artistVar.canConvert<QStringList>()) {
269 newArtist = artistVar.toStringList().join(", ");
270 } else if (artistVar.typeId() == QMetaType::QVariantList) {
271 QStringList tmp;
272 for (const QVariant &it : artistVar.toList())
273 tmp << it.toString();
274 newArtist = tmp.join(", ");
275 } else {
276 newArtist = artistVar.toString();
277 }
278
279 const qint64 lenUs = unwrapVariant(metadata.value("mpris:length")).toLongLong();
280 const qint64 newDurationMs = (lenUs > 0) ? (lenUs / 1000) : 0;
281
282 bool changed = false;
283
284 // On vérifie s'il y a eu un vrai changement avant d'émettre les signaux
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; }
289
290 if (changed) {
291 qDebug() << "🎵" << m_title << "-" << m_artist << "(" << m_album << ")";
292 emit metadataChanged();
293 }
294}
295
296void BluetoothManager::togglePlay() { if (m_playerInterface) m_playerInterface->call("PlayPause"); }
297void BluetoothManager::next() { if (m_playerInterface) m_playerInterface->call("Next"); }
298void BluetoothManager::previous() { if (m_playerInterface) m_playerInterface->call("Previous"); }
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.