lastfm_scrobbler Add settings WIP

add settings to the scrobbler plugin
Start Playback -- resume playback at plugin start
  (while enabled if nothing to resume will bring you back to scrobbler menu)
Save Threshold 0-100% -- when this much time has passed the track will be
  saved and marked (L)istened

Verbose -- Supress messages such as 'Scrobbler Started' and 'Scrobbler Flushed'

run the plugin a second time to bring up the menu
if you have resume playback enabled and there is nothing to resume
it brings you back to the scrobbler menu as well

Change-Id: I48d96ea3dc8f37d76a723136004af149429e0b2e
This commit is contained in:
William Wilgus 2022-05-27 22:04:25 -04:00 committed by William Wilgus
parent 7345666d9c
commit 1d39261338

View file

@ -26,6 +26,7 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging
*/ */
#include "plugin.h" #include "plugin.h"
#include "lib/configfile.h"
#ifndef UNTAGGED #ifndef UNTAGGED
#define UNTAGGED "<UNTAGGED>" #define UNTAGGED "<UNTAGGED>"
@ -55,6 +56,9 @@ http://www.audioscrobbler.net/wiki/Portable_Player_Logging
#define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID\n" #define ITEM_HDR "#ARTIST #ALBUM #TITLE #TRACKNUM #LENGTH #RATING #TIMESTAMP #MUSICBRAINZ_TRACKID\n"
#define CFG_FILE "/lastfm_scrobbler.cfg"
#define CFG_VER 1
#if CONFIG_RTC #if CONFIG_RTC
static time_t timestamp; static time_t timestamp;
#define BASE_FILENAME HOME_DIR "/.scrobbler.log" #define BASE_FILENAME HOME_DIR "/.scrobbler.log"
@ -71,7 +75,6 @@ static time_t timestamp;
#define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE #define THREAD_STACK_SIZE 4*DEFAULT_STACK_SIZE
/****************** prototypes ******************/ /****************** prototypes ******************/
int plugin_main(const void* parameter); /* main loop */
enum plugin_status plugin_start(const void* parameter); /* entry */ enum plugin_status plugin_start(const void* parameter); /* entry */
/****************** globals ******************/ /****************** globals ******************/
@ -95,6 +98,86 @@ static struct
bool force_flush; bool force_flush;
} gCache; } gCache;
static struct
{
int savepct;
bool playback;
bool verbose;
} gConfig;
static struct configdata config[] =
{
{TYPE_INT, 0, 100, { .int_p = &gConfig.savepct }, "SavePct", NULL},
{TYPE_BOOL, 0, 1, { .bool_p = &gConfig.playback }, "Playback", NULL},
{TYPE_BOOL, 0, 1, { .bool_p = &gConfig.verbose }, "Verbose", NULL},
};
const int gCfg_sz = sizeof(config)/sizeof(*config);
/****************** config functions *****************/
static void config_set_defaults(void)
{
gConfig.savepct = 50;
gConfig.playback = false;
gConfig.verbose = true;
}
static int config_settings_menu(void)
{
int selection = 0;
struct viewport parentvp[NB_SCREENS];
FOR_NB_SCREENS(l)
{
rb->viewport_set_defaults(&parentvp[l], l);
rb->viewport_set_fullscreen(&parentvp[l], l);
}
MENUITEM_STRINGLIST(settings_menu, ID2P(LANG_SETTINGS), NULL,
ID2P(LANG_RESUME_PLAYBACK),
"Save Threshold",
"Verbose",
ID2P(VOICE_BLANK),
ID2P(LANG_CANCEL_0),
ID2P(LANG_SAVE_EXIT));
do {
selection=rb->do_menu(&settings_menu,&selection, parentvp, true);
switch(selection) {
case 0:
rb->set_bool(str(LANG_RESUME_PLAYBACK), &gConfig.playback);
break;
case 1:
rb->set_int("Save Threshold", "%", UNIT_PERCENT,
&gConfig.savepct, NULL, 10, 0, 100, NULL );
break;
case 2:
rb->set_bool("Verbose", &gConfig.verbose);
break;
case 3: /*sep*/
continue;
case 4:
return -1;
break;
case 5:
{
int res = configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
if (res >= 0)
{
logf("Scrobbler cfg saved %s %d bytes", CFG_FILE, gCfg_sz);
return PLUGIN_OK;
}
logf("Scrobbler cfg FAILED (%d) %s", res, CFG_FILE);
return PLUGIN_ERROR;
}
case MENU_ATTACHED_USB:
return PLUGIN_USB_CONNECTED;
default:
return PLUGIN_OK;
}
} while ( selection >= 0 );
return 0;
}
/****************** helper fuctions ******************/ /****************** helper fuctions ******************/
int scrobbler_init(void) int scrobbler_init(void)
@ -210,6 +293,13 @@ static inline char* str_chk_valid(char *s, char *alt)
return (s != NULL ? s : alt); return (s != NULL ? s : alt);
} }
static unsigned long scrobbler_get_threshold(unsigned long length)
{
/* length is assumed to be in miliseconds */
return length / 100 * gConfig.savepct;
}
static void scrobbler_add_to_cache(const struct mp3entry *id) static void scrobbler_add_to_cache(const struct mp3entry *id)
{ {
static uint32_t last_crc = 0; static uint32_t last_crc = 0;
@ -223,7 +313,7 @@ static void scrobbler_add_to_cache(const struct mp3entry *id)
logf("SCROBBLER: add_to_cache[%d]", gCache.pos); logf("SCROBBLER: add_to_cache[%d]", gCache.pos);
if (id->elapsed > id->length / 2) if (id->elapsed >= scrobbler_get_threshold(id->length))
rating = 'L'; /* Listened */ rating = 'L'; /* Listened */
char tracknum[11] = { "" }; char tracknum[11] = { "" };
@ -294,9 +384,9 @@ static void scrobbler_change_event(unsigned short id, void *ev_data)
logf("%s", __func__); logf("%s", __func__);
struct mp3entry *id3 = ((struct track_event *)ev_data)->id3; struct mp3entry *id3 = ((struct track_event *)ev_data)->id3;
/* check if track was resumed > %50 played ( likely got saved ) /* check if track was resumed > %threshold played ( likely got saved )
check for blank artist or track name */ check for blank artist or track name */
if ((id3->elapsed > id3->length / 2) if ((id3->elapsed > scrobbler_get_threshold(id3->length))
|| (!id3->artist && !id3->albumartist) || !id3->title) || (!id3->artist && !id3->albumartist) || !id3->title)
{ {
gCache.pending = false; gCache.pending = false;
@ -332,25 +422,32 @@ static void scrobbler_finish_event(unsigned short id, void *ev_data)
{ {
(void)id; (void)id;
struct track_event *te = ((struct track_event *)ev_data); struct track_event *te = ((struct track_event *)ev_data);
struct mp3entry *id3 = te->id3;
logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te)); logf("%s %s %s", __func__, gCache.pending?"True":"False", track_event_info(te));
/* add entry using the currently ending track */ /* add entry using the currently ending track */
if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND)) if (gCache.pending && (te->flags & TEF_CURRENT) && !(te->flags & TEF_REWIND))
{ {
gCache.pending = false; gCache.pending = false;
if (id3->elapsed*2 >= id3->length) scrobbler_add_to_cache(te->id3);
scrobbler_add_to_cache(te->id3);
else
{
logf("%s Discarding < 50%% played", __func__);
}
} }
} }
/****************** main thread + helper ******************/ /****************** main thread + helpers ******************/
static void events_unregister(void)
{
/* we don't want any more events */
rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
}
static void events_register(void)
{
rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
}
void thread(void) void thread(void)
{ {
bool in_usb = false; bool in_usb = false;
@ -371,6 +468,7 @@ void thread(void)
in_usb = false; in_usb = false;
/*fall through*/ /*fall through*/
case EV_STARTUP: case EV_STARTUP:
events_register();
rb->beep_play(1500, 100, 1000); rb->beep_play(1500, 100, 1000);
break; break;
case SYS_POWEROFF: case SYS_POWEROFF:
@ -384,6 +482,7 @@ void thread(void)
if (!in_usb) if (!in_usb)
scrobbler_flush_cache(); scrobbler_flush_cache();
#endif #endif
events_unregister();
return; return;
case EV_FLUSHCACHE: case EV_FLUSHCACHE:
scrobbler_flush_cache(); scrobbler_flush_cache();
@ -412,22 +511,19 @@ void thread_create(void)
void thread_quit(void) void thread_quit(void)
{ {
if (!gThread.exiting) { if (!gThread.exiting) {
gThread.exiting = true;
rb->queue_post(&gThread.queue, EV_EXIT, 0); rb->queue_post(&gThread.queue, EV_EXIT, 0);
rb->thread_wait(gThread.id); rb->thread_wait(gThread.id);
/* we don't want any more events */
rb->remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
rb->remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
/* remove the thread's queue from the broadcast list */ /* remove the thread's queue from the broadcast list */
rb->queue_delete(&gThread.queue); rb->queue_delete(&gThread.queue);
gThread.exiting = true;
} }
} }
/* callback to end the TSR plugin, called before a new one gets loaded */ /* callback to end the TSR plugin, called before a new one gets loaded */
static bool exit_tsr(bool reenter) static bool exit_tsr(bool reenter)
{ {
MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, MENUITEM_STRINGLIST(menu, ID2P(LANG_AUDIOSCROBBLER), NULL, ID2P(LANG_SETTINGS),
"Flush Cache", "Quit", "Back"); "Flush Cache", "Exit Plugin", ID2P(LANG_BACK));
const struct text_message quit_prompt = { const struct text_message quit_prompt = {
(const char*[]){ ID2P(LANG_AUDIOSCROBBLER), (const char*[]){ ID2P(LANG_AUDIOSCROBBLER),
@ -437,22 +533,25 @@ static bool exit_tsr(bool reenter)
while(true) while(true)
{ {
int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 1; int result = reenter ? rb->do_menu(&menu, NULL, NULL, false) : 2;
switch(result) switch(result)
{ {
case 0: /* flush cache */ case 0: /* settings */
config_settings_menu();
break;
case 1: /* flush cache */
rb->queue_send(&gThread.queue, EV_FLUSHCACHE, 0); rb->queue_send(&gThread.queue, EV_FLUSHCACHE, 0);
rb->splashf(2*HZ, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER)); if (gConfig.verbose)
rb->splashf(2*HZ, "%s Cache Flushed", str(LANG_AUDIOSCROBBLER));
break; break;
case 1: /* quit */ case 2: /* exit plugin - quit */
if(rb->gui_syncyesno_run(&quit_prompt, NULL, NULL) == YESNO_YES) if(rb->gui_syncyesno_run(&quit_prompt, NULL, NULL) == YESNO_YES)
{ {
rb->queue_post(&gThread.queue, EV_EXIT, 0); thread_quit();
rb->thread_wait(gThread.id); if (reenter)
/* remove the thread's queue from the broadcast list */ rb->plugin_tsr(NULL); /* remove TSR cb */
rb->queue_delete(&gThread.queue); return !reenter;
return true;
} }
if(!reenter) if(!reenter)
@ -460,29 +559,29 @@ static bool exit_tsr(bool reenter)
break; break;
case 2: /* back to menu */ case 3: /* back to menu */
return false; return false;
} }
} }
} }
/****************** main ******************/ /****************** main ******************/
static int plugin_main(const void* parameter)
int plugin_main(const void* parameter)
{ {
(void)parameter; (void)parameter;
rb->memset(&gThread, 0, sizeof(gThread)); rb->memset(&gThread, 0, sizeof(gThread));
rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER)); if (gConfig.verbose)
rb->splashf(HZ / 2, "%s Started",str(LANG_AUDIOSCROBBLER));
logf("%s: %s Started", __func__, str(LANG_AUDIOSCROBBLER)); logf("%s: %s Started", __func__, str(LANG_AUDIOSCROBBLER));
rb->plugin_tsr(exit_tsr); /* stay resident */ rb->plugin_tsr(exit_tsr); /* stay resident */
rb->add_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
rb->add_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
thread_create(); thread_create();
return 0; if (gConfig.playback)
return PLUGIN_GOTO_WPS;
return PLUGIN_OK;
} }
/***************** Plugin Entry Point *****************/ /***************** Plugin Entry Point *****************/
@ -495,6 +594,16 @@ enum plugin_status plugin_start(const void* parameter)
language_strings = rb->language_strings; language_strings = rb->language_strings;
if (scrobbler_init() < 0) if (scrobbler_init() < 0)
return PLUGIN_ERROR; return PLUGIN_ERROR;
if (configfile_load(CFG_FILE, config, gCfg_sz, CFG_VER) < 0)
{
/* If the loading failed, save a new config file */
config_set_defaults();
configfile_save(CFG_FILE, config, gCfg_sz, CFG_VER);
rb->splash(HZ, ID2P(LANG_REVERT_TO_DEFAULT_SETTINGS));
}
int ret = plugin_main(parameter); int ret = plugin_main(parameter);
return (ret==0) ? PLUGIN_OK : PLUGIN_ERROR; return ret;
} }