diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst index 609f6498a4..6774170aee 100644 --- a/DOCS/interface-changes.rst +++ b/DOCS/interface-changes.rst @@ -20,6 +20,15 @@ Interface changes :: --- mpv 0.21.0 --- + - setting certain options at runtime will now take care of updating them + property (see for example issue #3281). On the other hand, it will also + do runtime verification and reject option changes that do not work + (example: setting the "vf" option to a filter during playback, which fails + to initialize - the option value will remain at its old value). In general, + "set name value" should be mostly equivalent to "set options/name value" + in cases where the "name" property is not deprecated and "options/name" + exists - deviations from this are either bugs, or documented as caveats + in the "Inconsistencies between options and properties" manpage section. - deprecate _all_ --vo and --ao suboptions. Generally, all suboptions are replaced by global options, which do exactly the same. For example, "--vo=opengl:scale=nearest" turns into "--scale=nearest". In some cases, diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst index cc81e6a125..3588d66599 100644 --- a/DOCS/man/input.rst +++ b/DOCS/man/input.rst @@ -2066,11 +2066,15 @@ caveats with some properties (due to historical reasons): allows setting any track ID, and which tracks to enable is chosen at loading time.) + Option changes at runtime are affected by this as well. + ``deinterlace`` While video is active, this behaves differently from the option. It will never return the ``auto`` value (but the state as observed by the video chain). You cannot set ``auto`` either. + Option changes at runtime are affected by this as well. + ``video-aspect`` While video is active, always returns the effective aspect ratio. @@ -2089,11 +2093,15 @@ caveats with some properties (due to historical reasons): same way. Also, there are no ``vf-add`` etc. properties, but you can use the ``vf``/``af`` group of commands to achieve the same. + Option changes at runtime are affected by this as well. + ``chapter`` While playback is *not* active, the property behaves like the option, and you can set a chapter range. While playback is active, you can set only the current chapter (to which the player will seek immediately). + Option changes at runtime are affected by this as well. + ``volume`` When set as option, the maximum (set by ``--volume-max``) is not checked, while when set as property, the maximum is enforced. diff --git a/options/m_config.c b/options/m_config.c index c6c1bf9af1..7cc9a46792 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -676,9 +676,11 @@ static int handle_set_opt_flags(struct m_config *config, return set ? 2 : 1; } -// The type data points to is as in: co->opt -int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, - void *data, int flags) +// Unlike m_config_set_option_raw() this does not go through the property layer +// via config.option_set_callback. +int m_config_set_option_raw_direct(struct m_config *config, + struct m_config_option *co, + void *data, int flags) { if (!co) return M_OPT_UNKNOWN; @@ -702,6 +704,24 @@ int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, return 0; } +// Similar to m_config_set_option_ext(), but set as data in its native format. +// This takes care of some details like sending change notifications. +// The type data points to is as in: co->opt +int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, + void *data, int flags) +{ + if (config->option_set_callback) { + int r = handle_set_opt_flags(config, co, flags); + if (r <= 1) + return r; + + return config->option_set_callback(config->option_set_callback_cb, + co, data, flags); + } else { + return m_config_set_option_raw_direct(config, co, data, flags); + } +} + static int parse_subopts(struct m_config *config, char *name, char *prefix, struct bstr param, int flags); diff --git a/options/m_config.h b/options/m_config.h index e7c095623b..9440bd5833 100644 --- a/options/m_config.h +++ b/options/m_config.h @@ -77,6 +77,10 @@ typedef struct m_config { int (*includefunc)(void *ctx, char *filename, int flags); void *includefunc_ctx; + int (*option_set_callback)(void *ctx, struct m_config_option *co, + void *data, int flags); + void *option_set_callback_cb; + // For the command line parser int recursion_depth; @@ -170,11 +174,13 @@ static inline int m_config_set_option0(struct m_config *config, return m_config_set_option(config, bstr0(name), bstr0(param)); } -// Similar to m_config_set_option_ext(), but set as data in its native format. -// The type data points to is as in co->opt int m_config_set_option_raw(struct m_config *config, struct m_config_option *co, void *data, int flags); +int m_config_set_option_raw_direct(struct m_config *config, + struct m_config_option *co, + void *data, int flags); + // Similar to m_config_set_option_ext(), but set as data using mpv_node. struct mpv_node; int m_config_set_option_node(struct m_config *config, bstr name, diff --git a/player/command.c b/player/command.c index f4e2a0acd3..d791838ef8 100644 --- a/player/command.c +++ b/player/command.c @@ -126,6 +126,9 @@ static int edit_filters(struct MPContext *mpctx, struct mp_log *log, static int set_filters(struct MPContext *mpctx, enum stream_type mediatype, struct m_obj_settings *new_chain); +static int mp_property_do_silent(const char *name, int action, void *val, + struct MPContext *ctx); + static void hook_remove(struct MPContext *mpctx, int index) { struct command_ctx *cmd = mpctx->command_ctx; @@ -254,6 +257,64 @@ static char *format_delay(double time) return talloc_asprintf(NULL, "%d ms", (int)lrint(time * 1000)); } +// Option-property bridge. This is used so that setting options via various +// mechanisms (including command line parsing, config files, per-file options) +// updates state associated with them. For that, they have to go through the +// property layer. (Ideally, this would be the other way around, and there +// would be per-option change handlers instead.) +// Note that the property-option bridge sidesteps this, as we'd get infinite +// recursion. +int mp_on_set_option(void *ctx, struct m_config_option *co, void *data, int flags) +{ + struct MPContext *mpctx = ctx; + + // These options are too inconsistent as they could be pulled through the + // property layer. Ideally we'd remove these inconsistencies in the future, + // though the actual problem is compatibility to user-expected behavior. + // What matters is whether _write_ access is different - property read + // access is not used here. + // We're also fine with cases where the property restricts the writable + // value range if playback is active, but not otherwise. + // OK, restrict during playback: vid, aid, sid, deinterlace, video-aspect, + // vf*, af*, chapter + // OK, is handled separately: playlist + // OK, does not conflict on low level: audio-file, sub-file, external-file + static const char *const no_property[] = { + "playlist-pos", // checks playlist bounds, "no" choice missing + "volume", // restricts to --volume-max + "demuxer", "idle", "length", "audio-samplerate", "audio-channels", + "audio-format", "fps", "cache", // different semantics + NULL + }; + + for (int n = 0; no_property[n]; n++) { + if (strcmp(co->name, no_property[n]) == 0) + goto direct_option; + } + + // Normalize "vf*" to "vf" + const char *name = co->name; + bstr bname = bstr0(name); + char tmp[50]; + if (bstr_eatend0(&bname, "*")) { + snprintf(tmp, sizeof(name), "%.*s", BSTR_P(bname)); + name = tmp; + } + + int r = mp_property_do_silent(name, M_PROPERTY_SET, data, mpctx); + if (r != M_PROPERTY_OK) + return M_OPT_INVALID; + + // The flag can't be passed through the property layer correctly. + if (flags & M_SETOPT_FROM_CMDLINE) + co->is_set_from_cmdline = true; + + return 0; + +direct_option: + return m_config_set_option_raw_direct(mpctx->mconfig, co, data, flags); +} + // Property-option bridge. (Maps the property to the option with the same name.) static int mp_property_generic_option_do(void *ctx, struct m_property *prop, int action, void *arg, bool force) @@ -277,7 +338,7 @@ static int mp_property_generic_option_do(void *ctx, struct m_property *prop, m_option_copy(opt->opt, arg, valptr); return M_PROPERTY_OK; case M_PROPERTY_SET: - if (m_config_set_option_raw(mpctx->mconfig, opt, arg, flags) < 0) + if (m_config_set_option_raw_direct(mpctx->mconfig, opt, arg, flags) < 0) return M_PROPERTY_ERROR; return M_PROPERTY_OK; } @@ -4069,13 +4130,20 @@ static bool is_property_set(int action, void *val) } } -int mp_property_do(const char *name, int action, void *val, - struct MPContext *ctx) +static int mp_property_do_silent(const char *name, int action, void *val, + struct MPContext *ctx) { struct command_ctx *cmd = ctx->command_ctx; int r = m_property_do(ctx->log, cmd->properties, name, action, val, ctx); if (r == M_PROPERTY_OK && is_property_set(action, val)) mp_notify_property(ctx, (char *)name); + return r; +} + +int mp_property_do(const char *name, int action, void *val, + struct MPContext *ctx) +{ + int r = mp_property_do_silent(name, action, val, ctx); if (mp_msg_test(ctx->log, MSGL_V) && is_property_set(action, val)) { struct m_option ot = {0}; void *data = val; diff --git a/player/command.h b/player/command.h index c082d8d0d7..33e5b74927 100644 --- a/player/command.h +++ b/player/command.h @@ -24,6 +24,7 @@ struct MPContext; struct mp_cmd; struct mp_log; struct mpv_node; +struct m_config_option; void command_init(struct MPContext *mpctx); void command_uninit(struct MPContext *mpctx); @@ -35,6 +36,8 @@ void property_print_help(struct MPContext *mpctx); int mp_property_do(const char* name, int action, void* val, struct MPContext *mpctx); +int mp_on_set_option(void *ctx, struct m_config_option *co, void *data, int flags); + void mp_notify(struct MPContext *mpctx, int event, void *arg); void mp_notify_property(struct MPContext *mpctx, const char *property); diff --git a/player/main.c b/player/main.c index d4c31a4726..12f0191fc6 100644 --- a/player/main.c +++ b/player/main.c @@ -356,6 +356,9 @@ struct MPContext *mp_create(void) mp_input_set_cancel(mpctx->input, mpctx->playback_abort); + mpctx->mconfig->option_set_callback = mp_on_set_option; + mpctx->mconfig->option_set_callback_cb = mpctx; + return mpctx; }