android 实现车载蓝牙音乐介绍
网上蓝牙音乐相关的文章实在太少,贡献一下自己的微薄之力
先讲一些零碎知识点:
##################################华丽分割线###################################
蓝牙的源码路径
frameworks\base\core\java\android\bluetooth
##################################华丽分割线###################################
蓝牙音乐使用中需要用到的权限
在apk中的AndroidManifest.xml中要有以下语句获得蓝牙相关权限:
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
##################################华丽分割线 下面介绍蓝牙的广播部分 start###############################
蓝牙的广播部分
蓝牙的连接
注册蓝牙回调,这里需要讲一下BluetoothAdapter、BluetoothAvrcpController、BluetoothA2dpSink三个类
BluetoothAdapter作用:
获取蓝牙开关状态,搜索蓝牙,配对蓝牙等
BluetoothAvrcpController作用:
这个类里主要是维护蓝牙音乐的相关信息更新(ID3),操作控制蓝牙音乐(播放暂停上一曲下一曲等)
BluetoothA2dpSink 作用:
这个类里主要是确定蓝牙音乐是否连接上
注册蓝牙回调广播
public void registerBtReceiver(Context context) {
IntentFilter intentFilter = new IntentFilter();
//A2DP连接状态改变
intentFilter.addAction(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED);
//A2DP播放状态改变
intentFilter.addAction(BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED);
//监听蓝牙音乐暂停、播放等
intentFilter.addAction(BluetoothAvrcpController.ACTION_TRACK_EVENT);
//连接状态
intentFilter.addAction(BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED);
//浏览
intentFilter.addAction(BluetoothAvrcpController.ACTION_BROWSE_CONNECTION_STATE_CHANGED);
// 正在浏览的事件
intentFilter.addAction(BluetoothAvrcpController.ACTION_BROWSING_EVENT);
//当前 媒体 项目 改变
intentFilter.addAction(BluetoothAvrcpController.ACTION_CURRENT_MEDIA_ITEM_CHANGED);
intentFilter.addAction(BluetoothAvrcpController.ACTION_PLAYER_SETTING);
//没有媒体信息
intentFilter.addAction(BluetoothAvrcpController.ACTION_PLAY_FAILURE);
intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
intentFilter.addAction(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.addAction(BluetoothDevice.ACTION_NAME_CHANGED);
context.registerReceiver(mBtReceiver, intentFilter);
}
注册完回调以后,会有一个回调函数
private BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
switch (intent.getAction()) {
case BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED:
//todo 主要处理蓝牙a2dp连接状态
break;
case BluetoothA2dpSink.ACTION_PLAYING_STATE_CHANGED:
LogUtil.e(TAG, "mBtReceiver,
//控制蓝牙的播放状态,启动这个作为播放状态更新,时序太慢,所以注意不要用这个回调更新播放状态,建议在BluetoothAvrcpController.ACTION_TRACK_EVENT回调中处理播放状态
break;
case BluetoothAvrcpController.ACTION_CONNECTION_STATE_CHANGED:
break;
case BluetoothAvrcpController.ACTION_TRACK_EVENT:
//处理媒体信息,包括需要显示的MediaMetadata基本信息,和实时更新的PlaybackState信息
break;
case BluetoothAvrcpController.ACTION_BROWSE_CONNECTION_STATE_CHANGED:
// 手机端断开并重新连接上需要更新
break;
case BluetoothAvrcpController.ACTION_BROWSING_EVENT:
//蓝牙音乐列表
break;
case BluetoothAvrcpController.ACTION_CURRENT_MEDIA_ITEM_CHANGED:
//广播得到媒体信息
BluetoothAvrcpMediaItemData mMeidaItemData = intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_MEDIA_ITEM_DATA);
break;
case BluetoothAvrcpController.ACTION_PLAYER_SETTING:
break;
case BluetoothAvrcpController.ACTION_PLAY_FAILURE:
//这个是系统增加的接口,用于提示 当手机端播放器没有打开或者没有播放器的时候,是没有蓝牙音乐相关信息的,考虑到有些只是上层应用用原生的蓝牙多说一下,这种接口上层是没有的
break;
case BluetoothAdapter.ACTION_STATE_CHANGED:
//蓝牙开关状态 但一般不用这个,而是用 BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED 来判断
break;
case BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED:
//用这个广播判断蓝牙连接状态,注意这个是总开关,包含了蓝牙音乐和蓝牙电话
break;
case BluetoothDevice.ACTION_NAME_CHANGED:
//检查蓝牙名字,是否更新
break;
}
}
};
注册profile回调
public void registerProfile(Context context) {
if (BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, profileServiceListener, BluetoothProfile.A2DP_SINK)) {
LogUtil.i(TAG, "registerProfile: A2DP_SINK success");
} else {
LogUtil.e(TAG, "registerProfile: A2DP_SINK failed");
}
if (BluetoothAdapter.getDefaultAdapter().getProfileProxy(context, profileServiceListener, BluetoothProfile.AVRCP_CONTROLLER)) {
LogUtil.i(TAG, "registerProfile: AVRCP_CONTROLLER success");
} else {
LogUtil.e(TAG, "registerProfile: AVRCP_CONTROLLER failed");
}
}
A2dp和Avrcp的监听,主要是处理一些,应用还未起来,蓝牙已经连接上了,有些广播不走,需要通过这里来处理
//这个类里主要是确定蓝牙音乐是否连接上
private BluetoothA2dpSink mBluetoothA2dpSink;
//这个类里主要是维护蓝牙音乐的相关信息更新(ID3),操作控制蓝牙音乐(播放暂停上一曲下一曲等)
private BluetoothAvrcpController mAvrcpController;
private BluetoothProfile.ServiceListener profileServiceListener = new BluetoothProfile.ServiceListener() {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
LogUtil.i(TAG, "onServiceConnected: profile=" + profile + ",BluetoothProfile=" + proxy);
switch (profile) {
case BluetoothProfile.A2DP_SINK:
mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
LogUtil.e(TAG, "onServiceConnected: mBluetoothA2dpSink=" + mBluetoothA2dpSink);
//todo 这里可以做设置蓝牙为可用状态,或者更新设备名字,设置音频焦点
break;
case BluetoothProfile.AVRCP_CONTROLLER:
mAvrcpController = (BluetoothAvrcpController) proxy;
LogUtil.e(TAG, "onServiceConnected: mAvrcpController=" + mAvrcpController);
//todo 第一次注册,这种情况需要更新播放状态,跟新媒体信息,播放进度
break;
}
}
@Override
public void onServiceDisconnected(int profile) {
LogUtil.i(TAG, "onServiceDisconnected: profile=" + profile);
switch (profile) {
case BluetoothProfile.A2DP_SINK:
mBluetoothA2dpSink = null;
break;
case BluetoothProfile.AVRCP_CONTROLLER:
mAvrcpController = null;
break;
}
}
};
注销广播,注销监听
public void unregisterBtReceiver(Context context) {
if (mBtReceiver != null) {
context.unregisterReceiver(mBtReceiver);
mBtReceiver = null;
}
}
public void unRegisterProfile() {
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
mBluetoothAdapter.closeProfileProxy(BluetoothProfile.AVRCP_CONTROLLER, mAvrcpController);
}
广播中获取媒体信息和进度条信息
/**
* 更新歌曲基本信息ui
*
* @param intent 广播回调的intent 在 BluetoothAvrcpController.ACTION_TRACK_EVENT 回调中
*/
private void updateMediaMetadata(Intent intent) {
MediaMetadata mediaMetadata = intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_METADATA);
String title = mediaMetadata.getString(MediaMetadata.METADATA_KEY_TITLE);
String artist = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ARTIST);
String album = mediaMetadata.getString(MediaMetadata.METADATA_KEY_ALBUM);
String genre = mediaMetadata.getString(MediaMetadata.METADATA_KEY_GENRE);
long totalTime = mediaMetadata.getLong(MediaMetadata.METADATA_KEY_DURATION); //总时间更新,ms单位
long currentTrack = mediaMetadata.getLong(MediaMetadata.METADATA_KEY_NUM_TRACKS);
long totalTrack = mediaMetadata.getLong(MediaMetadata.METADATA_KEY_TRACK_NUMBER);
}
/**
* 主要用来实时更新当前播放进度, 广播回调的intent 在 BluetoothAvrcpController.ACTION_TRACK_EVENT 回调中
*
* @param intent
*/
private void updatePlaybackState(Intent intent) {
PlaybackState playbackState = intent.getParcelableExtra(BluetoothAvrcpController.EXTRA_PLAYBACK);
//更新播放状态 ,这里可以自己保存一个播放状态的标识
if ((playbackState.getState() == PlaybackState.STATE_PLAYING)
|| (playbackState.getState() == PlaybackState.STATE_FAST_FORWARDING)//快进
|| (playbackState.getState() == PlaybackState.STATE_REWINDING)) {//快退
updataPlayState(true);
} else {
updataPlayState(false);
}
long currentTime = playbackState.getPosition();//当前时间,ms为单位
}
用a2dp的连接状态,来判断蓝牙音乐的打开,因为蓝牙有一个总开关,里面包含有用于蓝牙音乐的a2dp通道开关,一个是用于蓝牙电话的,这里主要是标识蓝牙是否可用的状态
/**
* 蓝牙a2dp连接状态。在BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED回调中
*
* @param intent
*/
private void btA2dpContentStatus(Intent intent) {
int a2dpSinkConnectionState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
switch (a2dpSinkConnectionState) {
case BluetoothProfile.STATE_CONNECTED:
//这里重新获取了一下当前的设备信息,有些时候蓝牙断开了,设备信息是会被清空的
mConnectedDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//蓝牙音乐连接上,设置音源为true,里面会自动播放
setAudioStreamMode(true);
//这里有个特殊处理iphone,前提:iphone 会先走 adapter 后走 BluetoothA2dpSink,所以这再次获取一下设备信息
initConnectDevice();
break;
case BluetoothProfile.STATE_DISCONNECTED:
// 设置蓝牙为不可用状态,清空对应的一些标示位就行了
break;
}
}
蓝牙开关连接状态
/**
* 蓝牙开关连接状态
*
* @param intent
*/
private void btContentStatus(Intent intent) {
int currentContentStatus = intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, -1);
switch (currentContentStatus) {
case BluetoothAdapter.STATE_DISCONNECTED:
LogUtil.e(TAG, "蓝牙已经断开连接");
//只有蓝牙断开设置才置为null
mConnectedDevice = null;
//todo 处理一些播放状态,设备名字等,清空操作
//蓝牙断开,蓝牙也不应该发声了
setAudioStreamMode(false);
break;
case BluetoothAdapter.STATE_CONNECTING:
LogUtil.e(TAG, "蓝牙正在连接");
break;
case BluetoothAdapter.STATE_CONNECTED:
LogUtil.e(TAG, "蓝牙已经连接");
//连接的操作这里就不处理了,蓝牙因为的连接操作,放到 BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED 这个回调中,更准确
break;
case BluetoothAdapter.STATE_DISCONNECTING:
LogUtil.e(TAG, "蓝牙正在断开连接");
break;
}
}
检查蓝牙名字,是否更新
/**
* 检查蓝牙名字,是否更新,在BluetoothDevice.ACTION_NAME_CHANGED 回调中
*
* @param intent
*/
private void checkBtName(Intent intent) {
BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (bluetoothDevice.equals(mConnectedDevice)) {//地址相等则更新名字
updataName();
}
}
以上就是用到的一些需要在广播回调中处理的一些事情及使用方法
//####################分割线 蓝牙的广播部分 end#####################
//####################分割线 下面是需要提供的一些接口 start#####################
蓝牙接口部分
获取蓝牙设备的名字
首先获取BluetoothDevice 蓝牙设备的管理类,通过遍历方式获取
//蓝牙开关状态,搜索蓝牙,配对蓝牙等
private BluetoothAdapter mBluetoothAdapter;
private BluetoothDevice mConnectedDevice = null;//蓝牙连接的设备
public void initConnectDevice() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//mBluetoothAdapter为null概率很低,这里不做判断,系统一启动就会赋值
Set<BluetoothDevice> bondedDevices = mBluetoothAdapter.getBondedDevices();
for (BluetoothDevice device : bondedDevices) {
if (device.isConnected()) {
mConnectedDevice = device;
LogUtil.e(TAG, "蓝牙连接上的设备:mConnectedDevice=" + mConnectedDevice);
}
}
}
然后根据 mConnectedDevice 获取设备的名字
/**
* 获得远端(手机端)已连接的蓝牙设备的名称
*/
public String getBTDeviceName() {
return mConnectedDevice.getName();
}
设置焦点和释放焦点
//系统没有记录蓝牙音乐是否出声状态,需要自己记录,false不可以出声,这个方法是系统修改的,原生没有算车机端特殊处理的
private boolean mIsAudioStreamModeOn = false;
public void setAudioStreamMode(boolean on) {
boolean ret = mBluetoothAdapter.setAudioStreamMode(on);
if (ret) {
mIsAudioStreamModeOn = on;
} else {
mIsAudioStreamModeOn = false;
}
}
播放与暂停的使用
/**
* 播放与暂停
*/
public void sendPlayPauseCmd(boolean isAvrcpPlayStatus) {
if (isAvrcpPlayStatus) {
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PLAY, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PLAY, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
} else {
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PAUSE, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_PAUSE, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
}
}
上一曲
/**
* 上一曲
*/
public void sendPastCmd() {
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_BACKWARD, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
}
下一曲
/**
* 下一曲
*/
public void sendNextCmd() {
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_FORWARD, BluetoothAvrcp.PASSTHROUGH_STATE_PRESS);
mAvrcpController.sendPassThroughCmd(mConnectedDevice, BluetoothAvrcp.PASSTHROUGH_ID_FORWARD, BluetoothAvrcp.PASSTHROUGH_STATE_RELEASE);
}
获取当前媒体信息
/**
* 获取当前媒体信息
*
* @return
*/
public MediaMetadata getCurrentMediaInfo() {
return mAvrcpController.getMetadata(mConnectedDevice);
}
获取当前进度
/**
* 获取当前进度
*
* @return
*/
public long getCurrentProgress() {
return mAvrcpController.getPlaybackState(mConnectedDevice) == null ? 0 : mAvrcpController.getPlaybackState(mConnectedDevice).getPosition();
}
获取当前媒体总时长
/**
* 获取当前媒体总时长
*
* @return
*/
public long getCurrentTotleTime() {
return mAvrcpController.getMetadata(mConnectedDevice) == null ? 0
: mAvrcpController.getMetadata(mConnectedDevice).getLong(MediaMetadata.METADATA_KEY_DURATION);
}
//####################分割线 下面是需要提供的一些接口 end#####################
以上是我使用到的一些东西,可留言,更新你需要的
这里有一些东西是隐藏文件,需要编译打开
项目地址:https://github.com/MironGsony/CarBluetoothMusic
如何编译android系统的隐藏文件:https://editor.csdn.net/md/?articleId=110139734
本文地址:https://blog.csdn.net/Gsony6501/article/details/110001152
上一篇: 如何操作Redis和zookeeper实现分布式锁
下一篇: Redis 出现错误1067的解决办法