/* SPDX-FileCopyrightText: 2012 Alex Merry SPDX-License-Identifier: LGPL-2.1-or-later */ #include "multiplexer.h" #include #include #include #include // for Q_ASSERT #include #include "debug.h" // the '@' at the start is not valid for D-Bus names, so it will // never interfere with an actual MPRIS2 player const QLatin1String Multiplexer::sourceName = QLatin1String("@multiplex"); Multiplexer::Multiplexer(QObject *parent) : DataContainer(parent) { setObjectName(sourceName); } void Multiplexer::evaluatePlayer(PlayerContainer *container) { bool makeActive = m_activeName.isEmpty(); QString name = container->objectName(); const QString containerPlaybackStatus = container->data().value(QStringLiteral("PlaybackStatus")).toString(); const QString multiplexerPlaybackStatus = data().value(QStringLiteral("PlaybackStatus")).toString(); m_playing.remove(name); m_paused.remove(name); m_stopped.remove(name); // Ensure the actual player is always in the correct category if (containerPlaybackStatus == QLatin1String("Playing")) { m_playing.insert(name, container); } else if (containerPlaybackStatus == QLatin1String("Paused")) { m_paused.insert(name, container); } else { m_stopped.insert(name, container); } const auto proxyPid = container->data().value(QStringLiteral("Metadata")).toMap().value(QStringLiteral("kde:pid")).toUInt(); if (proxyPid) { auto it = m_proxies.find(proxyPid); if (it == m_proxies.end()) { m_proxies.insert(proxyPid, container); } } const auto containerPid = container->data().value(QStringLiteral("InstancePid")).toUInt(); PlayerContainer *proxy = m_proxies.value(containerPid); if (proxy) { // Operate on the proxy from now on container = proxy; name = container->objectName(); } if (!makeActive) { // If this player has higher status than the current multiplexer player, switch over to it if (m_playing.value(name) && multiplexerPlaybackStatus != QLatin1String("Playing")) { qCDebug(MPRIS2) << "Player" << name << "is now playing but current was not"; makeActive = true; } else if (m_paused.value(name) && multiplexerPlaybackStatus != QLatin1String("Playing") && multiplexerPlaybackStatus != QLatin1String("Paused")) { qCDebug(MPRIS2) << "Player" << name << "is now paused but current was stopped"; makeActive = true; } } if (m_activeName == name) { // If we are the current player and move to a lower status, switch to another one, if necessary if (m_paused.value(name) && !m_playing.isEmpty()) { qCDebug(MPRIS2) << "Current player" << m_activeName << "is now paused but there is another playing one, switching players"; setBestActive(); makeActive = false; } else if (m_stopped.value(name) && (!m_playing.isEmpty() || !m_paused.isEmpty())) { qCDebug(MPRIS2) << "Current player" << m_activeName << "is now stopped but there is another playing or paused one, switching players"; setBestActive(); makeActive = false; } else { makeActive = true; } } if (makeActive) { if (m_activeName != name) { qCDebug(MPRIS2) << "Switching from" << m_activeName << "to" << name; m_activeName = name; } replaceData(container->data()); checkForUpdate(); Q_EMIT activePlayerChanged(container); } } void Multiplexer::addPlayer(PlayerContainer *container) { evaluatePlayer(container); connect(container, &Plasma::DataContainer::dataUpdated, this, &Multiplexer::playerUpdated); } void Multiplexer::removePlayer(const QString &name) { PlayerContainer *container = m_playing.take(name); if (!container) container = m_paused.take(name); if (!container) container = m_stopped.take(name); if (container) container->disconnect(this); // Remove proxy by value (container), not key (pid), which could have changed const auto pid = m_proxies.key(container); if (pid) { m_proxies.remove(pid); } if (name == m_activeName) { setBestActive(); } // When there is no player opened if (m_paused.empty() && m_stopped.empty() && m_playing.empty()) { Q_EMIT playerListEmptied(); } } PlayerContainer *Multiplexer::activePlayer() const { if (m_activeName.isEmpty()) { return nullptr; } PlayerContainer *container = m_playing.value(m_activeName); if (!container) container = m_paused.value(m_activeName); if (!container) container = m_stopped.value(m_activeName); Q_ASSERT(container); return container; } void Multiplexer::playerUpdated(const QString &name, const Plasma::DataEngine::Data &newData) { Q_UNUSED(name); Q_UNUSED(newData); evaluatePlayer(qobject_cast(sender())); } PlayerContainer *Multiplexer::firstPlayerFromHash(const QHash &hash, PlayerContainer **proxyCandidate) const { if (proxyCandidate) { *proxyCandidate = nullptr; } auto it = hash.begin(); if (it == hash.end()) { return nullptr; } PlayerContainer *container = it.value(); const auto containerPid = container->data().value(QStringLiteral("InstancePid")).toUInt(); // Check if this player is being proxied by someone else and prefer the proxy // but only if it is in the same hash (same state) if (PlayerContainer *proxy = m_proxies.value(containerPid)) { if (std::find(hash.begin(), hash.end(), proxy) == hash.end()) { if (proxyCandidate) { *proxyCandidate = proxy; } return nullptr; // continue; } return proxy; } return container; } void Multiplexer::setBestActive() { qCDebug(MPRIS2) << "Activating best player"; PlayerContainer *proxyCandidate = nullptr; PlayerContainer *container = firstPlayerFromHash(m_playing, &proxyCandidate); if (!container) { // If we found a proxy earlier, prefer it over a random other player in that category if (proxyCandidate && std::find(m_paused.constBegin(), m_paused.constEnd(), proxyCandidate) != m_paused.constEnd()) { container = proxyCandidate; } else { container = firstPlayerFromHash(m_paused, &proxyCandidate); } } if (!container) { if (proxyCandidate && std::find(m_stopped.constBegin(), m_stopped.constEnd(), proxyCandidate) != m_stopped.constEnd()) { container = proxyCandidate; } else { container = firstPlayerFromHash(m_stopped, &proxyCandidate); } } if (!container) { qCDebug(MPRIS2) << "There is currently no player"; m_activeName.clear(); removeAllData(); } else { m_activeName = container->objectName(); qCDebug(MPRIS2) << "Determined" << m_activeName << "to be the best player"; replaceData(container->data()); checkForUpdate(); } Q_EMIT activePlayerChanged(container); } void Multiplexer::replaceData(const Plasma::DataEngine::Data &data) { removeAllData(); Plasma::DataEngine::Data::const_iterator it = data.constBegin(); while (it != data.constEnd()) { setData(it.key(), it.value()); ++it; } setData(QStringLiteral("Source Name"), m_activeName); }