add change audio button with callback on ios
This commit is contained in:
@@ -17,3 +17,5 @@ These functions must be used in conjunction with the `controlledOutput` URL para
|
|||||||
- `controls.onOutputDeviceSelect: ((id: string) => void) | undefined` Callback called whenever the user or application selects a new audio output.
|
- `controls.onOutputDeviceSelect: ((id: string) => void) | undefined` Callback called whenever the user or application selects a new audio output.
|
||||||
- `controls.setOutputDevice(id: string): void` Sets the selected audio device in EC menu. This should be used if the os decides to automatically switch to bluetooth.
|
- `controls.setOutputDevice(id: string): void` Sets the selected audio device in EC menu. This should be used if the os decides to automatically switch to bluetooth.
|
||||||
- `controls.setOutputEnabled(enabled: boolean)` Enables/disables all audio output from the application. This can be useful for temporarily pausing audio while the controlling application is switching output devices. Output is enabled by default.
|
- `controls.setOutputEnabled(enabled: boolean)` Enables/disables all audio output from the application. This can be useful for temporarily pausing audio while the controlling application is switching output devices. Output is enabled by default.
|
||||||
|
- `showNativeOutputDevicePicker: () => void`. This callback will be code by the webview if the user presses the output button in the settings menu.
|
||||||
|
This button is only shown on ios. (`userAgent.includes("IPhone")`)
|
||||||
|
|||||||
@@ -173,6 +173,7 @@
|
|||||||
"devices": {
|
"devices": {
|
||||||
"camera": "Camera",
|
"camera": "Camera",
|
||||||
"camera_numbered": "Camera {{n}}",
|
"camera_numbered": "Camera {{n}}",
|
||||||
|
"change_device_button": "Change audio device",
|
||||||
"default": "Default",
|
"default": "Default",
|
||||||
"default_named": "Default <2>({{name}})</2>",
|
"default_named": "Default <2>({{name}})</2>",
|
||||||
"earpiece": "Earpiece",
|
"earpiece": "Earpiece",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface Controls {
|
|||||||
setOutputDevice(id: string): void;
|
setOutputDevice(id: string): void;
|
||||||
onOutputDeviceSelect?: (id: string) => void;
|
onOutputDeviceSelect?: (id: string) => void;
|
||||||
setOutputEnabled(enabled: boolean): void;
|
setOutputEnabled(enabled: boolean): void;
|
||||||
|
showNativeOutputDevicePicker?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OutputDevice {
|
export interface OutputDevice {
|
||||||
|
|||||||
@@ -77,6 +77,22 @@ export interface MediaDevices extends Omit<InputDevices, "usingNames"> {
|
|||||||
audioOutput: MediaDeviceHandle;
|
audioOutput: MediaDeviceHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An observable that represents if we should display the devices menu for iOS.
|
||||||
|
* This implies the following
|
||||||
|
* - hide any input devices (they do not work anyhow on ios)
|
||||||
|
* - Show a button to show the native output picker instead.
|
||||||
|
* - Only show the earpice toggle option if the earpiece is available:
|
||||||
|
* `setAvailableOutputDevices$.includes((d)=>d.forEarpiece)`
|
||||||
|
*/
|
||||||
|
export const iosDeviceMenu$ = alwaysShowIphoneEarpieceSetting.value$.pipe(
|
||||||
|
startWith(
|
||||||
|
alwaysShowIphoneEarpieceSetting.getValue() ||
|
||||||
|
navigator.userAgent.includes("iPhone"),
|
||||||
|
),
|
||||||
|
map((v) => v || navigator.userAgent.includes("iPhone")),
|
||||||
|
);
|
||||||
|
|
||||||
function useSelectedId(
|
function useSelectedId(
|
||||||
available: Map<string, DeviceLabel>,
|
available: Map<string, DeviceLabel>,
|
||||||
preferredId: string | undefined,
|
preferredId: string | undefined,
|
||||||
@@ -304,37 +320,32 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function useControlledOutput(): MediaDeviceHandle {
|
function useControlledOutput(): MediaDeviceHandle {
|
||||||
const { available, physicalDeviceForEarpiceMode } = useObservableEagerState(
|
const { available, deviceForEarpiece: physicalDeviceForEarpiceMode } =
|
||||||
|
useObservableEagerState(
|
||||||
useObservable(() => {
|
useObservable(() => {
|
||||||
const showEarpice$ = alwaysShowIphoneEarpieceSetting.value$.pipe(
|
|
||||||
startWith(alwaysShowIphoneEarpieceSetting.getValue()),
|
|
||||||
map((v) => v || navigator.userAgent.includes("iPhone")),
|
|
||||||
);
|
|
||||||
const outputDeviceData$ = setAvailableOutputDevices$.pipe(
|
const outputDeviceData$ = setAvailableOutputDevices$.pipe(
|
||||||
startWith<OutputDevice[]>([]),
|
startWith<OutputDevice[]>([]),
|
||||||
map((devices) => {
|
map((devices) => {
|
||||||
const physicalDeviceForEarpiceMode = devices.find(
|
const deviceForEarpiece = devices.find((d) => d.forEarpiece);
|
||||||
(d) => d.forEarpiece,
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
devicesMap: new Map<string, DeviceLabel>(
|
devicesMap: new Map<string, DeviceLabel>(
|
||||||
devices.map(({ id, name }) => [id, { type: "name", name }]),
|
devices.map(({ id, name }) => [id, { type: "name", name }]),
|
||||||
),
|
),
|
||||||
physicalDeviceForEarpiceMode,
|
deviceForEarpiece,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return combineLatest([outputDeviceData$, showEarpice$]).pipe(
|
return combineLatest([outputDeviceData$, iosDeviceMenu$]).pipe(
|
||||||
map(([{ devicesMap, physicalDeviceForEarpiceMode }, showEarpiece]) => {
|
map(([{ devicesMap, deviceForEarpiece }, iosShowEarpiece]) => {
|
||||||
let available = devicesMap;
|
let available = devicesMap;
|
||||||
if (showEarpiece && !!physicalDeviceForEarpiceMode) {
|
if (iosShowEarpiece && !!deviceForEarpiece) {
|
||||||
available = new Map([
|
available = new Map([
|
||||||
...devicesMap.entries(),
|
...devicesMap.entries(),
|
||||||
[EARPIECE_CONFIG_ID, { type: "earpiece" }],
|
[EARPIECE_CONFIG_ID, { type: "earpiece" }],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return { available, physicalDeviceForEarpiceMode };
|
return { available, deviceForEarpiece };
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ Please see LICENSE in the repository root for full details.
|
|||||||
import { type FC, type ReactNode, useState } from "react";
|
import { type FC, type ReactNode, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { type MatrixClient } from "matrix-js-sdk";
|
import { type MatrixClient } from "matrix-js-sdk";
|
||||||
import { Root as Form, Separator } from "@vector-im/compound-web";
|
import { Button, Root as Form, Separator } from "@vector-im/compound-web";
|
||||||
import { type Room as LivekitRoom } from "livekit-client";
|
import { type Room as LivekitRoom } from "livekit-client";
|
||||||
|
import { useObservableEagerState } from "observable-hooks";
|
||||||
|
|
||||||
import { Modal } from "../Modal";
|
import { Modal } from "../Modal";
|
||||||
import styles from "./SettingsModal.module.css";
|
import styles from "./SettingsModal.module.css";
|
||||||
@@ -19,6 +20,7 @@ import { FeedbackSettingsTab } from "./FeedbackSettingsTab";
|
|||||||
import {
|
import {
|
||||||
useMediaDevices,
|
useMediaDevices,
|
||||||
useMediaDeviceNames,
|
useMediaDeviceNames,
|
||||||
|
iosDeviceMenu$,
|
||||||
} from "../livekit/MediaDevicesContext";
|
} from "../livekit/MediaDevicesContext";
|
||||||
import { widget } from "../widget";
|
import { widget } from "../widget";
|
||||||
import {
|
import {
|
||||||
@@ -101,6 +103,7 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
const [showDeveloperSettingsTab] = useSetting(developerMode);
|
const [showDeveloperSettingsTab] = useSetting(developerMode);
|
||||||
|
|
||||||
const { available: isRageshakeAvailable } = useSubmitRageshake();
|
const { available: isRageshakeAvailable } = useSubmitRageshake();
|
||||||
|
const iosDeviceMenu = useObservableEagerState(iosDeviceMenu$);
|
||||||
|
|
||||||
const audioTab: Tab<SettingsTab> = {
|
const audioTab: Tab<SettingsTab> = {
|
||||||
key: "audio",
|
key: "audio",
|
||||||
@@ -108,6 +111,7 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
content: (
|
content: (
|
||||||
<>
|
<>
|
||||||
<Form>
|
<Form>
|
||||||
|
{!iosDeviceMenu && (
|
||||||
<DeviceSelection
|
<DeviceSelection
|
||||||
device={devices.audioInput}
|
device={devices.audioInput}
|
||||||
title={t("settings.devices.microphone")}
|
title={t("settings.devices.microphone")}
|
||||||
@@ -115,6 +119,17 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
t("settings.devices.microphone_numbered", { n })
|
t("settings.devices.microphone_numbered", { n })
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
{iosDeviceMenu && (
|
||||||
|
<Button
|
||||||
|
kind="secondary"
|
||||||
|
onClick={(): void => {
|
||||||
|
window.controls.showNativeOutputDevicePicker?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("settings.devices.change_device_button")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<DeviceSelection
|
<DeviceSelection
|
||||||
device={devices.audioOutput}
|
device={devices.audioOutput}
|
||||||
title={t("settings.devices.speaker")}
|
title={t("settings.devices.speaker")}
|
||||||
|
|||||||
Reference in New Issue
Block a user