From e0cabbc51477fe25fa37f5728eec090ff8caa407 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Oct 2023 18:22:56 +0100 Subject: [PATCH 1/2] Switch capture devices if the default device changes This is a bit of a hack, but is the only way I can see that we can update to using the new default device when the OS-level default changes. Hopefully the comments explain everything. --- src/livekit/useLiveKit.ts | 51 ++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index d6d8394b..f92047c1 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -20,6 +20,7 @@ import { ExternalE2EEKeyProvider, Room, RoomOptions, + Track, } from "livekit-client"; import { useLiveKitRoom } from "@livekit/components-react"; import { useEffect, useMemo, useRef, useState } from "react"; @@ -158,12 +159,50 @@ export function useLiveKit( if (room !== undefined && connectionState === ConnectionState.Connected) { const syncDevice = (kind: MediaDeviceKind, device: MediaDevice) => { const id = device.selectedId; - if (id !== undefined && room.getActiveDevice(kind) !== id) { - room - .switchActiveDevice(kind, id) - .catch((e) => - logger.error(`Failed to sync ${kind} device with LiveKit`, e) - ); + + // Detect if we're trying to use chrome's default device, in which case + // we need to to see if the default device has changed to a different device + // by comparing the group ID of the device we're using against the group ID + // of what the default device is *now*. + if ( + id === "default" && + kind === "audioinput" && + room.options.audioCaptureDefaults?.deviceId === "default" + ) { + const activeMicTrack = Array.from( + room.localParticipant.audioTracks.values() + ).find((d) => d.source === Track.Source.Microphone)?.track; + + const defaultDevice = device.available.find( + (d) => d.deviceId === "default" + ); + if ( + defaultDevice && + activeMicTrack && + // only restart if the stream is still running: LiveKit will detect + // when a track stops & restart appropriately, so this is not our job. + // Plus, we need to avoid restarting again if the track is already in + // the process of being restarted. + activeMicTrack.mediaStreamTrack.readyState !== "ended" && + defaultDevice.groupId !== + activeMicTrack.mediaStreamTrack.getSettings().groupId + ) { + // It's different, so restart the track, ie. cause Livekit to do another + // getUserMedia() call with deviceId: default to get the *new* default device. + // Note that room.switchActiveDevice() won't work: Livekit will ignore it because + // the deviceId hasn't changed (was & still is default). + room.localParticipant + .getTrack(Track.Source.Microphone) + ?.audioTrack?.restartTrack(); + } + } else { + if (id !== undefined && room.getActiveDevice(kind) !== id) { + room + .switchActiveDevice(kind, id) + .catch((e) => + logger.error(`Failed to sync ${kind} device with LiveKit`, e) + ); + } } }; From c37b2924afbd36d3ded5339a4317b81238bab400 Mon Sep 17 00:00:00 2001 From: David Baker Date: Tue, 3 Oct 2023 18:27:10 +0100 Subject: [PATCH 2/2] Comment --- src/livekit/useLiveKit.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/livekit/useLiveKit.ts b/src/livekit/useLiveKit.ts index f92047c1..bf12669c 100644 --- a/src/livekit/useLiveKit.ts +++ b/src/livekit/useLiveKit.ts @@ -164,6 +164,10 @@ export function useLiveKit( // we need to to see if the default device has changed to a different device // by comparing the group ID of the device we're using against the group ID // of what the default device is *now*. + // This is special-cased for only audio inputs because we need to dig around + // in the LocalParticipant object for the track object and there's not a nice + // way to do that generically. There is usually no OS-level default video capture + // device anyway, and audio outputs work differently. if ( id === "default" && kind === "audioinput" &&