Skip to content

Playback Statistics

Note

This functionality is only available in component version 3.3.12 and later.

This is a topic for intermediate/advanced users with a good understanding of how JScript Panel 3 works with relation to handles and handle lists and you're familiar with callbacks.

It uses the same mechanism for data storage as foo_playcount, and data is remembered for up to 4 weeks when no matching track is part of the Media Library or any playlist.

Unlike previous implementaions in other scripting components, this allows you to customise the title formatting that database records are bound to.

The default is $lower($meta(artist,0) - %title%) but it can be changed via File>Preferences>Advanced>Tools>JScript Panel 3>Playback Statistics Title Format.

Note that changing this requires an immediate foobar2000 restart and any previously saved data is lost forever. Be careful!

Remember that if you think of using %path%, you must also include %subsong%. One physical file can have the same path for multiple handles if it's a cuesheet or other type of track with multiple chapters.

Overview#

First of all, there are 6 fields available through title formatting in any component and search.

%jsp3_playcount%
%jsp3_loved%
%jsp3_first_played%
%jsp3_last_played%
%jsp3_rating%
%jsp3_skipcount% // added in component version 3.3.14

To write these values, 6 handle methods can be used.

var handle = fb.GetFocusItem();
if (handle) {
    // Must be a whole number, use 0 to clear.
    handle.SetPlayCount(12);
    // Must be a whole number, use 0 to clear.
    handle.SetLoved(1);
    // Must be a whole number, use 0 to clear.
    handle.SetFirstPlayed(unix_timestamp);
    // Must be a whole number, use 0 to clear.
    handle.SetLastPlayed(unix_timestamp);
    // Must be a whole number, use 0 to clear.
    // Obviously there are no restrictions on the maximum value
    handle.SetRating(5);
    // Added in 3.3.14. Must be a whole number, use 0 to clear.
    handle.SetSkipcount(skipcount);
}

Although first played and last played are written/stored as unix timestamps, they are retrieved as strings in the usual foobar2000 YYYY-MM-DD HH:MM:SS format, adjusted to your local time zone.

Helper methods already exist for converting between them.

utils.DateStringToTimestamp(str)

utils.TimestampToDateString(ts)

To get the current time as a unix timestamp, you can use this:

// Divide by 1000 because JavaScript timestamps are in milliseconds.
var now = Math.round(new Date().getTime() / 1000);

// In component version 3.4.3 and later, you can use utils.Now()

After updating value(s) for a handle, you must use RefreshStats so the foobar2000 core and all other components are made aware of the changes. There is a IMetadbHandleList RefreshStats() method for this.

var handle = fb.GetFocusItem();
handle.SetRating(5);

// creating a handle list from a single handle can be done like this...
var handles = fb.CreateHandleList(handle);
handles.RefreshStats();

Finally there is also IMetadbHandleList ClearStats() which should be self explanatory. As of component version 3.3.13, this method calls RefreshStats() internally so there is no need to call it after.

Example#

Here's a crude example that updates as you play using the same rules as Last.fm for the amount of time you have to play a track. Of course it can be modified in any way you like.

var time_elapsed = 0;
var target_time = 0;

var tf_fp = fb.TitleFormat("%jsp3_first_played%");

// if a track hasn't been played yet, we want zero
// so it can be easily incremented
var tf_pc = fb.TitleFormat("$if2(%jsp3_playcount%,0)");

function on_playback_new_track() {
    // reset
    time_elapsed = 0;

    // this example uses the same rule as last.fm
    // half the track length or 4 minutes - whichever is lower
    target_time = Math.min(Math.ceil(fb.PlaybackLength / 2), 240);
}

function on_playback_time() {
    time_elapsed++;
    if (time_elapsed == target_time) {
        var now = Math.round(new Date().getTime() / 1000);
        var handle = fb.GetNowPlaying();

        // the return value from title format functions is always a string
        // but we want a number
        var playcount = Number(tf_pc.Eval());

        // increment playcount by 1
        handle.SetPlaycount(playcount + 1)

        // only write first played if it's currently empty
        if (tf_fp.Eval() == "") {
            handle.SetFirstPlayed(now);
        }

        // always write last played
        handle.SetLastPlayed(now)

        // notify foobar2000 core / other components
        fb.CreateHandleList(handle).RefreshStats();
    }
}