Issues with monitoring and changing WebRTC audio output device in WKWebView

I am developing a VoIP app that uses WebRTC inside a WKWebView.

Question 1: How can I monitor which audio output device WebRTC is currently using? I want to display this information in the UI for the user .

Question 2: How can I change the current audio output device for WebRTC?

I am using a JS Bridge to Objective-C code, attempting to change the audio device with the following code:


void set_speaker(int n)
{
	session = [AVAudioSession sharedInstance];
    NSError *err = nil;
     
    if (n == 1) {
        [session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker error:&err];
    } else {
        [session overrideOutputAudioPort:AVAudioSessionPortOverrideNone error:&err];
    }
    
 
    
}

However, this approach does not work.

I am testing on an iPhone with iOS 16.7. Is a higher iOS version required?

Answered by DTS Engineer in 885337022

Question 1 — Monitoring the current audio output device

Register for AVAudioSessionRouteChangeNotification and inspect AVAudioSession.sharedInstance.currentRoute.outputs. Each entry is an AVAudioSessionPortDescription whose portType tells you what device is active (e.g. AVAudioSessionPortBuiltInSpeaker, AVAudioSessionPortBuiltInReceiver, AVAudioSessionPortBluetoothA2DP).

[[NSNotificationCenter defaultCenter]
    addObserver:self
       selector:@selector(audioRouteChanged:)
           name:AVAudioSessionRouteChangeNotification
         object:nil];

- (void)audioRouteChanged:(NSNotification *)notification {
    AVAudioSessionRouteDescription *route =
        [[AVAudioSession sharedInstance] currentRoute];
    for (AVAudioSessionPortDescription *output in route.outputs) {
        NSLog(@"Current output: %@", output.portType);
    }
}

Question 2 — Changing the audio output device

There is a concrete issue in the code you posted: overrideOutputAudioPort: is only valid when the audio session category is set to AVAudioSessionCategoryPlayAndRecord. Without that, the call fails. Your snippet never sets the category, and it doesn't check the NSError output, so the failure is silent. At minimum you need:

AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *err = nil;

[session setCategory:AVAudioSessionCategoryPlayAndRecord
         withOptions:AVAudioSessionCategoryOptionAllowBluetooth
               error:&err];
if (err) {
    NSLog(@"Category error: %@", err);
    return;
}

[session setActive:YES error:&err];
if (err) {
    NSLog(@"Activation error: %@", err);
    return;
}

[session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
                           error:&err];
if (err) {
    NSLog(@"Override error: %@", err);
}

Be aware that overrideOutputAudioPort: is a temporary override by design — the documentation states that it resets whenever a route change occurs. If you want the speaker to be the persistent default output (instead of the receiver) when no other audio route is connected, set the AVAudioSessionCategoryOptionDefaultToSpeaker option when you configure the category:

[session setCategory:AVAudioSessionCategoryPlayAndRecord
         withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker |
                     AVAudioSessionCategoryOptionAllowBluetooth
               error:&err];

One additional consideration: WKWebView manages its own AVAudioSession configuration internally when WebRTC captures audio via getUserMedia(). That internal configuration may trigger a route change, which would reset a prior overrideOutputAudioPort: call. If you find that fixing the category and using DefaultToSpeaker still doesn't give you the behavior you need, please file a feedback report at https://feedbackassistant.apple.com with a small sample project that demonstrates the issue, and post the feedback number here so we can track it.

Question 1 — Monitoring the current audio output device

Register for AVAudioSessionRouteChangeNotification and inspect AVAudioSession.sharedInstance.currentRoute.outputs. Each entry is an AVAudioSessionPortDescription whose portType tells you what device is active (e.g. AVAudioSessionPortBuiltInSpeaker, AVAudioSessionPortBuiltInReceiver, AVAudioSessionPortBluetoothA2DP).

[[NSNotificationCenter defaultCenter]
    addObserver:self
       selector:@selector(audioRouteChanged:)
           name:AVAudioSessionRouteChangeNotification
         object:nil];

- (void)audioRouteChanged:(NSNotification *)notification {
    AVAudioSessionRouteDescription *route =
        [[AVAudioSession sharedInstance] currentRoute];
    for (AVAudioSessionPortDescription *output in route.outputs) {
        NSLog(@"Current output: %@", output.portType);
    }
}

Question 2 — Changing the audio output device

There is a concrete issue in the code you posted: overrideOutputAudioPort: is only valid when the audio session category is set to AVAudioSessionCategoryPlayAndRecord. Without that, the call fails. Your snippet never sets the category, and it doesn't check the NSError output, so the failure is silent. At minimum you need:

AVAudioSession *session = [AVAudioSession sharedInstance];
NSError *err = nil;

[session setCategory:AVAudioSessionCategoryPlayAndRecord
         withOptions:AVAudioSessionCategoryOptionAllowBluetooth
               error:&err];
if (err) {
    NSLog(@"Category error: %@", err);
    return;
}

[session setActive:YES error:&err];
if (err) {
    NSLog(@"Activation error: %@", err);
    return;
}

[session overrideOutputAudioPort:AVAudioSessionPortOverrideSpeaker
                           error:&err];
if (err) {
    NSLog(@"Override error: %@", err);
}

Be aware that overrideOutputAudioPort: is a temporary override by design — the documentation states that it resets whenever a route change occurs. If you want the speaker to be the persistent default output (instead of the receiver) when no other audio route is connected, set the AVAudioSessionCategoryOptionDefaultToSpeaker option when you configure the category:

[session setCategory:AVAudioSessionCategoryPlayAndRecord
         withOptions:AVAudioSessionCategoryOptionDefaultToSpeaker |
                     AVAudioSessionCategoryOptionAllowBluetooth
               error:&err];

One additional consideration: WKWebView manages its own AVAudioSession configuration internally when WebRTC captures audio via getUserMedia(). That internal configuration may trigger a route change, which would reset a prior overrideOutputAudioPort: call. If you find that fixing the category and using DefaultToSpeaker still doesn't give you the behavior you need, please file a feedback report at https://feedbackassistant.apple.com with a small sample project that demonstrates the issue, and post the feedback number here so we can track it.

Issues with monitoring and changing WebRTC audio output device in WKWebView
 
 
Q