27

I'm trying to send track informations via A2DP/AVRCP. Right now, music is perfectly streamed, but on the "receiver" (ie: car audio), the "track informations screen" is blank (which is not the case using popular players out there). Any idea ?

elgui
  • 3,303
  • 4
  • 28
  • 37

5 Answers5

21

This code worked for me:

private static final String AVRCP_PLAYSTATE_CHANGED = "com.android.music.playstatechanged";
private static final String AVRCP_META_CHANGED = "com.android.music.metachanged";

private void bluetoothNotifyChange(String what) {
    Intent i = new Intent(what);
    i.putExtra("id", Long.valueOf(getAudioId()));
    i.putExtra("artist", getArtistName());
    i.putExtra("album",getAlbumName());
    i.putExtra("track", getTrackName());
    i.putExtra("playing", isPlaying());        
    i.putExtra("ListSize", getQueue());
    i.putExtra("duration", duration());
    i.putExtra("position", position());
    sendBroadcast(i);
}

Call bluetoothNotifyChange with the appropriate intent (defined above) depending on your playback status: pause/playing/metadata changed.

William Seemann
  • 3,440
  • 10
  • 44
  • 78
  • This code work for me with my bluetooth-headset, but shows only track name... Will test more it. Thx. – DavyJonesUA Jul 22 '13 at 13:23
  • This code didn't work for me on a Sony headset but the Google Music app still can show song information on that headset. – Wayne Aug 20 '13 at 03:29
  • Oh thank you, I can see track name but artist/album info on that Sony headset, so something is wrong with my code. I call bluetoothNotifyChange() function everytime song's status changed (before playing, after pausing) but song's information still not show on the headset and of course I don't know why :( – Wayne Aug 20 '13 at 06:57
  • Are you using my exact code with all of the values filled in with actual data (no nulls)? This code initially didn't work when I tried to omit values, I think all of the intent fields are required. – William Seemann Aug 20 '13 at 07:02
  • Yes, I have tried your code with all valid values but still not work as expected. I even tried using 20 valid values (hard-coded as http://pastebin.com/ie4ZSfZc) like Google Music app but still no luck. P/s: just found a bug in your ServeStream app: i.putExtra("ListSize", getQueue().LENGTH); – Wayne Aug 20 '13 at 10:21
  • Where do you get the audioId? – Héctor Júdez Sapena Jun 17 '15 at 13:08
15

If you just want to send metadata information from your phone to a connected AVRCP compatible audio bluetooth device and DON'T want to control your app from the bluetooth device at all, you may find the code below usefull. And there is NO need to implement and register a MediaButtonEventReceiver with AudioManager.

I also included code for API Version 21 (LOLLIPOP, 5.0). From API 21 usage of the RemoteControlClient is deprecated and usage of MediaSession is encouraged.

Init phase:

    if (mAudioManager == null) {
        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    }

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        if (mRemoteControlClient == null) {
            Log.d("init()", "API " + Build.VERSION.SDK_INT + " lower then " + Build.VERSION_CODES.LOLLIPOP);
            Log.d("init()", "Using RemoteControlClient API.");

            mRemoteControlClient = new RemoteControlClient(PendingIntent.getBroadcast(this, 0, new Intent(Intent.ACTION_MEDIA_BUTTON), 0));
            mAudioManager.registerRemoteControlClient(mRemoteControlClient);
        }
    } else {
        if (mMediaSession == null) {
            Log.d("init()", "API " + Build.VERSION.SDK_INT + " greater or equals " + Build.VERSION_CODES.LOLLIPOP);
            Log.d("init()", "Using MediaSession API.");

            mMediaSession = new MediaSession(this, "PlayerServiceMediaSession");
            mMediaSession.setFlags(MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);
            mMediaSession.setActive(true);

        }
    }

Method for sending song metadata information to AVRCP compatible bluetooth audio device:

private void onTrackChanged(String title, String artist, String album, long duration, long position, long trackNumber) {

    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {

        RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
        ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, title);
        ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, artist);
        ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, album);
        ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, duration);
        ed.putLong(MediaMetadataRetriever.METADATA_KEY_CD_TRACK_NUMBER, trackNumber);
        ed.apply();

        mRemoteControlClient.setPlaybackState(RemoteControlClient.PLAYSTATE_PLAYING, position, 1.0f);
    } else {

        MediaMetadata metadata = new MediaMetadata.Builder()
                .putString(MediaMetadata.METADATA_KEY_TITLE, title)
                .putString(MediaMetadata.METADATA_KEY_ARTIST, artist)
                .putString(MediaMetadata.METADATA_KEY_ALBUM, album)
                .putLong(MediaMetadata.METADATA_KEY_DURATION, duration)
                .putLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER, trackNumber)
                .build();

        mMediaSession.setMetadata(metadata);

        PlaybackState state = new PlaybackState.Builder()
                .setActions(PlaybackState.ACTION_PLAY)
                .setState(PlaybackState.STATE_PLAYING, position, 1.0f, SystemClock.elapsedRealtime())
                .build();

        mMediaSession.setPlaybackState(state);
    }
}

Call if metadata changes but check if we have a A2DP connection to an audio bluetooth device. No need to send metadata information if we are not connected:

if (mAudioManager.isBluetoothA2dpOn()) {
    Log.d("AudioManager", "isBluetoothA2dpOn() = true");
    onTrackChanged(getTitle(), getArtist(), getAlbum(), getDuration(), getCurrentPosition(), getId());
}

Clean up on destroy:

@Override
public void onDestroy() {
    super.onDestroy();

[..]    

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
    } else {
        mMediaSession.release();
    }
}

This is how it looks like on my car stereo

Mike Lowery
  • 2,630
  • 4
  • 34
  • 44
Christian Ehrl
  • 161
  • 2
  • 7
7

This took me forever to figure out. Just broadcasting the intent didn't work. I got AVRCP to work by sending the intent AND implementing RemoteControlClient

Here's the code I used:

public void onCreate(){
    super.onCreate();

    mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    ComponentName rec = new ComponentName(getPackageName(), MyReceiver.class.getName());
    mAudioManager.registerMediaButtonEventReceiver(rec);

    Intent i = new Intent(Intent.ACTION_MEDIA_BUTTON);
    i.setComponent(rec);
    PendingIntent pi = PendingIntent.getBroadcast(this, 0, i, 0);
    mRemoteControlClient = new RemoteControlClient(pi);
    mAudioManager.registerRemoteControlClient(mRemoteControlClient);

    int flags = RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS
            | RemoteControlClient.FLAG_KEY_MEDIA_NEXT
            | RemoteControlClient.FLAG_KEY_MEDIA_PLAY
            | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
            | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
            | RemoteControlClient.FLAG_KEY_MEDIA_STOP
            | RemoteControlClient.FLAG_KEY_MEDIA_FAST_FORWARD
            | RemoteControlClient.FLAG_KEY_MEDIA_REWIND;
    mRemoteControlClient.setTransportControlFlags(flags);
}

private void onTrackChanged(...) {
    String title = ...;
    String artist = ...;
    String album = ...;
    long duration = ...;

    Intent i = new Intent("com.android.music.metachanged");
    i.putExtra("id", 1);
    i.putExtra("track", title);
    i.putExtra("artist", artist);
    i.putExtra("album", album);
    i.putExtra("playing", "true");
    sendStickyBroadcast(i);

    RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
    ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE, title);
    ed.putString(MediaMetadataRetriever.METADATA_KEY_ALBUM, album);
    ed.putString(MediaMetadataRetriever.METADATA_KEY_ARTIST, artist);
    ed.putLong(MediaMetadataRetriever.METADATA_KEY_DURATION, track.getDuration());
    ed.apply();
}

public void onDestroy(){
    mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
    super.onDestroy();
}
James Zhang
  • 1,709
  • 1
  • 13
  • 8
  • can you explain the line `ComponentName rec = new ComponentName(getPackageName(), MyReceiver.class.getName());` – Distwo Jun 17 '15 at 18:09
  • I tried you code, which indeed is in line with what we can find in https://android.googlesource.com/platform/packages/apps/Music/+/android-5.1.1_r13/src/com/android/music/MediaPlaybackService.java, but this is not working. Could it be because we have to deal with AudioManager.AUDIOFOCUS ? – LM.Croisez Aug 30 '15 at 14:03
1

To send the track metadata to the headunit you need to send an intent.

Intent avrcp = new Intent("com.android.music.metachanged");
avrcp.putExtra("track", "song title");
avrcp.putExtra("artist", "artist name");
avrcp.putExtra("album", "album name");
Context.sendBroadcast(avrcp);

When the song is done playing send another intent with empty strings for the second parameter of the putExtra method.

Bill D
  • 271
  • 1
  • 3
  • 6
  • 1
    While I haven't found another way to do this. It seems like a hackish approach. It looks like you are relying on the google music app to handle this intent. How does the google music app do it? – Aaron Dancygier Jul 29 '13 at 15:46
  • Indeed. Is it possible that the google music is the only one that can use avrcp? All the apps should have this capability via the RemoteControlClient right? – Pedro Lopes Jan 20 '14 at 21:18
0

You don't need to control SDK_INT if you are using Compat version of components. Below code tested with many car bluetooth devices and works like charm. Some devices don't understand some KEYs so it's better to use possible KEY. Reference. Don't forget to .build() after putBitmap not before

public static void sendTrackInfo() {
if(audioManager == null) {
    audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
}

if (mMediaSession == null) {
    mMediaSession = new MediaSessionCompat(this, "PlayerServiceMediaSession");
    mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
    mMediaSession.setActive(true);
}

if (audioManager.isBluetoothA2dpOn()) {
    try {
        String songTitle = getTitle();
        String artistTitle = getArtist();
        String radioImageUri = getImagesArr().get(0);
        String songImageUri = getImagesArr().get(1);
        long duration = getDuration();

        final MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();

        metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, songTitle);
        metadata.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, songTitle);
        metadata.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, artistTitle);
        metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ARTIST, artistTitle);
        metadata.putString(MediaMetadataCompat.METADATA_KEY_ART_URI, radioImageUri);
        metadata.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, radioImageUri);
        metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, songImageUri);
        metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, duration);

        imageCounter = 0;

        Glide.with(act)
                .load(Uri.parse(radioImageUri))
                .asBitmap()
                .into(new SimpleTarget<Bitmap>(250, 250) {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ART, bitmap);
                        metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap);

                        imageCounter = imageCounter + 1;

                        if(imageCounter == 2) {
                            mMediaSession.setMetadata(metadata.build());
                        }
                    }
                });

        Glide.with(act)
                .load(Uri.parse(songImageUri))
                .asBitmap()
                .into(new SimpleTarget<Bitmap>(250, 250) {
                    @Override
                    public void onResourceReady(Bitmap bitmap, GlideAnimation anim) {
                        metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap);

                        imageCounter = imageCounter + 1;

                        if(imageCounter == 2) {
                            mMediaSession.setMetadata(metadata.build());
                        }
                    }
                });
    }
    catch (JSONException e) {
        e.printStackTrace();
    }
}

}

OMArikan
  • 306
  • 1
  • 3
  • 9