mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-01-28 22:01:50 +00:00
linux: add support for multiple media players (#222)
also add wait for setting card profiles to complete
This commit is contained in:
@@ -53,6 +53,7 @@ void MediaController::handleEarDetection(EarDetection *earDetection)
|
|||||||
{
|
{
|
||||||
if (getCurrentMediaState() == Playing)
|
if (getCurrentMediaState() == Playing)
|
||||||
{
|
{
|
||||||
|
LOG_DEBUG("Pausing playback for ear detection");
|
||||||
pause();
|
pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,7 +65,7 @@ void MediaController::handleEarDetection(EarDetection *earDetection)
|
|||||||
activateA2dpProfile();
|
activateA2dpProfile();
|
||||||
|
|
||||||
// Resume if conditions are met and we previously paused
|
// Resume if conditions are met and we previously paused
|
||||||
if (shouldResume && wasPausedByApp && isActiveOutputDeviceAirPods())
|
if (shouldResume && !pausedByAppServices.isEmpty() && isActiveOutputDeviceAirPods())
|
||||||
{
|
{
|
||||||
play();
|
play();
|
||||||
}
|
}
|
||||||
@@ -211,6 +212,7 @@ void MediaController::activateA2dpProfile() {
|
|||||||
if (!m_pulseAudio->setCardProfile(m_deviceOutputName, preferredProfile)) {
|
if (!m_pulseAudio->setCardProfile(m_deviceOutputName, preferredProfile)) {
|
||||||
LOG_ERROR("Failed to activate A2DP profile: " << preferredProfile);
|
LOG_ERROR("Failed to activate A2DP profile: " << preferredProfile);
|
||||||
}
|
}
|
||||||
|
LOG_INFO("A2DP profile activated successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaController::removeAudioOutputDevice() {
|
void MediaController::removeAudioOutputDevice() {
|
||||||
@@ -248,31 +250,53 @@ MediaController::MediaState MediaController::getCurrentMediaState() const
|
|||||||
return mediaStateFromPlayerctlOutput(PlayerStatusWatcher::getCurrentPlaybackStatus(""));
|
return mediaStateFromPlayerctlOutput(PlayerStatusWatcher::getCurrentPlaybackStatus(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MediaController::sendMediaPlayerCommand(const QString &method)
|
QStringList MediaController::getPlayingMediaPlayers()
|
||||||
{
|
{
|
||||||
// Connect to the session bus
|
QStringList playingServices;
|
||||||
QDBusConnection bus = QDBusConnection::sessionBus();
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
|
||||||
// Find available MPRIS-compatible media players
|
|
||||||
QStringList services = bus.interface()->registeredServiceNames().value();
|
QStringList services = bus.interface()->registeredServiceNames().value();
|
||||||
QStringList mprisServices;
|
|
||||||
for (const QString &service : services)
|
for (const QString &service : services)
|
||||||
{
|
{
|
||||||
if (service.startsWith("org.mpris.MediaPlayer2."))
|
if (!service.startsWith("org.mpris.MediaPlayer2."))
|
||||||
{
|
{
|
||||||
mprisServices << service;
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusInterface playerInterface(
|
||||||
|
service,
|
||||||
|
"/org/mpris/MediaPlayer2",
|
||||||
|
"org.mpris.MediaPlayer2.Player",
|
||||||
|
bus);
|
||||||
|
|
||||||
|
if (!playerInterface.isValid())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant playbackStatus = playerInterface.property("PlaybackStatus");
|
||||||
|
if (playbackStatus.isValid() && playbackStatus.toString() == "Playing")
|
||||||
|
{
|
||||||
|
playingServices << service;
|
||||||
|
LOG_DEBUG("Found playing service: " << service);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mprisServices.isEmpty())
|
return playingServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MediaController::play()
|
||||||
|
{
|
||||||
|
if (pausedByAppServices.isEmpty())
|
||||||
{
|
{
|
||||||
LOG_ERROR("No MPRIS-compatible media players found on DBus");
|
LOG_INFO("No services to resume");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool success = false;
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
// Try each MPRIS service until one succeeds
|
int resumedCount = 0;
|
||||||
for (const QString &service : mprisServices)
|
|
||||||
|
for (const QString &service : pausedByAppServices)
|
||||||
{
|
{
|
||||||
QDBusInterface playerInterface(
|
QDBusInterface playerInterface(
|
||||||
service,
|
service,
|
||||||
@@ -282,63 +306,87 @@ bool MediaController::sendMediaPlayerCommand(const QString &method)
|
|||||||
|
|
||||||
if (!playerInterface.isValid())
|
if (!playerInterface.isValid())
|
||||||
{
|
{
|
||||||
LOG_ERROR("Invalid DBus interface for service: " << service);
|
LOG_WARN("Service no longer available: " << service);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the Play or Pause command
|
QDBusReply<void> reply = playerInterface.call("Play");
|
||||||
if (method == "Play" || method == "Pause")
|
if (reply.isValid())
|
||||||
{
|
{
|
||||||
QDBusReply<void> reply = playerInterface.call(method);
|
LOG_INFO("Resumed playback for: " << service);
|
||||||
if (reply.isValid())
|
resumedCount++;
|
||||||
{
|
|
||||||
LOG_INFO("Successfully sent " << method << " to " << service);
|
|
||||||
success = true;
|
|
||||||
break; // Exit after the first successful command
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LOG_ERROR("Failed to send " << method << " to " << service
|
|
||||||
<< ": " << reply.error().message());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_ERROR("Unsupported method: " << method);
|
LOG_ERROR("Failed to resume " << service << ": " << reply.error().message());
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!success)
|
if (resumedCount > 0)
|
||||||
{
|
{
|
||||||
LOG_ERROR("No media player responded successfully to " << method);
|
LOG_INFO("Resumed " << resumedCount << " media player(s) via DBus");
|
||||||
}
|
pausedByAppServices.clear();
|
||||||
return success;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MediaController::play()
|
|
||||||
{
|
|
||||||
if (sendMediaPlayerCommand("Play"))
|
|
||||||
{
|
|
||||||
LOG_INFO("Resumed playback via DBus");
|
|
||||||
wasPausedByApp = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_ERROR("Failed to resume playback via DBus");
|
LOG_ERROR("Failed to resume any media players via DBus");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MediaController::pause()
|
void MediaController::pause()
|
||||||
{
|
{
|
||||||
if (sendMediaPlayerCommand("Pause"))
|
QDBusConnection bus = QDBusConnection::sessionBus();
|
||||||
|
QStringList services = bus.interface()->registeredServiceNames().value();
|
||||||
|
|
||||||
|
pausedByAppServices.clear();
|
||||||
|
int pausedCount = 0;
|
||||||
|
|
||||||
|
for (const QString &service : services)
|
||||||
{
|
{
|
||||||
LOG_INFO("Paused playback via DBus");
|
if (!service.startsWith("org.mpris.MediaPlayer2."))
|
||||||
wasPausedByApp = true;
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusInterface playerInterface(
|
||||||
|
service,
|
||||||
|
"/org/mpris/MediaPlayer2",
|
||||||
|
"org.mpris.MediaPlayer2.Player",
|
||||||
|
bus);
|
||||||
|
|
||||||
|
if (!playerInterface.isValid())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant playbackStatus = playerInterface.property("PlaybackStatus");
|
||||||
|
LOG_DEBUG("PlaybackStatus for " << service << ": " << playbackStatus.toString());
|
||||||
|
if (!playbackStatus.isValid() || playbackStatus.toString() != "Playing")
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDBusReply<void> reply = playerInterface.call("Pause");
|
||||||
|
LOG_DEBUG("Pausing service: " << service);
|
||||||
|
if (reply.isValid())
|
||||||
|
{
|
||||||
|
LOG_INFO("Paused playback for: " << service);
|
||||||
|
pausedByAppServices << service;
|
||||||
|
pausedCount++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LOG_ERROR("Failed to pause " << service << ": " << reply.error().message());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pausedCount > 0)
|
||||||
|
{
|
||||||
|
LOG_INFO("Paused " << pausedCount << " media player(s) via DBus");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LOG_ERROR("Failed to pause playback via DBus");
|
LOG_INFO("No playing media players found to pause");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,9 +55,9 @@ Q_SIGNALS:
|
|||||||
private:
|
private:
|
||||||
MediaState mediaStateFromPlayerctlOutput(const QString &output) const;
|
MediaState mediaStateFromPlayerctlOutput(const QString &output) const;
|
||||||
QString getAudioDeviceName();
|
QString getAudioDeviceName();
|
||||||
bool sendMediaPlayerCommand(const QString &method);
|
QStringList getPlayingMediaPlayers();
|
||||||
|
|
||||||
bool wasPausedByApp = false;
|
QStringList pausedByAppServices;
|
||||||
int initialVolume = -1;
|
int initialVolume = -1;
|
||||||
QString connectedDeviceMacAddress;
|
QString connectedDeviceMacAddress;
|
||||||
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
|
EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved;
|
||||||
|
|||||||
@@ -157,7 +157,14 @@ bool PulseAudioController::setSinkVolume(const QString &sinkName, int volumePerc
|
|||||||
pa_cvolume_set(&volume, 2, (volumePercent * PA_VOLUME_NORM) / 100);
|
pa_cvolume_set(&volume, 2, (volumePercent * PA_VOLUME_NORM) / 100);
|
||||||
|
|
||||||
pa_threaded_mainloop_lock(m_mainloop);
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
pa_operation *op = pa_context_set_sink_volume_by_name(m_context, sinkName.toUtf8().constData(), &volume, nullptr, nullptr);
|
|
||||||
|
auto successCallback = [](pa_context *c, int success, void *userdata) {
|
||||||
|
pa_threaded_mainloop *mainloop = static_cast<pa_threaded_mainloop*>(userdata);
|
||||||
|
pa_threaded_mainloop_signal(mainloop, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
pa_operation *op = pa_context_set_sink_volume_by_name(m_context, sinkName.toUtf8().constData(), &volume, successCallback, m_mainloop);
|
||||||
|
|
||||||
bool success = waitForOperation(op);
|
bool success = waitForOperation(op);
|
||||||
if (op) pa_operation_unref(op);
|
if (op) pa_operation_unref(op);
|
||||||
pa_threaded_mainloop_unlock(m_mainloop);
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
@@ -170,10 +177,16 @@ bool PulseAudioController::setCardProfile(const QString &cardName, const QString
|
|||||||
if (!m_initialized) return false;
|
if (!m_initialized) return false;
|
||||||
|
|
||||||
pa_threaded_mainloop_lock(m_mainloop);
|
pa_threaded_mainloop_lock(m_mainloop);
|
||||||
|
|
||||||
|
auto successCallback = [](pa_context *c, int success, void *userdata) {
|
||||||
|
pa_threaded_mainloop *mainloop = static_cast<pa_threaded_mainloop*>(userdata);
|
||||||
|
pa_threaded_mainloop_signal(mainloop, 0);
|
||||||
|
};
|
||||||
|
|
||||||
pa_operation *op = pa_context_set_card_profile_by_name(m_context,
|
pa_operation *op = pa_context_set_card_profile_by_name(m_context,
|
||||||
cardName.toUtf8().constData(),
|
cardName.toUtf8().constData(),
|
||||||
profileName.toUtf8().constData(),
|
profileName.toUtf8().constData(),
|
||||||
nullptr, nullptr);
|
successCallback, m_mainloop);
|
||||||
bool success = waitForOperation(op);
|
bool success = waitForOperation(op);
|
||||||
if (op) pa_operation_unref(op);
|
if (op) pa_operation_unref(op);
|
||||||
pa_threaded_mainloop_unlock(m_mainloop);
|
pa_threaded_mainloop_unlock(m_mainloop);
|
||||||
|
|||||||
Reference in New Issue
Block a user