CallKit Interferes With WebRTC Video - ios

When I use CallKit to answer a WebRTC call, the video chat works most of the time. Once in a while the camera on my local iPhone isn't accessed correctly because of CallKit. When I remove CallKit, the video chat always works. Also if I set a delay for 1.5 second after I answer a CallKit call and then start the video chat, it seems to work well all of the time. What's the reason for this?

Callkit is intended for Audio Call only, so callkit will activate AudioSession only.
Video will be activated only after you navigate to your application.
Try answering the call on phone lock screen, then you will get understood.
Test Facebook or any other popular app.

Try implementing your (re)connection logic in providerDidReset(_ provider: CXProvider). You may also want to provide checks in this method to ensure you aren't resetting a successful connection.

I am using WebRTC + CallKit in my App.
I started a call and when I press Lock / Power button then the CallKit call is getting disconnected and the My Voip call audio route changes to Receiver and remains.
Why locking the iPhone terminating the call.
Here is my Code.
var callUUID: UUID?
extension AppDelegate {
func initiateCallKitCall() {
let config = CXProviderConfiguration(localizedName: "Huddl.ai")
config.includesCallsInRecents = false;
config.supportsVideo = true;
config.maximumCallsPerCallGroup = 1
provider = CXProvider(configuration: config)
guard let provider = provider else { return }
provider.setDelegate(self, queue: nil)
callController = CXCallController()
guard let callController = callController else { return }
callUUID = UUID()
let transaction = CXTransaction(action: CXStartCallAction(call: callUUID!, handle: CXHandle(type: .generic, value: "Huddl.ai")))
callController.request(transaction, completion: { error in
print("Error is : \(String(describing: error))")
})
}
func endCallKitCall(userEnded: Bool) {
self.userEnded = userEnded
guard provider != nil else { return }
guard let callController = callController else { return }
if let uuid = callUUID {
let endCallAction = CXEndCallAction(call: uuid)
callController.request(
CXTransaction(action: endCallAction),
completion: { error in
if let error = error {
print("Error: \(error)")
} else {
print("Success")
}
})
}
}
func isCallGoing() -> Bool {
let callController = CXCallController()
if callController.callObserver.calls.count != 0 {
return true
}
return false
}
}
extension AppDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("-Provider-providerDidReset")
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("-Provider-perform action: CXAnswerCallAction")
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
action.fulfill()
print("-Provider: End Call")
}
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
action.fulfill()
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 3) {
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: Date())
DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now() + 1.5) {
provider.reportOutgoingCall(with: action.callUUID, connectedAt: Date())
}
}
}
func provider(_ provider: CXProvider, perform action: CXSetHeldCallAction) {
action.fulfill()
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = true
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
RTCAudioSession.sharedInstance().audioSessionDidDeactivate(audioSession)
RTCAudioSession.sharedInstance().isAudioEnabled = false
}
}

Related

How do I decline observed call with CallKit?

I'm completely new to Swift programming and I don't know how to make this work.
Basically I have and iOS app which observes incoming phone calls and declines it automatically if the caller number is equal to a user defined number to decline, without showing user any notification (this app is only for my private use).
Currently I have this code which observes incoming call. It works like a charm but I don't know how to make it decline the call (for now all phone numbers).
import CallKit
class CallTest: NSObject, CXCallObserverDelegate, ObservableObject {
#Published var incomingCallNumber: String?
let callObserver = CXCallObserver()
override init() {
super.init()
callObserver.setDelegate(self, queue: nil)
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if (call.isOutgoing == false && call.hasConnected == false) {
print("Incoming call")
}
}
}
I've tried to decline incoming call like this.
import CallKit
class CallTest: NSObject, CXCallObserverDelegate, ObservableObject {
#Published var incomingCallNumber: String?
let callObserver = CXCallObserver()
override init() {
super.init()
callObserver.setDelegate(self, queue: nil)
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if (call.isOutgoing == false && call.hasConnected == false) {
print("Incoming call")
let callController = CXCallController()
let endCallAction = CXEndCallAction(call: call.uuid)
let transaction = CXTransaction(action: endCallAction)
callController.request(transaction) { error in
if let error = error {
print("Error declining call: \(error)")
} else {
print("Call declined successfully")
}
}
}
}
}
But I keep getting error:
Error Domain=com.apple.CallKit.error.requesttransaction Code=2 "(null)"
Which means 'unknownCallProvider', but I don't know how to set provider for this.

AVFoundation- .audioDeviceInUseByAnotherClient runs after CXCallObserver call.hasEnded (call disconnection) is called

I'm using AVFoundation for video recording. I also use the CXCallObserverDelegate to listen to when a phone call is disconnected.
I go to the background
I make a phone call
While the phone call is active I bring the app back to the foreground and press the button to modally present the vc that contains AVFoundation
Once the vc is on scene and because I'm currently on a phone call the .audioDeviceInUseByAnotherClient gets called and I stop the capture session
Once the phone call is disconnected then CXCallObserver call.hasEnded is called and I restart the capture session. The .sessionInterruptionEnded also gets called but this isn't causing the issue.
This is where the problem occurs. Once call.hasEnded is called then .audioDeviceInUseByAnotherClient gets called again. Since the code to stop the capture session is in there this results in capture session stopping again
In step 6 why does .audioDeviceInUseByAnotherClient get called again after the call has been disconnected?
func sessionWasInterrupted(notification: NSNotification) {
let reasonIntegerValue = userInfoValue.integerValue,
let reason = AVCaptureSession.InterruptionReason(rawValue: reasonIntegerValue) {
case .audioDeviceInUseByAnotherClient:
stopCaptureSession()
}
}
}
func sessionInterruptionEnded(notification: NSNotification) {
print("-----Capture session interruption ended")
restartCaptureSession()
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasEnded == true {
print("\nCXCallState :Disconnected")
restartCaptureSession()
}
if call.hasConnected == true && call.hasEnded == false {
print("\nCXCallState : Connected")
// *** THIS NEVER GETS CALLED IN THIS SCENARIO ***
}
}
fileprivate func stopCaptureSession() {
if captureSession.isRunning {
DispatchQueue.global(qos: .background).async { [weak self] in
DispatchQueue.main.sync {
self?.captureSession.stopRunning()
}
DispatchQueue.main.async {
self?.previewLayer?.removeFromSuperlayer()
self?.previewLayer = nil
}
}
}
}
func restartCaptureSession() {
if !captureSession.isRunning {
DispatchQueue.global(qos: .background).async { [weak self] in
DispatchQueue.main.sync {
self?.captureSession.startRunning()
}
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
if let safeSelf = self {
if safeSelf.previewLayer == nil {
self?.previewLayer = AVCaptureVideoPreviewLayer(session: self!.captureSession)
self?.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
guard let previewLayer = self?.previewLayer else { return }
previewLayer.frame = self!.containerViewForPreviewLayer.bounds
self?.containerViewForPreviewLayer.layer.insertSublayer(previewLayer, at: 0)
}
}
}
}
}
}

No Voice Communication when call is streaming using CallKit

I was trying to make a call from extension 100(Linphone App) to 102(My application). The extension 102 has shown the incoming call screen and then clicked accept button and then, extension 100 starts to talk to extension 102 but extension 102 unable to hear anything from 100 and also extension 100 unable to hear from 102 as well.
I think the problem because of app. But i don't know what is wrong with my application.
These functions i declared in appdelegate file in order to handle incoming call
let callKitManager = CallKitCallInit(uuid: UUID(), handle: "")
lazy var providerDelegate: ProviderDelegate = ProviderDelegate(callKitManager: self.callKitManager)
func displayIncomingCall(uuid: UUID, handle: String, completion: ((NSError?) -> Void)?) {
providerDelegate.reportIncomingCall(uuid: uuid, handle: handle, completion: completion)
}
This is ProviderDelegate file
extension ProviderDelegate: CXProviderDelegate {
func providerDidReset(_ provider: CXProvider) {
print("Stop Audio ==STOP-AUDIO==")
for call in callKitManager.calls {
call.end(uuid: UUID())
}
callKitManager.removeAllCalls()
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
configureAudioSession()
call.answer()
action.fulfill()
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
guard let call = callKitManager.callWithUUID(uuid: action.callUUID) else {
action.fail()
return
}
print("Stop audio ==STOP-AUDIO==")
configureAudioSession()
call.end(uuid: action.callUUID)
action.fulfill()
callKitManager.remove(call: call)
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("Starting audio ==STARTING-AUDIO==")
}
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
print("Received \(#function)")
}
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try? session.setCategory(AVAudioSessionCategoryPlayAndRecord)
try? session.setMode(AVAudioSessionModeVoiceChat)
try? session.setPreferredSampleRate(44100.0)
try? session.setPreferredIOBufferDuration(0.005)
try? session.setActive(true)
}
}
}
class ProviderDelegate: NSObject {
fileprivate let callKitManager: CallKitCallInit
fileprivate let provider: CXProvider
init(callKitManager: CallKitCallInit) {
self.callKitManager = callKitManager
provider = CXProvider(configuration: type(of: self).providerConfiguration)
super.init()
provider.setDelegate(self, queue: nil)
}
static var providerConfiguration: CXProviderConfiguration {
let providerConfiguration = CXProviderConfiguration(localizedName: "vKclub")
providerConfiguration.supportsVideo = false
providerConfiguration.maximumCallsPerCallGroup = 1
providerConfiguration.supportedHandleTypes = [.phoneNumber]
return providerConfiguration
}
func reportIncomingCall(uuid: UUID, handle: String, hasVideo: Bool = false, completion: ((NSError?) -> Void)?) {
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?)
}
}
}

I can't play music with Spotify iOS SDK with Swift 4.0

I have a struggle with Spotify SDK, I followed every step correctly, but I can't play music with my premium account on my project. There is no error or crash, my app directs me to the Spotify login page and after facebook login, It brings me back to my app again. Yesterday I'd get "logged in" print but today I can't. I'm trying to play a song after login and also manually as you see below. I'm wondering, am I lucky to find an answer?
override func viewDidLoad() {
super.viewDidLoad()
self.spotify()
NotificationCenter.default.addObserver(self, selector: #selector(GeneralNewsViewController.updateAfterFirstLogin), name: NSNotification.Name(rawValue: "SpotifySession"), object: nil)
}
func spotify() {
// insert redirect your url and client ID below
auth.redirectURL = URL(string: "correctCallbackURl")
auth.clientID = "correctClientID"
auth.requestedScopes = [SPTAuthStreamingScope, SPTAuthPlaylistReadPrivateScope, SPTAuthPlaylistModifyPublicScope, SPTAuthPlaylistModifyPrivateScope]
loginUrl = auth.spotifyWebAuthenticationURL()
}
func initializaPlayer(authSession:SPTSession){
if self.player == nil {
self.player = SPTAudioStreamingController.sharedInstance()
self.player!.playbackDelegate = self
self.player!.delegate = self
try! player?.start(withClientId: auth.clientID)
self.player!.login(withAccessToken: authSession.accessToken)
}
}
#objc func updateAfterFirstLogin () {
loginButton.isHidden = true
let userDefaults = UserDefaults.standard
if let sessionObj:AnyObject = userDefaults.object(forKey: "SpotifySession") as AnyObject? {
let sessionDataObj = sessionObj as! Data
let firstTimeSession = NSKeyedUnarchiver.unarchiveObject(with: sessionDataObj) as! SPTSession
self.session = firstTimeSession
initializaPlayer(authSession: session)
self.loginButton.isHidden = true
// self.loadingLabel.isHidden = false
}
}
func audioStreamingDidLogin(_ audioStreaming: SPTAudioStreamingController!) {
// after a user authenticates a session, the SPTAudioStreamingController is then initialized and this method called
print("logged in")
self.player?.playSpotifyURI("spotify:track:4aDLPXlzHZm26GppvRwms8", startingWith: 0, startingWithPosition: 0, callback: { (error) in
if (error != nil) {
print("playing!")
} else {
print(error?.localizedDescription)
}
})
}
#objc func play() {
player?.playSpotifyURI("spotify:track:4aDLPXlzHZm26GppvRwms8", startingWith: 0, startingWithPosition: 0, callback: { (err) in
if err != nil {
print(err?.localizedDescription)
} else {
}
})
}
class AppDelegate: UIResponder, UIApplicationDelegate ,UNUserNotificationCenterDelegate{
auth.redirectURL = URL(string: "correctCallbackURl")
auth.sessionUserDefaultsKey = "current session"}
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
// 2- check if app can handle redirect URL
if auth.canHandle(auth.redirectURL) {
// 3 - handle callback in closure
auth.handleAuthCallback(withTriggeredAuthURL: url, callback: { (error, session) in
// 4- handle error
if error != nil {
print("error!")
}
// 5- Add session to User Defaults
let userDefaults = UserDefaults.standard
let sessionData = NSKeyedArchiver.archivedData(withRootObject: session!)
userDefaults.set(sessionData, forKey: "SpotifySession")
userDefaults.synchronize()
// 6 - Tell notification center login is successful
NotificationCenter.default.post(name: Notification.Name(rawValue: "loginSuccessfull"), object: nil)
})
return true
}
return false
}}
It looks like you're subscribed to the wrong notification, you're subscribing to "SpotifySession" but posting "loginSuccessfull"
this answer will help you making this kind of mistake in the future by having enums for events in a simple way.
good debugging is also key, you could've probably solved this problem with some breakpoints to see where you went wrong.
cheers

How to get notifications for Network change(Connect,Disconnect,Change)

In my app I have to alert whenever a network change happens for example when a app is connected or disconnected or changed(wifi to data) network.
But finding it by reaching a url seems expensive.
Is there any public api available in iOS to do this.
I followed this tutorial
https://www.youtube.com/watch?v=wDZmz9IsB-8
credit --> Vasil Nunev
Download this class and import to my project --> Reachability.swift
https://github.com/ashleymills/Reachability.swift
(Reachability.swift) --> Unzip -->Sources/Reachability.swift
This is my view controller
import UIKit
class ViewController: UIViewController {
let reachability = Reachability()!
override func viewDidLoad() {
super.viewDidLoad()
reachability.whenReachable = { _ in
DispatchQueue.main.async {
self.view.backgroundColor = UIColor.green
}
}
reachability.whenUnreachable = { _ in
DispatchQueue.main.async {
self.view.backgroundColor = UIColor.red
}
}
NotificationCenter.default.addObserver(self, selector: #selector(internetChanged), name: Notification.Name.reachabilityChanged , object: reachability)
do{
try reachability.startNotifier()
} catch {
print("Could not strat notifier")
}
}
#objc func internetChanged(note:Notification) {
let reachability = note.object as! Reachability
if reachability.isReachable{
if reachability.isReachableViaWiFi{
self.view.backgroundColor = UIColor.green
}
else{
DispatchQueue.main.async {
self.view.backgroundColor = UIColor.orange
}
}
} else{
DispatchQueue.main.async {
self.view.backgroundColor = UIColor.red
}
}
}
}
Look at this official Apple documentation
Using Reachability you can recognize the type of your connection.
There is also a complete example on how detect the changes and the documentation is updated now for ARC.
I hope this can help you
Possible help:
Reachability
Event notification for network activity
If you're using Alamofire in your application, you can use it's NetworkReachabilityManager.swift class to listen for network's connectivity.
This is the piece of code.
#discardableResult
open func startListening(onQueue queue: DispatchQueue = .main,
onUpdatePerforming listener: #escaping Listener) -> Bool {
stopListening()
$mutableState.write { state in
state.listenerQueue = queue
state.listener = listener
}
var context = SCNetworkReachabilityContext(version: 0,
info: Unmanaged.passUnretained(self).toOpaque(),
retain: nil,
release: nil,
copyDescription: nil)
let callback: SCNetworkReachabilityCallBack = { _, flags, info in
guard let info = info else { return }
let instance = Unmanaged<NetworkReachabilityManager>.fromOpaque(info).takeUnretainedValue()
instance.notifyListener(flags)
}
let queueAdded = SCNetworkReachabilitySetDispatchQueue(reachability, reachabilityQueue)
let callbackAdded = SCNetworkReachabilitySetCallback(reachability, callback, &context)
// Manually call listener to give initial state, since the framework may not.
if let currentFlags = flags {
reachabilityQueue.async {
self.notifyListener(currentFlags)
}
}
return callbackAdded && queueAdded
}
/// Stops listening for changes in network reachability status.
open func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
$mutableState.write { state in
state.listener = nil
state.listenerQueue = nil
state.previousStatus = nil
}
}
You can also use Apple's SCNetworkReachbility class instead.

Resources