diff --git a/linux/media/mediacontroller.cpp b/linux/media/mediacontroller.cpp index 9ba1c80..531efd5 100644 --- a/linux/media/mediacontroller.cpp +++ b/linux/media/mediacontroller.cpp @@ -53,6 +53,7 @@ void MediaController::handleEarDetection(EarDetection *earDetection) { if (getCurrentMediaState() == Playing) { + LOG_DEBUG("Pausing playback for ear detection"); pause(); } } @@ -64,7 +65,7 @@ void MediaController::handleEarDetection(EarDetection *earDetection) activateA2dpProfile(); // Resume if conditions are met and we previously paused - if (shouldResume && wasPausedByApp && isActiveOutputDeviceAirPods()) + if (shouldResume && !pausedByAppServices.isEmpty() && isActiveOutputDeviceAirPods()) { play(); } @@ -211,6 +212,7 @@ void MediaController::activateA2dpProfile() { if (!m_pulseAudio->setCardProfile(m_deviceOutputName, preferredProfile)) { LOG_ERROR("Failed to activate A2DP profile: " << preferredProfile); } + LOG_INFO("A2DP profile activated successfully"); } void MediaController::removeAudioOutputDevice() { @@ -248,31 +250,53 @@ MediaController::MediaState MediaController::getCurrentMediaState() const return mediaStateFromPlayerctlOutput(PlayerStatusWatcher::getCurrentPlaybackStatus("")); } -bool MediaController::sendMediaPlayerCommand(const QString &method) +QStringList MediaController::getPlayingMediaPlayers() { - // Connect to the session bus + QStringList playingServices; QDBusConnection bus = QDBusConnection::sessionBus(); - // Find available MPRIS-compatible media players QStringList services = bus.interface()->registeredServiceNames().value(); - QStringList mprisServices; 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"); - return false; + LOG_INFO("No services to resume"); + return; } - bool success = false; - // Try each MPRIS service until one succeeds - for (const QString &service : mprisServices) + QDBusConnection bus = QDBusConnection::sessionBus(); + int resumedCount = 0; + + for (const QString &service : pausedByAppServices) { QDBusInterface playerInterface( service, @@ -282,63 +306,87 @@ bool MediaController::sendMediaPlayerCommand(const QString &method) if (!playerInterface.isValid()) { - LOG_ERROR("Invalid DBus interface for service: " << service); + LOG_WARN("Service no longer available: " << service); continue; } - // Send the Play or Pause command - if (method == "Play" || method == "Pause") + QDBusReply reply = playerInterface.call("Play"); + if (reply.isValid()) { - QDBusReply reply = playerInterface.call(method); - if (reply.isValid()) - { - 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()); - } + LOG_INFO("Resumed playback for: " << service); + resumedCount++; } else { - LOG_ERROR("Unsupported method: " << method); - return false; + LOG_ERROR("Failed to resume " << service << ": " << reply.error().message()); } } - if (!success) + if (resumedCount > 0) { - LOG_ERROR("No media player responded successfully to " << method); - } - return success; -} - -void MediaController::play() -{ - if (sendMediaPlayerCommand("Play")) - { - LOG_INFO("Resumed playback via DBus"); - wasPausedByApp = false; + LOG_INFO("Resumed " << resumedCount << " media player(s) via DBus"); + pausedByAppServices.clear(); } else { - LOG_ERROR("Failed to resume playback via DBus"); + LOG_ERROR("Failed to resume any media players via DBus"); } } 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"); - wasPausedByApp = true; + if (!service.startsWith("org.mpris.MediaPlayer2.")) + { + 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 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 { - LOG_ERROR("Failed to pause playback via DBus"); + LOG_INFO("No playing media players found to pause"); } } diff --git a/linux/media/mediacontroller.h b/linux/media/mediacontroller.h index 3601cc8..0a06440 100644 --- a/linux/media/mediacontroller.h +++ b/linux/media/mediacontroller.h @@ -55,9 +55,9 @@ Q_SIGNALS: private: MediaState mediaStateFromPlayerctlOutput(const QString &output) const; QString getAudioDeviceName(); - bool sendMediaPlayerCommand(const QString &method); + QStringList getPlayingMediaPlayers(); - bool wasPausedByApp = false; + QStringList pausedByAppServices; int initialVolume = -1; QString connectedDeviceMacAddress; EarDetectionBehavior earDetectionBehavior = PauseWhenOneRemoved; diff --git a/linux/media/pulseaudiocontroller.cpp b/linux/media/pulseaudiocontroller.cpp index dedaecb..d1535d0 100644 --- a/linux/media/pulseaudiocontroller.cpp +++ b/linux/media/pulseaudiocontroller.cpp @@ -157,7 +157,14 @@ bool PulseAudioController::setSinkVolume(const QString &sinkName, int volumePerc pa_cvolume_set(&volume, 2, (volumePercent * PA_VOLUME_NORM) / 100); 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(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); if (op) pa_operation_unref(op); pa_threaded_mainloop_unlock(m_mainloop); @@ -170,10 +177,16 @@ bool PulseAudioController::setCardProfile(const QString &cardName, const QString if (!m_initialized) return false; pa_threaded_mainloop_lock(m_mainloop); + + auto successCallback = [](pa_context *c, int success, void *userdata) { + pa_threaded_mainloop *mainloop = static_cast(userdata); + pa_threaded_mainloop_signal(mainloop, 0); + }; + pa_operation *op = pa_context_set_card_profile_by_name(m_context, cardName.toUtf8().constData(), profileName.toUtf8().constData(), - nullptr, nullptr); + successCallback, m_mainloop); bool success = waitForOperation(op); if (op) pa_operation_unref(op); pa_threaded_mainloop_unlock(m_mainloop);