summaryrefslogtreecommitdiffstats
path: root/player/loadfile.c
diff options
context:
space:
mode:
Diffstat (limited to 'player/loadfile.c')
-rw-r--r--player/loadfile.c599
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 <