Merge pull request #2656 from Mathew-D/v5

fix(media): MPRIS change to async
This commit is contained in:
Lemmy
2026-05-10 17:16:07 -04:00
committed by GitHub
4 changed files with 730 additions and 496 deletions
File diff suppressed because it is too large Load Diff
+16 -1
View File
@@ -2,7 +2,9 @@
#include <chrono>
#include <cstdint>
#include <deque>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
@@ -11,8 +13,10 @@
#include <vector>
namespace sdbus {
class Error;
class IObject;
class IProxy;
class Variant;
} // namespace sdbus
class SessionBus;
@@ -98,16 +102,24 @@ private:
void syncSignals(const std::optional<MprisPlayerInfo>& previousActive);
void registerBusSignals();
void discoverPlayers();
void scheduleDiscoveryDrain();
void scheduleStartupRediscovery();
void scheduleRecoveryDiscovery();
void addOrRefreshPlayer(const std::string& busName);
void applyPlayerSnapshot(const std::string& busName, const MprisPlayerInfo& info, bool hadPositionSignal);
void refreshPlayerPosition(const std::string& busName, bool notifyChange);
void applyPositionSample(const std::string& busName, int64_t rawPositionUs, bool notifyChange);
void removePlayer(const std::string& busName);
[[nodiscard]] MprisPlayerInfo readPlayerInfo(sdbus::IProxy& proxy, const std::string& busName) const;
[[nodiscard]] MprisPlayerInfo
readPlayerInfoFromProperties(const std::string& busName, const std::map<std::string, sdbus::Variant>& rootProps,
const std::map<std::string, sdbus::Variant>& playerProps) const;
[[nodiscard]] MprisPlayerInfo projectedPlayerInfo(const MprisPlayerInfo& player) const;
[[nodiscard]] std::int64_t projectedPositionUs(const MprisPlayerInfo& player) const;
[[nodiscard]] std::optional<std::string> chooseActivePlayer() const;
[[nodiscard]] bool isBlacklisted(const MprisPlayerInfo& player) const;
std::function<void(std::optional<sdbus::Error>)> makeAsyncReplyHandler(std::string op, std::string busName);
std::function<void(std::optional<sdbus::Error>)> makeAsyncReplyHandler(std::string op, std::string busName,
std::string_view method);
[[nodiscard]] bool callPlayerMethod(const std::string& busName, const char* methodName);
[[nodiscard]] bool canInvoke(const MprisPlayerInfo& player, const char* methodName) const;
@@ -141,6 +153,7 @@ private:
[[nodiscard]] std::tuple<bool, std::string, std::vector<std::string>> onGetPlayerPreferences() const;
SessionBus& m_bus;
std::shared_ptr<void> m_aliveGuard = std::make_shared<int>(0);
std::unique_ptr<sdbus::IObject> m_controlObject;
std::unique_ptr<sdbus::IProxy> m_dbusProxy;
std::unordered_map<std::string, std::unique_ptr<sdbus::IProxy>> m_playerProxies;
@@ -161,6 +174,7 @@ private:
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_lastPropertiesUpdate;
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_lastPlayingUpdate;
std::unordered_map<std::string, std::chrono::steady_clock::time_point> m_lastStrongMetadataUpdate;
std::deque<std::string> m_pendingDiscoveryBusNames;
std::string m_lastActivePlayer;
std::string m_lastEmittedActivePlayer;
std::optional<std::string> m_pinnedPlayerPreference;
@@ -169,4 +183,5 @@ private:
std::function<void()> m_changeCallback;
int m_startupRediscoveryPassesRemaining = 4;
bool m_recoveryDiscoveryScheduled = false;
bool m_discoveryDrainScheduled = false;
};
+84 -46
View File
@@ -1,5 +1,6 @@
#include "shell/control_center/media_tab.h"
#include "core/deferred_call.h"
#include "core/log.h"
#include "dbus/mpris/mpris_art.h"
#include "dbus/mpris/mpris_service.h"
@@ -71,7 +72,7 @@ MediaTab::MediaTab(MprisService* mpris, HttpClient* httpClient, PipeWireSpectrum
: m_mpris(mpris), m_httpClient(httpClient), m_spectrum(spectrum), m_wayland(wayland),
m_renderContext(renderContext) {}
MediaTab::~MediaTab() = default;
MediaTab::~MediaTab() { m_aliveGuard.reset(); }
void MediaTab::openPlayerMenu() {
if (m_playerMenuPopup == nullptr || m_mpris == nullptr || m_playerMenuButton == nullptr) {
@@ -270,17 +271,19 @@ std::unique_ptr<Flex> MediaTab::create() {
m_pendingSeekBusName = seekBusName;
m_pendingSeekUs = targetUs;
m_pendingSeekUntil = now + std::chrono::milliseconds(3000);
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
bool seekIssued = false;
if (!seekBusName.empty()) {
seekIssued = m_mpris->setPosition(seekBusName, targetUs);
} else {
seekIssued = m_mpris->setPositionActive(targetUs);
}
if (!seekIssued) {
// Keep the thumb stable briefly even if transport seek dispatch races.
m_pendingSeekUntil = now + std::chrono::milliseconds(750);
}
DeferredCall::callLater([this, aliveGuard, seekBusName, targetUs]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
if (!seekBusName.empty()) {
(void)m_mpris->setPosition(seekBusName, targetUs);
} else {
(void)m_mpris->setPositionActive(targetUs);
}
PanelManager::instance().refresh();
});
});
m_progressSlider = progress.get();
mediaStack->addChild(std::move(progress));
@@ -305,13 +308,16 @@ std::unique_ptr<Flex> MediaTab::create() {
repeat->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
repeat->setRadius(Style::radiusLg * scale);
repeat->setOnClick([this]() {
if (m_mpris == nullptr) {
return;
}
const auto current = m_mpris->loopStatusActive().value_or("None");
const std::string next = current == "None" ? "Playlist" : (current == "Playlist" ? "Track" : "None");
m_mpris->setLoopStatusActive(next);
PanelManager::instance().refresh();
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
const auto current = m_mpris->loopStatusActive().value_or("None");
const std::string next = current == "None" ? "Playlist" : (current == "Playlist" ? "Track" : "None");
(void)m_mpris->setLoopStatusActive(next);
PanelManager::instance().refresh();
});
});
m_repeatButton = repeat.get();
controls->addChild(std::move(repeat));
@@ -324,10 +330,14 @@ std::unique_ptr<Flex> MediaTab::create() {
previous->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
previous->setRadius(Style::radiusLg * scale);
previous->setOnClick([this]() {
if (m_mpris != nullptr) {
m_mpris->previousActive();
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
(void)m_mpris->previousActive();
PanelManager::instance().refresh();
}
});
});
m_prevButton = previous.get();
controls->addChild(std::move(previous));
@@ -340,10 +350,14 @@ std::unique_ptr<Flex> MediaTab::create() {
playPause->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
playPause->setRadius(Style::radiusLg * scale);
playPause->setOnClick([this]() {
if (m_mpris != nullptr) {
m_mpris->playPauseActive();
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
(void)m_mpris->playPauseActive();
PanelManager::instance().refresh();
}
});
});
m_playPauseButton = playPause.get();
controls->addChild(std::move(playPause));
@@ -356,10 +370,14 @@ std::unique_ptr<Flex> MediaTab::create() {
next->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
next->setRadius(Style::radiusLg * scale);
next->setOnClick([this]() {
if (m_mpris != nullptr) {
m_mpris->nextActive();
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
(void)m_mpris->nextActive();
PanelManager::instance().refresh();
}
});
});
m_nextButton = next.get();
controls->addChild(std::move(next));
@@ -372,11 +390,15 @@ std::unique_ptr<Flex> MediaTab::create() {
shuffle->setPadding(Style::spaceSm * scale, Style::spaceSm * scale);
shuffle->setRadius(Style::radiusLg * scale);
shuffle->setOnClick([this]() {
if (m_mpris != nullptr) {
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
const bool enabled = m_mpris->shuffleActive().value_or(false);
m_mpris->setShuffleActive(!enabled);
(void)m_mpris->setShuffleActive(!enabled);
PanelManager::instance().refresh();
}
});
});
m_shuffleButton = shuffle.get();
controls->addChild(std::move(shuffle));
@@ -419,17 +441,21 @@ std::unique_ptr<Flex> MediaTab::create() {
if (m_wayland != nullptr && m_renderContext != nullptr) {
m_playerMenuPopup = std::make_unique<ContextMenuPopup>(*m_wayland, *m_renderContext);
m_playerMenuPopup->setOnActivate([this](const ContextMenuControlEntry& entry) {
if (m_mpris == nullptr) {
return;
}
if (entry.id == 0) {
m_mpris->clearPinnedPlayerPreference();
} else {
const std::size_t idx = static_cast<std::size_t>(entry.id - 1);
if (idx < m_playerBusNames.size()) {
m_mpris->setPinnedPlayerPreference(m_playerBusNames[idx]);
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard, entry]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
}
if (entry.id == 0) {
m_mpris->clearPinnedPlayerPreference();
} else {
const std::size_t idx = static_cast<std::size_t>(entry.id - 1);
if (idx < m_playerBusNames.size()) {
m_mpris->setPinnedPlayerPreference(m_playerBusNames[idx]);
}
}
PanelManager::instance().refresh();
});
});
}
@@ -603,7 +629,15 @@ void MediaTab::setActive(bool active) {
// Pull a fresh snapshot (including Position) when the tab opens so the
// progress slider starts at the current playback position.
m_positionSampleAt = {};
m_mpris->refreshPlayers();
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
m_mpris->refreshPlayers();
PanelManager::instance().requestUpdateOnly();
PanelManager::instance().requestRedraw();
});
m_lastMprisRefreshAttempt = std::chrono::steady_clock::now();
}
}
@@ -676,11 +710,15 @@ void MediaTab::refresh(Renderer& renderer) {
if (shouldRetryMpris) {
m_lastMprisRefreshAttempt = now;
kLog.debug("media tab retrying mpris discovery players={} active={}", players.size(), active.has_value());
m_mpris->refreshPlayers();
players = m_mpris->listPlayers();
active = m_mpris->activePlayer();
kLog.debug("media tab refresh after retry players={} active={} active_bus=\"{}\"", players.size(),
active.has_value(), active.has_value() ? active->busName : std::string{});
const std::weak_ptr<void> aliveGuard = m_aliveGuard;
DeferredCall::callLater([this, aliveGuard]() {
if (aliveGuard.expired() || m_mpris == nullptr) {
return;
}
m_mpris->refreshPlayers();
PanelManager::instance().requestUpdateOnly();
PanelManager::instance().requestRedraw();
});
}
}
+5
View File
@@ -43,6 +43,11 @@ private:
void openPlayerMenu();
// Guard token for deferred callbacks that run on the next main-loop tick.
// Callbacks capture a weak_ptr so they can detect destruction without
// relying on a raw this pointer staying valid.
std::shared_ptr<void> m_aliveGuard = std::make_shared<int>(0);
MprisService* m_mpris = nullptr;
HttpClient* m_httpClient = nullptr;
PipeWireSpectrum* m_spectrum = nullptr;