You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter iOS集成CallKit后音频(麦克风/扬声器)失效问题求助

解决Flutter iOS使用CallKit后音频失效的问题

核心原因

iOS的CallKit会系统级接管音频会话,WebRTC的音频配置必须和CallKit的音频生命周期同步,否则会出现音频抢占或权限请求被系统拦截的问题。

具体修复步骤

1. Swift侧:完善CallKit与音频会话的协同配置

修改AppDelegate.swift,在CallKit相关流程中正确管理AVAudioSession,确保CallKit和WebRTC的音频会话不冲突:

import AVFoundation

// 在原有pushRegistry方法中,调用showCallkitIncoming后添加音频会话基础配置
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
    // ... 保留原有代码
    
    // Display callkit
    SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
    
    // 配置音频会话为CallKit兼容模式
    let audioSession = AVAudioSession.sharedInstance()
    do {
        try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.mixWithOthers, .allowBluetooth])
        try audioSession.setActive(true, options: .notifyOthersOnDeactivation)
    } catch {
        NSLog("Failed to configure audio session: \(error)")
    }
    
    // ... 保留原有代码
}

// 新增CallKit回调扩展,处理接听/结束时的音频会话状态
extension AppDelegate: CXProviderDelegate {
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        // 接听时激活音频会话
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setActive(true)
        } catch {
            NSLog("Failed to activate audio session on answer: \(error)")
        }
        action.fulfill()
    }
    
    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
        // 结束时停用音频会话
        let audioSession = AVAudioSession.sharedInstance()
        do {
            try audioSession.setActive(false, options: .notifyOthersOnDeactivation)
        } catch {
            NSLog("Failed to deactivate audio session on end: \(error)")
        }
        action.fulfill()
    }
}

2. Dart侧:调整权限请求时机与媒体流初始化

CallKit唤起应用时处于后台,此时权限请求会被系统拒绝,需将权限请求移至用户接听来电后(应用切换到前台时)执行:

acceptIncomingCall(SIP.Call incomingCall) async {
  try {
    bool remoteHasVideo = incomingCall!.remote_has_video;
    
    // 先请求权限,再获取媒体流(此时应用已切换到前台)
    await requestMediaAccess();
    var mediaStream = await getUserMedia();
    
    // ... 保留原有代码
    incomingCall.answer(env.ua!.helper.buildCallOptions(!remoteHasVideo), mediaStream: mediaStream);
  } catch (err) {
    print('ERROR TRYING TO ACCEPT $err');
  }
}

// 简化媒体流获取逻辑,去掉不必要的视频渲染
getUserMedia() async {
  final mediaConstraints = <String, dynamic>{
    'audio': true,
    'video': false,
  };
  
  print('Requesting media access...');
  return await navigator.mediaDevices.getUserMedia(mediaConstraints);
}

requestMediaAccess() async {
  var microphoneStatus = await Permission.microphone.status;
 
  if (microphoneStatus.isDenied) {
    await Permission.microphone.request();
  } else if (microphoneStatus.isPermanentlyDenied) {
    openAppSettings(); 
  }
  // iOS中麦克风权限已覆盖音频访问,无需重复请求audio权限
}

3. 启用插件自带的WebRTC适配逻辑

确保你已经取消了flutter_callkit_incoming插件中WebRTC相关的模板代码注释,该代码会自动处理CallKit与WebRTC的音频会话同步,避免手动配置的冲突。

关键注意事项

  • 必须确保用户在应用前台时已授予麦克风权限,后台状态下无法弹出权限请求弹窗。
  • 不要同时手动配置AVAudioSession和依赖插件的自动配置,否则会导致音频会话冲突。

内容的提问来源于stack exchange,提问作者Mitchell

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 11:30:54