Android 通过A2DP/AVRCP发送航迹信息

Android 通过A2DP/AVRCP发送航迹信息,android,bluetooth,a2dp,avrcp,Android,Bluetooth,A2dp,Avrcp,我正试图通过A2DP/AVRCP发送音轨信息。现在,音乐是完美的流媒体,但在“接收器”(即:汽车音频)“曲目信息屏幕”是空白的(这不是使用流行播放器的情况)。 有什么想法吗?要将曲目元数据发送到主机,您需要发送一个意图 Intent avrcp = new Intent("com.android.music.metachanged"); avrcp.putExtra("track", "song title"); avrcp.putExtra("artist", "artist name");

我正试图通过A2DP/AVRCP发送音轨信息。现在,音乐是完美的流媒体,但在“接收器”(即:汽车音频)“曲目信息屏幕”是空白的(这不是使用流行播放器的情况)。
有什么想法吗?

要将曲目元数据发送到主机,您需要发送一个意图

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方法的第二个参数。

此代码对我有效:

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具有适当的意图(定义见上文)。

我花了很长时间才弄清楚。仅仅传播意图是行不通的。我通过发送意向和实施使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();
}

如果您只想将元数据信息从手机发送到已连接的AVRCP兼容音频蓝牙设备,而根本不想从蓝牙设备控制您的应用程序,您可能会发现下面的代码很有用。 而且,无需在AudioManager中实现并注册MediaButtonEventReceiver

我还包括API版本21(棒棒糖,5.0)的代码。根据API 21,不推荐使用RemoteControlClient,并鼓励使用

初始阶段:

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

        }
    }
销毁时的清理:

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

[..]    

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
    } else {
        mMediaSession.release();
    }
}
@覆盖
公共空间{
super.ondestory();
[..]    
if(Build.VERSION.SDK\u INT

这是我的车载立体声系统的外观

如果您使用的是Compat版本的组件,则无需控制SDK\u INT。 下面的代码使用许多车载蓝牙设备进行了测试,效果很好。 有些设备不理解某些密钥,因此最好使用可能的密钥。不要忘记在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();
    }
}
public static void sendTrackInfo(){
如果(audioManager==null){
audioManager=(audioManager)getSystemService(Context.AUDIO\u服务);
}
如果(mMediaSession==null){
mMediaSession=新的MediaSessionCompat(即“PlayerServiceMediaSession”);
mmediasion.setFlags(MediaSessionCompat.FLAG处理传输控制);
mmediasion.setActive(真);
}
if(audioManager.isBluetoothA2dpOn()){
试一试{
字符串songTitle=getTitle();
字符串artistTitle=getArtist();
字符串radiomageri=getImagesArr().get(0);
字符串songImageUri=getImagesArr().get(1);
long duration=getDuration();
final MediaMetadataCompat.Builder元数据=新的MediaMetadataCompat.Builder();
putString(mediamatadatacompat.metadata\u KEY\u TITLE,songtile);
putString(mediamatadatacompat.metadata\u KEY\u DISPLAY\u TITLE,songtile);
metadata.putString(mediamatadatacompat.metadata\u KEY\u ARTIST,artistTitle);
metadata.putString(mediamatadatacompat.metadata\u KEY\u ALBUM\u ARTIST、artistTitle);
putString(mediamatadatacompat.metadata\u KEY\u ART\u URI,radiomageuri);
putString(mediamatadatacompat.metadata\u KEY\u DISPLAY\u ICON\u URI,radiomageuri);
putString(mediamatadatacompat.metadata\u KEY\u ALBUM\u ART\u URI,songimageri);
metadata.putLong(mediamatadatacompat.metadata\u KEY\u DURATION,DURATION);
imageCounter=0;
滑翔
.load(Uri.parse(放射性图像Uri))
.asBitmap()
.into(新的SimpleTarget(250250){
@凌驾
public void onResourceReady(位图、动画){
metadata.putBitmap(MediaMetadataCompat.metadata\u KEY\u ART,位图);
metadata.putBitmap(MediaMetadataCompat.metadata\u键\u显示\u图标,位图);
imageCounter=imageCounter+1;
如果(imageCounter==2){
mmediasion.setMetadata(metadata.build());
}
}
});
滑翔
.load(Uri.parse(songImageUri))
.asBitmap()
.into(新的SimpleTarget(250250){
@凌驾
public void onResourceReady(位图、动画){
metadata.putBitmap(MediaMetadataCompat.metadata\u KEY\u ALBUM\u ART,位图);
imageCounter=imageCounter+1;
如果(imageCounter==2){
mmediasion.setMetadata(metadata.build());
}
}
});
}
捕获(JSONException e){
e、 printStackTrace();
}
}

}

此代码适用于我的蓝牙耳机,但仅显示曲目名称。。。将测试更多。Thx。这段代码在索尼耳机上对我不起作用,但谷歌音乐应用程序仍然可以在耳机上显示歌曲信息。哦,谢谢,我可以在索尼耳机上看到曲目名称,但艺术家/专辑信息,所以我的代码有问题。每当歌曲的状态改变时(播放前,暂停后),我都会调用bluetoothNotifyChange()函数,但歌曲的信息仍然没有显示在耳机上,当然我也不知道为什么:(您使用的是我的精确代码,所有值都用实际数据填充(没有空值)?当我试图省略值时,此代码最初不起作用,我认为所有的意图字段都是必需的。是的,我用所有有效值尝试了你的代码,但仍然没有按预期工作。我甚至尝试使用20个有效值(硬编码为),如谷歌音乐应用程序,但仍然没有成功。P/s:刚刚在你的ServeStream应用程序中发现一个错误:I.putExtra(“ListSize”,getQueue().LENGTH);而我还没有找到另一种方法来实现这一点。这似乎是一种黑客方法。看起来你依赖谷歌音乐应用程序来处理
@Override
public void onDestroy() {
    super.onDestroy();

[..]    

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
        mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
    } else {
        mMediaSession.release();
    }
}
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();
    }
}