How to detect other VOIP call (iOS)? - ios

I am working on an app that allows video calls using an SDK that utilises webRTC on iOS.
Intended functionality is that if another call is instantiated after my app has instantiated a call, I want my app to mute audio in both directions in my call until that call has ended, in which case audio is restored.
I have encountered a problem where I want to detect other calls that make VOIP calls (for example WeChat and Facebook Messenger).
In the case of WeChat I have solved this exploiting that it interrupts the shared audio session (of AVAudioSession). The code that handles this is as follows:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector
(audioSessionInterrupted:)
...
name:AVAudioSessionInterruptionNotification object:nil];
- (void) audioSessionInterrupted:(NSNotification*)notification
{
if(notification.name == AVAudioSessionInterruptionNotification)
{
NSDictionary* userInfo = notification.userInfo;
int result = userInfo[AVAudioSessionInterruptionTypeKey];
if(result == AVAudioSessionInterruptionTypeBegan)
{
[[AVAudioSession sharedInstance] setActive:NO error:nil];
[self setAudioEnabled:NO];
}
else if (result == AVAudioSessionInterruptionTypeEnded)
{
[[AVAudioSession sharedInstance] setActive:YES error:nil];
[self setAudioEnabled:YES];
}
}
}
However, for Facebook Messenger, this method is never called. I speculate from this that WeChat may be demanding exclusive access to the shared audio session (and thus causing an interruption of the audio session with my app) whereas Facebook Messenger chooses to mix its audio, or uses a separate audio session when a call is instantiated.
My question is does there exist another way of detecting other VOIP calls, possibly using the CallKit framework? My app uses CallKit to prompt user for incoming calls, and records ingoing/outgoing calls in the iOS phone log.

I would recommend checking all current native calls. All calls that are registered through CallKit are also considered native calls and trigger a call on this object, from CoreTelephony:
CTCallCenter *callCenter = [[CTCallCenter alloc] init];
callCenter.callEventHandler = ^(CTCall* call) {
//Native call changes are triggered here
};
For detecting VOIP calls from applications that do not support CallKit, this is harder. A possibility is listening to changes to the AVAudioUnit.

Related

Differentiate active call is cellular call or voip call

We are building voip based application and we have one scenario where we have to identify whether active call is voip call or cellular call(cs call). Before iOS10 and before callkit we used to check through CTCallCenter like below code snap.
- (BOOL)nativeCallPresent
//This only works before callkit and ios 10,
//If iOS is greater or equal 10 then it always return yes for CS and voip call both.
CTCallCenter * callcenter = [[CTCallCenter alloc] init];
BOOL nativeCallPresent = ([callcenter currentCalls] != nil);
return nativeCallPresent;
}
I checked in apple callkit but did not find any way to check that active call is cellular call or voip call.
Can somebody from apple or developer community can help here?
Thanks.

iOS - Can I open my VoIP app on answering call using Callkit?

I'm planning to create an iOS VoIP app(not made any iOS app before). I was reading about Callkit in IOS by which one can make his app receive phone call through iPhone native call screen.
I read Callkit api here where it is mentioned that one can know if a call is answered.
Going through this tutorial and here is the code which detects the call is answered:
-(void)reportIncomingCallWithHandle:(NSString *)handle
success:(void (^)())success
failure:(void (^)(NSError * error))failure {
CXCallUpdate *update = [self newCallUpdateWithHandle:handle];
self.callId = [NSUUID UUID];
[self.provider reportNewIncomingCallWithUUID:self.callId update:update completion:^(NSError * _Nullable error) {
if (error) {
if (failure) failure(error);
} else {
if (success) {
success();
}
}
}];
}
See the success block. So is there is a way to open my app when this success block executed? Or can I override default buttons on caller screen to open my app?
I know there is no way to open an app on receiving any kind of notification, or event trigger. So thought may be there is some way if I can do the same using Callkit
I Googled everything but found no clue regarding my above queries. Please help me if it is possible or not.
I encountered the same issue. The behavior varies depending on if the device is locked or not.
Locked: System calling screen appears. You can run the app in the background including view transitions. However, the user will only see the system calling screen although your app is kind of presented underneath the view. As the device is locked, deep links does not work as well.
Unlocked: Calling screen is the same but once the user answers the call, the app will be presented.
As you may know, we can change the icon of the button on the calling screen which opens the app, and that's the best we can do as of now.
You can not open your own VoIP app or custom UI of your App from CallKit. Use can use it in a way as Whatsapp does.
Means you can awake your app from background without using local notification. And OS will show the default incoming screen. You need not to handle anything during call. CallKit is specially made for enhancing VoIP apps by receiving calls in background, by making outgoing calls, by managing Call directory and blocking of users.

Check network connection in background and upload data

I get a requirement to be able to check if there is data with fail status and upload them via webservice.
The flow is gonna be:
User try to upload data but fail because of no internet connection
User stops using app, so the app goes background
Apps start to identify whether internet connection is available
If available, it directly calls web service to upload data
If not available, it will return to #3
Back to my last development on < iOS 7, previously background time was 10 minutes but becomes 3 minutes.
Background Fetch:
When this method is called, your app has up to 30 seconds of
wall-clock time to perform the download operation and call the
specified completion handler block. In practice, your app should call
the completion handler block as soon as possible after downloading the
needed data. If you do not call the completion handler in time, your
app is terminated.
Also that these are the only apps allowed to do background:
Apps that play audible content to the user while in the background, such as a music player app
Apps that record audio content while in the background.
Apps that keep users informed of their location at all times, such as a navigation app
Apps that support Voice over Internet Protocol (VoIP)
Apps that need to download and process new content regularly (Background Fetch)
Apps that receive regular updates from external accessories
Isn't there any way I can do background in iOS yet or at least I can get the network status in background?
Note: My app is for internal distribution only, no need to worry with rejection.
What you need to do is using NSURLSession instead of NSURLConnection (in case you are still using it)
Difference between NSURLSession and NSURLConnection
NSURLConnection: if we have an open connection with NSURLConnection and the system interrupt our App, when our App goes to background mode, everything we have received or sent were lost.
NSURLSession: solve this problem and also give us out of process downloads. It manage the connection process even when we don't have access. You will need to use application:handleEventsForBackgroundURLSession:completionHandler in your AppDelegate
So with the use of NSURLSession, you don't need to manage or to check
your internet connection because OS does it for you.
Extra: How to check internet connection in my iOS app
Disclaimer: I'm not sure that the following Framework can run in background mode
You can use framework Rechability to check if your device have internet connection.
https://github.com/tonymillion/Reachability
It is a drop-in replacement for Apple's Reachability class and it supports blocks to perform action when you receive the NSNotification of reachability changed.
You can check the README.md at his repo but it is very simple to use.
Using NSNotification
1
In your AppDelegate you have to call
[Reachability reachabilityWithHostname:#"www.google.com"];
Also, you will need to declare a property to store that value
#property (strong, nonatomic) Reachability *reach;
so now you will have to put the following code inside your application:didFinishLaunchingWithOptions:
_reach = [Reachability reachabilityWithHostname:#"www.google.com"];
2
In the Controller you need to check if reachability state, you must subscribe to that notification
[[NSNotificationCenter defaultCenter] addObserver:_sharedInstance selector:#selector(reachabilityDidChange:) name:#"kReachabilityChanged" object:nil];
3
Now you can implement the selector reachabilityDidChange: where you check if now are connected or not. Put it in the same Controller.
- (void)reachabilityDidChange:(NSNotification *)note
{
Reachability * reach = (Reachability *)[note object];
if (self.appDelegate.wasConnected != [reach isReachable]){
if ([reach isReachable]){
NSLog(#"Notification Says Reachable");
[[NSNotificationCenter defaultCenter] postNotificationName:#"ConnectionIsReachable" object:nil];
[self sendPendingOperations];
}else{
[[NSNotificationCenter defaultCenter] postNotificationName:#"ConnectionIsUnreachable" object:nil];
NSLog(#"Notification Says Unreachable");
}
self.appDelegate.wasConnected = [reach isReachable];
}
}
As you can see, we store the last connection state also in a AppDelegate property
`#property BOOL wasConnected;`
4
Now you have to subscribe to those new notifications (ConnectionIsReachable, ConnectionIsUnreachable) and perform the action you need to do.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(showNoConnectionAlert) name:#"ConnectionIsUnreachable" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(hideNoConnectionAlert) name:#"ConnectionIsReachable" object:nil];
Using blocks
I have not test it that feature but I copy to you the official documentation
// Allocate a reachability object
Reachability* reach = [Reachability reachabilityWithHostname:#"www.google.com"];
// Set the blocks
reach.reachableBlock = ^(Reachability*reach)
{
// keep in mind this is called on a background thread
// and if you are updating the UI it needs to happen
// on the main thread, like this:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"REACHABLE!");
});
};
reach.unreachableBlock = ^(Reachability*reach)
{
NSLog(#"UNREACHABLE!");
};
// Start the notifier, which will cause the reachability object to retain itself!
[reach startNotifier];

Detectiing a incoming Facetime call in ios

Is there anyway to detect an incoming Facetime call in ios. I tried CTCallCenter but it seems like it only works with cellular calls and not facetime calls.. I am using following code to detect facetime call but no success.
self.callCenter = [[CTCallCenter alloc] init];
self.callCenter.callEventHandler = ^(CTCall* myCall) {
NSString *call = myCall.callState;
if ([call isEqualToString:CTCallStateDisconnected])
NSLog(#"Call has been disconnected");
else if([call isEqualToString:CTCallStateDialing])
NSLog(#"Call start");
else if ([call isEqualToString:CTCallStateConnected])
NSLog(#"Call has just been connected");
else if([call isEqualToString:CTCallStateIncoming])
NSLog(#"Call is incoming");
else
NSLog(#"None");
};
Any help?
You don't need an app-specific detection for every app that needs pausing your playback. You should use AVAudioSession to detect any kind of audio interruption and notify your music player to pause.
See this image and explanation from Apple's documentation:
AVAudioSession gives you control your app’s audio behavior. You can:
Select the appropriate input and output routes for your app
Determine how your app integrates audio from other apps
Handle interruptions from other apps
Automatically configure audio for the type of app your are creating
So, you can use the AVAudioSession API to handle any incoming calls that can be a cellular call or a FaceTime one, or even anything third-party like Viber, Tango, Line, etc.
You can also check the AddMusic sample app to see how it's implemented.

Running iOS app in the background forever

I have a requirement where my app connects to a country channel (USA) and starts playing records from the channel. This is basically a channel which is run by users, the users upload their records to channel and they are played one by one. The user who connects to channel they start listening to channel.
The server sends the iOS app the URLs for the record that needs to be played via sockets, the iOS app creates AVQueuePlayer to play the URL's (using AVPlayerItems) one by one.
If I keep app in background when the channel is full of records for almost 1 day or so, the app keep running and keep playing all the records one by one. I know that AVQueuePlayer takes care of running the app all the time without killing as it receives new player items to play.
But if there are no records in channel and if user connects to channel, then app doesn't play the records in background if the idle time of the app exceeds 10 minutes.
I have written code with background task identifier which keeps my socket connect open so that new record URLs can be received all the time.
I see some of the crash reports in my device which says "AppName(my app) has active assertions beyond permitted time"
So can I know what wrong is going on here.
I am posting the background task code as well
- (void)keepBroadcastPersistentConnection {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if(self._bgTaskIdentifier)
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
self._bgTaskIdentifier = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^{
[[UIApplication sharedApplication] endBackgroundTask:self._bgTaskIdentifier];
self._bgTaskIdentifier = UIBackgroundTaskInvalid;
CGLog(#"========================================end bg task at time %#", [NSDate date]);
CGLog(#"Time taken by app to run in bg is %f seconds", [[NSDate date] timeIntervalSinceDate:self.date]);
}];
[[BroadcastSocketConnecter sharedSocketConnecter].socketIO sendHeartbeat]; // this keep the socket alive
self.date = [NSDate date];
CGLog(#"========================================begin bg task at time %#", self.date);
});
}
Thanks
From the audio session programming guide:
Why a Default Audio Session Usually Isn’t What You Want
Scenario 3. You write a streaming radio application that uses Audio
Queue Services for playback. While a user is listening, a phone call
arrives and stops your sound, as expected. The user chooses to ignore
the call and dismisses the alert. The user taps Play again to resume
the music stream, but nothing happens. To resume playback, the user
must quit your application and restart it.
To handle the interruption of an audio queue gracefully, implement
delegate methods or write an audio session callback function to allow
your application to continue playing automatically or to allow the
user to manually resume playing. See “Responding to Audio Session
Interruptions.”
Shortly, the solution would be to implement the AVAudioSessionDelegate protocol'a beginInterruption and endInterruption methods. However, the delegate property of the AvAudioSession class was deprecated in iOS6 and Notifications should be used instead. Namely, you are interested in the AVAudioSessionInterruptionNotification
Solution. According to this story if the playback stops, then you should explicitly activate the audio session again to prevent your app from being terminated.
Below is the source for the delegate implementation but the logic doesn't change much with the notifications so I feel it's still a good source for info.
- (void) beginInterruption {
if (playing) {
playing = NO;
interruptedWhilePlaying = YES;
[self updateUserInterface];
}
}
NSError *activationError = nil;
- (void) endInterruption {
if (interruptedWhilePlaying) {
BOOL success = [[AVAudioSession sharedInstance] setActive: YES error: &activationError];
if (!success) { /* handle the error in activationError */ }
[player play];
playing = YES;
interruptedWhilePlaying = NO;
[self updateUserInterface];
}
}
Old response which is still valid but not an elegant solution
You cannot start playing audio in the background. This answer explains what I mentioned in my comment above: https://stackoverflow.com/a/16568437/768935 Doing tricks with the AudioSession does not seem to have an effect on this policy.
As a solution, you need to keep playing audio. If there is no item in the queue, then insert the "silence" audio track. However, I have my doubts that the app with this trick will be admitted in the App Store. It may be better to inform the user that audio playback will be resumed on if the app is started again.

Resources