通过A2DP/AVRCP发送跟踪信息

27

我正在尝试通过A2DP/AVRCP发送音轨信息。目前,音乐被完美地传输,但是在“接收器”(例如:汽车音响)上,“音轨信息屏幕”为空白的(而使用流行播放器时不是这种情况)。有什么想法吗?

5个回答

21

这段代码对我有效:

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);
}

根据您的播放状态(暂停/播放/元数据更改),使用适当的意图(在上面定义)调用bluetoothNotifyChange函数。


这段代码在我的蓝牙耳机上可以工作,但只显示曲目名称... 我会进一步测试它。谢谢。 - DavyJonesUA
这段代码在我的Sony耳机上无法工作,但Google音乐应用程序仍然可以在该耳机上显示歌曲信息。 - Wayne
非常感谢,我可以在那个Sony耳机上看到曲目名称,但是艺术家/专辑信息无法显示,所以我的代码有问题。每次歌曲状态改变(播放前、暂停后)时,我都会调用bluetoothNotifyChange()函数,但歌曲信息仍然无法显示在耳机上,当然我不知道为什么 :( - Wayne
你是否使用了我的完整代码,并填入实际数据(没有空值)?当我尝试省略某些值时,这段代码最初并不起作用,我认为所有的意图字段都是必需的。 - William Seemann
是的,我已经尝试了您的代码,并使用所有有效值进行了测试,但仍然无法按预期工作。我甚至尝试使用20个有效值(硬编码为http://pastebin.com/ie4ZSfZc),就像Google音乐应用程序一样,但仍然没有运气。附言:我刚刚在您的ServeStream应用程序中发现了一个错误:i.putExtra("ListSize", getQueue().LENGTH); - Wayne
你从哪里获取audioId? - Héctor Júdez Sapena

15
如果您只想将元数据信息从手机发送到连接的AVRCP兼容音频蓝牙设备,并且不想完全从蓝牙设备控制应用程序,则可以使用下面的代码。而且,无需使用AudioManager实现和注册MediaButtonEventReceiver。
我还包括了API版本21(LOLLIPOP,5.0)的代码。从API 21开始,RemoteControlClient的使用已被弃用,鼓励使用MediaSession
初始化阶段:
    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);

        }
    }

将歌曲元数据信息发送到支持AVRCP的蓝牙音频设备的方法:
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);
    }
}

如果元数据发生变化,请检查我们是否与音频蓝牙设备建立了A2DP连接。如果没有连接,就不需要发送元数据信息:

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

销毁时进行清理:
@Override
public void onDestroy() {
    super.onDestroy();

[..]    

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

就是它在我的汽车音响上的样子


随机重复模式怎么样?你也处理了吗?谢谢。 - issamux
@issamux 看看这个 http://stackoverflow.com/questions/31194180/how-do-i-make-shuffle-playlist-button-and-repeat-button-in-android-studio - Christian Ehrl

7
这个问题花了我很长时间才解决。仅仅广播意图是不起作用的。我通过发送意图并且实现RemoteControlClient来使AVRCP正常工作。
这是我使用的代码:
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();
}

你能解释一下这行代码吗?ComponentName rec = new ComponentName(getPackageName(), MyReceiver.class.getName()); - Distwo
我已经尝试了您的代码,确实与我们可以在 https://android.googlesource.com/platform/packages/apps/Music/+/android-5.1.1_r13/src/com/android/music/MediaPlaybackService.java 找到的相符,但是它并没有工作。这可能是因为我们需要处理 AudioManager.AUDIOFOCUS 吗? - LM.Croisez

1

要将轨道元数据发送到主机单元,您需要发送一个意图。

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);

当歌曲播放完毕时,发送另一个意图,并在putExtra方法的第二个参数中使用空字符串。

1
虽然我还没有找到另一种方法来做这件事,但这似乎是一种hackish的方法。看起来你是依赖于Google音乐应用程序来处理这个意图。那么Google音乐应用程序是如何做到的呢? - Aaron Dancygier
确实。谷歌音乐是唯一可以使用AVRCP的吗?所有应用程序都应该通过RemoteControlClient具备这种功能,对吧? - Pedro Lopes

0
如果您正在使用兼容版本的组件,则不需要控制SDK_INT。下面的代码已经在许多汽车蓝牙设备上测试过,表现良好。一些设备无法理解某些KEYs,因此最好使用可能的KEY。参考。不要忘记在putBitmap之后而不是之前进行.build()操作。
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();
    }
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接