I am integrating the new CallKit API with my VOIP app.
As shown in the example app: https://developer.apple.com/library/content/samplecode/Speakerbox/Introduction/Intro.html
I am configuring the audio session:
- (void) configureAudioSession
{
// Configure the audio session
AVAudioSession *sessionInstance = [AVAudioSession sharedInstance];
// we are going to play and record so we pick that category
NSError *error = nil;
[sessionInstance setCategory:AVAudioSessionCategoryPlayAndRecord error:&error];
if (error) {
NSLog(#"error setting audio category %#",error);
}
// set the mode to voice chat
[sessionInstance setMode:AVAudioSessionModeVoiceChat error:&error];
if (error) {
NSLog(#"error setting audio mode %#",error);
}
NSLog(#"setupAudioSession");
return;
}
in my CXAnswerCallAction:
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("Provider - CXAnswerCallAction")
// get the active call
guard let call = self.softphone.getCallForCallId(self.currentCallId) else {
action.fail()
return
}
/*
Configure the audio session, but do not start call audio here, since it must be done once
the audio session has been activated by the system after having its priority elevated.
*/
self.softphone.configureAudioSession()
// Trigger the call to be answered via the underlying network service.
call.answer()
// Signal to the system that the action has been successfully performed.
action.fulfill()
}
According to the documentation, didActivate should be called back by the callkit:
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Provider - Received \(#function)")
// Start call audio media, now that the audio session has been activated after having its priority boosted.
}
For some reasons, it's not called back after the first VOIP call. The subsequent calls seem to receive the callback and they work fine.
How to fix this?
I've fixed this problem by setting call audio first then call "reportNewIncomingCall" method. Sample code is given below:
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)? = nil) {
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
DispatchQueue.global().sync {
_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord, with: AVAudioSessionCategoryOptions.mixWithOthers)
_ = try? AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSessionPortOverride.none)
if hasVideo == true {
_ = try? AVAudioSession.sharedInstance().setMode(AVAudioSessionModeVideoChat)
} else {
_ = try? AVAudioSession.sharedInstance().setMode(AVAudioSessionModeVoiceChat)
}
do {
_ = try AVAudioSession.sharedInstance().setActive(true)
} catch (let error){
print("audio session error: \(error)")
}
}
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
}
completion?(error as? NSError)
}
}
Related
I'm working on a voip app now and want to support holding.
But when a second call comes and I hold my current call. Switching to my first call I hear no sound at all.
The way to hear it is to navigate from callKit native screen to my app and hence I can hear the voice.
func configureAudioSession() {
_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: .videoChat, options: AVAudioSession.CategoryOptions.mixWithOthers)
_ = try? AVAudioSession.sharedInstance().overrideOutputAudioPort(AVAudioSession.PortOverride.none)
_ = try? AVAudioSession.sharedInstance().setMode(AVAudioSession.Mode.voiceChat)
}
func startAudio() {
print("Starting audio")
do {
_ = try AVAudioSession.sharedInstance().setActive(true)
} catch {
}
}
func stopAudio() {
print("Stopping audio")
do {
_ = try AVAudioSession.sharedInstance().setActive(false)
} catch {
}
}
For supporting holding, you dont have to start/stop audio session, instead you can use CXSetHeldCallAction provided by Callkit itself. Here, is the code for hold that i use.
let callKitCallController = CXCallController()
func performHoldAction(isOnHold:Bool, uuid:UUID) {
let holdCallAction = CXSetHeldCallAction(call: uuid, onHold: isOnHold)
let transaction = CXTransaction(action: holdCallAction)
callKitCallController.request(transaction) { error in
if let error = error {
CPrint("holdCallAction transaction request failed: \(error.localizedDescription).")
return
}
CPrint("holdCallAction transaction request successful")
}
}
Once system puts the call on hold(by above method OR due to other incoming call accepting or any other reason), then in CXProviderDelegate, the method func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) gives you the callback for the detail.
Here, system/callkit itself interacts with audio, you dont have to explicitly start or stop audio for holding.
Note: Make sure that you given supportsHolding to true for the CXCallUpdate that you gave for new call.
I am trying ScreenRecording implementation using Replaykit. It is currently able to record app screen, but when I am putting it in background for device screen recording, then RPScreenRecorder.startCapture callback is not getting called.
let recorder = RPScreenRecorder.shared()
recorder.delegate = self
recorder.startCapture(handler: { (buffer, bufferType, err) in
self.counter = self.counter.advanced(by: 1)
print(self.counter)
// self.assetWriter.write(buffer: buffer, bufferType: bufferType)
}, completionHandler: {
if let error = $0 {
print(error)
}
})
// stop recording
recorder.stopCapture {
if let err = $0 {
print(err)
}
// self.assetWriter.finishWriting()
}
Do we need to allow any permissions for background access.
I am buidling an app using VOIP service. Now i integrated my app with CallKit in order to handle incoming call request.
When the app stays in foreground or background they are working fine when answered call. But the problem is that when the screen is locked and i tried to answer the call but unfortunately I can't hear audio for both side even i unlocked the screen.
How to solve this issue?
This is how incoming call reports:
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
// 1.
print("This is UUID === ", uuid)
configureAudioSession()
let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .phoneNumber, value: handle)
update.hasVideo = hasVideo
provider.reportNewIncomingCall(with: uuid, update: update) { error in
if error == nil {
// 3.
self.configureAudioSession()
let call = CallKitCallInit(uuid: uuid, handle: handle)
self.callKitManager.add(call: call)
lastCallUUID = uuid
print("UUID === ", uuid)
} else {
}
// 4.
completion?(error as NSError?)
}
}
This is how i set audio
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do{
try session.setCategory(AVAudioSessionCategoryPlayAndRecord)
} catch {
print("========== Error in setting category \(error.localizedDescription)")
}
do {
try session.setMode(AVAudioSessionModeVoiceChat)
} catch {
print("========= Error in setting mode \(error.localizedDescription)")
}
do {
try session.setPreferredSampleRate(44100.0)
} catch {
print("======== Error setting rate \(error.localizedDescription)")
}
do {
try session.setPreferredIOBufferDuration(0.005)
} catch {
print("======== Error IOBufferDuration \(error.localizedDescription)")
}
do {
try session.setActive(true)
} catch {
print("========== Error starting session \(error.localizedDescription)")
}
}
When i answered the call when screen is locked i could see that the error that it throwed in configureAudioSession() function.
Why it not able to set audio when the screen is locked?
I did by adding these lines of code
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
// 1.
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
// 2.
configureAudioSession()
// 3.
call.answer()
// 4.
action.fulfill()
}
This is configureAudioSession
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do{
try session.setCategory(AVAudioSession.Category.playAndRecord,
mode: AVAudioSession.Mode.voiceChat,
options: [])
} catch {
print("========== Error in setting category \(error.localizedDescription)")
}
do {
try session.setPreferredSampleRate(44100.0)
} catch {
print("======== Error setting rate \(error.localizedDescription)")
}
do {
try session.setPreferredIOBufferDuration(0.005)
} catch {
print("======== Error IOBufferDuration \(error.localizedDescription)")
}
do {
try session.setActive(true)
} catch {
print("========== Error starting session \(error.localizedDescription)")
}
}
You can try using AVAudioSessionModeVoiceChat instead of AVAudioSessionModeMoviePlayback mode while configuring audio session.
I have a workout app that plays short clips of sound every couple of seconds. I have background music enabled so that music from other apps can be played while working out. The problem arises when I get a remote push notification (in my case, Slack) that has a sound, which somehow cancels out my duckingOther audio session and the music from other apps becomes loud again.
Question - How do I reset my duckingOthers audiosession when the user gets this type of interruption?
I set the audio session by calling the below function in didFinishLaunchingWithOptions:
private func setupAudioSession(){
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: [.mixWithOthers, .duckOthers, .interruptSpokenAudioAndMixWithOthers])
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
}
I have tried treating this as a hard interruption (for example a phone call), but when trying to apply the techniques used for this type of interruption, it seems that remote push notifications pass through the cracks. Below is what I used from a different question to try and catch interruptions.
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeValue = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSessionInterruptionType(rawValue: typeValue) else {
return
}
if type == .began {
// Interruption began, take appropriate actions
print("interruption started")
}
else if type == .ended {
if let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt {
let options = AVAudioSessionInterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
// Interruption Ended - playback should resume
print("should resume playback")
setupAudioSession()
} else {
// Interruption Ended - playback should NOT resume
print("should not resume playback")
}
}
}
}
func setupNotifications() {
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(handleInterruption),
name: .AVAudioSessionInterruption,
object: nil)
}
I have gone through the iOS CallKit documentation, the below is the code provided by Apple under the section 'Making Outgoing Calls'. When I try and call the function startOutGoingCall() nothing happens i.e. I don't see any outgoing call UI.
Could someone please suggest me how I could trigger a native outgoing call UI.
func startOutGoingCall(){
let uuid = UUID()
let handle = CXHandle(type: .emailAddress, value: "jappleseed#apple.com")
let startCallAction = CXStartCallAction(call: uuid)
startCallAction.destination = handle
let transaction = CXTransaction(action: startCallAction)
callController.request(transaction) { error in
if let error = error {
print("Error requesting transaction: \(error)")
} else {
print("Requested transaction successfully")
}
}
}
EDIT:
Added the delegate method from my code
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
logMessage(messageText: "provider:performStartCallAction:")
/*
* Configure the audio session, but do not start call audio here, since it must be done once
* the audio session has been activated by the system after having its priority elevated.
*/
localMedia?.audioController.configureAudioSession(.videoChatSpeaker)
callKitProvider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
performRoomConnect(uuid: action.callUUID, roomName: action.handle.value) { (success) in
if (success) {
provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date())
action.fulfill()
} else {
action.fail()
}
}
}
There is no outgoing UI you get from CallKit. When you make an outgoing call, your app is open, and therefore, it is your app that should show the UI.