手把手教你打通车载蓝牙与手机app的音频信息传输&车载反向控制手机app

===========

可以这样来进行简单的理解profile标致了一种能力,他定义 了设备之间如何进行交互,比如我们跨设备调用的AVRCP是定义如何进行跨设备调用的。每一个协议多有自己的profile(要看系统有没有实现对应的功能,如果不需要可以没有对应的profile)。

音频信息如何发送给车载蓝牙

在android5.0之前的音频信息发送是通过RemoteControlClient来完成的。5.0之后是通过MediaSession来完成。

MediaSession能够进行播放器音量、按键和数据传输控制的类。一般而言一个应用程序只有一个MediaSession。

如果想要接收其它设备的调用控制需要调用 setActive(true)和setCallback(Callback)。

音频信息的发送

通过MediaSession#setMetadata(@Nullable MediaMetadata metadata)可以发送当前歌曲信息,

在MediaMetadata 定义了很多相关的key,数据发送端只需要按照对应的key添加数据即可

MediaSession#setPlaybackState(@Nullable PlaybackState state)可以发送与播放状态相关的信息。

播放器数据发送:

mediaSession = new MediaSession(BluetoothPlayerActivity.this, “test”);
mediaSession.setActive(true);
mediaSession.setMetadata(new MediaMetadata.Builder()
.putString(MediaMetadata.METADATA_KEY_TITLE, “你是风而我是沙”)
.putString(MediaMetadata.METADATA_KEY_ARTIST, “我是谁谁谁”)
.build());
Toast.makeText(BluetoothPlayerActivity.this,“已经开始执行发送信息,重置需要退出重进”,Toast.LENGTH_SHORT).show();
AppExecutors.getInstance().networkIO().execute(new Runnable() {
@Override
public void run() {
int i = 0;
while (!destroy){
Log.d(TAG,"update PlaybackState "+mediaPlayer.getCurrentPosition());
mediaSession.setPlaybackState(new PlaybackState.Builder()
.setState(PlaybackState.STATE_PLAYING, mediaPlayer.getCurrentPosition(), 1.0f)
.build());
mediaSession.setMetadata(new MediaMetadata.Builder()
.putLong(MediaMetadata.METADATA_KEY_DURATION, mediaPlayer.getDuration())
.putString(MediaMetadata.METADATA_KEY_TITLE, “你是风而我是沙”+i)
.putString(MediaMetadata.METADATA_KEY_ARTIST, “我是谁谁谁”)
.build());
SystemClock.sleep(500);
i++;
}
}
});

车载音频信息接收

当系统接收到音频信息变化的时候会发送两个广播:

音频信息改变发送的广播.png 因此我们也同样的注册这个广播以便接收音频信息。

BroadcastReceiver avrcpBroadcastReceiver = new BroadcastReceiver() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(AvrcpControllerHelper.ACTION_TRACK_EVENT)){
final MediaMetadata mediaMetadata = intent.getParcelableExtra(AvrcpControllerHelper.EXTRA_METADATA);
final PlaybackState playbackState = intent.getParcelableExtra(AvrcpControllerHelper.EXTRA_PLAYBACK);
// if(mediaMetadata == null || playbackState == null){
// Log.e(TAG,"some info null ? = “+mediaMetadata+” “+playbackState);
// return;
// }
if(mediaMetadata != null){
name = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
author = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
totalTime = timeCover(+mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION));
}
if(playbackState != null){
currentTime = timeCover(playbackState.getPosition());
play = playbackState.getState() == PlaybackState.STATE_PLAYING;
}
TextView textView = findViewById(R.id.tvPlayTime);
textView.setText(“开始时间”+currentTime);
textView = findViewById(R.id.tvTotalTime);
textView.setText(“总时长”+totalTime);
textView = findViewById(R.id.tvMusicInfo);
textView.setText(“歌名:”+name +”------------ 作者 : "+author);
textView = findViewById(R.id.tvPlayToggle);
textView.setText("远程播放器 "+(play?“正在播放”:“未播放”));
}
}
};

registerReceiver(avrcpBroadcastReceiver,new IntentFilter(AvrcpControllerHelper.ACTION_TRACK_EVENT));

车载蓝牙如何反向控制音频播放器

车载蓝牙要反向控制音频播放器需要借助BluetoothAvrcpController,BluetoothAvrcpController对普通应而言是隐藏的。如果有framework可以直接访问,没有的话我们可以通过反射来进行处理。BluetoothAvrcpController#sendGroupNavigationCmd是用来反向调用音频播放器的。

同时我们为mediaSession设置callback对象

mediaSession.setCallback(new MediaSession.Callback() {

@Override
public void onCommand(@NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {
Log.e(TAG,“onCommand”);
super.onCommand(command, args, cb);
}

@Override
public boolean onMediaButtonEvent(@NonNull Intent mediaButtonIntent) {
Log.e(TAG,“onMediaButtonEvent”);
Toast.makeText(BluetoothPlayerActivity.this,“我对你那么好,你居然操作我”,Toast.LENGTH_SHORT).show();
return super.onMediaButtonEvent(mediaButtonIntent);
}
});

反射BluetoothAvrcpController来处理相关的方法

public class AvrcpControllerHelper {
private static final String TAG = AvrcpControllerHelper.class.getSimpleName();
public static int AVRCP_CONTROLLER = 12;

public static final String ACTION_TRACK_EVENT = “android.bluetooth.avrcp-controller.profile.action.TRACK_EVENT”;
public static final String EXTRA_METADATA = “android.bluetooth.avrcp-controller.profile.extra.METADATA”;
public static final String EXTRA_PLAYBACK = “android.bluetooth.avrcp-controller.profile.extra.PLAYBACK”;

public static void sendGroupNavigationCmd(BluetoothProfile bluetoothProfile, BluetoothDevice device, int keyCode, int keyState){
if(bluetoothProfile != null){
try {
Method m = bluetoothProfile.getClass().getMethod(“sendGroupNavigationCmd”,BluetoothDevice.class,int.class,int.class);
m.setAccessible(true);
m.invoke(bluetoothProfile,device,keyCode,keyState);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}

private static void fixSystemHideApi(){
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
return;
}
try {
Method forName = Class.class.getDeclaredMethod(“forName”, String.class);
Method getDeclaredMethod = Class.class.getDeclaredMethod(“getDeclaredMethod”, String.class, Class[].class);
Class<?> vmRuntimeClass = (Class<?>) forName.invoke(null, “dalvik.system.VMRuntime”);
Method getRuntime = (Method) getDeclaredMethod.invoke(vmRuntimeClass, “getRuntime”, null);
Method setHiddenApiExemptions = (Method) getDeclaredMethod.invoke(vmRuntimeClass, “setHiddenApiExemptions”, new Class[]{String[].class});
Object sVmRuntime = getRuntime.invoke(null);
setHiddenApiExemptions.invoke(sVmRuntime, new Object[]{new String[]{“L”}});
} catch (Throwable e) {
Log.e(“[error]”, “reflect bootstrap failed:”, e);
}
}

public static List getConnectedDevices(BluetoothProfile profile){
if(profile == null){
return null;
}
fixSystemHideApi();
try {
Method m = profile.getClass().getDeclaredMethod(“getConnectedDevices”);
m.setAccessible(true);
return (List) m.invoke(profile);
} catch (NoSuchMethodException e) {
Log.w(TAG, “No disconnect method in the " + profile.getClass().getName() +
" class, ignoring request.”);
return null;
} catch (InvocationTargetException | IllegalAccessException e) {
Log.w(TAG, "Could not execute method ‘disconnect’ in profile " +
profile.getClass().getName() + “, ignoring request.”, e);
return null;
}
}

//系统的BluetoothAvrcp 获取不到,这里把这个拷贝出来
public static class BluetoothAvrcp {

/*

  • State flags for Passthrough commands
    */
    public static final int PASSTHROUGH_STATE_PRESS = 0;
    public static final int PASSTHROUGH_STATE_RELEASE = 1;

/*

最后

目前已经更新的部分资料:



《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
N2-1715311671372)]
[外链图片转存中…(img-LNNgoJ8K-1715311671373)]
[外链图片转存中…(img-yJxanH0c-1715311671373)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 11
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值