Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

options: add watch-later-options #8960

Merged
merged 1 commit into from
Jul 21, 2021

Conversation

guidocella
Copy link
Contributor

@guidocella guidocella commented Jun 25, 2021

This allows configuring which properties are saved by quit-watch-later.

Fixes #4126, fixes #4641, fixes #5567.

@Dudemanguy
Copy link
Member

If you add properties at runtime, they will be saved if their values are
different from those they had when watch-later-properties was changed,
rather than when mpv started.

That's the current behavior with this implementation which is certainly fair. But perhaps it would be better if watch-later-properties only ever considered mpv's initial startup values? I'm not too sure. I could argue it either way.

@guidocella
Copy link
Contributor Author

I think that calculating almost 1000 properties + 1000 options on every startup could slow it done for basically no advantage.

@Dudemanguy
Copy link
Member

Dudemanguy commented Jul 6, 2021

Finally got around to testing this one, it seems start is always saved in watch later no matter what. Ex. if you do ./build/mpv --watch-later-properties=volume foo.mkv, I would expect only volume to possibly be saved in watch later but I always get start in there as well.

And I'm sorry, I know we've had this discussion in IRC before but why is all the struct resume_default business necessary? Wouldn't simply running mp_get_resume_defaults again on the callback be sufficient (and just have that function use watch_later_properties instead of the hardcoded backup_properties) or is there something I'm not seeing here?

@guidocella
Copy link
Contributor Author

Finally got around to testing this one, it seems start is always saved in watch later no matter what. Ex. if you do ./build/mpv --watch-later-properties=volume foo.mkv, I would expect only volume to possibly be saved in watch later but I always get start in there as well.

That's because start is not a property. Even before this patch it wasn't in backup_properties and was treated separately. That's why I wrote in the manpage "The playback position is always saved". Excluding it would require changing more code since it's assumed to be always present, and it might be more correct to allow doing so with another option since it is not a property (but is there even a use case for that?).

And I'm sorry, I know we've had this discussion in IRC before but why is all the struct resume_default business necessary? Wouldn't simply running mp_get_resume_defaults again on the callback be sufficient (and just have that function use watch_later_properties instead of the hardcoded backup_properties) or is there something I'm not seeing here?

When you add watch-later-properties at runtime, it uses their value at that time as the default to compare to when they're being saved, but for watch-later-properties that were already present, it copies the existing startup default values from the old struct (as you suggested in IRC). So if for example you set gamma to 10 at runtime and then append to watch-later-properties, gamma will still be saved because it's compared to the original 0 rather than 10. In order to copy the old values we need to keep the names of the resume_defaults properties to find out at what array index they were, since the old and new watch-later-properties can be completely different lists and the old list isn't persisted so it can't be used for the comparison.

I think there are 3 options:

  • We don't support runtime changes. This is what I did initially and it was simple as it only added 8 lines to configfiles.c. That still mostly worked with runtime changes except that it would store properties that haven't been changed since:
    if new watch-later-properties are added there's no default to compare to
    if you remove watch-later-properties, since the indexes of backup_properties and watch-later-properties no longer match properties would be compared to the defaults of different ones and lots of them get saved unnecessarily
  • We support runtime changes and save properties different from the value they had when they are appended to watch-later-properties, rather than at startup. This is more complex but more powerful and is what this PR does.
  • We calculate and store every possible property and option at startup (even renamed and deprecated ones) and use those values for comparison no matter when watch-later-properties are added. Even more complex, might slow down startups for minimal advantage?

@Dudemanguy
Copy link
Member

Dudemanguy commented Jul 6, 2021

That's because start is not a property. Even before this patch it wasn't in backup_properties and was treated separately.

Oops, I didn't notice that. It is indeed coded into mp_write_watch_later to always write start to the file, However, start is a valid option though so it should be pretty easy to just map it and handle like the rest of the properties here. If we're going to make watch-later-properties totally configurable, then I don't think start needs to be always, unconditionally written.

So if for example you set gamma to 10 at runtime and then append to watch-later-properties, gamma will still be saved because it's compared to the original 0 rather than 10.

To me, that makes sense and would be the preferred behavior. I would think values should always be compared to startup value regardless of when something is added/removed to the list. I understand that's incredibly subjective though.

@guidocella
Copy link
Contributor Author

But the start option is different from the playback position on quit. If you do mpv --start=5 --watch-later-properties=start, shouldn't start=5 be saved rather than start=${time-pos-on-quit}? I guess the playback time should have been saved as time-pos= rather than start=, but renaming it now would break existing watch later files. :/

@Dudemanguy
Copy link
Member

But the start option is different from the playback position on quit. If you do mpv --start=5 --watch-later-properties=start, shouldn't start=5 be saved rather than start=${time-pos-on-quit}

That's a good point.

I guess the playback time should have been saved as time-pos= rather than start=, but renaming it now would break existing watch later files. :/

You could do always do both. Have start and time-pos be a part of the defaults. Should be OK I think. I'm not sure what actually happens if start and time-pos both are read but normally only time-pos should be written.

@guidocella
Copy link
Contributor Author

Actually that made no sense since time-pos is not an option, so restoring it from a watch later file causes a Error parsing option time-pos (option not found); it would be like doing mpv --time-pos=5 foo.mp4, hence why start was used. So even if we move start to watch-later-properties, it would still need a special behavior that overwrites whatever value the start option actually has with the time-pos on quit. I'm not sure this is worth doing, or if another option should be added as I said before, and disabling that would allow saving the real start option if it is added to watch-later-properties?

@Dudemanguy
Copy link
Member

Dudemanguy commented Jul 6, 2021

Ouch, that gets trickier. I just assumed internally it used the option-property bridge when reading the file. I didn't think it literally only accepted options. If you don't feel like refactoring how the watch_later reading mechanism works (it would probably also need to accept generic properties), I think it's OK to just leave the start behavior alone like it currently is. --no-resume-playback still works (seems like it was specifically coded to do that). In the future perhaps this could be refactored.

As an aside if it doesn't actually accept properties that aren't options, the name is kind of misleading but eh.

@guidocella
Copy link
Contributor Author

It uses the same code that parses mpv.conf (options/parse_configfile.c).

There actually are some property-only keys like options/vid which wouldn't work as options like --options/vid, but after retrieving them as properties the options/ prefix is stripped when writing to the watch later file.

I don't think that there's any writable property without a corresponding option that is worth saving with quit-watch-later to warrant writting new code that parses watch later files differently than mpv.conf.

So do you want to try a version that fetches every property and option on startup and see how long it takes to run? Note that it would slow down quitting as well since for every watch-later property, it would have to loop through up to 3000 resume_defaults (since we also need to save 2 versions of options which may or may not have the options/ prefix) to find the matching one, and there was discussion in the previous PR about wanting to avoid nested loops.

With the current 108 watch-later-properties, mp_get_resume_defaults takes 0.000673 seconds on my machine, so with 20 times as many properties and options it might take 0.013 seconds.

@Dudemanguy
Copy link
Member

So do you want to try a version that fetches every property and option on startup and see how long it takes to run?

Nah that seems silly to me. To be honest, I don't really like the struct resume_default complication and I think merely calling the function again on callback like I suggested earlier is enough. Just add a note to the documentation that it always compares to the initial startup value of the player. I mean the worst case scenario is what, you might pointlessly write an extra line to the file? Doesn't seem like a big deal to me and you still get the runtime-updating behavior of the option. I dunno how everyone else feels, but probably it would get a LGTM from me if the logic was changed to that.

@guidocella guidocella force-pushed the watch-later-properties branch 3 times, most recently from 5ab2683 to a1e9ee3 Compare July 6, 2021 23:33
Copy link
Member

@Dudemanguy Dudemanguy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for the work on this. This is the best version of this feature yet so I guess it's a good thing the previous attempts all failed.

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

I'm glad that someone has started work in this. I implemented a prototype in https://github.com/wnoun/mpv/tree/watch-later-properties. I did not submit a patch due to some doubts. I would like to suggest that:

  1. I think it is ok to ignore rumtime changes, but you cannot prevent user from changing it(how do you make it immutable after playback is initialized?), I do not see you code handle such mismatch. So a key-value list may be still preferred.
  2. You should document in the man page that it does not support runtime changes but libmpv user can modify it before mpv_initialize.
  3. we should clarify that the current playback time is automatically saved as the start, so user won't put the start into the list and get unexpected result.

@Dudemanguy
Copy link
Member

It does work on runtime changes though.

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

It does work on runtime changes though.

but for the current implementation, if user changes it, the program may crash due to bad access to the resume_defaults

@Dudemanguy
Copy link
Member

Dudemanguy commented Jul 8, 2021

but for the current implementation, if user changes it, the program may crash due to bad access to the resume_defaults

Huh? How would that happen?

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

but for the current implementation, if user changes it, the program may crash due to bad access to the resume_defaults

Huh? How would that happen?

because the mismatch of resume_defaults and watch-later-properties, changing watch-later-properties does not update resume_defaults, the length and content may not match to each other. if new options are added to the 'watch-later-properties' at runtime, we will access resume_defaults out of its range.

@Dudemanguy
Copy link
Member

If you are referring to the loop in mp_write_watch_later_conf, it does not segfault when going out of range. The returned value is null if i is greater than the length of resume_defaults.

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

I means the statement char *prev = mpctx->resume_defaults[i]; if the length of watch-later-properties is greater than resume_defaults.

@Dudemanguy
Copy link
Member

Dudemanguy commented Jul 8, 2021

Right, but it actually just returns NULL in practice so there's no issue (char *prev is just NULL). It loops fine and the properties are all saved as you'd expect. You can test this yourself. I'd be lying if I told you that I knew exactly why this occurs though.

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

what you means is accessing an array out of it ranges always returns NULL without any other side effect!?

@Dudemanguy
Copy link
Member

Yes.

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

I think there is nothing to discuss

@guidocella
Copy link
Contributor Author

guidocella commented Jul 8, 2021

A quick search returns articles like https://www.geeksforgeeks.org/accessing-array-bounds-ccpp/ which state that accessing C arrays out of bounds can indeed cause a segmentation fault. This was meant to support runtime changes at the cost of writing more properties than necessary but I did not consider this. Since as wnoun says I don't think you can make options immutable, I guess we should go back to the version that saved the names of resume_defaults properties to copy them on runtime changes?

@guidocella
Copy link
Contributor Author

I added a check to not access elements past the end of resume_defaults, documented start and also reduced the indentation of the loop. It might be better to go back to the name-value structs since this has gotten more complex anyway though.

@wnoun
Copy link
Contributor

wnoun commented Jul 8, 2021

I think it is reasonable to change resume_defaults to a name-value list. as a reference, my implementation only changed dozens of lines.

it usually just causes extra properties to be saved unnecessarily

it breaks the current behavior. mpv uses option values in config file as the option default, which means whenever I change the config file, the option value is not affected by the watch later file as long as it is not modified.

@Dudemanguy
Copy link
Member

Sorry for all the fuss. I suppose the name-value implementation was necessary after all.

struct resume_default **list = talloc_zero_array(mpctx, struct resume_default *, size);

for (int i = 0; watch_later_properties[i]; i++) {
list[i] = talloc_zero(list, struct resume_default);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use array of resume_default instead of array of resume_default * so you can save memory allocations here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you adapt mpctx->resume_defaults = list?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just change resume_defaults to resume_default *

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean change mpctx->resume_defaults to array of resume_default

player/command.c Outdated
@@ -6713,6 +6713,9 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags,

if (opt_ptr == &opts->vo->taskbar_progress)
update_vo_playback_state(mpctx);

if (opt_ptr == &opts->watch_later_properties)
mp_get_resume_defaults(mpctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if runtime change support is unnecessary, please remove all relative codes. It doesn't seem to work as expected because the current option value may not be the default value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is? If we didn't want runtime support we could have used the simpler version. It's documented in the manpage that the value at the time options are added is used. The alternative is storing a copy of every option and property on startup and finding it for each proeprty in mp_write_watch_later_conf, which is slower, though we can try that too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I said, we cannot prevent user from changing it only through the man page. The name-value list works regardless of whether the user modifies it or not, while the "simpler version" does not. "no runtime change support" means that we allow user to change it but ignore the changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand. This supports runtime changes in the way documented in the manpage. And this name-value list doesn't work if when the user modifies watch-later-properties at runtime mp_get_resume_defaults isn't called again since mp_write_watch_later_conf still does the comparison based on the array indexes.

for (int i = 0; backup_properties[i]; i++) {
const char *pname = backup_properties[i];
for (int i = 0; opts->watch_later_properties[i]; i++) {
const char *pname = opts->watch_later_properties[i];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the key is that we shouldn't touch opts->watch_later_properties here. since the mpctx->resume_defaults has both the name and the default value, we can just iterate through mpctx->resume_defaults(add a sentinel element to make it null-terminated). so we are not affected by the the opts->watch_later_properties changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But why would we want that if this is supposed to support runtime changes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I explained the meaning of runtime change support. If you want the true runtime change support, you should backup all properties first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind if it supports runtime changes or not, since I only need this in mpv.conf as I suspect most users do, and didn't mean to support runtime changes at first. Copying the existing resume defaults and calculating any new one when watch-later-properties is modified at runtime is what Dudemanguy suggested on IRC, and he said storing every option and property at startup is silly, so it's up to him or the other maintainers to decide which version of this to use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Storing every single option/property on startup could have a measurable impact on startup times which is definitely unacceptable for this feature imo. I am perfectly fine with the caveat explained in the manpage and it's easy for a user to workaround it.

@guidocella
Copy link
Contributor Author

I tried to refactor this with MP_TARRAY_APPEND but it hangs on the second mp_property_do call. Any idea why?

void mp_get_resume_defaults(struct MPContext *mpctx)
{
    char **watch_later_properties = mpctx->opts->watch_later_properties;

    // If you do --watch-later-properties=,
    // the first element of the list option is "" rather than NULL.
    if (strcmp(watch_later_properties[0], "") == 0 && !watch_later_properties[1]) {
        talloc_free(mpctx->resume_defaults);
        return;
    }

    struct resume_default *list;
    int i = 0;
    while (watch_later_properties[i]) {
        struct resume_default def = {
            .pname = talloc_strdup(list, watch_later_properties[i]),
            .val = NULL
        };

        // If watch-later-properties is modified at runtime,
        // copy existing default values from the old resume_defaults.
        for (int j = 0; mpctx->resume_defaults
                && mpctx->resume_defaults[j].pname; j++) {
            if (strcmp(def.pname, mpctx->resume_defaults[j].pname) == 0) {
                def.val = talloc_strdup(list, mpctx->resume_defaults[j].val);
                break;
            }
        }
        if (!def.val) {
            char *val = NULL;
            int r = mp_property_do(def.pname, M_PROPERTY_GET_STRING, &val,
                    mpctx);
            if (r == M_PROPERTY_OK) {
                def.val = talloc_steal(list, val);
            } else if (r == M_PROPERTY_UNKNOWN) {
                MP_ERR(mpctx->mconfig, "Option %s not found.\n", def.pname);
            }
        }
        MP_TARRAY_APPEND(mpctx, list, i, def);
    }
    MP_TARRAY_APPEND(mpctx, list, i,
            (struct resume_default){ .pname = NULL, .val = NULL });

    talloc_free(mpctx->resume_defaults);
    mpctx->resume_defaults = list;
}

@guidocella
Copy link
Contributor Author

Actually I just had to initialize list. I switched to this version because while it's only 1 line shorter, having a single loop is nicer, and it's what the rest of the code does.

I also restored the watch_later_properties && watch_later_properties[i] check because, while --watch-later-properties= makes the first element an empty string, change-list watch-later-properties clr "" frees the array, so that was causing a segfault.

player/configfiles.c Outdated Show resolved Hide resolved
Copy link
Member

@Dudemanguy Dudemanguy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's good now (second time I know but still).

@guidocella
Copy link
Contributor Author

Alright. Are we sure we don't want to name this watch-later-options? I guess someone could try to save the writable properties chapter, ao-volume and ao-mute. But then it's confusing to explain why options/vid, options/aid, options/sid are used.

@Dudemanguy
Copy link
Member

It does work by reading mini config files so technically options is the more accurate name.

@guidocella guidocella changed the title options: add watch-later-properties options: add watch-later-options Jul 13, 2021
@guidocella
Copy link
Contributor Author

Renamed. I also moved the documentation to below reset-on-next-file since that it similar and it explains more clearly what is meant by initial value. I didn't try to explain that the options/ prefix can be used since I think it would just confuse the user, and it's not useful for anything but vid, aid and sid which are already in the default list.

@guidocella
Copy link
Contributor Author

guidocella commented Jul 20, 2021

I wrote another implementation that backups every option at startup, so that even if watch-later-options are added at runtime, they will be compared to the startup values: https://0x0.st/-Vev.diff

While calculating every property at startup may be slow, it's fine to do it just for options, since --reset-on-next-file=all already does just that.

It is also 30 lines shorter, and allows removing the options/ prefix from vid, aid and sid, which was necessary to not save them always.

With the default watch-later-options and optimize enabled, the execution times I measure on an old i3-3240 CPU @ 3.40GHz are:
Backuping up the options: before (the version with watch-later-options, not master) 0.000455s, after 0.000920s. This should be even more negligible on a decent CPU.
mp_write_watch_later_conf's loop, without changed options: before 0.000969s, after 0.000636s. Apparently calling mp_property_do only for changed options speeds this up.

What do you think?

@Dudemanguy
Copy link
Member

That approach looks better actually. The startup time increasing does feel a bit wrong, but the actual magnitude is incredibly small so it should be okay. Everything else looks better though.

One question. Why is this part needed exactly? I wasn't too sure from the patch.

-        return a == b;
+        return a == b || (!a && !b[0].name) || (!b && !a[0].name);

@guidocella
Copy link
Contributor Author

I wrote it in the commit message:

Toggling a video or audio filter twice would treat the option as changed
because the backup value is NULL, and the current value of vf/af is a
list with one empty item, so obj_settings_list_equal had to be changed.

It doesn't seem to break anything. I think it's reasonable to consider NULL and a list with one empty item as equal. Actually I was pretty worried about not being to use this version just because it was saving vf and af unnecessarily (which would overwrite new defaults you set in mpv.conf as wnoun noted), since I also think it's better overall.

I don't know how hard it would be modify the behavior of vf and af instead so they reset to NULL when removing all filters.

@Dudemanguy
Copy link
Member

Oh crap my bad for not reading that.

I don't know how hard it would be modify the behavior of vf and af instead so they reset to NULL when removing all filters.

Are these the only options that work like this internally? Probably given their relative complexity.

@guidocella
Copy link
Contributor Author

Are these the only options that work like this internally? Probably given their relative complexity.

It seems so, regular list options don't have this issue, only vf and af. If you revert the change to obj_settings_list_equal, and take for example reset-on-next-file, which starts blank like vf, and do mpv --watch-later-options=reset-on-next-file, change-list reset-on-next-file foo twice, it doesn't get saved.

@Dudemanguy
Copy link
Member

So just to document some brief IRC discussion, some testing seems to reveal that m_config_backup_watch_later_opts takes about double the amount of time to execute than the original mp_get_resume_defaults (both are called once on startup). Now on decent machines, the execution time is on the order of hundreds of microseconds (more specifically 270 microseconds to 500 microseconds on my ryzen 3600), so I don't believe this is a blocker. Anyways this version for real is the best version. I'll merge this later assuming nobody complains.

This allows configuring which options are saved by quit-watch-later.

Fixes mpv-player#4126, mpv-player#4641 and mpv-player#5567.

Toggling a video or audio filter twice would treat the option as changed
because the backup value is NULL, and the current value of vf/af is a
list with one empty item, so obj_settings_list_equal had to be changed.
@guidocella
Copy link
Contributor Author

Fixed the indentation in m_config_watch_later_backup_opt_changed.

@Dudemanguy Dudemanguy merged commit 1d1d1fb into mpv-player:master Jul 21, 2021
@guidocella guidocella deleted the watch-later-properties branch July 21, 2021 13:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants