diff --git a/Makefile.am b/Makefile.am index 030455ca2..50832a17f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -947,6 +947,7 @@ endif libbasic_a_SOURCES = \ src/ReplayGainConfig.hxx \ src/ReplayGainMode.cxx src/ReplayGainMode.hxx \ + src/SingleMode.cxx src/SingleMode.hxx \ src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx # configuration library diff --git a/NEWS b/NEWS index ba44df990..5b578056d 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,8 @@ ver 0.21 (not yet released) - "outputs" prints the plugin name - "outputset" sets runtime attributes - close connection when client sends HTTP request +* player + - "one-shot" single mode * input - qobuz: new plugin to play Qobuz streams - tidal: new plugin to play Tidal streams diff --git a/doc/protocol.xml b/doc/protocol.xml index bea985e9e..bc54c8d53 100644 --- a/doc/protocol.xml +++ b/doc/protocol.xml @@ -464,8 +464,8 @@ single: - Introduced with MPD 0.15 - 0 or 1 + Introduced with MPD 0.15 (oneshot introduced with 0.20) + 0, 1, or oneshot diff --git a/src/Partition.cxx b/src/Partition.cxx index cb603247f..37b0a56aa 100644 --- a/src/Partition.cxx +++ b/src/Partition.cxx @@ -109,6 +109,12 @@ Partition::SyncWithPlayer() playlist.SyncWithPlayer(pc); } +void +Partition::BorderPause() +{ + playlist.BorderPause(pc); +} + void Partition::OnQueueModified() { @@ -139,6 +145,12 @@ Partition::OnPlayerTagModified() noexcept EmitGlobalEvent(TAG_MODIFIED); } +void +Partition::OnBorderPause() noexcept +{ + EmitGlobalEvent(BORDER_PAUSE); +} + void Partition::OnMixerVolumeChanged(gcc_unused Mixer &mixer, gcc_unused int volume) { @@ -156,4 +168,7 @@ Partition::OnGlobalEvent(unsigned mask) if ((mask & TAG_MODIFIED) != 0) TagModified(); + + if ((mask & BORDER_PAUSE) != 0) + BorderPause(); } diff --git a/src/Partition.hxx b/src/Partition.hxx index 94056522d..e2e15351d 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -28,6 +28,7 @@ #include "player/Control.hxx" #include "player/Listener.hxx" #include "ReplayGainMode.hxx" +#include "SingleMode.hxx" #include "Chrono.hxx" #include "Compiler.h" @@ -46,6 +47,7 @@ class ClientListener; struct Partition final : QueueListener, PlayerListener, MixerListener { static constexpr unsigned TAG_MODIFIED = 0x1; static constexpr unsigned SYNC_WITH_PLAYER = 0x2; + static constexpr unsigned BORDER_PAUSE = 0x4; Instance &instance; @@ -184,7 +186,7 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { playlist.SetRandom(pc, new_value); } - void SetSingle(bool new_value) { + void SetSingle(SingleMode new_value) { playlist.SetSingle(pc, new_value); } @@ -237,6 +239,12 @@ struct Partition final : QueueListener, PlayerListener, MixerListener { */ void SyncWithPlayer(); + /** + * Border pause has just been enabled. Change single mode to off + * if it was one-shot. + */ + void BorderPause(); + private: /* virtual methods from class QueueListener */ void OnQueueModified() override; @@ -246,6 +254,7 @@ private: /* virtual methods from class PlayerListener */ void OnPlayerSync() noexcept override; void OnPlayerTagModified() noexcept override; + void OnBorderPause() noexcept override; /* virtual methods from class MixerListener */ void OnMixerVolumeChanged(Mixer &mixer, int volume) override; diff --git a/src/SingleMode.cxx b/src/SingleMode.cxx new file mode 100644 index 000000000..7d265158f --- /dev/null +++ b/src/SingleMode.cxx @@ -0,0 +1,58 @@ +/* + * Copyright 2003-2017 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. + */ + +#include "SingleMode.hxx" + +#include + +#include +#include + +const char * +SingleToString(SingleMode mode) noexcept +{ + switch (mode) { + case SingleMode::OFF: + return "0"; + + case SingleMode::ON: + return "1"; + + case SingleMode::ONE_SHOT: + return "oneshot"; + } + + assert(false); + gcc_unreachable(); +} + +SingleMode +SingleFromString(const char *s) +{ + assert(s != nullptr); + + if (strcmp(s, "0") == 0) + return SingleMode::OFF; + else if (strcmp(s, "1") == 0) + return SingleMode::ON; + else if (strcmp(s, "oneshot") == 0) + return SingleMode::ONE_SHOT; + else + throw std::invalid_argument("Unrecognized single mode, expected 0, 1, or oneshot"); +} diff --git a/src/SingleMode.hxx b/src/SingleMode.hxx new file mode 100644 index 000000000..e7a740800 --- /dev/null +++ b/src/SingleMode.hxx @@ -0,0 +1,46 @@ +/* + * Copyright 2003-2017 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_SINGLE_MODE_HXX +#define MPD_SINGLE_MODE_HXX + +#include "Compiler.h" + +#include + +enum class SingleMode : uint8_t { + OFF, + ON, + ONE_SHOT, +}; + +/** + * Return the string representation of a #SingleMode. + */ +gcc_pure +const char * +SingleToString(SingleMode mode) noexcept; + +/** + * Parse a string to a #SingleMode. Throws std::invalid_argument on error. + */ +SingleMode +SingleFromString(const char *s); + +#endif diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index bd80f0a9e..570bc75b8 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -23,6 +23,7 @@ #include "CommandError.hxx" #include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" +#include "SingleMode.hxx" #include "client/Client.hxx" #include "client/Response.hxx" #include "mixer/Volume.hxx" @@ -134,7 +135,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r) r.Format(COMMAND_STATUS_REPEAT ": %i\n" COMMAND_STATUS_RANDOM ": %i\n" - COMMAND_STATUS_SINGLE ": %i\n" + COMMAND_STATUS_SINGLE ": %s\n" COMMAND_STATUS_CONSUME ": %i\n" COMMAND_STATUS_PLAYLIST ": %li\n" COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" @@ -142,7 +143,7 @@ handle_status(Client &client, gcc_unused Request args, Response &r) COMMAND_STATUS_STATE ": %s\n", playlist.GetRepeat(), playlist.GetRandom(), - playlist.GetSingle(), + SingleToString(playlist.GetSingle()), playlist.GetConsume(), (unsigned long)playlist.GetVersion(), playlist.GetLength(), @@ -218,8 +219,8 @@ handle_next(Client &client, gcc_unused Request args, gcc_unused Response &r) /* single mode is not considered when this is user who * wants to change song. */ - const bool single = playlist.queue.single; - playlist.queue.single = false; + const SingleMode single = playlist.queue.single; + playlist.queue.single = SingleMode::OFF; AtScopeExit(&playlist, single) { playlist.queue.single = single; @@ -248,8 +249,8 @@ handle_repeat(Client &client, Request args, gcc_unused Response &r) CommandResult handle_single(Client &client, Request args, gcc_unused Response &r) { - bool status = args.ParseBool(0); - client.GetPartition().SetSingle(status); + auto new_mode = SingleFromString(args.front()); + client.GetPartition().SetSingle(new_mode); return CommandResult::OK; } diff --git a/src/player/Listener.hxx b/src/player/Listener.hxx index b34f85398..1f0bdd413 100644 --- a/src/player/Listener.hxx +++ b/src/player/Listener.hxx @@ -31,6 +31,11 @@ public: * The current song's tag has changed. */ virtual void OnPlayerTagModified() noexcept = 0; + + /** + * Playback went into border pause. + */ + virtual void OnBorderPause() noexcept = 0; }; #endif diff --git a/src/player/Thread.cxx b/src/player/Thread.cxx index 03bf2251f..2ffa5d933 100644 --- a/src/player/Thread.cxx +++ b/src/player/Thread.cxx @@ -930,6 +930,7 @@ Player::SongBorder() noexcept const bool border_pause = pc.ApplyBorderPause(); if (border_pause) { paused = true; + pc.listener.OnBorderPause(); idle_add(IDLE_PLAYER); } } diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx index aaed7190a..cb1de5171 100644 --- a/src/queue/Playlist.cxx +++ b/src/queue/Playlist.cxx @@ -23,6 +23,7 @@ #include "PlaylistError.hxx" #include "player/Control.hxx" #include "DetachedSong.hxx" +#include "SingleMode.hxx" #include "Log.hxx" #include @@ -136,7 +137,7 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) ? queue.GetNextOrder(current) : 0; - if (next_order == 0 && queue.random && !queue.single) { + if (next_order == 0 && queue.random && queue.single == SingleMode::OFF) { /* shuffle the song order again, so we get a different order each time the playlist is played completely */ @@ -257,7 +258,7 @@ playlist::SetRepeat(PlayerControl &pc, bool status) queue.repeat = status; - pc.LockSetBorderPause(queue.single && !queue.repeat); + pc.LockSetBorderPause(queue.single != SingleMode::OFF && !queue.repeat); /* if the last song is currently being played, the "next song" might change when repeat mode is toggled */ @@ -277,14 +278,15 @@ playlist_order(playlist &playlist) } void -playlist::SetSingle(PlayerControl &pc, bool status) +playlist::SetSingle(PlayerControl &pc, SingleMode status) { if (status == queue.single) return; queue.single = status; - pc.LockSetBorderPause(queue.single && !queue.repeat); + + pc.LockSetBorderPause(queue.single != SingleMode::OFF && !queue.repeat); /* if the last song is currently being played, the "next song" might change when single mode is toggled */ @@ -353,7 +355,7 @@ playlist::GetNextPosition() const noexcept if (current < 0) return -1; - if (queue.single && queue.repeat) + if (queue.single != SingleMode::OFF && queue.repeat) return queue.OrderToPosition(current); else if (queue.IsValidOrder(current + 1)) return queue.OrderToPosition(current + 1); @@ -362,3 +364,14 @@ playlist::GetNextPosition() const noexcept return -1; } + +void +playlist::BorderPause(PlayerControl &pc) +{ + if (queue.single == SingleMode::ONE_SHOT) { + queue.single = SingleMode::OFF; + pc.LockSetBorderPause(false); + + listener.OnQueueOptionsChanged(); + } +} diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx index 499481360..f2ef4eac5 100644 --- a/src/queue/Playlist.hxx +++ b/src/queue/Playlist.hxx @@ -20,6 +20,7 @@ #ifndef MPD_PLAYLIST_HXX #define MPD_PLAYLIST_HXX +#include "SingleMode.hxx" #include "queue/Queue.hxx" enum TagType : uint8_t; @@ -133,6 +134,12 @@ struct playlist { */ void SyncWithPlayer(PlayerControl &pc); + /** + * This is the "BORDER_PAUSE" event handler. It is invoked by + * the player thread whenever playback goes into border pause. + */ + void BorderPause(PlayerControl &pc); + protected: /** * Called by all editing methods after a modification. @@ -347,11 +354,11 @@ public: void SetRandom(PlayerControl &pc, bool new_value); - bool GetSingle() const { + SingleMode GetSingle() const { return queue.single; } - void SetSingle(PlayerControl &pc, bool new_value); + void SetSingle(PlayerControl &pc, SingleMode new_value); bool GetConsume() const { return queue.consume; diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx index 614f01c19..bb916f2c7 100644 --- a/src/queue/PlaylistState.cxx +++ b/src/queue/PlaylistState.cxx @@ -26,6 +26,7 @@ #include "PlaylistState.hxx" #include "PlaylistError.hxx" #include "Playlist.hxx" +#include "SingleMode.hxx" #include "queue/QueueSave.hxx" #include "fs/io/TextFile.hxx" #include "fs/io/BufferedOutputStream.hxx" @@ -88,7 +89,8 @@ playlist_state_save(BufferedOutputStream &os, const struct playlist &playlist, os.Format(PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random); os.Format(PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat); - os.Format(PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single); + os.Format(PLAYLIST_STATE_FILE_SINGLE "%i\n", + (int)playlist.queue.single); os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume); os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n", (int)pc.GetCrossFade()); @@ -153,7 +155,7 @@ playlist_state_restore(const char *line, TextFile &file, } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_REPEAT))) { playlist.SetRepeat(pc, StringIsEqual(p, "1")); } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_SINGLE))) { - playlist.SetSingle(pc, StringIsEqual(p, "1")); + playlist.SetSingle(pc, SingleFromString(p)); } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CONSUME))) { playlist.SetConsume(StringIsEqual(p, "1")); } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) { @@ -233,9 +235,10 @@ playlist_state_get_hash(const playlist &playlist, : 0) ^ ((int)pc.GetCrossFade() << 20) ^ (unsigned(player_status.state) << 24) ^ + /* note that this takes 2 bits */ + ((int)playlist.queue.single << 25) ^ (playlist.queue.random << 27) ^ (playlist.queue.repeat << 28) ^ - (playlist.queue.single << 29) ^ (playlist.queue.consume << 30) ^ (playlist.queue.random << 31); } diff --git a/src/queue/Queue.cxx b/src/queue/Queue.cxx index ada0346d3..bae64a1e6 100644 --- a/src/queue/Queue.cxx +++ b/src/queue/Queue.cxx @@ -42,7 +42,7 @@ Queue::GetNextOrder(unsigned _order) const noexcept { assert(_order < length); - if (single && repeat && !consume) + if (single != SingleMode::OFF && repeat && !consume) return _order; else if (_order + 1 < length) return _order + 1; diff --git a/src/queue/Queue.hxx b/src/queue/Queue.hxx index 557f74ed8..886ea31bb 100644 --- a/src/queue/Queue.hxx +++ b/src/queue/Queue.hxx @@ -22,6 +22,7 @@ #include "Compiler.h" #include "IdTable.hxx" +#include "SingleMode.hxx" #include "util/LazyRandomEngine.hxx" #include @@ -92,7 +93,7 @@ struct Queue { bool repeat = false; /** play only current song. */ - bool single = false; + SingleMode single = SingleMode::OFF; /** remove each played files. */ bool consume = false;