-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
bpd: support idle command and with it MPD 0.14 #3205
Conversation
I've tested this implementation of There are some subtleties about when events are fired by MPD that we aren't matching here. In practice it doesn't seem to matter since we're sending the events at least as often as MPD does, and redundant events shouldn't cause any problems. For example, the In terms of the implementation, the way that this command works doesn't seem to fit very elegantly with eventlet's coroutine model. I was initially hoping that I could avoid polling for events and instead rely on a waitable event. I couldn't find a nice way to do that, especially once I realised that the client is allowed to send The tests are also a bit hard to manage since they need threading to be able to deal with the blocking request. The tests I've added seem fairly robust, but there's always a chance that they could spuriously fail due to delays in the thread scheduler. If that becomes an issue we can add small delays to fix the tests, or skip them on certain platforms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!! Thank you for investigating this, and for trying this out with real MPD clients. 👍
Overall, everything in this design seems copacetic except for the polling mechanism. That's not too bad, of course, and we could totally get by with this mechanism, but I'd love to dive in a little bit to understand why a poll-free (i.e., proper "push") mechanism wasn't feasible with the current Bluelet-based server implementation.
So with apologies for how basic this will be, can you help me understand what would go wrong with this notional implementation?
- In
cmd_idle
(i.e., when entering idle mode), just set a flag onConnection
: i.e.,self.idle = True
. Andcmd_noidle
clears the flag. - In
Connection.notify
, check whetherself.idle
and, if so, do the subsystem check andself.send
out thechanged:
notifications immediately. Otherwise, queue them up to be released when we next enteridle
state.
I feel like I must be missing something big & obvious, so maybe I should just try implementing that to see what goes wrong as an educational exercise. 😃
Keep track of a list of currently-connected clients. Use `socket.getpeername()` to get an identifier for each connection and include this in each log message. This function is documented as not being available on all systems, but it's unclear which systems this involves. Also log a message on client connect and disconnect events. If the disconnection reason is because the client sent a blank line, match MPD by returning a protocol error then hanging up. Escape curly braces.
Getting this command puts the connection into a special mode where it awaits MPD events (like the player changing state or the playlist changing due to other clients interacting with the server. The MPD specification states that events should queue while a client is connected, and when it issues the `idle` command any matching events should be sent immediately if there are any, or as soon as they happen otherwise.
To revise the above simplified/maybe-infeasible proposal, in light of the structure of Instead of calling |
Thanks for your pointers! I've had a go and managed to get most of the way there. The only remaining problem I can think of is that there's now no way for the player to trigger idle events, which is a necessary feature. That's because the new When the player finishes a song it calls the |
Aha; that is a very good point! Of course, most of the purpose of doing this in the first place is to be able to send notifications from the player. 😃 Here is a somewhat off-the-wall idea that might work: have the player thread (from |
I played around yesterday and tried two approaches successfully:
I think you're right though that a separate socket would be neater so as not to pollute the real interface. We could even shift over the debugging commands like |
A new `ControlConnection` is created each time a client connects over a new control socket. This is used to forward events from the player, and also for debugging utilities that are not part of the real MPD protocol. This new feature reuses as much infrastructure from the normal protocol handling as possible (e.g. `Command` for parsing messages). While the normal connection delegates to server `cmd_*` methods which are string generators, the control connections delegate to `ctrl_*` methods defined on the connection itself that are full coroutines.
Ok I've implemented a second socket server that I'm calling the control server. It delegates to I'll keep testing this out with my normal mpDris2/mpdscribble/mpc mixture, but it seems to be working as intended. We also pick up a new |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!! I'm thrilled this approach seems to be working! 💖 I have one tiny coding suggestion inline, but I think this seems basically ready.
Looks perfect! Thank you for all your heroic work on this. Is it ready to merge, in your view? |
Thanks for your reviews and ideas :) Yeah I've tried several MPD clients with the new implementation and pushed it around a bit with MPC and I had the crazy idea that this new control socket could be a way to disentangle the player from bpd. The MPD clients communicate with bpd on the normal socket which handles all the playlist and library stuff, and then bpd translates it into a very minimal protocol on the control socket. Besides cleaner code, it would allow us to control multiple players in a very natural way (each |
That decoupling is a pretty cool idea! The notion could be that a lower-level MPD-like protocol is useful for direct playback control, whereas the higher-level MPD protocol is what you want for proper user interfaces. Seems worth exploring! In any case, I'm excited about this initial version to support |
The
idle
command from a client signals that the connection should remain idle until an event occurs in the player, at which point the client should be notified. This PR adds support for the command by making the client's thread poll a list of events. The server can asynchronously populate the event list in response to requests from other clients.This is the next step for #800 and gets us to MPD 0.14.
Details from the MPD protocol specification:
Example excerpt of the log (in the new format introduced in this PR) from an mpDris session:
(NB: this shows that mpDris sends
close
when it's quit while in idle mode. Here bpd is replying with an error since it's not expecting that. It didn't matter since the client anyway quit, and the protocol says a client should just disconnect without usingclose
, which is now deprecated.)Remaining work:
See if there's a way to use bluelet here without polling. The only alternative I can think of is to open a new socket we canselect
and use that to wait for the MPD events.set
for the queued events).idle
ing (currently the coroutine stays alive in that case).mpc
.