summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Kellermann <max@musicpd.org>2020-10-01 15:29:13 +0200
committerMax Kellermann <max@musicpd.org>2020-10-02 10:20:39 +0200
commit4cb5e6981104e6e874b19136e2361f7484edbb97 (patch)
tree7b31afebab28f0bd163001a875eff474c64953f2
parentb0596291a8b2df81f794a7126c683d6bc36afa7b (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.cxx14
-rw-r--r--src/output/Control.hxx9
-rw-r--r--src/output/Error.hxx29
-rw-r--r--src/output/Filtered.cxx6
-rw-r--r--src/output/Filtered.hxx2
-rw-r--r--src/output/Interface.hxx29
-rw-r--r--src/output/Thread.cxx18
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) {