diff options
author | Max Kellermann <max@musicpd.org> | 2020-10-01 15:29:13 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2020-10-02 10:20:39 +0200 |
commit | 4cb5e6981104e6e874b19136e2361f7484edbb97 (patch) | |
tree | 7b31afebab28f0bd163001a875eff474c64953f2 | |
parent | b0596291a8b2df81f794a7126c683d6bc36afa7b (diff) |
output/Interface: add virtual method Interrupt()
This allows interrupting the output thread (for some plugins which
implement this method). This way, operations can be canceled
properly, instead of waiting for some external entity.
-rw-r--r-- | src/output/Control.cxx | 14 | ||||
-rw-r--r-- | src/output/Control.hxx | 9 | ||||
-rw-r--r-- | src/output/Error.hxx | 29 | ||||
-rw-r--r-- | src/output/Filtered.cxx | 6 | ||||
-rw-r--r-- | src/output/Filtered.hxx | 2 | ||||
-rw-r--r-- | src/output/Interface.hxx | 29 | ||||
-rw-r--r-- | src/output/Thread.cxx | 18 |
7 files changed, 106 insertions, 1 deletions
diff --git a/src/output/Control.cxx b/src/output/Control.cxx index e4be4dfa7..fd4a1aa50 100644 --- a/src/output/Control.cxx +++ b/src/output/Control.cxx @@ -359,6 +359,9 @@ AudioOutputControl::LockPauseAsync() noexcept mixer_auto_close()) */ mixer_auto_close(output->mixer); + if (output) + output->Interrupt(); + const std::lock_guard<Mutex> protect(mutex); assert(allow_play); @@ -379,6 +382,9 @@ AudioOutputControl::LockDrainAsync() noexcept void AudioOutputControl::LockCancelAsync() noexcept { + if (output) + output->Interrupt(); + const std::lock_guard<Mutex> protect(mutex); if (IsOpen()) { @@ -403,6 +409,8 @@ AudioOutputControl::LockRelease() noexcept if (!output) return; + output->Interrupt(); + if (output->mixer != nullptr && (!always_on || !output->SupportsPause())) /* the device has no pause mode: close the mixer, @@ -426,6 +434,9 @@ AudioOutputControl::LockCloseWait() noexcept { assert(!open || !fail_timer.IsDefined()); + if (output) + output->Interrupt(); + std::unique_lock<Mutex> lock(mutex); CloseWait(lock); } @@ -434,6 +445,9 @@ void AudioOutputControl::BeginDestroy() noexcept { if (thread.IsDefined()) { + if (output) + output->Interrupt(); + const std::lock_guard<Mutex> protect(mutex); if (!killed) { killed = true; diff --git a/src/output/Control.hxx b/src/output/Control.hxx index c87dc133b..809c65101 100644 --- a/src/output/Control.hxx +++ b/src/output/Control.hxx @@ -197,6 +197,15 @@ class AudioOutputControl { bool allow_play = true; /** + * Was an #AudioOutputInterrupted caught? In this case, + * playback is suspended, and the output thread waits for a + * command. + * + * This field is only valid while the output is open. + */ + bool caught_interrupted; + + /** * True while the OutputThread is inside ao_play(). This * means the PlayerThread does not need to wake up the * OutputThread when new chunks are added to the MusicPipe, diff --git a/src/output/Error.hxx b/src/output/Error.hxx new file mode 100644 index 000000000..86fc1c297 --- /dev/null +++ b/src/output/Error.hxx @@ -0,0 +1,29 @@ +/* + * Copyright 2003-2020 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_AUDIO_OUTPUT_ERROR_HXX +#define MPD_AUDIO_OUTPUT_ERROR_HXX + +/** + * An exception class that will be thrown by various #AudioOutput + * methods after AudioOutput::Interrupt() has been called. + */ +class AudioOutputInterrupted {}; + +#endif diff --git a/src/output/Filtered.cxx b/src/output/Filtered.cxx index a38add5c4..b3833953e 100644 --- a/src/output/Filtered.cxx +++ b/src/output/Filtered.cxx @@ -184,6 +184,12 @@ FilteredAudioOutput::Drain() } void +FilteredAudioOutput::Interrupt() noexcept +{ + output->Interrupt(); +} + +void FilteredAudioOutput::Cancel() noexcept { output->Cancel(); diff --git a/src/output/Filtered.hxx b/src/output/Filtered.hxx index 11a0ac742..7e2413cc9 100644 --- a/src/output/Filtered.hxx +++ b/src/output/Filtered.hxx @@ -216,6 +216,8 @@ public: */ void CloseSoftwareMixer() noexcept; + void Interrupt() noexcept; + gcc_pure std::chrono::steady_clock::duration Delay() noexcept; diff --git a/src/output/Interface.hxx b/src/output/Interface.hxx index a54e23a0b..e39dac0b0 100644 --- a/src/output/Interface.hxx +++ b/src/output/Interface.hxx @@ -127,6 +127,24 @@ public: } /** + * Interrupt a blocking operation inside the plugin. This + * method will be called from outside the output thread (and + * therefore the method must be thread-safe), to make the + * output thread ready for receiving a command. For example, + * it will be called to prepare for an upcoming Close(), + * Cancel() or Pause() call. + * + * This method can be called any time, even if the output is + * not open or disabled. + * + * Implementations usually send some kind of message/signal to + * the output thread to wake it up and return to the output + * thread loop (e.g. by throwing #AudioOutputInterrupted), + * where the incoming command will be handled and dispatched. + */ + virtual void Interrupt() noexcept {} + + /** * Returns a positive number if the output thread shall further * delay the next call to Play() or Pause(), which will happen * until this function returns 0. This should be implemented @@ -142,6 +160,11 @@ public: /** * Display metadata for the next chunk. Optional method, * because not all devices can display metadata. + * + * Throws on error. + * + * May throw #AudioOutputInterrupted after Interrupt() has + * been called. */ virtual void SendTag(const Tag &) {} @@ -151,6 +174,9 @@ public: * * Throws on error. * + * May throw #AudioOutputInterrupted after Interrupt() has + * been called. + * * @return the number of bytes played (must be a multiple of * the frame size) */ @@ -177,6 +203,9 @@ public: * disconnected. Plugins which do not support pausing will * simply be closed, and have to be reopened when unpaused. * + * May throw #AudioOutputInterrupted after Interrupt() has + * been called. + * * @return false on error (output will be closed by caller), * true for continue to pause * diff --git a/src/output/Thread.cxx b/src/output/Thread.cxx index 23b6acaa0..1427e0d32 100644 --- a/src/output/Thread.cxx +++ b/src/output/Thread.cxx @@ -18,6 +18,7 @@ */ #include "Control.hxx" +#include "Error.hxx" #include "Filtered.hxx" #include "Client.hxx" #include "Domain.hxx" @@ -135,6 +136,7 @@ AudioOutputControl::InternalOpen(const AudioFormat in_audio_format, last_error = nullptr; fail_timer.Reset(); + caught_interrupted = false; skip_delay = true; AudioFormat f; @@ -243,6 +245,9 @@ AudioOutputControl::PlayChunk(std::unique_lock<Mutex> &lock) noexcept const ScopeUnlock unlock(mutex); try { output->SendTag(*tag); + } catch (AudioOutputInterrupted) { + caught_interrupted = true; + return false; } catch (...) { FormatError(std::current_exception(), "Failed to send tag to %s", @@ -267,6 +272,9 @@ AudioOutputControl::PlayChunk(std::unique_lock<Mutex> &lock) noexcept nbytes = output->Play(data.data, data.size); assert(nbytes > 0); assert(nbytes <= data.size); + } catch (AudioOutputInterrupted) { + caught_interrupted = true; + return false; } catch (...) { FormatError(std::current_exception(), "Failed to play on %s", GetLogName()); @@ -342,6 +350,7 @@ AudioOutputControl::InternalPause(std::unique_lock<Mutex> &lock) noexcept try { const ScopeUnlock unlock(mutex); success = output->IteratePause(); + } catch (AudioOutputInterrupted) { } catch (...) { FormatError(std::current_exception(), "Failed to pause %s", @@ -425,7 +434,8 @@ AudioOutputControl::Task() noexcept /* no pending command: play (or wait for a command) */ - if (open && allow_play && InternalPlay(lock)) + if (open && allow_play && !caught_interrupted && + InternalPlay(lock)) /* don't wait for an event if there are more chunks in the pipe */ continue; @@ -463,6 +473,8 @@ AudioOutputControl::Task() noexcept break; } + caught_interrupted = false; + InternalPause(lock); break; @@ -475,6 +487,8 @@ AudioOutputControl::Task() noexcept break; } + caught_interrupted = false; + if (always_on) { /* in "always_on" mode, the output is paused instead of being closed; @@ -499,6 +513,8 @@ AudioOutputControl::Task() noexcept break; case Command::CANCEL: + caught_interrupted = false; + source.Cancel(); if (open) { |