diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 62d271bcb1d2b..d398ab79877de 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -98,6 +98,7 @@ Interface changes - `--config-dir` no longer forces cache and state files to also reside in there - deprecate `--demuxer-cue-codepage` in favor of `--metadata-codepage` - change the default of `metadata-codepage` to `auto` + - add `playlist-next-playlist` and `playlist-prev-playlist` commands --- mpv 0.36.0 --- - add `--target-contrast` - Target luminance value is now also applied when ICC profile is used. diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index b4c133953e0e7..00ce20c846863 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -422,6 +422,13 @@ Remember to quote string arguments in input.conf (see `Flat command syntax`_). force Terminate playback if the first file is being played. +``playlist-next-playlist`` + Go to the next entry on the playlist with a different ``playlist-path``. + +``playlist-prev-playlist`` + Go to the first of the previous entries on the playlist with a different + ``playlist-path``. + ``playlist-play-index `` Start (or restart) playback of the given playlist index. In addition to the 0-based playlist entry index, it supports the following values: diff --git a/common/playlist.c b/common/playlist.c index 78a77134629ed..e79a96b4025eb 100644 --- a/common/playlist.c +++ b/common/playlist.c @@ -214,6 +214,74 @@ struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e, return playlist_entry_from_index(e->pl, e->pl_index + direction); } +struct playlist_entry *playlist_get_first_in_next_playlist(struct playlist *pl, + int direction) +{ + struct playlist_entry *entry = playlist_get_next(pl, direction); + if (!entry) + return NULL; + + while (entry && entry->playlist_path && + strcmp(entry->playlist_path, pl->current->playlist_path) == 0) + entry = playlist_entry_get_rel(entry, direction); + + if (direction < 0) + entry = playlist_get_first_in_same_playlist(entry, + pl->current->playlist_path); + + return entry; +} + +struct playlist_entry *playlist_get_first_in_same_playlist( + struct playlist_entry *entry, char *current_playlist_path) +{ + void *tmp = talloc_new(NULL); + + if (!entry || !entry->playlist_path) + goto exit; + + // Don't go to the beginning of the playlist when the current playlist-path + // starts with the previous playlist-path, e.g. with mpv --loop-playlist + // archive_dir/, which expands to archive_dir/{1..9}.zip, the current + // playlist path "archive_dir/1.zip" begins with the playlist-path + // "archive_dir/" of {2..9}.zip, so go to 9.zip instead of 2.zip. But + // playlist-prev-playlist from e.g. the directory "foobar" to the directory + // "foo" should still go to the first entry in "foo/", and this should all + // work whether mpv's arguments have trailing slashes or not, e.g. in the + // first example: + // mpv archive_dir results in the playlist-paths "archive_dir/1.zip" and + // "archive_dir" + // mpv archive_dir/ in "archive_dir/1.zip" and "archive_dir/" + // mpv archive_dir// in "archive_dir//1.zip" and "archive_dir//" + // Always adding a separator to entry->playlist_path to fix the foobar foo + // case would break the previous 2 cases instead. Stripping the separator + // from entry->playlist_path if present and appending it again makes this + // work in all cases. + char* playlist_path = talloc_strdup(tmp, entry->playlist_path); + mp_path_strip_trailing_separator(playlist_path); + if (bstr_startswith(bstr0(current_playlist_path), + bstr0(talloc_strdup_append(playlist_path, "/"))) +#if HAVE_DOS_PATHS + || + bstr_startswith(bstr0(current_playlist_path), + bstr0(talloc_strdup_append(playlist_path, "\\"))) +#endif + ) + goto exit; + + struct playlist_entry *prev = playlist_entry_get_rel(entry, -1); + + while (prev && prev->playlist_path && + strcmp(prev->playlist_path, entry->playlist_path) == 0) { + entry = prev; + prev = playlist_entry_get_rel(entry, -1); + } + +exit: + talloc_free(tmp); + return entry; +} + void playlist_add_base_path(struct playlist *pl, bstr base_path) { if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0) diff --git a/common/playlist.h b/common/playlist.h index c5b82781e6f61..aecd53988bf8b 100644 --- a/common/playlist.h +++ b/common/playlist.h @@ -98,6 +98,10 @@ struct playlist_entry *playlist_get_last(struct playlist *pl); struct playlist_entry *playlist_get_next(struct playlist *pl, int direction); struct playlist_entry *playlist_entry_get_rel(struct playlist_entry *e, int direction); +struct playlist_entry *playlist_get_first_in_next_playlist(struct playlist *pl, + int direction); +struct playlist_entry *playlist_get_first_in_same_playlist(struct playlist_entry *entry, + char *current_playlist_path); void playlist_add_base_path(struct playlist *pl, bstr base_path); void playlist_set_stream_flags(struct playlist *pl, int flags); int64_t playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl); diff --git a/player/command.c b/player/command.c index cf4971a4b35d2..8d7bc39c11692 100644 --- a/player/command.c +++ b/player/command.c @@ -5321,6 +5321,45 @@ static void cmd_playlist_next_prev(void *p) mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; } +static void cmd_playlist_next_prev_playlist(void *p) +{ + struct mp_cmd_ctx *cmd = p; + struct MPContext *mpctx = cmd->mpctx; + int direction = *(int *)cmd->priv; + + struct playlist_entry *entry = + playlist_get_first_in_next_playlist(mpctx->playlist, direction); + + if (!entry && mpctx->opts->loop_times != 1 && mpctx->playlist->current) { + entry = direction > 0 ? playlist_get_first(mpctx->playlist) + : playlist_get_last(mpctx->playlist); + + if (entry && entry->playlist_path && + strcmp(entry->playlist_path, + mpctx->playlist->current->playlist_path) == 0) + entry = NULL; + + if (direction > 0 && entry && mpctx->opts->loop_times > 1) { + mpctx->opts->loop_times--; + m_config_notify_change_opt_ptr(mpctx->mconfig, + &mpctx->opts->loop_times); + } + + if (direction < 0) + entry = playlist_get_first_in_same_playlist( + entry, mpctx->playlist->current->playlist_path); + } + + if (!entry) { + cmd->success = false; + return; + } + + mp_set_playlist_entry(mpctx, entry); + if (cmd->on_osd & MP_ON_OSD_MSG) + mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; +} + static void cmd_playlist_play_index(void *p) { struct mp_cmd_ctx *cmd = p; @@ -6373,6 +6412,10 @@ const struct mp_cmd_def mp_cmds[] = { }, .priv = &(const int){-1}, }, + { "playlist-next-playlist", cmd_playlist_next_prev_playlist, + .priv = &(const int){1} }, + { "playlist-prev-playlist", cmd_playlist_next_prev_playlist, + .priv = &(const int){-1} }, { "playlist-play-index", cmd_playlist_play_index, { {"index", OPT_CHOICE(v.i, {"current", -2}, {"none", -1}),