网上蓝牙音乐相关的文章实在太少,贡献一下本身的微薄之力java
先讲一些零碎知识点:
##################################华丽分割线###################################
蓝牙的源码路径
android
frameworks\base\core\java\android\bluetooth
##################################华丽分割线###################################
蓝牙音乐使用中须要用到的权限
git
在apk中的AndroidManifest.xml中要有如下语句得到蓝牙相关权限:github
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
##################################华丽分割线 下面介绍蓝牙的广播部分 start###############################iphone
蓝牙的广播部分
蓝牙的链接
注册蓝牙回调,这里须要讲一下BluetoothAdapter、BluetoothAvrcpController、BluetoothA2dpSink三个类
BluetoothAdapter做用:
获取蓝牙开关状态,搜索蓝牙,配对蓝牙等ide
BluetoothAvrcpController做用:
这个类里主要是维护蓝牙音乐的相关信息更新(ID3),操做控制蓝牙音乐(播放暂停上一曲下一曲等)函数
BluetoothA2dpSink 做用:
这个类里主要是肯定蓝牙音乐是否链接上ui
注册蓝牙回调广播
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); }
注册完回调之后,会有一个回调函数spa
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(); } }
以上就是用到的一些须要在广播回调中处理的一些事情及使用方法.net
//####################分割线 蓝牙的广播部分 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