Skip to content

Commit f3b8302

Browse files
authored
fix: fix useIosAudioManagement not getting mic/audio when going from playback to playAndRecord (#168)
1 parent b154a28 commit f3b8302

File tree

4 files changed

+81
-36
lines changed

4 files changed

+81
-36
lines changed

example/src/RoomPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
type TrackReferenceOrPlaceholder,
2828
type ReceivedDataMessage,
2929
AndroidAudioTypePresets,
30+
useIOSAudioManagement,
3031
} from '@livekit/react-native';
3132
import { Platform } from 'react-native';
3233
// @ts-ignore
@@ -89,6 +90,7 @@ const RoomView = ({ navigation }: RoomViewProps) => {
8990
const [isCameraFrontFacing, setCameraFrontFacing] = useState(true);
9091
const room = useRoomContext();
9192

93+
useIOSAudioManagement(room, true);
9294
// Setup room listeners
9395
const { send } = useDataChannel(
9496
(dataMessage: ReceivedDataMessage<string>) => {

ios/LivekitReactNative.m

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -121,44 +121,59 @@ +(void)setup {
121121

122122
/// Configure audio config for WebRTC
123123
RCT_EXPORT_METHOD(setAppleAudioConfiguration:(NSDictionary *) configuration){
124-
RTCAudioSession* session = [RTCAudioSession sharedInstance];
125-
RTCAudioSessionConfiguration* config = [RTCAudioSessionConfiguration webRTCConfiguration];
126-
127-
NSString* appleAudioCategory = configuration[@"audioCategory"];
128-
NSArray* appleAudioCategoryOptions = configuration[@"audioCategoryOptions"];
129-
NSString* appleAudioMode = configuration[@"audioMode"];
130-
131-
[session lockForConfiguration];
132-
133-
if(appleAudioCategoryOptions != nil) {
134-
config.categoryOptions = 0;
135-
for(NSString* option in appleAudioCategoryOptions) {
136-
if([@"mixWithOthers" isEqualToString:option]) {
137-
config.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers;
138-
} else if([@"duckOthers" isEqualToString:option]) {
139-
config.categoryOptions |= AVAudioSessionCategoryOptionDuckOthers;
140-
} else if([@"allowBluetooth" isEqualToString:option]) {
141-
config.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetooth;
142-
} else if([@"allowBluetoothA2DP" isEqualToString:option]) {
143-
config.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
144-
} else if([@"allowAirPlay" isEqualToString:option]) {
145-
config.categoryOptions |= AVAudioSessionCategoryOptionAllowAirPlay;
146-
} else if([@"defaultToSpeaker" isEqualToString:option]) {
147-
config.categoryOptions |= AVAudioSessionCategoryOptionDefaultToSpeaker;
148-
}
124+
RTCAudioSession* session = [RTCAudioSession sharedInstance];
125+
RTCAudioSessionConfiguration* config = [RTCAudioSessionConfiguration webRTCConfiguration];
126+
127+
NSString* appleAudioCategory = configuration[@"audioCategory"];
128+
NSArray* appleAudioCategoryOptions = configuration[@"audioCategoryOptions"];
129+
NSString* appleAudioMode = configuration[@"audioMode"];
130+
131+
[session lockForConfiguration];
132+
133+
NSError* error = nil;
134+
BOOL categoryChanged = NO;
135+
if(appleAudioCategoryOptions != nil) {
136+
categoryChanged = YES;
137+
config.categoryOptions = 0;
138+
for(NSString* option in appleAudioCategoryOptions) {
139+
if([@"mixWithOthers" isEqualToString:option]) {
140+
config.categoryOptions |= AVAudioSessionCategoryOptionMixWithOthers;
141+
} else if([@"duckOthers" isEqualToString:option]) {
142+
config.categoryOptions |= AVAudioSessionCategoryOptionDuckOthers;
143+
} else if([@"allowBluetooth" isEqualToString:option]) {
144+
config.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetooth;
145+
} else if([@"allowBluetoothA2DP" isEqualToString:option]) {
146+
config.categoryOptions |= AVAudioSessionCategoryOptionAllowBluetoothA2DP;
147+
} else if([@"allowAirPlay" isEqualToString:option]) {
148+
config.categoryOptions |= AVAudioSessionCategoryOptionAllowAirPlay;
149+
} else if([@"defaultToSpeaker" isEqualToString:option]) {
150+
config.categoryOptions |= AVAudioSessionCategoryOptionDefaultToSpeaker;
151+
}
152+
}
149153
}
150-
}
151154

152-
if(appleAudioCategory != nil) {
153-
config.category = [AudioUtils audioSessionCategoryFromString:appleAudioCategory];
154-
[session setCategory:config.category withOptions:config.categoryOptions error:nil];
155-
}
155+
if(appleAudioCategory != nil) {
156+
categoryChanged = YES;
157+
config.category = [AudioUtils audioSessionCategoryFromString:appleAudioCategory];
158+
}
159+
160+
if(categoryChanged) {
161+
[session setCategory:config.category withOptions:config.categoryOptions error:&error];
162+
if(error != nil) {
163+
NSLog(@"Error setting category: %@", [error localizedDescription]);
164+
error = nil;
165+
}
166+
}
156167

157-
if(appleAudioMode != nil) {
158-
config.mode = [AudioUtils audioSessionModeFromString:appleAudioMode];
159-
[session setMode:config.mode error:nil];
160-
}
168+
if(appleAudioMode != nil) {
169+
config.mode = [AudioUtils audioSessionModeFromString:appleAudioMode];
170+
[session setMode:config.mode error:&error];
171+
if(error != nil) {
172+
NSLog(@"Error setting category: %@", [error localizedDescription]);
173+
error = nil;
174+
}
175+
}
161176

162-
[session unlockForConfiguration];
177+
[session unlockForConfiguration];
163178
}
164179
@end

src/audio/AudioSession.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,12 @@ export default class AudioSession {
325325
}
326326
};
327327

328+
/**
329+
* Directly change the AVAudioSession category/mode.
330+
*
331+
* @param config The configuration to use. Null values will be omitted and the
332+
* existing values will be unchanged.
333+
*/
328334
static setAppleAudioConfiguration = async (
329335
config: AppleAudioConfiguration
330336
) => {

src/index.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import AudioSession, {
1313
} from './audio/AudioSession';
1414
import type { AudioConfiguration } from './audio/AudioSession';
1515
import { PixelRatio, Platform } from 'react-native';
16-
import type { LiveKitReactNativeInfo } from 'livekit-client';
16+
import { type LiveKitReactNativeInfo } from 'livekit-client';
1717
import type { LogLevel, SetLogLevelOptions } from './logger';
1818

1919
/**
@@ -23,6 +23,7 @@ import type { LogLevel, SetLogLevelOptions } from './logger';
2323
*/
2424
export function registerGlobals() {
2525
webrtcRegisterGlobals();
26+
iosCategoryEnforce();
2627
livekitRegisterGlobals();
2728
setupURLPolyfill();
2829
fixWebrtcAdapter();
@@ -31,6 +32,27 @@ export function registerGlobals() {
3132
shimAsyncIterator();
3233
shimIterator();
3334
}
35+
36+
/**
37+
* Enforces changing to playAndRecord category prior to obtaining microphone.
38+
*/
39+
function iosCategoryEnforce() {
40+
if (Platform.OS === 'ios') {
41+
// @ts-ignore
42+
let getUserMediaFunc = global.navigator.mediaDevices.getUserMedia;
43+
// @ts-ignore
44+
global.navigator.mediaDevices.getUserMedia = async (constraints: any) => {
45+
if (constraints.audio) {
46+
await AudioSession.setAppleAudioConfiguration({
47+
audioCategory: 'playAndRecord',
48+
});
49+
}
50+
51+
return await getUserMediaFunc(constraints);
52+
};
53+
}
54+
}
55+
3456
function livekitRegisterGlobals() {
3557
let lkGlobal: LiveKitReactNativeInfo = {
3658
platform: Platform.OS,

0 commit comments

Comments
 (0)