Skip to content

Commit 8ee4582

Browse files
committed
migrate from track counting to event based
1 parent dc668fa commit 8ee4582

File tree

5 files changed

+65
-151
lines changed

5 files changed

+65
-151
lines changed

example/index.js

Lines changed: 3 additions & 1 deletion
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 } from '@livekit/react-native';
4+
import { registerGlobals, setLogLevel, useIOSAudioManagement } 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,3 +16,5 @@ setupCallService();
1616
// Required React-Native setup for app
1717
registerGlobals();
1818
AppRegistry.registerComponent(appName, () => App);
19+
20+
useIOSAudioManagement();

example/src/RoomPage.tsx

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

109-
useIOSAudioManagement(room, true);
110-
111109
// Setup room listeners
112110
useEffect(() => {
113111
room.registerTextStreamHandler('lk.chat', async (reader, participant) => {

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, .allowBluetooth, .allowBluetoothA2DP, .defaultToSpeaker]
32+
config.categoryOptions = [.allowAirPlay, .allowBluetoothHFP, .allowBluetoothA2DP, .defaultToSpeaker]
3333
config.mode = AVAudioSession.Mode.videoChat.rawValue
3434

3535
RTCAudioSessionConfiguration.setWebRTC(config)

src/audio/AudioManager.ts

Lines changed: 61 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,141 +1,86 @@
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';
91
import AudioSession, {
10-
getDefaultAppleAudioConfigurationForMode,
112
type AppleAudioConfiguration,
12-
type AudioTrackState,
133
} from './AudioSession';
144
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+
};
1512

1613
/**
1714
* Handles setting the appropriate AVAudioSession options automatically
1815
* depending on the audio track states of the Room.
1916
*
20-
* @param room
2117
* @param preferSpeakerOutput
2218
* @param onConfigureNativeAudio A custom method for determining options used.
2319
*/
2420
export function useIOSAudioManagement(
25-
room: Room,
26-
preferSpeakerOutput: boolean = true,
27-
onConfigureNativeAudio?: (
28-
trackState: AudioTrackState,
29-
preferSpeakerOutput: boolean
30-
) => AppleAudioConfiguration
21+
preferSpeakerOutput = true,
22+
onConfigureNativeAudio?: (configurationState: AudioEngineConfigurationState) => AppleAudioConfiguration
3123
) {
32-
const [localTrackCount, setLocalTrackCount] = useState(0);
33-
const [remoteTrackCount, setRemoteTrackCount] = useState(0);
34-
const trackState = useMemo(
35-
() => computeAudioTrackState(localTrackCount, remoteTrackCount),
36-
[localTrackCount, remoteTrackCount]
37-
);
38-
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 () => {};
56-
}
24+
let audioEngineState: AudioEngineConfigurationState = {
25+
isPlayoutEnabled: false,
26+
isRecordingEnabled: false,
27+
preferSpeakerOutput: preferSpeakerOutput,
28+
};
5729

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);
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()
7641
}
77-
};
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);
42+
}
43+
};
9444

95-
return () => {
96-
room
97-
.off(RoomEvent.LocalTrackPublished, onLocalPublished)
98-
.off(RoomEvent.LocalTrackUnpublished, onLocalUnpublished)
99-
.off(RoomEvent.TrackPublished, onRemotePublished)
100-
.off(RoomEvent.TrackUnpublished, onRemoteUnpublished);
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,
10151
};
102-
}, [room, localTrackCount, remoteTrackCount]);
10352

104-
useEffect(() => {
105-
if (Platform.OS !== 'ios') {
106-
return;
107-
}
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+
};
10858

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

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';
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+
};
12879
}
129-
}
130-
131-
function getLocalAudioTrackCount(room: Room): number {
132-
return room.localParticipant.audioTrackPublications.size;
133-
}
13480

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

src/audio/AudioSession.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -197,37 +197,6 @@ 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-
231200
export default class AudioSession {
232201
/**
233202
* Applies the provided audio configuration to the underlying AudioSession.

0 commit comments

Comments
 (0)