[PLUGIN] Radio Paradise

@Adrian:
Sorry, my bad. I mixed album with artist. Please try this updated index.js:
github.com/marco79cgn/volumio-p … e/index.js
or just use the updated zip file:
dropbox.com/s/x2v43xjp5y8sh … 4.zip?dl=0

@michelangelo:
Absolutely, that‘s my intention. I just wanted to let some people test it before in order to make sure it works and doesn’t break anything.

Hi Marco

I updated index.js and the status values reported by the plugin are now like the webradio ones, and are displaying well on the OLED.

Adrian.

Thanks for the feedback to all of you so far.

I opened a Pull request in order to make the Plugin available in the Volumio Plugin section so that you can install it from the browser/GUI.

:arrow_right: github.com/volumio/volumio-plugins/pull/206

Well done Marco, the plugin is very well done! Congrats!

The only feedback I can give you is: saw it depends from lots of node_modules, in future releases it would be good if you could strip them down to the bare minimum.

The plugin is now published to Volumio’s plugin repo, well done!

Hi Marco,

Congratulations for this great plugin and for getting in it into Volumio’s standard plugin repository superfast.

I now switched to the official plugin - and you know - i use Adrians OLED solution. (updated to the latest version by hand as there unfortunately is no plugin for it at current )
Now i get text on it but unfortunately it is of this format:


Interpret
Interpret - title

… where i see the interpret twice what doesn’t make much sense IMHO
should be:


Radio Paradise
Interpret - title

Don’t know if you can change this or Adrian.

Anyway i’m excited.
Thank you again and thank you Adrian and certainly thank you Michelangelo.

Admins please forgive me to post a very similar info on Adrians topic - i simply don’t know who of both can fix that.

  • Josef

Congratulations Marco! I just watched and installed the official version of the RadioParadise plugin.
Now Volumio is complete :smiley:

Thank you. Congrats also go to user lombardox who helped a lot :exclamation:

Thanks for the feedback. I removed the unirest dependency which was only used once for a simple http GET request. I’m doing this now with native nodejs functionality and the node_modules size was reduced from 14 MB to 1.7 MB. The only dependency I’m using now - besides the volumio ones - is the Nanotimer (which is only 11 KB and has no transitive dependencies). I opened a new pull request for update 1.01:
:arrow_right: github.com/volumio/volumio-plug … /207/files

I guess Adrian or you have to change this. The last feedback I received from Adrian was that everything works as expected now, see
post48790.html#p48790

Thank you for your feature request! I never heard of Radio Paradise before and really like it a lot now. :slight_smile:

So glad this plugin is now in the Volumio plugins built in, working fantastic with FLAC, only issue I see is that it doesn’t scrobble using the LAST fM plugin as all other tracks do, including all my Spotify, deezer plugins etc.
Will it be adapted to use last fm plugin?

From what I can see in the source code of the Last FM plugin is that it simply doesn’t listen to my service name (radio_paradise). All the necessary information should be there as you can see in the logs:

May 17 15:31:15 volumio volumio[21957]: info: STATE SERVICE {"status":"play","service":"radio_paradise","type":"track","trackType":"flac","radioType":"rparadise","albumart":"http://img.radioparadise.com/covers/l/B000002KEP.jpg","uri":"http://apps.radioparadise.com/blocks/chan/0/4/1699491-1699495.flac?src=alexa","name":"Little Feat - On Your Way Down","title":"On Your Way Down","artist":"Radio Paradise","album":"Dixie Chicken","streaming":true,"disableUiControls":true,"duration":322,"seek":0,"samplerate":"44.1 KHz","bitdepth":"16 bit","channels":2}

I can’t use service type ‘webradio’ for the flac version as it breaks my metadata changes (because ‘webradio’ is handled in a special way by volumio/mpd).

The easiest way would be if Saiyato would add my service name and handle it as a webradio. Since I changed the artist name to “Radio Paradise” (because of the OLED feature request by Adrian), the correct artist has to be parsed from the ‘name’ attribute.

I’ll contact Saiyato.

Thank you Marco, I’m sorry if it’s causing you more headaches, it’s perfect as it is but if the developer of last fmplugin could do this for you/us that would be great.

Thanks again.

Those are actually only a few lines of code. It works flawless on my Volumio machine already.

If you feel comfortable with SSH, you can already test it by replacing the content of the file
:arrow_right: /data/plugins/miscellanea/lastfm/index.js
with this:

[code]‘use strict’;

var libQ = require(‘kew’);
var libNet = require(‘net’);
var fs = require(‘fs-extra’);
var config = new (require(‘v-conf’))();
var exec = require(‘child_process’).exec;
var net = require(‘net’);
var os = require(‘os’);
var currentMac = ‘’;
var pTimer = require(’./pausableTimer’);

var io = require(‘socket.io-client’);
var socket = io.connect(‘http://localhost:3000’);
var lastfm = require(“simple-lastfm”);
var crypto = require(‘crypto’);

// Define the ControllerLastFM class
module.exports = ControllerLastFM;

function ControllerLastFM(context)
{
var self = this;
self.previousState = null;
self.updatingNowPlaying = false;
self.playTime = 0;

this.context = context;
this.commandRouter = this.context.coreCommand;
this.logger = this.context.logger;
this.configManager = this.context.configManager;
this.previousScrobble = 
	{	artist: '',
		title: '',
		scrobbleTime: 0
	};
this.memoryTimer;

};

ControllerLastFM.prototype.onVolumioStart = function()
{
var self = this;
var initialize = false;
this.configFile = this.commandRouter.pluginManager.getConfigurationFile(this.context, ‘config.json’);
self.getConf(this.configFile);

self.logger.info('[LastFM] scrobbler initiated!');
self.logger.info('[LastFM] extended logging: ' + self.config.get('enable_debug_logging'));
self.logger.info('[LastFM] try scrobble radio plays: ' + self.config.get('tryScrobbleWebradio'));
self.currentTimer = new pTimer(self.context, self.config.get('enable_debug_logging'));

socket.on('pushState', function (state) {
	if(!self.currentTimer)
	{
		self.currentTimer = new pTimer(self.context, self.config.get('enable_debug_logging'));
		if(self.config.get('enable_debug_logging'))
			self.logger.info('[LastFM] created new timer object');
	}
	else
	{
		if(self.config.get('enable_debug_logging'))
			self.logger.info('[LastFM] timer should be there... using the existing instance');
	}
	
	var scrobbleThresholdInMilliseconds = 0;
	if(state.service == 'mpd' || state.service == 'airplay' || state.service == 'volspotconnect2' || state.service == 'radio_paradise')
		scrobbleThresholdInMilliseconds = state.duration * (self.config.get('scrobbleThreshold') / 100) * 1000;
	else if (state.service == 'webradio')
		scrobbleThresholdInMilliseconds = self.config.get('webradioScrobbleThreshold') * 1000;
	
	var previousTitle = 'null';
	if(self.previousState != null && self.previousState.title != null)
		previousTitle = self.previousState.title;
	
	// Set initial previousState object
	var init = '';
	if(self.previousState == null)
	{
		self.logger.info('[LastFM] initializing previous state object.');
		self.previousState = state;
		initialize = true;
		init = ' | Initializing: true';
	}
	
	if(self.config.get('enable_debug_logging'))
		self.logger.info('--------------------------------------------------------------------// [LastFM] new state has been pushed; status: ' + state.status + ' | service: ' + state.service + ' | duration: ' + state.duration + ' | title: ' + state.title + ' | previous title: ' + previousTitle + init);
	
	if (state.status == 'play' && (state.service == 'mpd' || state.service == 'airplay' || state.service == 'volspotconnect2' || state.service == 'radio_paradise' || (state.service == 'webradio' && self.config.get('tryScrobbleWebradio'))))
	{					
		if((self.previousState.artist == state.artist) && (self.previousState.title == state.title) && ((self.previousState.status == 'pause' || self.previousState == 'stop') || initialize) || (self.currentTimer && !self.currentTimer.isPaused()) && (self.previousScrobble.artist != state.artist && self.previousScrobble.title != state.title))
		{
			if(self.config.get('enable_debug_logging'))
				self.logger.info('[LastFM] artist and song are (still) the same; but not necessarily no update.');
			
			// Still the same song, but different status; continue timer is applicable, else start a new one | or the previousState has not yet been initialized.
			self.updateNowPlaying(state);
			if(state.duration > 0)
			{
				if(self.config.get('enable_debug_logging'))
					self.logger.info('[LastFM] playtime for current track: ' + self.playTime);
			
				if(self.playTime > 0)
				{
					var remainingTime = scrobbleThresholdInMilliseconds - self.playTime;
					if(self.config.get('enable_debug_logging'))
						self.logger.info('[LastFM] Continuing scrobble, starting new timer for the remainder of ' + remainingTime + ' milliseconds [' + state.artist + ' - ' + state.title + '].');
					
					self.currentTimer.stop();
					self.currentTimer.start(remainingTime, function(scrobbler){
						if(self.config.get('enable_debug_logging'))
							self.logger.info('[LastFM] scrobbling from restarted timer.');
						self.scrobble(state, self.config.get('scrobbleThreshold'), scrobbleThresholdInMilliseconds);
						self.currentTimer.stop();
						self.playTime = 0;
					});
				}
				else
				{
					if(scrobbleThresholdInMilliseconds > 0)
					{
						if(self.config.get('enable_debug_logging'))
							self.logger.info('[LastFM] starting new timer for ' + scrobbleThresholdInMilliseconds + ' milliseconds [' + state.artist + ' - ' + state.title + '].');
						
						self.currentTimer.stop();
						self.currentTimer.start(scrobbleThresholdInMilliseconds, function(scrobbler){							
							self.scrobble(state, self.config.get('scrobbleThreshold'), scrobbleThresholdInMilliseconds);
							self.currentTimer.stop();
							self.playTime = 0;
						});
					}
					else
					{
						if(self.config.get('enable_debug_logging'))
							self.logger.info('[LastFM] can not scrobble; state object: ' + JSON.stringify(state));
					}
				}
			}
			else if (state.duration == 0 && state.service == 'webradio')
			{
				if(self.config.get('enable_debug_logging'))
					self.logger.info('[LastFM] starting new timer for ' + scrobbleThresholdInMilliseconds + ' milliseconds [webradio: ' + state.title + '].');
				
				self.currentTimer.stop();
				self.currentTimer.start(scrobbleThresholdInMilliseconds, function(scrobbler){							
					self.scrobble(state, self.config.get('scrobbleThreshold'), scrobbleThresholdInMilliseconds);
					self.currentTimer.stop();
					self.playTime = 0;
				});
			}
			
			if(initialize)
					initialize = false;
		}
		else if (self.previousState.title == null || self.previousState.title != state.title)
		{
			// Scrobble new song
			// self.logger.info('[LastFM] previous state: ' + JSON.stringify(self.previousState));
			// self.logger.info('[LastFM] current state: ' + JSON.stringify(state));
			if(self.config.get('enable_debug_logging'))
				self.logger.info('[LastFM] previous title does not match current title, evaluating timer settings...');
			
			self.updateNowPlaying(state);

			if(self.config.get('enable_debug_logging'))
				self.logger.info('[LastFM] timer is counting: ' + self.currentTimer.isCounting());
			
			if(state.duration > 0 && (self.currentTimer && !self.currentTimer.isCounting()))
			{
				if(self.config.get('enable_debug_logging'))
				{
					self.logger.info('[LastFM] starting new timer for ' + scrobbleThresholdInMilliseconds + ' milliseconds [' + state.artist + ' - ' + state.title + '].');
					if(scrobbleThresholdInMilliseconds == undefined || scrobbleThresholdInMilliseconds == 0)
						self.logger.info('[LastFM] state object: ' + JSON.stringify(state));
				}
				
				self.currentTimer.stop();
				self.currentTimer.start(scrobbleThresholdInMilliseconds, function(scrobbler){							
					self.scrobble(state, self.config.get('scrobbleThreshold'), scrobbleThresholdInMilliseconds);
					self.currentTimer.stop();
					self.playTime = 0;
				});
				
				if(initialize)
					initialize = false;
			}
			else if (state.duration == 0 && state.service == 'webradio')
			{
				if(self.config.get('enable_debug_logging'))
					self.logger.info('[LastFM] starting new timer for ' + scrobbleThresholdInMilliseconds + ' milliseconds [webradio: ' + state.title + '].');
				
				self.currentTimer.stop();
				self.currentTimer.start(scrobbleThresholdInMilliseconds, function(scrobbler){							
					self.scrobble(state, self.config.get('scrobbleThreshold'), scrobbleThresholdInMilliseconds);
					self.currentTimer.stop();
					self.playTime = 0;
				});
			}
			else
				self.logger.info('[LastFM] duration is 0, ignoring status update for [' + state.artist + ' - ' + state.title + ']');
		}
		else if (self.previousState.artist == state.artist && self.previousState.title == state.title && self.previousState.duration != state.duration && self.currentTimer.isCounting())
		{
			// Airplay fix, the duration is propagated at a later point in time
			var addition = (state.duration - self.previousState.duration) * (self.config.get('scrobbleThreshold') / 100) * 1000;
			self.logger.info('[LastFM] updating timer, previous duration is obsolete; adding ' + addition + ' milliseconds.');
			self.currentTimer.addMilliseconds(addition, function(scrobbler){							
					self.scrobble(state, self.config.get('scrobbleThreshold'), scrobbleThresholdInMilliseconds);
					self.currentTimer.stop();
					self.playTime = 0;
				});				
		}
		else
		{
			if(self.config.get('enable_debug_logging'))
					self.logger.info('[LastFM] could not process current state: ' + JSON.stringify(state));
		}
		// else = multiple pushStates without change, ignoring them
	}
	else if (state.status == 'pause')
	{
		if(self.currentTimer.isCounting())
		{
			self.playTime = self.currentTimer.pause();
			self.previousState = state;
		}
	}
	else if (state.status == 'stop')
	{
		if(self.config.get('enable_debug_logging'))
			self.logger.info('[LastFM] stopping timer, song has ended.');
		
		if(self.currentTimer.isCounting())
		{
			self.currentTimer.stop();
			self.previousState = state;
		}
		self.playTime = 0;
	}
	
	self.previousState = state;
});

return libQ.resolve();	

};

ControllerLastFM.prototype.getConfigurationFiles = function()
{
return [‘config.json’];
};

// Plugin methods -----------------------------------------------------------------------------
ControllerLastFM.prototype.onStop = function() {
var self = this;
self.logger.info(“performing onStop action”);

return libQ.resolve();

};

ControllerLastFM.prototype.stop = function() {
var self = this;
self.logger.info(“performing stop action”);

return libQ.resolve();

};

ControllerLastFM.prototype.onStart = function() {
var self = this;
self.logger.info(“performing onStart action”);

return libQ.resolve();

};

ControllerLastFM.prototype.onRestart = function()
{
var self = this;
self.logger.info(“performing onRestart action”);
};

ControllerLastFM.prototype.onInstall = function()
{
var self = this;
self.logger.info(“performing onInstall action”);
};

ControllerLastFM.prototype.onUninstall = function()
{
// Perform uninstall tasks here!
self.logger.info(“performing onUninstall action”);
};

ControllerLastFM.prototype.getUIConfig = function() {
var self = this;
var defer = libQ.defer();
var lang_code = this.commandRouter.sharedVars.get(‘language_code’);
self.getConf(this.configFile);
self.logger.info(“Loaded the previous config.”);

var thresholds = fs.readJsonSync((__dirname + '/options/thresholds.json'),  'utf8', {throws: false});

self.commandRouter.i18nJson(__dirname+'/i18n/strings_' + lang_code + '.json',
	__dirname + '/i18n/strings_en.json',
	__dirname + '/UIConfig.json')
.then(function(uiconf)
{
	self.logger.info("## populating UI...");
	
	// Credentials settings
	uiconf.sections[0].content[0].value = self.config.get('API_KEY');
	uiconf.sections[0].content[1].value = self.config.get('API_SECRET');		
	uiconf.sections[0].content[2].value = self.config.get('username');
	if(self.config.get('password') != undefined && self.config.get('password') != '')
		uiconf.sections[0].content[3].value = self.config.get('password');
	else
		uiconf.sections[0].content[3].value = '******';
	self.logger.info("1/3 settings loaded");
	
	// Scrobble settings
	for (var n = 0; n < thresholds.percentages.length; n++){
		self.configManager.pushUIConfigParam(uiconf, 'sections[1].content[0].options', {
			value: thresholds.percentages[n].perc,
			label: thresholds.percentages[n].desc
		});
		
		if(thresholds.percentages[n].perc == parseInt(self.config.get('scrobbleThreshold')))
		{
			uiconf.sections[1].content[0].value.value = thresholds.percentages[n].perc;
			uiconf.sections[1].content[0].value.label = thresholds.percentages[n].desc;
		}
	}
	uiconf.sections[1].content[1].value = self.config.get('pushToastOnScrobble');
	uiconf.sections[1].content[2].value = self.config.get('tryScrobbleWebradio');
	uiconf.sections[1].content[3].value = self.config.get('webradioScrobbleThreshold');
	self.logger.info("2/3 settings loaded");
	
	uiconf.sections[2].content[0].value = self.config.get('enable_debug_logging');
	self.logger.info("3/3 settings loaded");
	
	self.logger.info("Populated config screen.");
			
	defer.resolve(uiconf);
})
.fail(function()
{
	defer.reject(new Error());
});

return defer.promise;

};

ControllerLastFM.prototype.setUIConfig = function(data) {
var self = this;

self.logger.info("Updating UI config");
var uiconf = fs.readJsonSync(__dirname + '/UIConfig.json');

return libQ.resolve();

};

ControllerLastFM.prototype.getConf = function(configFile) {
var self = this;
this.config = new (require(‘v-conf’))()
this.config.loadFile(configFile)

return libQ.resolve();

};

ControllerLastFM.prototype.setConf = function(conf) {
var self = this;
return libQ.resolve();
};

// Public Methods ---------------------------------------------------------------------------------------

ControllerLastFM.prototype.updateCredentials = function (data)
{
var self = this;
var defer=libQ.defer();

self.config.set('API_KEY', data['API_KEY']);
self.config.set('API_SECRET', data['API_SECRET']);
self.config.set('username', data['username']);
if(data['storePassword'] && data['passowrd'] != undefined && data['passowrd'] != '' && data['passowrd'] != '******')
	self.config.set('password', data['password']);
self.config.set('authToken', md5(data['username'] + md5(data['password'])));
defer.resolve();

self.commandRouter.pushToastMessage('success', "Saved settings", "Successfully saved authentication settings.");

return defer.promise;

};

ControllerLastFM.prototype.updateScrobbleSettings = function (data)
{
var self = this;
var defer=libQ.defer();

self.config.set('scrobbleThreshold', data['scrobbleThreshold'].value);
self.config.set('pushToastOnScrobble', data['pushToastOnScrobble']);
self.config.set('tryScrobbleWebradio', data['tryScrobbleWebradio']);
self.config.set('webradioScrobbleThreshold', data['webradioScrobbleThreshold']);
defer.resolve();

self.commandRouter.pushToastMessage('success', "Saved settings", "Successfully saved scrobble settings.");

return defer.promise;

};

ControllerLastFM.prototype.updateDebugSettings = function (data)
{
var self = this;
var defer=libQ.defer();

self.config.set('enable_debug_logging', data['enable_debug_logging']);
defer.resolve();

self.commandRouter.pushToastMessage('success', "Saved settings", "Successfully saved debug settings.");

return defer.promise;

};

ControllerLastFM.prototype.updateNowPlaying = function (state)
{
var self = this;
var defer=libQ.defer();
self.updatingNowPlaying = true;

var artist = state.artist;
var title = state.title;
var album = state.album;

if(state.service == 'webradio' && state.title.indexOf('-') > -1)
{
	var info = state.title.split('-');
	artist = info[0].trim();
	title = info[1].trim();
	album = '';
}

if(state.service == 'radio_paradise')
	{
		var info = state.title.split('-');
        artist = info[0].trim();
        title = info[1].trim();
	}

if (
	(self.config.get('API_KEY') != '') &&
	(self.config.get('API_SECRET') != '') &&
	(self.config.get('username') != '') &&
	(self.config.get('authToken') != '') &&
	artist != undefined &&
	title != undefined &&
	album != undefined
)
{
	if(self.config.get('enable_debug_logging'))
		self.logger.info('[LastFM] trying to authenticate...');
			
	var lfm = new lastfm({
		api_key: self.config.get('API_KEY'),
		api_secret: self.config.get('API_SECRET'),
		username: self.config.get('username'),
		authToken: self.config.get('authToken')
	});
	
	lfm.getSessionKey(function(result) {
		if(result.success) {
			if(self.config.get('enable_debug_logging'))
				self.logger.info('[LastFM] authenticated successfully!');
			// Use the last.fm corrections data to check whether the supplied track has a correction to a canonical track
			lfm.getCorrection({
				artist: artist,
				track: title,
				callback: function(result) {
					if(result.success)
					{
						// Try to correct the artist
						if(result.correction.artist.name != undefined && result.correction.artist.name != '' && artist != result.correction.artist.name)
						{	
							self.logger.info('[LastFM] corrected artist from: ' + artist + ' to: ' + result.correction.artist.name);
							artist = result.correction.artist.name;
						}
						
						// Try to correct the track title
						if(result.correction.name != undefined && result.correction.name != '' && title != result.correction.name)
						{	
							self.logger.info('[LastFM] corrected track title from: ' + title + ' to: ' + result.correction.name);
							title = result.correction.name;
						}
					}
					else
						self.logger.info('[LastFM] request failed with error: ' + result.error);
				}
			})

			// Used to notify Last.fm that a user has started listening to a track. Parameter names are case sensitive.
			lfm.scrobbleNowPlayingTrack({
				artist: artist,
				track: title,
				album: album,
				callback: function(result) {
					if(!result.success)
						console.log("in callback, finished: ", result);
				}
			});
		} else {
			self.logger.info("[LastFM] Error: " + result.error);
		}
	});
}
else
{
	// Configuration errors
	if(self.config.get('API_KEY') == '')
		self.logger.info('[LastFM] configuration error; "API_KEY" is not set.');
	if(self.config.get('API_SECRET') == '')
		self.logger.info('[LastFM] configuration error; "API_SECRET" is not set.');
	if(self.config.get('username') == '')
		self.logger.info('[LastFM] configuration error; "username" is not set.');
	if(self.config.get('authToken') == '')
		self.logger.info('[LastFM] configuration error; "authToken" is not set.');
}

//self.currentTimer = null;
self.updatingNowPlaying = false;
return defer.promise;

};

ControllerLastFM.prototype.scrobble = function (state, scrobbleThreshold, scrobbleThresholdInMilliseconds)
{
var self = this;
var defer=libQ.defer();

var now = new Date().getTime();
var artist = state.artist;
var title = state.title;
var album = state.album;

if(state.service == 'webradio' && state.title.indexOf('-') > -1)
{
	var info = state.title.split('-');
	artist = info[0].trim();
	title = info[1].trim();
	album = '';
}

if(state.service == 'radio_paradise')
{
    var info = state.title.split('-');
    artist = info[0].trim();
    title = info[1].trim();
}

if(self.config.get('enable_debug_logging'))
{
	self.logger.info('[LastFM] checking previously scrobbled song...');
	self.logger.info('[LastFM] previous scrobble: ' + JSON.stringify(self.previousScrobble));
}
	
if (
	(self.config.get('API_KEY') != '') &&
	(self.config.get('API_SECRET') != '') &&
	(self.config.get('username') != '') &&
	(self.config.get('authToken') != '') &&
	artist != undefined &&
	title != undefined &&
	album != undefined	
)
{
	if(self.config.get('enable_debug_logging'))
		self.logger.info('[LastFM] trying to authenticate for scrobbling...');
	
	var lfm = new lastfm({
		api_key: self.config.get('API_KEY'),
		api_secret: self.config.get('API_SECRET'),
		username: self.config.get('username'),
		authToken: self.config.get('authToken')
	});
	
	lfm.getSessionKey(function(result) {
		if(result.success)
		{		
			if(self.config.get('enable_debug_logging'))
				self.logger.info('[LastFM] authenticated successfully for scrobbling!');
			
			// Use the last.fm corrections data to check whether the supplied track has a correction to a canonical track
			lfm.getCorrection({
				artist: artist,
				track: title,
				callback: function(result) {
					if(result.success)
					{
						//self.logger.info("[LastFM] callback, finished: ", JSON.stringify(result));
						
						// Try to correct the artist
						if(result.correction.artist.name != undefined && result.correction.artist.name != '' && artist != result.correction.artist.name)
						{	
							self.logger.info('[LastFM] corrected artist from: ' + artist + ' to: ' + result.correction.artist.name);
							artist = result.correction.artist.name;
						}
						
						// Try to correct the track title
						if(result.correction.name != undefined && result.correction.name != '' && title != result.correction.name)
						{	
							self.logger.info('[LastFM] corrected track title from: ' + title + ' to: ' + result.correction.name);
							title = result.correction.name;
						}
					}
					else
						self.logger.info('[LastFM] request failed with error: ' + result.error);
				}
			});
			
			if(self.config.get('enable_debug_logging'))
				self.logger.info('[LastFM] preparing to scrobble...');

			lfm.scrobbleTrack({
				artist: artist,
				track: title,
				album: album,
				callback: function(result) {
					if(!result.success)
						console.log("in callback, finished: ", result);
					
					if(album == '')
						album = '[unknown album]';
					
					if(self.config.get('pushToastOnScrobble'))
						self.commandRouter.pushToastMessage('success', 'Scrobble succesful', 'Scrobbled: ' + artist + ' - ' + title + ' (' + album + ').');
					self.logger.info('[LastFM] Scrobble successful for: ' + artist + ' - ' + title + ' (' + album + ').');
				}
			});	
		}
		else
		{
			self.logger.info("[LastFM] Error: " + result.error);
		}
	});
	
	self.previousScrobble.artist = artist;
	self.previousScrobble.title = title;
	self.clearScrobbleMemory((state.duration * 1000) - scrobbleThresholdInMilliseconds);
}
else
{
	// Configuration errors
	if(self.config.get('API_KEY') == '')
		self.logger.info('[LastFM] configuration error; "API_KEY" is not set.');
	if(self.config.get('API_SECRET') == '')
		self.logger.info('[LastFM] configuration error; "API_SECRET" is not set.');
	if(self.config.get('username') == '')
		self.logger.info('[LastFM] configuration error; "username" is not set.');
	if(self.config.get('authToken') == '')
		self.logger.info('[LastFM] configuration error; "authToken" is not set.');
}

//self.currentTimer = null;
return defer.promise;

};

function md5(string) {
return crypto.createHash(‘md5’).update(string, ‘utf8’).digest(“hex”);
}

ControllerLastFM.prototype.clearScrobbleMemory = function (remainingPlaytime)
{
var self = this;
self.memoryTimer = setInterval(function(clear)
{
self.previousScrobble.artist = ‘’;
self.previousScrobble.title = ‘’;
}
, remainingPlaytime);
}

/*

P R E P A R A T I O N   F O R   F U T U R E   F U N C T I O N A L I T I E S

*/

ControllerLastFM.prototype.statePushed = function (timeLeft)
{
timer = setInterval(countdown, 1000);
function countdown() {
if (timeLeft == 0) {
clearTimeout(timer);
// scrobble
} else {
timeLeft–;
}
}
}

ControllerLastFM.prototype.getCurrentMac = function () {
var self = this;
var defer = libQ.defer();
var interfaces = os.networkInterfaces();
var macs = [];
var mac = ‘’;

try
{
	//self.logger.info('###### INTERFACES: ' + JSON.stringify(interfaces));
	for (var inter in interfaces)
	{
		if(!interfaces[inter][0].internal)
		{		
			// Omit any 'empty' MAC address
			if (interfaces[inter][0].mac != '00:00:00:00:00:00')
				macs.push({ interface: inter, mac: interfaces[inter][0].mac });
		}
	}
	
	// Sort by interface: eth0, eth1, ethx, wlan0, wlan1, wlanx etc.
	macs.sort(function(a, b){
		var compA = a.interface.toLowerCase(), compB = b.interface.toLowerCase()
		if (compA < compB)
			return -1 
		if (compA > compB)
			return 1
		return 0
	});
	
	//self.logger.info('########################### MACS: ' + JSON.stringify(macs));
	currentMac = macs[0].mac;
	self.logger.info('Determined MAC: ' + currentMac);
	
	defer.resolve(mac);
}
catch(e)
{
	self.logger.error('Could not determine MAC address with error: ' + e);
	defer.reject();
}

return defer.promise;

};[/code]
Restart Volumio afterwards. Then start the flac version of radio paradise again, et voila:

I already got feedback from Saiyato, he will adapt it as soon as he’ll find the time (maybe already the coming weekend).

Marco, great work! I will update the plugin next week and bring it also to all other supported platforms.
Well done again!

PS: If you have feedbacks on how we can improve the plugin development experience, I’m all ears!

Patched the LastFM plugin, it’s available on the github repo, the zipfile is updated as well.

Does this mean that the plugin within volumios plugins is updated too?

No, they have to be approved first by the Volumio team :wink:

When I want to activate the patched version with “volumio plugin install” I get an error and it is interrupted.

Mea culpa, I edited the plugin on my notebook and had to clone the repo… apparently this means that all line endings were changed to Windows. ;( I have changed the EOL back to Unix, I did this this morning and uploaded it back to github. It should be ok now :slight_smile: it installs on my Pi.

Any rough time guide when this will be approved for an auto update via Volumio?

The plugin has been updated

This applies to the radio paradise plugin, not my plugin(s).