Skip to content

fix: fall back to default camera on TrackEnded, matching audio behavior - #2098

Open
hardope wants to merge 2 commits into
livekit:mainfrom
hardope:fix/video-track-ended-device-fallback
Open

hardope wants to merge 2 commits into
livekit:mainfrom
hardope:fix/video-track-ended-device-fallback

Conversation

@hardope

@hardope hardope commented Sep 14, 2026

Copy link
Copy Markdown

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:

  • Added a regression test in LocalParticipant.test.ts asserting restartTrack
    is called with { deviceId: 'default' } for both video and audio tracks on
    handleTrackEnded. Verified it fails without the fix.
  • npx tsc --noEmit
  • npx eslint src
  • npx vitest run (821 passed, 1 pre-existing unrelated failure in
    smoke-tests due to a missing separate install, confirmed present on
    main before this change)

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
@changeset-bot

changeset-bot Bot commented Sep 14, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2fe72b1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
livekit-client Patch

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

@CLAassistant

CLAassistant commented Sep 14, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

devin-ai-integration[bot]

This comment was marked as resolved.

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.
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Video track does not fall back to default device on TrackEnded, unlike audio

3 participants