review
This commit is contained in:
@@ -12,10 +12,10 @@ A few aspects of Element Call's interface can be controlled through a global API
|
|||||||
|
|
||||||
These functions must be used in conjunction with the `controlledMediaDevices` URL parameter in order to have any effect.
|
These functions must be used in conjunction with the `controlledMediaDevices` URL parameter in order to have any effect.
|
||||||
|
|
||||||
- `controls.setAvailableOutputDevices(devices: { id: string, name: string, forEarpiece?: boolean, isEarpiece?: boolean isSpeaker?: boolean, isExternalHeadset?, boolean;}[]): void` Sets the list of available audio outputs. `forEarpiece` is used on ios only.
|
- `controls.setAvailableOutputDevices(devices: { id: string, name: string, forEarpiece?: boolean, isEarpiece?: boolean isSpeaker?: boolean, isExternalHeadset?, 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 earpiece mode. This should be the main stereo loudspeaker of the device.
|
It flags the device that should be used if the user selects earpiece 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.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 Element Call's menu. This should be used if the OS decides to automatically switch to Bluetooth, for example.
|
||||||
- `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.
|
- `showNativeOutputDevicePicker: (() => void) | undefined`. Callback called whenever the user presses the output button in the settings menu.
|
||||||
This button is only shown on ios. (`userAgent.includes("IPhone")`)
|
This button is only shown on iOS. (`userAgent.includes("iPhone")`)
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ These parameters are relevant to both [widget](./embedded-standalone.md) and [st
|
|||||||
| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. |
|
| `lang` | [BCP 47](https://www.rfc-editor.org/info/bcp47) code | No | No | The language the app should use. |
|
||||||
| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) |
|
| `password` | | No | No | E2EE password when using a shared secret. (For individual sender keys in embedded mode this is not required.) |
|
||||||
| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. |
|
| `perParticipantE2EE` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Enables per participant encryption with Keys exchanged over encrypted matrix room messages. |
|
||||||
| `controlledMediaDevices` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the [global JS controls for audio output devices](./controls.md#audio-devices) should be enabled, allowing the list of output devices to be controlled by the app hosting Element Call. |
|
| `controlledMediaDevices` | `true` or `false` | No, defaults to `false` | No, defaults to `false` | Whether the [global JS controls for audio output devices](./controls.md#audio-devices) should be enabled, allowing the list of output devices to be controlled by the app hosting Element Call. |
|
||||||
| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. |
|
| `roomId` | [Matrix Room ID](https://spec.matrix.org/v1.12/appendices/#room-ids) | Yes | No | Anything about what room we're pointed to should be from useRoomIdentifier which parses the path and resolves alias with respect to the default server name, however roomId is an exception as we need the room ID in embedded widget mode, and not the room alias (or even the via params because we are not trying to join it). This is also not validated, where it is in `useRoomIdentifier()`. |
|
||||||
| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. |
|
| `showControls` | `true` or `false` | No, defaults to `true` | No, defaults to `true` | Displays controls like mute, screen-share, invite, and hangup buttons during a call. |
|
||||||
| `skipLobby` (deprecated: use `intent` instead) | `true` or `false` | No. If `intent` is explicitly `start_call` then defaults to `true`. Otherwise defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) |
|
| `skipLobby` (deprecated: use `intent` instead) | `true` or `false` | No. If `intent` is explicitly `start_call` then defaults to `true`. Otherwise defaults to `false` | No, defaults to `false` | Skips the lobby to join a call directly, can be combined with preload in widget. When `true` the audio and video inputs will be muted by default. (This means there currently is no way to start without muted video if one wants to skip the lobby. Also not in widget mode.) |
|
||||||
|
|||||||
@@ -32,17 +32,13 @@ export interface OutputDevice {
|
|||||||
*/
|
*/
|
||||||
export const setPipEnabled$ = new Subject<boolean>();
|
export const setPipEnabled$ = new Subject<boolean>();
|
||||||
// BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state)
|
// BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state)
|
||||||
// We want the that has been set during loading to be be available immediately once loaded.
|
// We want the devices that have been set during loading to be available immediately once loaded.
|
||||||
export const setAvailableOutputDevices$ = new BehaviorSubject<OutputDevice[]>(
|
export const availableOutputDevices$ = new BehaviorSubject<OutputDevice[]>([]);
|
||||||
[],
|
|
||||||
);
|
|
||||||
// BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state)
|
// BehaviorSubject since the client might set this before we have subscribed (GroupCallView still in "loading" state)
|
||||||
// We want the that has been set during loading to be be available immediately once loaded.
|
// We want the device that has been set during loading to be available immediately once loaded.
|
||||||
export const setOutputDevice$ = new BehaviorSubject<string | undefined>(
|
export const outputDevice$ = new BehaviorSubject<string | undefined>(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
/**
|
/**
|
||||||
* This is currently unused. It might be possible to allow the os to mute the call this way if the user
|
* This allows the os to mute the call if the user
|
||||||
* presses the volume down button when it is at the minimum volume.
|
* presses the volume down button when it is at the minimum volume.
|
||||||
*
|
*
|
||||||
* This should also be used to display a darkened overlay screen letting the user know that audio is muted.
|
* This should also be used to display a darkened overlay screen letting the user know that audio is muted.
|
||||||
@@ -62,16 +58,16 @@ window.controls = {
|
|||||||
setPipEnabled$.next(false);
|
setPipEnabled$.next(false);
|
||||||
},
|
},
|
||||||
setAvailableOutputDevices(devices: OutputDevice[]): void {
|
setAvailableOutputDevices(devices: OutputDevice[]): void {
|
||||||
setAvailableOutputDevices$.next(devices);
|
availableOutputDevices$.next(devices);
|
||||||
},
|
},
|
||||||
setOutputDevice(id: string): void {
|
setOutputDevice(id: string): void {
|
||||||
setOutputDevice$.next(id);
|
outputDevice$.next(id);
|
||||||
},
|
},
|
||||||
setOutputEnabled(enabled: boolean): void {
|
setOutputEnabled(enabled: boolean): void {
|
||||||
if (!setOutputEnabled$.observed)
|
if (!setOutputEnabled$.observed)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Output controls are disabled. No setOutputEnabled$ observer",
|
"Output controls are disabled. No setOutputEnabled$ observer",
|
||||||
);
|
);
|
||||||
setOutputEnabled$.next(!enabled);
|
setOutputEnabled$.next(enabled);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import {
|
|||||||
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
alwaysShowIphoneEarpiece as alwaysShowIphoneEarpieceSetting,
|
||||||
type Setting,
|
type Setting,
|
||||||
} from "../settings/settings";
|
} from "../settings/settings";
|
||||||
import { setAvailableOutputDevices$, setOutputDevice$ } from "../controls";
|
import { outputDevice$, availableOutputDevices$ } from "../controls";
|
||||||
import { useUrlParams } from "../UrlParams";
|
import { useUrlParams } from "../UrlParams";
|
||||||
|
|
||||||
// This hardcoded id is used in EX ios! It can only be changed in coordination with
|
// This hardcoded id is used in EX ios! It can only be changed in coordination with
|
||||||
@@ -81,13 +81,9 @@ export interface MediaDevices extends Omit<InputDevices, "usingNames"> {
|
|||||||
* - hide any input devices (they do not work anyhow on ios)
|
* - hide any input devices (they do not work anyhow on ios)
|
||||||
* - Show a button to show the native output picker instead.
|
* - Show a button to show the native output picker instead.
|
||||||
* - Only show the earpiece toggle option if the earpiece is available:
|
* - Only show the earpiece toggle option if the earpiece is available:
|
||||||
* `setAvailableOutputDevices$.includes((d)=>d.forEarpiece)`
|
* `availableOutputDevices$.includes((d)=>d.forEarpiece)`
|
||||||
*/
|
*/
|
||||||
export const iosDeviceMenu$ = alwaysShowIphoneEarpieceSetting.value$.pipe(
|
export const iosDeviceMenu$ = alwaysShowIphoneEarpieceSetting.value$.pipe(
|
||||||
startWith(
|
|
||||||
alwaysShowIphoneEarpieceSetting.getValue() ||
|
|
||||||
navigator.userAgent.includes("iPhone"),
|
|
||||||
),
|
|
||||||
map((v) => v || navigator.userAgent.includes("iPhone")),
|
map((v) => v || navigator.userAgent.includes("iPhone")),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -115,8 +111,8 @@ function useSelectedId(
|
|||||||
/**
|
/**
|
||||||
* Hook to get access to a mediaDevice handle for a kind. This allows to list
|
* Hook to get access to a mediaDevice handle for a kind. This allows to list
|
||||||
* the available devices, read and set the selected device.
|
* the available devices, read and set the selected device.
|
||||||
* @param kind audio input, output or video output.
|
* @param kind Audio input, output or video output.
|
||||||
* @param setting The setting this handles selection should be synced with.
|
* @param setting The setting this handle's selection should be synced with.
|
||||||
* @param usingNames If the hook should query device names for the associated
|
* @param usingNames If the hook should query device names for the associated
|
||||||
* list.
|
* list.
|
||||||
* @returns A handle for the chosen kind.
|
* @returns A handle for the chosen kind.
|
||||||
@@ -320,7 +316,7 @@ export const MediaDevicesProvider: FC<Props> = ({ children }) => {
|
|||||||
function useControlledOutput(): MediaDeviceHandle {
|
function useControlledOutput(): MediaDeviceHandle {
|
||||||
const { available } = useObservableEagerState(
|
const { available } = useObservableEagerState(
|
||||||
useObservable(() => {
|
useObservable(() => {
|
||||||
const outputDeviceData$ = setAvailableOutputDevices$.pipe(
|
const outputDeviceData$ = availableOutputDevices$.pipe(
|
||||||
map((devices) => {
|
map((devices) => {
|
||||||
const deviceForEarpiece = devices.find((d) => d.forEarpiece);
|
const deviceForEarpiece = devices.find((d) => d.forEarpiece);
|
||||||
const deviceMapTuple: [string, DeviceLabel][] = devices.map(
|
const deviceMapTuple: [string, DeviceLabel][] = devices.map(
|
||||||
@@ -339,8 +335,9 @@ function useControlledOutput(): MediaDeviceHandle {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
return combineLatest([outputDeviceData$, iosDeviceMenu$]).pipe(
|
return combineLatest(
|
||||||
map(([{ devicesMap, deviceForEarpiece }, iosShowEarpiece]) => {
|
[outputDeviceData$, iosDeviceMenu$],
|
||||||
|
({ devicesMap, deviceForEarpiece }, iosShowEarpiece) => {
|
||||||
let available = devicesMap;
|
let available = devicesMap;
|
||||||
if (iosShowEarpiece && !!deviceForEarpiece) {
|
if (iosShowEarpiece && !!deviceForEarpiece) {
|
||||||
available = new Map([
|
available = new Map([
|
||||||
@@ -349,15 +346,16 @@ function useControlledOutput(): MediaDeviceHandle {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
return { available, deviceForEarpiece };
|
return { available, deviceForEarpiece };
|
||||||
}),
|
},
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const [preferredId, setPreferredId] = useSetting(audioOutputSetting);
|
const [preferredId, setPreferredId] = useSetting(audioOutputSetting);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOutputDevice$.subscribe((id) => {
|
const subscription = outputDevice$.subscribe((id) => {
|
||||||
if (id) setPreferredId(id);
|
if (id) setPreferredId(id);
|
||||||
});
|
});
|
||||||
|
return (): void => subscription.unsubscribe();
|
||||||
}, [setPreferredId]);
|
}, [setPreferredId]);
|
||||||
|
|
||||||
const selectedId = useSelectedId(available, preferredId);
|
const selectedId = useSelectedId(available, preferredId);
|
||||||
@@ -365,9 +363,10 @@ function useControlledOutput(): MediaDeviceHandle {
|
|||||||
const [asEarpiece, setAsEarpiece] = useState(false);
|
const [asEarpiece, setAsEarpiece] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// In earpiece mode we just sent the EARPIECE_CONFIG_ID to the native code
|
// Let the hosting application know which output device has been selected.
|
||||||
// This only happens on ios where we use the native picker.
|
// This information is probably only of interest if the earpiece mode has been
|
||||||
// So this only is needed so that ios can know if the proximity sensor should be used or not.
|
// selected - for example, Element X iOS listens to this to determine whether it
|
||||||
|
// should enable the proximity sensor.
|
||||||
if (selectedId) window.controls.onOutputDeviceSelect?.(selectedId);
|
if (selectedId) window.controls.onOutputDeviceSelect?.(selectedId);
|
||||||
setAsEarpiece(selectedId === EARPIECE_CONFIG_ID);
|
setAsEarpiece(selectedId === EARPIECE_CONFIG_ID);
|
||||||
}, [selectedId]);
|
}, [selectedId]);
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ export const SettingsModal: FC<Props> = ({
|
|||||||
const [showDeveloperSettingsTab] = useSetting(developerMode);
|
const [showDeveloperSettingsTab] = useSetting(developerMode);
|
||||||
|
|
||||||
const { available: isRageshakeAvailable } = useSubmitRageshake();
|
const { available: isRageshakeAvailable } = useSubmitRageshake();
|
||||||
// If we are on ios we will show a button to open the native picker.
|
// If we are on iOS we will show a button to open the native audio device picker.
|
||||||
const iosDeviceMenu = useObservableEagerState(iosDeviceMenu$);
|
const iosDeviceMenu = useObservableEagerState(iosDeviceMenu$);
|
||||||
// In controlled devices we will not show the input section
|
// In controlled devices we will not show the input section
|
||||||
const { controlledMediaDevices } = useUrlParams();
|
const { controlledMediaDevices } = useUrlParams();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|||||||
Please see LICENSE in the repository root for full details.
|
Please see LICENSE in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { combineLatest, map, startWith } from "rxjs";
|
import { combineLatest, startWith } from "rxjs";
|
||||||
|
|
||||||
import { setOutputEnabled$ } from "../controls";
|
import { setOutputEnabled$ } from "../controls";
|
||||||
import { muteAllAudio as muteAllAudioSetting } from "../settings/settings";
|
import { muteAllAudio as muteAllAudioSetting } from "../settings/settings";
|
||||||
@@ -13,10 +13,7 @@ import { muteAllAudio as muteAllAudioSetting } from "../settings/settings";
|
|||||||
/**
|
/**
|
||||||
* This can transition into sth more complete: `GroupCallViewModel.ts`
|
* This can transition into sth more complete: `GroupCallViewModel.ts`
|
||||||
*/
|
*/
|
||||||
export const muteAllAudio$ = combineLatest([
|
export const muteAllAudio$ = combineLatest(
|
||||||
setOutputEnabled$,
|
[setOutputEnabled$.pipe(startWith(true)), muteAllAudioSetting.value$],
|
||||||
muteAllAudioSetting.value$,
|
(outputEnabled, settingsMute) => !outputEnabled || settingsMute,
|
||||||
]).pipe(
|
|
||||||
startWith([true, muteAllAudioSetting.getValue()]),
|
|
||||||
map(([outputEnabled, settingsMute]) => !outputEnabled || settingsMute),
|
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user