[SOLVED] 1st Plugin -- help with mpd play controls?

Hi Everyone,

I’m working on a plugin for Phish.in using the API. I have shows and tracks listing properly and playback working, but I am having a hard time understanding what methods I need to write to get MPD to read and navigate the queue correctly, and then to update the Volumio web interface. I’ve read the docs and how to use MPD for playback is still not clear to me. I’m a self-taught programmer so I don’t think I quite understand the explanation of the Volumio architecture.

I’ve used examples from the already written plugins (thanks for great examples: @piffio for tunein_radio, @marco79cgn for radio_paradise, and whoever wrote the quboz plugin) to write (or copy) some playback methods utilizing MPD: clearAddPlayTrack, seek, stop, pause, resume, next, previous, and prefetch (to enable gapless). Honestly, I don’t understand how (or why) some of these work and some don’t. The logs (from journalctl) don’t tell me much, and diving in to the docs and code isn’t elucidating anything for me.

Here are the playback problems I’m encountering:

[UPDATE]
Could not get this working with consume nor volatile states; instead controlling MPD within the plugin with added listener (used YouTube service as model). This resolves everything below. There is still a bug that can affect playback; fix submitted in PR #1583.
[/UPDATE]

  • The previous button restarts the track, rather than skipping to the previous track in the queue. I suspect that the MPD queue is deleting tracks from its playlist after playing, so MPD doesn’t have a track previous to skip to. I’m not sure how to always make sure the MPD playlist queue matches the one listed in Volumio.
  • Random and Repeat buttons don’t work after playback from Phish.in tracks have started playing. Looks like the plugin takes control once playback begins and maybe I don’t have code to make these buttons work.
  • The prefetch method has made gapless work, but Volumio now doesn’t update the interface when the new track begins, and then crashes when a button is pressed.
  • Mute resets the track timer for some reason. The track continues to play normally, but the timer is reset to 0:00 when mute is enabled.

My code is up on GitHub: https://github.com/lostmyshape/volumio-phishin. Can anyone help me work through these playback issues?

Thanks to all!
Noah

Okay… Talking to myself here.

Got the prefetch method to work. I forgot to get the mpd state and then sync the stateMachine. Now prefetch seems to be working and not crashing anything.

So for playback controls, I’ve realized that the mpd playlist is only 1 track long – the current track playing. Each time a new track is played, it gets info from the play-queue module and pushes the new track to mpd via the state machine module.

Here’s what I don’t understand, how does the state machine module know which music plugin is in control? It seems that on some methods (like seek, pause, resume, and prefetch) it uses the functions in my ControllerPhishin plugin, and on other methods (like next, previous, random, and repeat) it uses the MPD plugin functions.

Hope someone can help me with this…

I’ve never gone this far in my plugins, but I think it determines the service based on the service in the track object.
So if you add a webradio type track, controls will control via the webradio plugin etc. etc.

But I might be wrong :wink:

Thanks Saiyato… am I really breaking new ground here?

Haha… I keep answering some of my questions myself, which leads me to new questions! I’ve been examining the code for the state machine play controls, specifically play/resume and next/previous. See if anyone can follow me here:

There are a couple different if statements for next and previous. First both methods check the “isVolatile” property, and if true finds the next method from this “volatileService.” I don’t know what sets “isVolatile” to true. Is that when you use a player daemon that isn’t MPD, like spop?

For previous, it then checks the play status and does different things depending on if it is stopped, playing, or paused. For stopped or paused, is basically checks if random is on then resets the currentPosition. But if playing it then checks the “isConsume” property. I’m not sure what the “isConsume” property is indicating. Is it related to the setConsumeUpdateService method?

It seems that I need to set setConsumeUpdateService to “mpd” in the ControllerPhishin clearAddPlayTrack method so that the web interface will update correctly. Does that mean I’ve set the this.consumeState.service property to “mpd”? The function uses the this.consumeState.service property to pick the plugin to sent the next and previous commands to. That would explain why next and previous use mpd plugin methods instead of ControllerPhishin methods.

Lastly, play/resume instead use the trackBlock.service property choose the plugin to send the commands to. So, when pausing and resuming, Volumio uses the methods on ControllerPhishin.

Am I on the right track here? If so, what do I do to get previous to work? Can I somehow redirect the controls to ControllerPhishin instead of MPD on next/previous?

Hi Noah,

I had (and still have) exactly the same problems as you. Unfortunately I can’t really help you out but I can totally understand your problems and situation. It’s really frustrating and time consuming! I tested the volatile state a little bit but was never satisfied with the results which is why I’m not using it in the Radio Paradise plugin. Volspotconnect2 by Saiyato is using the volatile state so you might check his sources.

Actually I’m also struggling with modifying the default behaviour of the previous/next buttons. I’d really like to add functionality to skip and repeat songs in Radio Paradise but I didn’t find a solution for that since I’m not using volatile.

Let me quote lombardox here:

var oldNext = self.commandRouter.volumioNext; self.commandRouter.volumioNext = function() { var vState = self.commandRouter.stateMachine.getState(); if (vState.service == 'radio_paradise') { return self.next(); } return oldNext(); }
This is not really suitable imho. :frowning:

For updating the metadata I’m also using a little ‘trick’ as you can see in the pushSongState function (big thanks to lombardox who figured it out!):
github.com/volumio/volumio-plug … #L444-L493

Keep up your good work and let me know if you can figure out how to modify the controls.

Thanks marco79cgn,

I guess I’m glad I’m not the only one struggling with this. Thanks for the suggestions. At least those are a few new leads to chase down.

I wonder how michelangelo would like this to work. Can you guys explain the “volatile” and “consume” states? Before I mess around with them, I’d like to know what they are supposed to do.

Thanks again,
Noah

So I’ve read through the code for the volspotconnect2 and airplay_emulation plugins, plus the mdp plugin, stateMachine, and coreCommandRouter code. And got some help from the discussions in these helpful threads: First Plugin & some questions and Pushing playback state/info to Volumio. I think I have some idea now what the volatile state is and how it works. Please correct me if I an wrong!!!

When volatile state is on and a plugin is named as the volatileService, it becomes the plugin’s responsibility to collect the metadata for the track playing , and the metatdata for the state of Volumio (status, random, disableUiControls, etc.) and to push the current state to Volumio. Then when the track is stopped or the next track is played, to disable the volatile state and pass state update back to Volumio. During volatile state the plugin named the volatileService, also completely controls a bunch of playback commands, namely: seek, next, stop, pause, and previous.

I think that implementing this on my plugin shouldn’t be too difficult. Since the mpd plugin is doing the playback, I should be able to get the mpd state and then push it back out to Volumio. The plugin would have to be responsible for all playback methods and correctly disable volatile pass control back to Volumio at the right times.

Again, please let me know if I’ve got something wrong here! I’m going to try to implement this as soon as I get a little time.

Thanks,
Noah

Hi lostmyshape,

I don’t think you need to use volatile state. As I understand it, that is for managing the state when mpd is not being used as the player, and the plugin needs complete control over the metadata and playback controls.

I took a very quick look at your code and it seems you are sending mpd a url for each track, so the mpd plugin and default statemachine state management should be ok.

What you will need to use is ‘consume’ mode. This tells the statemachine that even if the current track belongs to one service, it should consume state updates from the specified service. You are already doing this in clearAddPlayTrack where you call self.commandRouter.stateMachine.setConsumeUpdateService(‘mpd’). As you are using mpd for playback you need to do that whenever you call something that will cause an mpd event to be raised. So for example in prefetch you need to call
self.commandRouter.stateMachine.setConsumeUpdateService(‘mpd’) again and possibly on seek, pause etc as well. The reason for this is the code in the statemachine which checks if the next track to be played is from the same service as the current one, if there it is not and consume mode is not in operation, the state update (metadata) will not be pushed to the client (the dreaded ‘Received update from a service different from the one supposed to be playing music’ message).

There shouldn’t be any need to call getState or syncState from your plugin, unless you are doing something funky other than normal playback.

Radio paradise needed a custom solution because each file being streamed contained multiple tracks so although the file was being played by mpd, the usual events from mpd that trigger metadata refresh were not being raised. Marco figured out how to run an internal timer in his plugin (especially hard due to the quirky nature of the RP API) which refreshed the metadata at the appropriate time.

Thanks lombardox! That’s what I initially thought and makes a lot of sense. Will that work with previous/next, though? When debugging my code I noticed that consume mode is on during playback (set by the clearAddPlayTrack method as you noted). And during playback the statemachine is definitely sending the “previous” command to mpd, but mpd will only restart the track and won’t move to the previous track. Maybe if I clean up my other playback methods this will function correctly. I’ll mess around with that tonight and see what I can get to work.

BTW, when I finally get this to work, I’d love to update the documentation in the Music Service Plugin to make this clear and easy for plugin developers. I think understanding consume and volatile modes is essential to get playback to work on a music plugin.

Thanks again,
Noah

I could never get consume mode to work properly, so I’ve abandoned that. Instead I used the technique in the YouTube plugin. Essentially, that plugin takes complete control of all the playback methods and adds a listener on MPD events to push new states to Volumio statemachine/UI. The only weird thing is then there are MPD listener callback functions on both the YouTube Plugin and the MPD Plugin, so you get a quite a few ‘Received update from a service different from the one supposed to be playing music’ messages. In the Phish.in plugin, the listener callback function should be deleted after the track switches to a new playback type.

I’m still running into a few bugs, though, related to gapless playback and the prefetch method. I’m hoping pintea can jump in and explain how they resolved these issues in the YouTube Plugin.

Bug 1:
First, because MPD is getting the mp3 via https from the Phish.in server, sometimes buffering on playback and often buffering on seek makes the timer get out of sync with the track. This is causing the timer to overrun the length of the track and messing with the CoreStateMachine.prototype.increasePlaybackTimer method in statemachine.js. The 2 “if” statements in that codeblock are satisfied multiple times because the timer overrun will make the remainingTime variable go negative. The 1st “if” statement will run after the time dips below 5000, then the 2nd “if” statement will run after the time dips below 500, as expected. But then the timer will go under 0, and both “if” statements will run a 2nd time. This will cause the next 2 (and sometimes 3) tracks to be added to the MPD queue playlist AND Volumio to push 1 or 2 extra tracks ahead in the Volumio UI.

For testing, I’ve fixed this by adding “remainingTime>=0 &&” to both “if” statements to stop this from happening if the timer overruns. But I don’t know what other effect this might have. Is this a good way to solve this problem? Or should I be thinking of a way to prevent the timer from getting out of sync with the track playback during buffering so there are no overruns.

Bug 2:
In the playlist queue, if there is a non-Phish.in Plugin track AFTER a Phish.in Plugin track, the next track will skip and move to the track after it and will be paused on that 2nd track. This is very similar to the effect/bug reported for the YouTube plugin here and here. Sometimes I get this same result on local tracks AFTER YouTube Plugin tracks, too. I haven’t done too much debugging on this yet and the console output doesn’t tell me much, but I think this might be related to the extra Listener callbacks and prefetch.

Any ideas on these? They seem to be related to gapless and the prefetch method. I think my Phish.in Plugin will work without these bugs if I leave the listener and the prefetch method out, but then I lose gapless. I’d like to have gapless since this is live music!

Thanks in advance for any help!
Noah

Yes please – I remember digging through the statemachine.js for quite a while to figure out what was going on to get volspotconnect’s metadata to work!

If you are using MPD to play, I would suggest avoiding taking over complete control - I think Marco’s approach with the RadioParadise plugin would be perfect for the usecase of controlling playback/metadata?

That being said - for complete volatile state and the dreaded ‘‘Received update from a service different from the one supposed to be playing music’’
check out what I hacked together for volspotconnect I recall quite some some cups of coffee wondering why things don’t work, only to realise the statemachine was ignoring me. :cry:

Are you also running into the bug of the timer resetting when volume is changed via the UI when in volatile state mode?

Oh, man, I wish I could do it that way! It would make so many things easier, but my plugin is fundamentally different from RadioParadise in that it lists multiple tracks with distinct urls rather than a single stream. You should be able to add and rearrange Phish.in tracks in the play queue as though they were local mp3s.

I’ve tried to use consume mode (as lombardox suggests above) but I just don’t think it works properly. It’s fine on webradio where you only need the play/pause button to work and to push data. But I just ran into so many bugs when trying to use previous, next, random, and repeat (previous and next don’t work at all when stopped; previous only repeats the track playing during play; random and next will turn on but you can’t turn them off nor are the random/repeat buttons in the UI updated). Plus I got that weird bug you mention with the timer reset when volume is changed/muted.

All of that went away when I use the technique that pintea uses in the “YouTube” plugin. Now I’m just getting bugs related to timer overruns (and this is something that I think could be fixed easily with a simple patch in statemachine.js). It’s not perfect, but the UI and all the buttons work the right way. The YouTube plugin seems to be the only currently published plugin that uses MPD to play non-local, individual tracks rather than a webradio stream (and not in a dedicated player, like spotify). I’d love to seen an example of a plugin like this that uses consume and has “previous” working correctly…

I think I’ve debugged this now , too, and it comes down to the “increasePlaybackTimer” method in “statemachine.js”. Here’s what I think is happening:

  1. in the 1st “if” statement increasePlaybackTimer calls the “prefetch” method for the plugin listed for the playing track’s trackBlock service (if it exists and the next track has the same service as the playing track) and sets the prefetchDone variable to “true”
  2. since the prefetchDone variable is true, the 2nd “if” statement will now run (after the timeout that reruns the function) and will reset the currentPosition and push the new state to Volumio
  3. now prefetchDone is set to true and WONT’ ALWAYS be reset to false in the syncState method
  4. Uh, oh, with prefetchDone set to true, we can have unintended behavior if the currently playing track’s service is different from the next track’s service
  5. because of this service difference the prefetch method will not be called, and no track is loaded (in MPD) for gapless playback
  6. BUT, because prefetchDone still is true, the 2nd “if” statement is still called as though a track is loaded, the position is incremented and this new state is pushed to volumio without anything loaded in MPD
  7. now the Volumio UI shows a few tracks ahead but nothing is playing

I think this is causing all the problems in my Phish.in plugin and in the YouTube plugin when you have tracks from different services in the queue. I was able to fix this behavior by resetting prefetchDone to false at the end of the 2nd “if” statements.

Does this make sense to anyone else? Could it cause any problems that I’m not anticipating?
If it looks okay, I’ll figure out how to fork and submit a PR for these (minor) changes.

Thanks,
Noah