How to redirect audio to speakers in the AppRTC iOS example?
Asked Answered
D

6

15

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?

Drusilladrusus answered 6/7, 2014 at 11:58 Comment(3)
Did you find some solution for your problem? I'm facing same problem as you at the moment.Guglielmo
@JosipB. github.com/alongubkin/audiotoggleDrusilladrusus
doesn't work for me...Guglielmo
S
15

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;
  }
}
Sneck answered 30/1, 2015 at 10:0 Comment(2)
Finally a solution that worked for me. Many thanks.Counterplot
This solution is dangerous. This notification will get called each time someone changes the route, in other words, it could lead to potential issues later.Guarantor
B
6

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
    }
}
Brython answered 8/6, 2018 at 4:0 Comment(0)
O
5

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")
    }
}
Overbear answered 21/7, 2020 at 9:4 Comment(1)
This really is the only solution that worked for me from all that I tried. The problem seems to be that the category changes even after the session has been started and so overrides the initial configurationProtease
G
2

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.

Guglielmo answered 26/1, 2015 at 13:51 Comment(0)
M
1

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];
}
Machine answered 30/8, 2017 at 21:14 Comment(1)
"AVAudioSessionRouteChangeReasonCategoryChange" instead of "AVAudioSessionRouteChangeReasonRouteConfigurationChange" fixed my issue. ThanksHawk
M
1

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()
}
Mythical answered 14/6, 2022 at 9:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.