網上藍牙音樂相關的文章實在太少,貢獻一下本身的微薄之力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