-
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
redesign of mpv command input system (make mplayers' slave mode saner) #252
Comments
This looks interesting. It's worth noting that Windows named pipes can be used as an alternative to Unix domain sockets, since unlike anonymous pipes (which are the Windows equivalent of Unix pipes and FIFOs) named pipes are bidirectional, they can be message oriented or stream oriented, they can be read asynchronously, and they have a client/server model. In Qt, QLocalServer and QLocalSocket use named pipes on Windows and local domain sockets on *nix, so if mpv supported both of these, writing cross platform GUIs wouldn't require much platform specific code. |
Cool, so in essence they're the same as UNIX domain sockets? Are there any differences? It would indeed be a nice way to have a cross-platform local way to talk to mpv. Though I'm also thinking that TCP in general would be nice, since that would mean easy controlling of mpv over the network. Many UNIX daemons expose a domain socket because it avoids TCP overhead. Redis, MongoDB, postgres, mysql, they all do it. But in the case of mpv, we don't need to send a huge amount of data, so the overhead here is negligible. Since AFAIK the interface to f.ex. TCP sockets and UNIX sockets is exceedingly similar (BSD sockets), I reckon TCP should be implemented first. What do you think? |
My main problem with JSON is that you can't represent byte strings with it. Byte strings can contain invalid UTF-8, for example if the source uses a legacy encoding, instead of UTF-8. This affects things like chapter titles, metadata, and even filenames. With JSON, you'd have to transfer them as char arrays. TCP is out. It requires lots of platform specific code, which I just removed. Windows pipes still require some extra code, but it's already there. I like the idea of supporting a simple protocol; it boils down to allowing the "old" protocol along the new one. |
Does that mean mpv can no longer read from HTTP streams and things like that? HTTP goes over TCP. It seems contradictory to me, as I thought the libquvi integration was for getting clips from youtube and the likes... What about the UDP based synchronization that mplayer boasted? I was planning to use that for some cool stuff as well, has that been ripped out? EDIT: I just tested it with a mpv build from master and streaming from HTTP still works. So I wonder what's different. Is there perhaps some external library like libcurl used for HTTP-only? |
mpv uses ffmpeg's http implementation. libquvi does its own I/O and uses libcurl.
This was never merged into mplayer2/mpv in the first place. It looks like this stuff uses sockets manually (rather than layering on mplayer's network code), so it could easily merged... if someone really thinks this is useful. |
That's cool. The way I "understood" the concept was that it allow the synced playback of the same video on many different screens, which I could use on an airport project. But that's for later. To get back on topic:
Can't we just convert to UTF-8 here? I mean, otherwise we'd have to specify the encoding in the protocol and it would mean that any client apps would have to deal with any-to-UTF8 encoding, which seems nasty to me. If at all possible I would even suggest a |
Yes, the main problem is the actual wire protocol. The higher level protocol transports, i.e. what the wire protocol is used for to implement, isn't even interesting; I think it can just wrap the slave commands (those used by input.conf).
It should be something that is simple to parse. If the protocol comes with a library, the library should be either ubiquitous, or simple to reimplement by anyone. Here are some more: the dbus wire protocol (relatively simple), google protobufs, Thrift. See also: http://en.wikipedia.org/wiki/Comparison_of_data_serialization_formats I guess one additional requirement we have to put is: it should allow coexistence with simple input commands. But this could be realized on the transport layer, so to speak. |
It is nasty, but you can't detect everything reliably. Usually it's either original data or mojibake, and then I'd prefer to send the original data, and leave the decision what to do with it to the user. With filenames, you can't actually do much. Filenames come from the disk, and can easily be in a different encoding. This often happens when reading from older disks or when unzipping random files from the internet. |
If I understand correctly, the issue is just that the encoding of the characters (I assume chapter information and the likes) is not standard and more importantly, not included in the video. As such, one can only guess at the encoding. (reading up on "mojibake" it seems to be just what happens when you guess the wrong encoding for something, I just had to deal with a lot of double-UTF8 encoded strings in a personal project, for example (which one cannot render correctly)). In that case, I guess it's indeed better to just pass the raw byte array to the application. Which is another notch on the belt of the TLV formats, and it really does disqualify JSON, as JSON only supports UTF-8, 16 and 32.
Yea, not to mention that on UNIX systems no specific encoding is enforced, anyone can set their locale to anything and start filling in weird file names. I guess passing byte arrays would indeed be the most fitting option. |
Anyway, we just have to choose a protocol, then we can implement it. Currently, I'd tend to bencode, but maybe with custom extensions. I'd like to add a float type, and eliminate the need to sort dictionaries by key. I'd also prefix strings with a type code to make it easier to parse (e.g. To the higher level layers of the protocols, I wouldn't add much. I'd add sequence numbers to each request and reply (similar as planned for libmpv), and add a way to track property changes. |
I don't think that's really necessary, unless a certain command can return both strings and integers, for example. Which I wouldn't do. If one command can return one type (and accept one fixed types, of course), then that's simpler for everybody. Id est: you know what you're gonna get. Of course there should be a special
Sequence numbers would indeed be extremely useful, my browser plugin for mplayer just prays that the next response is the good one. Which is not nice at all.
Something like that? |
Well, bencode already supports distinguishing strings from integers. It's just that when parsing, you had to check whether the first character was between '0' and '9' to know that it's a string, while all other types has a single character to identify it (like 'i' for integers).
Yes. Though with properties I meant properties (which are a separate mechanism, see input.rst), while events are somewhat separate. |
One attempt at defining the lower parts of the protocol (using bencode): http://privatepaste.com/d1f1bec5eb Another attempt, after I felt that using bencode is too bloated and overengineered: http://privatepaste.com/295754ec5b (but still has the issue that you can't pass structured data) |
Nice exploration of the options, I'm gonna need to mull this over before I give a serious opinion. |
I'll probably implement the second option (http://privatepaste.com/295754ec5b) at some random point, whenever I feel like working on it. Still not sure what to do about complicated information like chapters, I'll just make something up on the spot that will at least half-work. (This issue has been annoying me from the very start because it's trivial yet I don't know how to solve this. So it's probably still better to solve this via an ad-hoc hack, instead of not at all and holding this back forever.) |
The pastbin died, repost (I hope it's the same): http://sprunge.us/CFJa I still don't like this option much... |
I contacted the team privately a while ago asking about slave modes for developing a GUI. The GUI is currently working using the older slave mode (check it out if you want: https://github.com/sebadoom/mpvguihs). I have reached a roadblock as I need more functionality from mpv/slave mode to further advance the GUI (subtitle/audio track lists, chapter lists, etc.). I'm willing to help on this if we can finally define what needs to be done. I tend to favor a simple approach such as JSON + a simple mpv-specific RPC mechanism. Of course something fancier could be in order as well. Regarding binary data (such as chapters and filenames) we could conceivably encode that as uuencode or base64 or something like that (yes, that would result in longer streams of data, but would keep the protocol simple and the use cases dictate it wouldn't be a problem at all, I believe). IMO, there's no point in developing yet another data format for such a simple use case as this. That problem, as I see it, is already solved, we just need to pick the right tool for it. I also tend to favor zeromq over sockets, but that's just me. Sockets remain simple enough that anyone could use them. Pipes are really cool because we could conceivably send commands from the command-line without external tools, right? A command could remain as simple as User readable (and writable). Parsers are readily available (no extra dependencies, many could be integrated directly into mpv). Current commands could easily be wrapped in something like this, I guess. Edit: clarified a couple of points. |
We could do that. JSON is not that easy to parse though, and there's still this problem that it's hard to put data into JSON without encoding them as byte arrays or without extra mechanism to mangle strings to pretend the data is valid UTF-8.
Maybe so. That would require treating all strings specially (except maybe dictionary keys, which are also strings), which would probably be pretty annoying for the user of the protocol. Personally I'd probably prefer URL encoding for strings instead of base64, though. (URL encoding is trivial to implement, even simpler than base64.) Alternatively, we could do the same thing Python does with filenames that have invalid UTF-8 (basically the same as our problem). Bytes which can't be decoded are escaped as certain reserved code points, details see http://www.python.org/dev/peps/pep-0383/. The problem with this is that the resulting string isn't valid UTF-8 either (and will probably be rejected by most JSON parsers), and it's slow and complicated too. In general, this doesn't seem to make JSON a good match. Although JSON is relatively simple, it's not the most simple to parse format either.
The idea of using JSON is not exactly new, and I've looked for parsers myself. I haven't found anything that was light-weight, correct enough, and bullshit-free enough. I'd probably write a parser myself, but that's not the problem. But I'd say that your claim that this problem is already solved is false, unless you pull in a rat-tail of dependencies and correctness issues (such as handling invalid UTF-8, which some people apparently just refuse to acknowledgde that it's a problem that needs proper handling).
Yes, we could add a zeromq transport. In fact there was someone who did that, but it wasn't merged. |
PS: by URL encoding I mean the percent encoding, that is commonly used to escape URLs, e.g. |
PS 2: of course I don't want to be stuck forever in designing a new slave mode, so if you think JSON + mangling strings is the way to go, we can do that. |
Hmm, I disagree. I know these ideas are not knew, I've read the ticket. This (https://bitbucket.org/zserge/jsmn/src/09ad3d4559ea3e5b1b93cd90350e1909534635dd/jsmn.c?at=default) appears to be a JSON parser in less than 300 lines of C89 (no dependencies, not counting headers, MIT licensed). It does not do UTF-8, though. I guess it comes down to how much work you want to put into fixing/integrating an external parser (do we care about other dependencies? do we want to depend on it or integrate it in the code? do we need an industrial-strength implementation? Are we willing to spend time fixing an implementation?). Additionally, I am not clear as to why all strings would need to be escaped/encoded. You would normally know if there is or could be unsupported data in your stream, so you would only need to encode whatever strings are necessary. Even if you didn't know, it would be trivial to add a check before encoding. Edit: TBH, I don't really care about the transport as much as the data-format. Whatever transport we pick (files, sockets, pipes, zeromq, shared memory, sysv-ipc, etc) is fine by me. Edit 2: Actually I'm against shared-memory as a transport. |
There are also other data formats that are available and work fine. The redis format described above looks like a good compromise. Protocol-buffers are cool too, but I don't really think requiring an IDL would be a good idea for mpv (unless we want a full RPC-like mechanism, then an IDL becomes ideal (since this is C after all)). |
Hmm, thinking about this other ticket you mentioned (issue #97), you could conceivably combine that with this. You could pick, say, protocol-buffers as an RPC mechanism. Develop the IDL based on the final C API you want. Publish both the IDL and the C API. Clients could use the C library (that is, what I think you want in ticket #97) or protocol-buffers with their language of choice. They would get the exact same functionality. For free. Edit: and you get binary encoding, too. You'd lose a user readable input command stream, though (a simple command line client could be developed for that). |
Yes, this was the most light-weight one I could find too. Most others get really awkward with trying to define their own dictionary data structures etc. in C.
It does, but it lacks some other syntax elements, Anyway, this is not really a problem. I'm just saying that just taking some protocol won't actually solve the oustanding issues.
How else would you distnguish strings that are passed literal, and strings that have been mangled?
Well, the most interesting issue is really how nested data like chapter lists etc. should be handled. This is a problem with issue #97 too, but there I'd probably just export these as special cased data structures.
Yeah, but I don't really want to deal with code generated from IDL and all that. And protobuf's C generator is third party code too, i.e. not directly supported by protobuf.
Also sounds possible, but I think this would be actually more work. For instance, we'd have to finish the C client library first. Anyway... what should we do now? Send JSON with mangled strings over zeromq? Finish libmpv and bolt a RPC layer using some middleware on top of it? Something else?
For that, the old input.conf style command processing could still be used. It's still needed for key bindings after all. By the way, I think in any case it's better to reuse the infrastructure behind input.conf (like commands and properties), instead of writing new command handlers etc., though this is mostly orthogonal to what we're discussing. |
I was thinking about perhaps making it clear in the docs for cases where we know that strings are mangled. Or publish a flag along with each potentially dangerous string. Or use a header? (uuencode does that, though it's been criticized precisely for that reason).
I tend to dislike code-generators too. Having worked with RPC mechanisms in C before, though, I have become to appreciate their usefulness in non-reflective languages.
Just an example. There's Apache Thrift, CORBA (yeah, I know), etc.
True.
Having little say and experience in mpv development, this is probably up to you and other developers to decide. I'm mainly concerned about how easy it is to use whatever you pick :) |
Also, maybe it would actually best if we advance libmpv, and you use it? Or is there a reason mpv has to be in a separate process? |
Nope, it's OK. It should work either way. |
Good, maybe this makes it simpler. This brings us to the question how the client API should export things like chapters (or anything else that isn't just a flat string). Maybe we should continue in #97. |
Also, there is 9P (http://en.wikipedia.org/wiki/9P) — posix/plan9 |
Isn't it simpler to just use D-Bus (http://www.freedesktop.org/wiki/Software/dbus/) like every other application on GNU/Linux, than to reinvent the wheel? It runs fine on OS X and Windows as well and has excellent API support for just about every programming language/framework around. |
I think dbus would make everything much more complicated. It also doesn't run very well on OSX and Windows; and we're already using too many Linux specific libraries that suck on other platforms (like fontconfig). |
There's a client API now (see https://github.com/mpv-player/mpv/blob/master/libmpv/client.h), and a slave protocol could be built on top of it. |
@wm4 it looks very well designed, props! After reading the comments in the file though, I'm still not entirely sure if it's possible to use this API while having mpv in a separate process. The thing is: I have a browser plugin that runs a (currently) mplayer instance for every video it encounters, this allows the mplayer process to stay separate and clean up after itself, it works wonderfully. Since this plugin needs to work for months on end (best uptime for a player units is 4 months, up until now), I'm very hesitant to change this setup. Which is why I would like to emulate this with the better, more modern mpv. Therefore I ask: is it possible to spin up an instance of mpv and still somehow use this API? Or is it such that I will need to make another executable, e.g.: mympv. Which internally embeds the player and communicates with the browser plugin? It seems like a lot of work just to get what mplayer already offers: communication via pipes or stdin/stdout. I'd prefer the real deal: communication via an API. |
It would still be possible if someone made a library with the same interface that uses a protocol to communicate to a separate mpv process (that is, as soon as there's an actual slave protocol). This would probably work ok for the asynchronous functions, but wouldn't make too much sense with the synchronous ones (too high latency, I suppose). But it still could be implemented. But first there needs to be a client protocol. Currently I'm thinking this could be done via JSON. I dabbled with JSON already over a month ago, didn't like it, and abandoned it. I may or may not resume this. By the way, I'm wondering: if there is a client library that can talk to mpv remotely, it should probably be a separate .so, right? I don't think providing a way to transparently to switch between the two APIs in a driver-callback like way would be useful due to the different performance and latency characteristics. Even if someone wants that, it could be implemented as a third .so (evil). |
It kind of depends on the protocol, a .so sounds nice, but if the protocol is sufficiently simple I would be tempted to do it in a single source file, so it can be included very easily in-tree or in a /deps folder clibs-style. |
Yeah, probably. The hard part is making it work on both win32 and Linux. That will probably require lots of, erm, stuff. |
Very right, as a matter of fact, for someone demanding cross-platform-ness even between non-POSIX systems, a .so/.dylib/.dll approach would probably be best. One doesn't preclude the other though. In fact, there could be a mpv_comm_posix.h and mpv_comm_win32.h with a mpv_comm.c file to instantiate it all. A makefile could be supplied to build a .so/.dylib/.dll depending on the selected OS (which would select the correct .h file), and projects wanting something more lightweight could just |
If that means the -udp-slave and -udp-master switches, yes, those are useful even on just one computer to get multiple output windows. Doubtless there should be a better (less CPU-wasting) way to get an arbitrary number of output windows, each of which should probably be able to run with arbitrary output drivers, but I suppose that would be a different topic. That way it would only have to be decoded once as long as you were using one computer, like with VLC's clone filter. |
We now have libmpv, and JSON-based IPC on top of it, so this issue is obsolete. UDP synchronization is discussed in #1272. |
NOTE: this issue is just a proof of concept, and perhaps we could turn it into a wiki page, any input welcome
Yesterday, me and @wm4 were talking about ways to improve on mplayers' slave mode. The current documentation mentions that it's insane, rightly so. Many apps that use mplayer resort to parsing the status line and other assorted messages intended for users to get the necessary information to display in their GUI or respond to mplayer. This is obviously bad: bug ridden, non-backwards and forwards compatible and wastes an unholy amount of CPU cycles. @wm4 says he's been brainstorming about this for some time without reaching a consensus, so I'd like to help. This issue was made to consolidate the possible approaches and finally reach a true consensus. Any comments welcome.
requirements
hard requirements:
nice to have:
echo "loadfile myvideo.mkv" > somefifo
can still work). Sending back structured, binary safe information almost forces one to use a TLV-style encoding, but perhaps there are some encodings which provide a fallback (and indeed there are, see the redis topic)There are 3 separate things here that need to be thought of: communication channel, encoding (format) and protocol. The communication channel is a totally separate layer from the other two, and should be coded this way. The encoding and protocol will most likely be related to and influenced by each other. Though ideally they should be separate.
communication channel
Many types of communication channels can be supported at the same time, perhaps any one of them should be enable-disable-able with a configure switch. This works well with the greedy configure approach currently taken.
A list of some possibilities (some of them already implemented):
mkfifo
et al - posixencoding
JSON
Disadvantages:
echo '{ "command": "load", "argument": "/home/shared/videos/coolmovie.mkv"}'' > mpvpipe
looks cumbersome.Advantages:
Precedents:
Libraries:
Another criticism of JSON is that the parsers are bulky and not very performant. And that they fragment the heap with many small allocations. Nobody likes that. Fortunately it appears that this need not be the case. For example
jsnm
is a lightweight single-file parser that doesn't even require libc, and doesn't require allocations: https://bitbucket.org/zserge/jsmn (the source is easy to read too, about 200 lines of code, have a look).Bencode
Advantages:
Precedents:
Libraries:
Redis-style messages
Advantages:
Disavantages:
Precedents:
Libraries:
How to flesh out a proof of concept
For anyone looking to do some work on it, @wm4 has mentioned that there's mainly 2 important files to look at:
The text was updated successfully, but these errors were encountered: