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.

I open the feedback page failed. so I decide post the content here.

How to change the audio route in the WKWebview ?

I have an app that uses WebRTC in a WKWebView.

I want to control the audio output route, either speaker or receiver.

I used a JS Bridge to Objective-C code, but it didn't work.

Here is a minimal example project demonstrating that the Objective-C code cannot control the audio output route inside the WKWebView.

the project file is https://www.ayi9.com/xx/test/test.zip

What needs to be done to achieve my goal?

I use iOS 16.7 for test. Should I change my iPhone for a new version of iOS ?

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