From f2d8fd769de98df13568989b7d6d164474380d19 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max@musicpd.org>
Date: Sat, 3 Aug 2019 12:30:10 +0200
Subject: [PATCH] player/Thread: don't restart unseekable song after failed
 seek attempt

The check IsSeekableCurrentSong() was added by commit
44b200240f1f4b8394dd2e58fec72da3d3ec448f in version 0.20.19, but it
caused a regression: by doing the branch only if the current song is
seekable, the player would restart the current song if it was not
seekable, and later the initial seek would fail; but we already know
it's not seekable, and so we should fail early.
---
 NEWS                    |  2 ++
 src/decoder/Control.hxx |  5 +++++
 src/player/Thread.cxx   | 13 +++++++++++++
 3 files changed, 20 insertions(+)

diff --git a/NEWS b/NEWS
index 1f40838f0..a4cade59e 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@ ver 0.21.12 (not yet released)
   - opus, vorbis: decode the "end of stream" packet
 * output
   - jack: fix mono-to-stereo conversion
+* player
+  - don't restart unseekable song after failed seek attempt
 * Windows
   - support backslash in relative URIs loaded from playlists
 
diff --git a/src/decoder/Control.hxx b/src/decoder/Control.hxx
index 1e272255f..385516a62 100644
--- a/src/decoder/Control.hxx
+++ b/src/decoder/Control.hxx
@@ -320,6 +320,11 @@ public:
 	gcc_pure
 	bool IsCurrentSong(const DetachedSong &_song) const noexcept;
 
+	gcc_pure
+	bool IsUnseekableCurrentSong(const DetachedSong &_song) const noexcept {
+		return !seekable && IsCurrentSong(_song);
+	}
+
 	gcc_pure
 	bool IsSeekableCurrentSong(const DetachedSong &_song) const noexcept {
 		return seekable && IsCurrentSong(_song);
diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx
index 028842b61..05e691bd1 100644
--- a/src/player/Thread.cxx
+++ b/src/player/Thread.cxx
@@ -599,6 +599,19 @@ Player::SeekDecoder() noexcept
 {
 	assert(pc.next_song != nullptr);
 
+	if (pc.seek_time > SongTime::zero() && // TODO: allow this only if the song duration is known
+	    dc.IsUnseekableCurrentSong(*pc.next_song)) {
+		/* seeking into the current song; but we already know
+		   it's not seekable, so let's fail early */
+		/* note the seek_time>0 check: if seeking to the
+		   beginning, we can simply restart the decoder */
+		pc.next_song.reset();
+		pc.SetError(PlayerError::DECODER,
+			    std::make_exception_ptr(std::runtime_error("Not seekable")));
+		pc.CommandFinished();
+		return true;
+	}
+
 	CancelPendingSeek();
 
 	{