diff options
Diffstat (limited to 'player/loadfile.c')
-rw-r--r-- | player/loadfile.c | 599 |
1 files changed, 369 insertions, 230 deletions
diff --git a/player/loadfile.c b/player/loadfile.c index f9dac4684d..a8dfda8c1c 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -23,7 +23,6 @@ #include <libavutil/avutil.h> -#include "config.h" #include "mpv_talloc.h" #include "misc/thread_pool.h" @@ -45,8 +44,9 @@ #include "options/m_property.h" #include "common/common.h" #include "common/encode.h" -#include "common/recorder.h" +#include "common/stats.h" #include "input/input.h" +#include "misc/language.h" #include "audio/out/ao.h" #include "filters/f_decoder_wrapper.h" @@ -74,7 +74,7 @@ void mp_abort_playback_async(struct MPContext *mpctx) { mp_cancel_trigger(mpctx->playback_abort); - pthread_mutex_lock(&mpctx->abort_lock); + mp_mutex_lock(&mpctx->abort_lock); for (int n = 0; n < mpctx->num_abort_list; n++) { struct mp_abort_entry *abort = mpctx->abort_list[n]; @@ -82,25 +82,25 @@ void mp_abort_playback_async(struct MPContext *mpctx) mp_abort_trigger_locked(mpctx, abort); } - pthread_mutex_unlock(&mpctx->abort_lock); + mp_mutex_unlock(&mpctx->abort_lock); } // Add it to the global list, and allocate required data structures. void mp_abort_add(struct MPContext *mpctx, struct mp_abort_entry *abort) { - pthread_mutex_lock(&mpctx->abort_lock); + mp_mutex_lock(&mpctx->abort_lock); assert(!abort->cancel); abort->cancel = mp_cancel_new(NULL); MP_TARRAY_APPEND(NULL, mpctx->abort_list, mpctx->num_abort_list, abort); mp_abort_recheck_locked(mpctx, abort); - pthread_mutex_unlock(&mpctx->abort_lock); + mp_mutex_unlock(&mpctx->abort_lock); } // Remove Add it to the global list, and free/clear required data structures. // Does not deallocate the abort value itself. void mp_abort_remove(struct MPContext *mpctx, struct mp_abort_entry *abort) { - pthread_mutex_lock(&mpctx->abort_lock); + mp_mutex_lock(&mpctx->abort_lock); for (int n = 0; n < mpctx->num_abort_list; n++) { if (mpctx->abort_list[n] == abort) { MP_TARRAY_REMOVE_AT(mpctx->abort_list, mpctx->num_abort_list, n); @@ -110,7 +110,7 @@ void mp_abort_remove(struct MPContext *mpctx, struct mp_abort_entry *abort) } } assert(!abort); // should have been in the list - pthread_mutex_unlock(&mpctx->abort_lock); + mp_mutex_unlock(&mpctx->abort_lock); } // Verify whether the abort needs to be signaled after changing certain fields @@ -192,11 +192,10 @@ static void kill_demuxers_reentrant(struct MPContext *mpctx, static void uninit_demuxer(struct MPContext *mpctx) { - for (int r = 0; r < NUM_PTRACKS; r++) { - for (int t = 0; t < STREAM_TYPE_COUNT; t++) + for (int t = 0; t < STREAM_TYPE_COUNT; t++) { + for (int r = 0; r < num_ptracks[t]; r++) mpctx->current_track[r][t] = NULL; } - mpctx->seek_slave = NULL; talloc_free(mpctx->chapters); mpctx->chapters = NULL; @@ -217,7 +216,6 @@ static void uninit_demuxer(struct MPContext *mpctx) assert(!track->dec && !track->d_sub); assert(!track->vo_c && !track->ao_c); assert(!track->sink); - assert(!track->remux_sink); // Demuxers can be added in any order (if they appear mid-stream), and // we can't know which tracks uses which, so here's some O(n^2) trash. @@ -258,7 +256,13 @@ static void print_stream(struct MPContext *mpctx, struct track *t) break; } char b[2048] = {0}; - APPEND(b, " %3s %-5s", t->selected ? "(+)" : "", tname); + bool forced_only = false; + if (t->type == STREAM_SUB) { + bool forced_opt = mpctx->opts->subs_rend->sub_forced_events_only; + if (forced_opt) + forced_only = t->selected; + } + APPEND(b, " %3s %-5s", t->selected ? (forced_only ? "(*)" : "(+)") : "", tname); APPEND(b, " --%s=%d", selopt, t->user_tid); if (t->lang && langopt) APPEND(b, " --%s=%s", langopt, t->lang); @@ -268,10 +272,14 @@ static void print_stream(struct MPContext *mpctx, struct track *t) APPEND(b, " (f)"); if (t->attached_picture) APPEND(b, " [P]"); + if (forced_only) + APPEND(b, " [F]"); if (t->title) APPEND(b, " '%s'", t->title); const char *codec = s ? s->codec->codec : NULL; APPEND(b, " (%s", codec ? codec : "<unknown>"); + if (s && s->codec->codec_profile) + APPEND(b, " [%s]", s->codec->codec_profile); if (t->type == STREAM_VIDEO) { if (s && s->codec->disp_w) APPEND(b, " %dx%d", s->codec->disp_w, s->codec->disp_h); @@ -284,6 +292,8 @@ static void print_stream(struct MPContext *mpctx, struct track *t) APPEND(b, " %dHz", s->codec->samplerate); } APPEND(b, ")"); + if (s && s->hls_bitrate > 0) + APPEND(b, " (%d kbps)", (s->hls_bitrate + 500) / 1000); if (t->is_external) APPEND(b, " (external)"); MP_INFO(mpctx, "%s\n", b); @@ -351,7 +361,7 @@ void update_demuxer_properties(struct MPContext *mpctx) } talloc_free(mpctx->filtered_tags); mpctx->filtered_tags = info; - mp_notify(mpctx, MPV_EVENT_METADATA_UPDATE, NULL); + mp_notify(mpctx, MP_EVENT_METADATA_UPDATE, NULL); } if (events & DEMUX_EVENT_DURATION) mp_notify(mpctx, MP_EVENT_DURATION_UPDATE, NULL); @@ -360,7 +370,9 @@ void update_demuxer_properties(struct MPContext *mpctx) // Enables or disables the stream for the given track, according to // track->selected. -void reselect_demux_stream(struct MPContext *mpctx, struct track *track) +// With refresh_only=true, refreshes the stream if it's enabled. +void reselect_demux_stream(struct MPContext *mpctx, struct track *track, + bool refresh_only) { if (!track->stream) return; @@ -370,9 +382,10 @@ void reselect_demux_stream(struct MPContext *mpctx, struct track *track) if (track->type == STREAM_SUB) pts -= 10.0; } - demuxer_select_track(track->demuxer, track->stream, pts, track->selected); - if (track == mpctx->seek_slave) - mpctx->seek_slave = NULL; + if (refresh_only) + demuxer_refresh_track(track->demuxer, track->stream, pts); + else + demuxer_select_track(track->demuxer, track->stream, pts, track->selected); } static void enable_demux_thread(struct MPContext *mpctx, struct demuxer *demux) @@ -410,12 +423,15 @@ static struct track *add_stream_track(struct MPContext *mpctx, .user_tid = find_new_tid(mpctx, stream->type), .demuxer_id = stream->demuxer_id, .ff_index = stream->ff_index, + .hls_bitrate = stream->hls_bitrate, + .program_id = stream->program_id, .title = stream->title, .default_track = stream->default_track, .forced_track = stream->forced_track, .dependent_track = stream->dependent_track, .visual_impaired_track = stream->visual_impaired_track, .hearing_impaired_track = stream->hearing_impaired_track, + .image = stream->image, .attached_picture = stream->attached_picture != NULL, .lang = stream->lang, .demuxer = demuxer, @@ -423,7 +439,7 @@ static struct track *add_stream_track(struct MPContext *mpctx, }; MP_TARRAY_APPEND(mpctx, mpctx->tracks, mpctx->num_tracks, track); - mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL); + mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL); return track; } @@ -435,11 +451,14 @@ void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer) } // Result numerically higher => better match. 0 == no match. -static int match_lang(char **langs, char *lang) +static int match_lang(char **langs, const char *lang) { + if (!lang) + return 0; for (int idx = 0; langs && langs[idx]; idx++) { - if (lang && strcasecmp(langs[idx], lang) == 0) - return INT_MAX - idx; + int score = mp_match_lang_single(langs[idx], lang); + if (score > 0) + return INT_MAX - (idx + 1) * LANGUAGE_SCORE_MAX + score - 1; } return 0; } @@ -448,39 +467,56 @@ static int match_lang(char **langs, char *lang) * tid is the track ID requested by the user (-2: deselect, -1: default) * lang is a string list, NULL is same as empty list * Sort tracks based on the following criteria, and pick the first: - * 0a) track matches ff-index (always wins) - * 0b) track matches tid (almost always wins) - * 0c) track is not from --external-file + *0a) track matches tid (always wins) + * 0b) track is not from --external-file * 1) track is external (no_default cancels this) * 1b) track was passed explicitly (is not an auto-loaded subtitle) - * 2) earlier match in lang list - * 3a) track is marked forced - * 3b) track is marked default + * 1c) track matches the program ID of the video + * 2) earlier match in lang list but not if we're using os_langs + * 3a) track is marked forced and we're preferring forced tracks + * 3b) track is marked non-forced and we're preferring non-forced tracks + * 3c) track is marked default + * 3d) match in lang list with os_langs * 4) attached picture, HLS bitrate * 5) lower track number * If select_fallback is not set, 5) is only used to determine whether a * matching track is preferred over another track. Otherwise, always pick a * track (if nothing else matches, return the track with lowest ID). + * Forced tracks are preferred when the user prefers not to display subtitles */ // Return whether t1 is preferred over t2 -static bool compare_track(struct track *t1, struct track *t2, char **langs, - struct MPOpts *opts) +static bool compare_track(struct track *t1, struct track *t2, char **langs, bool os_langs, + bool forced, struct MPOpts *opts, int preferred_program) { + bool sub = t2->type == STREAM_SUB; if (!opts->autoload_files && t1->is_external != t2->is_external) return !t1->is_external; bool ext1 = t1->is_external && !t1->no_default; bool ext2 = t2->is_external && !t2->no_default; - if (ext1 != ext2) + if (ext1 != ext2) { + if (t1->attached_picture && t2->attached_picture + && opts->audio_display == 1) + return !ext1; return ext1; + } if (t1->auto_loaded != t2->auto_loaded) return !t1->auto_loaded; + if (preferred_program != -1 && t1->program_id != -1 && t2->program_id != -1) { + if ((t1->program_id == preferred_program) != + (t2->program_id == preferred_program)) + return t1->program_id == preferred_program; + } int l1 = match_lang(langs, t1->lang), l2 = match_lang(langs, t2->lang); - if (l1 != l2) + if (!os_langs && l1 != l2) return l1 > l2; - if (t1->forced_track != t2->forced_track) + if (forced) return t1->forced_track; - if (t1->default_track != t2->default_track) + if (t1->default_track != t2->default_track && !t2->forced_select) return t1->default_track; + if (sub && !t2->forced_select && t2->forced_track) + return !t1->forced_track; + if (os_langs && l1 != l2) + return l1 > l2; if (t1->attached_picture != t2->attached_picture) return !t1->attached_picture; if (t1->stream && t2->stream && opts->hls_bitrate >= 0 && @@ -507,37 +543,133 @@ static bool duplicate_track(struct MPContext *mpctx, int order, return false; } +static bool append_lang(size_t *nb, char ***out, char *in) +{ + if (!in) + return false; + MP_TARRAY_GROW(NULL, *out, *nb + 1); + (*out)[(*nb)++] = in; + (*out)[*nb] = NULL; + ta_set_parent(in, *out); + return true; +} + +static char **add_os_langs(void) +{ + size_t nb = 0; + char **out = NULL; + char **autos = mp_get_user_langs(); + for (int i = 0; autos && autos[i]; i++) { + if (!append_lang(&nb, &out, autos[i])) + goto cleanup; + } + +cleanup: + talloc_free(autos); + return out; +} + +static char **process_langs(char **in) +{ + size_t nb = 0; + char **out = NULL; + for (int i = 0; in && in[i]; i++) { + if (!append_lang(&nb, &out, talloc_strdup(NULL, in[i]))) + break; + } + return out; +} + +static const char *get_audio_lang(struct MPContext *mpctx) +{ + // If we have a single current audio track, this is simple. + if (mpctx->current_track[0][STREAM_AUDIO]) + return mpctx->current_track[0][STREAM_AUDIO]->lang; + + const char *ret = NULL; + + // Otherwise, we may be using a filter with multiple inputs. + // Iterate over the tracks and find the ones in use. + for (int i = 0; i < mpctx->num_tracks; i++) { + const struct track *t = mpctx->tracks[i]; + if (t->type != STREAM_AUDIO || !t->selected) + continue; + + // If we have input in multiple audio languages, bail out; + // we don't have a meaningful single language. + // Partial matches (e.g. en-US vs en-GB) are acceptable here. + if (ret && t->lang && !mp_match_lang_single(t->lang, ret)) + return NULL; + + // We'll return the first non-null tag we see + if (!ret) + ret = t->lang; + } + + return ret; +} + struct track *select_default_track(struct MPContext *mpctx, int order, enum stream_type type) { struct MPOpts *opts = mpctx->opts; int tid = opts->stream_id[order][type]; - char **langs = opts->stream_lang[type]; + int preferred_program = (type != STREAM_VIDEO && mpctx->current_track[0][STREAM_VIDEO]) ? + mpctx->current_track[0][STREAM_VIDEO]->program_id : -1; if (tid == -2) return NULL; - bool select_fallback = type == STREAM_VIDEO || type == STREAM_AUDIO; + char **langs = process_langs(opts->stream_lang[type]); + bool os_langs = false; + // Try to add OS languages if enabled by the user and we don't already have a lang from slang. + if (type == STREAM_SUB && (!langs || !strcmp(langs[0], "")) && opts->subs_match_os_language) { + talloc_free(langs); + langs = add_os_langs(); + os_langs = true; + } + const char *audio_lang = get_audio_lang(mpctx); + bool sub = type == STREAM_SUB; struct track *pick = NULL; for (int n = 0; n < mpctx->num_tracks; n++) { struct track *track = mpctx->tracks[n]; if (track->type != type) continue; - if (track->user_tid == tid) - return track; + if (track->user_tid == tid) { + pick = track; + goto cleanup; + } + if (tid >= 0) + continue; if (track->no_auto_select) continue; if (duplicate_track(mpctx, order, type, track)) continue; - if (!pick || compare_track(track, pick, langs, mpctx->opts)) + if (sub) { + // Subtitle specific auto-selecting crap. + bool audio_matches = mp_match_lang_single(audio_lang, track->lang); + bool forced = track->forced_track && (opts->subs_fallback_forced == 2 || + (audio_matches && opts->subs_fallback_forced == 1)); + bool lang_match = !os_langs && match_lang(langs, track->lang) > 0; + bool subs_fallback = (track->is_external && !track->no_default) || opts->subs_fallback == 2 || + (opts->subs_fallback == 1 && track->default_track); + bool subs_matching_audio = (!match_lang(langs, audio_lang) || opts->subs_with_matching_audio == 2 || + (opts->subs_with_matching_audio == 1 && track->forced_track)); + if (subs_matching_audio && ((!pick && (forced || lang_match || subs_fallback)) || + (pick && compare_track(track, pick, langs, os_langs, forced, mpctx->opts, preferred_program)))) + { + pick = track; + pick->forced_select = forced; + } + } else if (!pick || compare_track(track, pick, langs, os_langs, false, mpctx->opts, preferred_program)) { pick = track; + } } - if (pick && !select_fallback && !(pick->is_external && !pick->no_default) - && !match_lang(langs, pick->lang) && !pick->default_track - && !pick->forced_track) - pick = NULL; + if (pick && pick->attached_picture && !mpctx->opts->audio_display) pick = NULL; if (pick && !opts->autoload_files && pick->is_external) pick = NULL; +cleanup: + talloc_free(langs); return pick; } @@ -571,12 +703,9 @@ static void check_previous_track_selection(struct MPContext *mpctx) // Reset selection, but only if they're not "auto" or "off". The // defaults are -1 (default selection), or -2 (off) for secondary tracks. for (int t = 0; t < STREAM_TYPE_COUNT; t++) { - for (int i = 0; i < NUM_PTRACKS; i++) { - if (opts->stream_id[i][t] >= 0) { - opts->stream_id[i][t] = i == 0 ? -1 : -2; - m_config_notify_change_opt_ptr(mpctx->mconfig, - &opts->stream_id[i][t]); - } + for (int i = 0; i < num_ptracks[t]; i++) { + if (opts->stream_id[i][t] >= 0) + mark_track_selection(mpctx, i, t, i == 0 ? -1 : -2); } } talloc_free(mpctx->track_layout_hash); @@ -585,19 +714,27 @@ static void check_previous_track_selection(struct MPContext *mpctx) talloc_free(h); } +// Update the matching track selection user option to the given value. +void mark_track_selection(struct MPContext *mpctx, int order, + enum stream_type type, int value) +{ + assert(order >= 0 && order < num_ptracks[type]); + mpctx->opts->stream_id[order][type] = value; + m_config_notify_change_opt_ptr(mpctx->mconfig, + &mpctx->opts->stream_id[order][type]); +} + void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type, struct track *track, int flags) { assert(!track || track->type == type); - assert(order >= 0 && order < NUM_PTRACKS); + assert(type >= 0 && type < STREAM_TYPE_COUNT); + assert(order >= 0 && order < num_ptracks[type]); // Mark the current track selection as explicitly user-requested. (This is // different from auto-selection or disabling a track due to errors.) - if (flags & FLAG_MARK_SELECTION) { - mpctx->opts->stream_id[order][type] = track ? track->user_tid : -2; - m_config_notify_change_opt_ptr(mpctx->mconfig, - &mpctx->opts->stream_id[order][type]); - } + if (flags & FLAG_MARK_SELECTION) + mark_track_selection(mpctx, order, type, track ? track->user_tid : -2); // No decoder should be initialized yet. if (!mpctx->demuxer) @@ -609,19 +746,19 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type if (current && current->sink) { MP_ERR(mpctx, "Can't disable input to complex filter.\n"); - return; + goto error; } if ((type == STREAM_VIDEO && mpctx->vo_chain && !mpctx->vo_chain->track) || (type == STREAM_AUDIO && mpctx->ao_chain && !mpctx->ao_chain->track)) { MP_ERR(mpctx, "Can't switch away from complex filter output.\n"); - return; + goto error; } if (track && track->selected) { // Track has been selected in a different order parameter. MP_ERR(mpctx, "Track %d is already selected.\n", track->user_tid); - return; + goto error; } if (order == 0) { @@ -632,24 +769,23 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type } else if (type == STREAM_AUDIO) { clear_audio_output_buffers(mpctx); uninit_audio_chain(mpctx); - uninit_audio_out(mpctx); + if (!track) + uninit_audio_out(mpctx); } } if (type == STREAM_SUB) uninit_sub(mpctx, current); if (current) { - if (current->remux_sink) - close_recorder_and_error(mpctx); current->selected = false; - reselect_demux_stream(mpctx, current); + reselect_demux_stream(mpctx, current, false); } mpctx->current_track[order][type] = track; if (track) { track->selected = true; - reselect_demux_stream(mpctx, track); + reselect_demux_stream(mpctx, track, false); } if (type == STREAM_VIDEO && order == 0) { @@ -660,11 +796,15 @@ void mp_switch_track_n(struct MPContext *mpctx, int order, enum stream_type type reinit_sub(mpctx, track); } - mp_notify(mpctx, MPV_EVENT_TRACK_SWITCHED, NULL); + mp_notify(mpctx, MP_EVENT_TRACK_SWITCHED, NULL); mp_wakeup_core(mpctx); talloc_free(mpctx->track_layout_hash); mpctx->track_layout_hash = talloc_steal(mpctx, track_layout_hash(mpctx)); + + return; +error: + mark_track_selection(mpctx, order, type, -1); } void mp_switch_track(struct MPContext *mpctx, enum stream_type type, @@ -676,8 +816,12 @@ void mp_switch_track(struct MPContext *mpctx, enum stream_type type, void mp_deselect_track(struct MPContext *mpctx, struct track *track) { if (track && track->selected) { - for (int t = 0; t < NUM_PTRACKS; t++) + for (int t = 0; t < num_ptracks[track->type]; t++) { + if (mpctx->current_track[t][track->type] != track) + continue; mp_switch_track_n(mpctx, t, track->type, NULL, 0); + mark_track_selection(mpctx, t, track->type, -1); // default + } } } @@ -705,9 +849,6 @@ bool mp_remove_track(struct MPContext *mpctx, struct track *track) struct demuxer *d = track->demuxer; - if (mpctx->seek_slave == track) - mpctx->seek_slave = NULL; - int index = 0; while (index < mpctx->num_tracks && mpctx->tracks[index] != track) index++; @@ -723,7 +864,7 @@ bool mp_remove_track(struct MPContext *mpctx, struct track *track) if (!in_use) demux_cancel_and_free(d); - mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL); + mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL); return true; } @@ -734,7 +875,8 @@ bool mp_remove_track(struct MPContext *mpctx, struct track *track) // cancel will generally be used to abort the loading process, but on success // the demuxer is changed to be slaved to mpctx->playback_abort instead. int mp_add_external_file(struct MPContext *mpctx, char *filename, - enum stream_type filter, struct mp_cancel *cancel) + enum stream_type filter, struct mp_cancel *cancel, + bool cover_art) { struct MPOpts *opts = mpctx->opts; if (!filename || mp_cancel_test(cancel)) @@ -808,6 +950,8 @@ int mp_add_external_file(struct MPContext *mpctx, char *filename, t->external_filename = talloc_strdup(t, filename); t->no_default = sh->type != filter; t->no_auto_select = t->no_default; + // if we found video, and we are loading cover art, flag as such. + t->attached_picture = t->type == STREAM_VIDEO && cover_art; if (first_num < 0 && (filter == STREAM_TYPE_COUNT || sh->type == filter)) first_num = mpctx->num_tracks - 1; } @@ -832,7 +976,9 @@ static void open_external_files(struct MPContext *mpctx, char **files, files = mp_dup_str_array(tmp, files); for (int n = 0; files && files[n]; n++) - mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort); + // when given filter is set to video, we are loading up cover art + mp_add_external_file(mpctx, files[n], filter, mpctx->playback_abort, + filter == STREAM_VIDEO); talloc_free(tmp); } @@ -840,14 +986,15 @@ static void open_external_files(struct MPContext *mpctx, char **files, // See mp_add_external_file() for meaning of cancel parameter. void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel) { - if (mpctx->opts->sub_auto < 0 && mpctx->opts->audiofile_auto < 0) + struct MPOpts *opts = mpctx->opts; + + if (opts->sub_auto < 0 && opts->audiofile_auto < 0 && opts->coverart_auto < 0) return; - if (!mpctx->opts->autoload_files || strcmp(mpctx->filename, "-") == 0) + if (!opts->autoload_files || strcmp(mpctx->filename, "-") == 0) return; void *tmp = talloc_new(NULL); - struct subfn *list = find_external_files(mpctx->global, mpctx->filename, - mpctx->opts); + struct subfn *list = find_external_files(mpctx->global, mpctx->filename, opts); talloc_steal(tmp, list); int sc[STREAM_TYPE_COUNT] = {0}; @@ -857,18 +1004,23 @@ void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel) } for (int i = 0; list && list[i].fname; i++) { - char *filename = list[i].fname; - char *lang = list[i].lang; + struct subfn *e = &list[i]; + for (int n = 0; n < mpctx->num_tracks; n++) { struct track *t = mpctx->tracks[n]; - if (t->demuxer && strcmp(t->demuxer->filename, filename) == 0) + if (t->demuxer && strcmp(t->demuxer->filename, e->fname) == 0) goto skip; } - if (list[i].type == STREAM_SUB && !sc[STREAM_VIDEO] && !sc[STREAM_AUDIO]) + if (e->type == STREAM_SUB && !sc[STREAM_VIDEO] && !sc[STREAM_AUDIO]) + goto skip; + if (e->type == STREAM_AUDIO && !sc[STREAM_VIDEO]) goto skip; - if (list[i].type == STREAM_AUDIO && !sc[STREAM_VIDEO]) + if (e->type == STREAM_VIDEO && (sc[STREAM_VIDEO] || !sc[STREAM_AUDIO])) goto skip; - int first = mp_add_external_file(mpctx, filename, list[i].type, cancel); + + // when given filter is set to video, we are loading up cover art + int first = mp_add_external_file(mpctx, e->fname, e->type, cancel, + e->type == STREAM_VIDEO); if (first < 0) goto skip; @@ -876,7 +1028,7 @@ void autoload_external_files(struct MPContext *mpctx, struct mp_cancel *cancel) struct track *t = mpctx->tracks[n]; t->auto_loaded = true; if (!t->lang) - t->lang = talloc_strdup(t, lang); + t->lang = talloc_strdup(t, e->lang); } skip:; } @@ -910,14 +1062,14 @@ void prepare_playlist(struct MPContext *mpctx, struct playlist *pl) // Replace the current playlist entry with playlist contents. Moves the entries // from the given playlist pl, so the entries don't actually need to be copied. -static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl) +static void transfer_playlist(struct MPContext *mpctx, struct playlist *pl, + int64_t *start_id, int *num_new_entries) { if (pl->num_entries) { prepare_playlist(mpctx, pl); struct playlist_entry *new = pl->current; - if (mpctx->playlist->current) - playlist_add_redirect(pl, mpctx->playlist->current->filename); - playlist_transfer_entries(mpctx->playlist, pl); + *num_new_entries = pl->num_entries; + *start_id = playlist_transfer_entries(mpctx->playlist, pl); // current entry is replaced if (mpctx->playlist->current) playlist_remove(mpctx->playlist, mpctx->playlist->current); @@ -935,7 +1087,8 @@ static void process_hooks(struct MPContext *mpctx, char *name) while (!mp_hook_test_completion(mpctx, name)) { mp_idle(mpctx); - // We have no idea what blocks a hook, so just do a full abort. + // We have no idea what blocks a hook, so just do a full abort. This + // does nothing for hooks that happen outside of playback. if (mpctx->stop_play) mp_abort_playback_async(mpctx); } @@ -986,11 +1139,11 @@ static void load_per_file_options(m_config_t *conf, } } -static void *open_demux_thread(void *ctx) +static MP_THREAD_VOID open_demux_thread(void *ctx) { struct MPContext *mpctx = ctx; - mpthread_set_name("opener"); + mp_thread_set_name("opener"); struct demuxer_params p = { .force_format = mpctx->open_format, @@ -1028,7 +1181,7 @@ static void *open_demux_thread(void *ctx) atomic_store(&mpctx->open_done, true); mp_wakeup_core(mpctx); - return NULL; + MP_THREAD_RETURN(); } static void cancel_open(struct MPContext *mpctx) @@ -1037,7 +1190,7 @@ static void cancel_open(struct MPContext *mpctx) mp_cancel_trigger(mpctx->open_cancel); if (mpctx->open_active) - pthread_join(mpctx->open_thread, NULL); + mp_thread_join(mpctx->open_thread); mpctx->open_active = false; if (mpctx->open_res_demuxer) @@ -1068,7 +1221,7 @@ static void start_open(struct MPContext *mpctx, char *url, int url_flags, mpctx->open_url_flags = url_flags; mpctx->open_for_prefetch = for_prefetch && mpctx->opts->demuxer_thread; - if (pthread_create(&mpctx->open_thread, NULL, open_demux_thread, mpctx)) { + if (mp_thread_create(&mpctx->open_thread, open_demux_thread, mpctx)) { cancel_open(mpctx); return; } @@ -1129,13 +1282,35 @@ void prefetch_next(struct MPContext *mpctx) if (!mpctx->opts->prefetch_open) return; - struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false, false); + struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false); if (new_entry && !mpctx->open_active && new_entry->filename) { MP_VERBOSE(mpctx, "Prefetching: %s\n", new_entry->filename); start_open(mpctx, new_entry->filename, new_entry->stream_flags, true); } } +static void clear_playlist_paths(struct MPContext *mpctx) +{ + TA_FREEP(&mpctx->playlist_paths); + mpctx->playlist_paths_len = 0; +} + +static bool infinite_playlist_loading_loop(struct MPContext *mpctx, struct playlist *pl) +{ + if (pl->num_entries) { + struct playlist_entry *e = pl->entries[0]; + for (int n = 0; n < mpctx->playlist_paths_len; n++) { + if (strcmp(mpctx->playlist_paths[n], e->filename) == 0) { + clear_playlist_paths(mpctx); + return true; + } + } + } + MP_TARRAY_APPEND(mpctx, mpctx->playlist_paths, mpctx->playlist_paths_len, + talloc_strdup(mpctx->playlist_paths, mpctx->filename)); + return false; +} + // Destroy the complex filter, and remove the references to the filter pads. // (Call cleanup_deassociated_complex_filters() to close decoders/VO/AO // that are not connected anymore due to this.) @@ -1214,7 +1389,7 @@ static int reinit_complex_filters(struct MPContext *mpctx, bool force_uninit) } struct mp_lavfi *l = - mp_lavfi_create_graph(mpctx->filter_root, 0, false, NULL, graph); + mp_lavfi_create_graph(mpctx->filter_root, 0, false, NULL, NULL, graph); if (!l) goto done; mpctx->lavfi = l->f; @@ -1312,10 +1487,10 @@ done: if (mpctx->playback_initialized) { for (int n = 0; n < mpctx->num_tracks; n++) - reselect_demux_stream(mpctx, mpctx->tracks[n]); + reselect_demux_stream(mpctx, mpctx->tracks[n], false); } - mp_notify(mpctx, MPV_EVENT_TRACKS_CHANGED, NULL); + mp_notify(mpctx, MP_EVENT_TRACKS_CHANGED, NULL); return success ? 1 : -1; } @@ -1342,6 +1517,7 @@ static void load_external_opts_thread(void *p) load_chapters(mpctx); open_external_files(mpctx, mpctx->opts->audio_files, STREAM_AUDIO); open_external_files(mpctx, mpctx->opts->sub_name, STREAM_SUB); + open_external_files(mpctx, mpctx->opts->coverart_files, STREAM_VIDEO); open_external_files(mpctx, mpctx->opts->external_files, STREAM_TYPE_COUNT); autoload_external_files(mpctx, mpctx->playback_abort); @@ -1375,22 +1551,31 @@ static void load_external_opts(struct MPContext *mpctx) static void play_current_file(struct MPContext *mpctx) { struct MPOpts *opts = mpctx->opts; - double playback_start = -1e100; assert(mpctx->stop_play); + mpctx->stop_play = 0; - mp_notify(mpctx, MPV_EVENT_START_FILE, NULL); + process_hooks(mpctx, "on_before_start_file"); + if (mpctx->stop_play || !mpctx->playlist->current) + return; + + mpv_event_start_file start_event = { + .playlist_entry_id = mpctx->playlist->current->id, + }; + mpv_event_end_file end_event = { + .playlist_entry_id = start_event.playlist_entry_id, + }; + + mp_notify(mpctx, MPV_EVENT_START_FILE, &start_event); mp_cancel_reset(mpctx->playback_abort); mpctx->error_playing = MPV_ERROR_LOADING_FAILED; - mpctx->stop_play = 0; mpctx->filename = NULL; mpctx->shown_aframes = 0; mpctx->shown_vframes = 0; - mpctx->last_vo_pts = MP_NOPTS_VALUE; mpctx->last_chapter_seek = -2; - mpctx->last_chapter_pts = MP_NOPTS_VALUE; + mpctx->last_chapter_flag = false; mpctx->last_chapter = -2; mpctx->paused = false; mpctx->playing_msg_shown = false; @@ -1403,13 +1588,14 @@ static void play_current_file(struct MPContext *mpctx) mpctx->last_seek_pts = 0.0; mpctx->seek = (struct seek_params){ 0 }; mpctx->filter_root = mp_filter_create_root(mpctx->global); - mp_filter_root_set_wakeup_cb(mpctx->filter_root, mp_wakeup_core_cb, mpctx); + mp_filter_graph_set_wakeup_cb(mpctx->filter_root, mp_wakeup_core_cb, mpctx); + mp_filter_graph_set_max_run_time(mpctx->filter_root, 0.1); reset_playback_state(mpctx); mpctx->playing = mpctx->playlist->current; - if (!mpctx->playing || !mpctx->playing->filename) - goto terminate_playback; + assert(mpctx->playing); + assert(mpctx->playing->filename); mpctx->playing->reserved += 1; mpctx->filename = talloc_strdup(NULL, mpctx->playing->filename); @@ -1432,7 +1618,7 @@ static void play_current_file(struct MPContext *mpctx) mp_load_auto_profiles(mpctx); - mp_load_playback_resume(mpctx, mpctx->filename); + bool watch_later = mp_load_playback_resume(mpctx, mpctx->filename); load_per_file_options(mpctx->mconfig, mpctx->playing->params, mpctx->playing->num_params); @@ -1442,7 +1628,7 @@ static void play_current_file(struct MPContext *mpctx) handle_force_window(mpctx, false); if (mpctx->playlist->num_entries > 1 || - mpctx->playing->num_redirects) + mpctx->playing->playlist_path) MP_INFO(mpctx, "Playing: %s\n", mpctx->filename); assert(mpctx->demuxer == NULL); @@ -1471,8 +1657,17 @@ static void play_current_file(struct MPContext *mpctx) goto terminate_playback; if (mpctx->demuxer->playlist) { + if (watch_later) + mp_delete_watch_later_conf(mpctx, mpctx->filename); struct playlist *pl = mpctx->demuxer->playlist; - transfer_playlist(mpctx, pl); + playlist_populate_playlist_path(pl, mpctx->filename); + if (infinite_playlist_loading_loop(mpctx, pl)) { + mpctx->stop_play = PT_STOP; + MP_ERR(mpctx, "Infinite playlist loading loop detected.\n"); + goto terminate_playback; + } + transfer_playlist(mpctx, pl, &end_event.playlist_insert_id, + &end_event.playlist_insert_num_entries); mp_notify_property(mpctx, "playlist"); mpctx->error_playing = 2; goto terminate_playback; @@ -1497,9 +1692,8 @@ static void play_current_file(struct MPContext *mpctx) if (reinit_complex_filters(mpctx, false) < 0) goto terminate_playback; - assert(NUM_PTRACKS == 2); // opts->stream_id is hardcoded to 2 for (int t = 0; t < STREAM_TYPE_COUNT; t++) { - for (int i = 0; i < NUM_PTRACKS; i++) { + for (int i = 0; i < num_ptracks[t]; i++) { struct track *sel = NULL; bool taken = (t == STREAM_VIDEO && mpctx->vo_chain) || (t == STREAM_AUDIO && mpctx->ao_chain); @@ -1509,22 +1703,38 @@ static void play_current_file(struct MPContext *mpctx) } } for (int t = 0; t < STREAM_TYPE_COUNT; t++) { - for (int i = 0; i < |