android 車載藍牙音樂介紹

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

相關文章
相關標籤/搜索