Skip to content

Commit 8c98afb

Browse files
committed
backward compatible
1 parent 388b144 commit 8c98afb

5 files changed

Lines changed: 159 additions & 25 deletions

File tree

example/index.js

Lines changed: 2 additions & 2 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, setupIOSAudioManagement } from '@livekit/react-native';
55
import { LogLevel } from 'livekit-client';
66
import { setupErrorLogHandler } from './src/utils/ErrorLogHandler';
77
import { setupCallService } from './src/callservice/CallService';
@@ -17,4 +17,4 @@ setupCallService();
1717
registerGlobals();
1818
AppRegistry.registerComponent(appName, () => App);
1919

20-
useIOSAudioManagement();
20+
setupIOSAudioManagement();

src/audio/AudioManager.ts

Lines changed: 56 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Platform } from 'react-native';
12
import AudioSession, {
23
type AppleAudioConfiguration,
34
} from './AudioSession';
@@ -10,65 +11,97 @@ export type AudioEngineConfigurationState = {
1011
preferSpeakerOutput: boolean;
1112
};
1213

14+
type CleanupFn = () => void;
15+
1316
/**
14-
* Handles setting the appropriate AVAudioSession options automatically
15-
* depending on the audio track states of the Room.
17+
* Sets up automatic iOS audio session management based on audio engine state.
18+
*
19+
* Call this once at app startup (e.g. in index.js). For usage inside React
20+
* components, use {@link useIOSAudioManagement} instead.
1621
*
17-
* @param preferSpeakerOutput
18-
* @param onConfigureNativeAudio A custom method for determining options used.
22+
* @param preferSpeakerOutput - Whether to prefer speaker output. Defaults to true.
23+
* @param onConfigureNativeAudio - Optional custom callback for determining audio configuration.
24+
* @returns A cleanup function that removes the event handlers.
1925
*/
20-
export function useIOSAudioManagement(
26+
export function setupIOSAudioManagement(
2127
preferSpeakerOutput = true,
22-
onConfigureNativeAudio?: (configurationState: AudioEngineConfigurationState) => AppleAudioConfiguration
23-
) {
28+
onConfigureNativeAudio?: (
29+
configurationState: AudioEngineConfigurationState
30+
) => AppleAudioConfiguration
31+
): CleanupFn {
32+
if (Platform.OS !== 'ios') {
33+
return () => {};
34+
}
35+
2436
let audioEngineState: AudioEngineConfigurationState = {
2537
isPlayoutEnabled: false,
2638
isRecordingEnabled: false,
27-
preferSpeakerOutput: preferSpeakerOutput,
39+
preferSpeakerOutput,
2840
};
2941

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()
42+
const tryConfigure = async (
43+
newState: AudioEngineConfigurationState,
44+
oldState: AudioEngineConfigurationState
45+
) => {
46+
if (
47+
!newState.isPlayoutEnabled &&
48+
!newState.isRecordingEnabled &&
49+
(oldState.isPlayoutEnabled || oldState.isRecordingEnabled)
50+
) {
51+
log.info('AudioSession deactivating...');
52+
await AudioSession.stopAudioSession();
3453
} 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)
54+
const config = onConfigureNativeAudio
55+
? onConfigureNativeAudio(newState)
56+
: getDefaultAppleAudioConfigurationForAudioState(newState);
57+
log.info('AudioSession configuring category:', config.audioCategory);
58+
await AudioSession.setAppleAudioConfiguration(config);
3859
if (!oldState.isPlayoutEnabled && !oldState.isRecordingEnabled) {
39-
log.info("AudioSession activating...")
40-
await AudioSession.startAudioSession()
60+
log.info('AudioSession activating...');
61+
await AudioSession.startAudioSession();
4162
}
4263
}
4364
};
4465

45-
const handleEngineStateUpdate = async ({ isPlayoutEnabled, isRecordingEnabled }: { isPlayoutEnabled: boolean, isRecordingEnabled: boolean }) => {
66+
const handleEngineStateUpdate = async ({
67+
isPlayoutEnabled,
68+
isRecordingEnabled,
69+
}: {
70+
isPlayoutEnabled: boolean;
71+
isRecordingEnabled: boolean;
72+
}) => {
4673
const oldState = audioEngineState;
47-
const newState = {
74+
const newState: AudioEngineConfigurationState = {
4875
isPlayoutEnabled,
4976
isRecordingEnabled,
5077
preferSpeakerOutput: audioEngineState.preferSpeakerOutput,
5178
};
5279

53-
// If this throws, the audio engine will not continue it's operation
80+
// If this throws, the audio engine will not continue its operation
5481
await tryConfigure(newState, oldState);
5582
// Update the audio state only if configure succeeds
5683
audioEngineState = newState;
5784
};
5885

59-
// Attach audio engine events
6086
audioDeviceModuleEvents.setWillEnableEngineHandler(handleEngineStateUpdate);
6187
audioDeviceModuleEvents.setDidDisableEngineHandler(handleEngineStateUpdate);
88+
89+
return () => {
90+
audioDeviceModuleEvents.setWillEnableEngineHandler(null);
91+
audioDeviceModuleEvents.setDidDisableEngineHandler(null);
92+
};
6293
}
6394

6495
function getDefaultAppleAudioConfigurationForAudioState(
65-
configurationState: AudioEngineConfigurationState,
96+
configurationState: AudioEngineConfigurationState
6697
): AppleAudioConfiguration {
6798
if (configurationState.isRecordingEnabled) {
6899
return {
69100
audioCategory: 'playAndRecord',
70101
audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
71-
audioMode: configurationState.preferSpeakerOutput ? 'videoChat' : 'voiceChat',
102+
audioMode: configurationState.preferSpeakerOutput
103+
? 'videoChat'
104+
: 'voiceChat',
72105
};
73106
} else if (configurationState.isPlayoutEnabled) {
74107
return {

src/audio/AudioManagerLegacy.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* Backward-compatible wrappers for the legacy AudioManager API.
3+
*
4+
* These exports preserve the old `useIOSAudioManagement(room, ...)` signature
5+
* and the removed `getDefaultAppleAudioConfigurationForMode` function so that
6+
* existing consumers continue to compile without changes.
7+
*
8+
* New code should use `setupIOSAudioManagement` from `./AudioManager` instead.
9+
*/
10+
import { useEffect } from 'react';
11+
import type { Room } from 'livekit-client';
12+
import type { AppleAudioConfiguration, AudioTrackState } from './AudioSession';
13+
import {
14+
setupIOSAudioManagement,
15+
type AudioEngineConfigurationState,
16+
} from './AudioManager';
17+
18+
/**
19+
* @deprecated Use {@link setupIOSAudioManagement} instead.
20+
* The `room` parameter is ignored — audio session is now managed
21+
* via audio engine events, not room track counts.
22+
*/
23+
export function useIOSAudioManagement(
24+
room: Room,
25+
preferSpeakerOutput: boolean = true,
26+
onConfigureNativeAudio?: (
27+
trackState: AudioTrackState,
28+
preferSpeakerOutput: boolean
29+
) => AppleAudioConfiguration
30+
) {
31+
useEffect(() => {
32+
let wrappedOnConfig:
33+
| ((state: AudioEngineConfigurationState) => AppleAudioConfiguration)
34+
| undefined;
35+
36+
if (onConfigureNativeAudio) {
37+
const legacyCb = onConfigureNativeAudio;
38+
wrappedOnConfig = (state: AudioEngineConfigurationState) =>
39+
legacyCb(engineStateToTrackState(state), state.preferSpeakerOutput);
40+
}
41+
42+
const cleanup = setupIOSAudioManagement(
43+
preferSpeakerOutput,
44+
wrappedOnConfig
45+
);
46+
return cleanup;
47+
}, [preferSpeakerOutput, onConfigureNativeAudio]);
48+
}
49+
50+
/**
51+
* @deprecated Use the default behavior of `setupIOSAudioManagement` instead.
52+
*/
53+
export function getDefaultAppleAudioConfigurationForMode(
54+
mode: AudioTrackState,
55+
preferSpeakerOutput: boolean = true
56+
): AppleAudioConfiguration {
57+
if (mode === 'remoteOnly') {
58+
return {
59+
audioCategory: 'playback',
60+
audioCategoryOptions: ['mixWithOthers'],
61+
audioMode: 'spokenAudio',
62+
};
63+
} else if (mode === 'localAndRemote' || mode === 'localOnly') {
64+
return {
65+
audioCategory: 'playAndRecord',
66+
audioCategoryOptions: ['allowBluetooth', 'mixWithOthers'],
67+
audioMode: preferSpeakerOutput ? 'videoChat' : 'voiceChat',
68+
};
69+
}
70+
71+
return {
72+
audioCategory: 'soloAmbient',
73+
audioCategoryOptions: [],
74+
audioMode: 'default',
75+
};
76+
}
77+
78+
function engineStateToTrackState(
79+
state: AudioEngineConfigurationState
80+
): AudioTrackState {
81+
if (state.isRecordingEnabled && state.isPlayoutEnabled) {
82+
return 'localAndRemote';
83+
} else if (state.isRecordingEnabled) {
84+
return 'localOnly';
85+
} else if (state.isPlayoutEnabled) {
86+
return 'remoteOnly';
87+
}
88+
return 'none';
89+
}

src/audio/AudioSession.ts

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

200+
/**
201+
* @deprecated Use `AudioEngineConfigurationState` from `AudioManager` instead.
202+
*/
203+
export type AudioTrackState =
204+
| 'none'
205+
| 'remoteOnly'
206+
| 'localOnly'
207+
| 'localAndRemote';
208+
200209
export default class AudioSession {
201210
/**
202211
* Applies the provided audio configuration to the underlying AudioSession.

src/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import AudioSession, {
1717
type AppleAudioCategoryOption,
1818
type AppleAudioConfiguration,
1919
type AppleAudioMode,
20+
type AudioTrackState,
2021
} from './audio/AudioSession';
2122
import type { AudioConfiguration } from './audio/AudioSession';
2223
import { PixelRatio, Platform } from 'react-native';
@@ -165,6 +166,7 @@ export * from './useParticipant'; // deprecated
165166
export * from './useRoom'; // deprecated
166167
export * from './logger';
167168
export * from './audio/AudioManager';
169+
export * from './audio/AudioManagerLegacy';
168170

169171
export {
170172
AudioSession,
@@ -183,6 +185,7 @@ export type {
183185
AppleAudioCategoryOption,
184186
AppleAudioConfiguration,
185187
AppleAudioMode,
188+
AudioTrackState,
186189
LogLevel,
187190
SetLogLevelOptions,
188191
RNKeyProviderOptions,

0 commit comments

Comments
 (0)