Skip to content

Commit f467a48

Browse files
committed
Revert "Update iOS audio management"
This reverts commit 7b60981.
1 parent 7b60981 commit f467a48

5 files changed

Lines changed: 150 additions & 65 deletions

File tree

example/index.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { AppRegistry } from 'react-native';
22
import App from './src/App';
33
import { name as appName } from './app.json';
4-
import { registerGlobals, setLogLevel, useIOSAudioManagement } from '@livekit/react-native';
4+
import { registerGlobals, setLogLevel } from '@livekit/react-native';
55
import { LogLevel } from 'livekit-client';
66
import { setupErrorLogHandler } from './src/utils/ErrorLogHandler';
77
import { setupCallService } from './src/callservice/CallService';
@@ -16,5 +16,3 @@ setupCallService();
1616
// Required React-Native setup for app
1717
registerGlobals();
1818
AppRegistry.registerComponent(appName, () => App);
19-
20-
useIOSAudioManagement();

example/src/RoomPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ const RoomView = ({ navigation, e2ee }: RoomViewProps) => {
106106
return () => {};
107107
}, [room, e2ee]);
108108

109+
useIOSAudioManagement(room, true);
109110
// Setup room listeners
110111
const { send } = useDataChannel(
111112
(dataMessage: ReceivedDataMessage<string>) => {

ios/LiveKitReactNativeModule.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class LivekitReactNativeModule: RCTEventEmitter {
2929
super.init()
3030
let config = RTCAudioSessionConfiguration()
3131
config.category = AVAudioSession.Category.playAndRecord.rawValue
32-
config.categoryOptions = [.allowAirPlay, .allowBluetoothHFP, .allowBluetoothA2DP, .defaultToSpeaker]
32+
config.categoryOptions = [.allowAirPlay, .allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker]
3333
config.mode = AVAudioSession.Mode.videoChat.rawValue
3434

3535
RTCAudioSessionConfiguration.setWebRTC(config)

src/audio/AudioManager.ts

Lines changed: 116 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,141 @@
1+
import { useState, useEffect, useMemo } from 'react';
2+
import { Platform } from 'react-native';
3+
import {
4+
RoomEvent,
5+
Room,
6+
type LocalTrackPublication,
7+
type RemoteTrackPublication,
8+
} from 'livekit-client';
19
import AudioSession, {
10+
getDefaultAppleAudioConfigurationForMode,
211
type AppleAudioConfiguration,
12+
type AudioTrackState,
313
} from './AudioSession';
414
import { log } from '..';
5-
import { audioDeviceModuleEvents } from '@livekit/react-native-webrtc';
6-
7-
export type AudioEngineConfigurationState = {
8-
isPlayoutEnabled: boolean;
9-
isRecordingEnabled: boolean;
10-
preferSpeakerOutput: boolean;
11-
};
1215

1316
/**
1417
* Handles setting the appropriate AVAudioSession options automatically
1518
* depending on the audio track states of the Room.
1619
*
20+
* @param room
1721
* @param preferSpeakerOutput
1822
* @param onConfigureNativeAudio A custom method for determining options used.
1923
*/
2024
export function useIOSAudioManagement(
21-
preferSpeakerOutput = true,
22-
onConfigureNativeAudio?: (configurationState: AudioEngineConfigurationState) => AppleAudioConfiguration
25+
room: Room,
26+
preferSpeakerOutput: boolean = true,
27+
onConfigureNativeAudio?: (
28+
trackState: AudioTrackState,
29+
preferSpeakerOutput: boolean
30+
) => AppleAudioConfiguration
2331
) {
24-
let audioEngineState: AudioEngineConfigurationState = {
25-
isPlayoutEnabled: false,
26-
isRecordingEnabled: false,
27-
preferSpeakerOutput: preferSpeakerOutput,
28-
};
32+
const [localTrackCount, setLocalTrackCount] = useState(0);
33+
const [remoteTrackCount, setRemoteTrackCount] = useState(0);
34+
const trackState = useMemo(
35+
() => computeAudioTrackState(localTrackCount, remoteTrackCount),
36+
[localTrackCount, remoteTrackCount]
37+
);
2938

30-
const tryConfigure = async (newState: AudioEngineConfigurationState, oldState: AudioEngineConfigurationState) => {
31-
if ((!newState.isPlayoutEnabled && !newState.isRecordingEnabled) && (oldState.isPlayoutEnabled || oldState.isRecordingEnabled)) {
32-
log.info("AudioSession deactivating...")
33-
await AudioSession.stopAudioSession()
34-
} else if (newState.isRecordingEnabled || newState.isPlayoutEnabled) {
35-
const config = onConfigureNativeAudio ? onConfigureNativeAudio(newState) : getDefaultAppleAudioConfigurationForAudioState(newState);
36-
log.info("AudioSession configuring category:", config.audioCategory)
37-
await AudioSession.setAppleAudioConfiguration(config)
38-
if (!oldState.isPlayoutEnabled && !oldState.isRecordingEnabled) {
39-
log.info("AudioSession activating...")
40-
await AudioSession.startAudioSession()
41-
}
39+
useEffect(() => {
40+
let recalculateTrackCounts = () => {
41+
setLocalTrackCount(getLocalAudioTrackCount(room));
42+
setRemoteTrackCount(getRemoteAudioTrackCount(room));
43+
};
44+
45+
recalculateTrackCounts();
46+
47+
room.on(RoomEvent.Connected, recalculateTrackCounts);
48+
49+
return () => {
50+
room.off(RoomEvent.Connected, recalculateTrackCounts);
51+
};
52+
}, [room]);
53+
useEffect(() => {
54+
if (Platform.OS !== 'ios') {
55+
return () => {};
4256
}
43-
};
4457

45-
const handleEngineStateUpdate = async ({ isPlayoutEnabled, isRecordingEnabled }: { isPlayoutEnabled: boolean, isRecordingEnabled: boolean }) => {
46-
const oldState = audioEngineState;
47-
const newState = {
48-
isPlayoutEnabled,
49-
isRecordingEnabled,
50-
preferSpeakerOutput: audioEngineState.preferSpeakerOutput,
58+
let onLocalPublished = (publication: LocalTrackPublication) => {
59+
if (publication.kind === 'audio') {
60+
setLocalTrackCount(localTrackCount + 1);
61+
}
62+
};
63+
let onLocalUnpublished = (publication: LocalTrackPublication) => {
64+
if (publication.kind === 'audio') {
65+
if (localTrackCount - 1 < 0) {
66+
log.warn(
67+
'mismatched local audio track count! attempted to reduce track count below zero.'
68+
);
69+
}
70+
setLocalTrackCount(Math.max(localTrackCount - 1, 0));
71+
}
72+
};
73+
let onRemotePublished = (publication: RemoteTrackPublication) => {
74+
if (publication.kind === 'audio') {
75+
setRemoteTrackCount(remoteTrackCount + 1);
76+
}
5177
};
78+
let onRemoteUnpublished = (publication: RemoteTrackPublication) => {
79+
if (publication.kind === 'audio') {
80+
if (remoteTrackCount - 1 < 0) {
81+
log.warn(
82+
'mismatched remote audio track count! attempted to reduce track count below zero.'
83+
);
84+
}
85+
setRemoteTrackCount(Math.max(remoteTrackCount - 1, 0));
86+
}
87+
};
88+
89+
room
90+
.on(RoomEvent.LocalTrackPublished, onLocalPublished)
91+
.on(RoomEvent.LocalTrackUnpublished, onLocalUnpublished)
92+
.on(RoomEvent.TrackPublished, onRemotePublished)
93+
.on(RoomEvent.TrackUnpublished, onRemoteUnpublished);
5294

53-
// If this throws, the audio engine will not continue it's operation
54-
await tryConfigure(newState, oldState);
55-
// Update the audio state only if configure succeeds
56-
audioEngineState = newState;
57-
};
95+
return () => {
96+
room
97+
.off(RoomEvent.LocalTrackPublished, onLocalPublished)
98+
.off(RoomEvent.LocalTrackUnpublished, onLocalUnpublished)
99+
.off(RoomEvent.TrackPublished, onRemotePublished)
100+
.off(RoomEvent.TrackUnpublished, onRemoteUnpublished);
101+
};
102+
}, [room, localTrackCount, remoteTrackCount]);
103+
104+
useEffect(() => {
105+
if (Platform.OS !== 'ios') {
106+
return;
107+
}
58108

59-
// Attach audio engine events
60-
audioDeviceModuleEvents.setWillEnableEngineHandler(handleEngineStateUpdate);
61-
audioDeviceModuleEvents.setDidDisableEngineHandler(handleEngineStateUpdate);
109+
let configFunc =
110+
onConfigureNativeAudio ?? getDefaultAppleAudioConfigurationForMode;
111+
let audioConfig = configFunc(trackState, preferSpeakerOutput);
112+
AudioSession.setAppleAudioConfiguration(audioConfig);
113+
}, [trackState, onConfigureNativeAudio, preferSpeakerOutput]);
62114
}
63115

64-
function getDefaultAppleAudioConfigurationForAudioState(
65-
configurationState: AudioEngineConfigurationState,
66-
): AppleAudioConfiguration {
67-
if (configurationState.isRecordingEnabled) {
68-
return {
69-
audioCategory: 'playAndRecord',
70-
audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
71-
audioMode: configurationState.preferSpeakerOutput ? 'videoChat' : 'voiceChat',
72-
};
73-
} else if (configurationState.isPlayoutEnabled) {
74-
return {
75-
audioCategory: 'playback',
76-
audioCategoryOptions: ['mixWithOthers'],
77-
audioMode: 'spokenAudio',
78-
};
116+
function computeAudioTrackState(
117+
localTracks: number,
118+
remoteTracks: number
119+
): AudioTrackState {
120+
if (localTracks > 0 && remoteTracks > 0) {
121+
return 'localAndRemote';
122+
} else if (localTracks > 0 && remoteTracks === 0) {
123+
return 'localOnly';
124+
} else if (localTracks === 0 && remoteTracks > 0) {
125+
return 'remoteOnly';
126+
} else {
127+
return 'none';
79128
}
129+
}
130+
131+
function getLocalAudioTrackCount(room: Room): number {
132+
return room.localParticipant.audioTrackPublications.size;
133+
}
80134

81-
return {
82-
audioCategory: 'soloAmbient',
83-
audioCategoryOptions: [],
84-
audioMode: 'default',
85-
};
135+
function getRemoteAudioTrackCount(room: Room): number {
136+
var audioTracks = 0;
137+
room.remoteParticipants.forEach((participant) => {
138+
audioTracks += participant.audioTrackPublications.size;
139+
});
140+
return audioTracks;
86141
}

src/audio/AudioSession.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,37 @@ export type AppleAudioConfiguration = {
197197
audioMode?: AppleAudioMode;
198198
};
199199

200+
export type AudioTrackState =
201+
| 'none'
202+
| 'remoteOnly'
203+
| 'localOnly'
204+
| 'localAndRemote';
205+
206+
export function getDefaultAppleAudioConfigurationForMode(
207+
mode: AudioTrackState,
208+
preferSpeakerOutput: boolean = true
209+
): AppleAudioConfiguration {
210+
if (mode === 'remoteOnly') {
211+
return {
212+
audioCategory: 'playback',
213+
audioCategoryOptions: ['mixWithOthers'],
214+
audioMode: 'spokenAudio',
215+
};
216+
} else if (mode === 'localAndRemote' || mode === 'localOnly') {
217+
return {
218+
audioCategory: 'playAndRecord',
219+
audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
220+
audioMode: preferSpeakerOutput ? 'videoChat' : 'voiceChat',
221+
};
222+
}
223+
224+
return {
225+
audioCategory: 'soloAmbient',
226+
audioCategoryOptions: [],
227+
audioMode: 'default',
228+
};
229+
}
230+
200231
export default class AudioSession {
201232
/**
202233
* Applies the provided audio configuration to the underlying AudioSession.

0 commit comments

Comments
 (0)