I have a calling app that uses CallKit and it works great except for the speaker button. I found that the CallKit speaker button doesn't work as expected, when I click on it to set the highlight, it always comes back after a while, the code example is as follows:
- (void)configAudiosession {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
NSUInteger options = 0;
options |= AVAudioSessionCategoryOptionAllowBluetooth;
options |= AVAudioSessionCategoryOptionDuckOthers;
[session setCategory:AVAudioSessionCategoryPlayAndRecord mode:AVAudioSessionModeVoiceChat options:options error:&error];
if (error) NSLog(@"setCategory error %@", error);
error = nil;
[session setActive:YES error:&error];
if (error) NSLog(@"setActive error %@", error);
}
- (void)reportIncomingCall {
[self configAudiosession];
NSUUID *uuid = [NSUUID UUID];
CXCallUpdate *update = [[CXCallUpdate alloc] init];
update.localizedCallerName = @"caller nickname";
update.supportsHolding = NO;
update.supportsDTMF = NO;
update.hasVideo = NO;
[self.provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
if (error) NSLog(@"reportNewIncomingCallWithUUID error %@", error);
}];
}
I refer to apple documentation https://developer.apple.com/documentation/avfaudio/avaudiosession/1616614-setmode?language=objc
Instead of setting your category and mode properties independently, set them at the same time using the setCategory:mode:options:error: or setCategory:mode:routeSharingPolicy:options:error: method.
So I use setCategory:mode:options:error: to set the audio session. But this setting will cause the callkit speaker buttons not to work,When I adjust the code as below, the callkit speaker button works fine:
- (void)configAudiosession {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
NSUInteger options = 0;
options |= AVAudioSessionCategoryOptionAllowBluetooth;
options |= AVAudioSessionCategoryOptionDuckOthers;
[session setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:options error:&error];
if (error) NSLog(@"setCategory error %@", error);
error = nil;
[session setMode:AVAudioSessionModeVoiceChat error:&error];
if (error) NSLog(@"setMode error %@", error);
error = nil;
[session setActive:YES error:&error];
if (error) NSLog(@"setActive error %@", error);
}
Here I use setCategory:withOptions:error: and setMode:error: instead setCategory:mode:options:error:.I know this goes against the advice of the apple docs, But the CallKit speaker button works fine.
But I found another problem.
typedef NS_OPTIONS(NSUInteger, AVAudioSessionCategoryOptions) {
AVAudioSessionCategoryOptionMixWithOthers = 0x1,
AVAudioSessionCategoryOptionDuckOthers = 0x2,
AVAudioSessionCategoryOptionAllowBluetooth API_UNAVAILABLE(tvos, watchos, macos) = 0x4,
AVAudioSessionCategoryOptionDefaultToSpeaker API_UNAVAILABLE(tvos, watchos, macos) = 0x8,
AVAudioSessionCategoryOptionInterruptSpokenAudioAndMixWithOthers API_AVAILABLE(ios(9.0), watchos(2.0), tvos(9.0)) API_UNAVAILABLE(macos) = 0x11,
AVAudioSessionCategoryOptionAllowBluetoothA2DP API_AVAILABLE(ios(10.0), watchos(3.0), tvos(10.0)) API_UNAVAILABLE(macos) = 0x20,
AVAudioSessionCategoryOptionAllowAirPlay API_AVAILABLE(ios(10.0), tvos(10.0)) API_UNAVAILABLE(watchos, macos) = 0x40,
AVAudioSessionCategoryOptionOverrideMutedMicrophoneInterruption API_AVAILABLE(ios(14.5), watchos(7.3)) API_UNAVAILABLE(tvos, macos) = 0x80,
};
The options set in my code should be mixWithOthers and duckOthers and allowBluetooth (according to Apple: Setting duckOthers option will also make your session mixable with others). The correct value should be 7, but the categoryOptions value of this setting is 3. Obviously, allowBluetooth option did not take effect.
This is apparently a bug known to Apple, and I found a similar fix in Google's webRTC code:
- (BOOL)setConfiguration:(RTC_OBJC_TYPE(RTCAudioSessionConfiguration) *)configuration
active:(BOOL)active
shouldSetActive:(BOOL)shouldSetActive
error:(NSError **)outError {
......
if (self.category != configuration.category ||
self.categoryOptions != configuration.categoryOptions) {
NSError *categoryError = nil;
if (![self setCategory:configuration.category
withOptions:configuration.categoryOptions
error:&categoryError]) {
RTCLogError(@"Failed to set category: %@",
categoryError.localizedDescription);
error = categoryError;
} else {
RTCLog(@"Set category to: %@", configuration.category);
}
}
if (self.mode != configuration.mode) {
NSError *modeError = nil;
if (![self setMode:configuration.mode error:&modeError]) {
RTCLogError(@"Failed to set mode: %@",
modeError.localizedDescription);
error = modeError;
} else {
RTCLog(@"Set mode to: %@", configuration.mode);
}
}
// Sometimes category options don't stick after setting mode.
if (self.categoryOptions != configuration.categoryOptions) {
NSError *categoryError = nil;
if (![self setCategory:configuration.category
withOptions:configuration.categoryOptions
error:&categoryError]) {
RTCLogError(@"Failed to set category options: %@",
categoryError.localizedDescription);
error = categoryError;
} else {
RTCLog(@"Set category options to: %ld",
(long)configuration.categoryOptions);
}
}
......
}
I saw that after setting the mode, the options were confused, and webRTC avoided this bug by setting it again. So I did the same:
- (void)configAudiosession {
AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *error = nil;
AVAudioSessionCategory category = AVAudioSessionCategoryPlayAndRecord;
NSUInteger options = 0;
options |= AVAudioSessionCategoryOptionAllowBluetooth;
options |= AVAudioSessionCategoryOptionDuckOthers;
options |= AVAudioSessionCategoryOptionMixWithOthers;
[session setCategory:category withOptions:options error:&error];
if (error) NSLog(@"setCategory error %@", error);
error = nil;
[session setMode:AVAudioSessionModeVoiceChat error:&error];
if (error) NSLog(@"setMode error %@", error);
if (![session.category isEqualToString:category] || options != session.categoryOptions) {
NSError *error = nil;
[session setCategory:category withOptions:options error:&error];
if (error) NSLog(@"again setCategory error %@", error);
}
error = nil;
[session setActive:YES error:&error];
if (error) NSLog(@"setActive error %@", error);
}
CallKit speaker buttons no longer work. I think it's apple bug, but some apps like what's app, teams they work fine. Can someone help me?