Open robsonvasquez opened 2 weeks ago
same here, @robsonvasquez did you find solution for this?
I have the same problem 👋
Hi! I solved my issue by switching to the fixed WebRTC version 7.0.1, as with other versions, the audio would either play only on the recipient’s end or only on the caller’s end, depending on the WebRTC version.
Here’s an example of how the delegate turned out:
import UIKit import Flutter import CallKit import PushKit import WebRTC import AVFAudio import shared_preferences_foundation import awesome_notifications
@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CXProviderDelegate {
let callController = CXCallController()
var provider: CXProvider?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self)
SwiftAwesomeNotificationsPlugin.setPluginRegistrantCallback { registry in
SwiftAwesomeNotificationsPlugin.register(
with: registry.registrar(forPlugin: "io.flutter.plugins.awesomenotifications.AwesomeNotificationsPlugin")!)
SharedPreferencesPlugin.register(
with: registry.registrar(forPlugin: "io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin")!)
}
// Configuração do CallKit
let configuration = CXProviderConfiguration(localizedName: "")
configuration.iconTemplateImageData = UIImage(named: "icon")?.pngData()
//configuration.ringtoneSound = "ring.aiff" // Som de toque personalizado, opcional
configuration.maximumCallGroups = 1
configuration.maximumCallsPerCallGroup = 1
// Configuração do PushKit para VoIP
let mainQueue = DispatchQueue.main
let voipRegistry: PKPushRegistry = PKPushRegistry(queue: mainQueue)
voipRegistry.delegate = self
voipRegistry.desiredPushTypes = [.voIP]
provider = CXProvider(configuration: configuration)
provider?.setDelegate(self, queue: mainQueue)
RTCAudioSession.sharedInstance().useManualAudio = true
RTCAudioSession.sharedInstance().isAudioEnabled = false
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
// Delegate para o PKPushRegistry - Recebimento de push VoIP
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
print("didReceiveIncomingPushWith t")
print(payload.dictionaryPayload)
print(type)
print("AQUI IOS ")
print(payload.dictionaryPayload["uuid"])
guard type == .voIP else { return }
// let callerName = payload.dictionaryPayload["callerName"] as? String ?? "Unknown Caller"
// let uuidString = payload.dictionaryPayload["uuid"] as? String ?? UUID().uuidString
// let uuid = UUID(uuidString: uuidString) ?? UUID()
let uuidString = payload.dictionaryPayload["uuid"] as? String ?? ""
let uuid = UUID(uuidString: uuidString)
let nameCaller = payload.dictionaryPayload["callerName"] as? String ?? ""
let id = payload.dictionaryPayload["id"] as? String ?? ""
let domain = payload.dictionaryPayload["domain"] as? String ?? ""
let iceTimeout = payload.dictionaryPayload["iceTimeout"] as? String ?? ""
let ring_time = payload.dictionaryPayload["ring_time"] as? String ?? ""
let stun = payload.dictionaryPayload["stun"] as? String ?? ""
let stunURL = payload.dictionaryPayload["stunURL"] as? String ?? ""
let tcpPort = payload.dictionaryPayload["tcpPort"] as? String ?? ""
let turn = payload.dictionaryPayload["turn"] as? String ?? ""
let turnCredential = payload.dictionaryPayload["turnCredential"] as? String ?? ""
let turnURL = payload.dictionaryPayload["turnURL"] as? String ?? ""
let turnUsername = payload.dictionaryPayload["turnUsername"] as? String ?? ""
let wsdomain = payload.dictionaryPayload["wsdomain"] as? String ?? ""
let sip_username = payload.dictionaryPayload["sip_username"] as? String ?? ""
let sip_password = payload.dictionaryPayload["sip_password"] as? String ?? ""
let zona = payload.dictionaryPayload["zona"] as? String ?? ""
let zone_id = payload.dictionaryPayload["zone_id"] as? String ?? ""
let conta_id = payload.dictionaryPayload["account"] as? String ?? ""
let conta = payload.dictionaryPayload["conta"] as? String ?? ""
// Cria um dicionário com todos os valores
let arguments: [String: Any] = [
"domain": domain,
"iceTimeout": iceTimeout,
"ring_time": ring_time,
"stun": stun,
"stunURL": stunURL,
"tcpPort": tcpPort,
"turn": turn,
"turnCredential": turnCredential,
"turnURL": turnURL,
"turnUsername": turnUsername,
"wsdomain": wsdomain,
"sip_username": sip_username,
"sip_password": sip_password,
"zona": zona,
"zone_id": zone_id,
"conta_id": conta_id,
"conta": conta,
"id": id,
"uuid": uuidString
]
// let audioSession = AVAudioSession.sharedInstance()
// do{
// try audioSession.setActive(true)
// print("deu")
// }catch{
// print("eroo")
// }
// Chamar método Flutter aqui
callFlutterMethod(method: "startSip", arguments: arguments)
reportIncomingCall(uuid: uuid!, handle: nameCaller, hasVideo: false)
}
// Função para relatar uma chamada VoIP para o CallKit
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: handle)
update.hasVideo = hasVideo
provider?.reportNewIncomingCall(with: uuid, update: update) { error in
if let error = error {
print("Erro ao reportar chamada: \(error.localizedDescription)")
} else {
print("Chamada reportada com sucesso.")
}
}
}
func providerDidReset(_ provider: CXProvider) {
print("Provider did reset")
// Você pode realizar ações de limpeza aqui, se necessário
}
// Delegate do CallKit - Ações de chamadas
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// Chamar método Flutter aqui
callFlutterMethod(method: "onAccept", arguments: [:])
//activateAudioSession()
// Ação ao atender a chamada
action.fulfill()
//startAudioSession()
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
// Notifica o WebRTC que a sessão de áudio foi desativada
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
// Desativa o áudio do WebRTC
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
func configureAudioSession(){
let RTCaudioSession = AVAudioSession.sharedInstance()
do{
try RTCaudioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.defaultToSpeaker])
try RTCaudioSession.setActive(true)
print("aqui deu")
}catch{
print("aqui não deu \(error), \(error.localizedDescription)")
}
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
//do {
configureAudioSession()
// Notifica o WebRTC que a sessão de áudio foi ativada
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
// Habilita o áudio do WebRTC
RTCAudioSession.sharedInstance().isAudioEnabled = true
//}catch{
// print("erro ao ativar audio")
//}
}
// Função para ativar/desativar alto-falante
func setSpeakerEnabled(_ enabled: Bool) {
do {
if enabled {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
print("Alto-falante ativado")
} else {
try AVAudioSession.sharedInstance().overrideOutputAudioPort(.none)
print("Alto-falante desativado")
}
} catch {
print("Erro ao configurar alto-falante: \(error.localizedDescription)")
}
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
callFlutterMethod(method: "onEnd", arguments: [:])
// Ação ao encerrar a chamada
action.fulfill()
}
// Delegate para atualização de credenciais de push VoIP
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
let deviceToken = credentials.token.map { String(format: "%02x", $0) }.joined()
print("Token do dispositivo VoIP: \(deviceToken)")
// Enviar token para o servidor, se necessário
callFlutterMethod(method: "tokenVoip", arguments: ["token":deviceToken])
}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
print("Token de push VoIP invalidado")
}
// Função para chamar o método Flutter
func callFlutterMethod(method: String, arguments: [String: Any]) {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
let channel = FlutterMethodChannel(name: "flutter_callkit_channel_ios", binaryMessenger: controller.binaryMessenger)
channel.invokeMethod(method, arguments: arguments) { result in
print("Result from Flutter: \(String(describing: result))")
}
channel.setMethodCallHandler { (call: FlutterMethodCall, result: @escaping FlutterResult) in
// Identifique o método chamado do Flutter
switch call.method {
case "onEnd":
// Aqui você pode processar os argumentos e retornar o resultado
if let arguments = call.arguments as? [String: Any],
let callId = arguments["id"] as? String,
let uuid = UUID(uuidString: callId) {
// Exemplo de processamento dos argumentos
let endCallAction = CXEndCallAction(call: uuid)
// Criar a transação com a ação de encerrar chamada
let transaction = CXTransaction(action: endCallAction)
// Solicitar a transação para encerrar a chamada
self.callController.request(transaction) { error in
if let error = error {
print("Erro ao encerrar a chamada: \(error.localizedDescription)")
} else {
print("Chamada encerrada com sucesso.")
}
}
print("Received arguments: \(arguments)")
result("Success from iOS") // Retornar um valor para o Flutter
self.callFlutterMethod(method: "onEnd", arguments: [:])
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Invalid arguments", details: nil))
}
case "onMute":
// Aqui você pode processar os argumentos e retornar o resultado
if let arguments = call.arguments as? [String: Any],
let callId = arguments["id"] as? String,
let callAction = arguments["action"] as? Bool,
let uuid = UUID(uuidString: callId) {
// Exemplo de processamento dos argumentos
let muteCAllAction = CXSetMutedCallAction(call: uuid, muted: callAction)
// Criar a transação com a ação de encerrar chamada
let transaction = CXTransaction(action: muteCAllAction)
// Solicitar a transação para encerrar a chamada
self.callController.request(transaction) { error in
if let error = error {
print("Erro ao mutar a chamada: \(error.localizedDescription)")
} else {
print("Chamada mutar com sucesso.")
}
}
print("Received arguments: \(arguments)")
result("Success from iOS") // Retornar um valor para o Flutter
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Invalid arguments", details: nil))
}
case "toggleSpeaker":
if let args = call.arguments as? [String: Any],
let enableSpeaker = args["enable"] as? Bool {
self.setSpeakerEnabled(enableSpeaker)
result("Speaker toggled to \(enableSpeaker)")
} else {
result(FlutterError(code: "INVALID_ARGUMENT", message: "Expected boolean argument", details: nil))
}
case "configAudio":
self.configureAudioSession()
default:
result(FlutterMethodNotImplemented)
}
}
}
}
Unfortunately, I'm having an audio issue on iOS when making a call from the app to someone. The audio doesn’t work on iOS, and the device’s microphone doesn’t open. This call is made with the app open through Flutter. If anyone manages to resolve this, please let me know.
I’m using WebRTC and flutter_callkit_incoming. When I make a call and answer through iOS's native interface, the audio doesn’t play on the iPhone, but it does for the recipient. It was working perfectly on iOS before 10/20/2024 with the latest versions of both libraries, but then the audio just stopped working on iOS when using CallKit to answer. Switching WebRTC versions sometimes makes the audio work on iOS but not for the recipient, and other versions have the opposite effect. Is anyone else experiencing audio issues with both libraries when answering through iOS's native interface?
Delegate
import UIKit import Flutter
import awesome_notifications //import shared_preferences_ios import shared_preferences_foundation
import CallKit import AVFAudio import PushKit import flutter_callkit_incoming import WebRTC
@UIApplicationMain @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate, CallkitIncomingAppDelegate {
override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool {
}
// Call back from Recent history override func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
}
}
}