Skip to content

LiveKit audio session stops working when using expo-audio to play sounds during an ongoing LiveKit meeting #286

@tirth0

Description

@tirth0

Describe the bug

When using expo-audio to play sounds during an ongoing LiveKit meeting (via @livekit/react-native), the LiveKit audio session stops working. After a sound is played, microphone input is no longer captured — no voice is picked up in the meeting. I'm not sure if this is even supposed to work. If not, are there any workarounds to this?

To Reproduce

Steps to reproduce the behavior:

  1. Join a LiveKit room using @livekit/react-native.
  2. Configure audio session with AudioSession.startAudioSession() and useIOSAudioManagement.
  3. Use expo-audio’s useAudioPlayer to play any sound (e.g., a short notification sound).
  4. After playback, the LiveKit audio session stops and voice input is no longer transmitted.

Expected behavior

Playing notification sounds with expo-audio should not interfere with or stop the LiveKit audio session. Microphone input should continue to function normally during and after playback.

Screenshots / Logs

N/A

Device Info

  • Device: IPhone XR
  • OS: IOS 18.6

Dependencies Info

  • @livekit/react-native: 2.9.1
  • livekit-client: 2.15.5
  • react-native-webrtc: 137.0.1
  • expo-audio: 0.4.9
  • react-native: 0.79.5
  • expo: 53.0.15

Additional context

Relevant code:

// Configure LiveKit AudioSession
useEffect(() => {
  const start = async () => {
    AudioSession.configureAudio({
      ios: { defaultOutput: "speaker" },
    })
    await AudioSession.startAudioSession()
  }
  start()
  return () => {
    AudioSession.stopAudioSession()
  }
}, [])

useIOSAudioManagement(room, true, (): AppleAudioConfiguration => {
  return {
    audioCategory: "playAndRecord",
    audioCategoryOptions: [
      "interruptSpokenAudioAndMixWithOthers",
      "allowAirPlay",
      "allowBluetooth",
      "allowBluetoothA2DP",
      "defaultToSpeaker",
    ],
    audioMode: "videoChat",
  }
})

// Expo Audio player hook
export function useSoundPlayer() {
  const chat = useAudioPlayer(SOUND_MAP.CHAT)
  const knock = useAudioPlayer(SOUND_MAP.KNOCK)
  const raiseHand = useAudioPlayer(SOUND_MAP.RAISE_HAND)
  const meetJoin = useAudioPlayer(SOUND_MAP.MEET_JOIN)
  const meetLeave = useAudioPlayer(SOUND_MAP.MEET_LEAVE)
  const meetEnd = useAudioPlayer(SOUND_MAP.MEET_END)
  const recStart = useAudioPlayer(SOUND_MAP.RECORDING_START)
  const recEnd = useAudioPlayer(SOUND_MAP.RECORDING_END)

  const play = (key: SoundKey) => {
    const map = {
      CHAT: chat,
      KNOCK: knock,
      RAISE_HAND: raiseHand,
      MEET_JOIN: meetJoin,
      MEET_LEAVE: meetLeave,
      MEET_END: meetEnd,
      RECORDING_START: recStart,
      RECORDING_END: recEnd,
    }
    const player = map[key]
    player.seekTo(0)
    player.play()
  }

  return { play }
}

// in app.tsx
  useEffect(() => {
    const prepare = async () => {

      await configureAudioForSpeaker()

      await SplashScreen.hideAsync()
    }

    prepare()
, [])

async function configureAudioForSpeaker() {
  await setAudioModeAsync({
    playsInSilentMode: true,
    shouldPlayInBackground: false,
    interruptionModeAndroid: "duckOthers",
    interruptionMode: "mixWithOthers",
    allowsRecording: true, // tried with both true and false
  })
}

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions