From 31a2b04ec36e9a3524804a2dbb7c2b663ea18b83 Mon Sep 17 00:00:00 2001 From: lukasIO Date: Mon, 13 Jan 2025 17:03:51 +0100 Subject: [PATCH] Improve default device handling (#1056) --- .changeset/unlucky-emus-float.md | 6 ++ .npmrc | 1 - docs/storybook/package.json | 2 +- examples/nextjs/package.json | 2 +- package.json | 3 - packages/core/etc/components-core.api.md | 4 +- packages/core/package.json | 2 +- .../core/src/components/mediaDeviceSelect.ts | 25 +++---- .../src/persistent-storage/user-choices.ts | 4 +- packages/react/etc/components-react.api.md | 4 +- packages/react/package.json | 2 +- .../components/controls/MediaDeviceSelect.tsx | 6 +- .../react/src/hooks/useMediaDeviceSelect.ts | 18 +++-- packages/react/src/prefabs/ControlBar.tsx | 8 ++- packages/react/src/prefabs/PreJoin.tsx | 9 ++- .../src/prefabs/VoiceAssistantControlBar.tsx | 4 +- pnpm-lock.yaml | 71 +++++++++---------- pnpm-workspace.yaml | 3 + 18 files changed, 93 insertions(+), 81 deletions(-) create mode 100644 .changeset/unlucky-emus-float.md diff --git a/.changeset/unlucky-emus-float.md b/.changeset/unlucky-emus-float.md new file mode 100644 index 000000000..b2efac513 --- /dev/null +++ b/.changeset/unlucky-emus-float.md @@ -0,0 +1,6 @@ +--- +"@livekit/components-core": minor +"@livekit/components-react": minor +--- + +Improve default device handling diff --git a/.npmrc b/.npmrc index e5e7fdfa4..1e114d5aa 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,3 @@ -dedupe-peer-dependents=true resolve-peers-from-workspace-root=true manage-package-manager-versions=true package-manager-strict-version=true \ No newline at end of file diff --git a/docs/storybook/package.json b/docs/storybook/package.json index 3feebe00f..94d8b99a0 100644 --- a/docs/storybook/package.json +++ b/docs/storybook/package.json @@ -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" }, diff --git a/examples/nextjs/package.json b/examples/nextjs/package.json index 6263d8340..85777b133 100644 --- a/examples/nextjs/package.json +++ b/examples/nextjs/package.json @@ -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", diff --git a/package.json b/package.json index eaf931747..472568207 100644 --- a/package.json +++ b/package.json @@ -40,9 +40,6 @@ "turbo": "^2.1.1", "typescript": "5.6.2" }, - "dependencies": { - "livekit-client": "^2.5.7" - }, "engines": { "node": ">=18" }, diff --git a/packages/core/etc/components-core.api.md b/packages/core/etc/components-core.api.md index 4ed33af59..a19af4075 100644 --- a/packages/core/etc/components-core.api.md +++ b/packages/core/etc/components-core.api.md @@ -546,9 +546,9 @@ export function setupDataMessageHandler(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; + activeDeviceObservable: Observable; setActiveMediaDevice: (id: string, options?: SetMediaDeviceOptions) => Promise; }; diff --git a/packages/core/package.json b/packages/core/package.json index 5b86118ba..aa8a785ec 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -40,7 +40,7 @@ "rxjs": "7.8.1" }, "peerDependencies": { - "livekit-client": "^2.5.7", + "livekit-client": "catalog:", "tslib": "^2.6.2" }, "devDependencies": { diff --git a/packages/core/src/components/mediaDeviceSelect.ts b/packages/core/src/components/mediaDeviceSelect.ts index 41ca6cc89..7fed70170 100644 --- a/packages/core/src/components/mediaDeviceSelect.ts +++ b/packages/core/src/components/mediaDeviceSelect.ts @@ -20,17 +20,21 @@ export type SetMediaDeviceOptions = { export function setupDeviceSelector( kind: MediaDeviceKind, - room?: Room, + room: Room, localTrack?: LocalAudioTrack | LocalVideoTrack, ) { const activeDeviceSubject = new BehaviorSubject(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; @@ -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'); diff --git a/packages/core/src/persistent-storage/user-choices.ts b/packages/core/src/persistent-storage/user-choices.ts index 3960f7f39..3542cee05 100644 --- a/packages/core/src/persistent-storage/user-choices.ts +++ b/packages/core/src/persistent-storage/user-choices.ts @@ -39,8 +39,8 @@ export type LocalUserChoices = { export const defaultUserChoices: LocalUserChoices = { videoEnabled: true, audioEnabled: true, - videoDeviceId: '', - audioDeviceId: '', + videoDeviceId: 'default', + audioDeviceId: 'default', username: '', } as const; diff --git a/packages/react/etc/components-react.api.md b/packages/react/etc/components-react.api.md index 7371a27c2..7ab98f369 100644 --- a/packages/react/etc/components-react.api.md +++ b/packages/react/etc/components-react.api.md @@ -1075,14 +1075,14 @@ export interface UsePersistentUserChoicesOptions { // @public export function usePinnedTracks(layoutContext?: LayoutContextType): TrackReferenceOrPlaceholder[]; -// @public (undocumented) +// @public @deprecated (undocumented) export function usePreviewDevice(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[] | undefined; // Warning: (ae-incompatible-release-tags) The symbol "useRemoteParticipant" is marked as @public, but its signature references "ParticipantIdentifier" which is marked as @beta diff --git a/packages/react/package.json b/packages/react/package.json index 4a2f60300..2e4517ebf 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -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" diff --git a/packages/react/src/components/controls/MediaDeviceSelect.tsx b/packages/react/src/components/controls/MediaDeviceSelect.tsx index e7abd9c99..b5ec469a5 100644 --- a/packages/react/src/components/controls/MediaDeviceSelect.tsx +++ b/packages/react/src/components/controls/MediaDeviceSelect.tsx @@ -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 ( diff --git a/packages/react/src/hooks/useMediaDeviceSelect.ts b/packages/react/src/hooks/useMediaDeviceSelect.ts index 614959e35..52f55b44d 100644 --- a/packages/react/src/hooks/useMediaDeviceSelect.ts +++ b/packages/react/src/hooks/useMediaDeviceSelect.ts @@ -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'; @@ -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), @@ -54,19 +57,20 @@ export function useMediaDeviceSelect({ const devices = useObservableState(deviceObserver, [] as MediaDeviceInfo[]); // Active device management. const [currentDeviceId, setCurrentDeviceId] = React.useState( - 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(); diff --git a/packages/react/src/prefabs/ControlBar.tsx b/packages/react/src/prefabs/ControlBar.tsx index 8cc21f11b..5e12c1499 100644 --- a/packages/react/src/prefabs/ControlBar.tsx +++ b/packages/react/src/prefabs/ControlBar.tsx @@ -144,7 +144,9 @@ export function ControlBar({
saveAudioInputDeviceId(deviceId ?? '')} + onActiveDeviceChange={(_kind, deviceId) => + saveAudioInputDeviceId(deviceId ?? 'default') + } />
@@ -162,7 +164,9 @@ export function ControlBar({
saveVideoInputDeviceId(deviceId ?? '')} + onActiveDeviceChange={(_kind, deviceId) => + saveVideoInputDeviceId(deviceId ?? 'default') + } />
diff --git a/packages/react/src/prefabs/PreJoin.tsx b/packages/react/src/prefabs/PreJoin.tsx index 65dd2f34d..e2a5e9715 100644 --- a/packages/react/src/prefabs/PreJoin.tsx +++ b/packages/react/src/prefabs/PreJoin.tsx @@ -55,7 +55,7 @@ export interface PreJoinProps videoProcessor?: TrackProcessor; } -/** @alpha */ +/** @public */ export function usePreviewTracks( options: CreateLocalTracksOptions, onError?: (err: Error) => void, @@ -100,7 +100,10 @@ export function usePreviewTracks( return tracks; } -/** @public */ +/** + * @public + * @deprecated use `usePreviewTracks` instead + */ export function usePreviewDevice( enabled: boolean, deviceId: string, @@ -131,7 +134,7 @@ export function usePreviewDevice( }) : await createLocalAudioTrack({ deviceId }); - const newDeviceId = await track.getDeviceId(); + const newDeviceId = await track.getDeviceId(false); if (newDeviceId && deviceId !== newDeviceId) { prevDeviceId.current = newDeviceId; setLocalDeviceId(newDeviceId); diff --git a/packages/react/src/prefabs/VoiceAssistantControlBar.tsx b/packages/react/src/prefabs/VoiceAssistantControlBar.tsx index 41bf08339..ece660ec6 100644 --- a/packages/react/src/prefabs/VoiceAssistantControlBar.tsx +++ b/packages/react/src/prefabs/VoiceAssistantControlBar.tsx @@ -95,7 +95,9 @@ export function VoiceAssistantControlBar({
saveAudioInputDeviceId(deviceId ?? '')} + onActiveDeviceChange={(_kind, deviceId) => + saveAudioInputDeviceId(deviceId ?? 'default') + } />
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8dc81fc1c..c6c416f12 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,13 +4,15 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +catalogs: + default: + livekit-client: + specifier: ^2.8.0 + version: 2.8.0 + importers: .: - dependencies: - livekit-client: - specifier: ^2.5.7 - version: 2.6.0 devDependencies: '@changesets/cli': specifier: ^2.27.1 @@ -55,8 +57,8 @@ importers: specifier: workspace:* version: link:../../packages/styles livekit-client: - specifier: ^2.5.7 - version: 2.6.0 + specifier: 'catalog:' + version: 2.8.0 react: specifier: ^18.2.0 version: 18.3.1 @@ -120,10 +122,10 @@ importers: version: link:../../packages/styles '@livekit/track-processors': specifier: ^0.3.2 - version: 0.3.2(livekit-client@2.6.0) + version: 0.3.2(livekit-client@2.8.0) livekit-client: - specifier: ^2.5.7 - version: 2.6.0 + specifier: 'catalog:' + version: 2.8.0 livekit-server-sdk: specifier: ^2.6.1 version: 2.6.1 @@ -162,8 +164,8 @@ importers: specifier: 1.6.11 version: 1.6.11 livekit-client: - specifier: ^2.5.7 - version: 2.6.0 + specifier: 'catalog:' + version: 2.8.0 loglevel: specifier: 1.9.1 version: 1.9.1 @@ -212,13 +214,13 @@ importers: version: link:../core '@livekit/krisp-noise-filter': specifier: ^0.2.12 - version: 0.2.12(livekit-client@2.6.0) + version: 0.2.12(livekit-client@2.8.0) clsx: specifier: 2.1.1 version: 2.1.1 livekit-client: - specifier: ^2.5.7 - version: 2.6.0 + specifier: 'catalog:' + version: 2.8.0 tslib: specifier: ^2.6.2 version: 2.8.1 @@ -1632,15 +1634,15 @@ packages: peerDependencies: livekit-client: ^2.0.8 - '@livekit/mutex@1.0.0': - resolution: {integrity: sha512-aiUhoThBNF9UyGTxEURFzJLhhPLIVTnQiEVMjRhPnfHNKLfo2JY9xovHKIus7B78UD5hsP6DlgpmAsjrz4U0Iw==} - - '@livekit/protocol@1.24.0': - resolution: {integrity: sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w==} + '@livekit/mutex@1.1.1': + resolution: {integrity: sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==} '@livekit/protocol@1.27.1': resolution: {integrity: sha512-ISEp7uWdV82mtCR1eyHFTzdRZTVbe2+ZztjmjiMPzR/KPrI1Ma/u5kLh87NNuY3Rn8wv1VlEvGHHsFjQ+dKVUw==} + '@livekit/protocol@1.30.0': + resolution: {integrity: sha512-SDI9ShVKj8N3oOSinr8inaxD3FXgmgoJlqN35uU/Yx1sdoDeQbzAuBFox7bYjM+VhnZ1V22ivIDjAsKr00H+XQ==} + '@livekit/track-processors@0.3.2': resolution: {integrity: sha512-4JUCzb7yIKoVsTo8J6FTzLZJHcI6DihfX/pGRDg0SOGaxprcDPrt8jaDBBTsnGBSXHeMxl2ugN+xQjdCWzLKEA==} peerDependencies: @@ -5603,8 +5605,8 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - livekit-client@2.6.0: - resolution: {integrity: sha512-hpxNBtyWIFCefoHjHoSjqPCw3m7AfSJVcVZw6rMsqds4u+dSpWLfYkglWP8JuPGUIssyOsZm/+bV3gBWfuOGGQ==} + livekit-client@2.8.0: + resolution: {integrity: sha512-8/IXhacAFYdXMU1wFyc8/MSGCzHr02Hn9T5o3MX19TR03RHSaBKBF2xK8fQFINBmpcYkiMAnQL0P6K3nfcifQA==} livekit-server-sdk@2.6.1: resolution: {integrity: sha512-j/8TOlahIyWnycNkuSzTv6q+win4JTbDGNH48iMsZDMnJBks9hhC9UwAO4ES42sAorIAxGkrH58hxt4KdTkZaQ==} @@ -7240,9 +7242,6 @@ packages: tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} @@ -9259,25 +9258,25 @@ snapshots: transitivePeerDependencies: - encoding - '@livekit/krisp-noise-filter@0.2.12(livekit-client@2.6.0)': + '@livekit/krisp-noise-filter@0.2.12(livekit-client@2.8.0)': dependencies: - livekit-client: 2.6.0 + livekit-client: 2.8.0 - '@livekit/mutex@1.0.0': {} + '@livekit/mutex@1.1.1': {} - '@livekit/protocol@1.24.0': + '@livekit/protocol@1.27.1': dependencies: '@bufbuild/protobuf': 1.10.0 - '@livekit/protocol@1.27.1': + '@livekit/protocol@1.30.0': dependencies: '@bufbuild/protobuf': 1.10.0 - '@livekit/track-processors@0.3.2(livekit-client@2.6.0)': + '@livekit/track-processors@0.3.2(livekit-client@2.8.0)': dependencies: '@mediapipe/holistic': 0.5.1675471629 '@mediapipe/tasks-vision': 0.10.9 - livekit-client: 2.6.0 + livekit-client: 2.8.0 '@manypkg/find-root@1.1.0': dependencies: @@ -14650,15 +14649,15 @@ snapshots: lines-and-columns@1.2.4: {} - livekit-client@2.6.0: + livekit-client@2.8.0: dependencies: - '@livekit/mutex': 1.0.0 - '@livekit/protocol': 1.24.0 + '@livekit/mutex': 1.1.1 + '@livekit/protocol': 1.30.0 events: 3.3.0 loglevel: 1.9.1 sdp-transform: 2.14.2 ts-debounce: 4.0.0 - tslib: 2.7.0 + tslib: 2.8.1 typed-emitter: 2.1.0 webrtc-adapter: 9.0.1 @@ -16383,8 +16382,6 @@ snapshots: tslib@1.14.1: {} - tslib@2.7.0: {} - tslib@2.8.1: {} tsup@8.1.0(@microsoft/api-extractor@7.47.9(@types/node@22.7.4))(postcss@8.4.47)(typescript@5.6.2): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index cdf3589e4..c491ecd55 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,3 +3,6 @@ packages: - 'examples/*' - 'docs/*' - 'tooling/*' + +catalog: + livekit-client: ^2.8.0