force to allow calls without video and audio in embedded mode (#11131)
* force to allow calls without video and audio in embedded mode * Check device access permission and introduce a only screen share call mode * Fix strict typ check issue * Fix i18n check issue * Add unit tests for device selection * Fix mocked media device querypull/28788/head^2
parent
3c81f30c26
commit
902263d7c9
|
@ -32,7 +32,7 @@ import MatrixClientContext from "../../../contexts/MatrixClientContext";
|
||||||
import AppTile from "../elements/AppTile";
|
import AppTile from "../elements/AppTile";
|
||||||
import { _t } from "../../../languageHandler";
|
import { _t } from "../../../languageHandler";
|
||||||
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
|
import { useAsyncMemo } from "../../../hooks/useAsyncMemo";
|
||||||
import MediaDeviceHandler from "../../../MediaDeviceHandler";
|
import MediaDeviceHandler, { IMediaDevices } from "../../../MediaDeviceHandler";
|
||||||
import { CallStore } from "../../../stores/CallStore";
|
import { CallStore } from "../../../stores/CallStore";
|
||||||
import IconizedContextMenu, {
|
import IconizedContextMenu, {
|
||||||
IconizedContextMenuOption,
|
IconizedContextMenuOption,
|
||||||
|
@ -149,14 +149,32 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
|
||||||
setVideoMuted(!videoMuted);
|
setVideoMuted(!videoMuted);
|
||||||
}, [videoMuted, setVideoMuted]);
|
}, [videoMuted, setVideoMuted]);
|
||||||
|
|
||||||
|
// In case we can not fetch media devices we should mute the devices
|
||||||
|
const handleMediaDeviceFailing = (message: string): void => {
|
||||||
|
MediaDeviceHandler.startWithAudioMuted = true;
|
||||||
|
MediaDeviceHandler.startWithVideoMuted = true;
|
||||||
|
logger.warn(message);
|
||||||
|
};
|
||||||
|
|
||||||
const [videoStream, audioInputs, videoInputs] = useAsyncMemo(
|
const [videoStream, audioInputs, videoInputs] = useAsyncMemo(
|
||||||
async (): Promise<[MediaStream | null, MediaDeviceInfo[], MediaDeviceInfo[]]> => {
|
async (): Promise<[MediaStream | null, MediaDeviceInfo[], MediaDeviceInfo[]]> => {
|
||||||
let devices = await MediaDeviceHandler.getDevices();
|
let devices: IMediaDevices | undefined;
|
||||||
|
try {
|
||||||
|
devices = await MediaDeviceHandler.getDevices();
|
||||||
|
if (devices === undefined) {
|
||||||
|
handleMediaDeviceFailing("Could not access devices!");
|
||||||
|
return [null, [], []];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
handleMediaDeviceFailing(`Unable to get Media Devices: ${error}`);
|
||||||
|
return [null, [], []];
|
||||||
|
}
|
||||||
|
|
||||||
// We get the preview stream before requesting devices: this is because
|
// We get the preview stream before requesting devices: this is because
|
||||||
// we need (in some browsers) an active media stream in order to get
|
// we need (in some browsers) an active media stream in order to get
|
||||||
// non-blank labels for the devices.
|
// non-blank labels for the devices.
|
||||||
let stream: MediaStream | null = null;
|
let stream: MediaStream | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (devices!.audioinput.length > 0) {
|
if (devices!.audioinput.length > 0) {
|
||||||
// Holding just an audio stream will be enough to get us all device labels, so
|
// Holding just an audio stream will be enough to get us all device labels, so
|
||||||
|
@ -170,7 +188,8 @@ export const Lobby: FC<LobbyProps> = ({ room, joinCallButtonDisabledTooltip, con
|
||||||
stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoInputId } });
|
stream = await navigator.mediaDevices.getUserMedia({ video: { deviceId: videoInputId } });
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to get stream for device ${videoInputId}`, e);
|
logger.warn(`Failed to get stream for device ${videoInputId}`, e);
|
||||||
|
handleMediaDeviceFailing(`Have access to Device list but unable to read from Media Devices`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh the devices now that we hold a stream
|
// Refresh the devices now that we hold a stream
|
||||||
|
|
|
@ -986,6 +986,8 @@
|
||||||
"Under active development, cannot be disabled.": "Under active development, cannot be disabled.",
|
"Under active development, cannot be disabled.": "Under active development, cannot be disabled.",
|
||||||
"Element Call video rooms": "Element Call video rooms",
|
"Element Call video rooms": "Element Call video rooms",
|
||||||
"New group call experience": "New group call experience",
|
"New group call experience": "New group call experience",
|
||||||
|
"Under active development.": "Under active development.",
|
||||||
|
"Allow screen share only mode": "Allow screen share only mode",
|
||||||
"Live Location Sharing": "Live Location Sharing",
|
"Live Location Sharing": "Live Location Sharing",
|
||||||
"Temporary implementation. Locations persist in room history.": "Temporary implementation. Locations persist in room history.",
|
"Temporary implementation. Locations persist in room history.": "Temporary implementation. Locations persist in room history.",
|
||||||
"Dynamic room predecessors": "Dynamic room predecessors",
|
"Dynamic room predecessors": "Dynamic room predecessors",
|
||||||
|
@ -993,7 +995,6 @@
|
||||||
"Force 15s voice broadcast chunk length": "Force 15s voice broadcast chunk length",
|
"Force 15s voice broadcast chunk length": "Force 15s voice broadcast chunk length",
|
||||||
"Enable new native OIDC flows (Under active development)": "Enable new native OIDC flows (Under active development)",
|
"Enable new native OIDC flows (Under active development)": "Enable new native OIDC flows (Under active development)",
|
||||||
"Rust cryptography implementation": "Rust cryptography implementation",
|
"Rust cryptography implementation": "Rust cryptography implementation",
|
||||||
"Under active development.": "Under active development.",
|
|
||||||
"Font size": "Font size",
|
"Font size": "Font size",
|
||||||
"Use custom size": "Use custom size",
|
"Use custom size": "Use custom size",
|
||||||
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
|
||||||
|
|
|
@ -660,6 +660,7 @@ export class ElementCall extends Call {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (SettingsStore.getValue("fallbackICEServerAllowed")) params.append("allowIceFallback", "");
|
if (SettingsStore.getValue("fallbackICEServerAllowed")) params.append("allowIceFallback", "");
|
||||||
|
if (SettingsStore.getValue("feature_allow_screen_share_only_mode")) params.append("allowVoipWithNoMedia", "");
|
||||||
|
|
||||||
// Set custom fonts
|
// Set custom fonts
|
||||||
if (SettingsStore.getValue("useSystemFont")) {
|
if (SettingsStore.getValue("useSystemFont")) {
|
||||||
|
|
|
@ -433,6 +433,15 @@ export const SETTINGS: { [setting: string]: ISetting } = {
|
||||||
controller: new ReloadOnChangeController(),
|
controller: new ReloadOnChangeController(),
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
"feature_allow_screen_share_only_mode": {
|
||||||
|
isFeature: true,
|
||||||
|
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
|
||||||
|
description: _td("Under active development."),
|
||||||
|
labsGroup: LabGroup.VoiceAndVideo,
|
||||||
|
displayName: _td("Allow screen share only mode"),
|
||||||
|
controller: new ReloadOnChangeController(),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
"feature_location_share_live": {
|
"feature_location_share_live": {
|
||||||
isFeature: true,
|
isFeature: true,
|
||||||
labsGroup: LabGroup.Messaging,
|
labsGroup: LabGroup.Messaging,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { WidgetMessagingStore } from "../../../../src/stores/widgets/WidgetMessa
|
||||||
import { CallStore } from "../../../../src/stores/CallStore";
|
import { CallStore } from "../../../../src/stores/CallStore";
|
||||||
import { Call, ConnectionState } from "../../../../src/models/Call";
|
import { Call, ConnectionState } from "../../../../src/models/Call";
|
||||||
import SdkConfig from "../../../../src/SdkConfig";
|
import SdkConfig from "../../../../src/SdkConfig";
|
||||||
|
import MediaDeviceHandler from "../../../../src/MediaDeviceHandler";
|
||||||
|
|
||||||
const CallView = wrapInMatrixClientContext(_CallView);
|
const CallView = wrapInMatrixClientContext(_CallView);
|
||||||
|
|
||||||
|
@ -247,6 +248,26 @@ describe("CallLobby", () => {
|
||||||
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hide when no access to device list", async () => {
|
||||||
|
mocked(navigator.mediaDevices.enumerateDevices).mockRejectedValue("permission denied");
|
||||||
|
await renderView();
|
||||||
|
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||||
|
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||||
|
expect(screen.queryByRole("button", { name: /microphone/ })).toBe(null);
|
||||||
|
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("hide when unknown error with device list", async () => {
|
||||||
|
const originalGetDevices = MediaDeviceHandler.getDevices;
|
||||||
|
MediaDeviceHandler.getDevices = () => Promise.reject("unknown error");
|
||||||
|
await renderView();
|
||||||
|
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||||
|
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||||
|
expect(screen.queryByRole("button", { name: /microphone/ })).toBe(null);
|
||||||
|
expect(screen.queryByRole("button", { name: /camera/ })).toBe(null);
|
||||||
|
MediaDeviceHandler.getDevices = originalGetDevices;
|
||||||
|
});
|
||||||
|
|
||||||
it("show without dropdown when only one device is available", async () => {
|
it("show without dropdown when only one device is available", async () => {
|
||||||
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1]);
|
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1]);
|
||||||
|
|
||||||
|
@ -286,5 +307,21 @@ describe("CallLobby", () => {
|
||||||
|
|
||||||
expect(client.getMediaHandler().setAudioInput).toHaveBeenCalledWith(fakeAudioInput2.deviceId);
|
expect(client.getMediaHandler().setAudioInput).toHaveBeenCalledWith(fakeAudioInput2.deviceId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("set media muted if no access to audio device", async () => {
|
||||||
|
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeAudioInput1, fakeAudioInput2]);
|
||||||
|
mocked(navigator.mediaDevices.getUserMedia).mockRejectedValue("permission rejected");
|
||||||
|
await renderView();
|
||||||
|
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||||
|
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("set media muted if no access to video device", async () => {
|
||||||
|
mocked(navigator.mediaDevices.enumerateDevices).mockResolvedValue([fakeVideoInput1, fakeVideoInput2]);
|
||||||
|
mocked(navigator.mediaDevices.getUserMedia).mockRejectedValue("permission rejected");
|
||||||
|
await renderView();
|
||||||
|
expect(MediaDeviceHandler.startWithAudioMuted).toBeTruthy();
|
||||||
|
expect(MediaDeviceHandler.startWithVideoMuted).toBeTruthy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue