From e097fef79e66b6ff6cf04a911067d3750b20bd48 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Mon, 12 Nov 2018 11:30:05 +0100
Subject: [PATCH] output/control: add command `RELEASE`

With the new command, the decision to pause or close the output moves
into the output thread.
---
 src/output/Control.cxx | 18 +++++++++++++++---
 src/output/Control.hxx |  6 ++++++
 src/output/Thread.cxx  | 24 ++++++++++++++++++++++++
 3 files changed, 45 insertions(+), 3 deletions(-)

diff --git a/src/output/Control.cxx b/src/output/Control.cxx
index 81847a1de..39812b165 100644
--- a/src/output/Control.cxx
+++ b/src/output/Control.cxx
@@ -347,10 +347,22 @@ AudioOutputControl::LockAllowPlay() noexcept
 void
 AudioOutputControl::LockRelease() noexcept
 {
-	if (always_on)
-		LockPauseAsync();
+	if (output->mixer != nullptr &&
+	    (!always_on || !output->SupportsPause()))
+		/* the device has no pause mode: close the mixer,
+		   unless its "global" flag is set (checked by
+		   mixer_auto_close()) */
+		mixer_auto_close(output->mixer);
+
+	const std::lock_guard<Mutex> protect(mutex);
+
+	assert(!open || !fail_timer.IsDefined());
+	assert(allow_play);
+
+	if (IsOpen())
+		CommandWait(Command::RELEASE);
 	else
-		LockCloseWait();
+		fail_timer.Reset();
 }
 
 void
diff --git a/src/output/Control.hxx b/src/output/Control.hxx
index 58924edd5..3a961c57c 100644
--- a/src/output/Control.hxx
+++ b/src/output/Control.hxx
@@ -131,6 +131,12 @@ class AudioOutputControl {
 		CLOSE,
 		PAUSE,
 
+		/**
+		 * Close or pause the device, depending on the
+		 * #always_on setting.
+		 */
+		RELEASE,
+
 		/**
 		 * Drains the internal (hardware) buffers of the device.  This
 		 * operation may take a while to complete.
diff --git a/src/output/Thread.cxx b/src/output/Thread.cxx
index 4211cd078..b88eecbad 100644
--- a/src/output/Thread.cxx
+++ b/src/output/Thread.cxx
@@ -456,6 +456,30 @@ AudioOutputControl::Task() noexcept
 			   the new command first */
 			continue;
 
+		case Command::RELEASE:
+			if (!open) {
+				/* the output has failed after
+				   the PAUSE command was submitted; bail
+				   out */
+				CommandFinished();
+				break;
+			}
+
+			if (always_on) {
+				/* in "always_on" mode, the output is
+				   paused instead of being closed */
+				InternalPause();
+			} else {
+				InternalClose(false);
+				CommandFinished();
+			}
+
+			/* don't "break" here: this might cause
+			   Play() to be called when command==CLOSE
+			   ends the paused state - "continue" checks
+			   the new command first */
+			continue;
+
 		case Command::DRAIN:
 			if (open)
 				InternalDrain();