CallKit:No sound when I use WebRTC - ios

Our project uses WebRTC for VOIP calls and it works fine before accessing the CallKit framework. But when I tried to access the CallKit framework, there was a situation where neither side could hear each other's speech. When I removed CallKit, everything returned to normal.
CallKit's answer button is the same function as the original answer button in the project.
And what amazed me was that it was not necessary to hear no sound. Sometimes everything is normal, but sometimes there will be problems. Well, the probability of a problem is greater.
I found the following flowchart, I suspect the problem lies in the order of function calls. But I do not know how WebRTC corresponds to the functions in the diagram.
In addition, I am curious whether socket instability will cause the CallKit framework to work abnormally
Please forgive me English is not good, but this problem has been haunted me for several days, I do not know where exactly a problem, is not where the conflict with the CallKit framework?
Hope you can help me, thank you very much!

Few steps need to be done to connect webrtc and callkit in proper way:
First of all, you have to use the RTCAudioTrack and add the RTCAudioSession for handling the audio. Old legacy RTCAudioSession added directly into RTCPeerConnection works but it's not prefered a way to do that.
Second thing is to use manualAudio. When app is booted you should change useManualAudio flag on RTCAudioSession:
RTCAudioSession.sharedSession().useManualAudio = true
which gives you possibility to postpone the audio until CallKit informs that audio session was activated, so inside the ProviderDelegate you should implement following method:
(void)provider:(CXProvider *)provider didActivateAudioSession:(AVAudioSession *)audioSession
RTCAudioSession.sharedSession().didActivecated(audioSession)
RTCAudioSession.sharedSession().isAudioEnabled = true
and for second audio delegate method don't forget to add:
(void)provider:(CXProvider *)provider didDeactivateAudioSession:(AVAudioSession *)audioSession
RTCAudioSession.sharedSession().didDeactivecated(audioSession)
RTCAudioSession.sharedSession().isAudioEnabled = false

Apple suggests us to wait till the Connection gets established and then fulfill the performAnswerAction. Below is the source
Apple Suggestion for Call Kit Documentation
If the recipient of a call answers before the app establishes a connection to your server, don't fulfill the CXAnswerCallAction object sent to the provider:performAnswerCallAction: method of your delegate immediately. Instead, wait until you establish a connection and then fulfill the object. While it waits for your app to fulfill the request, the incoming call interface lets the user know that the call is connecting, but not yet ready.
So we need to wait for a second or two before we fulfill the action in performAnswerCallAction

In the end, I solved the problem, but I still do not understand why it can be solved.Below is my solution:
First of all, I delay the call of "fulfill" by 1 second (note that this time can not be less than 1 second)
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
if (self.delegate && [self.delegate respondsToSelector:#selector(callKitManager:refreshCurrentCallStatus:)]) {
[self.delegate callKitManager:self refreshCurrentCallStatus:EUCCallKitStatusAnswerAccept];
}
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[action fulfill];
});}
Second, I also delayed my network request call by one second (here longer than the previous one)
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.peerConnection offerForConstraints:[self offerConstraintsRestartIce:NO] completionHandler:^(RTCSessionDescription * _Nullable sdp, NSError * _Nullable error) {
[self peerConnection:self.peerConnection didCreateSessionDescription:sdp error:error];
}];
});
In this way, my problem is solved.
If you know why this can solve this problem, please comment on me, thank you!

Related

CHHapticEngine: Properly stop engine after a playback

I was trying to play a haptic "AHAP" pattern from a file with the following code:
__strong static CHHapticEngine *engine;
engine = [[CHHapticEngine alloc] initAndReturnError:nil];
[engine startAndReturnError:nil];
[engine playPatternFromURL:[NSURL fileURLWithPath:#"/path/to/pattern.ahap"] error:nil];
The haptics does play successfully, but I have an issue that whenever was pattern is played, the first key press on keyboard make very loud flick sound (the default iOS flick sound) and subsequently back to normal on second press. I thought it was because the engine is still active, so I called
[engine stopWithCompletionHandler:nil];
but then the haptic doesn't play anymore (however, flick sound is normal for first key press). playPatternFromURL:error: is supposed to play synchronously, which means it'll finish playing before executing stopWithCompletionHandler: (from Apple Doc). I honestly has no idea why and how this happens. CoreHaptics rarely can be seen implemented in the wild and github except the official Apple Doc, so I have no useful references (maybe except this in github).
Any idea on this particular issue? Thanks in advance.
EDIT:
For future reader, I managed to mitigate this issue by playing it in another thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[engine playPatternFromURL:[NSURL fileURLWithPath:#"/path/to/pattern.ahap"] error:nil];
});
Perhaps this is due to the Frameworks being a beta software as of writing.
EDIT 2:
Above mitigation however doesn't solve it if you have CHHapticEventTypeAudioCustom
I managed to solve it using these codes below:
__strong static CHHapticEngine *engine;
engine = [[CHHapticEngine alloc] initAndReturnError:nil];
[engine startAndReturnError:nil];
[engine playPatternFromURL:[NSURL fileURLWithPath:#"/path/to/pattern.ahap"] error:nil];
[engine notifyWhenPlayersFinished:^CHHapticEngineFinishedAction(NSError * _Nullable error) {
[engine stopWithCompletionHandler:nil];
return CHHapticEngineFinishedActionStopEngine;
}];
It seems like I needs to observe whenever the pattern stopped playing and stop the engine (not due to the framework being a beta software, my bad). However, for the method playPatternFromURL:error:, quoting from Apple Doc:
This method blocks processing on the current thread until the pattern
has finished playing.
doesn't seems to means what it means, at least to my understanding. That's why calling stopWithCompletionHandler: right after playPatternFromURL:error: failed to trigger any haptics.
Solution:
engine.playsHapticsOnly = YES;

How to detect when Crashlytics has NOT created a report

I have two code paths that needs to execute on app launch:
1. When Crashlytics detects a report from the last run
2. When it is a clean launch, ie, no crash report was detected.
Crashlytics provides (and recommends) that this method be used to detect crashes:
- (void) crashlyticsDidDetectReportForLastExecution:(CLSReport *)report
but the documentation specifically says that the method is not called synchronously during initialization. So while I can use this for detecting case #1, I don't think it is possible to use the same method to detect case #2 without possibly introducing a race condition.
As far as I can tell, the current framework does not expose any method to check for the existence of a report, either in Crashlytics.h or CLSReport.h. If it did, I could check for the existence of a crash report before the framework initializes.
Suggestions?
Solution proposed by Mike (from Fabric)
Mike -- I'm used to assuming that delegate methods and callbacks cannot be assumed to happen synchronously, or on the same thread. You seem to be saying that I can/should make that assumption here, so that this (psdeudocode) would work:
(in AppDelegate)
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler {
self.HadCrash = YES;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler(YES);
}];
}
(In AppDelegate didFinishLaunching)
crashlytics.delegate = self;
[crashlytics init]; // presumably if the delegate method IS going to be called, it will be called here.
if (!HadCrash)
{ // do "no crash" stuff here }
Mike from Fabric here, there are two methods that can be used to know about a crash that happened.
1) - (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report;
This method has the following restrictions:
It is not called synchronously during initialization
It does not give you the ability to prevent the report from being submitted
The report object itself is immutable
The most important benefits are that the ability to report crashes is not affected in any way.
2) - (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL submit))completionHandler;
This is called synchronously when the last execution of the app ended in a crash.
You can then take whatever action you want to take, but the report will not be sent unless the completionHandler is called with YES passed in. If NO is passed in then the call will be finished, but no report would be sent.
Here's a sample implementation from the docs:
- (void)crashlyticsDidDetectReportForLastExecution:(CLSReport *)report completionHandler:(void (^)(BOOL))completionHandler {
// Use this opportunity to take synchronous action on a crash. See Crashlytics.h for
// details and implications.
// Maybe consult NSUserDefaults or show a UI prompt.
// But, make ABSOLUTELY SURE you invoke completionHandler, as the SDK
// will not submit the report until you do. You can do this from any
// thread, but that's optional. If you want, you can just call the
// completionHandler and return.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
completionHandler(YES);
}];
}
I think that addresses the question, but let me know if I missed something.

ios: waiting for method to finish executing before continuing

I am new to IOS development and am currently facing a problem.
When method A is called, it calls method B and then it wait for delegate connectionDidFinish which connectionDidFinish will execute MethodC.
My question is how do I ensure that methodA to methodC has finished executing before executing NSLog?
I found that a way to solve this problem is to use notification center. Send notification to me after finishing executing methodC. I don't think this is a good solution. Is there another way to do this?
Example:
[a methodA];
NSLog(#"FINISH");
If any of those methods perform actions asynchronously, you can't. You'll have to look into a different way of doing this. I personally try to use completion blocks when ever I can, although it's perfectly fine to do this other ways, like with delegate methods. Here's a basic example using a completion block.
- (void)someMethod
{
[self methodAWithCompletion:^(BOOL success) {
// check if thing worked.
}];
}
- (void)methodAWithCompletion:(void (^) (BOOL success))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, kNilOptions), ^{
// go do something asynchronous...
dispatch_async(dispatch_get_main_queue(), ^{
completion(ifThingWorked)
});
});
}
In the code you posted, methodA must finish executing before the log statement will execute.
However, if methodA starts an asynchronous process that takes a while to finish and returns before it is finished, then you need to do something different. Usually you don't want to freeze the user interface while you are waiting, so you set up a delegate, pass in a completion block, or wait for an "ok, I'm done" notification.
All those are very valid, good ways to solve the problem of waiting for asynchronous tasks to finish running.
Newer APIs are starting to use completion blocks. Examples are:
presentViewController:animated:completion:, which takes a completion
block that gets called once the new view controller is fully
on-screen and "ready for business.
animateWithDuration:animations:completion:, which takes a completion
block that gets executed once the animation is finished, and
sendAsynchronousRequest:queue:completionHandler:, which starts an
asynchronous URL request (usually an HTTP GET or PUT request) and
provides a completion block that gets called once the request has
been completed (or fails)

iOS bonjour programming - wait for a response

I am pretty new to bonjour/networking with ObjC (although well versed in other areas!) I am asking for a bit of advice - I have an iOS app that will run on multiple iPads, in a store. The apps occasionally have to share some data, and the internet isn't always available so a webservice is not an option, hence I decided on using bonjour.
I have setup the Bonjour/NSNetservices and everything is functioning correctly, the ipads basically form an 'ad-hoc network' and connect automatically at app launch, however I am looking for advice for the following situation:
The app normally shares data in the background, without any user intervention - however there is one function where when a button is pressed on one app, data should be returned from another app remotely. The UI then updates when the data has been received from the other device - however if the connection should be lost to the other device, the data will never reach the users device, and the data will not be displayed. I am wanting to implement some form of timout, but unsure how to do this - any suggestions would be much appreciated!
The process flow for this is something like this:
button press on 'dev 1' > 'dev 1' broadcasts 'dev 2 please send data message' > 'dev 2' responds with requested data [timeout required here] > UI is updated if data is received /[if timeout fires, error message is displayed]
So I really just need a timeout for the last section - and I really cannot think of a way to implement it.
I can post code for this if required.
Thanks!
This solution may work if you have the possibility to manually cancel the request.
I believe you can use a simple NSTimer to cancel the request after a wait. I'm sure it's not the best solution, but it will probably work.
Here's how I would do it.
Create your timer and an NSInteger (to store the timer value) in your class :
NSTimer *timer;
NSInteger timerValue;
Call this method with timer = [self startTimer]; when you fire your request :
- (NSTimer*)startTimer {
timerValue = 30;
return [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(timerTicked:) userInfo:nil repeats:YES];
}
Implement the timerTicked: method :
- (void)timerTicked:(NSTimer*)timer {
timerValue --;
if (timerValue <= 0) {
// Cancel the request
// Show an alert
}
}
You can cancel the timer with [timer invalidate]; but remember this will "destroy" your timer, so it won't fire events ever again.
As you've not indicated how you are currently requesting data from your other device, I have had to make some assumptions.
You can use NSURLRequest with a timeout using requestWithURL:cachePolicy:timeoutInterval:
See NSURLRequest documentation

Google Analytics on iOS returning NO on dispatch, no debug output

In trying to implement the Google Analytics SDK for iOS, I've run into two brick walls.
The first one is that after executing this code in application:DidFinishLaunchingWithOptions:
[[GANTracker sharedTracker] startTrackerWithAccountID:#"UA-XXXXXXX-YY"
dispatchPeriod:10
delegate:self];
[[GANTracker sharedTracker] setDebug:YES];
.. and then trying to track anything or call dispatch, no debug messages are logged whatsoever. I've added NSLog lines before and after tracking calls and the code is definitely being reached.
Secondly, when I do try and do a manual dispatch, it returns NO. All the other issues I've seen online are where dispatch returns YES but it's somehow not going through properly. What does one do if dispatch actually returns NO?
I've tried adding an NSError * reference to the track methods and those actually succeed (no error, function returns YES). But the events are definitely not being periodically dispatched, since we're seeing nothing on the GA account more than 24 hours later.
EDIT: I've also got NSLog calls in both of the delegate methods (hitDispatched: and trackerDispatchDidComplete:eventsDispatched:eventsFailedDispatch:), and neither of those are being called either.
i think you should check this to delegate method of GANTracker
- (void)trackerDispatchDidComplete:(GANTracker *)tracker
eventsDispatched:(NSUInteger)hitsDispatched
eventsFailedDispatch:(NSUInteger)hitsFailedDispatch{
//print or check number of events failed or success
}
//Delegate is set to 'nil' instead of class instance which implements the delegate methods.
[[GANTracker sharedTracker] startTrackerWithAccountID:#"UA-XXXXXXX-YY"
dispatchPeriod:10
delegate:nil];
In your case, assuming that UIApplicationDelegate may be implementing GANTrackerDelegate, the message call should set delegate as ' self '.
Cheers!! Amar.
Possibly the dispatch is relying on the calling thread's run loop - Is it possible you are running this from a secondary thread, one which might not exist by the time the dispatch is suppose to call you back?
You've not enabled dryRun have you? Double check with:
[[GANTracker sharedTracker] setDryRun:NO];
Also try dispatchSynchronous, it'll block as it's sending but might help with if things aren't on the same threads:
[[GANTracker sharedTracker] dispatchSynchronous];
Just've checked it from scratch, dispatch perfectly worked meaning
a) your device is somehow different (i still have unsolved crashes on the particular iPad's 3 from Apple tester's unresolved, so it wouldn't be a huge surprise)
b) your code is somehow different - and that's much easier for you to fix.
For the a) there's no advice but to test it against all the devices you might get, for the b) i could only say what worked for me:
downloaded 1.4 SDK here
got Google sample projects with git clone https://code.google.com/p/google-mobile-dev.analytics-end-to-end/
configured final/AnalyticsSample to launch, changed the source slightly
(trackEvent::::: were called from sample, app was restarted manually as there's zero time period requiring dispatch call)
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[GANTracker sharedTracker] startTrackerWithAccountID:kGANAccountId
dispatchPeriod:0
delegate:self];
NSLog(#"Dispatch%#", [[GANTracker sharedTracker] dispatch] ? #"ed Successfully": #" Failed");
[self.window addSubview:tabBarController.view];
[self.window makeKeyAndVisible];
return YES;
}
That's it, log says Dispatched Successfully, worth trying i guess.
cough
I'd misspelled the #define to start the tracker object in my app delegate. Other files were spelled correctly, hence the logging statements showing up, but when I tried to log just before the tracker was started it didn't show.
Oops. Well, at least there's a decent troubleshooting post for Google Analytics on SO now!

Resources