Skip to content

Commit

Permalink
Improve default device handling (#1056)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasIO authored Jan 13, 2025
1 parent 9f71b4d commit 31a2b04
Show file tree
Hide file tree
Showing 18 changed files with 93 additions and 81 deletions.
6 changes: 6 additions & 0 deletions .changeset/unlucky-emus-float.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@livekit/components-core": minor
"@livekit/components-react": minor
---

Improve default device handling
1 change: 0 additions & 1 deletion .npmrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
dedupe-peer-dependents=true
resolve-peers-from-workspace-root=true
manage-package-manager-versions=true
package-manager-strict-version=true
2 changes: 1 addition & 1 deletion docs/storybook/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"dependencies": {
"@livekit/components-react": "workspace:*",
"@livekit/components-styles": "workspace:*",
"livekit-client": "^2.5.7",
"livekit-client": "catalog:",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
Expand Down
2 changes: 1 addition & 1 deletion examples/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@livekit/components-react": "workspace:*",
"@livekit/components-styles": "workspace:*",
"@livekit/track-processors": "^0.3.2",
"livekit-client": "^2.5.7",
"livekit-client": "catalog:",
"livekit-server-sdk": "^2.6.1",
"next": "^14.2.13",
"react": "^18.2.0",
Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@
"turbo": "^2.1.1",
"typescript": "5.6.2"
},
"dependencies": {
"livekit-client": "^2.5.7"
},
"engines": {
"node": ">=18"
},
Expand Down
4 changes: 2 additions & 2 deletions packages/core/etc/components-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -546,9 +546,9 @@ export function setupDataMessageHandler<T extends string>(room: Room, topic?: T
};

// @public (undocumented)
export function setupDeviceSelector(kind: MediaDeviceKind, room?: Room, localTrack?: LocalAudioTrack | LocalVideoTrack): {
export function setupDeviceSelector(kind: MediaDeviceKind, room: Room, localTrack?: LocalAudioTrack | LocalVideoTrack): {
className: string;
activeDeviceObservable: Observable<string | undefined>;
activeDeviceObservable: Observable<string>;
setActiveMediaDevice: (id: string, options?: SetMediaDeviceOptions) => Promise<void>;
};

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"rxjs": "7.8.1"
},
"peerDependencies": {
"livekit-client": "^2.5.7",
"livekit-client": "catalog:",
"tslib": "^2.6.2"
},
"devDependencies": {
Expand Down
25 changes: 9 additions & 16 deletions packages/core/src/components/mediaDeviceSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,21 @@ export type SetMediaDeviceOptions = {

export function setupDeviceSelector(
kind: MediaDeviceKind,
room?: Room,
room: Room,
localTrack?: LocalAudioTrack | LocalVideoTrack,
) {
const activeDeviceSubject = new BehaviorSubject<string | undefined>(undefined);

const activeDeviceObservable = room
? createActiveDeviceObservable(room, kind)
: activeDeviceSubject.asObservable();
const activeDeviceObservable = createActiveDeviceObservable(room, kind);

const setActiveMediaDevice = async (id: string, options: SetMediaDeviceOptions = {}) => {
if (room) {
if (localTrack) {
await localTrack.setDeviceId(options.exact ? { exact: id } : id);
const actualId = await localTrack.getDeviceId(false);
activeDeviceSubject.next(
id === 'default' && localTrack.mediaStreamTrack.label.startsWith('Default') ? id : actualId,
);
} else if (room) {
log.debug(`Switching active device of kind "${kind}" with id ${id}.`);
await room.switchActiveDevice(kind, id, options.exact);
const actualDeviceId: string | undefined = room.getActiveDevice(kind) ?? id;
Expand All @@ -49,17 +53,6 @@ export function setupDeviceSelector(
(id === 'default' && !targetTrack) ||
(id === 'default' && targetTrack?.mediaStreamTrack.label.startsWith('Default'));
activeDeviceSubject.next(useDefault ? id : actualDeviceId);
} else if (localTrack) {
await localTrack.setDeviceId(options.exact ? { exact: id } : id);
const actualId = await localTrack.getDeviceId();
activeDeviceSubject.next(
id === 'default' && localTrack.mediaStreamTrack.label.startsWith('Default') ? id : actualId,
);
} else if (activeDeviceSubject.value !== id) {
log.warn(
'device switch skipped, please provide either a room or a local track to switch on. ',
);
activeDeviceSubject.next(id);
}
};
const className: string = prefixClass('media-device-select');
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/persistent-storage/user-choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ export type LocalUserChoices = {
export const defaultUserChoices: LocalUserChoices = {
videoEnabled: true,
audioEnabled: true,
videoDeviceId: '',
audioDeviceId: '',
videoDeviceId: 'default',
audioDeviceId: 'default',
username: '',
} as const;

Expand Down
4 changes: 2 additions & 2 deletions packages/react/etc/components-react.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1075,14 +1075,14 @@ export interface UsePersistentUserChoicesOptions {
// @public
export function usePinnedTracks(layoutContext?: LayoutContextType): TrackReferenceOrPlaceholder[];

// @public (undocumented)
// @public @deprecated (undocumented)
export function usePreviewDevice<T extends LocalVideoTrack | LocalAudioTrack>(enabled: boolean, deviceId: string, kind: 'videoinput' | 'audioinput'): {
selectedDevice: MediaDeviceInfo | undefined;
localTrack: T | undefined;
deviceError: Error | null;
};

// @alpha (undocumented)
// @public (undocumented)
export function usePreviewTracks(options: CreateLocalTracksOptions, onError?: (err: Error) => void): LocalTrack<Track.Kind>[] | undefined;

// Warning: (ae-incompatible-release-tags) The symbol "useRemoteParticipant" is marked as @public, but its signature references "ParticipantIdentifier" which is marked as @beta
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
},
"peerDependencies": {
"@livekit/krisp-noise-filter": "^0.2.12",
"livekit-client": "^2.5.7",
"livekit-client": "catalog:",
"react": ">=18",
"react-dom": ">=18",
"tslib": "^2.6.2"
Expand Down
6 changes: 5 additions & 1 deletion packages/react/src/components/controls/MediaDeviceSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,12 @@ export const MediaDeviceSelect: (
[className, props],
);

const hasDefault = !!devices.find((info) => info.label.toLowerCase().startsWith('default'));

function isActive(deviceId: string, activeDeviceId: string, index: number) {
return deviceId === activeDeviceId || (index === 0 && activeDeviceId === 'default');
return (
deviceId === activeDeviceId || (!hasDefault && index === 0 && activeDeviceId === 'default')
);
}

return (
Expand Down
18 changes: 11 additions & 7 deletions packages/react/src/hooks/useMediaDeviceSelect.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createMediaDeviceObserver, setupDeviceSelector, log } from '@livekit/components-core';
import type { LocalAudioTrack, LocalVideoTrack, Room } from 'livekit-client';
import { Room, type LocalAudioTrack, type LocalVideoTrack } from 'livekit-client';
import * as React from 'react';
import { useMaybeRoomContext } from '../context';
import { useObservableState } from './internal';
Expand Down Expand Up @@ -46,6 +46,9 @@ export function useMediaDeviceSelect({
onError,
}: UseMediaDeviceSelectProps) {
const roomContext = useMaybeRoomContext();

const roomFallback = React.useMemo(() => room ?? roomContext ?? new Room(), [room, roomContext]);

// List of all devices.
const deviceObserver = React.useMemo(
() => createMediaDeviceObserver(kind, onError, requestPermissions),
Expand All @@ -54,19 +57,20 @@ export function useMediaDeviceSelect({
const devices = useObservableState(deviceObserver, [] as MediaDeviceInfo[]);
// Active device management.
const [currentDeviceId, setCurrentDeviceId] = React.useState<string>(
roomContext?.getActiveDevice(kind) ?? '',
roomFallback?.getActiveDevice(kind) ?? 'default',
);
const { className, activeDeviceObservable, setActiveMediaDevice } = React.useMemo(
() => setupDeviceSelector(kind, room ?? roomContext, track),
[kind, room, roomContext, track],
() => setupDeviceSelector(kind, roomFallback),
[kind, roomFallback, track],
);

React.useEffect(() => {
const listener = activeDeviceObservable.subscribe((deviceId) => {
if (deviceId && deviceId !== currentDeviceId) {
log.info('setCurrentDeviceId', deviceId);
setCurrentDeviceId(deviceId);
if (!deviceId) {
return;
}
log.info('setCurrentDeviceId', deviceId);
setCurrentDeviceId(deviceId);
});
return () => {
listener?.unsubscribe();
Expand Down
8 changes: 6 additions & 2 deletions packages/react/src/prefabs/ControlBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,9 @@ export function ControlBar({
<div className="lk-button-group-menu">
<MediaDeviceMenu
kind="audioinput"
onActiveDeviceChange={(_kind, deviceId) => saveAudioInputDeviceId(deviceId ?? '')}
onActiveDeviceChange={(_kind, deviceId) =>
saveAudioInputDeviceId(deviceId ?? 'default')
}
/>
</div>
</div>
Expand All @@ -162,7 +164,9 @@ export function ControlBar({
<div className="lk-button-group-menu">
<MediaDeviceMenu
kind="videoinput"
onActiveDeviceChange={(_kind, deviceId) => saveVideoInputDeviceId(deviceId ?? '')}
onActiveDeviceChange={(_kind, deviceId) =>
saveVideoInputDeviceId(deviceId ?? 'default')
}
/>
</div>
</div>
Expand Down
9 changes: 6 additions & 3 deletions packages/react/src/prefabs/PreJoin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export interface PreJoinProps
videoProcessor?: TrackProcessor<Track.Kind.Video>;
}

/** @alpha */
/** @public */
export function usePreviewTracks(
options: CreateLocalTracksOptions,
onError?: (err: Error) => void,
Expand Down Expand Up @@ -100,7 +100,10 @@ export function usePreviewTracks(
return tracks;
}

/** @public */
/**
* @public
* @deprecated use `usePreviewTracks` instead
*/
export function usePreviewDevice<T extends LocalVideoTrack | LocalAudioTrack>(
enabled: boolean,
deviceId: string,
Expand Down Expand Up @@ -131,7 +134,7 @@ export function usePreviewDevice<T extends LocalVideoTrack | LocalAudioTrack>(
})
: await createLocalAudioTrack({ deviceId });

const newDeviceId = await track.getDeviceId();
const newDeviceId = await track.getDeviceId(false);
if (newDeviceId && deviceId !== newDeviceId) {
prevDeviceId.current = newDeviceId;
setLocalDeviceId(newDeviceId);
Expand Down
4 changes: 3 additions & 1 deletion packages/react/src/prefabs/VoiceAssistantControlBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ export function VoiceAssistantControlBar({
<div className="lk-button-group-menu">
<MediaDeviceMenu
kind="audioinput"
onActiveDeviceChange={(_kind, deviceId) => saveAudioInputDeviceId(deviceId ?? '')}
onActiveDeviceChange={(_kind, deviceId) =>
saveAudioInputDeviceId(deviceId ?? 'default')
}
/>
</div>
</div>
Expand Down
Loading

0 comments on commit 31a2b04

Please sign in to comment.