Get rid of some superfluous single-purpose functions in playback.

* Remove explicit tracking of elapsed time of previous track.
* Remove function to obtain auto skip flag.
* Most playback events now carry the extra information instead and
  pass 'struct track_event *' for data.
* Tweak scrobbler to use PLAYBACK_EVENT_TRACK_FINISH, which makes
  it cleaner and removes the struct mp3entry.

Change-Id: I500d2abb4056a32646496efc3617406e36811ec5
diff --git a/apps/appevents.h b/apps/appevents.h
index 506f003..8677dbd 100644
--- a/apps/appevents.h
+++ b/apps/appevents.h
@@ -31,21 +31,29 @@
 
 /** Playback events **/
 enum {
-    /* Playback is starting from a stopped state */
+    /* Playback is starting from a stopped state
+       data = NULL */
     PLAYBACK_EVENT_START_PLAYBACK = (EVENT_CLASS_PLAYBACK|1),
-    /* Audio has begun buffering for decoding track (or is already completed) */
+    /* Audio has begun buffering for decoding track (or is already completed)
+       data = &(struct track_event){} */
     PLAYBACK_EVENT_TRACK_BUFFER,
-    /* Handles for current user track are ready (other than audio or codec) */
+    /* Handles for current user track are ready (other than audio or codec)
+       data = &(struct track_event){}  */
     PLAYBACK_EVENT_CUR_TRACK_READY,
-    /* Current user track finished */
+    /* Current user track finished
+       data = &(struct track_event){}  */
     PLAYBACK_EVENT_TRACK_FINISH,
-    /* A new current user track has begun */
+    /* A new current user track has begun
+       data = &(struct track_event){} */
     PLAYBACK_EVENT_TRACK_CHANGE,
-    /* A manual skip is about to be processed */
+    /* A manual skip is about to be processed
+       data = NULL */
     PLAYBACK_EVENT_TRACK_SKIP,
-    /* Next track medadata was just loaded */
+    /* Next track medadata was just loaded
+       data = &(struct track_event){} */
     PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE,
-    /* Voice is playing: data = &(bool){true|false} */
+    /* Voice is playing
+       data = &(bool){true|false} */
     PLAYBACK_EVENT_VOICE_PLAYING,
 };
 
diff --git a/apps/gui/wps.c b/apps/gui/wps.c
index a3d7a1b..726df6a 100644
--- a/apps/gui/wps.c
+++ b/apps/gui/wps.c
@@ -1190,7 +1190,7 @@
 static void track_changed_callback(void *param)
 {
     struct wps_state *state = skin_get_global_state();
-    state->id3 = (struct mp3entry*)param;
+    state->id3 = ((struct track_event *)param)->id3;
     state->nid3 = audio_next_track();
     if (state->id3->cuesheet)
     {
diff --git a/apps/hosted/android/notification.c b/apps/hosted/android/notification.c
index 4bb8d0a..874cd3b 100644
--- a/apps/hosted/android/notification.c
+++ b/apps/hosted/android/notification.c
@@ -46,7 +46,7 @@
  * notify about track change, and show track info */
 static void track_changed_callback(void *param)
 {
-    struct mp3entry* id3 = (struct mp3entry*)param;
+    struct mp3entry* id3 = ((struct track_event *)param)->id3;
     JNIEnv e = *env_ptr;
     if (id3)
     {
@@ -108,7 +108,9 @@
  * notify about track finishing */
 static void track_finished_callback(void *param)
 {
-    (void)param;
+    if (((struct track_event *)param)->flags & TEF_REWIND)
+        return; /* Not a true track end */
+
     JNIEnv e = *env_ptr;
     e->CallVoidMethod(env_ptr, NotificationManager_instance,
                       finishNotification);
diff --git a/apps/main.c b/apps/main.c
index 7f44d89..7333f7d 100644
--- a/apps/main.c
+++ b/apps/main.c
@@ -420,7 +420,8 @@
               global_settings.superbass);
 #endif /* CONFIG_CODEC != SWCODEC */
 
-    scrobbler_init();
+    if (global_settings.audioscrobbler)
+        scrobbler_init();
 
     audio_init();
 
@@ -700,7 +701,10 @@
     playlist_init();
     tree_mem_init();
     filetype_init();
-    scrobbler_init();
+
+    if (global_settings.audioscrobbler)
+        scrobbler_init();
+
     shortcuts_init();
 
 #if CONFIG_CODEC != SWCODEC
diff --git a/apps/menus/playback_menu.c b/apps/menus/playback_menu.c
index dbfb44f..a82c88e 100644
--- a/apps/menus/playback_menu.c
+++ b/apps/menus/playback_menu.c
@@ -157,7 +157,7 @@
                 scrobbler_init();
 
             if(scrobbler_is_enabled() && !global_settings.audioscrobbler)
-                scrobbler_shutdown();
+                scrobbler_shutdown(false);
             break;
     }
     return action;
diff --git a/apps/misc.c b/apps/misc.c
index 91244f2..8dff227 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -269,8 +269,6 @@
 {
     long msg_id = -1;
 
-    scrobbler_poweroff();
-
 #if CONFIG_CHARGING && !defined(HAVE_POWEROFF_WHILE_CHARGING)
     if(!charger_inserted())
 #endif
@@ -349,6 +347,7 @@
 #if defined(HAVE_RECORDING) && CONFIG_CODEC == SWCODEC
             audio_close_recording();
 #endif
+            scrobbler_shutdown(true);
 
             if(global_settings.talk_menu)
             {
diff --git a/apps/mpeg.c b/apps/mpeg.c
index 5c206c7..2783a24 100644
--- a/apps/mpeg.c
+++ b/apps/mpeg.c
@@ -129,8 +129,6 @@
 
 #ifndef SIMULATOR
 static void stop_playing(void);
-/* Play time of the previous track */
-static unsigned long prev_track_elapsed;
 
 static int track_read_idx = 0;
 static int track_write_idx = 0;
@@ -362,7 +360,15 @@
     }
     return false;
 }
-#endif
+
+static void send_track_event(unsigned int id, struct mp3entry *id3)
+{
+    struct mp3entry *cur_id3 =
+        &trackdata[track_read_idx & MAX_TRACK_ENTRIES_MASK].id3;
+    unsigned int flags = id3 == cur_id3 ? TEF_CURRENT : 0;
+    send_event(id, &(struct track_event){ .flags = flags, .id3 = id3 });
+}
+#endif /* SIMULATOR */
 
 /***********************************************************************/
 
@@ -609,7 +615,7 @@
     for (i = 0; i < numentries; i++)
     {
         /* Send an event to notify that track has finished. */
-        send_event(PLAYBACK_EVENT_TRACK_FINISH, &trackdata[cur_idx].id3);
+        send_track_event(PLAYBACK_EVENT_TRACK_FINISH, &trackdata[cur_idx].id3);
         cur_idx = (cur_idx + 1) & MAX_TRACK_ENTRIES_MASK;
     }
 }
@@ -623,7 +629,7 @@
 
     for (i = 0; i < numentries; i++)
     {
-        send_event(PLAYBACK_EVENT_TRACK_BUFFER, &trackdata[cur_idx].id3);
+        send_track_event(PLAYBACK_EVENT_TRACK_BUFFER, &trackdata[cur_idx].id3);
         cur_idx = (cur_idx + 1) & MAX_TRACK_ENTRIES_MASK;
     }
 }
@@ -1006,7 +1012,7 @@
     send_nid3_event = (track_write_idx == track_read_idx + 1);
     track_write_idx = (track_write_idx+1) & MAX_TRACK_ENTRIES_MASK;
     if (send_nid3_event)
-        send_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, NULL);
+        send_track_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE, &track->id3);
     debug_tags();
     return track;
 }
@@ -1093,17 +1099,11 @@
 
 static void stop_playing(void)
 {
-    struct trackdata *track;
-
     /* Stop the current stream */
     mp3_play_stop();
     playing = false;
     filling = false;
 
-    track = get_trackdata(0);
-    if (track != NULL)
-        prev_track_elapsed = track->id3.elapsed;
-
     if(mpeg_file >= 0)
         close(mpeg_file);
     mpeg_file = -1;
@@ -1112,17 +1112,12 @@
     reset_mp3_buffer();
 }
 
-static void end_current_track(void) {
-    struct trackdata *track;
-
+static void end_current_track(void)
+{
     play_pending = false;
     playing = false;
     mp3_play_pause(false);
 
-    track = get_trackdata(0);
-    if (track != NULL)
-        prev_track_elapsed = track->id3.elapsed;
-
     reset_mp3_buffer();
     remove_all_tags();
     generate_unbuffer_events();
@@ -1164,9 +1159,6 @@
 {
     DEBUGF("Track change\n");
 
-    struct trackdata *track = get_trackdata(0);
-    prev_track_elapsed = track->id3.elapsed;
-
 #if (CONFIG_CODEC == MAS3587F) || (CONFIG_CODEC == MAS3539F)
     /* Reset the AVC */
     sound_set_avc(-1);
@@ -1177,17 +1169,15 @@
         remove_current_tag();
         update_playlist();
         if (is_playing)
-            send_event(PLAYBACK_EVENT_TRACK_CHANGE, audio_current_track());
+        {
+            send_track_event(PLAYBACK_EVENT_TRACK_CHANGE,
+                             audio_current_track());
+        }
     }
 
     current_track_counter++;
 }
 
-unsigned long audio_prev_elapsed(void)
-{
-    return prev_track_elapsed;
-}
-
 #ifdef DEBUG
 void hexdump(const unsigned char *buf, int len)
 {
@@ -1229,7 +1219,8 @@
                 if (play_pending_track_change)
                 {
                     play_pending_track_change = false;
-                    send_event(PLAYBACK_EVENT_TRACK_CHANGE, audio_current_track());
+                    send_track_event(PLAYBACK_EVENT_TRACK_CHANGE,
+                                     audio_current_track());
                 }
                 play_pending = false;
             }
@@ -2828,11 +2819,6 @@
 void audio_stop(void)
 {
 #ifndef SIMULATOR
-    if (playing)
-    {
-        struct trackdata *track = get_trackdata(0);
-        prev_track_elapsed = track->id3.elapsed;
-    }
     mpeg_stop_done = false;
     queue_post(&mpeg_queue, MPEG_STOP, 0);
     while(!mpeg_stop_done)
diff --git a/apps/playback.c b/apps/playback.c
index 8b498f2..a1db82e 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -155,13 +155,6 @@
 /* Peeking functions can yield and mess us up */
 static struct mutex id3_mutex SHAREDBSS_ATTR; /* (A,O)*/
 
-
-/** For Scrobbler support **/
-
-/* Previous track elapsed time */
-static unsigned long prev_track_elapsed = 0; /* (A,O-) */
-
-
 /** For album art support **/
 #define MAX_MULTIPLE_AA SKINNABLE_SCREENS_COUNT
 #ifdef HAVE_ALBUMART
@@ -296,9 +289,8 @@
    would work as expected */
 
 /* Used to indicate status for the events. Must be separate to satisfy all
-   clients so the correct metadata is read when sending the change events
-   and also so that it is read correctly outside the events. */
-static bool automatic_skip = false; /* (A, O-) */
+   clients so the correct metadata is read when sending the change events. */
+static unsigned int track_event_flags = TEF_NONE; /* (A, O-) */
 
 /* Pending manual track skip offset */
 static int skip_offset = 0; /* (A, O) */
@@ -1056,6 +1048,16 @@
     }
 }
 
+/* Send track events that use a struct track_event for data */
+static void send_track_event(unsigned int id, unsigned int flags,
+                             struct mp3entry *id3)
+{
+    if (id3 == id3_get(PLAYING_ID3))
+        flags |= TEF_CURRENT;
+
+    send_event(id, &(struct track_event){ .flags = flags, .id3 = id3 });
+}
+
 /* Announce the end of playing the current track */
 static void audio_playlist_track_finish(void)
 {
@@ -1066,12 +1068,8 @@
 
     if (id3)
     {
-        send_event(PLAYBACK_EVENT_TRACK_FINISH, id3);
-        prev_track_elapsed = id3->elapsed;
-    }
-    else
-    {
-        prev_track_elapsed = 0;
+        send_track_event(PLAYBACK_EVENT_TRACK_FINISH,
+                         track_event_flags, id3);
     }
 }
 
@@ -1081,7 +1079,10 @@
     struct mp3entry *id3 = valid_mp3entry(id3_get(PLAYING_ID3));
 
     if (id3)
-        send_event(PLAYBACK_EVENT_TRACK_CHANGE, id3);
+    {
+        send_track_event(PLAYBACK_EVENT_TRACK_CHANGE,
+                         track_event_flags, id3);
+    }
 
     position_key = pcmbuf_get_position_key();
 
@@ -1092,8 +1093,8 @@
 static void audio_update_and_announce_next_track(const struct mp3entry *id3_next)
 {
     id3_write_locked(NEXTTRACK_ID3, id3_next);
-    send_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE,
-               id3_get(NEXTTRACK_ID3));
+    send_track_event(PLAYBACK_EVENT_NEXTTRACKID3_AVAILABLE,
+                     0,  id3_get(NEXTTRACK_ID3));
 }
 
 /* Bring the user current mp3entry up to date and set a new offset for the
@@ -1441,7 +1442,7 @@
         bool resume = !auto_skip;
 
         /* Send the "buffer" event to obtain the resume position for the codec */
-        send_event(PLAYBACK_EVENT_TRACK_BUFFER, cur_id3);
+        send_track_event(PLAYBACK_EVENT_TRACK_BUFFER, 0, cur_id3);
 
         if (!resume)
         {
@@ -1497,7 +1498,7 @@
 #endif
     {
         /* Send the "buffer" event now */
-        send_event(PLAYBACK_EVENT_TRACK_BUFFER, cur_id3);
+        send_track_event(PLAYBACK_EVENT_TRACK_BUFFER, 0, cur_id3);
     }
 
     buf_pin_handle(info->id3_hid, false);
@@ -1893,7 +1894,8 @@
         /* Send only when the track handles could not all be opened ahead of
            time for the user's current track - otherwise everything is ready
            by the time PLAYBACK_EVENT_TRACK_CHANGE is sent */
-        send_event(PLAYBACK_EVENT_CUR_TRACK_READY, id3_get(PLAYING_ID3));
+        send_track_event(PLAYBACK_EVENT_CUR_TRACK_READY, 0,
+                         id3_get(PLAYING_ID3));
     }
 
 #ifdef HAVE_CODEC_BUFFERING
@@ -2157,7 +2159,7 @@
         buf_read_cuesheet(info->cuesheet_hid);
     }
 
-    if (audio_start_codec(automatic_skip))
+    if (audio_start_codec(track_event_flags & TEF_AUTO_SKIP))
     {
         if (is_user_current)
         {
@@ -2356,7 +2358,7 @@
 
     int trackstat = LOAD_TRACK_OK;
 
-    automatic_skip = true;
+    track_event_flags = TEF_AUTO_SKIP;
     skip_pending = TRACK_SKIP_AUTO;
 
     /* Does this track have an entry allocated? */
@@ -2471,7 +2473,7 @@
 
         halt_decoding_track(true);
 
-        automatic_skip = false;
+        track_event_flags = TEF_NONE;
         ff_rw_mode = false;
 
         if (flags & AUDIO_START_RESTART)
@@ -2595,7 +2597,7 @@
     audio_playlist_track_finish();
 
     skip_pending = TRACK_SKIP_NONE;
-    automatic_skip = false;
+    track_event_flags = TEF_NONE;
 
     /* Close all tracks and mark them NULL */
     remove_event(BUFFER_EVENT_REBUFFER, buffer_event_rebuffer_callback);
@@ -2667,7 +2669,7 @@
     ff_rw_mode = false;
 
     /* Manual skip */
-    automatic_skip = false;
+    track_event_flags = TEF_NONE;
 
     /* If there was an auto skip in progress, there will be residual
        advancement of the playlist and/or track list so compensation will be
@@ -2755,7 +2757,7 @@
     ff_rw_mode = false;
 
     /* Manual skip */
-    automatic_skip = false;
+    track_event_flags = TEF_NONE;
 
     audio_playlist_track_finish();
 
@@ -2820,14 +2822,14 @@
         struct mp3entry *id3 = id3_get(PLAYING_ID3);
         struct mp3entry *ci_id3 = id3_get(CODEC_ID3);
 
-        automatic_skip = false;
+        track_event_flags = TEF_NONE;
 
-        /* Send event before clobbering the time */
-        /* FIXME: Nasty, but the tagtree expects this so that rewinding and
-           then skipping back to this track resumes properly. Something else
-           should be sent. We're not _really_ finishing the track are we? */
+        /* Send event before clobbering the time if rewinding. */
         if (time == 0)
-            send_event(PLAYBACK_EVENT_TRACK_FINISH, id3);
+        {
+            send_track_event(PLAYBACK_EVENT_TRACK_FINISH,
+                             track_event_flags | TEF_REWIND, id3);
+        }
 
         id3->elapsed = time;
         queue_reply(&audio_queue, 1);
@@ -3662,14 +3664,6 @@
 }
 #endif /* HAVE_ALBUMART */
 
-/* Is an automatic skip in progress? If called outside transition callbacks,
-   indicates the last skip type at the time it was processed and isn't very
-   meaningful. */
-bool audio_automatic_skip(void)
-{
-    return automatic_skip;
-}
-
 /* Would normally calculate byte offset from an elapsed time but is not
    used on SWCODEC */
 int audio_get_file_pos(void)
@@ -3677,12 +3671,6 @@
     return 0;
 }
 
-/* Return the elapsed time of the track previous to the current */
-unsigned long audio_prev_elapsed(void)
-{
-    return prev_track_elapsed;
-}
-
 /* Return total file buffer length after accounting for the talk buf */
 size_t audio_get_filebuflen(void)
 {
diff --git a/apps/playback.h b/apps/playback.h
index 0a9d22c..f56bbfd 100644
--- a/apps/playback.h
+++ b/apps/playback.h
@@ -88,10 +88,6 @@
 bool audio_restore_playback(int type); /* Restores the audio buffer to handle the requested playback */
 size_t audio_get_filebuflen(void);
 
-/* Automatic transition? Only valid to call during the track change events,
-   otherwise the result is undefined. */
-bool audio_automatic_skip(void);
-
 unsigned int playback_status(void);
 
 #endif /* _PLAYBACK_H */
diff --git a/apps/root_menu.c b/apps/root_menu.c
index d03fee3..1ffde91 100644
--- a/apps/root_menu.c
+++ b/apps/root_menu.c
@@ -89,7 +89,7 @@
 static char current_track_path[MAX_PATH];
 static void rootmenu_track_changed_callback(void* param)
 {
-    struct mp3entry *id3 = (struct mp3entry *)param;
+    struct mp3entry *id3 = ((struct track_event *)param)->id3;
     strlcpy(current_track_path, id3->path, MAX_PATH);
 }
 static int browser(void* param)
diff --git a/apps/scrobbler.c b/apps/scrobbler.c
index be60cc1..efd0283 100644
--- a/apps/scrobbler.c
+++ b/apps/scrobbler.c
@@ -52,50 +52,40 @@
 /* longest entry I've had is 323, add a safety margin */
 #define SCROBBLER_CACHE_LEN 512
 
-static int scrobbler_cache;
-
-static int cache_pos;
-static struct mp3entry scrobbler_entry;
-static bool pending = false;
 static bool scrobbler_initialised = false;
+static int scrobbler_cache = 0;
+static int cache_pos = 0;
+static bool pending = false;
 #if CONFIG_RTC
 static time_t timestamp;
-#else
-static unsigned long timestamp;
-#endif
-
-/* Crude work-around for Archos Sims - return a set amount */
-#if (CONFIG_CODEC != SWCODEC) && (CONFIG_PLATFORM & PLATFORM_HOSTED)
-unsigned long audio_prev_elapsed(void)
-{
-    return 120000;
-}
-#endif
+#define BASE_FILENAME       ".scrobbler.log"
+#define HDR_STR_TIMELESS
+#define get_timestamp()     ((long)timestamp)
+#define record_timestamp()  ((void)(timestamp = mktime(get_time())))
+#else /* !CONFIG_RTC */
+#define HDR_STR_TIMELESS    " Timeless"
+#define BASE_FILENAME       ".scrobbler-timeless.log"
+#define get_timestamp()     (0l)
+#define record_timestamp()  ({})
+#endif /* CONFIG_RTC */
 
 static void get_scrobbler_filename(char *path, size_t size)
 {
     int used;
-
-#if CONFIG_RTC
-    const char *base_filename = ".scrobbler.log";
-#else
-    const char *base_filename = ".scrobbler-timeless.log";
-#endif
-
 /* Get location of USB mass storage area */
 #ifdef APPLICATION
 #if (CONFIG_PLATFORM & PLATFORM_MAEMO)
-    used = snprintf(path, size, "/home/user/MyDocs/%s", base_filename);
+    used = snprintf(path, size, "/home/user/MyDocs/%s", BASE_FILENAME);
 #elif (CONFIG_PLATFORM & PLATFORM_ANDROID)
-    used = snprintf(path, size, "/sdcard/%s", base_filename);
+    used = snprintf(path, size, "/sdcard/%s", BASE_FILENAME);
 #elif defined (SAMSUNG_YPR0)
-    used = snprintf(path, size, "%s/%s", HOME_DIR, base_filename);
+    used = snprintf(path, size, "%s/%s", HOME_DIR, BASE_FILENAME);
 #else /* SDL/unknown RaaA build */
-    used = snprintf(path, size, "%s/%s", ROCKBOX_DIR, base_filename);
+    used = snprintf(path, size, "%s/%s", ROCKBOX_DIR, BASE_FILENAME);
 #endif /* (CONFIG_PLATFORM & PLATFORM_MAEMO) */
 
 #else
-    used = snprintf(path, size, "/%s", base_filename);
+    used = snprintf(path, size, "/%s", BASE_FILENAME);
 #endif
 
     if (used >= (int)size)
@@ -121,12 +111,9 @@
         if(fd >= 0)
         {
             fdprintf(fd, "#AUDIOSCROBBLER/" SCROBBLER_VERSION "\n"
-                         "#TZ/UNKNOWN\n"
-#if CONFIG_RTC
-                         "#CLIENT/Rockbox " TARGET_NAME SCROBBLER_REVISION "\n");
-#else
-                         "#CLIENT/Rockbox " TARGET_NAME SCROBBLER_REVISION " Timeless\n");
-#endif
+                         "#TZ/UNKNOWN\n" "#CLIENT/Rockbox "
+                         TARGET_NAME SCROBBLER_REVISION
+                         HDR_STR_TIMELESS "\n");
 
             close(fd);
         }
@@ -170,51 +157,43 @@
         write_cache();
 }
 
-static void add_to_cache(unsigned long play_length)
+static void add_to_cache(const struct mp3entry *id)
 {
     if ( cache_pos >= SCROBBLER_MAX_CACHE )
         write_cache();
 
-    int ret;
     char rating = 'S'; /* Skipped */
     char* scrobbler_buf = core_get_data(scrobbler_cache);
 
     logf("SCROBBLER: add_to_cache[%d]", cache_pos);
 
-    if ( play_length > (scrobbler_entry.length/2) )
+    if (id->elapsed > id->length / 2)
         rating = 'L'; /* Listened */
 
-    if (scrobbler_entry.tracknum > 0)
-    {
-        ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos),
-                SCROBBLER_CACHE_LEN,
-                "%s\t%s\t%s\t%d\t%d\t%c\t%ld\t%s\n",
-                scrobbler_entry.artist,
-                scrobbler_entry.album?scrobbler_entry.album:"",
-                scrobbler_entry.title,
-                scrobbler_entry.tracknum,
-                (int)scrobbler_entry.length/1000,
-                rating,
-                (long)timestamp,
-                scrobbler_entry.mb_track_id?scrobbler_entry.mb_track_id:"");
-    } else {
-        ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos),
-                SCROBBLER_CACHE_LEN,
-                "%s\t%s\t%s\t\t%d\t%c\t%ld\t%s\n",
-                scrobbler_entry.artist,
-                scrobbler_entry.album?scrobbler_entry.album:"",
-                scrobbler_entry.title,
-                (int)scrobbler_entry.length/1000,
-                rating,
-                (long)timestamp,
-                scrobbler_entry.mb_track_id?scrobbler_entry.mb_track_id:"");
-    }
+    char tracknum[11] = { "" };
+
+    if (id->tracknum > 0)
+        snprintf(tracknum, sizeof (tracknum), "%d", id->tracknum);
+
+    int ret = snprintf(scrobbler_buf+(SCROBBLER_CACHE_LEN*cache_pos),
+                       SCROBBLER_CACHE_LEN,
+                       "%s\t%s\t%s\t%s\t%d\t%c\t%ld\t%s\n",
+                       id->artist,
+                       id->album ?: "",
+                       id->title,
+                       tracknum,
+                       (int)(id->length / 1000),
+                       rating,
+                       get_timestamp(),
+                       id->mb_track_id ?: "");
 
     if ( ret >= SCROBBLER_CACHE_LEN )
     {
         logf("SCROBBLER: entry too long:");
-        logf("SCROBBLER: %s", scrobbler_entry.path);
-    } else {
+        logf("SCROBBLER: %s", id->path);
+    }
+    else
+    {
         cache_pos++;
         register_storage_idle_func(scrobbler_flush_callback);
     }
@@ -223,15 +202,11 @@
 
 static void scrobbler_change_event(void *data)
 {
-    struct mp3entry *id = (struct mp3entry*)data;
-    /* add entry using the previous scrobbler_entry and timestamp */
-    if (pending)
-        add_to_cache(audio_prev_elapsed());
+    struct mp3entry *id = ((struct track_event *)data)->id3;
 
     /*  check if track was resumed > %50 played
         check for blank artist or track name */
-    if ((id->elapsed > (id->length/2)) ||
-        (!id->artist ) || (!id->title ) )
+    if (id->elapsed > id->length / 2 || !id->artist || !id->title)
     {
         pending = false;
         logf("SCROBBLER: skipping file %s", id->path);
@@ -239,81 +214,85 @@
     else
     {
         logf("SCROBBLER: add pending");
-        copy_mp3entry(&scrobbler_entry, id);
-#if CONFIG_RTC
-        timestamp = mktime(get_time());
-#else
-        timestamp = 0;
-#endif
+        record_timestamp();
         pending = true;
     }
 }
 
+static void scrobbler_finish_event(void *data)
+{
+    struct track_event *te = (struct track_event *)data;
+
+    /* add entry using the currently ending track */
+    if (pending && (te->flags & TEF_CURRENT)
+#if CONFIG_CODEC == SWCODEC
+        && !(te->flags & TEF_REWIND)
+#endif
+    )
+    {
+        pending = false;
+        add_to_cache(te->id3);
+    }
+}
+
 int scrobbler_init(void)
 {
-    logf("SCROBBLER: init %d", global_settings.audioscrobbler);
+    if (scrobbler_initialised)
+        return 1;
 
-    if(!global_settings.audioscrobbler)
-        return -1;
+    scrobbler_cache = core_alloc("scrobbler",
+        SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN);
 
-    scrobbler_cache = core_alloc("scrobbler", SCROBBLER_MAX_CACHE*SCROBBLER_CACHE_LEN);
     if (scrobbler_cache <= 0)
     {
         logf("SCROOBLER: OOM");
         return -1;
     }
 
-    add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, scrobbler_change_event);
     cache_pos = 0;
     pending = false;
+
     scrobbler_initialised = true;
 
+    add_event(PLAYBACK_EVENT_TRACK_CHANGE, false, scrobbler_change_event);
+    add_event(PLAYBACK_EVENT_TRACK_FINISH, false, scrobbler_finish_event);
+
     return 1;
 }
 
 static void scrobbler_flush_cache(void)
 {
-    if (scrobbler_initialised)
-    {
         /* Add any pending entries to the cache */
-        if(pending)
-            add_to_cache(audio_prev_elapsed());
-
-        /* Write the cache to disk if needed */
-        if (cache_pos)
-            write_cache();
-
+    if (pending)
+    {
         pending = false;
+        if (audio_status())
+            add_to_cache(audio_current_track());
     }
+
+    /* Write the cache to disk if needed */
+    if (cache_pos)
+        write_cache();
 }
 
-void scrobbler_shutdown(void)
+void scrobbler_shutdown(bool poweroff)
 {
+    if (!scrobbler_initialised)
+        return;
+
+    remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
+    remove_event(PLAYBACK_EVENT_TRACK_FINISH, scrobbler_finish_event);
+
     scrobbler_flush_cache();
 
-    if (scrobbler_initialised)
+    if (!poweroff)
     {
-        remove_event(PLAYBACK_EVENT_TRACK_CHANGE, scrobbler_change_event);
-        scrobbler_initialised = false;
         /* get rid of the buffer */
         core_free(scrobbler_cache);
         scrobbler_cache = 0;
     }
-}
 
-void scrobbler_poweroff(void)
-{
-    if (scrobbler_initialised && pending)
-    {
-        if ( audio_status() )
-            add_to_cache(audio_current_track()->elapsed);
-        else
-            add_to_cache(audio_prev_elapsed());
-
-        /* scrobbler_shutdown is called later, the cache will be written
-        *  make sure the final track isn't added twice when that happens */
-        pending = false;
-    }
+    scrobbler_initialised = false;
 }
 
 bool scrobbler_is_enabled(void)
diff --git a/apps/scrobbler.h b/apps/scrobbler.h
index d96b230..a3d1b36 100644
--- a/apps/scrobbler.h
+++ b/apps/scrobbler.h
@@ -23,8 +23,7 @@
 #define __SCROBBLER_H__
 
 int scrobbler_init(void);
-void scrobbler_shutdown(void);
-void scrobbler_poweroff(void);
+void scrobbler_shutdown(bool poweroff);
 bool scrobbler_is_enabled(void);
 
 #endif /* __SCROBBLER_H__ */
diff --git a/apps/tagtree.c b/apps/tagtree.c
index 6b0c6aa..417b6f2 100644
--- a/apps/tagtree.c
+++ b/apps/tagtree.c
@@ -794,10 +794,13 @@
 static void tagtree_buffer_event(void *data)
 {
     struct tagcache_search tcs;
-    struct mp3entry *id3 = (struct mp3entry*)data;
+    struct mp3entry *id3 = ((struct track_event *)data)->id3;
+
+    bool runtimedb = global_settings.runtimedb;
+    bool autoresume = global_settings.autoresume_enable;
 
     /* Do not gather data unless proper setting has been enabled. */
-    if (!global_settings.runtimedb && !global_settings.autoresume_enable)
+    if (!runtimedb && !autoresume)
         return;
 
     logf("be:%s", id3->path);
@@ -811,7 +814,7 @@
         return;
     }
 
-    if (global_settings.runtimedb)
+    if (runtimedb)
     {
         id3->playcount  = tagcache_get_numeric(&tcs, tag_playcount);
         if (!id3->rating)
@@ -824,7 +827,7 @@
     }
 
  #if CONFIG_CODEC == SWCODEC
-    if (global_settings.autoresume_enable)
+    if (autoresume)
     {
         /* Load current file resume offset if not already defined (by
            another resume mechanism) */
@@ -846,18 +849,10 @@
 
 static void tagtree_track_finish_event(void *data)
 {
-    long lastplayed;
-    long tagcache_idx;
-    struct mp3entry *id3 = (struct mp3entry*)data;
+    struct track_event *te = (struct track_event *)data;
+    struct mp3entry *id3 = te->id3;
 
-    /* Do not gather data unless proper setting has been enabled. */
-    if (!global_settings.runtimedb && !global_settings.autoresume_enable)
-    {
-        logf("runtimedb gathering and autoresume not enabled");
-        return;
-    }
-
-    tagcache_idx=id3->tagcache_idx;
+    long tagcache_idx = id3->tagcache_idx;
     if (!tagcache_idx)
     {
         logf("No tagcache index pointer found");
@@ -865,26 +860,51 @@
     }
     tagcache_idx--;
 
-    /* Don't process unplayed tracks, or tracks interrupted within the
-       first 15 seconds. */
-    if (id3->elapsed == 0
 #if CONFIG_CODEC == SWCODEC /* HWCODEC doesn't have automatic_skip */
-        || (id3->elapsed < 15 * 1000 && !audio_automatic_skip())
+    bool auto_skip = te->flags & TEF_AUTO_SKIP;
 #endif
-        )
+    bool runtimedb = global_settings.runtimedb;
+    bool autoresume = global_settings.autoresume_enable;
+
+    /* Don't process unplayed tracks, or tracks interrupted within the
+       first 15 seconds but always process autoresume point */
+    if (runtimedb && (id3->elapsed == 0
+#if CONFIG_CODEC == SWCODEC
+        || (id3->elapsed < 15 * 1000 && !auto_skip)
+#endif
+        ))
     {
-        logf("not logging unplayed or skipped track");
+        logf("not db logging unplayed or skipped track");
+        runtimedb = false;
+    }
+
+#if CONFIG_CODEC == SWCODEC
+    /* 3s because that is the threshold the WPS uses to rewind instead
+       of skip backwards */
+    if (autoresume && (id3->elapsed == 0
+        || (id3->elapsed < 3 * 1000 && !auto_skip)))
+    {
+        logf("not logging autoresume");
+        autoresume = false;
+    }
+#endif
+
+    /* Do not gather data unless proper setting has been enabled and at least
+       one is still slated to be recorded */
+    if (!(runtimedb || autoresume))
+    {
+        logf("runtimedb gathering and autoresume not enabled/ignored");
         return;
     }
 
-    lastplayed = tagcache_increase_serial();
+    long lastplayed = tagcache_increase_serial();
     if (lastplayed < 0)
     {
         logf("incorrect tc serial:%ld", lastplayed);
         return;
     }
 
-    if (global_settings.runtimedb)
+    if (runtimedb)
     {
         long playcount;
         long playtime;
@@ -906,10 +926,9 @@
     }
 
 #if CONFIG_CODEC == SWCODEC
-    if (global_settings.autoresume_enable)
+    if (autoresume)
     {
-        unsigned long offset
-            = audio_automatic_skip() ? 0 : id3->offset;
+        unsigned long offset = auto_skip ? 0 : id3->offset;
 
         tagcache_update_numeric(tagcache_idx, tag_lastoffset, offset);
 
diff --git a/firmware/export/audio.h b/firmware/export/audio.h
index 24e8e9a..8108f50 100644
--- a/firmware/export/audio.h
+++ b/firmware/export/audio.h
@@ -236,12 +236,26 @@
 void audio_spdif_set_monitor(int monitor_spdif);
 #endif /* HAVE_SPDIF_IN */
 
-unsigned long audio_prev_elapsed(void);
-
-#if CONFIG_CODEC != SWCODEC
 /***********************************************************************/
 /* audio event handling */
+enum track_event_flags
+{
+    TEF_NONE      = 0x0,  /* no flags are set */
+    TEF_CURRENT   = 0x1,  /* event is for the current track */
+#if CONFIG_CODEC == SWCODEC
+    TEF_AUTO_SKIP = 0x2,  /* event is sent in context of auto skip */
+    TEF_REWIND    = 0x4,  /* interpret as rewind, id3->elapsed is the
+                             position before the seek back to 0 */
+#endif /* CONFIG_CODEC == SWCODEC */
+};
 
+struct track_event
+{
+    unsigned int flags;   /* combo of enum track_event_flags values */
+    struct mp3entry *id3; /* pointer to mp3entry describing track */
+};
+
+#if CONFIG_CODEC != SWCODEC
 /* subscribe to one or more audio event(s) by OR'ing together the desired */
 /* event IDs (defined below); a handler is called with a solitary event ID */
 /* (so switch() is okay) and possibly some useful data (depending on the */