From 05c91082e3de0b0078c26ddb9da68fd00da8c90e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 4 Jan 2013 20:50:00 +0100 Subject: playlist: convert to C++ --- Makefile.am | 15 +- src/DatabaseQueue.cxx | 6 +- src/Main.cxx | 2 +- src/PlayerCommands.cxx | 2 +- src/Playlist.cxx | 452 +++++++++++++++++++++++++++++++++ src/Playlist.hxx | 256 +++++++++++++++++++ src/PlaylistCommands.cxx | 2 +- src/PlaylistControl.cxx | 293 ++++++++++++++++++++++ src/PlaylistEdit.cxx | 3 +- src/PlaylistGlobal.cxx | 60 +++++ src/PlaylistInternal.hxx | 56 +++++ src/PlaylistPrint.cxx | 2 +- src/PlaylistQueue.cxx | 2 +- src/PlaylistSave.cxx | 4 +- src/PlaylistState.cxx | 4 +- src/Queue.cxx | 604 +++++++++++++++++++++++++++++++++++++++++++++ src/Queue.hxx | 379 ++++++++++++++++++++++++++++ src/QueueCommands.cxx | 2 +- src/QueuePrint.cxx | 4 +- src/QueueSave.cxx | 2 +- src/StateFile.cxx | 2 +- src/UpdateGlue.cxx | 4 +- src/UpdateRemove.cxx | 2 +- src/playlist.c | 449 --------------------------------- src/playlist.h | 256 ------------------- src/playlist_control.c | 291 ---------------------- src/playlist_global.c | 57 ----- src/playlist_internal.h | 56 ----- src/queue.c | 604 --------------------------------------------- src/queue.h | 379 ---------------------------- src/replay_gain_config.c | 4 +- test/TestQueuePriority.cxx | 4 +- test/run_filter.c | 4 +- test/run_output.cxx | 2 +- 34 files changed, 2133 insertions(+), 2131 deletions(-) create mode 100644 src/Playlist.cxx create mode 100644 src/Playlist.hxx create mode 100644 src/PlaylistControl.cxx create mode 100644 src/PlaylistGlobal.cxx create mode 100644 src/PlaylistInternal.hxx create mode 100644 src/Queue.cxx create mode 100644 src/Queue.hxx delete mode 100644 src/playlist.c delete mode 100644 src/playlist.h delete mode 100644 src/playlist_control.c delete mode 100644 src/playlist_global.c delete mode 100644 src/playlist_internal.h delete mode 100644 src/queue.c delete mode 100644 src/queue.h diff --git a/Makefile.am b/Makefile.am index 0e76eb54f..e0ef76586 100644 --- a/Makefile.am +++ b/Makefile.am @@ -115,9 +115,9 @@ mpd_headers = \ src/output/httpd_internal.h \ src/page.h \ src/player_control.h \ - src/playlist.h \ + src/Playlist.hxx \ src/playlist_error.h \ - src/playlist_internal.h \ + src/PlaylistInternal.hxx \ src/playlist_plugin.h \ src/playlist_list.h \ src/playlist/extm3u_playlist_plugin.h \ @@ -132,7 +132,6 @@ mpd_headers = \ src/poison.h \ src/riff.h \ src/aiff.h \ - src/queue.h \ src/refcount.h \ src/replay_gain_config.h \ src/replay_gain_info.h \ @@ -267,9 +266,9 @@ src_mpd_SOURCES = \ src/Permission.cxx src/Permission.hxx \ src/PlayerThread.cxx src/PlayerThread.hxx \ src/PlayerControl.cxx \ - src/playlist.c \ - src/playlist_global.c \ - src/playlist_control.c \ + src/Playlist.cxx \ + src/PlaylistGlobal.cxx \ + src/PlaylistControl.cxx \ src/PlaylistEdit.cxx \ src/PlaylistPrint.cxx src/PlaylistPrint.hxx \ src/PlaylistSave.cxx src/PlaylistSave.hxx \ @@ -281,7 +280,7 @@ src_mpd_SOURCES = \ src/PlaylistVector.cxx src/PlaylistVector.hxx \ src/PlaylistInfo.hxx \ src/PlaylistDatabase.cxx \ - src/queue.c \ + src/Queue.cxx src/Queue.hxx \ src/QueuePrint.cxx src/QueuePrint.hxx \ src/QueueSave.cxx src/QueueSave.hxx \ src/replay_gain_config.c \ @@ -1339,7 +1338,7 @@ test_test_pcm_LDADD = \ $(GLIB_LIBS) test_TestQueuePriority_SOURCES = \ - src/queue.c \ + src/Queue.cxx \ test/TestQueuePriority.cxx test_TestQueuePriority_LDADD = \ $(GLIB_LIBS) diff --git a/src/DatabaseQueue.cxx b/src/DatabaseQueue.cxx index 325748d02..0b4d2bcb7 100644 --- a/src/DatabaseQueue.cxx +++ b/src/DatabaseQueue.cxx @@ -20,13 +20,9 @@ #include "config.h" #include "DatabaseQueue.hxx" #include "DatabaseSelection.hxx" - -extern "C" { -#include "playlist.h" -} - #include "DatabaseGlue.hxx" #include "DatabasePlugin.hxx" +#include "Playlist.hxx" #include diff --git a/src/Main.cxx b/src/Main.cxx index 2da1ca693..3340bf3de 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "Main.hxx" #include "CommandLine.hxx" +#include "Playlist.hxx" #include "PlaylistFile.hxx" #include "UpdateGlue.hxx" #include "MusicChunk.hxx" @@ -38,7 +39,6 @@ extern "C" { #include "daemon.h" #include "io_thread.h" #include "idle.h" -#include "playlist.h" #include "conf.h" #include "path.h" #include "player_control.h" diff --git a/src/PlayerCommands.cxx b/src/PlayerCommands.cxx index e4b5c145f..55bd83a13 100644 --- a/src/PlayerCommands.cxx +++ b/src/PlayerCommands.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "PlayerCommands.hxx" #include "CommandError.hxx" +#include "Playlist.hxx" #include "PlaylistPrint.hxx" #include "UpdateGlue.hxx" #include "ClientInternal.hxx" @@ -28,7 +29,6 @@ extern "C" { #include "player_control.h" -#include "playlist.h" #include "volume.h" #include "replay_gain_config.h" } diff --git a/src/Playlist.cxx b/src/Playlist.cxx new file mode 100644 index 000000000..de2daf144 --- /dev/null +++ b/src/Playlist.cxx @@ -0,0 +1,452 @@ +/* + * Copyright (C) 2003-2013 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 "PlaylistInternal.hxx" +#include "song.h" + +extern "C" { +#include "player_control.h" +#include "conf.h" +#include "idle.h" +} + +#include + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "playlist" + +void +playlist_increment_version_all(struct playlist *playlist) +{ + queue_modify_all(&playlist->queue); + idle_add(IDLE_PLAYLIST); +} + +void +playlist_tag_changed(struct playlist *playlist) +{ + if (!playlist->playing) + return; + + assert(playlist->current >= 0); + + queue_modify(&playlist->queue, playlist->current); + idle_add(IDLE_PLAYLIST); +} + +void +playlist_init(struct playlist *playlist) +{ + queue_init(&playlist->queue, + config_get_positive(CONF_MAX_PLAYLIST_LENGTH, + DEFAULT_PLAYLIST_MAX_LENGTH)); + + playlist->queued = -1; + playlist->current = -1; +} + +void +playlist_finish(struct playlist *playlist) +{ + queue_finish(&playlist->queue); +} + +/** + * Queue a song, addressed by its order number. + */ +static void +playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, + unsigned order) +{ + char *uri; + + assert(queue_valid_order(&playlist->queue, order)); + + playlist->queued = order; + + struct song *song = + song_dup_detached(queue_get_order(&playlist->queue, order)); + + uri = song_get_uri(song); + g_debug("queue song %i:\"%s\"", playlist->queued, uri); + g_free(uri); + + pc_enqueue_song(pc, song); +} + +/** + * Called if the player thread has started playing the "queued" song. + */ +static void +playlist_song_started(struct playlist *playlist, struct player_control *pc) +{ + assert(pc->next_song == NULL); + 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_delete(playlist, pc, + queue_order_to_position(&playlist->queue, + current)); + + idle_add(IDLE_PLAYER); +} + +const struct song * +playlist_get_queued_song(struct playlist *playlist) +{ + if (!playlist->playing || playlist->queued < 0) + return NULL; + + return queue_get_order(&playlist->queue, playlist->queued); +} + +void +playlist_update_queued_song(struct playlist *playlist, + struct player_control *pc, + const struct song *prev) +{ + int next_order; + const struct song *next_song; + + if (!playlist->playing) + return; + + assert(!queue_is_empty(&playlist->queue)); + assert((playlist->queued < 0) == (prev == NULL)); + + next_order = playlist->current >= 0 + ? queue_next_order(&playlist->queue, playlist->current) + : 0; + + if (next_order == 0 && playlist->queue.random && + !playlist->queue.single) { + /* shuffle the song order again, so we get a different + order each time the playlist is played + completely */ + unsigned current_position = + queue_order_to_position(&playlist->queue, + playlist->current); + + queue_shuffle_order(&playlist->queue); + + /* make sure that the playlist->current still points to + the current song, after the song order has been + shuffled */ + playlist->current = + queue_position_to_order(&playlist->queue, + current_position); + } + + if (next_order >= 0) + next_song = queue_get_order(&playlist->queue, next_order); + else + next_song = NULL; + + if (prev != NULL && next_song != prev) { + /* clear the currently queued song */ + pc_cancel(pc); + playlist->queued = -1; + } + + if (next_order >= 0) { + if (next_song != prev) + playlist_queue_song_order(playlist, pc, next_order); + else + playlist->queued = next_order; + } +} + +void +playlist_play_order(struct playlist *playlist, struct player_control *pc, + int orderNum) +{ + char *uri; + + playlist->playing = true; + playlist->queued = -1; + + struct song *song = + song_dup_detached(queue_get_order(&playlist->queue, orderNum)); + + uri = song_get_uri(song); + g_debug("play %i:\"%s\"", orderNum, uri); + g_free(uri); + + pc_play(pc, song); + playlist->current = orderNum; +} + +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc); + +/** + * 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 +playlist_sync(struct playlist *playlist, struct player_control *pc) +{ + if (!playlist->playing) + /* this event has reached us out of sync: we aren't + playing anymore; ignore the event */ + return; + + player_lock(pc); + enum player_state pc_state = pc_get_state(pc); + const struct song *pc_next_song = pc->next_song; + player_unlock(pc); + + if (pc_state == PLAYER_STATE_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(playlist, pc); + else { + /* check if the player thread has already started + playing the queued song */ + if (pc_next_song == NULL && playlist->queued != -1) + playlist_song_started(playlist, pc); + + player_lock(pc); + pc_next_song = pc->next_song; + player_unlock(pc); + + /* make sure the queued song is always set (if + possible) */ + if (pc_next_song == NULL && playlist->queued < 0) + playlist_update_queued_song(playlist, pc, NULL); + } +} + +/** + * The player has stopped for some reason. Check the error, and + * decide whether to re-start playback + */ +static void +playlist_resume_playback(struct playlist *playlist, struct player_control *pc) +{ + enum player_error error; + + assert(playlist->playing); + assert(pc_get_state(pc) == PLAYER_STATE_STOP); + + error = pc_get_error_type(pc); + if (error == PLAYER_ERROR_NONE) + playlist->error_count = 0; + else + ++playlist->error_count; + + if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) || + error == PLAYER_ERROR_OUTPUT || + playlist->error_count >= queue_length(&playlist->queue)) + /* too many errors, or critical error: stop + playback */ + playlist_stop(playlist, pc); + else + /* continue playback at the next song */ + playlist_next(playlist, pc); +} + +bool +playlist_get_repeat(const struct playlist *playlist) +{ + return playlist->queue.repeat; +} + +bool +playlist_get_random(const struct playlist *playlist) +{ + return playlist->queue.random; +} + +bool +playlist_get_single(const struct playlist *playlist) +{ + return playlist->queue.single; +} + +bool +playlist_get_consume(const struct playlist *playlist) +{ + return playlist->queue.consume; +} + +void +playlist_set_repeat(struct playlist *playlist, struct player_control *pc, + bool status) +{ + if (status == playlist->queue.repeat) + return; + + struct queue *queue = &playlist->queue; + + queue->repeat = status; + + pc_set_border_pause(pc, queue->single && !queue->repeat); + + /* if the last song is currently being played, the "next song" + might change when repeat mode is toggled */ + playlist_update_queued_song(playlist, pc, + playlist_get_queued_song(playlist)); + + idle_add(IDLE_OPTIONS); +} + +static void +playlist_order(struct playlist *playlist) +{ + if (playlist->current >= 0) + /* update playlist.current, order==position now */ + playlist->current = queue_order_to_position(&playlist->queue, + playlist->current); + + queue_restore_order(&playlist->queue); +} + +void +playlist_set_single(struct playlist *playlist, struct player_control *pc, + bool status) +{ + if (status == playlist->queue.single) + return; + + struct queue *queue = &playlist->queue; + + queue->single = status; + + pc_set_border_pause(pc, queue->single && !queue->repeat); + + /* if the last song is currently being played, the "next song" + might change when single mode is toggled */ + playlist_update_queued_song(playlist, pc, + playlist_get_queued_song(playlist)); + + idle_add(IDLE_OPTIONS); +} + +void +playlist_set_consume(struct playlist *playlist, bool status) +{ + if (status == playlist->queue.consume) + return; + + playlist->queue.consume = status; + idle_add(IDLE_OPTIONS); +} + +void +playlist_set_random(struct playlist *playlist, struct player_control *pc, + bool status) +{ + const struct song *queued; + + if (status == playlist->queue.random) + return; + + queued = playlist_get_queued_song(playlist); + + playlist->queue.random = status; + + if (playlist->queue.random) { + /* shuffle the queue order, but preserve + playlist->current */ + + int current_position = + playlist->playing && playlist->current >= 0 + ? (int)queue_order_to_position(&playlist->queue, + playlist->current) + : -1; + + queue_shuffle_order(&playlist->queue); + + 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_position_to_order(&playlist->queue, + current_position); + queue_swap_order(&playlist->queue, 0, current_order); + playlist->current = 0; + } else + playlist->current = -1; + } else + playlist_order(playlist); + + playlist_update_queued_song(playlist, pc, queued); + + idle_add(IDLE_OPTIONS); +} + +int +playlist_get_current_song(const struct playlist *playlist) +{ + if (playlist->current >= 0) + return queue_order_to_position(&playlist->queue, + playlist->current); + + return -1; +} + +int +playlist_get_next_song(const struct playlist *playlist) +{ + if (playlist->current >= 0) + { + if (playlist->queue.single == 1 && playlist->queue.repeat == 1) + return queue_order_to_position(&playlist->queue, + playlist->current); + else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) + return queue_order_to_position(&playlist->queue, + playlist->current + 1); + else if (playlist->queue.repeat == 1) + return queue_order_to_position(&playlist->queue, 0); + } + + return -1; +} + +unsigned long +playlist_get_version(const struct playlist *playlist) +{ + return playlist->queue.version; +} + +int +playlist_get_length(const struct playlist *playlist) +{ + return queue_length(&playlist->queue); +} + +unsigned +playlist_get_song_id(const struct playlist *playlist, unsigned song) +{ + return queue_position_to_id(&playlist->queue, song); +} diff --git a/src/Playlist.hxx b/src/Playlist.hxx new file mode 100644 index 000000000..3bbb112b7 --- /dev/null +++ b/src/Playlist.hxx @@ -0,0 +1,256 @@ +/* + * Copyright (C) 2003-2013 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.hxx" +#include "playlist_error.h" + +#include + +struct player_control; + +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; +}; + +/** the global playlist object */ +extern struct playlist g_playlist; + +void +playlist_global_init(void); + +void +playlist_global_finish(void); + +void +playlist_init(struct playlist *playlist); + +void +playlist_finish(struct playlist *playlist); + +void +playlist_tag_changed(struct playlist *playlist); + +/** + * Returns the "queue" object of the global playlist instance. + */ +static inline const struct queue * +playlist_get_queue(const struct playlist *playlist) +{ + return &playlist->queue; +} + +void +playlist_clear(struct playlist *playlist, struct player_control *pc); + +/** + * Appends a local file (outside the music database) to the playlist. + * + * Note: the caller is responsible for checking permissions. + */ +enum playlist_result +playlist_append_file(struct playlist *playlist, struct player_control *pc, + const char *path_fs, unsigned *added_id); + +enum playlist_result +playlist_append_uri(struct playlist *playlist, struct player_control *pc, + const char *file, unsigned *added_id); + +enum playlist_result +playlist_append_song(struct playlist *playlist, struct player_control *pc, + struct song *song, unsigned *added_id); + +enum playlist_result +playlist_delete(struct playlist *playlist, struct player_control *pc, + unsigned song); + +/** + * 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 + */ +enum playlist_result +playlist_delete_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end); + +enum playlist_result +playlist_delete_id(struct playlist *playlist, struct player_control *pc, + unsigned song); + +void +playlist_stop(struct playlist *playlist, struct player_control *pc); + +enum playlist_result +playlist_play(struct playlist *playlist, struct player_control *pc, + int song); + +enum playlist_result +playlist_play_id(struct playlist *playlist, struct player_control *pc, + int song); + +void +playlist_next(struct playlist *playlist, struct player_control *pc); + +void +playlist_sync(struct playlist *playlist, struct player_control *pc); + +void +playlist_previous(struct playlist *playlist, struct player_control *pc); + +void +playlist_shuffle(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end); + +void +playlist_delete_song(struct playlist *playlist, struct player_control *pc, + const struct song *song); + +enum playlist_result +playlist_move_range(struct playlist *playlist, struct player_control *pc, + unsigned start, unsigned end, int to); + +enum playlist_result +playlist_move_id(struct playlist *playlist, struct player_control *pc, + unsigned id, int to); + +enum playlist_result +playlist_swap_songs(struct playlist *playlist, struct player_control *pc, + unsigned song1, unsigned song2); + +enum playlist_result +playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, + unsigned id1, unsigned id2); + +enum playlist_result +playlist_set_priority(struct playlist *playlist, struct player_control *pc, + unsigned start_position, unsigned end_position, + uint8_t priority); + +enum playlist_result +playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, + unsigned song_id, uint8_t priority); + +bool +playlist_get_repeat(const struct playlist *playlist); + +void +playlist_set_repeat(struct playlist *playlist, struct player_control *pc, + bool status); + +bool +playlist_get_random(const struct playlist *playlist); + +void +playlist_set_random(struct playlist *playlist, struct player_control *pc, + bool status); + +bool +playlist_get_single(const struct playlist *playlist); + +void +playlist_set_single(struct playlist *playlist, struct player_control *pc, + bool status); + +bool +playlist_get_consume(const struct playlist *playlist); + +void +playlist_set_consume(struct playlist *playlist, bool status); + +int +playlist_get_current_song(const struct playlist *playlist); + +int +playlist_get_next_song(const struct playlist *playlist); + +unsigned +playlist_get_song_id(const struct playlist *playlist, unsigned song); + +int +playlist_get_length(const struct playlist *playlist); + +unsigned long +playlist_get_version(const struct playlist *playlist); + +enum playlist_result +playlist_seek_song(struct playlist *playlist, struct player_control *pc, + unsigned song, float seek_time); + +enum playlist_result +playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, + unsigned 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 + */ +enum playlist_result +playlist_seek_current(struct playlist *playlist, struct player_control *pc, + float seek_time, bool relative); + +void +playlist_increment_version_all(struct playlist *playlist); + +#endif diff --git a/src/PlaylistCommands.cxx b/src/PlaylistCommands.cxx index d2defff3f..d8a9b5fc9 100644 --- a/src/PlaylistCommands.cxx +++ b/src/PlaylistCommands.cxx @@ -31,9 +31,9 @@ #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" +#include "Playlist.hxx" extern "C" { -#include "playlist.h" #include "uri.h" } diff --git a/src/PlaylistControl.cxx b/src/PlaylistControl.cxx new file mode 100644 index 000000000..464b25cfc --- /dev/null +++ b/src/PlaylistControl.cxx @@ -0,0 +1,293 @@ +/* + * Copyright (C) 2003-2013 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 "PlaylistInternal.hxx" +#include "song.h" + +extern "C" { +#include "player_control.h" +} + +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "playlist" + +void +playlist_stop(struct playlist *playlist, struct player_control *pc) +{ + if (!playlist->playing) + return; + + assert(playlist->current >= 0); + + g_debug("stop"); + pc_stop(pc); + playlist->queued = -1; + playlist->playing = false; + + if (playlist->queue.random) { + /* shuffle the playlist, so the next playback will + result in a new random order */ + + unsigned current_position = + queue_order_to_position(&playlist->queue, + playlist->current); + + queue_shuffle_order(&playlist->queue); + + /* make sure that "current" stays valid, and the next + "play" command plays the same song again */ + playlist->current = + queue_position_to_order(&playlist->queue, + current_position); + } +} + +enum playlist_result +playlist_play(struct playlist *playlist, struct player_control *pc, + int song) +{ + unsigned i = song; + + pc_clear_error(pc); + + if (song == -1) { + /* play any song ("current" song, or the first song */ + + if (queue_is_empty(&playlist->queue)) + return PLAYLIST_RESULT_SUCCESS; + + if (playlist->playing) { + /* already playing: unpause playback, just in + case it was paused, and return */ + pc_set_pause(pc, false); + return PLAYLIST_RESULT_SUCCESS; + } + + /* select a song: "current" song, or the first one */ + i = playlist->current >= 0 + ? playlist->current + : 0; + } else if (!queue_valid_position(&playlist->queue, song)) + return PLAYLIST_RESULT_BAD_RANGE; + + if (playlist->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_position_to_order(&playlist->queue, song); + + if (!playlist->playing) + playlist->current = 0; + + /* swap the new song with the previous "current" one, + so playback continues as planned */ + queue_swap_order(&playlist->queue, + i, playlist->current); + i = playlist->current; + } + + playlist->stop_on_error = false; + playlist->error_count = 0; + + playlist_play_order(playlist, pc, i); + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_play_id(struct playlist *playlist, struct player_control *pc, + int id) +{ + int song; + + if (id == -1) { + return playlist_play(playlist, pc, id); + } + + song = queue_id_to_position(&playlist->queue, id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_play(playlist, pc, song); +} + +void +playlist_next(struct playlist *playlist, struct player_control *pc) +{ + int next_order; + int current; + + if (!playlist->playing) + return; + + assert(!queue_is_empty(&playlist->queue)); + assert(queue_valid_order(&playlist->queue, playlist->current)); + + current = playlist->current; + playlist->stop_on_error = false; + + /* determine the next song from the queue's order list */ + + next_order = queue_next_order(&playlist->queue, playlist->current); + if (next_order < 0) { + /* no song after this one: stop playback */ + playlist_stop(playlist, pc); + + /* reset "current song" */ + playlist->current = -1; + } + else + { + if (next_order == 0 && playlist->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(playlist->queue.repeat); + + queue_shuffle_order(&playlist->queue); + + /* note that playlist->current and playlist->queued are + now invalid, but playlist_play_order() will + discard them anyway */ + } + + playlist_play_order(playlist, pc, next_order); + } + + /* Consume mode removes each played songs. */ + if(playlist->queue.consume) + playlist_delete(playlist, pc, + queue_order_to_position(&playlist->queue, + current)); +} + +void +playlist_previous(struct playlist *playlist, struct player_control *pc) +{ + if (!playlist->playing) + return; + + assert(queue_length(&playlist->queue) > 0); + + if (playlist->current > 0) { + /* play the preceding song */ + playlist_play_order(playlist, pc, + playlist->current - 1); + } else if (playlist->queue.repeat) { + /* play the last song in "repeat" mode */ + playlist_play_order(playlist, pc, + queue_length(&playlist->queue) - 1); + } else { + /* re-start playing the current song if it's + the first one */ + playlist_play_order(playlist, pc, playlist->current); + } +} + +enum playlist_result +playlist_seek_song(struct playlist *playlist, struct player_control *pc, + unsigned song, float seek_time) +{ + const struct song *queued; + unsigned i; + bool success; + + if (!queue_valid_position(&playlist->queue, song)) + return PLAYLIST_RESULT_BAD_RANGE; + + queued = playlist_get_queued_song(playlist); + + if (playlist->queue.random) + i = queue_position_to_order(&playlist->queue, song); + else + i = song; + + pc_clear_error(pc); + playlist->stop_on_error = true; + playlist->error_count = 0; + + if (!playlist->playing || (unsigned)playlist->current != i) { + /* seeking is not within the current song - prepare + song change */ + + playlist->playing = true; + playlist->current = i; + + queued = NULL; + } + + struct song *the_song = + song_dup_detached(queue_get_order(&playlist->queue, i)); + success = pc_seek(pc, the_song, seek_time); + if (!success) { + playlist_update_queued_song(playlist, pc, queued); + + return PLAYLIST_RESULT_NOT_PLAYING; + } + + playlist->queued = -1; + playlist_update_queued_song(playlist, pc, NULL); + + return PLAYLIST_RESULT_SUCCESS; +} + +enum playlist_result +playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, + unsigned id, float seek_time) +{ + int song = queue_id_to_position(&playlist->queue, id); + if (song < 0) + return PLAYLIST_RESULT_NO_SUCH_SONG; + + return playlist_seek_song(playlist, pc, song, seek_time); +} + +enum playlist_result +playlist_seek_current(struct playlist *playlist, struct player_control *pc, + float seek_time, bool relative) +{ + if (!playlist->playing) + return PLAYLIST_RESULT_NOT_PLAYING; + + if (relative) { + struct player_status status; + pc_get_status(pc, &status); + + if (status.state != PLAYER_STATE_PLAY && + status.state != PLAYER_STATE_PAUSE) + return PLAYLIST_RESULT_NOT_PLAYING; + + seek_time += (int)status.elapsed_time; + } + + if (seek_time < 0) + seek_time = 0; + + return playlist_seek_song(playlist, pc, playlist->current, seek_time); +} diff --git a/src/PlaylistEdit.cxx b/src/PlaylistEdit.cxx index d44de0581..baf483427 100644 --- a/src/PlaylistEdit.cxx +++ b/src/PlaylistEdit.cxx @@ -24,8 +24,9 @@ */ #include "config.h" +#include "PlaylistInternal.hxx" + extern "C" { -#include "playlist_internal.h" #include "player_control.h" #include "uri.h" #include "song.h" diff --git a/src/PlaylistGlobal.cxx b/src/PlaylistGlobal.cxx new file mode 100644 index 000000000..91c571b6f --- /dev/null +++ b/src/PlaylistGlobal.cxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +/* + * The manager of the global "struct playlist" instance (g_playlist). + * + */ + +#include "config.h" +#include "Playlist.hxx" +#include "Main.hxx" + +extern "C" { +#include "event_pipe.h" +} + +struct playlist g_playlist; + +static void +playlist_tag_event(void) +{ + playlist_tag_changed(&g_playlist); +} + +static void +playlist_event(void) +{ + playlist_sync(&g_playlist, global_player_control); +} + +void +playlist_global_init(void) +{ + playlist_init(&g_playlist); + + event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event); + event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); +} + +void +playlist_global_finish(void) +{ + playlist_finish(&g_playlist); +} diff --git a/src/PlaylistInternal.hxx b/src/PlaylistInternal.hxx new file mode 100644 index 000000000..f0cad8e68 --- /dev/null +++ b/src/PlaylistInternal.hxx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2003-2013 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. + */ + +/* + * Internal header for the components of the playlist code. + * + */ + +#ifndef MPD_PLAYLIST_INTERNAL_HXX +#define MPD_PLAYLIST_INTERNAL_HXX + +#include "Playlist.hxx" + +struct player_control; + +/** + * Returns the song object which is currently queued. Returns none if + * there is none (yet?) or if MPD isn't playing. + */ +const struct song * +playlist_get_queued_song(struct playlist *playlist); + +/** + * 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 +playlist_update_queued_song(struct playlist *playlist, + struct player_control *pc, + const struct song *prev); + +void +playlist_play_order(struct playlist *playlist, struct player_control *pc, + int orderNum); + +#endif diff --git a/src/PlaylistPrint.cxx b/src/PlaylistPrint.cxx index de17ac2c8..cbbd71bbd 100644 --- a/src/PlaylistPrint.cxx +++ b/src/PlaylistPrint.cxx @@ -22,6 +22,7 @@ #include "PlaylistFile.hxx" #include "PlaylistAny.hxx" #include "PlaylistSong.hxx" +#include "Playlist.hxx" #include "QueuePrint.hxx" #include "SongPrint.hxx" #include "DatabaseGlue.hxx" @@ -31,7 +32,6 @@ extern "C" { #include "playlist_list.h" #include "playlist_plugin.h" -#include "playlist.h" #include "song.h" #include "input_stream.h" } diff --git a/src/PlaylistQueue.cxx b/src/PlaylistQueue.cxx index ad9739449..e75720315 100644 --- a/src/PlaylistQueue.cxx +++ b/src/PlaylistQueue.cxx @@ -22,9 +22,9 @@ #include "playlist_plugin.h" #include "PlaylistAny.hxx" #include "PlaylistSong.hxx" +#include "Playlist.hxx" extern "C" { -#include "playlist.h" #include "song.h" #include "input_stream.h" } diff --git a/src/PlaylistSave.cxx b/src/PlaylistSave.cxx index 722ae9c83..7b88a8434 100644 --- a/src/PlaylistSave.cxx +++ b/src/PlaylistSave.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,11 +20,11 @@ #include "config.h" #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" +#include "Playlist.hxx" #include "song.h" #include "Mapper.hxx" extern "C" { -#include "playlist.h" #include "path.h" #include "uri.h" #include "idle.h" diff --git a/src/PlaylistState.cxx b/src/PlaylistState.cxx index 1bb18250f..92aa62947 100644 --- a/src/PlaylistState.cxx +++ b/src/PlaylistState.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,11 +24,11 @@ #include "config.h" #include "PlaylistState.hxx" +#include "Playlist.hxx" #include "QueueSave.hxx" #include "TextFile.hxx" extern "C" { -#include "playlist.h" #include "player_control.h" #include "conf.h" } diff --git a/src/Queue.cxx b/src/Queue.cxx new file mode 100644 index 000000000..485ffb1cc --- /dev/null +++ b/src/Queue.cxx @@ -0,0 +1,604 @@ +/* + * Copyright (C) 2003-2013 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 "Queue.hxx" +#include "song.h" + +#include + +/** + * Generate a non-existing id number. + */ +static unsigned +queue_generate_id(const struct queue *queue) +{ + static unsigned cur = (unsigned)-1; + + do { + cur++; + + if (cur >= queue->max_length * QUEUE_HASH_MULT) + cur = 0; + } while (queue->id_to_position[cur] != -1); + + return cur; +} + +int +queue_next_order(const struct queue *queue, unsigned order) +{ + assert(order < queue->length); + + if (queue->single && queue->repeat && !queue->consume) + return order; + else if (order + 1 < queue->length) + return order + 1; + else if (queue->repeat && (order > 0 || !queue->consume)) + /* restart at first song */ + return 0; + else + /* end of queue */ + return -1; +} + +void +queue_increment_version(struct queue *queue) +{ + static unsigned long max = ((uint32_t) 1 << 31) - 1; + + queue->version++; + + if (queue->version >= max) { + for (unsigned i = 0; i < queue->length; i++) + queue->items[i].version = 0; + + queue->version = 1; + } +} + +void +queue_modify(struct queue *queue, unsigned order) +{ + unsigned position; + + assert(order < queue->length); + + position = queue->order[order]; + queue->items[position].version = queue->version; + + queue_increment_version(queue); +} + +void +queue_modify_all(struct queue *queue) +{ + for (unsigned i = 0; i < queue->length; i++) + queue->items[i].version = queue->version; + + queue_increment_version(queue); +} + +unsigned +queue_append(struct queue *queue, struct song *song, uint8_t priority) +{ + unsigned id = queue_generate_id(queue); + + assert(!queue_is_full(queue)); + + auto &item = queue->items[queue->length]; + item.song = song_dup_detached(song); + item.id = id; + item.version = queue->version; + item.priority = priority; + + queue->order[queue->length] = queue->length; + queue->id_to_position[id] = queue->length; + + ++queue->length; + + return id; +} + +void +queue_swap(struct queue *queue, unsigned position1, unsigned position2) +{ + struct queue_item tmp; + unsigned id1 = queue->items[position1].id; + unsigned id2 = queue->items[position2].id; + + tmp = queue->items[position1]; + queue->items[position1] = queue->items[position2]; + queue->items[position2] = tmp; + + queue->items[position1].version = queue->version; + queue->items[position2].version = queue->version; + + queue->id_to_position[id1] = position2; + queue->id_to_position[id2] = position1; +} + +static void +queue_move_song_to(struct queue *queue, unsigned from, unsigned to) +{ + unsigned from_id = queue->items[from].id; + + queue->items[to] = queue->items[from]; + queue->items[to].version = queue->version; + queue->id_to_position[from_id] = to; +} + +void +queue_move(struct queue *queue, unsigned from, unsigned to) +{ + struct queue_item item = queue->items[from]; + + /* move songs to one less in from->to */ + + for (unsigned i = from; i < to; i++) + queue_move_song_to(queue, i + 1, i); + + /* move songs to one more in to->from */ + + for (unsigned i = from; i > to; i--) + queue_move_song_to(queue, i - 1, i); + + /* put song at _to_ */ + + queue->id_to_position[item.id] = to; + queue->items[to] = item; + queue->items[to].version = queue->version; + + /* now deal with order */ + + if (queue->random) { + for (unsigned i = 0; i < queue->length; i++) { + if (queue->order[i] > from && queue->order[i] <= to) + queue->order[i]--; + else if (queue->order[i] < from && + queue->order[i] >= to) + queue->order[i]++; + else if (from == queue->order[i]) + queue->order[i] = to; + } + } +} + +void +queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) +{ + struct queue_item items[end - start]; + // Copy the original block [start,end-1] + for (unsigned i = start; i < end; i++) + items[i - start] = queue->items[i]; + + // If to > start, we need to move to-start items to start, starting from end + for (unsigned i = end; i < end + to - start; i++) + queue_move_song_to(queue, i, start + i - end); + + // If to < start, we need to move start-to items to newend (= end + to - start), starting from to + // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 + // We have to iterate in this order to avoid writing over something we haven't yet moved + for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--) + queue_move_song_to(queue, i, i + end - start); + + // Copy the original block back in, starting at to. + for (unsigned i = start; i< end; i++) + { + queue->id_to_position[items[i-start].id] = to + i - start; + queue->items[to + i - start] = items[i-start]; + queue->items[to + i - start].version = queue->version; + } + + if (queue->random) { + // Update the positions in the queue. + // Note that the ranges for these cases are the same as the ranges of + // the loops above. + for (unsigned i = 0; i < queue->length; i++) { + if (queue->order[i] >= end && queue->order[i] < to + end - start) + queue->order[i] -= end - start; + else if (queue->order[i] < start && + queue->order[i] >= to) + queue->order[i] += end - start; + else if (start <= queue->order[i] && queue->order[i] < end) + queue->order[i] += to - start; + } + } +} + +/** + * Moves a song to a new position in the "order" list. + */ +static void +queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) +{ + assert(queue != NULL); + assert(from_order < queue->length); + assert(to_order <= queue->length); + + const unsigned from_position = + queue_order_to_position(queue, from_order); + + if (from_order < to_order) { + for (unsigned i = from_order; i < to_order; ++i) + queue->order[i] = queue->order[i + 1]; + } else { + for (unsigned i = from_order; i > to_order; --i) + queue->order[i] = queue->order[i - 1]; + } + + queue->order[to_order] = from_position; +} + +void +queue_delete(struct queue *queue, unsigned position) +{ + struct song *song; + unsigned id, order; + + assert(position < queue->length); + + song = queue_get(queue, position); + assert(!song_in_database(song) || song_is_detached(song)); + song_free(song); + + id = queue_position_to_id(queue, position); + order = queue_position_to_order(queue, position); + + --queue->length; + + /* release the song id */ + + queue->id_to_position[id] = -1; + + /* delete song from songs array */ + + for (unsigned i = position; i < queue->length; i++) + queue_move_song_to(queue, i + 1, i); + + /* delete the entry from the order array */ + + for (unsigned i = order; i < queue->length; i++) + queue->order[i] = queue->order[i + 1]; + + /* readjust values in the order array */ + + for (unsigned i = 0; i < queue->length; i++) + if (queue->order[i] > position) + --queue->order[i]; +} + +void +queue_clear(struct queue *queue) +{ + for (unsigned i = 0; i < queue->length; i++) { + struct queue_item *item = &queue->items[i]; + + assert(!song_in_database(item->song) || + song_is_detached(item->song)); + song_free(item->song); + + queue->id_to_position[item->id] = -1; + } + + queue->length = 0; +} + +void +queue_init(struct queue *queue, unsigned max_length) +{ + queue->max_length = max_length; + queue->length = 0; + queue->version = 1; + queue->repeat = false; + queue->random = false; + queue->single = false; + queue->consume = false; + + queue->items = g_new(struct queue_item, max_length); + queue->order = (unsigned *) + g_malloc(sizeof(queue->order[0]) * max_length); + queue->id_to_position = (int *) + g_malloc(sizeof(queue->id_to_position[0]) * + max_length * QUEUE_HASH_MULT); + + for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) + queue->id_to_position[i] = -1; + + queue->rand = g_rand_new(); +} + +void +queue_finish(struct queue *queue) +{ + queue_clear(queue); + + g_free(queue->items); + g_free(queue->order); + g_free(queue->id_to_position); + + g_rand_free(queue->rand); +} + +static const struct queue_item * +queue_get_order_item_const(const struct queue *queue, unsigned order) +{ + assert(queue != NULL); + assert(order < queue->length); + + return &queue->items[queue->order[order]]; +} + +static uint8_t +queue_get_order_priority(const struct queue *queue, unsigned order) +{ + return queue_get_order_item_const(queue, order)->priority; +} + +static gint +queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, + gpointer user_data) +{ + const struct queue *queue = (const struct queue *)user_data; + const unsigned *const ap = (const unsigned *)av; + const unsigned *const bp = (const unsigned *)bv; + assert(ap >= queue->order && ap < queue->order + queue->length); + assert(bp >= queue->order && bp < queue->order + queue->length); + uint8_t a = queue->items[*ap].priority; + uint8_t b = queue->items[*bp].priority; + + if (G_LIKELY(a == b)) + return 0; + else if (a > b) + return -1; + else + return 1; +} + +static void +queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + g_qsort_with_data(&queue->order[start], end - start, + sizeof(queue->order[0]), + queue_item_compare_order_priority, + queue); +} + +/** + * Shuffle the order of items in the specified range, ignoring their + * priorities. + */ +static void +queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + for (unsigned i = start; i < end; ++i) + queue_swap_order(queue, i, + g_rand_int_range(queue->rand, i, end)); +} + +/** + * Sort the "order" of items by priority, and then shuffle each + * priority group. + */ +void +queue_shuffle_order_range_with_priority(struct queue *queue, + unsigned start, unsigned end) +{ + assert(queue != NULL); + assert(queue->random); + assert(start <= end); + assert(end <= queue->length); + + if (start == end) + return; + + /* first group the range by priority */ + queue_sort_order_by_priority(queue, start, end); + + /* now shuffle each priority group */ + unsigned group_start = start; + uint8_t group_priority = queue_get_order_priority(queue, start); + + for (unsigned i = start + 1; i < end; ++i) { + uint8_t priority = queue_get_order_priority(queue, i); + assert(priority <= group_priority); + + if (priority != group_priority) { + /* start of a new group - shuffle the one that + has just ended */ + queue_shuffle_order_range(queue, group_start, i); + group_start = i; + group_priority = priority; + } + } + + /* shuffle the last group */ + queue_shuffle_order_range(queue, group_start, end); +} + +void +queue_shuffle_order(struct queue *queue) +{ + queue_shuffle_order_range_with_priority(queue, 0, queue->length); +} + +static void +queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) +{ + queue_swap_order(queue, start, + g_rand_int_range(queue->rand, start, end)); +} + +void +queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end) +{ + queue_swap_order(queue, end - 1, + g_rand_int_range(queue->rand, start, end)); +} + +void +queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) +{ + assert(start <= end); + assert(end <= queue->length); + + for (unsigned i = start; i < end; i++) { + unsigned ri = g_rand_int_range(queue->rand, i, end); + queue_swap(queue, i, ri); + } +} + +/** + * Find the first item that has this specified priority or higher. + */ +G_GNUC_PURE +static unsigned +queue_find_priority_order(const struct queue *queue, unsigned start_order, + uint8_t priority, unsigned exclude_order) +{ + assert(queue != NULL); + assert(queue->random); + assert(start_order <= queue->length); + + for (unsigned order = start_order; order < queue->length; ++order) { + const unsigned position = queue_order_to_position(queue, order); + const struct queue_item *item = &queue->items[position]; + if (item->priority <= priority && order != exclude_order) + return order; + } + + return queue->length; +} + +G_GNUC_PURE +static unsigned +queue_count_same_priority(const struct queue *queue, unsigned start_order, + uint8_t priority) +{ + assert(queue != NULL); + assert(queue->random); + assert(start_order <= queue->length); + + for (unsigned order = start_order; order < queue->length; ++order) { + const unsigned position = queue_order_to_position(queue, order); + const struct queue_item *item = &queue->items[position]; + if (item->priority != priority) + return order - start_order; + } + + return queue->length - start_order; +} + +bool +queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, + int after_order) +{ + assert(queue != NULL); + assert(position < queue->length); + + struct queue_item *item = &queue->items[position]; + uint8_t old_priority = item->priority; + if (old_priority == priority) + return false; + + item->version = queue->version; + item->priority = priority; + + if (!queue->random) + /* don't reorder if not in random mode */ + return true; + + unsigned order = queue_position_to_order(queue, position); + if (after_order >= 0) { + if (order == (unsigned)after_order) + /* don't reorder the current song */ + return true; + + if (order < (unsigned)after_order) { + /* the specified song has been played already + - enqueue it only if its priority has just + become bigger than the current one's */ + + const unsigned after_position = + queue_order_to_position(queue, after_order); + const struct queue_item *after_item = + &queue->items[after_position]; + if (old_priority > after_item->priority || + priority <= after_item->priority) + /* priority hasn't become bigger */ + return true; + } + } + + /* move the item to the beginning of the priority group (or + create a new priority group) */ + + const unsigned before_order = + queue_find_priority_order(queue, after_order + 1, priority, + order); + const unsigned new_order = before_order > order + ? before_order - 1 + : before_order; + queue_move_order(queue, order, new_order); + + /* shuffle the song within that priority group */ + + const unsigned priority_count = + queue_count_same_priority(queue, new_order, priority); + assert(priority_count >= 1); + queue_shuffle_order_first(queue, new_order, + new_order + priority_count); + + return true; +} + +bool +queue_set_priority_range(struct queue *queue, + unsigned start_position, unsigned end_position, + uint8_t priority, int after_order) +{ + assert(queue != NULL); + assert(start_position <= end_position); + assert(end_position <= queue->length); + + bool modified = false; + int after_position = after_order >= 0 + ? (int)queue_order_to_position(queue, after_order) + : -1; + for (unsigned i = start_position; i < end_position; ++i) { + after_order = after_position >= 0 + ? (int)queue_position_to_order(queue, after_position) + : -1; + + modified |= queue_set_priority(queue, i, priority, + after_order); + } + + return modified; +} diff --git a/src/Queue.hxx b/src/Queue.hxx new file mode 100644 index 000000000..4c6d5ea11 --- /dev/null +++ b/src/Queue.hxx @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2003-2013 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_QUEUE_HXX +#define MPD_QUEUE_HXX + +#include + +#include +#include +#include + +enum { + /** + * reserve max_length * QUEUE_HASH_MULT elements in the id + * number space + */ + QUEUE_HASH_MULT = 4, +}; + +/** + * One element of the queue: basically a song plus some queue specific + * information attached. + */ +struct queue_item { + struct song *song; + + /** the unique id of this item in the queue */ + unsigned id; + + /** when was this item last changed? */ + uint32_t version; + + /** + * The priority of this item, between 0 and 255. High + * priority value means that this song gets played first in + * "random" mode. + */ + uint8_t priority; +}; + +/** + * A queue of songs. This is the backend of the playlist: it contains + * an ordered list of songs. + * + * Songs can be addressed in three possible ways: + * + * - the position in the queue + * - the unique id (which stays the same, regardless of moves) + * - the order number (which only differs from "position" in random mode) + */ +struct queue { + /** configured maximum length of the queue */ + unsigned max_length; + + /** number of songs in the queue */ + unsigned length; + + /** the current version number */ + uint32_t version; + + /** all songs in "position" order */ + struct queue_item *items; + + /** map order numbers to positions */ + unsigned *order; + + /** map song ids to positions */ + int *id_to_position; + + /** repeat playback when the end of the queue has been + reached? */ + bool repeat; + + /** play only current song. */ + bool single; + + /** remove each played files. */ + bool consume; + + /** play back songs in random order? */ + bool random; + + /** random number generator for shuffle and random mode */ + GRand *rand; +}; + +static inline unsigned +queue_length(const struct queue *queue) +{ + assert(queue->length <= queue->max_length); + + return queue->length; +} + +/** + * Determine if the queue is empty, i.e. there are no songs. + */ +static inline bool +queue_is_empty(const struct queue *queue) +{ + return queue->length == 0; +} + +/** + * Determine if the maximum number of songs has been reached. + */ +static inline bool +queue_is_full(const struct queue *queue) +{ + assert(queue->length <= queue->max_length); + + return queue->length >= queue->max_length; +} + +/** + * Is that a valid position number? + */ +static inline bool +queue_valid_position(const struct queue *queue, unsigned position) +{ + return position < queue->length; +} + +/** + * Is that a valid order number? + */ +static inline bool +queue_valid_order(const struct queue *queue, unsigned order) +{ + return order < queue->length; +} + +static inline int +queue_id_to_position(const struct queue *queue, unsigned id) +{ + if (id >= queue->max_length * QUEUE_HASH_MULT) + return -1; + + assert(queue->id_to_position[id] >= -1); + assert(queue->id_to_position[id] < (int)queue->length); + + return queue->id_to_position[id]; +} + +static inline int +queue_position_to_id(const struct queue *queue, unsigned position) +{ + assert(position < queue->length); + + return queue->items[position].id; +} + +static inline unsigned +queue_order_to_position(const struct queue *queue, unsigned order) +{ + assert(order < queue->length); + + return queue->order[order]; +} + +static inline unsigned +queue_position_to_order(const struct queue *queue, unsigned position) +{ + assert(position < queue->length); + + for (unsigned i = 0;; ++i) { + assert(i < queue->length); + + if (queue->order[i] == position) + return i; + } +} + +G_GNUC_PURE +static inline uint8_t +queue_get_priority_at_position(const struct queue *queue, unsigned position) +{ + assert(position < queue->length); + + return queue->items[position].priority; +} + +/** + * Returns the song at the specified position. + */ +static inline struct song * +queue_get(const struct queue *queue, unsigned position) +{ + assert(position < queue->length); + + return queue->items[position].song; +} + +/** + * Returns the song at the specified order number. + */ +static inline struct song * +queue_get_order(const struct queue *queue, unsigned order) +{ + return queue_get(queue, queue_order_to_position(queue, order)); +} + +/** + * Is the song at the specified position newer than the specified + * version? + */ +static inline bool +queue_song_newer(const struct queue *queue, unsigned position, + uint32_t version) +{ + assert(position < queue->length); + + return version > queue->version || + queue->items[position].version >= version || + queue->items[position].version == 0; +} + +/** + * Initialize a queue object. + */ +void +queue_init(struct queue *queue, unsigned max_length); + +/** + * Deinitializes a queue object. It does not free the queue pointer + * itself. + */ +void +queue_finish(struct queue *queue); + +/** + * Returns the order number following the specified one. This takes + * end of queue and "repeat" mode into account. + * + * @return the next order number, or -1 to stop playback + */ +int +queue_next_order(const struct queue *queue, unsigned order); + +/** + * Increments the queue's version number. This handles integer + * overflow well. + */ +void +queue_increment_version(struct queue *queue); + +/** + * Marks the specified song as "modified" and increments the version + * number. + */ +void +queue_modify(struct queue *queue, unsigned order); + +/** + * Marks all songs as "modified" and increments the version number. + */ +void +queue_modify_all(struct queue *queue); + +/** + * Appends a song to the queue and returns its position. Prior to + * that, the caller must check if the queue is already full. + * + * If a song is not in the database (determined by + * song_in_database()), it is freed when removed from the queue. + * + * @param priority the priority of this new queue item + */ +unsigned +queue_append(struct queue *queue, struct song *song, uint8_t priority); + +/** + * Swaps two songs, addressed by their position. + */ +void +queue_swap(struct queue *queue, unsigned position1, unsigned position2); + +/** + * Swaps two songs, addressed by their order number. + */ +static inline void +queue_swap_order(struct queue *queue, unsigned order1, unsigned order2) +{ + unsigned tmp = queue->order[order1]; + queue->order[order1] = queue->order[order2]; + queue->order[order2] = tmp; +} + +/** + * Moves a song to a new position. + */ +void +queue_move(struct queue *queue, unsigned from, unsigned to); + +/** + * Moves a range of songs to a new position. + */ +void +queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to); + +/** + * Removes a song from the playlist. + */ +void +queue_delete(struct queue *queue, unsigned position); + +/** + * Removes all songs from the playlist. + */ +void +queue_clear(struct queue *queue); + +/** + * Initializes the "order" array, and restores "normal" order. + */ +static inline void +queue_restore_order(struct queue *queue) +{ + for (unsigned i = 0; i < queue->length; ++i) + queue->order[i] = i; +} + +/** + * Shuffle the order of items in the specified range, taking their + * priorities into account. + */ +void +queue_shuffle_order_range_with_priority(struct queue *queue, + unsigned start, unsigned end); + +/** + * Shuffles the virtual order of songs, but does not move them + * physically. This is used in random mode. + */ +void +queue_shuffle_order(struct queue *queue); + +/** + * Shuffles the virtual order of the last song in the specified + * (order) range. This is used in random mode after a song has been + * appended by queue_append(). + */ +void +queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); + +/** + * Shuffles a (position) range in the queue. The songs are physically + * shuffled, not by using the "order" mapping. + */ +void +queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); + +bool +queue_set_priority(struct queue *queue, unsigned position, + uint8_t priority, int after_order); + +bool +queue_set_priority_range(struct queue *queue, + unsigned start_position, unsigned end_position, + uint8_t priority, int after_order); + +#endif diff --git a/src/QueueCommands.cxx b/src/QueueCommands.cxx index 8a13075a5..af3ad60c8 100644 --- a/src/QueueCommands.cxx +++ b/src/QueueCommands.cxx @@ -22,6 +22,7 @@ #include "CommandError.hxx" #include "DatabaseQueue.hxx" #include "SongFilter.hxx" +#include "Playlist.hxx" #include "PlaylistPrint.hxx" #include "ClientFile.hxx" #include "ClientInternal.hxx" @@ -30,7 +31,6 @@ #include "ls.hxx" extern "C" { -#include "playlist.h" #include "uri.h" } diff --git a/src/QueuePrint.cxx b/src/QueuePrint.cxx index bd49f92ec..289ae26b8 100644 --- a/src/QueuePrint.cxx +++ b/src/QueuePrint.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2012 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,13 +19,13 @@ #include "config.h" #include "QueuePrint.hxx" +#include "Queue.hxx" #include "SongFilter.hxx" #include "SongPrint.hxx" #include "Mapper.hxx" #include "Client.hxx" extern "C" { -#include "queue.h" #include "song.h" } diff --git a/src/QueueSave.cxx b/src/QueueSave.cxx index 6ba48f336..a667bc855 100644 --- a/src/QueueSave.cxx +++ b/src/QueueSave.cxx @@ -19,6 +19,7 @@ #include "config.h" #include "QueueSave.hxx" +#include "Playlist.hxx" #include "song.h" #include "SongSave.hxx" #include "DatabasePlugin.hxx" @@ -26,7 +27,6 @@ #include "TextFile.hxx" extern "C" { -#include "queue.h" #include "uri.h" } diff --git a/src/StateFile.cxx b/src/StateFile.cxx index cd344eb0e..db6635025 100644 --- a/src/StateFile.cxx +++ b/src/StateFile.cxx @@ -20,7 +20,7 @@ #include "config.h" #include "StateFile.hxx" #include "OutputState.hxx" -#include "playlist.h" +#include "Playlist.hxx" #include "PlaylistState.hxx" #include "TextFile.hxx" diff --git a/src/UpdateGlue.cxx b/src/UpdateGlue.cxx index f5664893b..cb1600d4f 100644 --- a/src/UpdateGlue.cxx +++ b/src/UpdateGlue.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,9 +24,9 @@ #include "UpdateRemove.hxx" #include "Mapper.hxx" #include "DatabaseSimple.hxx" +#include "Playlist.hxx" extern "C" { -#include "playlist.h" #include "event_pipe.h" #include "idle.h" #include "stats.h" diff --git a/src/UpdateRemove.cxx b/src/UpdateRemove.cxx index c88eec42a..814c803b2 100644 --- a/src/UpdateRemove.cxx +++ b/src/UpdateRemove.cxx @@ -19,10 +19,10 @@ #include "config.h" /* must be first for large file support */ #include "UpdateRemove.hxx" +#include "Playlist.hxx" extern "C" { #include "event_pipe.h" -#include "playlist.h" } #include "song.h" diff --git a/src/playlist.c b/src/playlist.c deleted file mode 100644 index 2532d9d46..000000000 --- a/src/playlist.c +++ /dev/null @@ -1,449 +0,0 @@ -/* - * Copyright (C) 2003-2011 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_internal.h" -#include "player_control.h" -#include "song.h" -#include "conf.h" -#include "idle.h" - -#include - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_increment_version_all(struct playlist *playlist) -{ - queue_modify_all(&playlist->queue); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_tag_changed(struct playlist *playlist) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - queue_modify(&playlist->queue, playlist->current); - idle_add(IDLE_PLAYLIST); -} - -void -playlist_init(struct playlist *playlist) -{ - queue_init(&playlist->queue, - config_get_positive(CONF_MAX_PLAYLIST_LENGTH, - DEFAULT_PLAYLIST_MAX_LENGTH)); - - playlist->queued = -1; - playlist->current = -1; -} - -void -playlist_finish(struct playlist *playlist) -{ - queue_finish(&playlist->queue); -} - -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(struct playlist *playlist, struct player_control *pc, - unsigned order) -{ - char *uri; - - assert(queue_valid_order(&playlist->queue, order)); - - playlist->queued = order; - - struct song *song = - song_dup_detached(queue_get_order(&playlist->queue, order)); - - uri = song_get_uri(song); - g_debug("queue song %i:\"%s\"", playlist->queued, uri); - g_free(uri); - - pc_enqueue_song(pc, song); -} - -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(struct playlist *playlist, struct player_control *pc) -{ - assert(pc->next_song == NULL); - 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_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); - - idle_add(IDLE_PLAYER); -} - -const struct song * -playlist_get_queued_song(struct playlist *playlist) -{ - if (!playlist->playing || playlist->queued < 0) - return NULL; - - return queue_get_order(&playlist->queue, playlist->queued); -} - -void -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev) -{ - int next_order; - const struct song *next_song; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert((playlist->queued < 0) == (prev == NULL)); - - next_order = playlist->current >= 0 - ? queue_next_order(&playlist->queue, playlist->current) - : 0; - - if (next_order == 0 && playlist->queue.random && - !playlist->queue.single) { - /* shuffle the song order again, so we get a different - order each time the playlist is played - completely */ - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that the playlist->current still points to - the current song, after the song order has been - shuffled */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } - - if (next_order >= 0) - next_song = queue_get_order(&playlist->queue, next_order); - else - next_song = NULL; - - if (prev != NULL && next_song != prev) { - /* clear the currently queued song */ - pc_cancel(pc); - playlist->queued = -1; - } - - if (next_order >= 0) { - if (next_song != prev) - playlist_queue_song_order(playlist, pc, next_order); - else - playlist->queued = next_order; - } -} - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum) -{ - char *uri; - - playlist->playing = true; - playlist->queued = -1; - - struct song *song = - song_dup_detached(queue_get_order(&playlist->queue, orderNum)); - - uri = song_get_uri(song); - g_debug("play %i:\"%s\"", orderNum, uri); - g_free(uri); - - pc_play(pc, song); - playlist->current = orderNum; -} - -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc); - -/** - * 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 -playlist_sync(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - /* this event has reached us out of sync: we aren't - playing anymore; ignore the event */ - return; - - player_lock(pc); - enum player_state pc_state = pc_get_state(pc); - const struct song *pc_next_song = pc->next_song; - player_unlock(pc); - - if (pc_state == PLAYER_STATE_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(playlist, pc); - else { - /* check if the player thread has already started - playing the queued song */ - if (pc_next_song == NULL && playlist->queued != -1) - playlist_song_started(playlist, pc); - - player_lock(pc); - pc_next_song = pc->next_song; - player_unlock(pc); - - /* make sure the queued song is always set (if - possible) */ - if (pc_next_song == NULL && playlist->queued < 0) - playlist_update_queued_song(playlist, pc, NULL); - } -} - -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(struct playlist *playlist, struct player_control *pc) -{ - enum player_error error; - - assert(playlist->playing); - assert(pc_get_state(pc) == PLAYER_STATE_STOP); - - error = pc_get_error_type(pc); - if (error == PLAYER_ERROR_NONE) - playlist->error_count = 0; - else - ++playlist->error_count; - - if ((playlist->stop_on_error && error != PLAYER_ERROR_NONE) || - error == PLAYER_ERROR_OUTPUT || - playlist->error_count >= queue_length(&playlist->queue)) - /* too many errors, or critical error: stop - playback */ - playlist_stop(playlist, pc); - else - /* continue playback at the next song */ - playlist_next(playlist, pc); -} - -bool -playlist_get_repeat(const struct playlist *playlist) -{ - return playlist->queue.repeat; -} - -bool -playlist_get_random(const struct playlist *playlist) -{ - return playlist->queue.random; -} - -bool -playlist_get_single(const struct playlist *playlist) -{ - return playlist->queue.single; -} - -bool -playlist_get_consume(const struct playlist *playlist) -{ - return playlist->queue.consume; -} - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.repeat) - return; - - struct queue *queue = &playlist->queue; - - queue->repeat = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when repeat mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -static void -playlist_order(struct playlist *playlist) -{ - if (playlist->current >= 0) - /* update playlist.current, order==position now */ - playlist->current = queue_order_to_position(&playlist->queue, - playlist->current); - - queue_restore_order(&playlist->queue); -} - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status) -{ - if (status == playlist->queue.single) - return; - - struct queue *queue = &playlist->queue; - - queue->single = status; - - pc_set_border_pause(pc, queue->single && !queue->repeat); - - /* if the last song is currently being played, the "next song" - might change when single mode is toggled */ - playlist_update_queued_song(playlist, pc, - playlist_get_queued_song(playlist)); - - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_consume(struct playlist *playlist, bool status) -{ - if (status == playlist->queue.consume) - return; - - playlist->queue.consume = status; - idle_add(IDLE_OPTIONS); -} - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status) -{ - const struct song *queued; - - if (status == playlist->queue.random) - return; - - queued = playlist_get_queued_song(playlist); - - playlist->queue.random = status; - - if (playlist->queue.random) { - /* shuffle the queue order, but preserve - playlist->current */ - - int current_position = - playlist->playing && playlist->current >= 0 - ? (int)queue_order_to_position(&playlist->queue, - playlist->current) - : -1; - - queue_shuffle_order(&playlist->queue); - - 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_position_to_order(&playlist->queue, - current_position); - queue_swap_order(&playlist->queue, 0, current_order); - playlist->current = 0; - } else - playlist->current = -1; - } else - playlist_order(playlist); - - playlist_update_queued_song(playlist, pc, queued); - - idle_add(IDLE_OPTIONS); -} - -int -playlist_get_current_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - return queue_order_to_position(&playlist->queue, - playlist->current); - - return -1; -} - -int -playlist_get_next_song(const struct playlist *playlist) -{ - if (playlist->current >= 0) - { - if (playlist->queue.single == 1 && playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, - playlist->current); - else if (playlist->current + 1 < (int)queue_length(&playlist->queue)) - return queue_order_to_position(&playlist->queue, - playlist->current + 1); - else if (playlist->queue.repeat == 1) - return queue_order_to_position(&playlist->queue, 0); - } - - return -1; -} - -unsigned long -playlist_get_version(const struct playlist *playlist) -{ - return playlist->queue.version; -} - -int -playlist_get_length(const struct playlist *playlist) -{ - return queue_length(&playlist->queue); -} - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song) -{ - return queue_position_to_id(&playlist->queue, song); -} diff --git a/src/playlist.h b/src/playlist.h deleted file mode 100644 index a21bdf24a..000000000 --- a/src/playlist.h +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (C) 2003-2011 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_H -#define MPD_PLAYLIST_H - -#include "queue.h" -#include "playlist_error.h" - -#include - -struct player_control; - -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; -}; - -/** the global playlist object */ -extern struct playlist g_playlist; - -void -playlist_global_init(void); - -void -playlist_global_finish(void); - -void -playlist_init(struct playlist *playlist); - -void -playlist_finish(struct playlist *playlist); - -void -playlist_tag_changed(struct playlist *playlist); - -/** - * Returns the "queue" object of the global playlist instance. - */ -static inline const struct queue * -playlist_get_queue(const struct playlist *playlist) -{ - return &playlist->queue; -} - -void -playlist_clear(struct playlist *playlist, struct player_control *pc); - -/** - * Appends a local file (outside the music database) to the playlist. - * - * Note: the caller is responsible for checking permissions. - */ -enum playlist_result -playlist_append_file(struct playlist *playlist, struct player_control *pc, - const char *path_fs, unsigned *added_id); - -enum playlist_result -playlist_append_uri(struct playlist *playlist, struct player_control *pc, - const char *file, unsigned *added_id); - -enum playlist_result -playlist_append_song(struct playlist *playlist, struct player_control *pc, - struct song *song, unsigned *added_id); - -enum playlist_result -playlist_delete(struct playlist *playlist, struct player_control *pc, - unsigned song); - -/** - * 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 - */ -enum playlist_result -playlist_delete_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -enum playlist_result -playlist_delete_id(struct playlist *playlist, struct player_control *pc, - unsigned song); - -void -playlist_stop(struct playlist *playlist, struct player_control *pc); - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song); - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int song); - -void -playlist_next(struct playlist *playlist, struct player_control *pc); - -void -playlist_sync(struct playlist *playlist, struct player_control *pc); - -void -playlist_previous(struct playlist *playlist, struct player_control *pc); - -void -playlist_shuffle(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end); - -void -playlist_delete_song(struct playlist *playlist, struct player_control *pc, - const struct song *song); - -enum playlist_result -playlist_move_range(struct playlist *playlist, struct player_control *pc, - unsigned start, unsigned end, int to); - -enum playlist_result -playlist_move_id(struct playlist *playlist, struct player_control *pc, - unsigned id, int to); - -enum playlist_result -playlist_swap_songs(struct playlist *playlist, struct player_control *pc, - unsigned song1, unsigned song2); - -enum playlist_result -playlist_swap_songs_id(struct playlist *playlist, struct player_control *pc, - unsigned id1, unsigned id2); - -enum playlist_result -playlist_set_priority(struct playlist *playlist, struct player_control *pc, - unsigned start_position, unsigned end_position, - uint8_t priority); - -enum playlist_result -playlist_set_priority_id(struct playlist *playlist, struct player_control *pc, - unsigned song_id, uint8_t priority); - -bool -playlist_get_repeat(const struct playlist *playlist); - -void -playlist_set_repeat(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_random(const struct playlist *playlist); - -void -playlist_set_random(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_single(const struct playlist *playlist); - -void -playlist_set_single(struct playlist *playlist, struct player_control *pc, - bool status); - -bool -playlist_get_consume(const struct playlist *playlist); - -void -playlist_set_consume(struct playlist *playlist, bool status); - -int -playlist_get_current_song(const struct playlist *playlist); - -int -playlist_get_next_song(const struct playlist *playlist); - -unsigned -playlist_get_song_id(const struct playlist *playlist, unsigned song); - -int -playlist_get_length(const struct playlist *playlist); - -unsigned long -playlist_get_version(const struct playlist *playlist); - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time); - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned 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 - */ -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative); - -void -playlist_increment_version_all(struct playlist *playlist); - -#endif diff --git a/src/playlist_control.c b/src/playlist_control.c deleted file mode 100644 index 57cc428fe..000000000 --- a/src/playlist_control.c +++ /dev/null @@ -1,291 +0,0 @@ -/* - * Copyright (C) 2003-2011 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_internal.h" -#include "player_control.h" -#include "song.h" -#include "idle.h" - -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "playlist" - -void -playlist_stop(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(playlist->current >= 0); - - g_debug("stop"); - pc_stop(pc); - playlist->queued = -1; - playlist->playing = false; - - if (playlist->queue.random) { - /* shuffle the playlist, so the next playback will - result in a new random order */ - - unsigned current_position = - queue_order_to_position(&playlist->queue, - playlist->current); - - queue_shuffle_order(&playlist->queue); - - /* make sure that "current" stays valid, and the next - "play" command plays the same song again */ - playlist->current = - queue_position_to_order(&playlist->queue, - current_position); - } -} - -enum playlist_result -playlist_play(struct playlist *playlist, struct player_control *pc, - int song) -{ - unsigned i = song; - - pc_clear_error(pc); - - if (song == -1) { - /* play any song ("current" song, or the first song */ - - if (queue_is_empty(&playlist->queue)) - return PLAYLIST_RESULT_SUCCESS; - - if (playlist->playing) { - /* already playing: unpause playback, just in - case it was paused, and return */ - pc_set_pause(pc, false); - return PLAYLIST_RESULT_SUCCESS; - } - - /* select a song: "current" song, or the first one */ - i = playlist->current >= 0 - ? playlist->current - : 0; - } else if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - if (playlist->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_position_to_order(&playlist->queue, song); - - if (!playlist->playing) - playlist->current = 0; - - /* swap the new song with the previous "current" one, - so playback continues as planned */ - queue_swap_order(&playlist->queue, - i, playlist->current); - i = playlist->current; - } - - playlist->stop_on_error = false; - playlist->error_count = 0; - - playlist_play_order(playlist, pc, i); - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_play_id(struct playlist *playlist, struct player_control *pc, - int id) -{ - int song; - - if (id == -1) { - return playlist_play(playlist, pc, id); - } - - song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_play(playlist, pc, song); -} - -void -playlist_next(struct playlist *playlist, struct player_control *pc) -{ - int next_order; - int current; - - if (!playlist->playing) - return; - - assert(!queue_is_empty(&playlist->queue)); - assert(queue_valid_order(&playlist->queue, playlist->current)); - - current = playlist->current; - playlist->stop_on_error = false; - - /* determine the next song from the queue's order list */ - - next_order = queue_next_order(&playlist->queue, playlist->current); - if (next_order < 0) { - /* no song after this one: stop playback */ - playlist_stop(playlist, pc); - - /* reset "current song" */ - playlist->current = -1; - } - else - { - if (next_order == 0 && playlist->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(playlist->queue.repeat); - - queue_shuffle_order(&playlist->queue); - - /* note that playlist->current and playlist->queued are - now invalid, but playlist_play_order() will - discard them anyway */ - } - - playlist_play_order(playlist, pc, next_order); - } - - /* Consume mode removes each played songs. */ - if(playlist->queue.consume) - playlist_delete(playlist, pc, - queue_order_to_position(&playlist->queue, - current)); -} - -void -playlist_previous(struct playlist *playlist, struct player_control *pc) -{ - if (!playlist->playing) - return; - - assert(queue_length(&playlist->queue) > 0); - - if (playlist->current > 0) { - /* play the preceding song */ - playlist_play_order(playlist, pc, - playlist->current - 1); - } else if (playlist->queue.repeat) { - /* play the last song in "repeat" mode */ - playlist_play_order(playlist, pc, - queue_length(&playlist->queue) - 1); - } else { - /* re-start playing the current song if it's - the first one */ - playlist_play_order(playlist, pc, playlist->current); - } -} - -enum playlist_result -playlist_seek_song(struct playlist *playlist, struct player_control *pc, - unsigned song, float seek_time) -{ - const struct song *queued; - unsigned i; - bool success; - - if (!queue_valid_position(&playlist->queue, song)) - return PLAYLIST_RESULT_BAD_RANGE; - - queued = playlist_get_queued_song(playlist); - - if (playlist->queue.random) - i = queue_position_to_order(&playlist->queue, song); - else - i = song; - - pc_clear_error(pc); - playlist->stop_on_error = true; - playlist->error_count = 0; - - if (!playlist->playing || (unsigned)playlist->current != i) { - /* seeking is not within the current song - prepare - song change */ - - playlist->playing = true; - playlist->current = i; - - queued = NULL; - } - - struct song *the_song = - song_dup_detached(queue_get_order(&playlist->queue, i)); - success = pc_seek(pc, the_song, seek_time); - if (!success) { - playlist_update_queued_song(playlist, pc, queued); - - return PLAYLIST_RESULT_NOT_PLAYING; - } - - playlist->queued = -1; - playlist_update_queued_song(playlist, pc, NULL); - - return PLAYLIST_RESULT_SUCCESS; -} - -enum playlist_result -playlist_seek_song_id(struct playlist *playlist, struct player_control *pc, - unsigned id, float seek_time) -{ - int song = queue_id_to_position(&playlist->queue, id); - if (song < 0) - return PLAYLIST_RESULT_NO_SUCH_SONG; - - return playlist_seek_song(playlist, pc, song, seek_time); -} - -enum playlist_result -playlist_seek_current(struct playlist *playlist, struct player_control *pc, - float seek_time, bool relative) -{ - if (!playlist->playing) - return PLAYLIST_RESULT_NOT_PLAYING; - - if (relative) { - struct player_status status; - pc_get_status(pc, &status); - - if (status.state != PLAYER_STATE_PLAY && - status.state != PLAYER_STATE_PAUSE) - return PLAYLIST_RESULT_NOT_PLAYING; - - seek_time += (int)status.elapsed_time; - } - - if (seek_time < 0) - seek_time = 0; - - return playlist_seek_song(playlist, pc, playlist->current, seek_time); -} diff --git a/src/playlist_global.c b/src/playlist_global.c deleted file mode 100644 index e66febce0..000000000 --- a/src/playlist_global.c +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2003-2011 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. - */ - -/* - * The manager of the global "struct playlist" instance (g_playlist). - * - */ - -#include "config.h" -#include "playlist.h" -#include "event_pipe.h" -#include "Main.hxx" - -struct playlist g_playlist; - -static void -playlist_tag_event(void) -{ - playlist_tag_changed(&g_playlist); -} - -static void -playlist_event(void) -{ - playlist_sync(&g_playlist, global_player_control); -} - -void -playlist_global_init(void) -{ - playlist_init(&g_playlist); - - event_pipe_register(PIPE_EVENT_TAG, playlist_tag_event); - event_pipe_register(PIPE_EVENT_PLAYLIST, playlist_event); -} - -void -playlist_global_finish(void) -{ - playlist_finish(&g_playlist); -} diff --git a/src/playlist_internal.h b/src/playlist_internal.h deleted file mode 100644 index 81b175176..000000000 --- a/src/playlist_internal.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2003-2011 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. - */ - -/* - * Internal header for the components of the playlist code. - * - */ - -#ifndef PLAYLIST_INTERNAL_H -#define PLAYLIST_INTERNAL_H - -#include "playlist.h" - -struct player_control; - -/** - * Returns the song object which is currently queued. Returns none if - * there is none (yet?) or if MPD isn't playing. - */ -const struct song * -playlist_get_queued_song(struct playlist *playlist); - -/** - * 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 -playlist_update_queued_song(struct playlist *playlist, - struct player_control *pc, - const struct song *prev); - -void -playlist_play_order(struct playlist *playlist, struct player_control *pc, - int orderNum); - -#endif diff --git a/src/queue.c b/src/queue.c deleted file mode 100644 index 098cbcce9..000000000 --- a/src/queue.c +++ /dev/null @@ -1,604 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 "queue.h" -#include "song.h" - -#include - -/** - * Generate a non-existing id number. - */ -static unsigned -queue_generate_id(const struct queue *queue) -{ - static unsigned cur = (unsigned)-1; - - do { - cur++; - - if (cur >= queue->max_length * QUEUE_HASH_MULT) - cur = 0; - } while (queue->id_to_position[cur] != -1); - - return cur; -} - -int -queue_next_order(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - if (queue->single && queue->repeat && !queue->consume) - return order; - else if (order + 1 < queue->length) - return order + 1; - else if (queue->repeat && (order > 0 || !queue->consume)) - /* restart at first song */ - return 0; - else - /* end of queue */ - return -1; -} - -void -queue_increment_version(struct queue *queue) -{ - static unsigned long max = ((uint32_t) 1 << 31) - 1; - - queue->version++; - - if (queue->version >= max) { - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = 0; - - queue->version = 1; - } -} - -void -queue_modify(struct queue *queue, unsigned order) -{ - unsigned position; - - assert(order < queue->length); - - position = queue->order[order]; - queue->items[position].version = queue->version; - - queue_increment_version(queue); -} - -void -queue_modify_all(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) - queue->items[i].version = queue->version; - - queue_increment_version(queue); -} - -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority) -{ - unsigned id = queue_generate_id(queue); - - assert(!queue_is_full(queue)); - - queue->items[queue->length] = (struct queue_item){ - .song = song_dup_detached(song), - .id = id, - .version = queue->version, - .priority = priority, - }; - - queue->order[queue->length] = queue->length; - queue->id_to_position[id] = queue->length; - - ++queue->length; - - return id; -} - -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2) -{ - struct queue_item tmp; - unsigned id1 = queue->items[position1].id; - unsigned id2 = queue->items[position2].id; - - tmp = queue->items[position1]; - queue->items[position1] = queue->items[position2]; - queue->items[position2] = tmp; - - queue->items[position1].version = queue->version; - queue->items[position2].version = queue->version; - - queue->id_to_position[id1] = position2; - queue->id_to_position[id2] = position1; -} - -static void -queue_move_song_to(struct queue *queue, unsigned from, unsigned to) -{ - unsigned from_id = queue->items[from].id; - - queue->items[to] = queue->items[from]; - queue->items[to].version = queue->version; - queue->id_to_position[from_id] = to; -} - -void -queue_move(struct queue *queue, unsigned from, unsigned to) -{ - struct queue_item item = queue->items[from]; - - /* move songs to one less in from->to */ - - for (unsigned i = from; i < to; i++) - queue_move_song_to(queue, i + 1, i); - - /* move songs to one more in to->from */ - - for (unsigned i = from; i > to; i--) - queue_move_song_to(queue, i - 1, i); - - /* put song at _to_ */ - - queue->id_to_position[item.id] = to; - queue->items[to] = item; - queue->items[to].version = queue->version; - - /* now deal with order */ - - if (queue->random) { - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] > from && queue->order[i] <= to) - queue->order[i]--; - else if (queue->order[i] < from && - queue->order[i] >= to) - queue->order[i]++; - else if (from == queue->order[i]) - queue->order[i] = to; - } - } -} - -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to) -{ - struct queue_item items[end - start]; - // Copy the original block [start,end-1] - for (unsigned i = start; i < end; i++) - items[i - start] = queue->items[i]; - - // If to > start, we need to move to-start items to start, starting from end - for (unsigned i = end; i < end + to - start; i++) - queue_move_song_to(queue, i, start + i - end); - - // If to < start, we need to move start-to items to newend (= end + to - start), starting from to - // This is the same as moving items from start-1 to to (decreasing), with start-1 going to end-1 - // We have to iterate in this order to avoid writing over something we haven't yet moved - for (unsigned i = start - 1; i >= to && i != G_MAXUINT; i--) - queue_move_song_to(queue, i, i + end - start); - - // Copy the original block back in, starting at to. - for (unsigned i = start; i< end; i++) - { - queue->id_to_position[items[i-start].id] = to + i - start; - queue->items[to + i - start] = items[i-start]; - queue->items[to + i - start].version = queue->version; - } - - if (queue->random) { - // Update the positions in the queue. - // Note that the ranges for these cases are the same as the ranges of - // the loops above. - for (unsigned i = 0; i < queue->length; i++) { - if (queue->order[i] >= end && queue->order[i] < to + end - start) - queue->order[i] -= end - start; - else if (queue->order[i] < start && - queue->order[i] >= to) - queue->order[i] += end - start; - else if (start <= queue->order[i] && queue->order[i] < end) - queue->order[i] += to - start; - } - } -} - -/** - * Moves a song to a new position in the "order" list. - */ -static void -queue_move_order(struct queue *queue, unsigned from_order, unsigned to_order) -{ - assert(queue != NULL); - assert(from_order < queue->length); - assert(to_order <= queue->length); - - const unsigned from_position = - queue_order_to_position(queue, from_order); - - if (from_order < to_order) { - for (unsigned i = from_order; i < to_order; ++i) - queue->order[i] = queue->order[i + 1]; - } else { - for (unsigned i = from_order; i > to_order; --i) - queue->order[i] = queue->order[i - 1]; - } - - queue->order[to_order] = from_position; -} - -void -queue_delete(struct queue *queue, unsigned position) -{ - struct song *song; - unsigned id, order; - - assert(position < queue->length); - - song = queue_get(queue, position); - assert(!song_in_database(song) || song_is_detached(song)); - song_free(song); - - id = queue_position_to_id(queue, position); - order = queue_position_to_order(queue, position); - - --queue->length; - - /* release the song id */ - - queue->id_to_position[id] = -1; - - /* delete song from songs array */ - - for (unsigned i = position; i < queue->length; i++) - queue_move_song_to(queue, i + 1, i); - - /* delete the entry from the order array */ - - for (unsigned i = order; i < queue->length; i++) - queue->order[i] = queue->order[i + 1]; - - /* readjust values in the order array */ - - for (unsigned i = 0; i < queue->length; i++) - if (queue->order[i] > position) - --queue->order[i]; -} - -void -queue_clear(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; i++) { - struct queue_item *item = &queue->items[i]; - - assert(!song_in_database(item->song) || - song_is_detached(item->song)); - song_free(item->song); - - queue->id_to_position[item->id] = -1; - } - - queue->length = 0; -} - -void -queue_init(struct queue *queue, unsigned max_length) -{ - queue->max_length = max_length; - queue->length = 0; - queue->version = 1; - queue->repeat = false; - queue->random = false; - queue->single = false; - queue->consume = false; - - queue->items = g_new(struct queue_item, max_length); - queue->order = g_malloc(sizeof(queue->order[0]) * - max_length); - queue->id_to_position = g_malloc(sizeof(queue->id_to_position[0]) * - max_length * QUEUE_HASH_MULT); - - for (unsigned i = 0; i < max_length * QUEUE_HASH_MULT; ++i) - queue->id_to_position[i] = -1; - - queue->rand = g_rand_new(); -} - -void -queue_finish(struct queue *queue) -{ - queue_clear(queue); - - g_free(queue->items); - g_free(queue->order); - g_free(queue->id_to_position); - - g_rand_free(queue->rand); -} - -static const struct queue_item * -queue_get_order_item_const(const struct queue *queue, unsigned order) -{ - assert(queue != NULL); - assert(order < queue->length); - - return &queue->items[queue->order[order]]; -} - -static uint8_t -queue_get_order_priority(const struct queue *queue, unsigned order) -{ - return queue_get_order_item_const(queue, order)->priority; -} - -static gint -queue_item_compare_order_priority(gconstpointer av, gconstpointer bv, - gpointer user_data) -{ - const struct queue *queue = user_data; - const unsigned *const ap = av; - const unsigned *const bp = bv; - assert(ap >= queue->order && ap < queue->order + queue->length); - assert(bp >= queue->order && bp < queue->order + queue->length); - uint8_t a = queue->items[*ap].priority; - uint8_t b = queue->items[*bp].priority; - - if (G_LIKELY(a == b)) - return 0; - else if (a > b) - return -1; - else - return 1; -} - -static void -queue_sort_order_by_priority(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - g_qsort_with_data(&queue->order[start], end - start, - sizeof(queue->order[0]), - queue_item_compare_order_priority, - queue); -} - -/** - * Shuffle the order of items in the specified range, ignoring their - * priorities. - */ -static void -queue_shuffle_order_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; ++i) - queue_swap_order(queue, i, - g_rand_int_range(queue->rand, i, end)); -} - -/** - * Sort the "order" of items by priority, and then shuffle each - * priority group. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end) -{ - assert(queue != NULL); - assert(queue->random); - assert(start <= end); - assert(end <= queue->length); - - if (start == end) - return; - - /* first group the range by priority */ - queue_sort_order_by_priority(queue, start, end); - - /* now shuffle each priority group */ - unsigned group_start = start; - uint8_t group_priority = queue_get_order_priority(queue, start); - - for (unsigned i = start + 1; i < end; ++i) { - uint8_t priority = queue_get_order_priority(queue, i); - assert(priority <= group_priority); - - if (priority != group_priority) { - /* start of a new group - shuffle the one that - has just ended */ - queue_shuffle_order_range(queue, group_start, i); - group_start = i; - group_priority = priority; - } - } - - /* shuffle the last group */ - queue_shuffle_order_range(queue, group_start, end); -} - -void -queue_shuffle_order(struct queue *queue) -{ - queue_shuffle_order_range_with_priority(queue, 0, queue->length); -} - -static void -queue_shuffle_order_first(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, start, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end) -{ - queue_swap_order(queue, end - 1, - g_rand_int_range(queue->rand, start, end)); -} - -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end) -{ - assert(start <= end); - assert(end <= queue->length); - - for (unsigned i = start; i < end; i++) { - unsigned ri = g_rand_int_range(queue->rand, i, end); - queue_swap(queue, i, ri); - } -} - -/** - * Find the first item that has this specified priority or higher. - */ -G_GNUC_PURE -static unsigned -queue_find_priority_order(const struct queue *queue, unsigned start_order, - uint8_t priority, unsigned exclude_order) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority <= priority && order != exclude_order) - return order; - } - - return queue->length; -} - -G_GNUC_PURE -static unsigned -queue_count_same_priority(const struct queue *queue, unsigned start_order, - uint8_t priority) -{ - assert(queue != NULL); - assert(queue->random); - assert(start_order <= queue->length); - - for (unsigned order = start_order; order < queue->length; ++order) { - const unsigned position = queue_order_to_position(queue, order); - const struct queue_item *item = &queue->items[position]; - if (item->priority != priority) - return order - start_order; - } - - return queue->length - start_order; -} - -bool -queue_set_priority(struct queue *queue, unsigned position, uint8_t priority, - int after_order) -{ - assert(queue != NULL); - assert(position < queue->length); - - struct queue_item *item = &queue->items[position]; - uint8_t old_priority = item->priority; - if (old_priority == priority) - return false; - - item->version = queue->version; - item->priority = priority; - - if (!queue->random) - /* don't reorder if not in random mode */ - return true; - - unsigned order = queue_position_to_order(queue, position); - if (after_order >= 0) { - if (order == (unsigned)after_order) - /* don't reorder the current song */ - return true; - - if (order < (unsigned)after_order) { - /* the specified song has been played already - - enqueue it only if its priority has just - become bigger than the current one's */ - - const unsigned after_position = - queue_order_to_position(queue, after_order); - const struct queue_item *after_item = - &queue->items[after_position]; - if (old_priority > after_item->priority || - priority <= after_item->priority) - /* priority hasn't become bigger */ - return true; - } - } - - /* move the item to the beginning of the priority group (or - create a new priority group) */ - - const unsigned before_order = - queue_find_priority_order(queue, after_order + 1, priority, - order); - const unsigned new_order = before_order > order - ? before_order - 1 - : before_order; - queue_move_order(queue, order, new_order); - - /* shuffle the song within that priority group */ - - const unsigned priority_count = - queue_count_same_priority(queue, new_order, priority); - assert(priority_count >= 1); - queue_shuffle_order_first(queue, new_order, - new_order + priority_count); - - return true; -} - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order) -{ - assert(queue != NULL); - assert(start_position <= end_position); - assert(end_position <= queue->length); - - bool modified = false; - int after_position = after_order >= 0 - ? (int)queue_order_to_position(queue, after_order) - : -1; - for (unsigned i = start_position; i < end_position; ++i) { - after_order = after_position >= 0 - ? (int)queue_position_to_order(queue, after_position) - : -1; - - modified |= queue_set_priority(queue, i, priority, - after_order); - } - - return modified; -} diff --git a/src/queue.h b/src/queue.h deleted file mode 100644 index e4bfcdffa..000000000 --- a/src/queue.h +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (C) 2003-2011 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 QUEUE_H -#define QUEUE_H - -#include - -#include -#include -#include - -enum { - /** - * reserve max_length * QUEUE_HASH_MULT elements in the id - * number space - */ - QUEUE_HASH_MULT = 4, -}; - -/** - * One element of the queue: basically a song plus some queue specific - * information attached. - */ -struct queue_item { - struct song *song; - - /** the unique id of this item in the queue */ - unsigned id; - - /** when was this item last changed? */ - uint32_t version; - - /** - * The priority of this item, between 0 and 255. High - * priority value means that this song gets played first in - * "random" mode. - */ - uint8_t priority; -}; - -/** - * A queue of songs. This is the backend of the playlist: it contains - * an ordered list of songs. - * - * Songs can be addressed in three possible ways: - * - * - the position in the queue - * - the unique id (which stays the same, regardless of moves) - * - the order number (which only differs from "position" in random mode) - */ -struct queue { - /** configured maximum length of the queue */ - unsigned max_length; - - /** number of songs in the queue */ - unsigned length; - - /** the current version number */ - uint32_t version; - - /** all songs in "position" order */ - struct queue_item *items; - - /** map order numbers to positions */ - unsigned *order; - - /** map song ids to positions */ - int *id_to_position; - - /** repeat playback when the end of the queue has been - reached? */ - bool repeat; - - /** play only current song. */ - bool single; - - /** remove each played files. */ - bool consume; - - /** play back songs in random order? */ - bool random; - - /** random number generator for shuffle and random mode */ - GRand *rand; -}; - -static inline unsigned -queue_length(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length; -} - -/** - * Determine if the queue is empty, i.e. there are no songs. - */ -static inline bool -queue_is_empty(const struct queue *queue) -{ - return queue->length == 0; -} - -/** - * Determine if the maximum number of songs has been reached. - */ -static inline bool -queue_is_full(const struct queue *queue) -{ - assert(queue->length <= queue->max_length); - - return queue->length >= queue->max_length; -} - -/** - * Is that a valid position number? - */ -static inline bool -queue_valid_position(const struct queue *queue, unsigned position) -{ - return position < queue->length; -} - -/** - * Is that a valid order number? - */ -static inline bool -queue_valid_order(const struct queue *queue, unsigned order) -{ - return order < queue->length; -} - -static inline int -queue_id_to_position(const struct queue *queue, unsigned id) -{ - if (id >= queue->max_length * QUEUE_HASH_MULT) - return -1; - - assert(queue->id_to_position[id] >= -1); - assert(queue->id_to_position[id] < (int)queue->length); - - return queue->id_to_position[id]; -} - -static inline int -queue_position_to_id(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].id; -} - -static inline unsigned -queue_order_to_position(const struct queue *queue, unsigned order) -{ - assert(order < queue->length); - - return queue->order[order]; -} - -static inline unsigned -queue_position_to_order(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - for (unsigned i = 0;; ++i) { - assert(i < queue->length); - - if (queue->order[i] == position) - return i; - } -} - -G_GNUC_PURE -static inline uint8_t -queue_get_priority_at_position(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].priority; -} - -/** - * Returns the song at the specified position. - */ -static inline struct song * -queue_get(const struct queue *queue, unsigned position) -{ - assert(position < queue->length); - - return queue->items[position].song; -} - -/** - * Returns the song at the specified order number. - */ -static inline struct song * -queue_get_order(const struct queue *queue, unsigned order) -{ - return queue_get(queue, queue_order_to_position(queue, order)); -} - -/** - * Is the song at the specified position newer than the specified - * version? - */ -static inline bool -queue_song_newer(const struct queue *queue, unsigned position, - uint32_t version) -{ - assert(position < queue->length); - - return version > queue->version || - queue->items[position].version >= version || - queue->items[position].version == 0; -} - -/** - * Initialize a queue object. - */ -void -queue_init(struct queue *queue, unsigned max_length); - -/** - * Deinitializes a queue object. It does not free the queue pointer - * itself. - */ -void -queue_finish(struct queue *queue); - -/** - * Returns the order number following the specified one. This takes - * end of queue and "repeat" mode into account. - * - * @return the next order number, or -1 to stop playback - */ -int -queue_next_order(const struct queue *queue, unsigned order); - -/** - * Increments the queue's version number. This handles integer - * overflow well. - */ -void -queue_increment_version(struct queue *queue); - -/** - * Marks the specified song as "modified" and increments the version - * number. - */ -void -queue_modify(struct queue *queue, unsigned order); - -/** - * Marks all songs as "modified" and increments the version number. - */ -void -queue_modify_all(struct queue *queue); - -/** - * Appends a song to the queue and returns its position. Prior to - * that, the caller must check if the queue is already full. - * - * If a song is not in the database (determined by - * song_in_database()), it is freed when removed from the queue. - * - * @param priority the priority of this new queue item - */ -unsigned -queue_append(struct queue *queue, struct song *song, uint8_t priority); - -/** - * Swaps two songs, addressed by their position. - */ -void -queue_swap(struct queue *queue, unsigned position1, unsigned position2); - -/** - * Swaps two songs, addressed by their order number. - */ -static inline void -queue_swap_order(struct queue *queue, unsigned order1, unsigned order2) -{ - unsigned tmp = queue->order[order1]; - queue->order[order1] = queue->order[order2]; - queue->order[order2] = tmp; -} - -/** - * Moves a song to a new position. - */ -void -queue_move(struct queue *queue, unsigned from, unsigned to); - -/** - * Moves a range of songs to a new position. - */ -void -queue_move_range(struct queue *queue, unsigned start, unsigned end, unsigned to); - -/** - * Removes a song from the playlist. - */ -void -queue_delete(struct queue *queue, unsigned position); - -/** - * Removes all songs from the playlist. - */ -void -queue_clear(struct queue *queue); - -/** - * Initializes the "order" array, and restores "normal" order. - */ -static inline void -queue_restore_order(struct queue *queue) -{ - for (unsigned i = 0; i < queue->length; ++i) - queue->order[i] = i; -} - -/** - * Shuffle the order of items in the specified range, taking their - * priorities into account. - */ -void -queue_shuffle_order_range_with_priority(struct queue *queue, - unsigned start, unsigned end); - -/** - * Shuffles the virtual order of songs, but does not move them - * physically. This is used in random mode. - */ -void -queue_shuffle_order(struct queue *queue); - -/** - * Shuffles the virtual order of the last song in the specified - * (order) range. This is used in random mode after a song has been - * appended by queue_append(). - */ -void -queue_shuffle_order_last(struct queue *queue, unsigned start, unsigned end); - -/** - * Shuffles a (position) range in the queue. The songs are physically - * shuffled, not by using the "order" mapping. - */ -void -queue_shuffle_range(struct queue *queue, unsigned start, unsigned end); - -bool -queue_set_priority(struct queue *queue, unsigned position, - uint8_t priority, int after_order); - -bool -queue_set_priority_range(struct queue *queue, - unsigned start_position, unsigned end_position, - uint8_t priority, int after_order); - -#endif diff --git a/src/replay_gain_config.c b/src/replay_gain_config.c index 2181387b7..3988e497a 100644 --- a/src/replay_gain_config.c +++ b/src/replay_gain_config.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,7 +19,7 @@ #include "config.h" #include "replay_gain_config.h" -#include "playlist.h" +#include "Playlist.hxx" #include "conf.h" #include "idle.h" #include "mpd_error.h" diff --git a/test/TestQueuePriority.cxx b/test/TestQueuePriority.cxx index 6d994176f..b187176e7 100644 --- a/test/TestQueuePriority.cxx +++ b/test/TestQueuePriority.cxx @@ -1,7 +1,5 @@ #include "config.h" -extern "C" { -#include "queue.h" -} +#include "Queue.hxx" #include "song.h" #include "Directory.hxx" diff --git a/test/run_filter.c b/test/run_filter.c index 8e793b768..6a2cd1ea2 100644 --- a/test/run_filter.c +++ b/test/run_filter.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2011 The Music Player Daemon Project + * Copyright (C) 2003-2013 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,7 +25,7 @@ #include "pcm_volume.h" #include "idle.h" #include "mixer_control.h" -#include "playlist.h" +#include "Playlist.hxx" #include "stdbin.h" #include diff --git a/test/run_output.cxx b/test/run_output.cxx index 2b0fdd5f0..9307256ab 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -32,7 +32,7 @@ extern "C" { #include "idle.h" } -#include "playlist.h" +#include "Playlist.hxx" #include "player_control.h" #include "stdbin.h" -- cgit v1.2.3