wonderful-airport-21213
05/07/2026, 8:57 AMtall-airline-16756
05/07/2026, 8:57 AMtall-airline-16756
05/07/2026, 8:58 AMwonderful-airport-21213
05/07/2026, 8:58 AMtall-airline-16756
05/07/2026, 8:58 AMwonderful-airport-21213
05/07/2026, 8:59 AMwonderful-airport-21213
05/07/2026, 8:59 AMtall-airline-16756
05/07/2026, 8:59 AMwonderful-airport-21213
05/07/2026, 9:01 AMtall-airline-16756
05/07/2026, 9:01 AMroom.disconnect() (or equivalent) before rejoining; abrupt process exits can leave server state behind.
2. Verify your server’s session/connection timeouts in the config—shorten them temporarily to see if stale participants are removed faster.
3. Upgrade test: try a non-production upgrade of the server to the latest stable release (in a staging environment) and reproduce the issue. Observe whether participant cleanup behavior changes.
4. If ghosts show track publish 0 but participant listed active, capture:
◦ server logs around the participant ID
◦ the participant identity and SID
◦ timestamps of join/leave/rejoin
If you can paste relevant log excerpts (timestamped connect/disconnect events) or confirm whether room.disconnect() is used, I’ll analyze them and suggest the precise next action or workaround.
, with interactive elementswonderful-airport-21213
05/12/2026, 5:16 AMtall-airline-16756
05/12/2026, 5:17 AMroom.disconnect() (graceful disconnect) before rejoining; abrupt exits can leave state behind.
• Shorten session/connection timeouts in config temporarily to see if stale participants are removed faster.
• Restart the LiveKit server to verify ghosts clear, and reproduce after restart.
• Test upgrading the server in a staging environment to a newer release and reproduce to see if behavior changes.
If you can paste the relevant server log excerpt (timestamps, participant SID/identity, and any disconnect reasons), I’ll analyze it and suggest the precise fix.
Sources: Understanding the STATE_MISMATCH Disconnect Reason | How we built a globally distributed mesh network to scale WebRTC | LiveKit | Firewall Tips | Diagnosing Connection Errors with Connection Test Utility, with interactive elementswonderful-airport-21213
05/13/2026, 5:08 AMtall-airline-16756
05/13/2026, 5:08 AMroom.getAudioDevices(...).
• Switch input by enabling microphone with AudioCaptureOptions(deviceId: ...) or creating an audio track with the deviceId.
• Switch output with room.setDefaultAudioOutputDevice(deviceId).
• Listen for device changes with the deviceChanged event and update UI accordingly.
Example (Flutter, using LiveKit Flutter SDK)
1) Enumerate devices
final audioInputs = await room.getAudioDevices(DeviceType.audioInput);
final audioOutputs = await room.getAudioDevices(DeviceType.audioOutput);
2) Switch audio input (microphone)
// Enable mic with specific device
await room.localParticipant.setMicrophoneEnabled(
true,
options: AudioCaptureOptions(deviceId: 'desired-input-device-id'),
);
// Or create a new audio track from device
final track = await Device.captureAudioTrack(
AudioCaptureOptions(deviceId: 'desired-input-device-id'),
);
await room.localParticipant.publishTrack(track);
3) Switch audio output (speaker/headset)
await room.setDefaultAudioOutputDevice('desired-output-device-id');
4) Listen for device list changes
room.on(RoomEvent.deviceChanged, (event) async {
final inputs = await room.getAudioDevices(DeviceType.audioInput);
final outputs = await room.getAudioDevices(DeviceType.audioOutput);
// update UI lists
});
Platform notes and considerations
• Desktop (Windows/macOS/Linux): full input and output device selection is supported.
• Mobile (iOS/Android): input device selection supported; output routing is typically managed by the OS (speaker, earpiece, Bluetooth). setDefaultAudioOutputDevice may be limited on mobile—use platform routing APIs or let OS handle output switching.
• Always handle permissions: request microphone permission before capturing.
• Prefer calling room.localParticipant.setMicrophoneEnabled(false) before switching, then re-enable with the new device to reduce glitches.
• Update UI when device availability changes; handle failures (device ID not found) by falling back to default device.
• Test on each target platform—device ID formats and behaviors can differ.
If you want, tell me:
• Which platforms you need sample UI for (desktop, Android, iOS), and I’ll provide a small runnable widget example per platform and show permission handling and error checks., with interactive elementswonderful-airport-21213
05/13/2026, 5:10 AMtall-airline-16756
05/13/2026, 5:10 AMgetAudioDevices, setDefaultAudioOutputDevice, AudioCaptureOptions, and setMicrophoneEnabled exist in your SDK and show exact code lines to call them.
Which would you prefer?
, with interactive elementswonderful-airport-21213
05/13/2026, 5:10 AMtall-airline-16756
05/13/2026, 5:11 AMlivekit_client: 2.7.0 line or your pubspec.lock entry) or tell me you want me to assume livekit_client: 2.7.0.
3. Do you prefer a minimal API-only snippet or a small runnable Flutter widget with permission handling and UI?
Reply with those and I’ll send the v2.7.0-specific code.
, with interactive elementswonderful-airport-21213
05/13/2026, 5:11 AMtall-airline-16756
05/13/2026, 5:11 AMfinal inputs = await room.getAudioDevices(DeviceType.audioInput);
final outputs = await room.getAudioDevices(DeviceType.audioOutput);
// MediaDeviceInfo has id and label
2) Switch audio input (microphone)
// Gracefully disable first to reduce glitches
await room.localParticipant.setMicrophoneEnabled(false);
// Enable mic with specific device
await room.localParticipant.setMicrophoneEnabled(
true,
options: AudioCaptureOptions(deviceId: 'device-id'),
);
// Or create & publish a track from device
final track = await Device.captureAudioTrack(
AudioCaptureOptions(deviceId: 'device-id'),
);
await room.localParticipant.publishTrack(track);
3) Switch audio output (speaker/headset) — desktop & limited mobile routing
await room.setDefaultAudioOutputDevice('output-device-id');
4) Listen for device changes
room.on(RoomEvent.deviceChanged, (_) async {
final inputs = await room.getAudioDevices(DeviceType.audioInput);
final outputs = await room.getAudioDevices(DeviceType.audioOutput);
// update UI
});
Platform notes
• Desktop (Windows/macOS): input and output device selection supported.
• Mobile (Android/iOS): input device selection supported. Output routing on mobile is typically managed by the OS (speaker, earpiece, Bluetooth); setDefaultAudioOutputDevice may be limited.
• Always request microphone permission before capturing.
• Prefer disabling mic before switching deviceId, then re-enable.
Runnable Flutter widget (compact)
import 'package:flutter/material.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:permission_handler/permission_handler.dart';
class AudioDeviceSwitcher extends StatefulWidget {
final Room room;
AudioDeviceSwitcher({required this.room});
@override
_AudioDeviceSwitcherState createState() => _AudioDeviceSwitcherState();
}
class _AudioDeviceSwitcherState extends State<AudioDeviceSwitcher> {
List<MediaDeviceInfo> inputs = [];
List<MediaDeviceInfo> outputs = [];
String? selectedInputId;
String? selectedOutputId;
@override
void initState() {
super.initState();
_refreshDevices();
widget.room.on(RoomEvent.deviceChanged, (_) => _refreshDevices());
}
Future<void> _requestPermission() async {
if (await Permission.microphone.request().isGranted) return;
// handle denial if needed
}
Future<void> _refreshDevices() async {
inputs = await widget.room.getAudioDevices(DeviceType.audioInput);
outputs = await widget.room.getAudioDevices(DeviceType.audioOutput);
setState(() {});
}
Future<void> _selectInput(String deviceId) async {
await _requestPermission();
await widget.room.localParticipant.setMicrophoneEnabled(false);
await widget.room.localParticipant.setMicrophoneEnabled(
true,
options: AudioCaptureOptions(deviceId: deviceId),
);
selectedInputId = deviceId;
setState(() {});
}
Future<void> _selectOutput(String deviceId) async {
try {
await widget.room.setDefaultAudioOutputDevice(deviceId);
selectedOutputId = deviceId;
setState(() {});
} catch (e) {
// on mobile this might fail; handle fallback
print('setDefaultAudioOutputDevice failed: $e');
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Inputs'),
for (var d in inputs)
ListTile(
title: Text(d.label ?? d.deviceId),
leading: Radio(
groupValue: selectedInputId,
value: d.deviceId,
onChanged: (v) => _selectInput(d.deviceId),
),
),
SizedBox(height: 12),
Text('Outputs'),
for (var d in outputs)
ListTile(
title: Text(d.label ?? d.deviceId),
leading: Radio(
groupValue: selectedOutputId,
value: d.deviceId,
onChanged: (v) => _selectOutput(d.deviceId),
),
),
ElevatedButton(
onPressed: _refreshDevices,
child: Text('Refresh Devices'),
),
],
);
}
}
If you want, I can:
• Add explicit Windows/macOS permission snippets.
• Provide an example that re-creates the audio track (unpublish old, publish new).
• Produce a full minimal app (main.dart) that demonstrates connecting to a Room and using this widget.
Which of those would you like next?
, with interactive elements