diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst index a812fb088c..079672533f 100644 --- a/DOCS/man/options.rst +++ b/DOCS/man/options.rst @@ -2737,6 +2737,27 @@ Demuxer (This value tends to be fuzzy, because many file formats don't store linear timestamps.) +``--prefetch-playlist=`` + Prefetch next playlist entry while playback of the current entry is ending + (default: no). This merely opens the URL of the next playlist entry as soon + as the current URL is fully read. + + This does **not** work with URLs resolved by the ``youtube-dl`` wrapper, + and it won't. + + This does not affect HLS (``.m3u8`` URLs) - HLS prefetching depends on the + demuxer cache settings and is on by default. + + This can give subtly wrong results if per-file options are used, or if + options are changed in the time window between prefetching start and next + file played. + + This can occasionally make wrong prefetching decisions. For example, it + can't predict whether you go backwards in the playlist, and assumes you + won't edit the playlist. + + Highly experimental. + ``--force-seekable=`` If the player thinks that the media is not seekable (e.g. playing from a pipe, or it's an http stream with a server that doesn't support range diff --git a/options/options.c b/options/options.c index bc9ddf8614..6afe611c6d 100644 --- a/options/options.c +++ b/options/options.c @@ -383,6 +383,7 @@ const m_option_t mp_opts[] = { OPT_STRING("audio-demuxer", audio_demuxer_name, 0), OPT_STRING("sub-demuxer", sub_demuxer_name, 0), OPT_FLAG("demuxer-thread", demuxer_thread, 0), + OPT_FLAG("prefetch-playlist", prefetch_open, 0), OPT_FLAG("cache-pause", cache_pausing, 0), OPT_DOUBLE("mf-fps", mf_fps, 0), diff --git a/options/options.h b/options/options.h index 1d79d55c31..d3b0ace24f 100644 --- a/options/options.h +++ b/options/options.h @@ -219,6 +219,7 @@ typedef struct MPOpts { char **audio_files; char *demuxer_name; int demuxer_thread; + int prefetch_open; char *audio_demuxer_name; char *sub_demuxer_name; diff --git a/player/command.c b/player/command.c index 1beca6158a..7c3e269e9c 100644 --- a/player/command.c +++ b/player/command.c @@ -4959,7 +4959,7 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re int dir = cmd->id == MP_CMD_PLAYLIST_PREV ? -1 : +1; int force = cmd->args[0].v.i; - struct playlist_entry *e = mp_next_file(mpctx, dir, force); + struct playlist_entry *e = mp_next_file(mpctx, dir, force, true); if (!e && !force) return -1; mp_set_playlist_entry(mpctx, e); diff --git a/player/core.h b/player/core.h index ad61500741..4767830bc9 100644 --- a/player/core.h +++ b/player/core.h @@ -435,6 +435,21 @@ typedef struct MPContext { // --- The following fields are protected by lock struct mp_cancel *demuxer_cancel; // cancel handle for MPContext.demuxer + + // --- Owned by MPContext + pthread_t open_thread; + bool open_active; // open_thread is a valid thread handle, all setup + atomic_bool open_done; + // --- All fields below are immutable while open_active is true. + // Otherwise, they're owned by MPContext. + struct mp_cancel *open_cancel; + char *open_url; + char *open_format; + int open_url_flags; + // --- All fields below are owned by open_thread, unless open_done was set + // to true. + struct demuxer *open_res_demuxer; + int open_res_error; } MPContext; // audio.c @@ -480,7 +495,7 @@ struct track *mp_track_by_tid(struct MPContext *mpctx, enum stream_type type, void add_demuxer_tracks(struct MPContext *mpctx, struct demuxer *demuxer); bool mp_remove_track(struct MPContext *mpctx, struct track *track); struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction, - bool force); + bool force, bool mutate); void mp_set_playlist_entry(struct MPContext *mpctx, struct playlist_entry *e); void mp_play_files(struct MPContext *mpctx); void update_demuxer_properties(struct MPContext *mpctx); @@ -490,6 +505,7 @@ void prepare_playlist(struct MPContext *mpctx, struct playlist *pl); void autoload_external_files(struct MPContext *mpctx); struct track *select_default_track(struct MPContext *mpctx, int order, enum stream_type type); +void prefetch_next(struct MPContext *mpctx); // main.c int mp_initialize(struct MPContext *mpctx, char **argv); @@ -508,8 +524,6 @@ void update_vo_playback_state(struct MPContext *mpctx); void update_window_title(struct MPContext *mpctx, bool force); void error_on_track(struct MPContext *mpctx, struct track *track); int stream_dump(struct MPContext *mpctx, const char *source_filename); -int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg), - void *thread_arg); double get_track_seek_offset(struct MPContext *mpctx, struct track *track); // osd.c diff --git a/player/loadfile.c b/player/loadfile.c index e59acbc8d4..8023428e75 100644 --- a/player/loadfile.c +++ b/player/loadfile.c @@ -790,61 +790,141 @@ static void load_per_file_options(m_config_t *conf, } } -struct demux_open_args { - int stream_flags; - char *url; - struct mpv_global *global; - struct mp_cancel *cancel; - struct mp_log *log; - // results - struct demuxer *demux; - int err; -}; - -static void open_demux_thread(void *pctx) +static void *open_demux_thread(void *ctx) { - struct demux_open_args *args = pctx; - struct mpv_global *global = args->global; + struct MPContext *mpctx = ctx; + struct demuxer_params p = { - .force_format = global->opts->demuxer_name, + .force_format = mpctx->open_format, .allow_capture = true, - .stream_flags = args->stream_flags, + .stream_flags = mpctx->open_url_flags, }; - args->demux = demux_open_url(args->url, &p, args->cancel, global); - if (!args->demux) { + mpctx->open_res_demuxer = + demux_open_url(mpctx->open_url, &p, mpctx->open_cancel, mpctx->global); + + if (mpctx->open_res_demuxer) { + MP_VERBOSE(mpctx, "Opening done: %s\n", mpctx->open_url); + } else { + MP_VERBOSE(mpctx, "Opening failed or was aborted: %s\n", mpctx->open_url); + if (p.demuxer_failed) { - args->err = MPV_ERROR_UNKNOWN_FORMAT; + mpctx->open_res_error = MPV_ERROR_UNKNOWN_FORMAT; } else { - args->err = MPV_ERROR_LOADING_FAILED; + mpctx->open_res_error = MPV_ERROR_LOADING_FAILED; } } + + atomic_store(&mpctx->open_done, true); + mp_wakeup_core(mpctx); + return NULL; +} + +static void cancel_open(struct MPContext *mpctx) +{ + if (mpctx->open_cancel) + mp_cancel_trigger(mpctx->open_cancel); + + if (mpctx->open_active) + pthread_join(mpctx->open_thread, NULL); + mpctx->open_active = false; + + TA_FREEP(&mpctx->open_cancel); + TA_FREEP(&mpctx->open_url); + TA_FREEP(&mpctx->open_format); + + if (mpctx->open_res_demuxer) + free_demuxer_and_stream(mpctx->open_res_demuxer); + mpctx->open_res_demuxer = NULL; + + atomic_store(&mpctx->open_done, false); +} + +// Setup all the field to open this url, and make sure a thread is running. +static void start_open(struct MPContext *mpctx, char *url, int url_flags) +{ + cancel_open(mpctx); + + assert(!mpctx->open_active); + assert(!mpctx->open_cancel); + assert(!mpctx->open_res_demuxer); + assert(!atomic_load(&mpctx->open_done)); + + mpctx->open_cancel = mp_cancel_new(NULL); + mpctx->open_url = talloc_strdup(NULL, url); + mpctx->open_format = talloc_strdup(NULL, mpctx->opts->demuxer_name); + mpctx->open_url_flags = url_flags; + if (mpctx->opts->load_unsafe_playlists) + mpctx->open_url_flags = 0; + + if (pthread_create(&mpctx->open_thread, NULL, open_demux_thread, mpctx)) { + cancel_open(mpctx); + return; + } + + mpctx->open_active = true; } static void open_demux_reentrant(struct MPContext *mpctx) { - struct demux_open_args args = { - .global = mpctx->global, - .cancel = mp_cancel_new(NULL), - .log = mpctx->log, - .stream_flags = mpctx->playing->stream_flags, - .url = talloc_strdup(NULL, mpctx->stream_open_filename), - }; + char *url = mpctx->stream_open_filename; + + if (mpctx->open_active) { + bool done = atomic_load(&mpctx->open_done); + bool failed = done && !mpctx->open_res_demuxer; + bool correct_url = strcmp(mpctx->open_url, url) == 0; + + if (correct_url && !failed) { + MP_VERBOSE(mpctx, "Using prefetched/prefetching URL.\n"); + } else if (correct_url && failed) { + MP_VERBOSE(mpctx, "Prefetched URL failed, retrying.\n"); + cancel_open(mpctx); + } else { + if (!done) + MP_VERBOSE(mpctx, "Aborting onging prefetch of wrong URL.\n"); + cancel_open(mpctx); + } + } + + if (!mpctx->open_active) + start_open(mpctx, url, mpctx->playing->stream_flags); + + // User abort should cancel the opener now. pthread_mutex_lock(&mpctx->lock); - mpctx->demuxer_cancel = args.cancel; + mpctx->demuxer_cancel = mpctx->open_cancel; pthread_mutex_unlock(&mpctx->lock); - if (mpctx->opts->load_unsafe_playlists) - args.stream_flags = 0; - mpctx_run_reentrant(mpctx, open_demux_thread, &args); - if (args.demux) { - mpctx->demuxer = args.demux; + + while (!atomic_load(&mpctx->open_done)) { + mp_idle(mpctx); + + if (mpctx->stop_play) + mp_abort_playback_async(mpctx); + } + + if (mpctx->open_res_demuxer) { + assert(mpctx->demuxer_cancel == mpctx->open_cancel); + mpctx->demuxer = mpctx->open_res_demuxer; + mpctx->open_res_demuxer = NULL; + mpctx->open_cancel = NULL; } else { - mpctx->error_playing = args.err; + mpctx->error_playing = mpctx->open_res_error; pthread_mutex_lock(&mpctx->lock); - talloc_free(mpctx->demuxer_cancel); mpctx->demuxer_cancel = NULL; pthread_mutex_unlock(&mpctx->lock); } - talloc_free(args.url); + + cancel_open(mpctx); // cleanup +} + +void prefetch_next(struct MPContext *mpctx) +{ + if (!mpctx->opts->prefetch_open) + return; + + struct playlist_entry *new_entry = mp_next_file(mpctx, +1, false, 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); + } } static bool init_complex_filters(struct MPContext *mpctx) @@ -1279,8 +1359,9 @@ terminate_playback: // it can have side-effects and mutate mpctx. // direction: -1 (previous) or +1 (next) // force: if true, don't skip playlist entries marked as failed +// mutate: if true, change loop counters struct playlist_entry *mp_next_file(struct MPContext *mpctx, int direction, - bool force) + bool force, bool mutate) { struct playlist_entry *next = playlist_get_next(mpctx->playlist, direction); if (next && direction < 0 && !force) { @@ -1340,7 +1421,7 @@ void mp_play_files(struct MPContext *mpctx) if (mpctx->stop_play == PT_NEXT_ENTRY || mpctx->stop_play == PT_ERROR || mpctx->stop_play == AT_END_OF_FILE || !mpctx->stop_play) { - new_entry = mp_next_file(mpctx, +1, false); + new_entry = mp_next_file(mpctx, +1, false, true); } mpctx->playlist->current = new_entry; @@ -1350,6 +1431,8 @@ void mp_play_files(struct MPContext *mpctx) if (!mpctx->playlist->current && mpctx->opts->player_idle_mode < 2) break; } + + cancel_open(mpctx); } // Abort current playback and set the given entry to play next. diff --git a/player/misc.c b/player/misc.c index 032342e84a..2c160aef73 100644 --- a/player/misc.c +++ b/player/misc.c @@ -17,7 +17,6 @@ #include #include -#include #include #include "config.h" @@ -251,51 +250,3 @@ void merge_playlist_files(struct playlist *pl) playlist_add_file(pl, edl); talloc_free(edl); } - -struct wrapper_args { - struct MPContext *mpctx; - void (*thread_fn)(void *); - void *thread_arg; - pthread_mutex_t mutex; - bool done; -}; - -static void *thread_wrapper(void *pctx) -{ - struct wrapper_args *args = pctx; - mpthread_set_name("opener"); - args->thread_fn(args->thread_arg); - pthread_mutex_lock(&args->mutex); - args->done = true; - pthread_mutex_unlock(&args->mutex); - mp_wakeup_core(args->mpctx); // this interrupts mp_idle() - return NULL; -} - -// Run the thread_fn in a new thread. Wait until the thread returns, but while -// waiting, process input and input commands. -int mpctx_run_reentrant(struct MPContext *mpctx, void (*thread_fn)(void *arg), - void *thread_arg) -{ - struct wrapper_args args = {mpctx, thread_fn, thread_arg}; - pthread_mutex_init(&args.mutex, NULL); - bool success = false; - pthread_t thread; - if (pthread_create(&thread, NULL, thread_wrapper, &args)) - goto done; - while (!success) { - mp_idle(mpctx); - - if (mpctx->stop_play) - mp_abort_playback_async(mpctx); - - pthread_mutex_lock(&args.mutex); - success |= args.done; - pthread_mutex_unlock(&args.mutex); - } - pthread_join(thread, NULL); -done: - pthread_mutex_destroy(&args.mutex); - mp_wakeup_core(mpctx); // avoid lost wakeups during waiting - return success ? 0 : -1; -} diff --git a/player/playloop.c b/player/playloop.c index 9db9396f95..232a75f814 100644 --- a/player/playloop.c +++ b/player/playloop.c @@ -663,6 +663,9 @@ static void handle_pause_on_low_cache(struct MPContext *mpctx) force_update = true; } + if (s.eof && !busy) + prefetch_next(mpctx); + if (force_update) mp_notify(mpctx, MP_EVENT_CACHE_UPDATE, NULL); }