summaryrefslogtreecommitdiff
path: root/src/output
diff options
context:
space:
mode:
Diffstat (limited to 'src/output')
-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) {