diff --git a/client/player/pulse_player.cpp b/client/player/pulse_player.cpp index d6c76a19..8f687b40 100644 --- a/client/player/pulse_player.cpp +++ b/client/player/pulse_player.cpp @@ -177,15 +177,32 @@ bool PulsePlayer::needsThread() const return true; } - void PulsePlayer::worker() { - pa_mainloop_run(pa_ml_, nullptr); -} + while (active_) + { + pa_mainloop_run(pa_ml_, nullptr); + + // if we are still active, wait for a chunk and attempt to reconnect + while (active_ && !stream_->waitForChunk(100ms)) + { + LOG(DEBUG, LOG_TAG) << "Waiting for a chunk to become available before reconnecting\n"; + } + if (active_) + { + LOG(INFO, LOG_TAG) << "Chunk available, reconnecting to pulse\n"; + this->connect(); + } + } +} void PulsePlayer::setHardwareVolume(double volume, bool muted) { + // if we're not connected to pulse, ignore the hardware volume change as the volume will be set upon reconnect + if (playstream_ == nullptr) + return; + last_change_ = std::chrono::steady_clock::now(); pa_cvolume cvolume; if (muted) @@ -321,19 +338,38 @@ void PulsePlayer::writeCallback(pa_stream* stream, size_t nbytes) // LOG(TRACE, LOG_TAG) << "writeCallback latency " << usec << " us, frames: " << numFrames << "\n"; if (!stream_->getPlayerChunkOrSilence(buffer_.data(), std::chrono::microseconds(usec), numFrames)) { + // if we haven't got a chunk for a while, it's time to disconnect from pulse so the sound device + // can become idle/suspended. + if (chronos::getTickCount() - last_chunk_tick_ > 5000) + { + LOG(INFO, LOG_TAG) << "No chunk received for 5000ms, disconnecting from pulse.\n"; + this->disconnect(); + return; + } // LOG(TRACE, LOG_TAG) << "Failed to get chunk. Playing silence.\n"; } else { + last_chunk_tick_ = chronos::getTickCount(); adjustVolume(static_cast(buffer_.data()), numFrames); } pa_stream_write(stream, buffer_.data(), nbytes, nullptr, 0LL, PA_SEEK_RELATIVE); } - void PulsePlayer::start() { + LOG(INFO, LOG_TAG) << "Start\n"; + + this->connect(); + Player::start(); +} + +void PulsePlayer::connect() +{ + std::lock_guard lock(mutex_); + LOG(INFO, LOG_TAG) << "Connecting to pulse\n"; + if (settings_.pcm_device.idx == -1) throw SnapException("Can't open " + settings_.pcm_device.name + ", error: No such device"); @@ -466,20 +502,28 @@ void PulsePlayer::start() if (result < 0) throw SnapException("Failed to connect PulseAudio playback stream"); - Player::start(); + // we just connected, pretend we got a chunk so we don't immediately disconnect. + last_chunk_tick_ = chronos::getTickCount(); } - void PulsePlayer::stop() { LOG(INFO, LOG_TAG) << "Stop\n"; + + this->disconnect(); + Player::stop(); +} + +void PulsePlayer::disconnect() +{ + std::lock_guard lock(mutex_); + LOG(INFO, LOG_TAG) << "Disconnecting from pulse\n"; + if (pa_ml_ != nullptr) { pa_mainloop_quit(pa_ml_, 0); } - Player::stop(); - if (pa_ctx_ != nullptr) { pa_context_disconnect(pa_ctx_); diff --git a/client/player/pulse_player.hpp b/client/player/pulse_player.hpp index 052e8d2c..71a6cf99 100644 --- a/client/player/pulse_player.hpp +++ b/client/player/pulse_player.hpp @@ -52,6 +52,9 @@ class PulsePlayer : public Player bool needsThread() const override; void worker() override; + void connect(); + void disconnect(); + bool getHardwareVolume(double& volume, bool& muted) override; void setHardwareVolume(double volume, bool muted) override; @@ -68,6 +71,8 @@ class PulsePlayer : public Player int underflows_ = 0; std::atomic pa_ready_; + long last_chunk_tick_; + pa_buffer_attr bufattr_; pa_sample_spec pa_ss_; pa_mainloop* pa_ml_;