Summary
Unplugging (or otherwise losing) the default microphone while the mic is enabled aborts the whole process with:
thread 'main' panicked at 'Encountered an error in system `comms::livekit::mic::publish_tracks`:
Parameter `Res<'_, MicrophoneDevice>` failed validation: Resource does not exist
Hit live on macOS by physically unplugging a mic mid-run. Affects the desktop client (a mic unplug crashes the app) and is worse on the headless authoritative server, where an abort takes every co-tenant scene down with it.
Mechanism
All the mic systems are .chain()ed in Update (crates/comms/src/livekit/mic.rs:75-95), in this order:
verify_microphone_device_health — run_if(in_state(MicrophoneAvailability::Available))
- ...
publish_tracks — run_if(in_state(MicrophoneAvailability::Available).and(in_state(MicrophonePermission::Granted)))
When the device disappears, verify_microphone_device_health (:190-201) does both of:
commands.set_state(MicrophoneAvailability::Unavailable);
commands.remove_resource::<MicrophoneDevice>();
These two land at different times:
- the resource removal is a command, applied at the sync point
.chain() inserts — i.e. before publish_tracks runs, in the same frame;
- the state transition only takes effect when the
StateTransition schedule runs, i.e. at the next frame boundary.
So for exactly one frame the resource is gone while in_state(Available) still evaluates true. publish_tracks is scheduled, its Res<MicrophoneDevice> fails param validation, and the process aborts. Note the query filters inside publish_tracks are irrelevant — param validation happens before the system body, so an empty participant query does not save it.
This should reproduce every time the device is lost while MicrophoneState::Enabled + MicrophonePermission::Granted, not intermittently.
Suggested fix
Minimal and ordering-independent — take the resource optionally and bail:
fn publish_tracks(
// ...
microphone_device: Option<Res<MicrophoneDevice>>,
) {
let Some(microphone_device) = microphone_device else {
return;
};
That removes the assumption that state and resource lifetime stay in lockstep, rather than trying to make the two commands land in the same frame.
Related
The headless server has no use for a microphone at all. HEADLESS_TOWEROFMADNESS_TEST.md §2b.5 ("audio gate") already called for gating mic/kira behind a resource so the server does not pull in cpal/kira; that never landed. Doing so would make this unreachable server-side, but the client fix above is still wanted on its own.
Summary
Unplugging (or otherwise losing) the default microphone while the mic is enabled aborts the whole process with:
Hit live on macOS by physically unplugging a mic mid-run. Affects the desktop client (a mic unplug crashes the app) and is worse on the headless authoritative server, where an abort takes every co-tenant scene down with it.
Mechanism
All the mic systems are
.chain()ed inUpdate(crates/comms/src/livekit/mic.rs:75-95), in this order:verify_microphone_device_health—run_if(in_state(MicrophoneAvailability::Available))publish_tracks—run_if(in_state(MicrophoneAvailability::Available).and(in_state(MicrophonePermission::Granted)))When the device disappears,
verify_microphone_device_health(:190-201) does both of:These two land at different times:
.chain()inserts — i.e. beforepublish_tracksruns, in the same frame;StateTransitionschedule runs, i.e. at the next frame boundary.So for exactly one frame the resource is gone while
in_state(Available)still evaluates true.publish_tracksis scheduled, itsRes<MicrophoneDevice>fails param validation, and the process aborts. Note the query filters insidepublish_tracksare irrelevant — param validation happens before the system body, so an empty participant query does not save it.This should reproduce every time the device is lost while
MicrophoneState::Enabled+MicrophonePermission::Granted, not intermittently.Suggested fix
Minimal and ordering-independent — take the resource optionally and bail:
That removes the assumption that state and resource lifetime stay in lockstep, rather than trying to make the two commands land in the same frame.
Related
The headless server has no use for a microphone at all.
HEADLESS_TOWEROFMADNESS_TEST.md§2b.5 ("audio gate") already called for gating mic/kira behind a resource so the server does not pull in cpal/kira; that never landed. Doing so would make this unreachable server-side, but the client fix above is still wanted on its own.