Changeset View
Changeset View
Standalone View
Standalone View
dataengines/mpris2/multiplexer.cpp
Show All 19 Lines | |||||
20 | #include "multiplexer.h" | 20 | #include "multiplexer.h" | ||
21 | #include <mprisplayer.h> | 21 | #include <mprisplayer.h> | ||
22 | 22 | | |||
23 | #include <KLocalizedString> | 23 | #include <KLocalizedString> | ||
24 | 24 | | |||
25 | #include <QDebug> // for Q_ASSERT | 25 | #include <QDebug> // for Q_ASSERT | ||
26 | #include <QAction> | 26 | #include <QAction> | ||
27 | 27 | | |||
28 | #include <algorithm> | ||||
29 | | ||||
30 | #include "debug.h" | ||||
28 | 31 | | |||
29 | // the '@' at the start is not valid for D-Bus names, so it will | 32 | // the '@' at the start is not valid for D-Bus names, so it will | ||
30 | // never interfere with an actual MPRIS2 player | 33 | // never interfere with an actual MPRIS2 player | ||
31 | const QLatin1String Multiplexer::sourceName = QLatin1String("@multiplex"); | 34 | const QLatin1String Multiplexer::sourceName = QLatin1String("@multiplex"); | ||
32 | 35 | | |||
33 | Multiplexer::Multiplexer(QObject* parent) | 36 | Multiplexer::Multiplexer(QObject* parent) | ||
34 | : DataContainer(parent) | 37 | : DataContainer(parent) | ||
35 | { | 38 | { | ||
36 | setObjectName(sourceName); | 39 | setObjectName(sourceName); | ||
37 | } | 40 | } | ||
38 | 41 | | |||
39 | void Multiplexer::addPlayer(PlayerContainer *container) | 42 | void Multiplexer::evaluatePlayer(PlayerContainer *container) | ||
40 | { | 43 | { | ||
41 | bool makeActive = m_activeName.isEmpty(); | 44 | bool makeActive = m_activeName.isEmpty(); | ||
42 | 45 | | |||
43 | if (container->data().value(QStringLiteral("PlaybackStatus")) == QLatin1String("Playing")) { | 46 | QString name = container->objectName(); | ||
44 | m_playing.insert(container->objectName(), container); | 47 | const QString containerPlaybackStatus = container->data().value(QStringLiteral("PlaybackStatus")).toString(); | ||
45 | if (!makeActive && | 48 | const QString multiplexerPlaybackStatus = data().value(QStringLiteral("PlaybackStatus")).toString(); | ||
46 | data().value(QStringLiteral("PlaybackStatus")) != QLatin1String("Playing")) { | 49 | | ||
47 | makeActive = true; | 50 | m_playing.remove(name); | ||
51 | m_paused.remove(name); | ||||
52 | m_stopped.remove(name); | ||||
53 | | ||||
54 | // Ensure the actual player is always in the correct category | ||||
55 | if (containerPlaybackStatus == QLatin1String("Playing")) { | ||||
56 | m_playing.insert(name, container); | ||||
57 | } else if (containerPlaybackStatus == QLatin1String("Paused")) { | ||||
58 | m_paused.insert(name, container); | ||||
59 | } else { | ||||
60 | m_stopped.insert(name, container); | ||||
61 | } | ||||
62 | | ||||
63 | const auto proxyPid = container->data().value(QStringLiteral("Metadata")).toMap().value(QStringLiteral("kde:pid")).toUInt(); | ||||
64 | if (proxyPid) { | ||||
65 | auto it = m_proxies.find(proxyPid); | ||||
66 | if (it == m_proxies.end()) { | ||||
67 | m_proxies.insert(proxyPid, container); | ||||
68 | } | ||||
48 | } | 69 | } | ||
49 | } else if (container->data().value(QStringLiteral("PlaybackStatus")) == QLatin1String("Paused")) { | 70 | | ||
50 | m_paused.insert(container->objectName(), container); | 71 | const auto containerPid = container->data().value(QStringLiteral("InstancePid")).toUInt(); | ||
51 | if (!makeActive && | 72 | PlayerContainer *proxy = m_proxies.value(containerPid); | ||
52 | data().value(QStringLiteral("PlaybackStatus")) != QLatin1String("Playing") && | 73 | if (proxy) { | ||
53 | data().value(QStringLiteral("PlaybackStatus")) != QLatin1String("Paused")) { | 74 | // Operate on the proxy from now on | ||
75 | container = proxy; | ||||
76 | name = container->objectName(); | ||||
77 | } | ||||
78 | | ||||
79 | if (!makeActive) { | ||||
80 | // If this player has higher status than the current multiplexer player, switch over to it | ||||
81 | if (m_playing.value(name) && multiplexerPlaybackStatus != QLatin1String("Playing")) { | ||||
82 | qCDebug(MPRIS2) << "Player" << name << "is now playing but current was not"; | ||||
83 | makeActive = true; | ||||
84 | } else if (m_paused.value(name) | ||||
85 | && multiplexerPlaybackStatus != QLatin1String("Playing") | ||||
86 | && multiplexerPlaybackStatus != QLatin1String("Paused")) { | ||||
87 | qCDebug(MPRIS2) << "Player" << name << "is now paused but current was stopped"; | ||||
54 | makeActive = true; | 88 | makeActive = true; | ||
55 | } | 89 | } | ||
56 | } else { | | |||
57 | m_stopped.insert(container->objectName(), container); | | |||
58 | } | 90 | } | ||
59 | 91 | | |||
60 | connect(container, &Plasma::DataContainer::dataUpdated, | 92 | if (m_activeName == name) { | ||
61 | this, &Multiplexer::playerUpdated); | 93 | // If we are the current player and move to a lower status, switch to another one, if necessary | ||
94 | if (m_paused.value(name) && !m_playing.isEmpty()) { | ||||
95 | qCDebug(MPRIS2) << "Current player" << m_activeName << "is now paused but there is another playing one, switching players"; | ||||
96 | setBestActive(); | ||||
97 | makeActive = false; | ||||
98 | } else if (m_stopped.value(name) && (!m_playing.isEmpty() || !m_paused.isEmpty())) { | ||||
99 | qCDebug(MPRIS2) << "Current player" << m_activeName << "is now stopped but there is another playing or paused one, switching players"; | ||||
100 | setBestActive(); | ||||
101 | makeActive = false; | ||||
102 | } else { | ||||
103 | makeActive = true; | ||||
104 | } | ||||
105 | } | ||||
62 | 106 | | |||
63 | if (makeActive) { | 107 | if (makeActive) { | ||
64 | m_activeName = container->objectName(); | 108 | if (m_activeName != name) { | ||
109 | qCDebug(MPRIS2) << "Switching from" << m_activeName << "to" << name; | ||||
110 | m_activeName = name; | ||||
111 | } | ||||
65 | replaceData(container->data()); | 112 | replaceData(container->data()); | ||
66 | checkForUpdate(); | 113 | checkForUpdate(); | ||
67 | emit activePlayerChanged(container); | 114 | emit activePlayerChanged(container); | ||
68 | } | 115 | } | ||
69 | } | 116 | } | ||
70 | 117 | | |||
118 | void Multiplexer::addPlayer(PlayerContainer *container) | ||||
119 | { | ||||
120 | evaluatePlayer(container); | ||||
121 | | ||||
122 | connect(container, &Plasma::DataContainer::dataUpdated, | ||||
123 | this, &Multiplexer::playerUpdated); | ||||
124 | } | ||||
125 | | ||||
71 | void Multiplexer::removePlayer(const QString &name) | 126 | void Multiplexer::removePlayer(const QString &name) | ||
72 | { | 127 | { | ||
73 | PlayerContainer *container = m_playing.take(name); | 128 | PlayerContainer *container = m_playing.take(name); | ||
74 | if (!container) | 129 | if (!container) | ||
75 | container = m_paused.take(name); | 130 | container = m_paused.take(name); | ||
76 | if (!container) | 131 | if (!container) | ||
77 | container = m_stopped.take(name); | 132 | container = m_stopped.take(name); | ||
78 | if (container) | 133 | if (container) | ||
79 | container->disconnect(this); | 134 | container->disconnect(this); | ||
80 | 135 | | |||
136 | // Remove proxy by value (container), not key (pid), which could have changed | ||||
137 | const auto pid = m_proxies.key(container); | ||||
138 | if (pid) { | ||||
139 | m_proxies.remove(pid); | ||||
140 | } | ||||
141 | | ||||
81 | if (name == m_activeName) { | 142 | if (name == m_activeName) { | ||
82 | setBestActive(); | 143 | setBestActive(); | ||
83 | } | 144 | } | ||
84 | } | 145 | } | ||
85 | 146 | | |||
86 | PlayerContainer *Multiplexer::activePlayer() const | 147 | PlayerContainer *Multiplexer::activePlayer() const | ||
87 | { | 148 | { | ||
88 | if (m_activeName.isEmpty()) { | 149 | if (m_activeName.isEmpty()) { | ||
89 | return nullptr; | 150 | return nullptr; | ||
90 | } | 151 | } | ||
91 | 152 | | |||
92 | PlayerContainer *container = m_playing.value(m_activeName); | 153 | PlayerContainer *container = m_playing.value(m_activeName); | ||
93 | if (!container) | 154 | if (!container) | ||
94 | container = m_paused.value(m_activeName); | 155 | container = m_paused.value(m_activeName); | ||
95 | if (!container) | 156 | if (!container) | ||
96 | container = m_stopped.value(m_activeName); | 157 | container = m_stopped.value(m_activeName); | ||
97 | Q_ASSERT(container); | 158 | Q_ASSERT(container); | ||
98 | return container; | 159 | return container; | ||
99 | } | 160 | } | ||
100 | 161 | | |||
101 | void Multiplexer::playerUpdated(const QString &name, const Plasma::DataEngine::Data &newData) | 162 | void Multiplexer::playerUpdated(const QString &name, const Plasma::DataEngine::Data &newData) | ||
102 | { | 163 | { | ||
103 | if (newData.value(QStringLiteral("PlaybackStatus")) == QLatin1String("Playing")) { | 164 | Q_UNUSED(name); | ||
104 | if (!m_playing.contains(name)) { | 165 | Q_UNUSED(newData); | ||
105 | PlayerContainer *container = m_paused.take(name); | 166 | evaluatePlayer(qobject_cast<PlayerContainer *>(sender())); | ||
106 | if (!container) { | | |||
107 | container = m_stopped.take(name); | | |||
108 | } | | |||
109 | Q_ASSERT(container); | | |||
110 | m_playing.insert(name, container); | | |||
111 | } | 167 | } | ||
112 | if (m_activeName != name && | 168 | | ||
113 | data().value(QStringLiteral("PlaybackStatus")) != QLatin1String("Playing")) { | 169 | PlayerContainer *Multiplexer::firstPlayerFromHash(const QHash<QString, PlayerContainer *> &hash, PlayerContainer **proxyCandidate) const | ||
114 | m_activeName = name; | 170 | { | ||
115 | replaceData(newData); | 171 | if (proxyCandidate) { | ||
116 | checkForUpdate(); | 172 | *proxyCandidate = nullptr; | ||
117 | emit activePlayerChanged(activePlayer()); | | |||
118 | return; | | |||
119 | } | | |||
120 | } else if (newData.value(QStringLiteral("PlaybackStatus")) == QLatin1String("Paused")) { | | |||
121 | if (!m_paused.contains(name)) { | | |||
122 | PlayerContainer *container = m_playing.take(name); | | |||
123 | if (!container) { | | |||
124 | container = m_stopped.take(name); | | |||
125 | } | | |||
126 | Q_ASSERT(container); | | |||
127 | m_paused.insert(name, container); | | |||
128 | } | 173 | } | ||
129 | if (m_activeName != name && | 174 | | ||
130 | data().value(QStringLiteral("PlaybackStatus")) != QLatin1String("Playing") && | 175 | auto it = hash.begin(); | ||
131 | data().value(QStringLiteral("PlaybackStatus")) != QLatin1String("Paused")) { | 176 | if (it == hash.end()) { | ||
132 | m_activeName = name; | 177 | return nullptr; | ||
133 | replaceData(newData); | | |||
134 | checkForUpdate(); | | |||
135 | emit activePlayerChanged(activePlayer()); | | |||
136 | return; | | |||
137 | } | 178 | } | ||
138 | } else { | 179 | | ||
139 | if (!m_stopped.contains(name)) { | 180 | PlayerContainer *container = it.value(); | ||
140 | PlayerContainer *container = m_playing.take(name); | 181 | const auto containerPid = container->data().value(QStringLiteral("InstancePid")).toUInt(); | ||
141 | if (!container) { | 182 | | ||
142 | container = m_paused.take(name); | 183 | // Check if this player is being proxied by someone else and prefer the proxy | ||
184 | // but only if it is in the same hash (same state) | ||||
185 | if (PlayerContainer *proxy = m_proxies.value(containerPid)) { | ||||
186 | if (std::find(hash.begin(), hash.end(), proxy) == hash.end()) { | ||||
187 | if (proxyCandidate) { | ||||
188 | *proxyCandidate = proxy; | ||||
143 | } | 189 | } | ||
144 | Q_ASSERT(container); | 190 | return nullptr; | ||
145 | m_stopped.insert(name, container); | 191 | //continue; | ||
146 | } | 192 | } | ||
193 | return proxy; | ||||
147 | } | 194 | } | ||
148 | 195 | | |||
149 | if (m_activeName == name) { | 196 | return container; | ||
150 | bool isPaused = newData.value(QStringLiteral("PlaybackStatus")) == QLatin1String("Paused"); | | |||
151 | bool isStopped = !isPaused && newData.value(QStringLiteral("PlaybackStatus")) != QLatin1String("Playing"); | | |||
152 | if (isPaused && !m_playing.isEmpty()) { | | |||
153 | setBestActive(); | | |||
154 | } else if (isStopped && (!m_playing.isEmpty() || !m_paused.isEmpty())) { | | |||
155 | setBestActive(); | | |||
156 | } else { | | |||
157 | replaceData(newData); | | |||
158 | checkForUpdate(); | | |||
159 | } | | |||
160 | } | | |||
161 | } | 197 | } | ||
162 | 198 | | |||
163 | void Multiplexer::setBestActive() | 199 | void Multiplexer::setBestActive() | ||
164 | { | 200 | { | ||
165 | QHash<QString,PlayerContainer*>::const_iterator it = m_playing.constBegin(); | 201 | qCDebug(MPRIS2) << "Activating best player"; | ||
166 | if (it != m_playing.constEnd()) { | 202 | PlayerContainer *proxyCandidate = nullptr; | ||
167 | m_activeName = it.key(); | 203 | | ||
168 | replaceData(it.value()->data()); | 204 | PlayerContainer *container = firstPlayerFromHash(m_playing, &proxyCandidate); | ||
169 | emit activePlayerChanged(it.value()); | 205 | if (!container) { | ||
170 | } else { | 206 | // If we found a proxy earlier, prefer it over a random other player in that category | ||
171 | it = m_paused.constBegin(); | 207 | if (proxyCandidate && std::find(m_paused.constBegin(), m_paused.constEnd(), proxyCandidate) != m_paused.constEnd()) { | ||
172 | if (it != m_paused.constEnd()) { | 208 | container = proxyCandidate; | ||
173 | m_activeName = it.key(); | | |||
174 | replaceData(it.value()->data()); | | |||
175 | emit activePlayerChanged(it.value()); | | |||
176 | } else { | | |||
177 | it = m_stopped.constBegin(); | | |||
178 | if (it != m_stopped.constEnd()) { | | |||
179 | m_activeName = it.key(); | | |||
180 | replaceData(it.value()->data()); | | |||
181 | emit activePlayerChanged(it.value()); | | |||
182 | } else { | 209 | } else { | ||
183 | m_activeName = QString(); | 210 | container = firstPlayerFromHash(m_paused, &proxyCandidate); | ||
184 | removeAllData(); | 211 | } | ||
185 | emit activePlayerChanged(nullptr); | | |||
186 | } | 212 | } | ||
213 | if (!container) { | ||||
214 | if (proxyCandidate && std::find(m_stopped.constBegin(), m_stopped.constEnd(), proxyCandidate) != m_stopped.constEnd()) { | ||||
215 | container = proxyCandidate; | ||||
216 | } else { | ||||
217 | container = firstPlayerFromHash(m_stopped, &proxyCandidate); | ||||
187 | } | 218 | } | ||
188 | } | 219 | } | ||
220 | | ||||
221 | if (!container) { | ||||
222 | qCDebug(MPRIS2) << "There is currently no player"; | ||||
223 | m_activeName.clear(); | ||||
224 | removeAllData(); | ||||
225 | } else { | ||||
226 | m_activeName = container->objectName(); | ||||
227 | qCDebug(MPRIS2) << "Determined" << m_activeName << "to be the best player"; | ||||
228 | replaceData(container->data()); | ||||
189 | checkForUpdate(); | 229 | checkForUpdate(); | ||
190 | } | 230 | } | ||
191 | 231 | | |||
232 | emit activePlayerChanged(container); | ||||
233 | } | ||||
234 | | ||||
192 | void Multiplexer::replaceData(const Plasma::DataEngine::Data &data) | 235 | void Multiplexer::replaceData(const Plasma::DataEngine::Data &data) | ||
193 | { | 236 | { | ||
194 | removeAllData(); | 237 | removeAllData(); | ||
195 | 238 | | |||
196 | Plasma::DataEngine::Data::const_iterator it = data.constBegin(); | 239 | Plasma::DataEngine::Data::const_iterator it = data.constBegin(); | ||
197 | while (it != data.constEnd()) { | 240 | while (it != data.constEnd()) { | ||
198 | setData(it.key(), it.value()); | 241 | setData(it.key(), it.value()); | ||
199 | ++it; | 242 | ++it; | ||
200 | } | 243 | } | ||
201 | setData(QStringLiteral("Source Name"), m_activeName); | 244 | setData(QStringLiteral("Source Name"), m_activeName); | ||
202 | } | 245 | } | ||
203 | 246 | |