-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
libmpv is very hard to work with #15253
Comments
So TLDR the problem is:
|
This is not a problem in my experience, mpv-android has been calling |
This is unrelated to mpv. You should never call any external API/RPC that may take an unknown amount of time on the UI thread. Create your own dispatch thread, add a message queue, whatever works, as this has been a solved problem for years. Never stall the UI thread. It is not libmpv's job to handle this for you; it is your application's job to manage messages and their dispatch in a way that avoids issues. For example, you should never perform any I/O on the UI thread.
It is your application's responsibility to handle it correctly.
I don't know what this means, but it sounds scary and not at all like something you'd want to do.
Each call to
You are free to design and implement an API wrapper that will work better for your use case/application. libmpv is a C API, and many things are more complex than what you would expect from high-level bindings like JavaScript or Lua. For example, mpv_node can be annoying to work with in C, but it is a low-level primitive that you should adopt or wrap for your language of choice. I can't speak for everyone, but I believe there are no current plans to change the official libmpv API. And for what it's worth, the C interface should remain as is; it has worked with multiple projects for years. You are free to use any existing wrapper or bindings, or to develop your own that makes interfacing with libmpv easier. I would do that too as a first step in implementing a libmpv-based application.
I agree with @sfan5 here. It doesn't sound like the delay is coming from mpv itself, to be quite honest. We have a g-r binding that lists all properties available in mpv, and this doesn't take long. Performing a single command shouldn't take that long either. In fact, all commands that take a long time, like the screenshot command, are forcefully asynchronous. If you see any commands that are stalling for 500 ms, please share more details so we can fix that. Thanks. |
in my app i use mpv_set_property(mpvhandle, "time-pos", MPV_FORMAT_DOUBLE, wantedposition) and it looks fast. only occasionally it's slow when hardware decoding is enabled and the gpu performances are poor. |
It's great that we all agree that a 500ms for a property set is not a normal thing, but to be honest, I'm kind of surprised that you don't have similar problems in your use. This is time measurement for mpv_command calls executed one right after another in my program. Only the first one completes in a reasonable time, the second one is already 9ms (probably because there is some locking mechanism that blocks command completion until previous seek finishes or something).
|
If you don't want to wait for seek to complete synchronously, you should use |
This is what |
No, mpv_command_async doesn't set new time-pos immediately. |
Yes, because seeking takes time. |
time-pos is actually a writable property, if you write it directly using mpv_set_property, then it's updated immediately. So why seek (which conceptually is the same thing, just with extra options) does behave differently and doesn't set time-pos instantly? |
What is updated? Settings |
It's not ideal but I do this in mpv-android too: https://github.com/mpv-android/mpv-android/blob/045b7f176858ac6ca3c40c318c3016a1084d80e2/app/src/main/java/is/xyz/mpv/Utils.kt#L247-L320 for these reasons:
|
Oh, my bad. I think I misread the "copy of player state" part. I though it is somehow clone of mpv. If it it like in your case some cache of current player state, it is good. I would call it "observe and cache" approach and this is common in .lua scripts also. To observe properties and cache its state to have easier access to this data and be able to aggregate it to call downstream APIs.
I agree, if it is possible and easier to cache observed values it should be better this way. Although depending on the amount of properties involved it is also possible to get_properties(), each time we need them. Some props are very light to query. But like said "observe and cache" is good way to work with it too.
Yeah, I guess it depends, on the UI design and how to handle dynamic player state changes. |
You're right, I wasn't aware that this works this way.
Great to hear that we're in fact on the same page here. May I ask how you handle that particular case where you do execute an async seek command, and before it finishes a time-pos MPV_EVENT_PROPERTY_CHANGE event (read by
I would say that it's a reasonable approach when you only read those "cached" values, but if you also write them to kind of guess what player state will be after the async command finishes, then it becomes more tricky, because if your predictions about future state aren't right, then your state can get out-of-sync with the real one. One solution would be to read the properties again when the async command finishes, but that's a lot of things to think about for such a simple task. |
While the user is operating the seek bar (holding down his finger) I simply block changes to the bar position. It could theoretically jump back after the touch is released but I haven't noticed that in practice. I can't think of any other situations with the same problem.
(I don't do that)
As for this: 11-05 19:51:01.861 17878 17878 V mpv : mpv_command(loadfile): 0ms 11-05 19:51:01.862 17878 17878 V mpv : mpv_set_property(android-surface-size): 0ms 11-05 19:51:02.447 17878 17878 V mpv : mpv_set_property(android-surface-size): 1ms 11-05 19:51:15.870 17878 17878 V mpv : mpv_set_property(pause): 1ms 11-05 19:51:15.870 17878 17878 V mpv : mpv_set_property(time-pos): 1ms 11-05 19:51:15.901 17878 17878 V mpv : mpv_set_property(time-pos): 7ms 11-05 19:51:15.949 17878 17878 V mpv : mpv_set_property(time-pos): 30ms 11-05 19:51:15.987 17878 17878 V mpv : mpv_set_property(time-pos): 18ms 11-05 19:51:16.034 17878 17878 V mpv : mpv_set_property(time-pos): 31ms 11-05 19:51:16.076 17878 17878 V mpv : mpv_set_property(time-pos): 23ms 11-05 19:51:16.116 17878 17878 V mpv : mpv_set_property(time-pos): 25ms 11-05 19:51:16.167 17878 17878 V mpv : mpv_set_property(time-pos): 29ms 11-05 19:51:16.213 17878 17878 V mpv : mpv_set_property(time-pos): 27ms 11-05 19:51:16.256 17878 17878 V mpv : mpv_set_property(time-pos): 30ms 11-05 19:51:16.300 17878 17878 V mpv : mpv_set_property(time-pos): 31ms 11-05 19:51:16.343 17878 17878 V mpv : mpv_set_property(time-pos): 24ms 11-05 19:51:16.387 17878 17878 V mpv : mpv_set_property(time-pos): 30ms 11-05 19:51:16.450 17878 17878 V mpv : mpv_set_property(time-pos): 47ms 11-05 19:51:16.495 17878 17878 V mpv : mpv_set_property(time-pos): 25ms 11-05 19:51:16.544 17878 17878 V mpv : mpv_set_property(time-pos): 35ms 11-05 19:51:16.585 17878 17878 V mpv : mpv_set_property(time-pos): 27ms 11-05 19:51:16.628 17878 17878 V mpv : mpv_set_property(time-pos): 24ms 11-05 19:51:16.672 17878 17878 V mpv : mpv_set_property(time-pos): 32ms 11-05 19:51:16.722 17878 17878 V mpv : mpv_set_property(time-pos): 34ms 11-05 19:51:16.773 17878 17878 V mpv : mpv_set_property(time-pos): 36ms 11-05 19:51:16.826 17878 17878 V mpv : mpv_set_property(time-pos): 38ms 11-05 19:51:16.885 17878 17878 V mpv : mpv_set_property(time-pos): 47ms 11-05 19:51:16.934 17878 17878 V mpv : mpv_set_property(time-pos): 29ms 11-05 19:51:16.944 17878 17878 V mpv : mpv_set_property(pause): 6ms 11-05 19:51:19.869 17878 17878 V mpv : mpv_set_property(time-pos): 0ms 11-05 19:51:19.931 17878 17878 V mpv : mpv_set_property(time-pos): 1ms 11-05 19:51:19.983 17878 17878 V mpv : mpv_set_property(time-pos): 3ms 11-05 19:51:20.035 17878 17878 V mpv : mpv_set_property(time-pos): 38ms 11-05 19:51:20.045 17878 17878 V mpv : mpv_set_property(time-pos): 7ms 11-05 19:51:20.086 17878 17878 V mpv : mpv_set_property(time-pos): 38ms 11-05 19:51:20.129 17878 17878 V mpv : mpv_set_property(time-pos): 38ms 11-05 19:51:20.177 17878 17878 V mpv : mpv_set_property(time-pos): 31ms 11-05 19:51:20.221 17878 17878 V mpv : mpv_set_property(time-pos): 35ms 11-05 19:51:20.265 17878 17878 V mpv : mpv_set_property(time-pos): 37ms 11-05 19:51:20.304 17878 17878 V mpv : mpv_set_property(time-pos): 24ms 11-05 19:51:20.339 17878 17878 V mpv : mpv_set_property(time-pos): 26ms 11-05 19:51:20.379 17878 17878 V mpv : mpv_set_property(time-pos): 31ms 11-05 19:51:20.417 17878 17878 V mpv : mpv_set_property(time-pos): 20ms 11-05 19:51:20.458 17878 17878 V mpv : mpv_set_property(time-pos): 29ms 11-05 19:51:20.503 17878 17878 V mpv : mpv_set_property(time-pos): 36ms 11-05 19:51:20.552 17878 17878 V mpv : mpv_set_property(time-pos): 43ms 11-05 19:51:20.588 17878 17878 V mpv : mpv_set_property(time-pos): 25ms 11-05 19:51:20.631 17878 17878 V mpv : mpv_set_property(time-pos): 33ms 11-05 19:51:20.671 17878 17878 V mpv : mpv_set_property(time-pos): 24ms 11-05 19:51:20.712 17878 17878 V mpv : mpv_set_property(time-pos): 31ms 11-05 19:51:20.755 17878 17878 V mpv : mpv_set_property(time-pos): 25ms 11-05 19:51:20.795 17878 17878 V mpv : mpv_set_property(time-pos): 29ms 11-05 19:51:20.841 17878 17878 V mpv : mpv_set_property(time-pos): 19ms 11-05 19:51:23.476 17878 17878 V mpv : mpv_set_property(time-pos): 3ms 11-05 19:51:23.534 17878 17878 V mpv : mpv_set_property(time-pos): 28ms 11-05 19:51:23.549 17878 17878 V mpv : mpv_set_property(time-pos): 1ms 11-05 19:51:23.586 17878 17878 V mpv : mpv_set_property(time-pos): 29ms 11-05 19:51:23.630 17878 17878 V mpv : mpv_set_property(time-pos): 37ms 11-05 19:51:23.669 17878 17878 V mpv : mpv_set_property(time-pos): 28ms 11-05 19:51:23.710 17878 17878 V mpv : mpv_set_property(time-pos): 32ms 11-05 19:51:23.752 17878 17878 V mpv : mpv_set_property(time-pos): 37ms 11-05 19:51:23.802 17878 17878 V mpv : mpv_set_property(time-pos): 37ms 11-05 19:51:23.849 17878 17878 V mpv : mpv_set_property(time-pos): 35ms 11-05 19:51:23.893 17878 17878 V mpv : mpv_set_property(time-pos): 33ms 11-05 19:51:23.944 17878 17878 V mpv : mpv_set_property(time-pos): 39ms 11-05 19:51:23.996 17878 17878 V mpv : mpv_set_property(time-pos): 41ms 11-05 19:51:24.042 17878 17878 V mpv : mpv_set_property(time-pos): 35ms 11-05 19:51:24.086 17878 17878 V mpv : mpv_set_property(time-pos): 36ms 11-05 19:51:26.533 17878 17878 V mpv : mpv_set_property(time-pos): 1ms (hardware decoding, local file playback) ¯\_(ツ)_/¯ |
Yeah, that seems to be ok for the progress bar case, but unfortunately my use case is far more complicated than this. The bottom line is, that I need a variable that stores time-pos and can be updated immediately when seek happens, and is not overwritten by any events until the seek is finished (just like DOM's video.currentTime). I've managed to eliminate the outdated events case by setting a flag to ignore time-pos property change events until MPV_EVENT_COMMAND_REPLY is received, but there is another problem. What sometimes happens (about 10% of cases) is that, the time-pos property changes twice (probably because seek target isn't a keyframe), e.g.:
This makes it impossible to know whenever a given time-pos is a final resulting position after the seek or not. Looking at the API reference I cannot find anything that would allow circumventing that. What I would rather expect is that the
|
And why do you need that? |
What does video.currentTime do when you try to seek to a timestamp but there's no keyframe at that timestamp, so the seek is at an earlier or a later timestamp? |
If I remember correctly, the currentTime will stay at whatever value you set (even though the playback've snapped to the nearest keyframe, so theoretically it's slightly incorrect). Maybe it's not perfect, but at least it's manageable.
My player has a built-in watch together style remote playback synchronization feature. One of the things it does, is continuously exchanging a ReportPosition packets between the users. Which is as follows:
the seekCounter is incremented each time a seek happens, this allows detecting whenever a given user have already acknowledged the latest seek when he produced this packet or not. If a large time difference is detected, despite having the same seekCounter value, the playback rewinds to the smallest position with the biggest (latest) seekCounter. In order for this to function correctly I must make sure, that position and seekCounter are always in sync (e.g. it's incorrect if seekCounter is already incremented, but position is still from before the seek). And that's why I need to know to which seek a given time-pos belongs (and also if it's already the keyframe aligned one or not). |
Then just setting the UI element to the timestamp sent with the seek command, and updating it after the seek went through should achieve this no? |
But the timeline of events looks something like (things on the same line happen in parallel):
|
And I've just noticed that this order appears to actually be random, sometimes the property is updated before the command finishes, so there is literally no way to know if it's even the correct one. |
Scripts trying to synchronize multiple players over network have a master player which is considered the source of truth for the play state for a reason. If you're going to let any player trigger a seek, then I don't believe libmpv is going to offer the kind of control you want. There could be small differences between the players that can't be reconciled, depending on versions of dependencies like ffmpeg for example. A libmpv player with a ffmpeg 6.1 for example is going to seek to different frames on a HEVC mov/mp4 file than a libmpv with ffmpeg 7.1 will; this is just one example that I know of I'm sure there are others. There are many issues with what you're trying to do, but if you want to attempt to do it anyway then I think you'll be better served using ffmpeg directly since you need far more control over the player than libmpv can offer at this time. |
No, but I've had a working web version for years, and it was working flawlessly. I ship the program with all dependencies bundled, so version differences shouldn't matter, and also I've put multiple mechanisms in place to make sure that even if something wrong happens, players will still be able to resync. The only thing I need is somewhat accurate information about current position and which seek was already applied when this position was produced. Unfortunately I cannot find a reliable way to do that in libmpv, the seek command completion seems to be completely detached from time-pos property update. Sometimes the property is updated first, and then the event completes, and sometimes the event completes first. This makes this API very hard to use for anything more than a simple GUI integration. |
Expected behavior of the wanted feature
Hi, I'm currently developing a player frontend using libmpv, and I'm facing a lot of problems, because of how the api is designed, particularly that the synchronous calls (even simple property read and writes) can block thread for arbitrary amount of time (in practice can be up to 500ms).
One example is that when you implement a seeable progress bar, you cannot just call
mpv_command(['seek', ...])
in the UI thread, because this call doesn't always complete immediately, so instead you have to dompv_command_async(...
. But because the UI progress bar position needs to update instantly after being pressed (for the UI to feel responsive) and becausempv_command_async
doesn't update the property instantly, I have to create my own copy of player state (which I can update without any delay) and try to mirror the actual player state via MPV_EVENT_PROPERTY_CHANGE. This even on its own is already bad, because it makes the program more fragile, because the 2 states can get out of sync if something unexpected happens.But even worse part is that there is no easy way to know whenever a particular event was produced before or after a call to
mpv_command_async
, this creaes teribble race conditions, e.g.:mpv_command_async(['seek', ...]
is calledI know there are some workarounds, that can be done, to mitigate these issues, however they too cause risk of incorrect behaviour in case the state would get corrupted for some reason and introduce unncecesary complexity.
I think the DOM video API is a good example of how this should work, if you call
video.currentTime = 12371
the state is updated to the new value instantly (even if the player hasn't actually completed the seek yet), if one needs to know when the seek actually finishes then he can listen toseeked
event. But more importantly, notimeupdate
event will fire for playback progression that happened before this call was made.So my suggestion would be, to make
mpv_get_property()
,mpv_set_property()
and the majority of commands (like seek) complete immediately and just signal if needed a more detailed completion timing through events.Alternative behavior of the wanted feature
No response
Log File
No response
Sample Files
No response
The text was updated successfully, but these errors were encountered: