enum playlist_result playlist_move_range(struct playlist *playlist, unsigned start, unsigned end, int to) { const struct song *queued; int currentSong; if (!queue_valid_position(&playlist->queue, start) || !queue_valid_position(&playlist->queue, end - 1)) return PLAYLIST_RESULT_BAD_RANGE; if ((to >= 0 && to + end - start - 1 >= queue_length(&playlist->queue)) || (to < 0 && abs(to) > (int)queue_length(&playlist->queue))) return PLAYLIST_RESULT_BAD_RANGE; if ((int)start == to) /* nothing happens */ return PLAYLIST_RESULT_SUCCESS; queued = playlist_get_queued_song(playlist); /* * (to < 0) => move to offset from current song * (-playlist.length == to) => move to position BEFORE current song */ currentSong = playlist->current >= 0 ? (int)queue_order_to_position(&playlist->queue, playlist->current) : -1; if (to < 0 && playlist->current >= 0) { if (start <= (unsigned)currentSong && (unsigned)currentSong <= end) /* no-op, can't be moved to offset of itself */ return PLAYLIST_RESULT_SUCCESS; to = (currentSong + abs(to)) % queue_length(&playlist->queue); if (start < (unsigned)to) to--; } queue_move_range(&playlist->queue, start, end, to); if (!playlist->queue.random) { /* update current/queued */ if ((int)start <= playlist->current && (unsigned)playlist->current < end) playlist->current += to - start; else if (playlist->current >= (int)end && playlist->current <= to) { playlist->current -= end - start; } else if (playlist->current >= to && playlist->current < (int)start) { playlist->current += end - start; } } playlist_increment_version(playlist); playlist_update_queued_song(playlist, queued); return PLAYLIST_RESULT_SUCCESS; }
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); } }
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; }
void playlist_state_save(FILE *fp, const struct playlist *playlist) { struct player_status player_status; pc_get_status(&player_status); fputs(PLAYLIST_STATE_FILE_STATE "\n", fp); if (playlist->playing) { switch (player_status.state) { case PLAYER_STATE_PAUSE: fputs(PLAYLIST_STATE_FILE_STATE_PAUSE "\n", fp); break; default: fputs(PLAYLIST_STATE_FILE_STATE_PLAY "\n", fp); } fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", queue_order_to_position(&playlist->queue, playlist->current)); fprintf(fp, PLAYLIST_STATE_FILE_TIME "%i\n", (int)player_status.elapsed_time); } else { fputs(PLAYLIST_STATE_FILE_STATE_STOP "\n", fp); if (playlist->current >= 0) fprintf(fp, PLAYLIST_STATE_FILE_CURRENT "%i\n", queue_order_to_position(&playlist->queue, playlist->current)); } fprintf(fp, PLAYLIST_STATE_FILE_RANDOM "%i\n", playlist->queue.random); fprintf(fp, PLAYLIST_STATE_FILE_REPEAT "%i\n", playlist->queue.repeat); fprintf(fp, PLAYLIST_STATE_FILE_SINGLE "%i\n", playlist->queue.single); fprintf(fp, PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist->queue.consume); fprintf(fp, PLAYLIST_STATE_FILE_CROSSFADE "%i\n", (int)(pc_get_cross_fade())); fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc_get_mixramp_db()); fprintf(fp, PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", pc_get_mixramp_delay()); fputs(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n", fp); queue_save(fp, &playlist->queue); fputs(PLAYLIST_STATE_FILE_PLAYLIST_END "\n", fp); }
int playlist_get_current_song(const struct playlist *playlist) { if (playlist->current >= 0) return queue_order_to_position(&playlist->queue, playlist->current); return -1; }
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_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_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)); }
static void check_descending_priority(G_GNUC_UNUSED const struct queue *queue, unsigned start_order) { assert(start_order < queue_length(queue)); uint8_t last_priority = 0xff; for (unsigned order = start_order; order < queue_length(queue); ++order) { unsigned position = queue_order_to_position(queue, order); uint8_t priority = queue->items[position].priority; assert(priority <= last_priority); (void)last_priority; last_priority = priority; } }
void playlist_shuffle(struct playlist *playlist, struct player_control *pc, unsigned start, unsigned end) { const struct song *queued; if (end > queue_length(&playlist->queue)) /* correct the "end" offset */ end = queue_length(&playlist->queue); if ((start+1) >= end) /* needs at least two entries. */ return; queued = playlist_get_queued_song(playlist); if (playlist->playing && playlist->current >= 0) { unsigned current_position; current_position = queue_order_to_position(&playlist->queue, playlist->current); if (current_position >= start && current_position < end) { /* put current playing song first */ queue_swap(&playlist->queue, start, current_position); if (playlist->queue.random) { playlist->current = queue_position_to_order(&playlist->queue, start); } else playlist->current = start; /* start shuffle after the current song */ start++; } } else { /* no playback currently: reset playlist->current */ playlist->current = -1; } queue_shuffle_range(&playlist->queue, start, end); playlist_increment_version(playlist); playlist_update_queued_song(playlist, pc, queued); }
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); }
/** * 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); }
enum playlist_result playlist_set_priority(struct playlist *playlist, struct player_control *pc, unsigned start, unsigned end, uint8_t priority) { if (start >= queue_length(&playlist->queue)) return PLAYLIST_RESULT_BAD_RANGE; if (end > queue_length(&playlist->queue)) end = queue_length(&playlist->queue); if (start >= end) return PLAYLIST_RESULT_SUCCESS; /* remember "current" and "queued" */ int current_position = playlist->current >= 0 ? (int)queue_order_to_position(&playlist->queue, playlist->current) : -1; const struct song *queued = playlist_get_queued_song(playlist); /* apply the priority changes */ queue_set_priority_range(&playlist->queue, start, end, priority, playlist->current); playlist_increment_version(playlist); /* restore "current" and choose a new "queued" */ if (current_position >= 0) playlist->current = queue_position_to_order(&playlist->queue, current_position); playlist_update_queued_song(playlist, pc, queued); return PLAYLIST_RESULT_SUCCESS; }
/** * Called if the player thread has started playing the "queued" song. */ static void playlist_song_started(struct playlist *playlist) { 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; /* Pause if we are in single mode. */ if(playlist->queue.single && !playlist->queue.repeat) { pc_set_pause(true); } if(playlist->queue.consume) playlist_delete(playlist, queue_order_to_position(&playlist->queue, current)); idle_add(IDLE_PLAYER); }
unsigned playlist_state_get_hash(const struct playlist *playlist) { struct player_status player_status; pc_get_status(&player_status); return playlist->queue.version ^ (player_status.state != PLAYER_STATE_STOP ? ((int)player_status.elapsed_time << 8) : 0) ^ (playlist->current >= 0 ? (queue_order_to_position(&playlist->queue, playlist->current) << 16) : 0) ^ ((int)pc_get_cross_fade() << 20) ^ (player_status.state << 24) ^ (playlist->queue.random << 27) ^ (playlist->queue.repeat << 28) ^ (playlist->queue.single << 29) ^ (playlist->queue.consume << 30) ^ (playlist->queue.random << 31); }
int main(G_GNUC_UNUSED int argc, G_GNUC_UNUSED char **argv) { struct song songs[16]; struct queue queue; queue_init(&queue, 32); for (unsigned i = 0; i < G_N_ELEMENTS(songs); ++i) queue_append(&queue, &songs[i]); assert(queue_length(&queue) == G_N_ELEMENTS(songs)); /* priority=10 for 4 items */ queue_set_priority_range(&queue, 4, 8, 10, -1); queue.random = true; queue_shuffle_order(&queue); check_descending_priority(&queue, 0); for (unsigned i = 0; i < 4; ++i) { assert(queue_position_to_order(&queue, i) >= 4); } for (unsigned i = 4; i < 8; ++i) { assert(queue_position_to_order(&queue, i) < 4); } for (unsigned i = 8; i < G_N_ELEMENTS(songs); ++i) { assert(queue_position_to_order(&queue, i) >= 4); } /* priority=50 one more item */ queue_set_priority_range(&queue, 15, 16, 50, -1); check_descending_priority(&queue, 0); assert(queue_position_to_order(&queue, 15) == 0); for (unsigned i = 0; i < 4; ++i) { assert(queue_position_to_order(&queue, i) >= 4); } for (unsigned i = 4; i < 8; ++i) { assert(queue_position_to_order(&queue, i) >= 1 && queue_position_to_order(&queue, i) < 5); } for (unsigned i = 8; i < 15; ++i) { assert(queue_position_to_order(&queue, i) >= 5); } /* priority=20 for one of the 4 priority=10 items */ queue_set_priority_range(&queue, 3, 4, 20, -1); check_descending_priority(&queue, 0); assert(queue_position_to_order(&queue, 3) == 1); assert(queue_position_to_order(&queue, 15) == 0); for (unsigned i = 0; i < 3; ++i) { assert(queue_position_to_order(&queue, i) >= 5); } for (unsigned i = 4; i < 8; ++i) { assert(queue_position_to_order(&queue, i) >= 2 && queue_position_to_order(&queue, i) < 6); } for (unsigned i = 8; i < 15; ++i) { assert(queue_position_to_order(&queue, i) >= 6); } /* priority=20 for another one of the 4 priority=10 items; pass "after_order" (with priority=10) and see if it's moved after that one */ unsigned current_order = 4; unsigned current_position = queue_order_to_position(&queue, current_order); unsigned a_order = 3; unsigned a_position = queue_order_to_position(&queue, a_order); assert(queue.items[a_position].priority == 10); queue_set_priority(&queue, a_position, 20, current_order); current_order = queue_position_to_order(&queue, current_position); assert(current_order == 3); a_order = queue_position_to_order(&queue, a_position); assert(a_order == 4); check_descending_priority(&queue, current_order + 1); /* priority=70 for one of the last items; must be inserted right after the current song, before the priority=20 one we just created */ unsigned b_order = 10; unsigned b_position = queue_order_to_position(&queue, b_order); assert(queue.items[b_position].priority == 0); queue_set_priority(&queue, b_position, 70, current_order); current_order = queue_position_to_order(&queue, current_position); assert(current_order == 3); b_order = queue_position_to_order(&queue, b_position); assert(b_order == 4); check_descending_priority(&queue, current_order + 1); /* priority=60 for the old prio50 item; must not be moved, because it's before the current song, and it's status hasn't changed (it was already higher before) */ unsigned c_order = 0; unsigned c_position = queue_order_to_position(&queue, c_order); assert(queue.items[c_position].priority == 50); queue_set_priority(&queue, c_position, 60, current_order); current_order = queue_position_to_order(&queue, current_position); assert(current_order == 3); c_order = queue_position_to_order(&queue, c_position); assert(c_order == 0); /* move the prio=20 item back */ a_order = queue_position_to_order(&queue, a_position); assert(a_order == 5); assert(queue.items[a_position].priority == 20); queue_set_priority(&queue, a_position, 5, current_order); current_order = queue_position_to_order(&queue, current_position); assert(current_order == 3); a_order = queue_position_to_order(&queue, a_position); assert(a_order == 6); }