refactor
- `MediaDevice`->`MediaDeviceHandle` - use just one provider and switch inside the MediaDevicesProvider between: controlledAudioOutput, webViewAudioOutput - fix muteAllAudio
This commit is contained in:
45
src/App.tsx
45
src/App.tsx
@@ -19,10 +19,7 @@ import { ClientProvider } from "./ClientContext";
|
|||||||
import { ErrorPage, LoadingPage } from "./FullScreenView";
|
import { ErrorPage, LoadingPage } from "./FullScreenView";
|
||||||
import { DisconnectedBanner } from "./DisconnectedBanner";
|
import { DisconnectedBanner } from "./DisconnectedBanner";
|
||||||
import { Initializer } from "./initializer";
|
import { Initializer } from "./initializer";
|
||||||
import {
|
import { MediaDevicesProvider } from "./livekit/MediaDevicesContext";
|
||||||
ControlledOutputMediaDevicesProvider,
|
|
||||||
MediaDevicesProvider,
|
|
||||||
} from "./livekit/MediaDevicesContext";
|
|
||||||
import { widget } from "./widget";
|
import { widget } from "./widget";
|
||||||
import { useTheme } from "./useTheme";
|
import { useTheme } from "./useTheme";
|
||||||
import { ProcessorProvider } from "./livekit/TrackProcessorContext";
|
import { ProcessorProvider } from "./livekit/TrackProcessorContext";
|
||||||
@@ -55,7 +52,6 @@ const ThemeProvider: FC<SimpleProviderProps> = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const App: FC = () => {
|
export const App: FC = () => {
|
||||||
// const { controlledOutput } = useUrlParams();
|
|
||||||
const [loaded, setLoaded] = useState(false);
|
const [loaded, setLoaded] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Initializer.init()
|
Initializer.init()
|
||||||
@@ -67,20 +63,6 @@ export const App: FC = () => {
|
|||||||
.catch(logger.error);
|
.catch(logger.error);
|
||||||
});
|
});
|
||||||
|
|
||||||
const inner = (
|
|
||||||
<Sentry.ErrorBoundary
|
|
||||||
fallback={(error) => <ErrorPage error={error} widget={widget} />}
|
|
||||||
>
|
|
||||||
<DisconnectedBanner />
|
|
||||||
<Routes>
|
|
||||||
<SentryRoute path="/" element={<HomePage />} />
|
|
||||||
<SentryRoute path="/login" element={<LoginPage />} />
|
|
||||||
<SentryRoute path="/register" element={<RegisterPage />} />
|
|
||||||
<SentryRoute path="*" element={<RoomPage />} />
|
|
||||||
</Routes>
|
|
||||||
</Sentry.ErrorBoundary>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@@ -92,13 +74,24 @@ export const App: FC = () => {
|
|||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<ClientProvider>
|
<ClientProvider>
|
||||||
<ProcessorProvider>
|
<ProcessorProvider>
|
||||||
{true ? (
|
<MediaDevicesProvider>
|
||||||
<ControlledOutputMediaDevicesProvider>
|
<Sentry.ErrorBoundary
|
||||||
{inner}
|
fallback={(error) => (
|
||||||
</ControlledOutputMediaDevicesProvider>
|
<ErrorPage error={error} widget={widget} />
|
||||||
) : (
|
)}
|
||||||
<MediaDevicesProvider>{inner}</MediaDevicesProvider>
|
>
|
||||||
)}
|
<DisconnectedBanner />
|
||||||
|
<Routes>
|
||||||
|
<SentryRoute path="/" element={<HomePage />} />
|
||||||
|
<SentryRoute path="/login" element={<LoginPage />} />
|
||||||
|
<SentryRoute
|
||||||
|
path="/register"
|
||||||
|
element={<RegisterPage />}
|
||||||
|
/>
|
||||||
|
<SentryRoute path="*" element={<RoomPage />} />
|
||||||
|
</Routes>
|
||||||
|
</Sentry.ErrorBoundary>
|
||||||
|
</MediaDevicesProvider>
|
||||||
</ProcessorProvider>
|
</ProcessorProvider>
|
||||||
</ClientProvider>
|
</ClientProvider>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
type JSX,
|
type JSX,
|
||||||
} from "react";
|
} from "react";
|
||||||
import { createMediaDeviceObserver } from "@livekit/components-core";
|
import { createMediaDeviceObserver } from "@livekit/components-core";
|
||||||
import { map, startWith } from "rxjs";
|
import { combineLatest, map, startWith } from "rxjs";
|
||||||
import { useObservable, useObservableEagerState } from "observable-hooks";
|
import { useObservable, useObservableEagerState } from "observable-hooks";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
type Setting,
|
type Setting,
|
||||||
} from "../settings/settings";
|
} from "../settings/settings";
|
||||||
import { type OutputDevice, setOutputDevices$ } from "../controls";
|
import { type OutputDevice, setOutputDevices$ } from "../controls";
|
||||||
|
import { useUrlParams } from "../UrlParams";
|
||||||
|
|
||||||
export const EARPIECE_CONFIG_ID = "earpiece-id";
|
export const EARPIECE_CONFIG_ID = "earpiece-id";
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ export type DeviceLabel =
|
|||||||
| { type: "earpiece" }
|
| { type: "earpiece" }
|
||||||
| { type: "default"; name: string | null };
|
| { type: "default"; name: string | null };
|
||||||
|
|
||||||
export interface MediaDevice {
|
export interface MediaDeviceHandle {
|
||||||
/**
|
/**
|
||||||
* A map from available device IDs to labels.
|
* A map from available device IDs to labels.
|
||||||
*/
|
*/
|
||||||
@@ -61,35 +62,33 @@ export interface MediaDevice {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface InputDevices {
|
interface InputDevices {
|
||||||
audioInput: MediaDevice;
|
audioInput: MediaDeviceHandle;
|
||||||
videoInput: MediaDevice;
|
videoInput: MediaDeviceHandle;
|
||||||
startUsingDeviceNames: () => void;
|
startUsingDeviceNames: () => void;
|
||||||
stopUsingDeviceNames: () => void;
|
stopUsingDeviceNames: () => void;
|
||||||
usingNames: boolean;
|
usingNames: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MediaDevices extends Omit<InputDevices, "usingNames"> {
|
export interface MediaDevices extends Omit<InputDevices, "usingNames"> {
|
||||||
audioOutput: MediaDevice;
|
audioOutput: MediaDeviceHandle;
|
||||||
}
|
|
||||||
function useShowEarpiece(): boolean {
|
|
||||||
const [alwaysShowIphoneEarpice] = useSetting(alwaysShowIphoneEarpieceSetting);
|
|
||||||
const m = useMemo(
|
|
||||||
() =>
|
|
||||||
(navigator.userAgent.match("iPhone")?.length ?? 0) > 0 ||
|
|
||||||
alwaysShowIphoneEarpice,
|
|
||||||
[alwaysShowIphoneEarpice],
|
|
||||||
);
|
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useMediaDevice(
|
/**
|
||||||
|
* Hook to get access to a mediaDevice handle for a kind. This allows to list
|
||||||
|
* the available devices, read and set the selected device.
|
||||||
|
* @param kind audio input, output or video output.
|
||||||
|
* @param setting The setting this handles selection should be synced with.
|
||||||
|
* @param usingNames If the hook should query device names for the associated
|
||||||
|
* list.
|
||||||
|
* @returns A handle for the choosen kind.
|
||||||
|
*/
|
||||||
|
function useMediaDeviceHandle(
|
||||||
kind: MediaDeviceKind,
|
kind: MediaDeviceKind,
|
||||||
setting: Setting<string | undefined>,
|
setting: Setting<string | undefined>,
|
||||||
usingNames: boolean,
|
usingNames: boolean,
|
||||||
): MediaDevice {
|
): MediaDeviceHandle {
|
||||||
// Make sure we don't needlessly reset to a device observer without names,
|
// Make sure we don't needlessly reset to a device observer without names,
|
||||||
// once permissions are already given
|
// once permissions are already given
|
||||||
const showEarpiece = useShowEarpiece();
|
|
||||||
const hasRequestedPermissions = useRef(false);
|
const hasRequestedPermissions = useRef(false);
|
||||||
const requestPermissions = usingNames || hasRequestedPermissions.current;
|
const requestPermissions = usingNames || hasRequestedPermissions.current;
|
||||||
hasRequestedPermissions.current ||= usingNames;
|
hasRequestedPermissions.current ||= usingNames;
|
||||||
@@ -133,31 +132,23 @@ function useMediaDevice(
|
|||||||
kind === "audiooutput" &&
|
kind === "audiooutput" &&
|
||||||
!available.has("") &&
|
!available.has("") &&
|
||||||
!available.has("default") &&
|
!available.has("default") &&
|
||||||
(available.size || showEarpiece)
|
available.size
|
||||||
)
|
)
|
||||||
available = new Map([
|
available = new Map([
|
||||||
["", { type: "default", name: availableRaw[0]?.label || null }],
|
["", { type: "default", name: availableRaw[0]?.label || null }],
|
||||||
...available,
|
...available,
|
||||||
]);
|
]);
|
||||||
if (kind === "audiooutput" && showEarpiece)
|
|
||||||
// On IPhones we have to create a virtual earpiece device, because
|
|
||||||
// the earpiece is not available as a device ID.
|
|
||||||
available = new Map([
|
|
||||||
...available,
|
|
||||||
[EARPIECE_CONFIG_ID, { type: "earpiece" }],
|
|
||||||
]);
|
|
||||||
// Note: creating virtual default input devices would be another problem
|
// Note: creating virtual default input devices would be another problem
|
||||||
// entirely, because requesting a media stream from deviceId "" won't
|
// entirely, because requesting a media stream from deviceId "" won't
|
||||||
// automatically track the default device.
|
// automatically track the default device.
|
||||||
return available;
|
return available;
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
[deviceObserver$, kind, showEarpiece],
|
[deviceObserver$, kind],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [preferredId, setPreferredId] = useSetting(setting);
|
const [preferredId, select] = useSetting(setting);
|
||||||
const [asEarpice, setAsEarpiece] = useState(false);
|
|
||||||
const selectedId = useMemo(() => {
|
const selectedId = useMemo(() => {
|
||||||
if (available.size) {
|
if (available.size) {
|
||||||
// If the preferred device is available, use it. Or if every available
|
// If the preferred device is available, use it. Or if every available
|
||||||
@@ -187,37 +178,26 @@ function useMediaDevice(
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
const select = useCallback(
|
|
||||||
(id: string) => {
|
|
||||||
if (id === EARPIECE_CONFIG_ID) {
|
|
||||||
setAsEarpiece(true);
|
|
||||||
} else {
|
|
||||||
setAsEarpiece(false);
|
|
||||||
setPreferredId(id);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[setPreferredId],
|
|
||||||
);
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
available,
|
available,
|
||||||
selectedId,
|
selectedId,
|
||||||
useAsEarpiece: asEarpice,
|
useAsEarpiece: false,
|
||||||
selectedGroupId,
|
selectedGroupId,
|
||||||
select,
|
select,
|
||||||
}),
|
}),
|
||||||
[available, selectedId, asEarpice, selectedGroupId, select],
|
[available, selectedId, selectedGroupId, select],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deviceStub: MediaDevice = {
|
export const deviceStub: MediaDeviceHandle = {
|
||||||
available: new Map(),
|
available: new Map(),
|
||||||
selectedId: undefined,
|
selectedId: undefined,
|
||||||
selectedGroupId: undefined,
|
selectedGroupId: undefined,
|
||||||
select: () => {},
|
select: () => {},
|
||||||
useAsEarpiece: false,
|
useAsEarpiece: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const devicesStub: MediaDevices = {
|
export const devicesStub: MediaDevices = {
|
||||||
audioInput: deviceStub,
|
audioInput: deviceStub,
|
||||||
audioOutput: deviceStub,
|
audioOutput: deviceStub,
|
||||||
@@ -233,12 +213,12 @@ function useInputDevices(): InputDevices {
|
|||||||
const [numCallersUsingNames, setNumCallersUsingNames] = useState(0);
|
const [numCallersUsingNames, setNumCallersUsingNames] = useState(0);
|
||||||
const usingNames = numCallersUsingNames > 0;
|
const usingNames = numCallersUsingNames > 0;
|
||||||
|
|
||||||
const audioInput = useMediaDevice(
|
const audioInput = useMediaDeviceHandle(
|
||||||
"audioinput",
|
"audioinput",
|
||||||
audioInputSetting,
|
audioInputSetting,
|
||||||
usingNames,
|
usingNames,
|
||||||
);
|
);
|
||||||
const videoInput = useMediaDevice(
|
const videoInput = useMediaDeviceHandle(
|
||||||
"videoinput",
|
"videoinput",
|
||||||
videoInputSetting,
|
videoInputSetting,
|
||||||
usingNames,
|
usingNames,
|
||||||
@@ -275,23 +255,30 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
usingNames,
|
usingNames,
|
||||||
} = useInputDevices();
|
} = useInputDevices();
|
||||||
|
|
||||||
const audioOutput = useMediaDevice(
|
const { controlledOutput } = useUrlParams();
|
||||||
|
|
||||||
|
const webViewAudioOutput = useMediaDeviceHandle(
|
||||||
"audiooutput",
|
"audiooutput",
|
||||||
audioOutputSetting,
|
audioOutputSetting,
|
||||||
usingNames,
|
usingNames,
|
||||||
);
|
);
|
||||||
|
const controlledAudioOutput = useControlledOutput();
|
||||||
|
|
||||||
const context: MediaDevices = useMemo(
|
const context: MediaDevices = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
audioInput,
|
audioInput,
|
||||||
audioOutput,
|
audioOutput: controlledOutput
|
||||||
|
? controlledAudioOutput
|
||||||
|
: webViewAudioOutput,
|
||||||
videoInput,
|
videoInput,
|
||||||
startUsingDeviceNames,
|
startUsingDeviceNames,
|
||||||
stopUsingDeviceNames,
|
stopUsingDeviceNames,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
audioInput,
|
audioInput,
|
||||||
audioOutput,
|
controlledOutput,
|
||||||
|
controlledAudioOutput,
|
||||||
|
webViewAudioOutput,
|
||||||
videoInput,
|
videoInput,
|
||||||
startUsingDeviceNames,
|
startUsingDeviceNames,
|
||||||
stopUsingDeviceNames,
|
stopUsingDeviceNames,
|
||||||
@@ -305,29 +292,36 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function useControlledOutput(): MediaDevice {
|
function useControlledOutput(): MediaDeviceHandle {
|
||||||
const showEarpiece = useShowEarpiece();
|
const { available, physicalDeviceForEarpiceMode } = useObservableEagerState(
|
||||||
|
useObservable(() => {
|
||||||
const available = useObservableEagerState(
|
const showEarpice$ = alwaysShowIphoneEarpieceSetting.value$.pipe(
|
||||||
useObservable(() =>
|
startWith(alwaysShowIphoneEarpieceSetting.getValue()),
|
||||||
setOutputDevices$.pipe(
|
map((v) => v || navigator.userAgent.includes("iPhone")),
|
||||||
|
);
|
||||||
|
const outputDeviceData$ = setOutputDevices$.pipe(
|
||||||
startWith<OutputDevice[]>([]),
|
startWith<OutputDevice[]>([]),
|
||||||
map((devices) => {
|
map((devices) => {
|
||||||
const devicesMap = new Map<string, DeviceLabel>(
|
const physicalDeviceForEarpiceMode = devices.find(
|
||||||
devices.map(({ id, name }) => [id, { type: "name", name }]),
|
(d) => d.forEarpiece,
|
||||||
);
|
);
|
||||||
if (showEarpiece)
|
return {
|
||||||
devicesMap.set(EARPIECE_CONFIG_ID, { type: "earpiece" });
|
devicesMap: new Map<string, DeviceLabel>(
|
||||||
return devicesMap;
|
devices.map(({ id, name }) => [id, { type: "name", name }]),
|
||||||
|
),
|
||||||
|
physicalDeviceForEarpiceMode,
|
||||||
|
};
|
||||||
}),
|
}),
|
||||||
),
|
);
|
||||||
),
|
|
||||||
);
|
return combineLatest([outputDeviceData$, showEarpice$]).pipe(
|
||||||
const earpiceDevice = useObservableEagerState(
|
map(([{ devicesMap, physicalDeviceForEarpiceMode }, showEarpiece]) => {
|
||||||
setOutputDevices$.pipe(
|
if (showEarpiece && !!physicalDeviceForEarpiceMode)
|
||||||
startWith<OutputDevice[]>([]),
|
devicesMap.set(EARPIECE_CONFIG_ID, { type: "earpiece" });
|
||||||
map((devices) => devices.find((d) => d.forEarpiece)),
|
return { available: devicesMap, physicalDeviceForEarpiceMode };
|
||||||
),
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [preferredId, setPreferredId] = useSetting(audioOutputSetting);
|
const [preferredId, setPreferredId] = useSetting(audioOutputSetting);
|
||||||
@@ -348,74 +342,32 @@ function useControlledOutput(): MediaDevice {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}, [available, preferredId]);
|
}, [available, preferredId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedId === EARPIECE_CONFIG_ID)
|
|
||||||
if (selectedId !== undefined)
|
|
||||||
window.controls.onOutputDeviceSelect?.(selectedId);
|
|
||||||
}, [selectedId]);
|
|
||||||
|
|
||||||
const [asEarpice, setAsEarpiece] = useState(false);
|
const [asEarpice, setAsEarpiece] = useState(false);
|
||||||
|
|
||||||
const select = useCallback(
|
useEffect(() => {
|
||||||
(id: string) => {
|
let selectForController = selectedId;
|
||||||
if (id === EARPIECE_CONFIG_ID) {
|
const earpiece = selectedId === EARPIECE_CONFIG_ID;
|
||||||
setAsEarpiece(true);
|
|
||||||
if (earpiceDevice) setPreferredId(earpiceDevice.id);
|
setAsEarpiece(earpiece);
|
||||||
} else {
|
if (earpiece && physicalDeviceForEarpiceMode !== undefined)
|
||||||
setAsEarpiece(false);
|
selectForController = physicalDeviceForEarpiceMode.id;
|
||||||
setPreferredId(id);
|
|
||||||
}
|
if (selectForController)
|
||||||
},
|
window.controls.onOutputDeviceSelect?.(selectForController);
|
||||||
[earpiceDevice, setPreferredId],
|
}, [physicalDeviceForEarpiceMode, selectedId]);
|
||||||
);
|
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
available: available,
|
available: available,
|
||||||
selectedId,
|
selectedId,
|
||||||
selectedGroupId: undefined,
|
selectedGroupId: undefined,
|
||||||
select,
|
select: setPreferredId,
|
||||||
useAsEarpiece: asEarpice,
|
useAsEarpiece: asEarpice,
|
||||||
}),
|
}),
|
||||||
[available, selectedId, select, asEarpice],
|
[available, selectedId, setPreferredId, asEarpice],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ControlledOutputMediaDevicesProvider: FC<Props> = ({
|
|
||||||
children,
|
|
||||||
}) => {
|
|
||||||
const {
|
|
||||||
audioInput,
|
|
||||||
videoInput,
|
|
||||||
startUsingDeviceNames,
|
|
||||||
stopUsingDeviceNames,
|
|
||||||
} = useInputDevices();
|
|
||||||
const audioOutput = useControlledOutput();
|
|
||||||
|
|
||||||
const context: MediaDevices = useMemo(
|
|
||||||
() => ({
|
|
||||||
audioInput,
|
|
||||||
audioOutput,
|
|
||||||
videoInput,
|
|
||||||
startUsingDeviceNames,
|
|
||||||
stopUsingDeviceNames,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
audioInput,
|
|
||||||
audioOutput,
|
|
||||||
videoInput,
|
|
||||||
startUsingDeviceNames,
|
|
||||||
stopUsingDeviceNames,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<MediaDevicesContext.Provider value={context}>
|
|
||||||
{children}
|
|
||||||
</MediaDevicesContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useMediaDevices = (): MediaDevices =>
|
export const useMediaDevices = (): MediaDevices =>
|
||||||
useContext(MediaDevicesContext);
|
useContext(MediaDevicesContext);
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import { defaultLiveKitOptions } from "./options";
|
|||||||
import { type SFUConfig } from "./openIDSFU";
|
import { type SFUConfig } from "./openIDSFU";
|
||||||
import { type MuteStates } from "../room/MuteStates";
|
import { type MuteStates } from "../room/MuteStates";
|
||||||
import {
|
import {
|
||||||
type MediaDevice,
|
type MediaDeviceHandle,
|
||||||
type MediaDevices,
|
type MediaDevices,
|
||||||
useMediaDevices,
|
useMediaDevices,
|
||||||
} from "./MediaDevicesContext";
|
} from "./MediaDevicesContext";
|
||||||
@@ -306,7 +306,10 @@ export function useLivekit(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Sync the requested devices with LiveKit's devices
|
// Sync the requested devices with LiveKit's devices
|
||||||
if (room !== undefined && connectionState === ConnectionState.Connected) {
|
if (room !== undefined && connectionState === ConnectionState.Connected) {
|
||||||
const syncDevice = (kind: MediaDeviceKind, device: MediaDevice): void => {
|
const syncDevice = (
|
||||||
|
kind: MediaDeviceKind,
|
||||||
|
device: MediaDeviceHandle,
|
||||||
|
): void => {
|
||||||
const id = device.selectedId;
|
const id = device.selectedId;
|
||||||
|
|
||||||
// Detect if we're trying to use chrome's default device, in which case
|
// Detect if we're trying to use chrome's default device, in which case
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
} from "matrix-js-sdk/lib/matrixrtc";
|
} from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useObservableEagerState } from "observable-hooks";
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
import { startWith } from "rxjs";
|
||||||
|
|
||||||
import type { IWidgetApiRequest } from "matrix-widget-api";
|
import type { IWidgetApiRequest } from "matrix-widget-api";
|
||||||
import {
|
import {
|
||||||
@@ -106,7 +107,9 @@ export const GroupCallView: FC<Props> = ({
|
|||||||
const [externalError, setExternalError] = useState<ElementCallError | null>(
|
const [externalError, setExternalError] = useState<ElementCallError | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
const muteAllAudioControlled = useObservableEagerState(setOutputEnabled$);
|
const muteAllAudioControlled = useObservableEagerState(
|
||||||
|
setOutputEnabled$.pipe(startWith(false)),
|
||||||
|
);
|
||||||
const [muteAllAudioFromSetting] = useSetting(muteAllAudioSetting);
|
const [muteAllAudioFromSetting] = useSetting(muteAllAudioSetting);
|
||||||
const muteAllAudio = muteAllAudioControlled || muteAllAudioFromSetting;
|
const muteAllAudio = muteAllAudioControlled || muteAllAudioFromSetting;
|
||||||
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
const memberships = useMatrixRTCSessionMemberships(rtcSession);
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
import { type MatrixRTCSession } from "matrix-js-sdk/lib/matrixrtc";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { BehaviorSubject, map } from "rxjs";
|
import { BehaviorSubject, map, startWith } from "rxjs";
|
||||||
import { useObservable, useObservableEagerState } from "observable-hooks";
|
import { useObservable, useObservableEagerState } from "observable-hooks";
|
||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
|
import { RoomAndToDeviceEvents } from "matrix-js-sdk/lib/matrixrtc/RoomAndToDeviceKeyTransport";
|
||||||
@@ -223,7 +223,9 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
room: livekitRoom,
|
room: livekitRoom,
|
||||||
});
|
});
|
||||||
|
|
||||||
const muteAllAudioControlled = useObservableEagerState(setOutputEnabled$);
|
const muteAllAudioControlled = useObservableEagerState(
|
||||||
|
setOutputEnabled$.pipe(startWith(false)),
|
||||||
|
);
|
||||||
const [muteAllAudioFromSetting] = useSetting(muteAllAudioSetting);
|
const [muteAllAudioFromSetting] = useSetting(muteAllAudioSetting);
|
||||||
const muteAllAudio = muteAllAudioControlled || muteAllAudioFromSetting;
|
const muteAllAudio = muteAllAudioControlled || muteAllAudioFromSetting;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import userEvent from "@testing-library/user-event";
|
|||||||
import { useMuteStates } from "./MuteStates";
|
import { useMuteStates } from "./MuteStates";
|
||||||
import {
|
import {
|
||||||
type DeviceLabel,
|
type DeviceLabel,
|
||||||
type MediaDevice,
|
type MediaDeviceHandle,
|
||||||
type MediaDevices,
|
type MediaDevices,
|
||||||
MediaDevicesContext,
|
MediaDevicesContext,
|
||||||
} from "../livekit/MediaDevicesContext";
|
} from "../livekit/MediaDevicesContext";
|
||||||
@@ -73,7 +73,7 @@ const mockCamera: MediaDeviceInfo = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function mockDevices(available: Map<string, DeviceLabel>): MediaDevice {
|
function mockDevices(available: Map<string, DeviceLabel>): MediaDeviceHandle {
|
||||||
return {
|
return {
|
||||||
available,
|
available,
|
||||||
selectedId: "",
|
selectedId: "",
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { type IWidgetApiRequest } from "matrix-widget-api";
|
|||||||
import { logger } from "matrix-js-sdk/lib/logger";
|
import { logger } from "matrix-js-sdk/lib/logger";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type MediaDevice,
|
type MediaDeviceHandle,
|
||||||
useMediaDevices,
|
useMediaDevices,
|
||||||
} from "../livekit/MediaDevicesContext";
|
} from "../livekit/MediaDevicesContext";
|
||||||
import { useReactiveState } from "../useReactiveState";
|
import { useReactiveState } from "../useReactiveState";
|
||||||
@@ -53,7 +53,7 @@ export interface MuteStates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function useMuteState(
|
function useMuteState(
|
||||||
device: MediaDevice,
|
device: MediaDeviceHandle,
|
||||||
enabledByDefault: () => boolean,
|
enabledByDefault: () => boolean,
|
||||||
): MuteState {
|
): MuteState {
|
||||||
const [enabled, setEnabled] = useReactiveState<boolean | undefined>(
|
const [enabled, setEnabled] = useReactiveState<boolean | undefined>(
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ import { Trans, useTranslation } from "react-i18next";
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
EARPIECE_CONFIG_ID,
|
EARPIECE_CONFIG_ID,
|
||||||
type MediaDevice,
|
type MediaDeviceHandle,
|
||||||
} from "../livekit/MediaDevicesContext";
|
} from "../livekit/MediaDevicesContext";
|
||||||
import styles from "./DeviceSelection.module.css";
|
import styles from "./DeviceSelection.module.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
device: MediaDevice;
|
device: MediaDeviceHandle;
|
||||||
title: string;
|
title: string;
|
||||||
numberedLabel: (number: number) => string;
|
numberedLabel: (number: number) => string;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user