отправлять информацию о треке через A2DP/AVRCP

Я пытаюсь отправить информацию о треке через A2DP/AVRCP. Прямо сейчас музыка отлично транслируется, но на «приемнике» (то есть на автомобильной аудиосистеме) «экран с информацией о дорожке» пуст (чего нельзя сказать о популярных проигрывателях). Есть идеи ?


person elgui    schedule 20.03.2013    source источник


Ответы (5)


Этот код работал для меня:

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 с соответствующим намерением (определенным выше) в зависимости от вашего статуса воспроизведения: пауза/воспроизведение/метаданные изменены.

person William Seemann    schedule 08.04.2013
comment
Этот код работает для меня с моей bluetooth-гарнитурой, но показывает только название трека... Буду тестировать дальше. Спасибо. - person DavyJonesUA; 22.07.2013
comment
Этот код не работал у меня на гарнитуре Sony, но приложение Google Music по-прежнему может отображать информацию о песне на этой гарнитуре. - person Wayne; 20.08.2013
comment
О, спасибо, я вижу название трека, но информацию об исполнителе/альбоме на этой гарнитуре Sony, так что что-то не так с моим кодом. Я вызываю функцию bluetoothNotifyChange() каждый раз, когда изменяется статус песни (перед воспроизведением, после паузы), но информация о песне по-прежнему не отображается на гарнитуре, и, конечно, я не знаю, почему :( - person Wayne; 20.08.2013
comment
Вы используете мой точный код со всеми значениями, заполненными фактическими данными (без нулей)? Этот код изначально не работал, когда я пытался опустить значения, я думаю, что все поля намерения обязательны. - person William Seemann; 20.08.2013
comment
Да, я пробовал ваш код со всеми допустимыми значениями, но все равно не работает должным образом. Я даже пытался использовать 20 допустимых значений (жестко закодированных как pastebin.com/ie4ZSfZc), как приложение Google Music, но все же неудачно. P/s: только что обнаружил ошибку в вашем приложении ServeStream: i.putExtra(ListSize, getQueue().LENGTH); - person Wayne; 20.08.2013
comment
Где взять audioId? - person Héctor Júdez Sapena; 17.06.2015

Если вы просто хотите отправить метаданные со своего телефона на подключенное аудиоустройство Bluetooth, совместимое с AVRCP, и НЕ вообще хотите управлять своим приложением с устройства Bluetooth, вам может пригодиться приведенный ниже код. И нет необходимости НЕ реализовывать и регистрировать MediaButtonEventReceiver с помощью AudioManager.

Я также включил код для 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);

        }
    }

Способ отправки метаданных песни на Bluetooth-аудиоустройство, совместимое с 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 с аудиоустройством Bluetooth. Нет необходимости отправлять метаданные, если мы не подключены:

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

Вот это выглядит на магнитоле моей машины

person Christian Ehrl    schedule 19.10.2015
comment
а как насчет режима случайного повтора? вы с этим тоже справлялись? Спасибо - person issamux; 03.06.2016
comment
@issamux Взгляните на этот stackoverflow.com/questions/31194180/ - person Christian Ehrl; 09.07.2016

Это заняло у меня целую вечность, чтобы понять. Просто трансляция намерения не сработала. Я заставил AVRCP работать, отправив намерение И реализовать RemoteControlClient.

Вот код, который я использовал:

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();
}
person James Zhang    schedule 16.04.2015
comment
можешь объяснить строчку ComponentName rec = new ComponentName(getPackageName(), MyReceiver.class.getName()); - person Distwo; 17.06.2015
comment
Я попробовал ваш код, который действительно соответствует тому, что мы можем найти в android.googlesource.com/platform/packages/apps/Music/+/, но это не работает. Может быть, это потому, что нам приходится иметь дело с AudioManager.AUDIOFOCUS? - person LM.Croisez; 30.08.2015

Чтобы отправить метаданные трека на головное устройство, вам нужно отправить намерение.

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.

person Bill D    schedule 29.03.2013
comment
Пока я не нашел другого способа сделать это. Похоже на хакерский подход. Похоже, вы полагаетесь на музыкальное приложение Google, чтобы справиться с этим намерением. Как приложение Google Music делает это? - person Aaron Dancygier; 29.07.2013
comment
Конечно. Возможно ли, что только Google Music может использовать avrcp? Все приложения должны иметь эту возможность через RemoteControlClient, верно? - person Pedro Lopes; 21.01.2014

Вам не нужно управлять SDK_INT, если вы используете совместимую версию компонентов. Приведенный ниже код протестирован со многими автомобильными Bluetooth-устройствами и работает отлично. Некоторые устройства не понимают некоторые KEY, поэтому лучше использовать возможный KEY. Справочник. Не забудьте .build() после putBitmap не раньше

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

}

person OMArikan    schedule 09.02.2017