文档中心>TRTC 云助手>模块化方案>系统来电监听与处理

系统来电监听与处理

最近更新时间:2024-07-30 08:55:11

我的收藏
在使用语聊直播 App 的过程当中,经常会遇到移动设备系统来电的情形。此时会出现语聊直播 App 的音频采集被打断、音频播放干扰来电通话声音等问题。下面我们将分别介绍在 AndroidiOSFlutter 平台上,针对系统来电状态的监听方法,以及对应状态下 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),用于监听特定的系统事件(如电话状态变化)。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// 注册广播接收器
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.PHONE_STATE");
registerReceiver(callStateReceiver, filter);
}

@Override
protected 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 监听事件源(如系统电话状态变化)。
Android
iOS
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原生部分的示例,使用CoreTelephony
let callObserver = CXCallObserver()

override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let 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 = events
return nil
}

func onCancel(withArguments arguments: Any?) -> FlutterError? {
self.eventSink = nil
return nil
}

步骤二:事件发送

在原生平台上,当事件发生时,通过 EventSink 将事件发送到 Flutter。
Android
iOS
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); }); }