diff --git a/app/components/EnsurePermissions.tsx b/app/components/EnsurePermissions.tsx index 267aea7d..73ee39dd 100644 --- a/app/components/EnsurePermissions.tsx +++ b/app/components/EnsurePermissions.tsx @@ -3,6 +3,8 @@ import { Button } from './Button' export interface EnsurePermissionsProps { children?: ReactNode + onMicSelected: (deviceId: string) => void + onCameraSelected: (deviceId: string) => void } type PermissionState = 'denied' | 'granted' | 'prompt' | 'unable-to-determine' @@ -66,6 +68,10 @@ export function EnsurePermissions(props: EnsurePermissionsProps) { }) .then((ms) => { if (mountedRef.current) setPermissionState('granted') + const micId = ms.getAudioTracks()[0].getSettings().deviceId + if (micId) props.onMicSelected(micId) + const cameraId = ms.getVideoTracks()[0].getSettings().deviceId + if (cameraId) props.onCameraSelected(cameraId) ms.getTracks().forEach((t) => t.stop()) }) .catch(() => { diff --git a/app/hooks/useUserMedia.ts b/app/hooks/useUserMedia.ts index cc120109..1fd43960 100644 --- a/app/hooks/useUserMedia.ts +++ b/app/hooks/useUserMedia.ts @@ -76,7 +76,29 @@ function useScreenshare() { } } -export default function useUserMedia() { +export default function useUserMedia(options: { + micDeviceId?: string + cameraDeviceId?: string +}) { + useEffect(() => { + if (!options.micDeviceId) return + navigator.mediaDevices + .enumerateDevices() + .then((ds) => ds.find((d) => d.deviceId === options.micDeviceId)) + .then((d) => { + d && mic.setPreferredDevice(d) + }) + }, [options.micDeviceId]) + useEffect(() => { + if (!options.cameraDeviceId) return + navigator.mediaDevices + .enumerateDevices() + .then((ds) => ds.find((d) => d.deviceId === options.cameraDeviceId)) + .then((d) => { + d && camera.setPreferredDevice(d) + }) + }, [options.cameraDeviceId]) + const [suppressNoise, setSuppressNoise] = useNoiseSuppression() const [blurVideo, setBlurVideo] = useBlurVideo() diff --git a/app/routes/_room.tsx b/app/routes/_room.tsx index da2c1f3b..316845af 100644 --- a/app/routes/_room.tsx +++ b/app/routes/_room.tsx @@ -64,8 +64,13 @@ export const loader = async ({ context }: LoaderFunctionArgs) => { } export default function RoomWithPermissions() { + const [micDeviceId, setMicDeviceId] = useState() + const [cameraDeviceId, setCameraDeviceId] = useState() return ( - + @@ -78,16 +83,22 @@ export default function RoomWithPermissions() { } > - + ) } -function RoomPreparation() { +function RoomPreparation(props: { + micDeviceId?: string + cameraDeviceId?: string +}) { const { roomName } = useParams() invariant(roomName) - const userMedia = useUserMedia() + const userMedia = useUserMedia(props) const room = useRoom({ roomName, userMedia }) return room.roomState.meetingId ? ( diff --git a/package-lock.json b/package-lock.json index 6235b33c..b47d82ff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "nanoid": "^5.0.7", "partyserver": "^0.0.57", "partysocket": "^1.0.2", - "partytracks": "^0.0.46", + "partytracks": "^0.0.54", "react": "^18.3.1", "react-dom": "^18.3.1", "react-flip-toolkit": "^7.2.4", @@ -9392,9 +9392,9 @@ } }, "node_modules/jose": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", - "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -11956,13 +11956,13 @@ } }, "node_modules/partytracks": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/partytracks/-/partytracks-0.0.46.tgz", - "integrity": "sha512-lYhyJOzqDXTrVrMKdOSaKRP1KvtkxXB174WXAud0GMMJmHAYdNWQJTDxlaHK2K/AeDmDlIveYY0aP5pbbCUJ7Q==", + "version": "0.0.54", + "resolved": "https://registry.npmjs.org/partytracks/-/partytracks-0.0.54.tgz", + "integrity": "sha512-KERbusDv3Rd9zFfUoUVTatolyJJnmNNhpBZ5aynn3HPhfzRBrpuy8R+fjEDu9MWM/cBil2CiHqXRCZZn2bN+eQ==", "license": "ISC", "dependencies": { "cookie": "^1.0.2", - "jose": "^6.0.10", + "jose": "^6.1.0", "rxjs": "^7.8.2", "tiny-invariant": "^1.3.3" } @@ -22802,9 +22802,9 @@ "dev": true }, "jose": { - "version": "6.0.11", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.0.11.tgz", - "integrity": "sha512-QxG7EaliDARm1O1S8BGakqncGT9s25bKL1WSf6/oa17Tkqwi8D2ZNglqCF+DsYF88/rV66Q/Q2mFAy697E1DUg==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.0.tgz", + "integrity": "sha512-TTQJyoEoKcC1lscpVDCSsVgYzUDg/0Bt3WE//WiTPK6uOCQC2KZS4MpugbMWt/zyjkopgZoXhZuCi00gLudfUA==" }, "js-cookie": { "version": "2.2.1", @@ -24632,12 +24632,12 @@ } }, "partytracks": { - "version": "0.0.46", - "resolved": "https://registry.npmjs.org/partytracks/-/partytracks-0.0.46.tgz", - "integrity": "sha512-lYhyJOzqDXTrVrMKdOSaKRP1KvtkxXB174WXAud0GMMJmHAYdNWQJTDxlaHK2K/AeDmDlIveYY0aP5pbbCUJ7Q==", + "version": "0.0.54", + "resolved": "https://registry.npmjs.org/partytracks/-/partytracks-0.0.54.tgz", + "integrity": "sha512-KERbusDv3Rd9zFfUoUVTatolyJJnmNNhpBZ5aynn3HPhfzRBrpuy8R+fjEDu9MWM/cBil2CiHqXRCZZn2bN+eQ==", "requires": { "cookie": "^1.0.2", - "jose": "^6.0.10", + "jose": "^6.1.0", "rxjs": "^7.8.2", "tiny-invariant": "^1.3.3" }, diff --git a/package.json b/package.json index ba06ddb9..d7674435 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "nanoid": "^5.0.7", "partyserver": "^0.0.57", "partysocket": "^1.0.2", - "partytracks": "^0.0.46", + "partytracks": "^0.0.54", "react": "^18.3.1", "react-dom": "^18.3.1", "react-flip-toolkit": "^7.2.4",