Add custom audio renderer to only render joined participants & add ios earpice workaround

fix left right to match chromium + safari
(firefox is swapped)

earpice as setting

Simpler code and documentation
The doc explains, what this class actually does and why it is so complicated.

Signed-off-by: Timo K <toger5@hotmail.de>

use only one audioContext, remove (non working) standby fallback
This commit is contained in:
Timo
2025-05-14 10:41:03 +02:00
parent 1ff2fb3fff
commit 56328108ca
9 changed files with 351 additions and 30 deletions

View File

@@ -18,6 +18,7 @@ import {
useNewMembershipManager as useNewMembershipManagerSetting,
useExperimentalToDeviceTransport as useExperimentalToDeviceTransportSetting,
muteAllAudio as muteAllAudioSetting,
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
} from "./settings";
import type { MatrixClient } from "matrix-js-sdk";
import type { Room as LivekitRoom } from "livekit-client";
@@ -46,6 +47,9 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRoom }) => {
useNewMembershipManagerSetting,
);
const [alwaysShowIphoneEarpiece, setAlwaysShowIphoneEarpiece] = useSetting(
alwaysShowIphoneEarpieceSetting,
);
const [
useExperimentalToDeviceTransport,
setUseExperimentalToDeviceTransport,
@@ -192,6 +196,20 @@ export const DeveloperSettingsTab: FC<Props> = ({ client, livekitRoom }) => {
[setMuteAllAudio],
)}
/>
</FieldRow>{" "}
<FieldRow>
<InputField
id="alwaysShowIphoneEarpiece"
type="checkbox"
label={t("developer_mode.always_show_iphone_earpiece")}
checked={alwaysShowIphoneEarpiece}
onChange={useCallback(
(event: ChangeEvent<HTMLInputElement>): void => {
setAlwaysShowIphoneEarpiece(event.target.checked);
},
[setAlwaysShowIphoneEarpiece],
)}
/>{" "}
</FieldRow>
{livekitRoom ? (
<>

View File

@@ -22,17 +22,20 @@ import {
} from "@vector-im/compound-web";
import { Trans, useTranslation } from "react-i18next";
import { type MediaDevice } from "../livekit/MediaDevicesContext";
import {
EARPIECE_CONFIG_ID,
type MediaDevice,
} from "../livekit/MediaDevicesContext";
import styles from "./DeviceSelection.module.css";
interface Props {
devices: MediaDevice;
device: MediaDevice;
title: string;
numberedLabel: (number: number) => string;
}
export const DeviceSelection: FC<Props> = ({
devices,
device,
title,
numberedLabel,
}) => {
@@ -40,12 +43,13 @@ export const DeviceSelection: FC<Props> = ({
const groupId = useId();
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
devices.select(e.target.value);
device.select(e.target.value);
},
[devices],
[device],
);
if (devices.available.size == 0) return null;
// There is no need to show the menu if there is no choice that can be made.
if (device.available.size == 1) return null;
return (
<div className={styles.selection}>
@@ -60,7 +64,7 @@ export const DeviceSelection: FC<Props> = ({
</Heading>
<Separator className={styles.separator} />
<div className={styles.options}>
{[...devices.available].map(([id, label]) => {
{[...device.available].map(([id, label]) => {
let labelText: ReactNode;
switch (label.type) {
case "name":
@@ -85,6 +89,16 @@ export const DeviceSelection: FC<Props> = ({
</Trans>
);
break;
case "earpiece":
labelText = t("settings.devices.earpiece");
break;
}
let isSelected = false;
if (device.useAsEarpiece) {
isSelected = id === EARPIECE_CONFIG_ID;
} else {
isSelected = id === device.selectedId;
}
return (
@@ -93,7 +107,7 @@ export const DeviceSelection: FC<Props> = ({
name={groupId}
control={
<RadioControl
checked={id === devices.selectedId}
checked={isSelected}
onChange={onChange}
value={id}
/>

View File

@@ -98,7 +98,6 @@ export const SettingsModal: FC<Props> = ({
useMediaDeviceNames(devices, open);
const [soundVolume, setSoundVolume] = useSetting(soundEffectVolumeSetting);
const [soundVolumeRaw, setSoundVolumeRaw] = useState(soundVolume);
const [showDeveloperSettingsTab] = useSetting(developerMode);
const { available: isRageshakeAvailable } = useSubmitRageshake();
@@ -110,17 +109,18 @@ export const SettingsModal: FC<Props> = ({
<>
<Form>
<DeviceSelection
devices={devices.audioInput}
device={devices.audioInput}
title={t("settings.devices.microphone")}
numberedLabel={(n) =>
t("settings.devices.microphone_numbered", { n })
}
/>
<DeviceSelection
devices={devices.audioOutput}
device={devices.audioOutput}
title={t("settings.devices.speaker")}
numberedLabel={(n) => t("settings.devices.speaker_numbered", { n })}
/>
<div className={styles.volumeSlider}>
<label>{t("settings.audio_tab.effect_volume_label")}</label>
<p>{t("settings.audio_tab.effect_volume_description")}</p>
@@ -146,7 +146,7 @@ export const SettingsModal: FC<Props> = ({
<>
<Form>
<DeviceSelection
devices={devices.videoInput}
device={devices.videoInput}
title={t("settings.devices.camera")}
numberedLabel={(n) => t("settings.devices.camera_numbered", { n })}
/>

View File

@@ -44,6 +44,9 @@ export class Setting<T> {
this._value$.next(value);
localStorage.setItem(this.key, JSON.stringify(value));
};
public readonly getValue = (): T => {
return this._value$.getValue();
};
}
/**
@@ -128,3 +131,8 @@ export const useExperimentalToDeviceTransport = new Setting<boolean>(
export const muteAllAudio = new Setting<boolean>("mute-all-audio", false);
export const alwaysShowSelf = new Setting<boolean>("always-show-self", true);
export const alwaysShowIphoneEarpiece = new Setting<boolean>(
"always-show-iphone-earpice",
false,
);