在使用语聊直播 App 的过程当中,经常会遇到移动设备系统来电的情形。此时会出现语聊直播 App 的音频采集被打断、音频播放干扰来电通话声音等问题。下面我们将分别介绍在 Android、iOS、Flutter 平台上,针对系统来电状态的监听方法,以及对应状态下 RTC 的处理策略。
Android
步骤一:声明权限
在 AndroidManifest.xml 文件中添加以下权限声明,以便应用能够访问电话状态。
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
步骤二:检查并申请权限
检查权限授予情况,并在运行时动态申请 READ_PHONE_STATE 权限。
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE)!= PackageManager.PERMISSION_GRANTED) {ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_PHONE_STATE}, PERMISSION_REQUEST_CODE);}
步骤三:注册广播接收器
注册广播接收器(BroadcastReceiver),用于监听特定的系统事件(如电话状态变化)。
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);// 注册广播接收器IntentFilter filter = new IntentFilter();filter.addAction("android.intent.action.PHONE_STATE");registerReceiver(callStateReceiver, filter);}@Overrideprotected void onDestroy() {super.onDestroy();// 取消注册广播接收器unregisterReceiver(callStateReceiver);}
步骤四:接收电话状态变化
通过广播接收器(BroadcastReceiver)接收系统电话状态变化,并进行通话状态映射。
private final BroadcastReceiver callStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.PHONE_STATE")) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) {// 空闲状态 handleCallState("idle"); } if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {// 来电状态 handleCallState("incoming"); } if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) {// 通话状态 handleCallState("offhook"); } } } };
步骤五:处理电话状态变化
处理通话状态变化,在特定状态下进行 RTC 音频处理,避免对来电通话声音产生干扰。
private void handleCallState(String state) { Log.d("CallState", state); TRTCCloud mTRTCCloud = TRTCCloud.sharedInstance(this); if (!state.equals("idle")) {// 暂停播放所有远端用户的音频流 mTRTCCloud.muteAllRemoteAudio(true); } else {// 恢复播放所有远端用户的音频流 mTRTCCloud.muteAllRemoteAudio(false); } }
iOS
iOS 平台有两类 API 可以监听获取系统电话状态,下面分别介绍两种方法的具体实现步骤。
方法一:CoreTelephony
这是一种较老的 API,虽然 Xcode 会提示该 API 已过时,但目前在 iOS 17 及以下系统版本仍能正常使用。
步骤一:引入头文件,声明 property
#import <CoreTelephony/CTCall.h>#import <CoreTelephony/CTCallCenter.h>@interface ViewController ()@property (nonatomic, strong) CTCallCenter *callCenter;@end
步骤二:注册回调,处理电话状态变化
self.callCenter = [[CTCallCenter alloc] init];self.callCenter.callEventHandler = ^(CTCall *call) {if ([call.callState isEqualToString:CTCallStateDialing]) {NSLog(@"正在拨号");// 暂停播放所有远端用户的音频流[self.trtcCloud muteAllRemoteAudio:YES];}else if([call.callState isEqualToString:CTCallStateIncoming]) {NSLog(@"来电");[self.trtcCloud muteAllRemoteAudio:YES];}else if([call.callState isEqualToString:CTCallStateConnected]) {NSLog(@"正在通话");[self.trtcCloud muteAllRemoteAudio:YES];}else if([call.callState isEqualToString:CTCallStateDisconnected]) {NSLog(@"断开通话");// 恢复播放所有远端用户的音频流[self.trtcCloud muteAllRemoteAudio:NO];}else {NSLog(@"未知");}};
方法二:CallKit
这是一种新的 API,但目前在中国大陆地区使用 CallKit,可能会无法通过 App Store 的审核,请酌情谨慎选择。
步骤一:引入头文件,声明 property
#import <CallKit/CXCallObserver.h>#import <CallKit/CXCall.h>@interface ViewController () <CXCallObserverDelegate>@property (nonatomic, strong) CXCallObserver *callObserver;@end
步骤二:设置代理
self.callObserver = [[CXCallObserver alloc] init];[self.callObserver setDelegate:self queue:dispatch_get_main_queue()];
步骤三:实现回调,监听通话状态
#pragma mark - CXCallObserverDelegate- (void)callObserver:(CXCallObserver *)callObserver callChanged:(CXCall *)call {NSLog(@"The unique identifier for the call: %@", call.UUID);NSLog(@"outgoing(拨打):%d onHold(挂起):%d hasConnected(已接通):%d hasEnded(已挂断):%d", call.outgoing, call.onHold, call.hasConnected, call.hasEnded);// 来电if (!call.outgoing && !call.hasConnected && !call.hasEnded) {NSLog(@"来电");// 暂停播放所有远端用户的音频流[self.trtcCloud muteAllRemoteAudio:YES];}// 拨打if (call.outgoing && !call.hasConnected && !call.hasEnded) {NSLog(@"拨打");[self.trtcCloud muteAllRemoteAudio:YES];}// 接通if (call.hasConnected && !call.hasEnded) {NSLog(@"接通");[self.trtcCloud muteAllRemoteAudio:YES];}// 挂断if (call.hasEnded) {NSLog(@"挂断");// 恢复播放所有远端用户的音频流[self.trtcCloud muteAllRemoteAudio:NO];}}
Flutter
Flutter 应用可以通过底层原生平台监听系统电话状态,然后利用 EventChannel 传递来自原生平台的事件。
步骤一:事件监听
在原生平台上,通过 StreamHandler 监听事件源(如系统电话状态变化)。
private static final String CALL_STATUS_CHANNEL = "com.example.call_status_listener/callStatus";private EventChannel.EventSink eventSink;new EventChannel(getFlutterEngine().getDartExecutor().getBinaryMessenger(), CALL_STATUS_CHANNEL).setStreamHandler( new EventChannel.StreamHandler() { @Override public void onListen(Object arguments, EventChannel.EventSink events) { eventSink = events; IntentFilter filter = new IntentFilter(); filter.addAction("android.intent.action.PHONE_STATE"); registerReceiver(callStateReceiver, filter); } @Override public void onCancel(Object arguments) { eventSink = null; unregisterReceiver(callStateReceiver); } } );
var eventSink: FlutterEventSink?// 以CallKit为例,也可参考iOS原生部分的示例,使用CoreTelephonylet callObserver = CXCallObserver()override func application(_ application: UIApplication,didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {GeneratedPluginRegistrant.register(with: self)let controller : FlutterViewController = window?.rootViewController as! FlutterViewControllerlet eventChannel = FlutterEventChannel(name: "com.example.call_status_listener/callStatus",binaryMessenger: controller.binaryMessenger)eventChannel.setStreamHandler(self)callObserver.setDelegate(self, queue: nil)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {self.eventSink = eventsreturn nil}func onCancel(withArguments arguments: Any?) -> FlutterError? {self.eventSink = nilreturn nil}
步骤二:事件发送
在原生平台上,当事件发生时,通过 EventSink 将事件发送到 Flutter。
private final BroadcastReceiver callStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.PHONE_STATE")) { String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); if (eventSink != null) { if (TelephonyManager.EXTRA_STATE_IDLE.equals(state)) { eventSink.success("idle"); } if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) { eventSink.success("incoming"); } if (TelephonyManager.EXTRA_STATE_OFFHOOK.equals(state)) { eventSink.success("offhook"); } } } } };
@objc func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {// 挂断if call.hasEnded == true {eventSink?("idle")}// 拨打else if call.isOutgoing == true && call.hasConnected == false {eventSink?("offhook")}// 来电else if call.isOutgoing == false && call.hasConnected == false && call.hasEnded == false {eventSink?("incoming")}// 接通else if call.hasConnected == true && call.hasEnded == false {eventSink?("offhook")}}
步骤三:事件接收
在 Flutter 端,通过 EventChannel 通道接收特定事件,并处理这些事件。
static const EventChannel _callStateChannel = EventChannel("com.example.call_status_listener/callStatus");String _callState = "Unknown";TRTCCloud trtcCloud = (await TRTCCloud.sharedInstance())!;_callStateChannel.receiveBroadcastStream().listen(_onCallStateChanged, onError: _onError);void _onCallStateChanged(Object? state) { setState(() { _callState = state.toString(); print("_callState: " + _callState);if (_callState != "idle") { // 暂停播放所有远端用户的音频流 trtcCloud.muteAllRemoteAudio(true); } else { // 恢复播放所有远端用户的音频流 trtcCloud.muteAllRemoteAudio(false); } }); }void _onError(Object error) { setState(() { _callState = "Failed to get call state: $error"; print("_callState: " + _callState); }); }