From 8718d928c9b2a45614961c92ccdb04c05f9fee72 Mon Sep 17 00:00:00 2001 From: Kevin Kipp Date: Wed, 15 Oct 2025 12:30:31 -0500 Subject: [PATCH] Fix device selection --- .changeset/twelve-clubs-kick.md | 6 ++ packages/partytracks/src/client/getDevices.ts | 3 + .../partytracks/src/client/resilientTrack$.ts | 67 ++++++++++++++++--- 3 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 .changeset/twelve-clubs-kick.md diff --git a/.changeset/twelve-clubs-kick.md b/.changeset/twelve-clubs-kick.md new file mode 100644 index 00000000..b9fc2d95 --- /dev/null +++ b/.changeset/twelve-clubs-kick.md @@ -0,0 +1,6 @@ +--- +"partytracks": patch +--- + +- Specify exact deviceId when requesting media +- Handle when permission has not been granted yet so that the user's selection is persisted diff --git a/packages/partytracks/src/client/getDevices.ts b/packages/partytracks/src/client/getDevices.ts index 875b03e3..78b8c52c 100644 --- a/packages/partytracks/src/client/getDevices.ts +++ b/packages/partytracks/src/client/getDevices.ts @@ -81,6 +81,9 @@ const getDevice = ({ deprioritizeDevice(device); if (onDeviceFailure) onDeviceFailure(device); }, + onUnconstrainedDeviceSelection: (device) => { + deviceManagerPublicApi.setPreferredDevice(device); + }, ...resilientTrackOptions }).pipe( tap((track) => diff --git a/packages/partytracks/src/client/resilientTrack$.ts b/packages/partytracks/src/client/resilientTrack$.ts index e729c41f..f4257bc6 100644 --- a/packages/partytracks/src/client/resilientTrack$.ts +++ b/packages/partytracks/src/client/resilientTrack$.ts @@ -83,13 +83,20 @@ export interface ResilientTrackOptions { for optionally deprioritizing the device in the future. */ onDeviceFailure?: (device: MediaDeviceInfo) => void; + /** + A callback to be notified when a device was selected that was unconstrained. + This should really only happen when the user is granting permission for the + first time and the browser showed them a device selector. + */ + onUnconstrainedDeviceSelection?: (device: MediaDeviceInfo) => void; } export const resilientTrack$ = ({ kind, constraints = {}, devicePriority$ = devices$, - onDeviceFailure = () => {} + onDeviceFailure = () => {}, + onUnconstrainedDeviceSelection = () => {} }: ResilientTrackOptions): Observable => devicePriority$ .pipe( @@ -105,7 +112,13 @@ export const resilientTrack$ = ({ ...deviceList.map( (device) => new Observable((subscriber) => { - acquireTrack(subscriber, device, constraints, onDeviceFailure); + acquireTrack( + subscriber, + device, + constraints, + onDeviceFailure, + onUnconstrainedDeviceSelection + ); }) ), throwError(() => new DevicesExhaustedError()) @@ -123,17 +136,30 @@ export const resilientTrack$ = ({ function acquireTrack( subscriber: Subscriber, - device: MediaDeviceInfo, + device: Partial, constraints: MediaTrackConstraints, - onDeviceFailure: (device: MediaDeviceInfo) => void + onDeviceFailure: (device: MediaDeviceInfo) => void, + onUnconstrainedDeviceSelection: (device: MediaDeviceInfo) => void = () => {} ) { const { deviceId, groupId, label } = device; logger.log(`🙏🏻 Requesting ${label}`); navigator.mediaDevices .getUserMedia( device.kind === "videoinput" - ? { video: { ...constraints, deviceId, groupId } } - : { audio: { ...constraints, deviceId, groupId } } + ? { + video: { + ...constraints, + deviceId: deviceId ? { exact: deviceId } : undefined, + groupId: groupId ? { exact: groupId } : undefined + } + } + : { + audio: { + ...constraints, + deviceId: deviceId ? { exact: deviceId } : undefined, + groupId: groupId ? { exact: groupId } : undefined + } + } ) .then(async (mediaStream) => { const track = @@ -152,14 +178,39 @@ function acquireTrack( if (await trackIsHealthy(track)) return; logger.log("Reacquiring track"); cleanup(); - acquireTrack(subscriber, device, constraints, onDeviceFailure); + acquireTrack( + subscriber, + device, + constraints, + onDeviceFailure, + onUnconstrainedDeviceSelection + ); }; document.addEventListener("visibilitychange", onVisibleHandler); subscriber.add(cleanup); subscriber.next(track); + // request was unconstrained + if (!deviceId && !groupId) { + const trackSettings = track.getSettings(); + const foundDevice = await navigator.mediaDevices + .enumerateDevices() + .then((devices) => + devices.find( + (d) => + d.deviceId === trackSettings.deviceId && + d.groupId === trackSettings.groupId + ) + ) + .catch(() => undefined); + if (foundDevice) { + onUnconstrainedDeviceSelection(foundDevice); + } + } } else { logger.log("☠️ track is not healthy, stopping"); - onDeviceFailure(device); + if (device instanceof MediaDeviceInfo) { + onDeviceFailure(device); + } track.stop(); subscriber.complete(); }