add earpice mode
This commit is contained in:
@@ -12,6 +12,7 @@ A few aspects of Element Call's interface can be controlled through a global API
|
|||||||
|
|
||||||
These functions must be used in conjunction with the `controlledOutput` URL parameter in order to have any effect.
|
These functions must be used in conjunction with the `controlledOutput` URL parameter in order to have any effect.
|
||||||
|
|
||||||
- `controls.setOutputDevices(devices: { id: string, name: string }[]): void` Sets the list of available audio outputs.
|
- `controls.setOutputDevices(devices: { id: string, name: string, forEarpiece?: boolean }[]): void` Sets the list of available audio outputs. `forEarpiece` is used on ios only.
|
||||||
|
It flags the device that should be used if the user selects earpice mode. This should be the main (stereo loudspeaker) of the device.
|
||||||
- `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.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.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ export interface Controls {
|
|||||||
export interface OutputDevice {
|
export interface OutputDevice {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
forEarpiece?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setPipEnabled$ = new Subject<boolean>();
|
export const setPipEnabled$ = new Subject<boolean>();
|
||||||
|
|||||||
@@ -71,6 +71,16 @@ interface InputDevices {
|
|||||||
export interface MediaDevices extends Omit<InputDevices, "usingNames"> {
|
export interface MediaDevices extends Omit<InputDevices, "usingNames"> {
|
||||||
audioOutput: MediaDevice;
|
audioOutput: MediaDevice;
|
||||||
}
|
}
|
||||||
|
function useShowEarpiece(): boolean {
|
||||||
|
const [alwaysShowIphoneEarpice] = useSetting(alwaysShowIphoneEarpieceSetting);
|
||||||
|
const m = useMemo(
|
||||||
|
() =>
|
||||||
|
(navigator.userAgent.match("iPhone")?.length ?? 0) > 0 ||
|
||||||
|
alwaysShowIphoneEarpice,
|
||||||
|
[alwaysShowIphoneEarpice],
|
||||||
|
);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
function useMediaDevice(
|
function useMediaDevice(
|
||||||
kind: MediaDeviceKind,
|
kind: MediaDeviceKind,
|
||||||
@@ -79,7 +89,7 @@ function useMediaDevice(
|
|||||||
): MediaDevice {
|
): MediaDevice {
|
||||||
// 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 [alwaysShowIphoneEarpice] = useSetting(alwaysShowIphoneEarpieceSetting);
|
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;
|
||||||
@@ -119,8 +129,6 @@ function useMediaDevice(
|
|||||||
// recognizes.
|
// recognizes.
|
||||||
// We also create this if we do not have any available devices, so that
|
// We also create this if we do not have any available devices, so that
|
||||||
// we can use the default or the earpiece.
|
// we can use the default or the earpiece.
|
||||||
const showEarpiece =
|
|
||||||
navigator.userAgent.match("iPhone") || alwaysShowIphoneEarpice;
|
|
||||||
if (
|
if (
|
||||||
kind === "audiooutput" &&
|
kind === "audiooutput" &&
|
||||||
!available.has("") &&
|
!available.has("") &&
|
||||||
@@ -144,7 +152,7 @@ function useMediaDevice(
|
|||||||
return available;
|
return available;
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
[alwaysShowIphoneEarpice, deviceObserver$, kind],
|
[deviceObserver$, kind, showEarpiece],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -298,20 +306,31 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function useControlledOutput(): MediaDevice {
|
function useControlledOutput(): MediaDevice {
|
||||||
|
const showEarpiece = useShowEarpiece();
|
||||||
|
|
||||||
const available = useObservableEagerState(
|
const available = useObservableEagerState(
|
||||||
useObservable(() =>
|
useObservable(() =>
|
||||||
setOutputDevices$.pipe(
|
setOutputDevices$.pipe(
|
||||||
startWith<OutputDevice[]>([]),
|
startWith<OutputDevice[]>([]),
|
||||||
map(
|
map((devices) => {
|
||||||
(devices) =>
|
const devicesMap = new Map<string, DeviceLabel>(
|
||||||
new Map<string, DeviceLabel>(
|
devices.map(({ id, name }) => [id, { type: "name", name }]),
|
||||||
devices.map(({ id, name }) => [id, { type: "name", name }]),
|
);
|
||||||
),
|
if (showEarpiece)
|
||||||
),
|
devicesMap.set(EARPIECE_CONFIG_ID, { type: "earpiece" });
|
||||||
|
return devicesMap;
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
const [preferredId, select] = useSetting(audioOutputSetting);
|
const earpiceDevice = useObservableEagerState(
|
||||||
|
setOutputDevices$.pipe(
|
||||||
|
map((devices) => devices.find((d) => d.forEarpiece)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const [preferredId, setPreferredId] = useSetting(audioOutputSetting);
|
||||||
|
|
||||||
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
|
||||||
@@ -327,19 +346,37 @@ function useControlledOutput(): MediaDevice {
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [available, preferredId]);
|
}, [available, preferredId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (selectedId !== undefined)
|
if (selectedId === EARPIECE_CONFIG_ID)
|
||||||
window.controls.onOutputDeviceSelect?.(selectedId);
|
if (selectedId !== undefined)
|
||||||
|
window.controls.onOutputDeviceSelect?.(selectedId);
|
||||||
}, [selectedId]);
|
}, [selectedId]);
|
||||||
|
|
||||||
|
const [asEarpice, setAsEarpiece] = useState(false);
|
||||||
|
|
||||||
|
const select = useCallback(
|
||||||
|
(id: string) => {
|
||||||
|
if (id === EARPIECE_CONFIG_ID) {
|
||||||
|
setAsEarpiece(true);
|
||||||
|
if (earpiceDevice) setPreferredId(earpiceDevice.id);
|
||||||
|
} else {
|
||||||
|
setAsEarpiece(false);
|
||||||
|
setPreferredId(id);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[earpiceDevice, setPreferredId],
|
||||||
|
);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
available,
|
available: available,
|
||||||
selectedId,
|
selectedId,
|
||||||
selectedGroupId: undefined,
|
selectedGroupId: undefined,
|
||||||
select,
|
select,
|
||||||
|
useAsEarpiece: asEarpice,
|
||||||
}),
|
}),
|
||||||
[available, selectedId, select],
|
[available, selectedId, select, asEarpice],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user