FloatingLyrics™ integration

Starting from Musixmatch 4.0 for Android, we launched an extremely powerful and exciting feature called FloatingLyrics™. With FloatingLyrics™, we offer a seamless integration to every music player in the Android ecosystem by popping up a floating window that shows the lyrics to the user.


FloatingLyrics™ has the following capabilities, with many more to come:

  • switch from a compact to an extended view for synced lyrics
  • ability to be minimized (both with gestures or via a dedicated button), becoming a floating bubble that can be positioned everywhere on the screen (like Facebook's Chat Heads)
  • ability to be freely resized
  • low-priority persistent notification (to avoid accidental dismisses), which becomes a normal notification once FloatingLyrics™ is dismissed
  • automatically reacts to seeks, playstate changes and songs changes



Integration - Broadcast intents

Enabling FloatingLyrics™ for your application is fairly straightforward, as it is entirely controlled via broadcast intents. Basically, FloatingLyrics™ reacts to these two events:

  • com.android.music.metachanged

    the metadata has changed, which typically means that a new song is now playing

  • com.android.music.playstatechanged

    the playstate has changed, indicating that a playback event has occurred (play/pause, seek, stop, next/prev, ..)

The following list contains all the extra fields that the Intent must contain:

trackTitle of the current track (String)
artistArtist of the current track (String)
albumAlbum of the current track (String)
durationDuration of the current track (Long, milliseconds)
positionThe current playback position of the track (Long, milliseconds)
playingPlayback status of the current track (Boolean, whether is playing or paused)
scrobbling_sourcePackage name of the application that sends the Intent (String)

Let's see how to create a sample intent for these events. This is how the intent for a "new" song should look like:


    Intent intent = new Intent();
    intent.setAction("com.android.music.metachanged");
    Bundle bundle = new Bundle();

    // put the song's metadata
    bundle.putString("track", "Torn");
    bundle.putString("artist", "Natalie Imbruglia");
    bundle.putString("album", "Left of the Middle");

    // put the song's total duration (in ms)
    bundle.putLong("duration", 245000L); // 4:05

    // put the song's current position
    bundle.putLong("position", 1L); // beginning of the song

    // put the playback status
    bundle.putBoolean("playing", true); // currently playing

    // put your application's package
    bundle.putString("scrobbling_source", "com.yourcompany.yourapp");

    intent.putExtras(bundle);
    context.sendBroadcast(intent);

And this is a sample intent for the same song, which has been put to pause at 30 seconds:


    Intent intent = new Intent();
    intent.setAction("com.android.music.playstatechanged");
    Bundle bundle = new Bundle();

    // put the song's metadata
    bundle.putString("track", "Torn");
    bundle.putString("artist", "Natalie Imbruglia");
    bundle.putString("album", "Left of the Middle");

    // put the song's total duration (in ms)
    bundle.putLong("duration", 245000L); // 4:05

    // put the song's current position
    bundle.putLong("position", 30000L); // 0:30

    // put the playback status
    bundle.putBoolean("playing", false); // currently playing

    // put your application's package
    bundle.putString("scrobbling_source", "com.yourcompany.yourapp");

    intent.putExtras(bundle);
    context.sendBroadcast(intent);

The snippets are pretty self-explanatory. However, there are a few points worth noting:

  • the action "com.android.music.xxxxxxxchanged" corresponds to the same action that is used by default on Android; rather than creating a custom action for every player, it is better to stick to this one
  • as a consequence to the first note, the "scrobbling_source" extra is used to identify the source application (i.e., your app)
  • the "duration" parameter is very important as well, because it allows to precisely identify the right lyrics for that song
  • the values for position and duration are expressed in millisecods with a Long type


You can also use the following command line instructions, which match the previous snippets:

adb shell am broadcast -a com.android.music.metachanged -e track "Torn" -e artist "Natalie Imbruglia" -e album "Left of the Middle" -e scrobbling_source "com.yourcompany.yourapp" --el position 1 --ez playing true
adb shell am broadcast -a com.android.music.playstatechanged -e track "Torn" -e artist "Natalie Imbruglia" -e album "Left of the Middle" --el position 30000 --ez playing false -e scrobbling_source "com.yourcompany.yourapp"

Please note that, depending on your ADB version, whitespaces might need to be escaped, as you can see here:

adb shell am broadcast -a com.android.music.metachanged -e track "Torn" -e artist "Natalie\ Imbruglia" -e album "Left\ of\ the\ Middle" --el position 1 --ez playing true -e scrobbling_source "com.yourcompany.yourapp"



Integration - Media sessions

For devices that run on API 19 or above, Musixmatch receives notifications every time a media session is opened/changed/closed. For Musixmatch to correctly display synced lyrics, your application should provide the following information to the media session:

trackTitle of the current track (String)
artistArtist of the current track (String)
albumAlbum of the current track (String)
durationDuration of the current track (Long, milliseconds)
positionThe current playback position of the track (Long, milliseconds)
playingPlayback status of the current track (Boolean, whether is playing or paused)
scrobbling_sourcePackage name of the application that controls the media session (String)

For a full overview on the MediaSessionCompat APIs, please refer to the documentation.



Testing

Testing your integration is super simple. Just install Musixmatch on your Android device and play a song from your application: since FloatingLyrics™ is enabled by default, you should see it popping up on your screen.

Alternatively, you can send the command line test instructions to check that FloatingLyrics™ is working as expected.



Troubleshooting

There are a few ways of troubleshooting you integration.

LOGCAT

Keep an eye on on the log and look for warnings/errors. For instance, this error appears if you pass a String value for the position, rather than a Long value:


W/Bundle﹕ Key playing expected Boolean but value was a java.lang.String.  The default value false was returned.
W/Bundle﹕ Attempt to cast generated internal exception:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Boolean
    at android.os.Bundle.getBoolean(Bundle.java:864)
    at com.designfuture.music.scrobbler.BuiltInMusicAppReceiver.parseIntent(BuiltInMusicAppReceiver.java:65)
    at com.designfuture.music.scrobbler.AbstractPlayStatusReceiver.onReceive(AbstractPlayStatusReceiver.java:135)
    [...]

LYRICS ARE NEVER SYNCED

The fact that lyrics do not appear as synced (they don't scroll automatically) may indicate an issue on the "position" extra in your intent. Double check that it is a Long value and try hardcoding it to 1L.

Please note, however, that not all the lyrics we have are synced: be sure to test with the top hits of the moment, which are typically synced.