/* * 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 "playlist_save.h" #include "player_control.h" #include "command.h" #include "tag.h" #include "song.h" #include "conf.h" #include "stored_playlist.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) { struct song *song; char *uri; assert(queue_valid_order(&playlist->queue, order)); playlist->queued = order; song = 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) { struct song *song; char *uri; playlist->playing = true; playlist->queued = -1; song = 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(pc); if (error == PLAYER_ERROR_NOERROR) playlist->error_count = 0; else ++playlist->error_count; if ((playlist->stop_on_error && error != PLAYER_ERROR_NOERROR) || error == PLAYER_ERROR_AUDIO || error == PLAYER_ERROR_SYSTEM || 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); }