手把手教你打通车载蓝牙与手机 app 的音频信息传输 & 车载反向控制手机 app
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++;}}});
车载音频信息接收
当系统接收到音频信息变化的时候会发送两个广播:
因此我们也同样的注册这个广播以便接收音频信息。
BroadcastReceiver avrcpBroadcastReceiver = new BroadcastReceiver() {@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)@Overridepublic 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() {
@Overridepublic void onCommand(@NonNull String command, @Nullable Bundle args, @Nullable ResultReceiver cb) {Log.e(TAG,"onCommand");super.onCommand(command, args, cb);}
@Overridepublic 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.invok
e(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<BluetoothDevice> getConnectedDevices(BluetoothProfile profile){if(profile == null){return null;}fixSystemHideApi();try {Method m = profile.getClass().getDeclaredMethod("getConnectedDevices");m.setAccessible(true);return (List<BluetoothDevice>) 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;
/*
Operation IDs for Passthrough commands/public static final int PASSTHROUGH_ID_SELECT = 0x00; / select /public static final int PASSTHROUGH_ID_UP = 0x01; / up /public static final int PASSTHROUGH_ID_DOWN = 0x02; / down /public static final int PASSTHROUGH_ID_LEFT = 0x03; / left /public static final int PASSTHROUGH_ID_RIGHT = 0x04; / right /public static final int PASSTHROUGH_ID_RIGHT_UP = 0x05; / right-up /public static final int PASSTHROUGH_ID_RIGHT_DOWN = 0x06; / right-down /public static final int PASSTHROUGH_ID_LEFT_UP = 0x07; / left-up /public static final int PASSTHROUGH_ID_LEFT_DOWN = 0x08; / left-down /public static final int PASSTHROUGH_ID_ROOT_MENU = 0x09; / root menu */
评论