;
* 停止SCO
* @param promise
@ReactMethod
public void stopBluetoothSco(Promise promise) {
mAudioManager.setBluetoothScoOn(false);
mAudioManager.stopBluetoothSco();
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.setSpeakerphoneOn(true);
promise.resolve(true);
* 开启SCO
* @param promise
@ReactMethod
public void startBluetoothSco(Promise promise) {
mAudioManager.setBluetoothScoOn(true);
mAudioManager.startBluetoothSco();
mAudioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);
mAudioManager.setSpeakerphoneOn(false);
promise.resolve(true);
// 注册广播
private void registerBluetoothDeviceReceiver() {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
intentFilter.setPriority(Integer.MAX_VALUE);//设置优先级
blueToothtReceiver = new BlueToothtReceiver();//注册
mReactContext.registerReceiver(blueToothtReceiver, intentFilter);
public class BlueToothtReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();if(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED.equals(action)){//蓝牙连接成功
int currentState= intent.getIntExtra(BluetoothA2dp.EXTRA_STATE, -1);//当前状态
if(currentState == BluetoothA2dp.STATE_CONNECTED) {//连接成功
// connectPromise.resolve("连接成功");
sendEvent("connectSucceeded", "");
if(currentState == BluetoothA2dp.STATE_DISCONNECTED){//断开连接
sendEvent("connectDisconnect", "");
// connectPromise.reject("连接失败");
// 发送事件到js
public static void sendEvent(String eventName, @Nullable WritableMap params) {
A2dpModule.reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
原生实现耳机播放录音
这一个可以参考RN Incall的原生代码实现,与蓝牙类似、获取是否存在耳机、已经监听耳机插入拔出,进行对应的切换控制
这里我尝试后,发现ACTION_HEADSET_PLUG事件一直注册不上,注册了一获取action,app打开后就会闪退,希望有Android大咖能指出错误,谢谢
public class HeadsetModule extends ReactContextBaseJavaModule {
private BroadcastReceiver wiredHeadsetReceiver;
private boolean hasWiredHeadset = false;
private static final String ACTION_HEADSET_PLUG = (android.os.Build.VERSION.SDK_INT >= 21)
? AudioManager.ACTION_HEADSET_PLUG
: Intent.ACTION_HEADSET_PLUG;
public static ReactApplicationContext reactContext;
public HeadsetModule(ReactApplicationContext context) {
super(context);
reactContext = context;
// registerBluetoothDeviceReceiver();
@Override
public String getName() {
return "Headset";
// 注册广播
private void registerBluetoothDeviceReceiver() {
IntentFilter filter = new IntentFilter(ACTION_HEADSET_PLUG);
wiredHeadsetReceiver = new WiredHeadsetReceiver();// 注册
reactContext.registerReceiver(wiredHeadsetReceiver, filter);
public class WiredHeadsetReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ACTION_HEADSET_PLUG.equals(intent.getAction())) {
hasWiredHeadset = intent.getIntExtra("state", 0) == 1;
String deviceName = intent.getStringExtra("name");
if (deviceName == null) {
deviceName = "";
WritableMap data = Arguments.createMap();
data.putBoolean("isPlugged", (intent.getIntExtra("state", 0) == 1) ? true : false);
data.putBoolean("hasMic", (intent.getIntExtra("microphone", 0) == 1) ? true : false);
data.putString("deviceName", deviceName);
sendEvent("WiredHeadset", data);
} else {
hasWiredHeadset = false;
public static void sendEvent(String eventName, String params) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
RN通过原生代码实现对应设备播放录音
通过原生实现了基础能力,接下来只要在RN中调用进行切换状态即可,下面是RN切换要点(暂不考虑耳机、蓝牙同时使用情况)
// 初始化蓝牙监听器、自动切换播放录音模式
(async () => {
let bluetoothStatus = await A2dp.getBluetoothStatus();
if(bluetoothStatus > 0){
dispatch(playRecordDeviceChange('bluetooth'))
}else{
dispatch(playRecordDeviceChange('local_machine'))
A2dp.on('connectSucceeded', async () => {
console.log('蓝牙连接成功');
dispatch(playRecordDeviceChange('bluetooth'))
A2dp.on('connectDisconnect', async () => {
console.log('蓝牙断开连接');
dispatch(playRecordDeviceChange('local_machine'))
})();
return () => {
if(playRecordDevice == 'bluetooth'){
A2dp.stopBluetoothSco();
console.log("关闭蓝牙SCO(app关闭)");
}, [])
useEffect(() => {
// 初始化权限
// 初始化录音模块 AudioRecord
const options = {
sampleRate: 16000, // default 44100
channels: 1, // 1 or 2, default 1
bitsPerSample: 16, // 8 or 16, default 16
//7 -> AudioSource#VOICE_COMMUNICATION
//1 -> MIC
audioSource: 7, // android only (see below) https://developer.android.com/reference/android/media/MediaRecorder.AudioSource#VOICE_COMMUNICATION
wavFile: 'test.wav', // default 'audio.wav'
(async () => {
if(!hasPermission.current) {
console.log("检查权限",hasPermission.current)
hasPermission.current = await checkPermission();
if(hasPermission.current){
console.log("权限获取成功",hasPermission.current)
if(playRecordDevice){
if(playRecordDevice == 'local_machine'){
await A2dp.stopBluetoothSco();
console.log("关闭蓝牙SCO(playRecordDevice变化)");
}else if(playRecordDevice == 'bluetooth'){
options.audioSource = 1
await A2dp.startBluetoothSco();
console.log("打开蓝牙SCO(playRecordDevice变化)");
dispatch(initAudioRecord(options));
console.log("重新初始化录音模块 AudioRecord",options);
let mode = await A2dp.getAudioManagerMode()
console.log('AudioManager mode :>> ', mode);
}else{
dispatch(initAudioRecord(options));
}else{
console.log("权限获取失败");
Alert.alert("权限错误","软件无法使用,获取权限失败")
})();
return () => {}
}, [playRecordDevice])
参考资料/代码:
https://dandelioncloud.cn/article/details/1518425101983899650
microCloudCode/react-native-a2dp: reactNative连接a2dp和开启sco (github.com)
react-native-webrtc/react-native-incall-manager: Handling media-routes/sensors/events during a audio/video chat on React Native (github.com)