From 809b89b5af5eaf7abc3240d786cda15f354b6624 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Thu, 27 Feb 2014 17:12:42 +0100 Subject: Playlist*: move to queue/ --- Makefile.am | 12 +- src/Partition.hxx | 2 +- src/Playlist.cxx | 333 ------------------------------- src/Playlist.hxx | 264 ------------------------- src/PlaylistControl.cxx | 260 ------------------------- src/PlaylistEdit.cxx | 410 --------------------------------------- src/PlaylistPrint.cxx | 2 +- src/PlaylistSave.cxx | 2 +- src/PlaylistState.cxx | 245 ----------------------- src/PlaylistState.hxx | 54 ------ src/PlaylistTag.cxx | 93 --------- src/PlaylistUpdate.cxx | 73 ------- src/StateFile.cxx | 2 +- src/command/PlayerCommands.cxx | 2 +- src/command/PlaylistCommands.cxx | 2 +- src/command/QueueCommands.cxx | 2 +- src/playlist/PlaylistQueue.cxx | 2 +- src/queue/Playlist.cxx | 333 +++++++++++++++++++++++++++++++ src/queue/Playlist.hxx | 264 +++++++++++++++++++++++++ src/queue/PlaylistControl.cxx | 260 +++++++++++++++++++++++++ src/queue/PlaylistEdit.cxx | 410 +++++++++++++++++++++++++++++++++++++++ src/queue/PlaylistState.cxx | 245 +++++++++++++++++++++++ src/queue/PlaylistState.hxx | 54 ++++++ src/queue/PlaylistTag.cxx | 93 +++++++++ src/queue/PlaylistUpdate.cxx | 73 +++++++ 25 files changed, 1746 insertions(+), 1746 deletions(-) delete mode 100644 src/Playlist.cxx delete mode 100644 src/Playlist.hxx delete mode 100644 src/PlaylistControl.cxx delete mode 100644 src/PlaylistEdit.cxx delete mode 100644 src/PlaylistState.cxx delete mode 100644 src/PlaylistState.hxx delete mode 100644 src/PlaylistTag.cxx delete mode 100644 src/PlaylistUpdate.cxx create mode 100644 src/queue/Playlist.cxx create mode 100644 src/queue/Playlist.hxx create mode 100644 src/queue/PlaylistControl.cxx create mode 100644 src/queue/PlaylistEdit.cxx create mode 100644 src/queue/PlaylistState.cxx create mode 100644 src/queue/PlaylistState.hxx create mode 100644 src/queue/PlaylistTag.cxx create mode 100644 src/queue/PlaylistUpdate.cxx diff --git a/Makefile.am b/Makefile.am index 11ab70a6e..83a61fbc9 100644 --- a/Makefile.am +++ b/Makefile.am @@ -141,19 +141,14 @@ libmpd_a_SOURCES = \ src/PlayerThread.cxx src/PlayerThread.hxx \ src/PlayerControl.cxx src/PlayerControl.hxx \ src/PlayerListener.hxx \ - src/Playlist.cxx src/Playlist.hxx \ src/PlaylistError.cxx src/PlaylistError.hxx \ src/PlaylistGlobal.cxx src/PlaylistGlobal.hxx \ - src/PlaylistControl.cxx \ - src/PlaylistEdit.cxx \ - src/PlaylistTag.cxx \ src/PlaylistPrint.cxx src/PlaylistPrint.hxx \ src/PlaylistSave.cxx src/PlaylistSave.hxx \ src/playlist/PlaylistStream.cxx src/playlist/PlaylistStream.hxx \ src/playlist/PlaylistMapper.cxx src/playlist/PlaylistMapper.hxx \ src/playlist/PlaylistAny.cxx src/playlist/PlaylistAny.hxx \ src/playlist/PlaylistSong.cxx src/playlist/PlaylistSong.hxx \ - src/PlaylistState.cxx src/PlaylistState.hxx \ src/playlist/PlaylistQueue.cxx src/playlist/PlaylistQueue.hxx \ src/playlist/Print.cxx src/playlist/Print.hxx \ src/db/PlaylistVector.cxx src/db/PlaylistVector.hxx \ @@ -162,6 +157,11 @@ libmpd_a_SOURCES = \ src/queue/Queue.cxx src/queue/Queue.hxx \ src/queue/QueuePrint.cxx src/queue/QueuePrint.hxx \ src/queue/QueueSave.cxx src/queue/QueueSave.hxx \ + src/queue/Playlist.cxx src/queue/Playlist.hxx \ + src/queue/PlaylistControl.cxx \ + src/queue/PlaylistEdit.cxx \ + src/queue/PlaylistTag.cxx \ + src/queue/PlaylistState.cxx src/queue/PlaylistState.hxx \ src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \ src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \ src/DetachedSong.cxx src/DetachedSong.hxx \ @@ -191,7 +191,7 @@ endif if ENABLE_DATABASE libmpd_a_SOURCES += \ - src/PlaylistUpdate.cxx \ + src/queue/PlaylistUpdate.cxx \ src/command/StorageCommands.cxx src/command/StorageCommands.hxx \ src/command/DatabaseCommands.cxx src/command/DatabaseCommands.hxx \ src/db/LightSong.cxx src/db/LightSong.hxx \ diff --git a/src/Partition.hxx b/src/Partition.hxx index 991234a50..7f7def4af 100644 --- a/src/Partition.hxx +++ b/src/Partition.hxx @@ -20,7 +20,7 @@ #ifndef MPD_PARTITION_HXX #define MPD_PARTITION_HXX -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "output/MultipleOutputs.hxx" #include "mixer/Listener.hxx" #include "PlayerControl.hxx" diff --git a/src/Playlist.cxx b/src/Playlist.cxx deleted file mode 100644 index abcb2ceaa..000000000 --- a/src/Playlist.cxx +++ /dev/null @@ -1,333 +0,0 @@ -/* - * Copyright (C) 2003-2014 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 "config.h" -#include "Playlist.hxx" -#include "PlaylistError.hxx" -#include "PlayerControl.hxx" -#include "DetachedSong.hxx" -#include "Idle.hxx" -#include "Log.hxx" - -#include - -void -playlist::TagModified(DetachedSong &&song) -{ - if (!playing) - return; - - assert(current >= 0); - - DetachedSong ¤t_song = queue.GetOrder(current); - if (song.IsSame(current_song)) - current_song.MoveTagFrom(std::move(song)); - - queue.ModifyAtOrder(current); - queue.IncrementVersion(); - idle_add(IDLE_PLAYLIST); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(playlist &playlist, PlayerControl &pc, - unsigned order) -{ - assert(playlist.queue.IsValidOrder(order)); - - playlist.queued = order; - - const DetachedSong &song = playlist.queue.GetOrder(order); - - FormatDebug(playlist_domain, "queue song %i:\"%s\"", - playlist.queued, song.GetURI()); - - pc.EnqueueSong(new DetachedSong(song)); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(playlist &playlist, PlayerControl &pc) -{ - assert(pc.next_song == nullptr); - assert(playlist.queued >= -1); - - /* queued song has started: copy queued to current, - and notify the clients */ - - int current = playlist.current; - playlist.current = playlist.queued; - playlist.queued = -1; - - if(playlist.queue.consume) - playlist.DeleteOrder(pc, current); - - idle_add(IDLE_PLAYER); -} - -const DetachedSong * -playlist::GetQueuedSong() const -{ - return playing && queued >= 0 - ? &queue.GetOrder(queued) - : nullptr; -} - -void -playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) -{ - if (!playing) - return; - - assert(!queue.IsEmpty()); - assert((queued < 0) == (prev == nullptr)); - - const int next_order = current >= 0 - ? queue.GetNextOrder(current) - : 0; - - if (next_order == 0 && queue.random && !queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - const unsigned current_position = - queue.OrderToPosition(current); - - queue.ShuffleOrder(); - - /* make sure that the current still points to - the current song, after the song order has been - shuffled */ - current = queue.PositionToOrder(current_position); - } - - const DetachedSong *const next_song = next_order >= 0 - ? &queue.GetOrder(next_order) - : nullptr; - - if (prev != nullptr && next_song != prev) { - /* clear the currently queued song */ - pc.Cancel(); - queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(*this, pc, next_order); - else - queued = next_order; - } -} - -void -playlist::PlayOrder(PlayerControl &pc, int order) -{ - playing = true; - queued = -1; - - const DetachedSong &song = queue.GetOrder(order); - - FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI()); - - pc.Play(new DetachedSong(song)); - current = order; -} - -static void -playlist_resume_playback(playlist &playlist, PlayerControl &pc); - -void -playlist::SyncWithPlayer(PlayerControl &pc) -{ - if (!playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - pc.Lock(); - const PlayerState pc_state = pc.GetState(); - const DetachedSong *pc_next_song = pc.next_song; - pc.Unlock(); - - if (pc_state == PlayerState::STOP) - /* the player thread has stopped: check if playback - should be restarted with the next song. That can - happen if the playlist isn't filling the queue fast - enough */ - playlist_resume_playback(*this, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == nullptr && queued != -1) - playlist_song_started(*this, pc); - - pc.Lock(); - pc_next_song = pc.next_song; - pc.Unlock(); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == nullptr && queued < 0) - UpdateQueuedSong(pc, nullptr); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(playlist &playlist, PlayerControl &pc) -{ - assert(playlist.playing); - assert(pc.GetState() == PlayerState::STOP); - - const auto error = pc.GetErrorType(); - if (error == PlayerError::NONE) - playlist.error_count = 0; - else - ++playlist.error_count; - - if ((playlist.stop_on_error && error != PlayerError::NONE) || - error == PlayerError::OUTPUT || - playlist.error_count >= playlist.queue.GetLength()) - /* too many errors, or critical error: stop - playback */ - playlist.Stop(pc); - else - /* continue playback at the next song */ - playlist.PlayNext(pc); -} - -void -playlist::SetRepeat(PlayerControl &pc, bool status) -{ - if (status == queue.repeat) - return; - - queue.repeat = status; - - pc.SetBorderPause(queue.single && !queue.repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - UpdateQueuedSong(pc, GetQueuedSong()); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(playlist &playlist) -{ - if (playlist.current >= 0) - /* update playlist.current, order==position now */ - playlist.current = playlist.queue.OrderToPosition(playlist.current); - - playlist.queue.RestoreOrder(); -} - -void -playlist::SetSingle(PlayerControl &pc, bool status) -{ - if (status == queue.single) - return; - - queue.single = status; - - pc.SetBorderPause(queue.single && !queue.repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - UpdateQueuedSong(pc, GetQueuedSong()); - - idle_add(IDLE_OPTIONS); -} - -void -playlist::SetConsume(bool status) -{ - if (status == queue.consume) - return; - - queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist::SetRandom(PlayerControl &pc, bool status) -{ - if (status == queue.random) - return; - - const DetachedSong *const queued_song = GetQueuedSong(); - - queue.random = status; - - if (queue.random) { - /* shuffle the queue order, but preserve current */ - - const int current_position = GetCurrentPosition(); - - queue.ShuffleOrder(); - - if (current_position >= 0) { - /* make sure the current song is the first in - the order list, so the whole rest of the - playlist is played after that */ - unsigned current_order = - queue.PositionToOrder(current_position); - queue.SwapOrders(0, current_order); - current = 0; - } else - current = -1; - } else - playlist_order(*this); - - UpdateQueuedSong(pc, queued_song); - - idle_add(IDLE_OPTIONS); -} - -int -playlist::GetCurrentPosition() const -{ - return current >= 0 - ? queue.OrderToPosition(current) - : -1; -} - -int -playlist::GetNextPosition() const -{ - if (current < 0) - return -1; - - if (queue.single && queue.repeat) - return queue.OrderToPosition(current); - else if (queue.IsValidOrder(current + 1)) - return queue.OrderToPosition(current + 1); - else if (queue.repeat) - return queue.OrderToPosition(0); - - return -1; -} diff --git a/src/Playlist.hxx b/src/Playlist.hxx deleted file mode 100644 index 09980155e..000000000 --- a/src/Playlist.hxx +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2003-2014 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_PLAYLIST_HXX -#define MPD_PLAYLIST_HXX - -#include "queue/Queue.hxx" -#include "PlaylistError.hxx" - -enum TagType : uint8_t; -struct PlayerControl; -class DetachedSong; -class Database; -class Error; -class SongLoader; - -struct playlist { - /** - * The song queue - it contains the "real" playlist. - */ - struct Queue queue; - - /** - * This value is true if the player is currently playing (or - * should be playing). - */ - bool playing; - - /** - * If true, then any error is fatal; if false, MPD will - * attempt to play the next song on non-fatal errors. During - * seeking, this flag is set. - */ - bool stop_on_error; - - /** - * Number of errors since playback was started. If this - * number exceeds the length of the playlist, MPD gives up, - * because all songs have been tried. - */ - unsigned error_count; - - /** - * The "current song pointer". This is the song which is - * played when we get the "play" command. It is also the song - * which is currently being played. - */ - int current; - - /** - * The "next" song to be played, when the current one - * finishes. The decoder thread may start decoding and - * buffering it, while the "current" song is still playing. - * - * This variable is only valid if #playing is true. - */ - int queued; - - playlist(unsigned max_length) - :queue(max_length), playing(false), current(-1), queued(-1) { - } - - ~playlist() { - } - - uint32_t GetVersion() const { - return queue.version; - } - - unsigned GetLength() const { - return queue.GetLength(); - } - - unsigned PositionToId(unsigned position) const { - return queue.PositionToId(position); - } - - gcc_pure - int GetCurrentPosition() const; - - gcc_pure - int GetNextPosition() const; - - /** - * Returns the song object which is currently queued. Returns - * none if there is none (yet?) or if MPD isn't playing. - */ - gcc_pure - const DetachedSong *GetQueuedSong() const; - - /** - * This is the "PLAYLIST" event handler. It is invoked by the - * player thread whenever it requests a new queued song, or - * when it exits. - */ - void SyncWithPlayer(PlayerControl &pc); - -protected: - /** - * Called by all editing methods after a modification. - * Updates the queue version and emits #IDLE_PLAYLIST. - */ - void OnModified(); - - /** - * Updates the "queued song". Calculates the next song - * according to the current one (if MPD isn't playing, it - * takes the first song), and queues this song. Clears the - * old queued song if there was one. - * - * @param prev the song which was previously queued, as - * determined by playlist_get_queued_song() - */ - void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev); - -public: - void Clear(PlayerControl &pc); - - /** - * A tag in the play queue has been modified by the player - * thread. Apply the given song's tag to the current song if - * the song matches. - */ - void TagModified(DetachedSong &&song); - -#ifdef ENABLE_DATABASE - /** - * The database has been modified. Pull all updates. - */ - void DatabaseModified(const Database &db); -#endif - - PlaylistResult AppendSong(PlayerControl &pc, - DetachedSong &&song, - unsigned *added_id=nullptr); - - PlaylistResult AppendURI(PlayerControl &pc, - const SongLoader &loader, - const char *uri_utf8, - unsigned *added_id=nullptr); - -protected: - void DeleteInternal(PlayerControl &pc, - unsigned song, const DetachedSong **queued_p); - -public: - PlaylistResult DeletePosition(PlayerControl &pc, - unsigned position); - - PlaylistResult DeleteOrder(PlayerControl &pc, - unsigned order) { - return DeletePosition(pc, queue.OrderToPosition(order)); - } - - PlaylistResult DeleteId(PlayerControl &pc, unsigned id); - - /** - * Deletes a range of songs from the playlist. - * - * @param start the position of the first song to delete - * @param end the position after the last song to delete - */ - PlaylistResult DeleteRange(PlayerControl &pc, - unsigned start, unsigned end); - - void DeleteSong(PlayerControl &pc, const char *uri); - - void Shuffle(PlayerControl &pc, unsigned start, unsigned end); - - PlaylistResult MoveRange(PlayerControl &pc, - unsigned start, unsigned end, int to); - - PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to); - - PlaylistResult SwapPositions(PlayerControl &pc, - unsigned song1, unsigned song2); - - PlaylistResult SwapIds(PlayerControl &pc, - unsigned id1, unsigned id2); - - PlaylistResult SetPriorityRange(PlayerControl &pc, - unsigned start_position, - unsigned end_position, - uint8_t priority); - - PlaylistResult SetPriorityId(PlayerControl &pc, - unsigned song_id, uint8_t priority); - - bool AddSongIdTag(unsigned id, TagType tag_type, const char *value, - Error &error); - bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error); - - void Stop(PlayerControl &pc); - - PlaylistResult PlayPosition(PlayerControl &pc, int position); - - void PlayOrder(PlayerControl &pc, int order); - - PlaylistResult PlayId(PlayerControl &pc, int id); - - void PlayNext(PlayerControl &pc); - - void PlayPrevious(PlayerControl &pc); - - PlaylistResult SeekSongPosition(PlayerControl &pc, - unsigned song_position, - float seek_time); - - PlaylistResult SeekSongId(PlayerControl &pc, - unsigned song_id, float seek_time); - - /** - * Seek within the current song. Fails if MPD is not currently - * playing. - * - * @param time the time in seconds - * @param relative if true, then the specified time is relative to the - * current position - */ - PlaylistResult SeekCurrent(PlayerControl &pc, - float seek_time, bool relative); - - bool GetRepeat() const { - return queue.repeat; - } - - void SetRepeat(PlayerControl &pc, bool new_value); - - bool GetRandom() const { - return queue.random; - } - - void SetRandom(PlayerControl &pc, bool new_value); - - bool GetSingle() const { - return queue.single; - } - - void SetSingle(PlayerControl &pc, bool new_value); - - bool GetConsume() const { - return queue.consume; - } - - void SetConsume(bool new_value); -}; - -#endif diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx deleted file mode 100644 index 9d75cc26d..000000000 --- a/src/PlaylistControl.cxx +++ /dev/null @@ -1,260 +0,0 @@ -/* - * Copyright (C) 2003-2014 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. - */ - -/* - * Functions for controlling playback on the playlist level. - * - */ - -#include "config.h" -#include "Playlist.hxx" -#include "PlaylistError.hxx" -#include "PlayerControl.hxx" -#include "DetachedSong.hxx" -#include "Log.hxx" - -void -playlist::Stop(PlayerControl &pc) -{ - if (!playing) - return; - - assert(current >= 0); - - FormatDebug(playlist_domain, "stop"); - pc.Stop(); - queued = -1; - playing = false; - - if (queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = queue.OrderToPosition(current); - - queue.ShuffleOrder(); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - current = queue.PositionToOrder(current_position); - } -} - -PlaylistResult -playlist::PlayPosition(PlayerControl &pc, int song) -{ - pc.ClearError(); - - unsigned i = song; - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue.IsEmpty()) - return PlaylistResult::SUCCESS; - - if (playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc.SetPause(false); - return PlaylistResult::SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = current >= 0 - ? current - : 0; - } else if (!queue.IsValidPosition(song)) - return PlaylistResult::BAD_RANGE; - - if (queue.random) { - if (song >= 0) - /* "i" is currently the song position (which - would be equal to the order number in - no-random mode); convert it to a order - number, because random mode is enabled */ - i = queue.PositionToOrder(song); - - if (!playing) - current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue.SwapOrders(i, current); - i = current; - } - - stop_on_error = false; - error_count = 0; - - PlayOrder(pc, i); - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::PlayId(PlayerControl &pc, int id) -{ - if (id == -1) - return PlayPosition(pc, id); - - int song = queue.IdToPosition(id); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return PlayPosition(pc, song); -} - -void -playlist::PlayNext(PlayerControl &pc) -{ - if (!playing) - return; - - assert(!queue.IsEmpty()); - assert(queue.IsValidOrder(current)); - - const int old_current = current; - stop_on_error = false; - - /* determine the next song from the queue's order list */ - - const int next_order = queue.GetNextOrder(current); - if (next_order < 0) { - /* no song after this one: stop playback */ - Stop(pc); - - /* reset "current song" */ - current = -1; - } - else - { - if (next_order == 0 && queue.random) { - /* The queue told us that the next song is the first - song. This means we are in repeat mode. Shuffle - the queue order, so this time, the user hears the - songs in a different than before */ - assert(queue.repeat); - - queue.ShuffleOrder(); - - /* note that current and queued are - now invalid, but playlist_play_order() will - discard them anyway */ - } - - PlayOrder(pc, next_order); - } - - /* Consume mode removes each played songs. */ - if (queue.consume) - DeleteOrder(pc, old_current); -} - -void -playlist::PlayPrevious(PlayerControl &pc) -{ - if (!playing) - return; - - assert(!queue.IsEmpty()); - - int order; - if (current > 0) { - /* play the preceding song */ - order = current - 1; - } else if (queue.repeat) { - /* play the last song in "repeat" mode */ - order = queue.GetLength() - 1; - } else { - /* re-start playing the current song if it's - the first one */ - order = current; - } - - PlayOrder(pc, order); -} - -PlaylistResult -playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) -{ - if (!queue.IsValidPosition(song)) - return PlaylistResult::BAD_RANGE; - - const DetachedSong *queued_song = GetQueuedSong(); - - unsigned i = queue.random - ? queue.PositionToOrder(song) - : song; - - pc.ClearError(); - stop_on_error = true; - error_count = 0; - - if (!playing || (unsigned)current != i) { - /* seeking is not within the current song - prepare - song change */ - - playing = true; - current = i; - - queued_song = nullptr; - } - - if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) { - UpdateQueuedSong(pc, queued_song); - - return PlaylistResult::NOT_PLAYING; - } - - queued = -1; - UpdateQueuedSong(pc, nullptr); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time) -{ - int song = queue.IdToPosition(id); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return SeekSongPosition(pc, song, seek_time); -} - -PlaylistResult -playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative) -{ - if (!playing) - return PlaylistResult::NOT_PLAYING; - - if (relative) { - const auto status = pc.GetStatus(); - - if (status.state != PlayerState::PLAY && - status.state != PlayerState::PAUSE) - return PlaylistResult::NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return SeekSongPosition(pc, current, seek_time); -} diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx deleted file mode 100644 index 8d2c76e6e..000000000 --- a/src/PlaylistEdit.cxx +++ /dev/null @@ -1,410 +0,0 @@ -/* - * Copyright (C) 2003-2014 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. - */ - -/* - * Functions for editing the playlist (adding, removing, reordering - * songs in the queue). - * - */ - -#include "config.h" -#include "Playlist.hxx" -#include "PlaylistError.hxx" -#include "PlayerControl.hxx" -#include "util/UriUtil.hxx" -#include "util/Error.hxx" -#include "DetachedSong.hxx" -#include "SongLoader.hxx" -#include "Idle.hxx" -#include "Log.hxx" - -#include - -void -playlist::OnModified() -{ - queue.IncrementVersion(); - - idle_add(IDLE_PLAYLIST); -} - -void -playlist::Clear(PlayerControl &pc) -{ - Stop(pc); - - queue.Clear(); - current = -1; - - OnModified(); -} - -PlaylistResult -playlist::AppendSong(PlayerControl &pc, - DetachedSong &&song, unsigned *added_id) -{ - unsigned id; - - if (queue.IsFull()) - return PlaylistResult::TOO_LARGE; - - const DetachedSong *const queued_song = GetQueuedSong(); - - id = queue.Append(std::move(song), 0); - - if (queue.random) { - /* shuffle the new song into the list of remaning - songs to play */ - - unsigned start; - if (queued >= 0) - start = queued + 1; - else - start = current + 1; - if (start < queue.GetLength()) - queue.ShuffleOrderLast(start, queue.GetLength()); - } - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - if (added_id) - *added_id = id; - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::AppendURI(PlayerControl &pc, - const SongLoader &loader, - const char *uri, unsigned *added_id) -{ - FormatDebug(playlist_domain, "add to playlist: %s", uri); - - Error error; - DetachedSong *song = loader.LoadSong(uri, error); - if (song == nullptr) { - // TODO: return the Error - LogError(error); - return error.IsDomain(playlist_domain) - ? PlaylistResult(error.GetCode()) - : PlaylistResult::NO_SUCH_SONG; - } - - PlaylistResult result = AppendSong(pc, std::move(*song), added_id); - delete song; - - return result; -} - -PlaylistResult -playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2) -{ - if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) - return PlaylistResult::BAD_RANGE; - - const DetachedSong *const queued_song = GetQueuedSong(); - - queue.SwapPositions(song1, song2); - - if (queue.random) { - /* update the queue order, so that current - still points to the current song order */ - - queue.SwapOrders(queue.PositionToOrder(song1), - queue.PositionToOrder(song2)); - } else { - /* correct the "current" song order */ - - if (current == (int)song1) - current = song2; - else if (current == (int)song2) - current = song1; - } - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2) -{ - int song1 = queue.IdToPosition(id1); - int song2 = queue.IdToPosition(id2); - - if (song1 < 0 || song2 < 0) - return PlaylistResult::NO_SUCH_SONG; - - return SwapPositions(pc, song1, song2); -} - -PlaylistResult -playlist::SetPriorityRange(PlayerControl &pc, - unsigned start, unsigned end, - uint8_t priority) -{ - if (start >= GetLength()) - return PlaylistResult::BAD_RANGE; - - if (end > GetLength()) - end = GetLength(); - - if (start >= end) - return PlaylistResult::SUCCESS; - - /* remember "current" and "queued" */ - - const int current_position = GetCurrentPosition(); - const DetachedSong *const queued_song = GetQueuedSong(); - - /* apply the priority changes */ - - queue.SetPriorityRange(start, end, priority, current); - - /* restore "current" and choose a new "queued" */ - - if (current_position >= 0) - current = queue.PositionToOrder(current_position); - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::SetPriorityId(PlayerControl &pc, - unsigned song_id, uint8_t priority) -{ - int song_position = queue.IdToPosition(song_id); - if (song_position < 0) - return PlaylistResult::NO_SUCH_SONG; - - return SetPriorityRange(pc, song_position, song_position + 1, - priority); - -} - -void -playlist::DeleteInternal(PlayerControl &pc, - unsigned song, const DetachedSong **queued_p) -{ - assert(song < GetLength()); - - unsigned songOrder = queue.PositionToOrder(song); - - if (playing && current == (int)songOrder) { - const bool paused = pc.GetState() == PlayerState::PAUSE; - - /* the current song is going to be deleted: stop the player */ - - pc.Stop(); - playing = false; - - /* see which song is going to be played instead */ - - current = queue.GetNextOrder(current); - if (current == (int)songOrder) - current = -1; - - if (current >= 0 && !paused) - /* play the song after the deleted one */ - PlayOrder(pc, current); - else - /* no songs left to play, stop playback - completely */ - Stop(pc); - - *queued_p = nullptr; - } else if (current == (int)songOrder) - /* there's a "current song" but we're not playing - currently - clear "current" */ - current = -1; - - /* now do it: remove the song */ - - queue.DeletePosition(song); - - /* update the "current" and "queued" variables */ - - if (current > (int)songOrder) - current--; -} - -PlaylistResult -playlist::DeletePosition(PlayerControl &pc, unsigned song) -{ - if (song >= queue.GetLength()) - return PlaylistResult::BAD_RANGE; - - const DetachedSong *queued_song = GetQueuedSong(); - - DeleteInternal(pc, song, &queued_song); - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end) -{ - if (start >= queue.GetLength()) - return PlaylistResult::BAD_RANGE; - - if (end > queue.GetLength()) - end = queue.GetLength(); - - if (start >= end) - return PlaylistResult::SUCCESS; - - const DetachedSong *queued_song = GetQueuedSong(); - - do { - DeleteInternal(pc, --end, &queued_song); - } while (end != start); - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::DeleteId(PlayerControl &pc, unsigned id) -{ - int song = queue.IdToPosition(id); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return DeletePosition(pc, song); -} - -void -playlist::DeleteSong(PlayerControl &pc, const char *uri) -{ - for (int i = queue.GetLength() - 1; i >= 0; --i) - if (queue.Get(i).IsURI(uri)) - DeletePosition(pc, i); -} - -PlaylistResult -playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to) -{ - if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) - return PlaylistResult::BAD_RANGE; - - if ((to >= 0 && to + end - start - 1 >= GetLength()) || - (to < 0 && unsigned(abs(to)) > GetLength())) - return PlaylistResult::BAD_RANGE; - - if ((int)start == to) - /* nothing happens */ - return PlaylistResult::SUCCESS; - - const DetachedSong *const queued_song = GetQueuedSong(); - - /* - * (to < 0) => move to offset from current song - * (-playlist.length == to) => move to position BEFORE current song - */ - const int currentSong = GetCurrentPosition(); - if (to < 0) { - if (currentSong < 0) - /* can't move relative to current song, - because there is no current song */ - return PlaylistResult::BAD_RANGE; - - if (start <= (unsigned)currentSong && (unsigned)currentSong < end) - /* no-op, can't be moved to offset of itself */ - return PlaylistResult::SUCCESS; - to = (currentSong + abs(to)) % GetLength(); - if (start < (unsigned)to) - to--; - } - - queue.MoveRange(start, end, to); - - if (!queue.random) { - /* update current/queued */ - if ((int)start <= current && (unsigned)current < end) - current += to - start; - else if (current >= (int)end && current <= to) - current -= end - start; - else if (current >= to && current < (int)start) - current += end - start; - } - - UpdateQueuedSong(pc, queued_song); - OnModified(); - - return PlaylistResult::SUCCESS; -} - -PlaylistResult -playlist::MoveId(PlayerControl &pc, unsigned id1, int to) -{ - int song = queue.IdToPosition(id1); - if (song < 0) - return PlaylistResult::NO_SUCH_SONG; - - return MoveRange(pc, song, song + 1, to); -} - -void -playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) -{ - if (end > GetLength()) - /* correct the "end" offset */ - end = GetLength(); - - if (start + 1 >= end) - /* needs at least two entries. */ - return; - - const DetachedSong *const queued_song = GetQueuedSong(); - if (playing && current >= 0) { - unsigned current_position = queue.OrderToPosition(current); - - if (current_position >= start && current_position < end) { - /* put current playing song first */ - queue.SwapPositions(start, current_position); - - if (queue.random) { - current = queue.PositionToOrder(start); - } else - current = start; - - /* start shuffle after the current song */ - start++; - } - } else { - /* no playback currently: reset current */ - - current = -1; - } - - queue.ShuffleRange(start, end); - - UpdateQueuedSong(pc, queued_song); - OnModified(); -} diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx index 9e0dcade0..cfa56c7b3 100644 --- a/src/PlaylistPrint.cxx +++ b/src/PlaylistPrint.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "PlaylistPrint.hxx" #include "PlaylistFile.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "queue/QueuePrint.hxx" #include "SongPrint.hxx" #include "Partition.hxx" diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx index 62c27c96e..875fb1eeb 100644 --- a/src/PlaylistSave.cxx +++ b/src/PlaylistSave.cxx @@ -21,7 +21,7 @@ #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" #include "PlaylistError.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "DetachedSong.hxx" #include "SongLoader.hxx" #include "Mapper.hxx" diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx deleted file mode 100644 index f5c798e3e..000000000 --- a/src/PlaylistState.cxx +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2003-2014 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. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#include "config.h" -#include "PlaylistState.hxx" -#include "PlaylistError.hxx" -#include "Playlist.hxx" -#include "queue/QueueSave.hxx" -#include "fs/TextFile.hxx" -#include "PlayerControl.hxx" -#include "config/ConfigGlobal.hxx" -#include "config/ConfigOption.hxx" -#include "fs/Limits.hxx" -#include "util/CharUtil.hxx" -#include "util/StringUtil.hxx" -#include "Log.hxx" - -#include -#include - -#define PLAYLIST_STATE_FILE_STATE "state: " -#define PLAYLIST_STATE_FILE_RANDOM "random: " -#define PLAYLIST_STATE_FILE_REPEAT "repeat: " -#define PLAYLIST_STATE_FILE_SINGLE "single: " -#define PLAYLIST_STATE_FILE_CONSUME "consume: " -#define PLAYLIST_STATE_FILE_CURRENT "current: " -#define PLAYLIST_STATE_FILE_TIME "time: " -#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " -#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " -#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " -#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" -#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" - -#define PLAYLIST_STATE_FILE_STATE_PLAY "play" -#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" -#define PLAYLIST_STATE_FILE_STATE_STOP "stop" - -#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX - -void -playlist_state_save(FILE *fp, const struct playlist &playlist, - PlayerControl &pc) -{ - const auto player_status = pc.GetStatus(); - - fputs(PLAYLIST_STATE_FILE_STATE, fp); - - if (playlist.playing) { - switch (player_status.state) { - case PlayerState::PAUSE: - fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); - break; - default: - fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); - } - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - playlist.queue.OrderToPosition(playlist.current)); - fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", - (int)player_status.elapsed_time); - } else { - fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); - - if (playlist.current >= 0) - fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", - playlist.queue.OrderToPosition(playlist.current)); - } - - fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random); - fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat); - fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single); - fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", - playlist.queue.consume); - fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", - (int)pc.GetCrossFade()); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", - pc.GetMixRampDb()); - fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", - pc.GetMixRampDelay()); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); - queue_save(fp, playlist.queue); - fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); -} - -static void -playlist_state_load(TextFile &file, const SongLoader &song_loader, - struct playlist &playlist) -{ - const char *line = file.ReadLine(); - if (line == nullptr) { - LogWarning(playlist_domain, "No playlist in state file"); - return; - } - - while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { - queue_load_song(file, song_loader, line, playlist.queue); - - line = file.ReadLine(); - if (line == nullptr) { - LogWarning(playlist_domain, - "'" PLAYLIST_STATE_FILE_PLAYLIST_END - "' not found in state file"); - break; - } - } - - playlist.queue.IncrementVersion(); -} - -bool -playlist_state_restore(const char *line, TextFile &file, - const SongLoader &song_loader, - struct playlist &playlist, PlayerControl &pc) -{ - int current = -1; - int seek_time = 0; - bool random_mode = false; - - if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE)) - return false; - - line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; - - PlayerState state; - if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) - state = PlayerState::PLAY; - else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) - state = PlayerState::PAUSE; - else - state = PlayerState::STOP; - - while ((line = file.ReadLine()) != nullptr) { - if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) { - seek_time = - atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) { - playlist.SetRepeat(pc, - strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), - "1") == 0); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) { - playlist.SetSingle(pc, - strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), - "1") == 0); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) { - playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), - "1") == 0); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) { - pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { - pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { - const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY); - - /* this check discards "nan" which was used - prior to MPD 0.18 */ - if (IsDigitASCII(*p)) - pc.SetMixRampDelay(atof(p)); - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) { - random_mode = - strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), - "1") == 0; - } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) { - current = atoi(&(line - [strlen - (PLAYLIST_STATE_FILE_CURRENT)])); - } else if (StringStartsWith(line, - PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { - playlist_state_load(file, song_loader, playlist); - } - } - - playlist.SetRandom(pc, random_mode); - - if (!playlist.queue.IsEmpty()) { - if (!playlist.queue.IsValidPosition(current)) - current = 0; - - if (state == PlayerState::PLAY && - config_get_bool(CONF_RESTORE_PAUSED, false)) - /* the user doesn't want MPD to auto-start - playback after startup; fall back to - "pause" */ - state = PlayerState::PAUSE; - - /* enable all devices for the first time; this must be - called here, after the audio output states were - restored, before playback begins */ - if (state != PlayerState::STOP) - pc.UpdateAudio(); - - if (state == PlayerState::STOP /* && config_option */) - playlist.current = current; - else if (seek_time == 0) - playlist.PlayPosition(pc, current); - else - playlist.SeekSongPosition(pc, current, seek_time); - - if (state == PlayerState::PAUSE) - pc.Pause(); - } - - return true; -} - -unsigned -playlist_state_get_hash(const playlist &playlist, - PlayerControl &pc) -{ - const auto player_status = pc.GetStatus(); - - return playlist.queue.version ^ - (player_status.state != PlayerState::STOP - ? ((int)player_status.elapsed_time << 8) - : 0) ^ - (playlist.current >= 0 - ? (playlist.queue.OrderToPosition(playlist.current) << 16) - : 0) ^ - ((int)pc.GetCrossFade() << 20) ^ - (unsigned(player_status.state) << 24) ^ - (playlist.queue.random << 27) ^ - (playlist.queue.repeat << 28) ^ - (playlist.queue.single << 29) ^ - (playlist.queue.consume << 30) ^ - (playlist.queue.random << 31); -} diff --git a/src/PlaylistState.hxx b/src/PlaylistState.hxx deleted file mode 100644 index 8d3f88ae2..000000000 --- a/src/PlaylistState.hxx +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2003-2014 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. - */ - -/* - * Saving and loading the playlist to/from the state file. - * - */ - -#ifndef MPD_PLAYLIST_STATE_HXX -#define MPD_PLAYLIST_STATE_HXX - -#include - -struct playlist; -struct PlayerControl; -class TextFile; -class SongLoader; - -void -playlist_state_save(FILE *fp, const playlist &playlist, - PlayerControl &pc); - -bool -playlist_state_restore(const char *line, TextFile &file, - const SongLoader &song_loader, - playlist &playlist, PlayerControl &pc); - -/** - * Generates a hash number for the current state of the playlist and - * the playback options. This is used by timer_save_state_file() to - * determine whether the state has changed and the state file should - * be saved. - */ -unsigned -playlist_state_get_hash(const playlist &playlist, - PlayerControl &c); - -#endif diff --git a/src/PlaylistTag.cxx b/src/PlaylistTag.cxx deleted file mode 100644 index 556e7f4e9..000000000 --- a/src/PlaylistTag.cxx +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (C) 2003-2014 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. - */ - -/* - * Functions for editing the playlist (adding, removing, reordering - * songs in the queue). - * - */ - -#include "config.h" -#include "Playlist.hxx" -#include "PlaylistError.hxx" -#include "DetachedSong.hxx" -#include "tag/Tag.hxx" -#include "tag/TagBuilder.hxx" -#include "util/Error.hxx" - -bool -playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value, - Error &error) -{ - const int position = queue.IdToPosition(id); - if (position < 0) { - error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), - "No such song"); - return false; - } - - DetachedSong &song = queue.Get(position); - if (song.IsFile()) { - error.Set(playlist_domain, int(PlaylistResult::DENIED), - "Cannot edit tags of local file"); - return false; - } - - { - TagBuilder tag(std::move(song.WritableTag())); - tag.AddItem(tag_type, value); - song.SetTag(tag.Commit()); - } - - queue.ModifyAtPosition(position); - OnModified(); - return true; -} - -bool -playlist::ClearSongIdTag(unsigned id, TagType tag_type, - Error &error) -{ - const int position = queue.IdToPosition(id); - if (position < 0) { - error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), - "No such song"); - return false; - } - - DetachedSong &song = queue.Get(position); - if (song.IsFile()) { - error.Set(playlist_domain, int(PlaylistResult::DENIED), - "Cannot edit tags of local file"); - return false; - } - - { - TagBuilder tag(std::move(song.WritableTag())); - if (tag_type == TAG_NUM_OF_ITEM_TYPES) - tag.RemoveAll(); - else - tag.RemoveType(tag_type); - song.SetTag(tag.Commit()); - } - - queue.ModifyAtPosition(position); - OnModified(); - return true; -} diff --git a/src/PlaylistUpdate.cxx b/src/PlaylistUpdate.cxx deleted file mode 100644 index 8876711ef..000000000 --- a/src/PlaylistUpdate.cxx +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (C) 2003-2014 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 "config.h" -#include "Playlist.hxx" -#include "db/Interface.hxx" -#include "db/LightSong.hxx" -#include "DetachedSong.hxx" -#include "tag/Tag.hxx" -#include "Idle.hxx" -#include "util/Error.hxx" - -static bool -UpdatePlaylistSong(const Database &db, DetachedSong &song) -{ - if (!song.IsInDatabase() || !song.IsFile()) - /* only update Songs instances that are "detached" - from the Database */ - return false; - - const LightSong *original = db.GetSong(song.GetURI(), IgnoreError()); - if (original == nullptr) - /* not found - shouldn't happen, because the update - thread should ensure that all stale Song instances - have been purged */ - return false; - - if (original->mtime == song.GetLastModified()) { - /* not modified */ - db.ReturnSong(original); - return false; - } - - song.SetLastModified(original->mtime); - song.SetTag(*original->tag); - - db.ReturnSong(original); - return true; -} - -void -playlist::DatabaseModified(const Database &db) -{ - bool modified = false; - - for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) { - if (UpdatePlaylistSong(db, queue.Get(i))) { - queue.ModifyAtPosition(i); - modified = true; - } - } - - if (modified) { - queue.IncrementVersion(); - idle_add(IDLE_PLAYLIST); - } -} diff --git a/src/StateFile.cxx b/src/StateFile.cxx index a1d8945c4..a3069605f 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "StateFile.hxx" #include "output/OutputState.hxx" -#include "PlaylistState.hxx" +#include "queue/PlaylistState.hxx" #include "fs/TextFile.hxx" #include "Partition.hxx" #include "Instance.hxx" diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index a4e0ec0a7..748b5b894 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "PlayerCommands.hxx" #include "CommandError.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" #include "client/Client.hxx" #include "mixer/Volume.hxx" diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index 6406b0770..35dc0ceb3 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -28,12 +28,12 @@ #include "SongLoader.hxx" #include "playlist/PlaylistQueue.hxx" #include "playlist/Print.hxx" +#include "queue/Playlist.hxx" #include "TimePrint.hxx" #include "client/Client.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" -#include "Playlist.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index f14beb872..105a33ebb 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -24,7 +24,7 @@ #include "db/Selection.hxx" #include "SongFilter.hxx" #include "SongLoader.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" #include "client/Client.hxx" #include "Partition.hxx" diff --git a/src/playlist/PlaylistQueue.cxx b/src/playlist/PlaylistQueue.cxx index 80018658d..3c7194ad7 100644 --- a/src/playlist/PlaylistQueue.cxx +++ b/src/playlist/PlaylistQueue.cxx @@ -21,7 +21,7 @@ #include "PlaylistQueue.hxx" #include "PlaylistAny.hxx" #include "PlaylistSong.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "SongEnumerator.hxx" #include "DetachedSong.hxx" #include "thread/Mutex.hxx" diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx new file mode 100644 index 000000000..abcb2ceaa --- /dev/null +++ b/src/queue/Playlist.cxx @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2003-2014 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 "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "DetachedSong.hxx" +#include "Idle.hxx" +#include "Log.hxx" + +#include + +void +playlist::TagModified(DetachedSong &&song) +{ + if (!playing) + return; + + assert(current >= 0); + + DetachedSong ¤t_song = queue.GetOrder(current); + if (song.IsSame(current_song)) + current_song.MoveTagFrom(std::move(song)); + + queue.ModifyAtOrder(current); + queue.IncrementVersion(); + idle_add(IDLE_PLAYLIST); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(playlist &playlist, PlayerControl &pc, + unsigned order) +{ + assert(playlist.queue.IsValidOrder(order)); + + playlist.queued = order; + + const DetachedSong &song = playlist.queue.GetOrder(order); + + FormatDebug(playlist_domain, "queue song %i:\"%s\"", + playlist.queued, song.GetURI()); + + pc.EnqueueSong(new DetachedSong(song)); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(playlist &playlist, PlayerControl &pc) +{ + assert(pc.next_song == nullptr); + assert(playlist.queued >= -1); + + /* queued song has started: copy queued to current, + and notify the clients */ + + int current = playlist.current; + playlist.current = playlist.queued; + playlist.queued = -1; + + if(playlist.queue.consume) + playlist.DeleteOrder(pc, current); + + idle_add(IDLE_PLAYER); +} + +const DetachedSong * +playlist::GetQueuedSong() const +{ + return playing && queued >= 0 + ? &queue.GetOrder(queued) + : nullptr; +} + +void +playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert((queued < 0) == (prev == nullptr)); + + const int next_order = current >= 0 + ? queue.GetNextOrder(current) + : 0; + + if (next_order == 0 && queue.random && !queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + const unsigned current_position = + queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that the current still points to + the current song, after the song order has been + shuffled */ + current = queue.PositionToOrder(current_position); + } + + const DetachedSong *const next_song = next_order >= 0 + ? &queue.GetOrder(next_order) + : nullptr; + + if (prev != nullptr && next_song != prev) { + /* clear the currently queued song */ + pc.Cancel(); + queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(*this, pc, next_order); + else + queued = next_order; + } +} + +void +playlist::PlayOrder(PlayerControl &pc, int order) +{ + playing = true; + queued = -1; + + const DetachedSong &song = queue.GetOrder(order); + + FormatDebug(playlist_domain, "play %i:\"%s\"", order, song.GetURI()); + + pc.Play(new DetachedSong(song)); + current = order; +} + +static void +playlist_resume_playback(playlist &playlist, PlayerControl &pc); + +void +playlist::SyncWithPlayer(PlayerControl &pc) +{ + if (!playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + pc.Lock(); + const PlayerState pc_state = pc.GetState(); + const DetachedSong *pc_next_song = pc.next_song; + pc.Unlock(); + + if (pc_state == PlayerState::STOP) + /* the player thread has stopped: check if playback + should be restarted with the next song. That can + happen if the playlist isn't filling the queue fast + enough */ + playlist_resume_playback(*this, pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == nullptr && queued != -1) + playlist_song_started(*this, pc); + + pc.Lock(); + pc_next_song = pc.next_song; + pc.Unlock(); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == nullptr && queued < 0) + UpdateQueuedSong(pc, nullptr); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(playlist &playlist, PlayerControl &pc) +{ + assert(playlist.playing); + assert(pc.GetState() == PlayerState::STOP); + + const auto error = pc.GetErrorType(); + if (error == PlayerError::NONE) + playlist.error_count = 0; + else + ++playlist.error_count; + + if ((playlist.stop_on_error && error != PlayerError::NONE) || + error == PlayerError::OUTPUT || + playlist.error_count >= playlist.queue.GetLength()) + /* too many errors, or critical error: stop + playback */ + playlist.Stop(pc); + else + /* continue playback at the next song */ + playlist.PlayNext(pc); +} + +void +playlist::SetRepeat(PlayerControl &pc, bool status) +{ + if (status == queue.repeat) + return; + + queue.repeat = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(playlist &playlist) +{ + if (playlist.current >= 0) + /* update playlist.current, order==position now */ + playlist.current = playlist.queue.OrderToPosition(playlist.current); + + playlist.queue.RestoreOrder(); +} + +void +playlist::SetSingle(PlayerControl &pc, bool status) +{ + if (status == queue.single) + return; + + queue.single = status; + + pc.SetBorderPause(queue.single && !queue.repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + UpdateQueuedSong(pc, GetQueuedSong()); + + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetConsume(bool status) +{ + if (status == queue.consume) + return; + + queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist::SetRandom(PlayerControl &pc, bool status) +{ + if (status == queue.random) + return; + + const DetachedSong *const queued_song = GetQueuedSong(); + + queue.random = status; + + if (queue.random) { + /* shuffle the queue order, but preserve current */ + + const int current_position = GetCurrentPosition(); + + queue.ShuffleOrder(); + + if (current_position >= 0) { + /* make sure the current song is the first in + the order list, so the whole rest of the + playlist is played after that */ + unsigned current_order = + queue.PositionToOrder(current_position); + queue.SwapOrders(0, current_order); + current = 0; + } else + current = -1; + } else + playlist_order(*this); + + UpdateQueuedSong(pc, queued_song); + + idle_add(IDLE_OPTIONS); +} + +int +playlist::GetCurrentPosition() const +{ + return current >= 0 + ? queue.OrderToPosition(current) + : -1; +} + +int +playlist::GetNextPosition() const +{ + if (current < 0) + return -1; + + if (queue.single && queue.repeat) + return queue.OrderToPosition(current); + else if (queue.IsValidOrder(current + 1)) + return queue.OrderToPosition(current + 1); + else if (queue.repeat) + return queue.OrderToPosition(0); + + return -1; +} diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx new file mode 100644 index 000000000..09980155e --- /dev/null +++ b/src/queue/Playlist.hxx @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2003-2014 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_PLAYLIST_HXX +#define MPD_PLAYLIST_HXX + +#include "queue/Queue.hxx" +#include "PlaylistError.hxx" + +enum TagType : uint8_t; +struct PlayerControl; +class DetachedSong; +class Database; +class Error; +class SongLoader; + +struct playlist { + /** + * The song queue - it contains the "real" playlist. + */ + struct Queue queue; + + /** + * This value is true if the player is currently playing (or + * should be playing). + */ + bool playing; + + /** + * If true, then any error is fatal; if false, MPD will + * attempt to play the next song on non-fatal errors. During + * seeking, this flag is set. + */ + bool stop_on_error; + + /** + * Number of errors since playback was started. If this + * number exceeds the length of the playlist, MPD gives up, + * because all songs have been tried. + */ + unsigned error_count; + + /** + * The "current song pointer". This is the song which is + * played when we get the "play" command. It is also the song + * which is currently being played. + */ + int current; + + /** + * The "next" song to be played, when the current one + * finishes. The decoder thread may start decoding and + * buffering it, while the "current" song is still playing. + * + * This variable is only valid if #playing is true. + */ + int queued; + + playlist(unsigned max_length) + :queue(max_length), playing(false), current(-1), queued(-1) { + } + + ~playlist() { + } + + uint32_t GetVersion() const { + return queue.version; + } + + unsigned GetLength() const { + return queue.GetLength(); + } + + unsigned PositionToId(unsigned position) const { + return queue.PositionToId(position); + } + + gcc_pure + int GetCurrentPosition() const; + + gcc_pure + int GetNextPosition() const; + + /** + * Returns the song object which is currently queued. Returns + * none if there is none (yet?) or if MPD isn't playing. + */ + gcc_pure + const DetachedSong *GetQueuedSong() const; + + /** + * This is the "PLAYLIST" event handler. It is invoked by the + * player thread whenever it requests a new queued song, or + * when it exits. + */ + void SyncWithPlayer(PlayerControl &pc); + +protected: + /** + * Called by all editing methods after a modification. + * Updates the queue version and emits #IDLE_PLAYLIST. + */ + void OnModified(); + + /** + * Updates the "queued song". Calculates the next song + * according to the current one (if MPD isn't playing, it + * takes the first song), and queues this song. Clears the + * old queued song if there was one. + * + * @param prev the song which was previously queued, as + * determined by playlist_get_queued_song() + */ + void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev); + +public: + void Clear(PlayerControl &pc); + + /** + * A tag in the play queue has been modified by the player + * thread. Apply the given song's tag to the current song if + * the song matches. + */ + void TagModified(DetachedSong &&song); + +#ifdef ENABLE_DATABASE + /** + * The database has been modified. Pull all updates. + */ + void DatabaseModified(const Database &db); +#endif + + PlaylistResult AppendSong(PlayerControl &pc, + DetachedSong &&song, + unsigned *added_id=nullptr); + + PlaylistResult AppendURI(PlayerControl &pc, + const SongLoader &loader, + const char *uri_utf8, + unsigned *added_id=nullptr); + +protected: + void DeleteInternal(PlayerControl &pc, + unsigned song, const DetachedSong **queued_p); + +public: + PlaylistResult DeletePosition(PlayerControl &pc, + unsigned position); + + PlaylistResult DeleteOrder(PlayerControl &pc, + unsigned order) { + return DeletePosition(pc, queue.OrderToPosition(order)); + } + + PlaylistResult DeleteId(PlayerControl &pc, unsigned id); + + /** + * Deletes a range of songs from the playlist. + * + * @param start the position of the first song to delete + * @param end the position after the last song to delete + */ + PlaylistResult DeleteRange(PlayerControl &pc, + unsigned start, unsigned end); + + void DeleteSong(PlayerControl &pc, const char *uri); + + void Shuffle(PlayerControl &pc, unsigned start, unsigned end); + + PlaylistResult MoveRange(PlayerControl &pc, + unsigned start, unsigned end, int to); + + PlaylistResult MoveId(PlayerControl &pc, unsigned id, int to); + + PlaylistResult SwapPositions(PlayerControl &pc, + unsigned song1, unsigned song2); + + PlaylistResult SwapIds(PlayerControl &pc, + unsigned id1, unsigned id2); + + PlaylistResult SetPriorityRange(PlayerControl &pc, + unsigned start_position, + unsigned end_position, + uint8_t priority); + + PlaylistResult SetPriorityId(PlayerControl &pc, + unsigned song_id, uint8_t priority); + + bool AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error); + bool ClearSongIdTag(unsigned id, TagType tag_type, Error &error); + + void Stop(PlayerControl &pc); + + PlaylistResult PlayPosition(PlayerControl &pc, int position); + + void PlayOrder(PlayerControl &pc, int order); + + PlaylistResult PlayId(PlayerControl &pc, int id); + + void PlayNext(PlayerControl &pc); + + void PlayPrevious(PlayerControl &pc); + + PlaylistResult SeekSongPosition(PlayerControl &pc, + unsigned song_position, + float seek_time); + + PlaylistResult SeekSongId(PlayerControl &pc, + unsigned song_id, float seek_time); + + /** + * Seek within the current song. Fails if MPD is not currently + * playing. + * + * @param time the time in seconds + * @param relative if true, then the specified time is relative to the + * current position + */ + PlaylistResult SeekCurrent(PlayerControl &pc, + float seek_time, bool relative); + + bool GetRepeat() const { + return queue.repeat; + } + + void SetRepeat(PlayerControl &pc, bool new_value); + + bool GetRandom() const { + return queue.random; + } + + void SetRandom(PlayerControl &pc, bool new_value); + + bool GetSingle() const { + return queue.single; + } + + void SetSingle(PlayerControl &pc, bool new_value); + + bool GetConsume() const { + return queue.consume; + } + + void SetConsume(bool new_value); +}; + +#endif diff --git a/src/queue/PlaylistControl.cxx b/src/queue/PlaylistControl.cxx new file mode 100644 index 000000000..9d75cc26d --- /dev/null +++ b/src/queue/PlaylistControl.cxx @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Functions for controlling playback on the playlist level. + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "DetachedSong.hxx" +#include "Log.hxx" + +void +playlist::Stop(PlayerControl &pc) +{ + if (!playing) + return; + + assert(current >= 0); + + FormatDebug(playlist_domain, "stop"); + pc.Stop(); + queued = -1; + playing = false; + + if (queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = queue.OrderToPosition(current); + + queue.ShuffleOrder(); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + current = queue.PositionToOrder(current_position); + } +} + +PlaylistResult +playlist::PlayPosition(PlayerControl &pc, int song) +{ + pc.ClearError(); + + unsigned i = song; + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue.IsEmpty()) + return PlaylistResult::SUCCESS; + + if (playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc.SetPause(false); + return PlaylistResult::SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = current >= 0 + ? current + : 0; + } else if (!queue.IsValidPosition(song)) + return PlaylistResult::BAD_RANGE; + + if (queue.random) { + if (song >= 0) + /* "i" is currently the song position (which + would be equal to the order number in + no-random mode); convert it to a order + number, because random mode is enabled */ + i = queue.PositionToOrder(song); + + if (!playing) + current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue.SwapOrders(i, current); + i = current; + } + + stop_on_error = false; + error_count = 0; + + PlayOrder(pc, i); + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::PlayId(PlayerControl &pc, int id) +{ + if (id == -1) + return PlayPosition(pc, id); + + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return PlayPosition(pc, song); +} + +void +playlist::PlayNext(PlayerControl &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + assert(queue.IsValidOrder(current)); + + const int old_current = current; + stop_on_error = false; + + /* determine the next song from the queue's order list */ + + const int next_order = queue.GetNextOrder(current); + if (next_order < 0) { + /* no song after this one: stop playback */ + Stop(pc); + + /* reset "current song" */ + current = -1; + } + else + { + if (next_order == 0 && queue.random) { + /* The queue told us that the next song is the first + song. This means we are in repeat mode. Shuffle + the queue order, so this time, the user hears the + songs in a different than before */ + assert(queue.repeat); + + queue.ShuffleOrder(); + + /* note that current and queued are + now invalid, but playlist_play_order() will + discard them anyway */ + } + + PlayOrder(pc, next_order); + } + + /* Consume mode removes each played songs. */ + if (queue.consume) + DeleteOrder(pc, old_current); +} + +void +playlist::PlayPrevious(PlayerControl &pc) +{ + if (!playing) + return; + + assert(!queue.IsEmpty()); + + int order; + if (current > 0) { + /* play the preceding song */ + order = current - 1; + } else if (queue.repeat) { + /* play the last song in "repeat" mode */ + order = queue.GetLength() - 1; + } else { + /* re-start playing the current song if it's + the first one */ + order = current; + } + + PlayOrder(pc, order); +} + +PlaylistResult +playlist::SeekSongPosition(PlayerControl &pc, unsigned song, float seek_time) +{ + if (!queue.IsValidPosition(song)) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *queued_song = GetQueuedSong(); + + unsigned i = queue.random + ? queue.PositionToOrder(song) + : song; + + pc.ClearError(); + stop_on_error = true; + error_count = 0; + + if (!playing || (unsigned)current != i) { + /* seeking is not within the current song - prepare + song change */ + + playing = true; + current = i; + + queued_song = nullptr; + } + + if (!pc.Seek(new DetachedSong(queue.GetOrder(i)), seek_time)) { + UpdateQueuedSong(pc, queued_song); + + return PlaylistResult::NOT_PLAYING; + } + + queued = -1; + UpdateQueuedSong(pc, nullptr); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SeekSongId(PlayerControl &pc, unsigned id, float seek_time) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SeekSongPosition(pc, song, seek_time); +} + +PlaylistResult +playlist::SeekCurrent(PlayerControl &pc, float seek_time, bool relative) +{ + if (!playing) + return PlaylistResult::NOT_PLAYING; + + if (relative) { + const auto status = pc.GetStatus(); + + if (status.state != PlayerState::PLAY && + status.state != PlayerState::PAUSE) + return PlaylistResult::NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return SeekSongPosition(pc, current, seek_time); +} diff --git a/src/queue/PlaylistEdit.cxx b/src/queue/PlaylistEdit.cxx new file mode 100644 index 000000000..8d2c76e6e --- /dev/null +++ b/src/queue/PlaylistEdit.cxx @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "PlayerControl.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "DetachedSong.hxx" +#include "SongLoader.hxx" +#include "Idle.hxx" +#include "Log.hxx" + +#include + +void +playlist::OnModified() +{ + queue.IncrementVersion(); + + idle_add(IDLE_PLAYLIST); +} + +void +playlist::Clear(PlayerControl &pc) +{ + Stop(pc); + + queue.Clear(); + current = -1; + + OnModified(); +} + +PlaylistResult +playlist::AppendSong(PlayerControl &pc, + DetachedSong &&song, unsigned *added_id) +{ + unsigned id; + + if (queue.IsFull()) + return PlaylistResult::TOO_LARGE; + + const DetachedSong *const queued_song = GetQueuedSong(); + + id = queue.Append(std::move(song), 0); + + if (queue.random) { + /* shuffle the new song into the list of remaning + songs to play */ + + unsigned start; + if (queued >= 0) + start = queued + 1; + else + start = current + 1; + if (start < queue.GetLength()) + queue.ShuffleOrderLast(start, queue.GetLength()); + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + if (added_id) + *added_id = id; + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::AppendURI(PlayerControl &pc, + const SongLoader &loader, + const char *uri, unsigned *added_id) +{ + FormatDebug(playlist_domain, "add to playlist: %s", uri); + + Error error; + DetachedSong *song = loader.LoadSong(uri, error); + if (song == nullptr) { + // TODO: return the Error + LogError(error); + return error.IsDomain(playlist_domain) + ? PlaylistResult(error.GetCode()) + : PlaylistResult::NO_SUCH_SONG; + } + + PlaylistResult result = AppendSong(pc, std::move(*song), added_id); + delete song; + + return result; +} + +PlaylistResult +playlist::SwapPositions(PlayerControl &pc, unsigned song1, unsigned song2) +{ + if (!queue.IsValidPosition(song1) || !queue.IsValidPosition(song2)) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *const queued_song = GetQueuedSong(); + + queue.SwapPositions(song1, song2); + + if (queue.random) { + /* update the queue order, so that current + still points to the current song order */ + + queue.SwapOrders(queue.PositionToOrder(song1), + queue.PositionToOrder(song2)); + } else { + /* correct the "current" song order */ + + if (current == (int)song1) + current = song2; + else if (current == (int)song2) + current = song1; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SwapIds(PlayerControl &pc, unsigned id1, unsigned id2) +{ + int song1 = queue.IdToPosition(id1); + int song2 = queue.IdToPosition(id2); + + if (song1 < 0 || song2 < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SwapPositions(pc, song1, song2); +} + +PlaylistResult +playlist::SetPriorityRange(PlayerControl &pc, + unsigned start, unsigned end, + uint8_t priority) +{ + if (start >= GetLength()) + return PlaylistResult::BAD_RANGE; + + if (end > GetLength()) + end = GetLength(); + + if (start >= end) + return PlaylistResult::SUCCESS; + + /* remember "current" and "queued" */ + + const int current_position = GetCurrentPosition(); + const DetachedSong *const queued_song = GetQueuedSong(); + + /* apply the priority changes */ + + queue.SetPriorityRange(start, end, priority, current); + + /* restore "current" and choose a new "queued" */ + + if (current_position >= 0) + current = queue.PositionToOrder(current_position); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::SetPriorityId(PlayerControl &pc, + unsigned song_id, uint8_t priority) +{ + int song_position = queue.IdToPosition(song_id); + if (song_position < 0) + return PlaylistResult::NO_SUCH_SONG; + + return SetPriorityRange(pc, song_position, song_position + 1, + priority); + +} + +void +playlist::DeleteInternal(PlayerControl &pc, + unsigned song, const DetachedSong **queued_p) +{ + assert(song < GetLength()); + + unsigned songOrder = queue.PositionToOrder(song); + + if (playing && current == (int)songOrder) { + const bool paused = pc.GetState() == PlayerState::PAUSE; + + /* the current song is going to be deleted: stop the player */ + + pc.Stop(); + playing = false; + + /* see which song is going to be played instead */ + + current = queue.GetNextOrder(current); + if (current == (int)songOrder) + current = -1; + + if (current >= 0 && !paused) + /* play the song after the deleted one */ + PlayOrder(pc, current); + else + /* no songs left to play, stop playback + completely */ + Stop(pc); + + *queued_p = nullptr; + } else if (current == (int)songOrder) + /* there's a "current song" but we're not playing + currently - clear "current" */ + current = -1; + + /* now do it: remove the song */ + + queue.DeletePosition(song); + + /* update the "current" and "queued" variables */ + + if (current > (int)songOrder) + current--; +} + +PlaylistResult +playlist::DeletePosition(PlayerControl &pc, unsigned song) +{ + if (song >= queue.GetLength()) + return PlaylistResult::BAD_RANGE; + + const DetachedSong *queued_song = GetQueuedSong(); + + DeleteInternal(pc, song, &queued_song); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::DeleteRange(PlayerControl &pc, unsigned start, unsigned end) +{ + if (start >= queue.GetLength()) + return PlaylistResult::BAD_RANGE; + + if (end > queue.GetLength()) + end = queue.GetLength(); + + if (start >= end) + return PlaylistResult::SUCCESS; + + const DetachedSong *queued_song = GetQueuedSong(); + + do { + DeleteInternal(pc, --end, &queued_song); + } while (end != start); + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::DeleteId(PlayerControl &pc, unsigned id) +{ + int song = queue.IdToPosition(id); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return DeletePosition(pc, song); +} + +void +playlist::DeleteSong(PlayerControl &pc, const char *uri) +{ + for (int i = queue.GetLength() - 1; i >= 0; --i) + if (queue.Get(i).IsURI(uri)) + DeletePosition(pc, i); +} + +PlaylistResult +playlist::MoveRange(PlayerControl &pc, unsigned start, unsigned end, int to) +{ + if (!queue.IsValidPosition(start) || !queue.IsValidPosition(end - 1)) + return PlaylistResult::BAD_RANGE; + + if ((to >= 0 && to + end - start - 1 >= GetLength()) || + (to < 0 && unsigned(abs(to)) > GetLength())) + return PlaylistResult::BAD_RANGE; + + if ((int)start == to) + /* nothing happens */ + return PlaylistResult::SUCCESS; + + const DetachedSong *const queued_song = GetQueuedSong(); + + /* + * (to < 0) => move to offset from current song + * (-playlist.length == to) => move to position BEFORE current song + */ + const int currentSong = GetCurrentPosition(); + if (to < 0) { + if (currentSong < 0) + /* can't move relative to current song, + because there is no current song */ + return PlaylistResult::BAD_RANGE; + + if (start <= (unsigned)currentSong && (unsigned)currentSong < end) + /* no-op, can't be moved to offset of itself */ + return PlaylistResult::SUCCESS; + to = (currentSong + abs(to)) % GetLength(); + if (start < (unsigned)to) + to--; + } + + queue.MoveRange(start, end, to); + + if (!queue.random) { + /* update current/queued */ + if ((int)start <= current && (unsigned)current < end) + current += to - start; + else if (current >= (int)end && current <= to) + current -= end - start; + else if (current >= to && current < (int)start) + current += end - start; + } + + UpdateQueuedSong(pc, queued_song); + OnModified(); + + return PlaylistResult::SUCCESS; +} + +PlaylistResult +playlist::MoveId(PlayerControl &pc, unsigned id1, int to) +{ + int song = queue.IdToPosition(id1); + if (song < 0) + return PlaylistResult::NO_SUCH_SONG; + + return MoveRange(pc, song, song + 1, to); +} + +void +playlist::Shuffle(PlayerControl &pc, unsigned start, unsigned end) +{ + if (end > GetLength()) + /* correct the "end" offset */ + end = GetLength(); + + if (start + 1 >= end) + /* needs at least two entries. */ + return; + + const DetachedSong *const queued_song = GetQueuedSong(); + if (playing && current >= 0) { + unsigned current_position = queue.OrderToPosition(current); + + if (current_position >= start && current_position < end) { + /* put current playing song first */ + queue.SwapPositions(start, current_position); + + if (queue.random) { + current = queue.PositionToOrder(start); + } else + current = start; + + /* start shuffle after the current song */ + start++; + } + } else { + /* no playback currently: reset current */ + + current = -1; + } + + queue.ShuffleRange(start, end); + + UpdateQueuedSong(pc, queued_song); + OnModified(); +} diff --git a/src/queue/PlaylistState.cxx b/src/queue/PlaylistState.cxx new file mode 100644 index 000000000..f5c798e3e --- /dev/null +++ b/src/queue/PlaylistState.cxx @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#include "config.h" +#include "PlaylistState.hxx" +#include "PlaylistError.hxx" +#include "Playlist.hxx" +#include "queue/QueueSave.hxx" +#include "fs/TextFile.hxx" +#include "PlayerControl.hxx" +#include "config/ConfigGlobal.hxx" +#include "config/ConfigOption.hxx" +#include "fs/Limits.hxx" +#include "util/CharUtil.hxx" +#include "util/StringUtil.hxx" +#include "Log.hxx" + +#include +#include + +#define PLAYLIST_STATE_FILE_STATE "state: " +#define PLAYLIST_STATE_FILE_RANDOM "random: " +#define PLAYLIST_STATE_FILE_REPEAT "repeat: " +#define PLAYLIST_STATE_FILE_SINGLE "single: " +#define PLAYLIST_STATE_FILE_CONSUME "consume: " +#define PLAYLIST_STATE_FILE_CURRENT "current: " +#define PLAYLIST_STATE_FILE_TIME "time: " +#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: " +#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: " +#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: " +#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin" +#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end" + +#define PLAYLIST_STATE_FILE_STATE_PLAY "play" +#define PLAYLIST_STATE_FILE_STATE_PAUSE "pause" +#define PLAYLIST_STATE_FILE_STATE_STOP "stop" + +#define PLAYLIST_BUFFER_SIZE 2*MPD_PATH_MAX + +void +playlist_state_save(FILE *fp, const struct playlist &playlist, + PlayerControl &pc) +{ + const auto player_status = pc.GetStatus(); + + fputs(PLAYLIST_STATE_FILE_STATE, fp); + + if (playlist.playing) { + switch (player_status.state) { + case PlayerState::PAUSE: + fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); + break; + default: + fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); + } + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist.queue.OrderToPosition(playlist.current)); + fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", + (int)player_status.elapsed_time); + } else { + fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); + + if (playlist.current >= 0) + fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", + playlist.queue.OrderToPosition(playlist.current)); + } + + fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist.queue.random); + fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist.queue.repeat); + fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist.queue.single); + fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", + playlist.queue.consume); + fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", + (int)pc.GetCrossFade()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", + pc.GetMixRampDb()); + fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", + pc.GetMixRampDelay()); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); + queue_save(fp, playlist.queue); + fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); +} + +static void +playlist_state_load(TextFile &file, const SongLoader &song_loader, + struct playlist &playlist) +{ + const char *line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, "No playlist in state file"); + return; + } + + while (!StringStartsWith(line, PLAYLIST_STATE_FILE_PLAYLIST_END)) { + queue_load_song(file, song_loader, line, playlist.queue); + + line = file.ReadLine(); + if (line == nullptr) { + LogWarning(playlist_domain, + "'" PLAYLIST_STATE_FILE_PLAYLIST_END + "' not found in state file"); + break; + } + } + + playlist.queue.IncrementVersion(); +} + +bool +playlist_state_restore(const char *line, TextFile &file, + const SongLoader &song_loader, + struct playlist &playlist, PlayerControl &pc) +{ + int current = -1; + int seek_time = 0; + bool random_mode = false; + + if (!StringStartsWith(line, PLAYLIST_STATE_FILE_STATE)) + return false; + + line += sizeof(PLAYLIST_STATE_FILE_STATE) - 1; + + PlayerState state; + if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PLAY) == 0) + state = PlayerState::PLAY; + else if (strcmp(line, PLAYLIST_STATE_FILE_STATE_PAUSE) == 0) + state = PlayerState::PAUSE; + else + state = PlayerState::STOP; + + while ((line = file.ReadLine()) != nullptr) { + if (StringStartsWith(line, PLAYLIST_STATE_FILE_TIME)) { + seek_time = + atoi(&(line[strlen(PLAYLIST_STATE_FILE_TIME)])); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_REPEAT)) { + playlist.SetRepeat(pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_REPEAT)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_SINGLE)) { + playlist.SetSingle(pc, + strcmp(&(line[strlen(PLAYLIST_STATE_FILE_SINGLE)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CONSUME)) { + playlist.SetConsume(strcmp(&(line[strlen(PLAYLIST_STATE_FILE_CONSUME)]), + "1") == 0); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CROSSFADE)) { + pc.SetCrossFade(atoi(line + strlen(PLAYLIST_STATE_FILE_CROSSFADE))); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDB)) { + pc.SetMixRampDb(atof(line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB))); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) { + const char *p = line + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY); + + /* this check discards "nan" which was used + prior to MPD 0.18 */ + if (IsDigitASCII(*p)) + pc.SetMixRampDelay(atof(p)); + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_RANDOM)) { + random_mode = + strcmp(line + strlen(PLAYLIST_STATE_FILE_RANDOM), + "1") == 0; + } else if (StringStartsWith(line, PLAYLIST_STATE_FILE_CURRENT)) { + current = atoi(&(line + [strlen + (PLAYLIST_STATE_FILE_CURRENT)])); + } else if (StringStartsWith(line, + PLAYLIST_STATE_FILE_PLAYLIST_BEGIN)) { + playlist_state_load(file, song_loader, playlist); + } + } + + playlist.SetRandom(pc, random_mode); + + if (!playlist.queue.IsEmpty()) { + if (!playlist.queue.IsValidPosition(current)) + current = 0; + + if (state == PlayerState::PLAY && + config_get_bool(CONF_RESTORE_PAUSED, false)) + /* the user doesn't want MPD to auto-start + playback after startup; fall back to + "pause" */ + state = PlayerState::PAUSE; + + /* enable all devices for the first time; this must be + called here, after the audio output states were + restored, before playback begins */ + if (state != PlayerState::STOP) + pc.UpdateAudio(); + + if (state == PlayerState::STOP /* && config_option */) + playlist.current = current; + else if (seek_time == 0) + playlist.PlayPosition(pc, current); + else + playlist.SeekSongPosition(pc, current, seek_time); + + if (state == PlayerState::PAUSE) + pc.Pause(); + } + + return true; +} + +unsigned +playlist_state_get_hash(const playlist &playlist, + PlayerControl &pc) +{ + const auto player_status = pc.GetStatus(); + + return playlist.queue.version ^ + (player_status.state != PlayerState::STOP + ? ((int)player_status.elapsed_time << 8) + : 0) ^ + (playlist.current >= 0 + ? (playlist.queue.OrderToPosition(playlist.current) << 16) + : 0) ^ + ((int)pc.GetCrossFade() << 20) ^ + (unsigned(player_status.state) << 24) ^ + (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/PlaylistState.hxx b/src/queue/PlaylistState.hxx new file mode 100644 index 000000000..8d3f88ae2 --- /dev/null +++ b/src/queue/PlaylistState.hxx @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Saving and loading the playlist to/from the state file. + * + */ + +#ifndef MPD_PLAYLIST_STATE_HXX +#define MPD_PLAYLIST_STATE_HXX + +#include + +struct playlist; +struct PlayerControl; +class TextFile; +class SongLoader; + +void +playlist_state_save(FILE *fp, const playlist &playlist, + PlayerControl &pc); + +bool +playlist_state_restore(const char *line, TextFile &file, + const SongLoader &song_loader, + playlist &playlist, PlayerControl &pc); + +/** + * Generates a hash number for the current state of the playlist and + * the playback options. This is used by timer_save_state_file() to + * determine whether the state has changed and the state file should + * be saved. + */ +unsigned +playlist_state_get_hash(const playlist &playlist, + PlayerControl &c); + +#endif diff --git a/src/queue/PlaylistTag.cxx b/src/queue/PlaylistTag.cxx new file mode 100644 index 000000000..556e7f4e9 --- /dev/null +++ b/src/queue/PlaylistTag.cxx @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2003-2014 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. + */ + +/* + * Functions for editing the playlist (adding, removing, reordering + * songs in the queue). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "PlaylistError.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "tag/TagBuilder.hxx" +#include "util/Error.hxx" + +bool +playlist::AddSongIdTag(unsigned id, TagType tag_type, const char *value, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + tag.AddItem(tag_type, value); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} + +bool +playlist::ClearSongIdTag(unsigned id, TagType tag_type, + Error &error) +{ + const int position = queue.IdToPosition(id); + if (position < 0) { + error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), + "No such song"); + return false; + } + + DetachedSong &song = queue.Get(position); + if (song.IsFile()) { + error.Set(playlist_domain, int(PlaylistResult::DENIED), + "Cannot edit tags of local file"); + return false; + } + + { + TagBuilder tag(std::move(song.WritableTag())); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) + tag.RemoveAll(); + else + tag.RemoveType(tag_type); + song.SetTag(tag.Commit()); + } + + queue.ModifyAtPosition(position); + OnModified(); + return true; +} diff --git a/src/queue/PlaylistUpdate.cxx b/src/queue/PlaylistUpdate.cxx new file mode 100644 index 000000000..8876711ef --- /dev/null +++ b/src/queue/PlaylistUpdate.cxx @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2003-2014 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 "config.h" +#include "Playlist.hxx" +#include "db/Interface.hxx" +#include "db/LightSong.hxx" +#include "DetachedSong.hxx" +#include "tag/Tag.hxx" +#include "Idle.hxx" +#include "util/Error.hxx" + +static bool +UpdatePlaylistSong(const Database &db, DetachedSong &song) +{ + if (!song.IsInDatabase() || !song.IsFile()) + /* only update Songs instances that are "detached" + from the Database */ + return false; + + const LightSong *original = db.GetSong(song.GetURI(), IgnoreError()); + if (original == nullptr) + /* not found - shouldn't happen, because the update + thread should ensure that all stale Song instances + have been purged */ + return false; + + if (original->mtime == song.GetLastModified()) { + /* not modified */ + db.ReturnSong(original); + return false; + } + + song.SetLastModified(original->mtime); + song.SetTag(*original->tag); + + db.ReturnSong(original); + return true; +} + +void +playlist::DatabaseModified(const Database &db) +{ + bool modified = false; + + for (unsigned i = 0, n = queue.GetLength(); i != n; ++i) { + if (UpdatePlaylistSong(db, queue.Get(i))) { + queue.ModifyAtPosition(i); + modified = true; + } + } + + if (modified) { + queue.IncrementVersion(); + idle_add(IDLE_PLAYLIST); + } +} -- cgit v1.2.3