admin管理员组

文章数量:1300200

When using CallKit in my flutter app audio(both mic and speaker) stop working. When not using call kit to answer calls the app work fine. I am using the flutter flutter_callkit_incoming to use callkit and flutter_webrtc for the telephony. Flutter_callkit_incoming has some boilerplate code code include sections to uncomment when using webrtc and I have seen multiple fixes to suggest to make sure the to configure sharedAudioSession before the callkit is sent. Neither of this approaches seemed to have worked.

Here are some snippets of codes from the AppDelegate side and the swift side. There is a lot more code, and it is very messy, but this is everything involving the issue at hand.

//AppDelagate.swift section that handle incoming voip push
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        NSLog("flutter ns didReceiveIncomingPushWith\n\(payload.dictionaryPayload as AnyObject)")
        guard type == .voIP else { return }
        let linkedid = payload.dictionaryPayload["linkedid"] as? String ?? ""
        let from = payload.dictionaryPayload["from"] as? String ?? ""
        let handle = payload.dictionaryPayload["callerIdNumber"] as? String ?? ""
        let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false
        let foundLessThan = from.firstIndex(of: "<") ?? from.startIndex
        var nameCaller = String(from[..<foundLessThan])
        if(nameCaller.count == 0){
            nameCaller = handle
        }
        let data = flutter_callkit_incoming.Data(id: UUID().uuidString, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
        //set more data
        data.extra = ["from": from, "linkedid": linkedid, "platform": "ios"]
        NSLog("flutter ns displaying callkit nameCaller: " + nameCaller + ", handle: " + handle)
        // Display callkit
        SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
        NSLog("flutter ns find controller")
        let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController


        let channel = FlutterMethodChannel(name: "voip_notification", binaryMessenger: controller.binaryMessenger)
        let messageData = [String: String]()
        NSLog("flutter ns Trying to get flutter to do things")
        //Invoke channel doesn't do any logic but seems to ensure that the darr/flutter side of the app
        //Is active, which is the side that handles connecting the call
        channel.invokeMethod("voip_notification", arguments:messageData) { (result) in
            if let resultString = result as? String {
                NSLog("flutter ns flutter invoked: " + resultString)
            }
        }
        //Make sure call completion()
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            print("flutter ns Flutter exiting")
            NSLog("flutter ns Flutter exiting")
            completion()
        }
    }

This next section it the relevant dart/flutter portion of the code

import 'dart:async';

import 'package:flutter_callkit_incoming/entities/entities.dart';
import 'package:hcs_mobile/utilities/environment.dart' as env;
import 'package:hcs_mobile/utilities/user_agent.dart';
import 'package:hcs_mobile/utilities/call.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:sip_ua/sip_ua.dart' as SIP;
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';

void initCallKitListener(){
  FlutterCallkitIncoming.onEvent.listen((event) async {
    env.logger.d('CallKit Event: $event');
    switch (event!.event) {
      case Event.actionCallIncoming:
        // TODO: received an incoming call
        break;
      //...
      case Event.actionCallAccept:
        //env.callManager is where we hold pending and awnswered calls
        final CallObj? call = findCall(env.callManager.pendingIncomingCalls, event);
        print("INCOMEING CALLS ${env.callManager.pendingIncomingCalls}");
        final String callId = event.body['id'] as String;
        print("Found call $call");
        if(call != null){
          acceptIncomingCall(call.sipCall);
        }
        break;
      case Event.actionCallDecline:
        //...
  });
}

// Finds pending call with matching(enough) from
Future<String?> findCall(final Map<String, CallObj> callMap, final dynamic event) async{
  try{
    final String eventFrom = event.body['extra']['from'] as String;
    int startGrab = eventFrom.indexOf('>'), endGrab = eventFrom.indexOf(';');
    if(endGrab == -1){
      endGrab = eventFrom.length;
    }
    final String matchingPart = eventFrom.substring(startGrab +1, endGrab);
      for(final CallObj checkCall in callMap.values){
        print(checkCall.sipCall.session.remote_identity?.uri?.toString());
        print(matchingPart);
        if(checkCall.sipCall.session.remote_identity?.uri?.toString().contains(matchingPart)?? false){
          return checkCall;
        }
      }
  }catch(error){
    env.logger.e('Encountered an error trying to match from to call: $error');
  }
  return null;
}

acceptIncomingCall(SIP.Call incomingCall) async {
  try {
    bool remoteHasVideo = incomingCall!.remote_has_video;
    var mediaStream = await getUserMedia();
    //Moves pending call to active list
    await env.callManager.convertPendingToActive(incomingCall.id!);
    // Puts all other active calls on hold
    env.callManager.setActiveCall(incomingCall.id);
    //env.ua.helper is our SIP.SIPUAHelper object that is connect to our phone system
    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,
  };  
  
  print('Requesting medai access...');
  await requestMediaAccess();
  print('Passed request media access...');
  MediaStream? localStream;
  List<MediaDeviceInfo>? mediaDevicesList;
  final localRenderer = RTCVideoRenderer();
  mediaDevicesList = await navigator.mediaDevices.enumerateDevices();
  await localRenderer.initialize();
  localStream = await Helper.openCamera({
      'video': false,
      'audio': true,
  });
  localRenderer.srcObject = localStream;
  return localStream;
}

requestMediaAccess() async {
  var audioStatus = await Permission.audio.status;
  var microphoneStatus = await Permission.microphone.status;
 

  //microphone and audio permissions is always denied when callkit is active
  if (audioStatus.isDenied) {
    await Permission.audio.request()
  } else if (audioStatus.isPermanentlyDenied) {
    openAppSettings(); 
  }

  if (microphoneStatus.isDenied) {
    await Permission.microphone.request();
  } else if (microphoneStatus.isPermanentlyDenied) {
    openAppSettings(); 
  }
}

When using CallKit in my flutter app audio(both mic and speaker) stop working. When not using call kit to answer calls the app work fine. I am using the flutter flutter_callkit_incoming to use callkit and flutter_webrtc for the telephony. Flutter_callkit_incoming has some boilerplate code code include sections to uncomment when using webrtc and I have seen multiple fixes to suggest to make sure the to configure sharedAudioSession before the callkit is sent. Neither of this approaches seemed to have worked.

Here are some snippets of codes from the AppDelegate side and the swift side. There is a lot more code, and it is very messy, but this is everything involving the issue at hand.

//AppDelagate.swift section that handle incoming voip push
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
        NSLog("flutter ns didReceiveIncomingPushWith\n\(payload.dictionaryPayload as AnyObject)")
        guard type == .voIP else { return }
        let linkedid = payload.dictionaryPayload["linkedid"] as? String ?? ""
        let from = payload.dictionaryPayload["from"] as? String ?? ""
        let handle = payload.dictionaryPayload["callerIdNumber"] as? String ?? ""
        let isVideo = payload.dictionaryPayload["isVideo"] as? Bool ?? false
        let foundLessThan = from.firstIndex(of: "<") ?? from.startIndex
        var nameCaller = String(from[..<foundLessThan])
        if(nameCaller.count == 0){
            nameCaller = handle
        }
        let data = flutter_callkit_incoming.Data(id: UUID().uuidString, nameCaller: nameCaller, handle: handle, type: isVideo ? 1 : 0)
        //set more data
        data.extra = ["from": from, "linkedid": linkedid, "platform": "ios"]
        NSLog("flutter ns displaying callkit nameCaller: " + nameCaller + ", handle: " + handle)
        // Display callkit
        SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true)
        NSLog("flutter ns find controller")
        let controller : FlutterViewController = self.window?.rootViewController as! FlutterViewController


        let channel = FlutterMethodChannel(name: "voip_notification", binaryMessenger: controller.binaryMessenger)
        let messageData = [String: String]()
        NSLog("flutter ns Trying to get flutter to do things")
        //Invoke channel doesn't do any logic but seems to ensure that the darr/flutter side of the app
        //Is active, which is the side that handles connecting the call
        channel.invokeMethod("voip_notification", arguments:messageData) { (result) in
            if let resultString = result as? String {
                NSLog("flutter ns flutter invoked: " + resultString)
            }
        }
        //Make sure call completion()
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            print("flutter ns Flutter exiting")
            NSLog("flutter ns Flutter exiting")
            completion()
        }
    }

This next section it the relevant dart/flutter portion of the code

import 'dart:async';

import 'package:flutter_callkit_incoming/entities/entities.dart';
import 'package:hcs_mobile/utilities/environment.dart' as env;
import 'package:hcs_mobile/utilities/user_agent.dart';
import 'package:hcs_mobile/utilities/call.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:sip_ua/sip_ua.dart' as SIP;
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';

void initCallKitListener(){
  FlutterCallkitIncoming.onEvent.listen((event) async {
    env.logger.d('CallKit Event: $event');
    switch (event!.event) {
      case Event.actionCallIncoming:
        // TODO: received an incoming call
        break;
      //...
      case Event.actionCallAccept:
        //env.callManager is where we hold pending and awnswered calls
        final CallObj? call = findCall(env.callManager.pendingIncomingCalls, event);
        print("INCOMEING CALLS ${env.callManager.pendingIncomingCalls}");
        final String callId = event.body['id'] as String;
        print("Found call $call");
        if(call != null){
          acceptIncomingCall(call.sipCall);
        }
        break;
      case Event.actionCallDecline:
        //...
  });
}

// Finds pending call with matching(enough) from
Future<String?> findCall(final Map<String, CallObj> callMap, final dynamic event) async{
  try{
    final String eventFrom = event.body['extra']['from'] as String;
    int startGrab = eventFrom.indexOf('>'), endGrab = eventFrom.indexOf(';');
    if(endGrab == -1){
      endGrab = eventFrom.length;
    }
    final String matchingPart = eventFrom.substring(startGrab +1, endGrab);
      for(final CallObj checkCall in callMap.values){
        print(checkCall.sipCall.session.remote_identity?.uri?.toString());
        print(matchingPart);
        if(checkCall.sipCall.session.remote_identity?.uri?.toString().contains(matchingPart)?? false){
          return checkCall;
        }
      }
  }catch(error){
    env.logger.e('Encountered an error trying to match from to call: $error');
  }
  return null;
}

acceptIncomingCall(SIP.Call incomingCall) async {
  try {
    bool remoteHasVideo = incomingCall!.remote_has_video;
    var mediaStream = await getUserMedia();
    //Moves pending call to active list
    await env.callManager.convertPendingToActive(incomingCall.id!);
    // Puts all other active calls on hold
    env.callManager.setActiveCall(incomingCall.id);
    //env.ua.helper is our SIP.SIPUAHelper object that is connect to our phone system
    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,
  };  
  
  print('Requesting medai access...');
  await requestMediaAccess();
  print('Passed request media access...');
  MediaStream? localStream;
  List<MediaDeviceInfo>? mediaDevicesList;
  final localRenderer = RTCVideoRenderer();
  mediaDevicesList = await navigator.mediaDevices.enumerateDevices();
  await localRenderer.initialize();
  localStream = await Helper.openCamera({
      'video': false,
      'audio': true,
  });
  localRenderer.srcObject = localStream;
  return localStream;
}

requestMediaAccess() async {
  var audioStatus = await Permission.audio.status;
  var microphoneStatus = await Permission.microphone.status;
 

  //microphone and audio permissions is always denied when callkit is active
  if (audioStatus.isDenied) {
    await Permission.audio.request()
  } else if (audioStatus.isPermanentlyDenied) {
    openAppSettings(); 
  }

  if (microphoneStatus.isDenied) {
    await Permission.microphone.request();
  } else if (microphoneStatus.isPermanentlyDenied) {
    openAppSettings(); 
  }
}
Share Improve this question edited Feb 12 at 15:51 Mitchell asked Feb 11 at 14:28 MitchellMitchell 112 bronze badges 1
  • Please provide enough code so others can better understand or reproduce the problem. – Community Bot Commented Feb 12 at 6:24
Add a comment  | 

1 Answer 1

Reset to default 0

I resolved the issue, This didn't have anything to do with flutter. I was simply waiting too long to call the complete() function after receive the voip push info.

//Make sure call completion()
   //DispatchQueue.main.asyncAfter(deadline: .now() + 3) { THis is the cause of my madness
   DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
     print("flutter ns Flutter exiting")
     NSLog("flutter ns Flutter exiting")
     completion()
   }

本文标签: Using callkit stops audio from working on flutter iOSStack Overflow