First Plugin & some questions

Hi,

I’m currently trying to build my first plugin which is kind of a web radio station (radioparadise.com). It’s the one in lossless FLAC which sounds really nice.

The thing is that it is no static URL where you can listen to the stream. Instead, you have to query their API to get a json response containing the first stream url. This url (=flac file) includes the first 1-8 songs. The response also contains the event id of the following event. You have to query the API again with this following event id to get the next file with the next 1-8 songs and make sure to play them after the first one finished. And so on. So in order to simulate an endless stream (aka web radio), you have to think about a way how to handle this in Volumio.

How would you do that?

I already implemented it as a music service and successfully obtained the first event playing the first songs (via the explodeUri function).

But how should I add the following events to the Queue?

One solution might be to add the first two events directly at start and then listen to every event when a new track starts playing (via Websocket). Then I could check whether it’s a track from my service and if so, get the next event and add it to the queue. But this doesn’t sound so smooth, does it?

What do you think?

By the way, here’s the snippet of the explodeUri function to get the first event (including info of the next event url to poll).

[code]var url = ‘https://api.radioparadise.com/api/get_block?bitrate=4&info=true’;
var streamLength = 0;
var nextEventApiUrl;
// FLAC option chosen, getting first event
self.getStream(url).then(function (eventResponse) {
if (eventResponse !== null) {
var result = JSON.parse(eventResponse);
var streamUrl = result.url;

    if (streamUrl === undefined) {
        streamUrl = null;
        self.errorToast(station, 'INCORRECT_RESPONSE');
    }
    // the first stream event url to play
    streamUrl = result.url+'?src=alexa';

    response.push({
        service: self.serviceName,
        type: 'track',
        trackType: self.getRadioI18nString('PLUGIN_NAME'),
        radioType: station,
        albumart: '/albumart?sourceicon=music_service/radio_paradise/rp-cover.png',
        uri: streamUrl,
        name: 'Radio Paradise FLAC',
        title: 'Radio Paradise FLAC'
    });

    // the length of the first event in seconds
    streamLength = result.length;

    // the url needed to retrieve the next stream event
    nextEventApiUrl = url + '&event=' + result.end_event;
}
defer.resolve(response);

});[/code]

//UPDATE:
You can find the source code on my github page, including an explanation of the Radio Paradise API. The current version plays all normal (non-Flac) streams as expected and with the Flac option it plays the first two events.
https://github.com/marco79cgn/volumio-plugins/tree/master/plugins/music_service/radio_paradise

I think you will need to use “volatile state” which means that the plugin is telling Volumio that it will manage things like moving to the next track and building the object contrasting the meta data for the Ui to display. Have a look at the volspotconnect2 plugin to see how they do it there.

If you fork the Volumio_plugins project on github and commit the code you have written so far it will be easier for people to help.

In the json comes the duration of each track and the total duration of the list, can not that be used?

OK, I’ll have a look at it and see how far I can get, thanks.

I already forked it and you can find the code on my github page, including an explanation of the Radio Paradise API. The current version plays all normal (non-Flac) streams as expected and with the Flac option it gets the first two events.
https://github.com/marco79cgn/volumio-plugins/tree/master/plugins/music_service/radio_paradise

Indeed but this doesn’t help a lot. I have to get feedback from the player because at any time, the user might pause the stream, seek to another position (which btw crashes Volumio at the moment) or switch to another track/service. I need a concept first how to implement this in Volumio. The worst option would be to pull the current play state every second or two. And even if so, I have to add more streams to the play queue over time.

I already built a web browser version in plain Javascript and with the help of howlerjs.com it was quite easy to implement an endless stream, see this gist (which I optimised at the end to get the next event in advance so that it’s ready to play before the previous one ends):
https://gist.github.com/marco79cgn/25719af25ba7b73b84c72947c84b6c46

Excuse me, mine is only interest in helping.
Did not you look like they do the plugin for LMS Squeezbox? I seemed to read that they were with the same problem. https://forums.slimdevices.com/showthread.php?108189-Announce-Radio-Paradise-Lossless-Streaming-(Plugin-v2)/Page40

Good progress, so far!

So you are using MPD to retrieve and play the stream. You can add items to the MPD queue (this is separate to the Volumio queue) which you can use to implement the same ‘pre-fetch’ that you have done in your javascript version.

             self.mpdPlugin.sendMpdCommand('add "' + nextEvent.uri + '"', [])
                .then(function () {
                    return self.mpdPlugin.sendMpdCommand('consume 1', []);
             });

To notify the UI with updated metadata use the commandRouter.servicePushState method.

//change this to something that makes sense for Radio Paradise
 self.state = {
  status: 'stop',
  service: self.servicename,
  title: '',
  artist: '',
  album: '',
  albumart: '/albumart',
  uri: '',
  //icon: 'fa fa-spotify',
  trackType: 'spotify',
  seek: 0,
  duration: 0,
  //samplerate: 'Volspotconnect2',
  //bitdepth: 'HQ',
  channels: 2,
  streaming: true,
  disableUiControls: true
};

    self.commandRouter.servicePushState(self.state, self.servicename);

I think you will have to run an internal timer within the plugin based on the duration of the Radio Paradise event, so that you do a ‘pushState’ at the correct time - for example when the duration of the individual RP tracks within an event has elapsed. The timer would also let you know when the next event needs to be retrieved and added to the MPD queue.

You will also need to push the correct state when the stop button is pressed - and also unset volatile at that point. I believe volspotconnect2 plugin would be a good guide for this as they use the volatile state although they do not use MPD to actually play the tracks.

Hope that helps!

First of all, thanks a lot for your help, both of you - very appreciated!

I managed to get the “endless stream” working now (which took me half a day to figure it out). :sunglasses:

This is done with the help of the WebSocket API. I found this solution in the AmpSwitch plugin (thanks to user Fightclub).

Once my plugin is started, it is listening on the ‘pushState’ event. This is emitted by the server after each state change (play, stop, etc.). As soon as a new rp flac event starts playing for the first time, I query the next event from the RP API and add it to the Volumio playlist queue once. This way I can make sure that the following event is already there a few seconds after the previous event starts playing. So the transition from one event to another is quite smooth.

The metadata (artist, title, cover) is not implemented yet. But what’s already working is:

  • endless flac stream
  • seek to a position inside an event
  • skip to the next event
  • repeat the current event

The changelog of the source code is here: https://github.com/marco79cgn/volumio-plugins/commit/6c9072dc8483ca034c82ad708f13e02e7ec3d51a

I also took a look at the VolspotConnect2 plugin but this seems a bit too much for my simple use case (e.g. a dedicated daemon is needed etc.). I hope I can figure out a way to show and update the song metadata. Have to get some sleep first. :smiley:

I’m currently stuck with the display. As you can see in the picture, the artist and title is not shown on the main screen even though I set them. They are correct in the playlist queue view. The main view always shows the “file name” (the last part of the url).

  • How can I manage to show artist on title instead of the file name?
  • Is it possible to disable the possibility to seek manually (like in any normal webradio stream just showing the green circle instead)?
  • Is it possible to override the behaviour of the prev/next buttons so that they will skip inside the same event (to a given seek position) instead of playing the next item of the playlist (= next event)?

It’s working by using this in the clearAddPlayTrack function:

return self.mpdPlugin.sendMpdCommand('play', []).then(function () { return self.mpdPlugin.getState().then(function (state) { return self.commandRouter.stateMachine.syncState(state, self.serviceName); });
// UPDATE:
Arrgh. But this way, the stream plays endlessly and doesn’t skip to the next one. Why is that? :blush:
If I just use this option, I don’t get track metadata but only the filename - but playing the next track of the playlist queue works here!

self.commandRouter.stateMachine.setConsumeUpdateService('mpd');

I still don’t know, probably not. Setting disableUiControls: true doesn’t change anything. When syncing with the mpd state machine I managed to fix the seeking inside a track by setting the correct length of the track but as I wrote before, each stream plays endlessly then (without jumping to the next queue entry. :frowning:

Probably not without the volatile state. I’ll try to implement this as a next step (but am unconfident to succeed).

What’s already working:

  • all normal streams (MP3, AAC)
  • the FLAC version playing endlessly (only with setConsumeUpdateService(‘mpd’) and broken metadata)
  • seeking to a position inside an event (via circle on the left side)
  • skipping to the next event (not yet to the next song inside an event!)
  • repeating the previous event(s)
  • track metadata of each first song of an event

What’s not yet working:

  • updating the metadata during an event (always the first song is shown)
  • skipping to the next song inside an event (only by seeking to a position via circle)

I’m stuck. :frowning:

This happens at the end of each track. Any ideas? Looks like a bug in Volumio.

I’ve seen that error in a number of logs but don’t know the cause sorry: hopefully Michelangelo will be able to add more info.

Ahh the dreaded Statemachine is rejecting your state, welcome to the club!

Look at volspotconnect2 – you need to unset the volatile and then push your state. I am travelling now, but can have a crack at it soon :slight_smile:

EDIT: I took a quick peek at your code - correct me if I am wrong, but you use mpd to play your flac stream right? In that case, you don’t need to mess around with setting the state to volatile as with volspotconnect2.

Thanks for looking into this!

I tried to play the flac verison like this (in clearAddPlayTrack()):

if(track.uri.startsWith("https://apps.radioparadise.com")) { // FLAC return self.mpdPlugin.getState().then(function (state) { return self.commandRouter.stateMachine.syncState(state, self.serviceName); }); } else { // other normal streams self.commandRouter.stateMachine.setConsumeUpdateService('mpd'); return libQ.resolve(); }
This way, at the end of each track I get the error with the different service, everything else works.

If I play everything the “else” way (pure mpd), then the automatic skipping to the next track in the queue works, but I have no track metadata in the UI, just the (ugly) filename.

Either way, as soon as a new track starts, I listen to this ‘Play’ event (via Socket) and collect & add the next track to the Volumio playlist queue (socket.emit(‘addToQueue’, nextEventToAdd);). That’s basically everything I do at all.

Hi there,

I think you have a few issues.

Don’t use sockets - this is only for communicating with other devices, the plugin is running as part of the Volumio process so no need.
Don’t add individual tracks to the Volumio queue, you should just be adding one queue item ‘Radio Paradise Flac’ or whatever.
The mpd queue is a separate thing and is probably what you need to use.

You’re passing the uri of a flac file to mpd and asking it to play it. The single flac file contains several tracks. I could be wrong about this but I don’t think mpd issues any state change notification (unlike normal webradio) when one track finishes and another starts. That’s why I’m thinking you need to push the state from within the plugin.

spyking might be best placed to advise you on how to get the volatile side of things working correctly with seek, stop, resume etc.

The code might look something like this:

[code]
function pushSongState(song)
{
var self = this;
var rpState = //generate new state from song;
//do volumio push state here
self.commandRouter.servicePushState(rpState, self.servicename);
}

function playNextTrack(songIndex, songsArray){
var self = this;
pushSongState(songsArray[songIndex]);
//if not the last track
setTimeout(playNextTrack.bind(self, songIndex + 1, songsArray), songsArray[songIndex].duration);
//else get the next block of tracks from RP and add another item to MPD queue
getMoreRPTracks()
.then(function () {
return self.mpdPlugin.sendMpdCommand.bind(self, ‘add "’ + newStreamUri + ‘"’, []);
})
.then(function () {
return self.mpdPlugin.sendMpdCommand(‘consume 1’, []);
})
.then(function () {
setVolatile();
playNextTrack(0,songsArray);
});
}

function clearAddPlayTrack(track){
var self = this;
var songsArray = listOfTracksFrom RP API call;

//add and play stream uri on MPD, don't use setConsumeUpdateService('mpd')
return play.then(function(){
    setVolatile();
   playNextTrack(0, songsArray);
}

}[/code]

Thanks lombardox for the tipps and even the code snippets. Very appreciated!

With my multiple tracks approach I used it to have a way to get notified about track changes (without having to pull the state every second or two). I didn’t know better. Basically everything I did was somehow “reverse engineered” and copied by other plugins/approaches because I didn’t have a clue. An API documentation would be awesome and save a lot of time.

Actually that’s my biggest problem. I have no clue how to notice when the songs inside a track change. The “setTimeout” would only work if the stream will play continously - as soon as there’s a user interaction (pausing/seeking/etc.) I would have to stop the timeout process and start a new one based on the changed conditions. Probably the best solution would be to disable the seeking option completely (if possible) and change the prev/next button behaviour to skip to the next song inside a track (using the volatile state which I didn’t completely understand yet). Song changes without user interaction would have to be pulled somehow (setTimeout or setInterval->checkCurrentElapsedTime). Hmmm…

Intersting api radioParadise has, here is an idea for the track group look into the Statemachine’s prefetch() method.

EDIT: The state machine also references something called a trackblock - maybe that might be interesting for you?

I implemented a manual prefetch strategy by getting the next event url (songs) as soon as the last song of the previous event starts. Works quite good.

It’s possible in the RP apps to skip a song (if you don’t like it). Is it possible to change the default behaviour of the previous/next buttons in the UI? How can I listen to a button pressed event of those two buttons?