Conversation
When a local track's underlying MediaStreamTrack ends unexpectedly (e.g. a device is unplugged), LocalParticipant.handleTrackEnded restarts it against a different device. Audio already forced deviceId: 'default' for this case, but video called restartTrack() with no options, which reuses the previous constraints, including the deviceId of the device that just disappeared. getUserMedia then fails again with the same error and the track is muted instead of falling back to another camera. This applies the same deviceId: 'default' fallback to video, and adds a regression test. Fixes livekit#2035
hardope
requested review from
1egoman,
lukasIO and
xianshijing-lk
as code owners
September 14, 2026 00:03
🦋 Changeset detectedLatest commit: 2fe72b1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Falling back to deviceId: 'default' for video replaced the track's entire constraints object, dropping resolution, frame rate, and facing mode, and persisting that reduced object for future restarts. Spread the track's existing constraints and only override deviceId. Audio fallback is unchanged; it already drops non-deviceId constraints on restart, which is existing behavior outside the scope of this fix.
1egoman
requested changes
Sep 14, 2026
Comment on lines
+2142
to
+2149
| // fall back to default device if available | ||
| if (isLocalVideoTrack(track)) { | ||
| // keep the previously requested resolution/frame rate/facing mode, | ||
| // only swap out the (now gone) deviceId | ||
| await track.restartTrack({ | ||
| ...track.constraints, | ||
| deviceId: 'default', | ||
| } as VideoCaptureOptions); |
Contributor
There was a problem hiding this comment.
I will defer to @lukasIO and @davidzhao here (as I am not as familiar with the historical context), but my understanding is the decision was purposefully made to NOT switch back to video in the same way as audio, because it could startle a user and cause a camera they didn't expect to be live to suddenly be published.
Because of this, I'm not sure that this change makes sense in its current state.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
LocalParticipant.handleTrackEnded already falls back to the default microphone
(deviceId: 'default') when an audio track's underlying MediaStreamTrack ends
unexpectedly (e.g. the device is unplugged). The video branch instead called
restartTrack() with no options, which reuses the previous constraints,
including the deviceId of the device that just disappeared. getUserMedia then
fails again with the same error and the track is muted instead of switching
to another camera.
This applies the same deviceId: 'default' fallback to video, so camera swaps
behave the same as microphone swaps.
Fixes #2035
Test plan:
is called with { deviceId: 'default' } for both video and audio tracks on
handleTrackEnded. Verified it fails without the fix.
smoke-tests due to a missing separate install, confirmed present on
main before this change)