Skip to content

fix(react): forward track to setupDeviceSelector in useMediaDeviceSelect#1330

Open
nikhilgupta58 wants to merge 2 commits into
livekit:mainfrom
nikhilgupta58:fix/usemediadeviceselect-forward-track-1315
Open

fix(react): forward track to setupDeviceSelector in useMediaDeviceSelect#1330
nikhilgupta58 wants to merge 2 commits into
livekit:mainfrom
nikhilgupta58:fix/usemediadeviceselect-forward-track-1315

Conversation

@nikhilgupta58
Copy link
Copy Markdown

@nikhilgupta58 nikhilgupta58 commented May 20, 2026

Closes #1315

Root cause

useMediaDeviceSelect keeps track in its useMemo dependency array but never forwards it to setupDeviceSelector.

packages/react/src/hooks/useMediaDeviceSelect.ts (line 62-65):

const { className, activeDeviceObservable, setActiveMediaDevice } = React.useMemo(
  () => setupDeviceSelector(kind, roomFallback), // track is a dep but never passed
  [kind, roomFallback, track],
);

packages/core/src/components/mediaDeviceSelect.ts defines a 3-arg signature and branches on the track:

export function setupDeviceSelector(
  kind: MediaDeviceKind,
  room: Room,
  localTrack?: LocalAudioTrack | LocalVideoTrack,
) {
  ...
  const setActiveMediaDevice = async (id, options = {}) => {
    if (localTrack) {
      await localTrack.setDeviceId(...);   // preview / pre-join path
    } else if (room) {
      await room.switchActiveDevice(...);  // no-ops on the dummy `new Room()`
    }
  };
}

In pre-join / preview UIs there is no connected room, so roomFallback is a dummy new Room(). Because localTrack is undefined, setActiveMediaDevice takes the room.switchActiveDevice branch, which does nothing on that dummy room. The result is that setActiveMediaDevice silently fails to switch the mic or camera before joining.

The fix

-    () => setupDeviceSelector(kind, roomFallback),
+    () => setupDeviceSelector(kind, roomFallback, track),
     [kind, roomFallback, track],

track is already in the useMemo deps, so no dependency-array change is needed. This is purely additive forwarding of an already-tracked value.

Proof (verify-first)

I reproduced the bug with a deterministic test BEFORE writing the fix.

The test renders useMediaDeviceSelect({ kind: 'audioinput', track }) in preview mode (no room, no RoomContext, so roomFallback is a fresh new Room()) with a spy track.setDeviceId, calls setActiveMediaDevice('mic-2'), and asserts the track was switched. It keeps the real setupDeviceSelector (only the device enumeration and logging are mocked so jsdom does not touch navigator.mediaDevices).

RED on current code (packages/react):

 × forwards the local track so setActiveMediaDevice switches the device
 FAIL src/hooks/useMediaDeviceSelect.test.ts > useMediaDeviceSelect in preview mode > forwards the local track so setActiveMediaDevice switches the device
 AssertionError: expected "vi.fn()" to be called with arguments: [ 'mic-2' ]
 Number of calls: 0
   45|     expect(setDeviceId).toHaveBeenCalledWith('mic-2');
 Test Files  1 failed (1)
      Tests  1 failed (1)

GREEN after the fix:

 ✓ src/hooks/useMediaDeviceSelect.test.ts (1 test)
 Test Files  1 passed (1)
      Tests  1 passed (1)

Full suites stay green: packages/react 8 passed, packages/core 98 passed. pnpm build (all packages), lint, and format check are clean.

Risk

None expected. The change only forwards a value that was already a memo dependency, and the preview-track code path in setupDeviceSelector already existed and was simply unreachable from this hook.

Out of scope

While testing I noticed a separate UI-feedback quirk: setupDeviceSelector updates an internal activeDeviceSubject but returns the room-event-based activeDeviceObservable, so the active-item highlight does not reflect a preview-mode switch even after this fix. That is a distinct issue from #1315 (which is about the switch itself not happening) and is not addressed here.

nikhilgupta58 and others added 2 commits May 20, 2026 09:51
Add a deterministic test that renders useMediaDeviceSelect in preview
mode (no connected room) with a local preview track and asserts that
setActiveMediaDevice forwards the switch to the track. This fails on
the current code because the track is never passed to setupDeviceSelector.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ect (closes livekit#1315)

useMediaDeviceSelect listed track in the useMemo deps but called
setupDeviceSelector(kind, roomFallback) with only two arguments. Without
the localTrack, setupDeviceSelector falls back to room.switchActiveDevice,
which no-ops on the dummy Room used in preview / pre-join mode. As a result
setActiveMediaDevice silently failed to switch the mic or camera before
joining a room.

Pass the already-tracked track as the third argument so the preview track's
setDeviceId is used in preview mode.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 20, 2026

🦋 Changeset detected

Latest commit: bb622ae

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

This PR includes changesets to release 6 packages
Name Type
@livekit/components-react Patch
@agents-ui Patch
@livekit/component-example-next Patch
@livekit/components-js-docs Patch
@livekit/component-docs-storybook Patch
@livekit/components-docs-gen 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

@vercel
Copy link
Copy Markdown

vercel Bot commented May 20, 2026

@nikhilgupta58 is attempting to deploy a commit to the LiveKit Team on Vercel.

A member of the Team first needs to authorize it.

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.

useMediaDeviceSelect doesn't pass track to setupDeviceSelector, making setActiveMediaDevice ineffective in preview mode

1 participant