Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve default device handling #1056

Merged
merged 9 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
8 changes: 4 additions & 4 deletions packages/core/etc/components-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export type GridLayoutInfo = {
export function isEqualTrackRef(a?: TrackReferenceOrPlaceholder, b?: TrackReferenceOrPlaceholder): boolean;

// @public (undocumented)
export function isLocal(p: Participant): p is LocalParticipant;
export function isLocal(p: Participant): boolean;

// @public
export function isMobileBrowser(): boolean;
Expand All @@ -235,7 +235,7 @@ export function isParticipantTrackReferencePinned(trackRef: TrackReference, pinS
export function isPlaceholderReplacement(currentTrackRef: TrackReferenceOrPlaceholder, nextTrackRef: TrackReferenceOrPlaceholder): boolean;

// @public (undocumented)
export function isRemote(p: Participant): p is RemoteParticipant;
export function isRemote(p: Participant): boolean;

// @public (undocumented)
export function isSourcesWithOptions(sources: SourcesArray): sources is TrackSourceWithOptions[];
Expand Down 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 @@ -1058,14 +1058,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
Loading