From 40b351d76d580dca20185151d8f23f85f1751abc Mon Sep 17 00:00:00 2001 From: Robin Date: Wed, 4 Jun 2025 11:23:20 -0400 Subject: [PATCH] Ignore spurious 'devicechange' events This gives us the additional insurance of breaking the Safari media acquisition loop at the source by admitting that they can be spurious in practice. Safari, why!? --- src/livekit/MediaDevicesContext.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/livekit/MediaDevicesContext.tsx b/src/livekit/MediaDevicesContext.tsx index 636f5a6d..8a19395b 100644 --- a/src/livekit/MediaDevicesContext.tsx +++ b/src/livekit/MediaDevicesContext.tsx @@ -17,9 +17,10 @@ import { type JSX, } from "react"; import { createMediaDeviceObserver } from "@livekit/components-core"; -import { combineLatest, map, startWith } from "rxjs"; +import { combineLatest, distinctUntilChanged, map, startWith } from "rxjs"; import { useObservable, useObservableEagerState } from "observable-hooks"; import { logger } from "matrix-js-sdk/lib/logger"; +import { isEqual } from "lodash-es"; import { useSetting, @@ -140,7 +141,13 @@ function useMediaDeviceHandle( kind, () => logger.error("Error creating MediaDeviceObserver"), requestPermissions, - ).pipe(startWith([])), + // This Observable emits new values whenever the browser fires a + // MediaDevices 'devicechange' event. One would think, innocently, that + // a 'devicechange' event means the devices have changed. But as of the + // time of writing, we are seeing mobile Safari firing spurious + // 'devicechange' events (where no change has actually occurred) when + // we call MediaDevices.getUserMedia. So, filter by deep equality. + ).pipe(startWith([]), distinctUntilChanged(isEqual)), [kind, requestPermissions], ); const available = useObservableEagerState(