struct song * playlist_check_translate_song(struct song *song, const char *base_uri, bool secure) { if (song_in_database(song)) /* already ok */ return song; const char *uri = song->uri; if (uri_has_scheme(uri)) { if (uri_supported_scheme(uri)) /* valid remote song */ return song; else { /* unsupported remote song */ song_free(song); return NULL; } } if (base_uri != NULL && strcmp(base_uri, ".") == 0) /* g_path_get_dirname() returns "." when there is no directory name in the given path; clear that now, because it would break the database lookup functions */ base_uri = NULL; if (g_path_is_absolute(uri)) { /* XXX fs_charset vs utf8? */ const char *suffix = map_to_relative_path(uri); assert(suffix != NULL); if (suffix != uri) uri = suffix; else if (!secure) { /* local files must be relative to the music directory when "secure" is enabled */ song_free(song); return NULL; } base_uri = NULL; } char *allocated = NULL; if (base_uri != NULL) uri = allocated = g_build_filename(base_uri, uri, NULL); struct song *dest = playlist_check_load_song(song, uri, secure); song_free(song); g_free(allocated); return dest; }
/** * After the decoder has been started asynchronously, wait for the * "START" command to finish. The decoder may not be initialized yet, * i.e. there is no audio_format information yet. * * The player lock is not held. */ static bool player_wait_for_decoder(struct player *player) { struct player_control *pc = player->pc; struct decoder_control *dc = player->dc; assert(player->queued || pc->command == PLAYER_COMMAND_SEEK); assert(pc->next_song != NULL); player->queued = false; GError *error = dc_lock_get_error(dc); if (error != NULL) { player_lock(pc); pc_set_error(pc, PLAYER_ERROR_DECODER, error); song_free(pc->next_song); pc->next_song = NULL; player_unlock(pc); return false; } if (player->song != NULL) song_free(player->song); player->song = pc->next_song; player->elapsed_time = 0.0; /* set the "starting" flag, which will be cleared by player_check_decoder_startup() */ player->decoder_starting = true; player_lock(pc); /* update player_control's song information */ pc->total_time = song_get_duration(pc->next_song); pc->bit_rate = 0; audio_format_clear(&pc->audio_format); /* clear the queued song */ pc->next_song = NULL; player_unlock(pc); /* call syncPlaylistWithQueue() in the main thread */ event_pipe_emit(PIPE_EVENT_PLAYLIST); return true; }
static struct song * apply_song_metadata(struct song *dest, const struct song *src) { struct song *tmp; assert(dest != NULL); assert(src != NULL); if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) return dest; if (song_in_database(dest)) { char *path_fs = map_song_fs(dest); if (path_fs == NULL) return dest; tmp = song_file_new(path_fs, NULL); g_free(path_fs); merge_song_metadata(tmp, dest, src); } else { tmp = song_file_new(dest->uri, NULL); merge_song_metadata(tmp, dest, src); song_free(dest); } return tmp; }
struct song * song_file_load(const char *path, struct directory *parent) { struct song *song; bool ret; assert((parent == NULL) == g_path_is_absolute(path)); assert(!uri_has_scheme(path)); assert(strchr(path, '\n') == NULL); song = song_file_new(path, parent); //in archive ? if (parent != NULL && parent->device == DEVICE_INARCHIVE) { ret = song_file_update_inarchive(song); } else { ret = song_file_update(song); } if (!ret) { song_free(song); return NULL; } return song; }
enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, struct playlist *dest, struct player_control *pc, bool secure) { enum playlist_result result; struct song *song; char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; while ((song = playlist_plugin_read(source)) != NULL) { song = playlist_check_translate_song(song, base_uri, secure); if (song == NULL) continue; result = playlist_append_song(dest, pc, song, NULL); if (result != PLAYLIST_RESULT_SUCCESS) { if (!song_in_database(song)) song_free(song); g_free(base_uri); return result; } } g_free(base_uri); return PLAYLIST_RESULT_SUCCESS; }
static void asx_end_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *element_name, gpointer user_data, G_GNUC_UNUSED GError **error) { struct asx_parser *parser = user_data; switch (parser->state) { case ROOT: break; case ENTRY: if (g_ascii_strcasecmp(element_name, "entry") == 0) { if (strcmp(parser->song->uri, "asx:") != 0) parser->songs = g_slist_prepend(parser->songs, parser->song); else song_free(parser->song); parser->state = ROOT; } else parser->tag = TAG_NUM_OF_ITEM_TYPES; break; } }
static void song_free_callback(gpointer data, G_GNUC_UNUSED gpointer user_data) { struct song *song = data; song_free(song); }
enum playlist_result playlist_load_into_queue(const char *uri, struct playlist_provider *source, unsigned start_index, unsigned end_index, struct playlist *dest, struct player_control *pc, bool secure) { enum playlist_result result; struct song *song; char *base_uri = uri != NULL ? g_path_get_dirname(uri) : NULL; for (unsigned i = 0; i < end_index && (song = playlist_plugin_read(source)) != NULL; ++i) { if (i < start_index) { /* skip songs before the start index */ song_free(song); continue; } song = playlist_check_translate_song(song, base_uri, secure); if (song == NULL) continue; #ifdef ENABLE_DESPOTIFY // Not the right place to do this... if (strcmp(g_uri_parse_scheme(song->uri), "spt") == 0) { g_debug("Despotify update info : %s\n", song->uri); struct song *song_new; song_new = despotify_update_song(song); if (song_new != NULL) { song = song_new; } } #endif result = playlist_append_song(dest, pc, song, NULL); song_free(song); if (result != PLAYLIST_RESULT_SUCCESS) { g_free(base_uri); return result; } } g_free(base_uri); return PLAYLIST_RESULT_SUCCESS; }
void pc_free(struct player_control *pc) { if (pc->next_song != NULL) song_free(pc->next_song); g_cond_free(pc->cond); g_mutex_free(pc->mutex); g_free(pc); }
static void asx_parser_destroy(gpointer data) { struct asx_parser *parser = data; if (parser->state >= ENTRY) song_free(parser->song); g_slist_foreach(parser->songs, song_free_callback, NULL); g_slist_free(parser->songs); }
static void xspf_parser_destroy(gpointer data) { struct xspf_parser *parser = data; if (parser->state >= TRACK && parser->song != NULL) song_free(parser->song); g_slist_foreach(parser->songs, song_free_callback, NULL); g_slist_free(parser->songs); }
static void delete_song(struct directory *dir, struct song *del) { /* first, prevent traversers in main task from getting this */ songvec_delete(&dir->songs, del); /* now take it out of the playlist (in the main_task) */ update_remove_song(del); /* finally, all possible references gone, free it */ song_free(del); }
static void asx_start_element(G_GNUC_UNUSED GMarkupParseContext *context, const gchar *element_name, const gchar **attribute_names, const gchar **attribute_values, gpointer user_data, G_GNUC_UNUSED GError **error) { struct asx_parser *parser = user_data; switch (parser->state) { case ROOT: if (g_ascii_strcasecmp(element_name, "entry") == 0) { parser->state = ENTRY; parser->song = song_remote_new("asx:"); parser->tag = TAG_NUM_OF_ITEM_TYPES; } break; case ENTRY: if (g_ascii_strcasecmp(element_name, "ref") == 0) { const gchar *href = get_attribute(attribute_names, attribute_values, "href"); if (href != NULL) { /* create new song object, and copy the existing tag over; we cannot replace the existing song's URI, because that attribute is immutable */ struct song *song = song_remote_new(href); if (parser->song != NULL) { song->tag = parser->song->tag; parser->song->tag = NULL; song_free(parser->song); } parser->song = song; } } else if (g_ascii_strcasecmp(element_name, "author") == 0) /* is that correct? or should it be COMPOSER or PERFORMER? */ parser->tag = TAG_ARTIST; else if (g_ascii_strcasecmp(element_name, "title") == 0) parser->tag = TAG_TITLE; break; } }
void queue_clear(struct queue *queue) { for (unsigned i = 0; i < queue->length; i++) { struct queue_item *item = &queue->items[i]; if (!song_in_database(item->song)) song_free(item->song); queue->id_to_position[item->id] = -1; } queue->length = 0; }
void directory_free(struct directory *directory) { for (unsigned i = 0; i < directory->songs.nr; ++i) song_free(directory->songs.base[i]); for (unsigned i = 0; i < directory->children.nr; ++i) directory_free(directory->children.base[i]); dirvec_destroy(&directory->children); songvec_destroy(&directory->songs); g_free(directory); /* this resets last dir returned */ /*directory_get_path(NULL); */ }
void directory_free(struct directory *directory) { playlist_vector_deinit(&directory->playlists); struct song *song, *ns; directory_for_each_song_safe(song, ns, directory) song_free(song); struct directory *child, *n; directory_for_each_child_safe(child, n, directory) directory_free(child); g_free(directory); /* this resets last dir returned */ /*directory_get_path(NULL); */ }
/* Destroy play list */ void plist_free( plist_t *pl ) { if (pl != NULL) { if (pl->m_list != NULL) { int i; plist_lock(pl); for ( i = 0; i < pl->m_len; i ++ ) song_free(pl->m_list[i]); free(pl->m_list); plist_unlock(pl); } pthread_mutex_destroy(&pl->m_mutex); free(pl); } } /* End of 'plist_free' function */
static struct song * apply_song_metadata(struct song *dest, const struct song *src) { struct song *tmp; assert(dest != NULL); assert(src != NULL); if (src->tag == NULL && src->start_ms == 0 && src->end_ms == 0) return dest; if (song_in_database(dest)) { char *path_fs = map_song_fs(dest); if (path_fs == NULL) return dest; char *path_utf8 = fs_charset_to_utf8(path_fs); if (path_utf8 != NULL) g_free(path_fs); else path_utf8 = path_fs; tmp = song_file_new(path_utf8, NULL); g_free(path_utf8); merge_song_metadata(tmp, dest, src); } else { tmp = song_file_new(dest->uri, NULL); merge_song_metadata(tmp, dest, src); } if (dest->tag != NULL && dest->tag->time > 0 && src->start_ms > 0 && src->end_ms == 0 && src->start_ms / 1000 < (unsigned)dest->tag->time) /* the range is open-ended, and the playlist plugin did not know the total length of the song file (e.g. last track on a CUE file); fix it up here */ tmp->tag->time = dest->tag->time - src->start_ms / 1000; song_free(dest); return tmp; }
enum playlist_result spl_append_uri(const char *url, const char *utf8file) { struct song *song; if (uri_has_scheme(url)) { enum playlist_result ret; song = song_remote_new(url); ret = spl_append_song(utf8file, song); song_free(song); return ret; } else { song = db_get_song(url); if (song == NULL) return PLAYLIST_RESULT_NO_SUCH_SONG; return spl_append_song(utf8file, song); } }
bool pc_seek(struct player_control *pc, struct song *song, float seek_time) { assert(song != NULL); player_lock(pc); if (pc->next_song != NULL) song_free(pc->next_song); pc->next_song = song; pc->seek_where = seek_time; player_command_locked(pc, PLAYER_COMMAND_SEEK); player_unlock(pc); assert(pc->next_song == NULL); idle_add(IDLE_PLAYER); return true; }
bool spl_append_uri(const char *url, const char *utf8file, GError **error_r) { struct song *song; if (uri_has_scheme(url)) { song = song_remote_new(url); bool success = spl_append_song(utf8file, song, error_r); song_free(song); return success; } else { song = db_get_song(url); if (song == NULL) { g_set_error_literal(error_r, playlist_quark(), PLAYLIST_RESULT_NO_SUCH_SONG, "No such song"); return false; } return spl_append_song(utf8file, song, error_r); } }
void queue_delete(struct queue *queue, unsigned position) { struct song *song; unsigned id, order; assert(position < queue->length); song = queue_get(queue, position); if (!song_in_database(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]; }
struct song * song_load(FILE *fp, struct directory *parent, const char *uri, GString *buffer, GError **error_r) { struct song *song = parent != NULL ? song_file_new(uri, parent) : song_remote_new(uri); char *line, *colon; enum tag_type type; const char *value; while ((line = read_text_line(fp, buffer)) != NULL && strcmp(line, SONG_END) != 0) { colon = strchr(line, ':'); if (colon == NULL || colon == line) { if (song->tag != NULL) tag_end_add(song->tag); song_free(song); g_set_error(error_r, song_save_quark(), 0, "unknown line in db: %s", line); return NULL; } *colon++ = 0; value = g_strchug(colon); if ((type = tag_name_parse(line)) != TAG_NUM_OF_ITEM_TYPES) { if (!song->tag) { song->tag = tag_new(); tag_begin_add(song->tag); } tag_add_item(song->tag, type, value); } else if (strcmp(line, "Time") == 0) { if (!song->tag) { song->tag = tag_new(); tag_begin_add(song->tag); } song->tag->time = atoi(value); } else if (strcmp(line, SONG_MTIME) == 0) { song->mtime = atoi(value); } else if (strcmp(line, "Range") == 0) { char *endptr; song->start_ms = strtoul(value, &endptr, 10); if (*endptr == '-') song->end_ms = strtoul(endptr + 1, NULL, 10); } else { if (song->tag != NULL) tag_end_add(song->tag); song_free(song); g_set_error(error_r, song_save_quark(), 0, "unknown line in db: %s", line); return NULL; } } if (song->tag != NULL) tag_end_add(song->tag); return song; }
struct song * playlist_check_translate_song(struct song *song, const char *base_uri, bool secure) { struct song *dest; if (song_in_database(song)) /* already ok */ return song; char *uri = song->uri; if (uri_has_scheme(uri)) { if (uri_supported_scheme(uri)) /* valid remote song */ return song; else { /* unsupported remote song */ song_free(song); return NULL; } } if (base_uri != NULL && strcmp(base_uri, ".") == 0) /* g_path_get_dirname() returns "." when there is no directory name in the given path; clear that now, because it would break the database lookup functions */ base_uri = NULL; if (g_path_is_absolute(uri)) { /* XXX fs_charset vs utf8? */ const char *prefix = mapper_get_music_directory(); if (prefix != NULL && g_str_has_prefix(uri, prefix) && uri[strlen(prefix)] == '/') uri += strlen(prefix) + 1; else if (!secure) { /* local files must be relative to the music directory when "secure" is enabled */ song_free(song); return NULL; } base_uri = NULL; } if (base_uri != NULL) uri = g_build_filename(base_uri, uri, NULL); else uri = g_strdup(uri); if (uri_has_scheme(uri)) { dest = song_remote_new(uri); g_free(uri); } else if (g_path_is_absolute(uri) && secure) { dest = song_file_load(uri, NULL); if (dest == NULL) { song_free(song); return NULL; } } else { dest = db_get_song(uri); g_free(uri); if (dest == NULL) { /* not found in database */ song_free(song); return dest; } } dest = apply_song_metadata(dest, song); song_free(song); return dest; }
struct song * playlist_check_translate_song(struct song *song, const char *base_uri) { struct song *dest; if (song_in_database(song)) /* already ok */ return song; char *uri = song->uri; if (uri_has_scheme(uri)) { if (uri_supported_scheme(uri)) /* valid remote song */ return song; else { /* unsupported remote song */ song_free(song); return NULL; } } if (g_path_is_absolute(uri)) { /* XXX fs_charset vs utf8? */ char *prefix = base_uri != NULL ? map_uri_fs(base_uri) : map_directory_fs(db_get_root()); if (prefix == NULL || !g_str_has_prefix(uri, prefix) || uri[strlen(prefix)] != '/') { /* local files must be relative to the music directory */ g_free(prefix); song_free(song); return NULL; } uri += strlen(prefix) + 1; g_free(prefix); } if (base_uri != NULL) uri = g_build_filename(base_uri, uri, NULL); else uri = g_strdup(uri); if (uri_has_scheme(base_uri)) { dest = song_remote_new(uri); g_free(uri); } else { dest = db_get_song(uri); g_free(uri); if (dest == NULL) { /* not found in database */ song_free(song); return dest; } } dest = apply_song_metadata(dest, song); song_free(song); return dest; }
/* Remove selected songs from play list */ void plist_rem( plist_t *pl ) { int start, end, i, cur; assert(pl); /* Get real selection bounds */ PLIST_GET_SEL(pl, start, end); if (start >= pl->m_len) start = pl->m_len - 1; if (end >= pl->m_len) end = pl->m_len - 1; if (start < 0) start = 0; if (end < 0) end = 0; /* Check if we have anything to delete */ if (!pl->m_len) return; /* Store undo information */ if (player_store_undo) { struct tag_undo_list_item_t *undo; struct tag_undo_list_rem_t *data; int j; song_metadata_t metadata_empty = SONG_METADATA_EMPTY; undo = (struct tag_undo_list_item_t *)malloc(sizeof(*undo)); undo->m_type = UNDO_REM; undo->m_next = undo->m_prev = NULL; data = &undo->m_data.m_rem; data->m_num_files = end - start + 1; data->m_start_pos = start; data->m_files = (struct song_name *)malloc(sizeof(struct song_name) * data->m_num_files); for ( i = start, j = 0; i <= end; i ++, j ++ ) { song_t *s = pl->m_list[i]; struct song_name *sn = &data->m_files[j]; if (s->m_filename) { sn->m_filename = strdup(s->m_filename); sn->m_fullname = NULL; } else { sn->m_fullname = strdup(s->m_fullname); sn->m_filename = NULL; } song_metadata_t *metadata = &sn->m_metadata; (*metadata) = metadata_empty; metadata->m_start_time = s->m_start_time; metadata->m_end_time = s->m_end_time; metadata->m_len = s->m_len; if (s->m_default_title) metadata->m_title = strdup(s->m_default_title); } undo_add(player_ul, undo); } /* Stop currently playing song if it is inside area being removed */ if (pl->m_cur_song >= start && pl->m_cur_song <= end) { player_end_play(TRUE); } /* Unlock play list */ plist_lock(pl); /* Free memory */ for ( i = start; i <= end; i ++ ) song_free(pl->m_list[i]); /* Shift songs list and reallocate memory */ memmove(&pl->m_list[start], &pl->m_list[end + 1], (pl->m_len - end - 1) * sizeof(*pl->m_list)); pl->m_len -= (end - start + 1); if (pl->m_len) pl->m_list = (song_t **)realloc(pl->m_list, pl->m_len * sizeof(*pl->m_list)); else { free(pl->m_list); pl->m_list = NULL; } /* Fix cursor */ plist_move(pl, start, FALSE); pl->m_sel_start = pl->m_sel_end; if (pl->m_cur_song > end) pl->m_cur_song -= (end - start + 1); /* Unlock play list */ plist_unlock(pl); pmng_hook(player_pmng, "playlist"); } /* End of 'plist_rem' function */
/* * The main loop of the player thread, during playback. This is * basically a state machine, which multiplexes data between the * decoder thread and the output threads. */ static void do_play(struct player_control *pc, struct decoder_control *dc) { struct player player = { .pc = pc, .dc = dc, .buffering = true, .decoder_starting = false, .paused = false, .queued = true, .output_open = false, .song = NULL, .xfade = XFADE_UNKNOWN, .cross_fading = false, .cross_fade_chunks = 0, .cross_fade_tag = NULL, .elapsed_time = 0.0, }; player_unlock(pc); player.pipe = music_pipe_new(); player_dc_start(&player, player.pipe); if (!player_wait_for_decoder(&player)) { assert(player.song == NULL); player_dc_stop(&player); player_command_finished(pc); music_pipe_free(player.pipe); event_pipe_emit(PIPE_EVENT_PLAYLIST); player_lock(pc); return; } player_lock(pc); pc->state = PLAYER_STATE_PLAY; if (pc->command == PLAYER_COMMAND_SEEK) player.elapsed_time = pc->seek_where; player_command_finished_locked(pc); while (true) { player_process_command(&player); if (pc->command == PLAYER_COMMAND_STOP || pc->command == PLAYER_COMMAND_EXIT || pc->command == PLAYER_COMMAND_CLOSE_AUDIO) { player_unlock(pc); audio_output_all_cancel(); break; } player_unlock(pc); if (player.buffering) { /* buffering at the start of the song - wait until the buffer is large enough, to prevent stuttering on slow machines */ if (music_pipe_size(player.pipe) < pc->buffered_before_play && !decoder_lock_is_idle(dc)) { /* not enough decoded buffer space yet */ if (!player.paused && player.output_open && audio_output_all_check() < 4 && !player_send_silence(&player)) break; decoder_lock(dc); /* XXX race condition: check decoder again */ player_wait_decoder(pc, dc); decoder_unlock(dc); player_lock(pc); continue; } else { /* buffering is complete */ player.buffering = false; } } if (player.decoder_starting) { /* wait until the decoder is initialized completely */ if (!player_check_decoder_startup(&player)) break; player_lock(pc); continue; } #ifndef NDEBUG /* music_pipe_check_format(&play_audio_format, player.next_song_chunk, &dc->out_audio_format); */ #endif if (decoder_lock_is_idle(dc) && player.queued && dc->pipe == player.pipe) { /* the decoder has finished the current song; make it decode the next song */ assert(dc->pipe == NULL || dc->pipe == player.pipe); player_dc_start(&player, music_pipe_new()); } if (/* no cross-fading if MPD is going to pause at the end of the current song */ !pc->border_pause && player_dc_at_next_song(&player) && player.xfade == XFADE_UNKNOWN && !decoder_lock_is_starting(dc)) { /* enable cross fading in this song? if yes, calculate how many chunks will be required for it */ player.cross_fade_chunks = cross_fade_calc(pc->cross_fade_seconds, dc->total_time, pc->mixramp_db, pc->mixramp_delay_seconds, dc->replay_gain_db, dc->replay_gain_prev_db, dc->mixramp_start, dc->mixramp_prev_end, &dc->out_audio_format, &player.play_audio_format, music_buffer_size(player_buffer) - pc->buffered_before_play); if (player.cross_fade_chunks > 0) { player.xfade = XFADE_ENABLED; player.cross_fading = false; } else /* cross fading is disabled or the next song is too short */ player.xfade = XFADE_DISABLED; } if (player.paused) { player_lock(pc); if (pc->command == PLAYER_COMMAND_NONE) player_wait(pc); continue; } else if (!music_pipe_empty(player.pipe)) { /* at least one music chunk is ready - send it to the audio output */ play_next_chunk(&player); } else if (audio_output_all_check() > 0) { /* not enough data from decoder, but the output thread is still busy, so it's okay */ /* XXX synchronize in a better way */ g_usleep(10000); } else if (player_dc_at_next_song(&player)) { /* at the beginning of a new song */ if (!player_song_border(&player)) break; } else if (decoder_lock_is_idle(dc)) { /* check the size of the pipe again, because the decoder thread may have added something since we last checked */ if (music_pipe_empty(player.pipe)) { /* wait for the hardware to finish playback */ audio_output_all_drain(); break; } } else if (player.output_open) { /* the decoder is too busy and hasn't provided new PCM data in time: send silence (if the output pipe is empty) */ if (!player_send_silence(&player)) break; } player_lock(pc); } player_dc_stop(&player); music_pipe_clear(player.pipe, player_buffer); music_pipe_free(player.pipe); if (player.cross_fade_tag != NULL) tag_free(player.cross_fade_tag); if (player.song != NULL) song_free(player.song); player_lock(pc); if (player.queued) { assert(pc->next_song != NULL); song_free(pc->next_song); pc->next_song = NULL; } pc->state = PLAYER_STATE_STOP; player_unlock(pc); event_pipe_emit(PIPE_EVENT_PLAYLIST); player_lock(pc); } static gpointer player_task(gpointer arg) { struct player_control *pc = arg; struct decoder_control *dc = dc_new(pc->cond); decoder_thread_start(dc); player_buffer = music_buffer_new(pc->buffer_chunks); player_lock(pc); while (1) { switch (pc->command) { case PLAYER_COMMAND_SEEK: case PLAYER_COMMAND_QUEUE: assert(pc->next_song != NULL); do_play(pc, dc); break; case PLAYER_COMMAND_STOP: player_unlock(pc); audio_output_all_cancel(); player_lock(pc); /* fall through */ case PLAYER_COMMAND_PAUSE: if (pc->next_song != NULL) { song_free(pc->next_song); pc->next_song = NULL; } player_command_finished_locked(pc); break; case PLAYER_COMMAND_CLOSE_AUDIO: player_unlock(pc); audio_output_all_release(); player_lock(pc); player_command_finished_locked(pc); #ifndef NDEBUG /* in the DEBUG build, check for leaked music_chunk objects by freeing the music_buffer */ music_buffer_free(player_buffer); player_buffer = music_buffer_new(pc->buffer_chunks); #endif break; case PLAYER_COMMAND_UPDATE_AUDIO: player_unlock(pc); audio_output_all_enable_disable(); player_lock(pc); player_command_finished_locked(pc); break; case PLAYER_COMMAND_EXIT: player_unlock(pc); dc_quit(dc); dc_free(dc); audio_output_all_close(); music_buffer_free(player_buffer); player_command_finished(pc); return NULL; case PLAYER_COMMAND_CANCEL: if (pc->next_song != NULL) { song_free(pc->next_song); pc->next_song = NULL; } player_command_finished_locked(pc); break; case PLAYER_COMMAND_REFRESH: /* no-op when not playing */ player_command_finished_locked(pc); break; case PLAYER_COMMAND_NONE: player_wait(pc); break; } } } void player_create(struct player_control *pc) { assert(pc->thread == NULL); GError *e = NULL; pc->thread = g_thread_create(player_task, pc, true, &e); if (pc->thread == NULL) MPD_ERROR("Failed to spawn player task: %s", e->message); }
/** * Player lock must be held before calling. */ static void player_process_command(struct player *player) { struct player_control *pc = player->pc; G_GNUC_UNUSED struct decoder_control *dc = player->dc; switch (pc->command) { case PLAYER_COMMAND_NONE: case PLAYER_COMMAND_STOP: case PLAYER_COMMAND_EXIT: case PLAYER_COMMAND_CLOSE_AUDIO: break; case PLAYER_COMMAND_UPDATE_AUDIO: player_unlock(pc); audio_output_all_enable_disable(); player_lock(pc); player_command_finished_locked(pc); break; case PLAYER_COMMAND_QUEUE: assert(pc->next_song != NULL); assert(!player->queued); assert(!player_dc_at_next_song(player)); player->queued = true; player_command_finished_locked(pc); break; case PLAYER_COMMAND_PAUSE: player_unlock(pc); player->paused = !player->paused; if (player->paused) { audio_output_all_pause(); player_lock(pc); pc->state = PLAYER_STATE_PAUSE; } else if (!audio_format_defined(&player->play_audio_format)) { /* the decoder hasn't provided an audio format yet - don't open the audio device yet */ player_lock(pc); pc->state = PLAYER_STATE_PLAY; } else { player_open_output(player); player_lock(pc); } player_command_finished_locked(pc); break; case PLAYER_COMMAND_SEEK: player_unlock(pc); player_seek_decoder(player); player_lock(pc); break; case PLAYER_COMMAND_CANCEL: if (pc->next_song == NULL) { /* the cancel request arrived too late, we're already playing the queued song... stop everything now */ pc->command = PLAYER_COMMAND_STOP; return; } if (player_dc_at_next_song(player)) { /* the decoder is already decoding the song - stop it and reset the position */ player_unlock(pc); player_dc_stop(player); player_lock(pc); } song_free(pc->next_song); pc->next_song = NULL; player->queued = false; player_command_finished_locked(pc); break; case PLAYER_COMMAND_REFRESH: if (player->output_open && !player->paused) { player_unlock(pc); audio_output_all_check(); player_lock(pc); } pc->elapsed_time = audio_output_all_get_elapsed_time(); if (pc->elapsed_time < 0.0) pc->elapsed_time = player->elapsed_time; player_command_finished_locked(pc); break; } }
/** * This is the handler for the #PLAYER_COMMAND_SEEK command. * * The player lock is not held. */ static bool player_seek_decoder(struct player *player) { struct player_control *pc = player->pc; struct song *song = pc->next_song; struct decoder_control *dc = player->dc; assert(pc->next_song != NULL); const unsigned start_ms = song->start_ms; if (!decoder_lock_is_current_song(dc, song)) { /* the decoder is already decoding the "next" song - stop it and start the previous song again */ player_dc_stop(player); /* clear music chunks which might still reside in the pipe */ music_pipe_clear(player->pipe, player_buffer); /* re-start the decoder */ player_dc_start(player, player->pipe); if (!player_wait_for_decoder(player)) { /* decoder failure */ player_command_finished(pc); return false; } } else { if (!player_dc_at_current_song(player)) { /* the decoder is already decoding the "next" song, but it is the same song file; exchange the pipe */ music_pipe_clear(player->pipe, player_buffer); music_pipe_free(player->pipe); player->pipe = dc->pipe; } song_free(pc->next_song); pc->next_song = NULL; player->queued = false; } /* wait for the decoder to complete initialization */ while (player->decoder_starting) { if (!player_check_decoder_startup(player)) { /* decoder failure */ player_command_finished(pc); return false; } } /* send the SEEK command */ double where = pc->seek_where; if (where > pc->total_time) where = pc->total_time - 0.1; if (where < 0.0) where = 0.0; if (!dc_seek(dc, where + start_ms / 1000.0)) { /* decoder failure */ player_command_finished(pc); return false; } player->elapsed_time = where; player_command_finished(pc); player->xfade = XFADE_UNKNOWN; /* re-fill the buffer after seeking */ player->buffering = true; audio_output_all_cancel(); return true; }
int main(int argc, char **argv) { const char *uri; struct input_stream *is = NULL; bool success; GError *error = NULL; struct playlist_provider *playlist; struct song *song; if (argc != 3) { g_printerr("Usage: dump_playlist CONFIG URI\n"); return 1; } uri = argv[2]; /* initialize GLib */ g_thread_init(NULL); g_log_set_default_handler(my_log_func, NULL); /* initialize MPD */ config_global_init(); success = config_read_file(argv[1], &error); if (!success) { g_printerr("%s\n", error->message); g_error_free(error); return 1; } io_thread_init(); if (!io_thread_start(&error)) { g_warning("%s", error->message); g_error_free(error); return EXIT_FAILURE; } if (!input_stream_global_init(&error)) { g_warning("%s", error->message); g_error_free(error); return 2; } playlist_list_global_init(); decoder_plugin_init_all(); /* open the playlist */ GMutex *mutex = g_mutex_new(); GCond *cond = g_cond_new(); playlist = playlist_list_open_uri(uri, mutex, cond); if (playlist == NULL) { /* open the stream and wait until it becomes ready */ is = input_stream_open(uri, mutex, cond, &error); if (is == NULL) { if (error != NULL) { g_warning("%s", error->message); g_error_free(error); } else g_printerr("input_stream_open() failed\n"); return 2; } input_stream_lock_wait_ready(is); /* open the playlist */ playlist = playlist_list_open_stream(is, uri); if (playlist == NULL) { input_stream_close(is); g_printerr("Failed to open playlist\n"); return 2; } } /* dump the playlist */ while ((song = playlist_plugin_read(playlist)) != NULL) { g_print("%s\n", song->uri); if (song->end_ms > 0) g_print("range: %u:%02u..%u:%02u\n", song->start_ms / 60000, (song->start_ms / 1000) % 60, song->end_ms / 60000, (song->end_ms / 1000) % 60); else if (song->start_ms > 0) g_print("range: %u:%02u..\n", song->start_ms / 60000, (song->start_ms / 1000) % 60); if (song->tag != NULL) tag_save(stdout, song->tag); song_free(song); } /* deinitialize everything */ playlist_plugin_close(playlist); if (is != NULL) input_stream_close(is); g_cond_free(cond); g_mutex_free(mutex); decoder_plugin_deinit_all(); playlist_list_global_finish(); input_stream_global_finish(); io_thread_deinit(); config_global_finish(); return 0; }