Reputation: 57119
I'm trying to redirect audio to speakers in the AppRTC iOS example.
I tried:
AVAudioSession* session = [AVAudioSession sharedInstance];
//error handling
BOOL success;
NSError* error;
//set the audioSession category.
//Needs to be Record or PlayAndRecord to use audioRouteOverride:
success = [session setCategory:AVAudioSessionCategoryPlayAndRecord
error:&error];
if (!success) NSLog(@"AVAudioSession error setting category:%@",error);
//set the audioSession override
success = [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
error:&error];
if (!success) NSLog(@"AVAudioSession error overrideOutputAudioPort:%@",error);
//activate the audio session
success = [session setActive:YES error:&error];
if (!success) NSLog(@"AVAudioSession error activating: %@",error);
else NSLog(@"audioSession active");
There are no errors, but it doesn't work. How can I fix this?
Upvotes: 15
Views: 8431
Reputation: 1954
I have been facing this issue for a while and I have found a solution for this. The problem was caused when we set AVAudioSessionCategory before the WebRTC completed its configurations. We can fix this by updating the audio configurations on AudioSessionRouteChange happens. There are two approaches to finding the AudioSessionRouteChange and update the configurations.
1. Using RTCAudioSessionDelegate
let rtcAudioSession = RTCAudioSession.sharedInstance()
// set RTCAudioSessionDelegate to your class
rtcAudioSession.add(self)
// MARK: - RTCAudioSessionDelegate
extension YourClass: RTCAudioSessionDelegate {
func audioSessionDidChangeRoute(_ session: RTCAudioSession,
reason: AVAudioSession.RouteChangeReason,
previousRoute: AVAudioSessionRouteDescription) {
// set audio configurations
rtcAudioSession.lockForConfiguration()
do {
try rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.defaultToSpeaker, .allowBluetoothA2DP, .allowBluetooth])
try rtcAudioSession.setMode(AVAudioSession.Mode.videoChat.rawValue)
try rtcAudioSession.setActive(true)
} catch let error {
debugPrint("Error changeing AVAudioSession category: \(error)")
}
rtcAudioSession.unlockForConfiguration()
}
}
1. AVAudioSession.routeChangeNotification
let rtcAudioSession = RTCAudioSession.sharedInstance()
NotificationCenter.default.addObserver(forName: AVAudioSession.routeChangeNotification,
object: nil,
queue: nil,
using: onRouteChange)
private func onRouteChange(_ notification: Notification) {
// set audio configurations
rtcAudioSession.lockForConfiguration()
do {
try rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue, with: [.defaultToSpeaker, .allowBluetoothA2DP, .allowBluetooth])
try rtcAudioSession.setMode(AVAudioSession.Mode.videoChat.rawValue)
try rtcAudioSession.setActive(true)
} catch let error {
debugPrint("Error changeing AVAudioSession category: \(error)")
}
rtcAudioSession.unlockForConfiguration()
}
Upvotes: 1
Reputation: 1191
For anyone who came here, searching for a solution in Swift that also accounts for changes back from (BT-)earphones. The below sample (Swift 5) does that.
Adopted in part from @Teivaz
@objc func handleRouteChange(notification: Notification) {
guard let info = notification.userInfo,
let value = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSession.RouteChangeReason(rawValue: value) else { return }
switch reason {
case .categoryChange:
try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
case .oldDeviceUnavailable:
try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
default:
l.debug("other")
}
}
Upvotes: 5
Reputation: 5460
Still seems to be an issue, phuongle answer worked for me. Swift 4 version:
NotificationCenter.default.addObserver(forName: .AVAudioSessionRouteChange, object: nil, queue: nil, using: routeChange)
private func routeChange(_ n: Notification) {
guard let info = n.userInfo,
let value = info[AVAudioSessionRouteChangeReasonKey] as? UInt,
let reason = AVAudioSessionRouteChangeReason(rawValue: value) else { return }
switch reason {
case .categoryChange: try? AVAudioSession.sharedInstance().overrideOutputAudioPort(.speaker)
default: break
}
}
Upvotes: 6
Reputation: 5665
phuongle's answer is correct. Though when you enable override this will actually override audio output even when user plugs in headphones. There's not much sense in playing audio through loudspeaker when user is using headphones. For this purpose use following code:
- (void)didSessionRouteChange:(NSNotification *)notification
{
NSDictionary *interuptionDict = notification.userInfo;
const NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
if (routeChangeReason == AVAudioSessionRouteChangeReasonRouteConfigurationChange) {
[self enableLoudspeaker];
}
}
- (void)enableLoudspeaker {
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
AVAudioSessionCategoryOptions options = audioSession.categoryOptions;
if (options & AVAudioSessionCategoryOptionDefaultToSpeaker) return;
options |= AVAudioSessionCategoryOptionDefaultToSpeaker;
[audioSession setActive:YES error:nil];
[audioSession setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:options error:nil];
}
Upvotes: 1
Reputation: 1186
I solved it by the solution. Just listen AVAudioSessionRouteChangeNotification
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didSessionRouteChange:) name:AVAudioSessionRouteChangeNotification object:nil];
And using the didSessionRouteChange selector as below:
- (void)didSessionRouteChange:(NSNotification *)notification
{
NSDictionary *interuptionDict = notification.userInfo;
NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
switch (routeChangeReason) {
case AVAudioSessionRouteChangeReasonCategoryChange: {
// Set speaker as default route
NSError* error;
[[AVAudioSession sharedInstance] overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&error];
}
break;
default:
break;
}
}
Upvotes: 15
Reputation: 2464
I found solution in the end.
Reason was that you need to set AVAudioSession
category to AVAudioSessionCategoryPlayback
. But for some reason after establishing webRTC call it was set back to AVAudioSessionCategoryPlayAndRecord
.
In the end I decided to add observer for AVAudioSessionRouteChangeNotification
and switch to AVAudioSessionCategoryPlayback
each time I detected unwanted category change.
A bit of hack solution but worked in the end. You can check it here.
Upvotes: 2