Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit fca6ff2

Browse files
authored
Extract requestMediaPermissions (#9568)
1 parent f2f00f6 commit fca6ff2

File tree

11 files changed

+196
-53
lines changed

11 files changed

+196
-53
lines changed

src/components/views/settings/tabs/user/VoiceUserSettingsTab.tsx

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,16 @@ limitations under the License.
1616
*/
1717

1818
import React from 'react';
19-
import { logger } from "matrix-js-sdk/src/logger";
2019

2120
import { _t } from "../../../../../languageHandler";
22-
import SdkConfig from "../../../../../SdkConfig";
2321
import MediaDeviceHandler, { IMediaDevices, MediaDeviceKindEnum } from "../../../../../MediaDeviceHandler";
2422
import Field from "../../../elements/Field";
2523
import AccessibleButton from "../../../elements/AccessibleButton";
2624
import { MatrixClientPeg } from "../../../../../MatrixClientPeg";
27-
import Modal from "../../../../../Modal";
2825
import { SettingLevel } from "../../../../../settings/SettingLevel";
2926
import SettingsFlag from '../../../elements/SettingsFlag';
3027
import LabelledToggleSwitch from "../../../elements/LabelledToggleSwitch";
31-
import ErrorDialog from '../../../dialogs/ErrorDialog';
28+
import { requestMediaPermissions } from '../../../../../utils/media/requestMediaPermissions';
3229

3330
const getDefaultDevice = (devices: Array<Partial<MediaDeviceInfo>>) => {
3431
// Note we're looking for a device with deviceId 'default' but adding a device
@@ -90,37 +87,8 @@ export default class VoiceUserSettingsTab extends React.Component<{}, IState> {
9087
};
9188

9289
private requestMediaPermissions = async (): Promise<void> => {
93-
let constraints;
94-
let stream;
95-
let error;
96-
try {
97-
constraints = { video: true, audio: true };
98-
stream = await navigator.mediaDevices.getUserMedia(constraints);
99-
} catch (err) {
100-
// user likely doesn't have a webcam,
101-
// we should still allow to select a microphone
102-
if (err.name === "NotFoundError") {
103-
constraints = { audio: true };
104-
try {
105-
stream = await navigator.mediaDevices.getUserMedia(constraints);
106-
} catch (err) {
107-
error = err;
108-
}
109-
} else {
110-
error = err;
111-
}
112-
}
113-
if (error) {
114-
logger.log("Failed to list userMedia devices", error);
115-
const brand = SdkConfig.get().brand;
116-
Modal.createDialog(ErrorDialog, {
117-
title: _t('No media permissions'),
118-
description: _t(
119-
'You may need to manually permit %(brand)s to access your microphone/webcam',
120-
{ brand },
121-
),
122-
});
123-
} else {
90+
const stream = await requestMediaPermissions();
91+
if (stream) {
12492
this.refreshMediaDevices(stream);
12593
}
12694
};

src/i18n/strings/en_EN.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,8 @@
754754
"Invite to %(spaceName)s": "Invite to %(spaceName)s",
755755
"Share your public space": "Share your public space",
756756
"Unknown App": "Unknown App",
757+
"No media permissions": "No media permissions",
758+
"You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam",
757759
"This homeserver is not configured to display maps.": "This homeserver is not configured to display maps.",
758760
"This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.": "This homeserver is not configured correctly to display maps, or the configured map server may be unreachable.",
759761
"Toggle attribution": "Toggle attribution",
@@ -1618,8 +1620,6 @@
16181620
"Rooms outside of a space": "Rooms outside of a space",
16191621
"Group all your rooms that aren't part of a space in one place.": "Group all your rooms that aren't part of a space in one place.",
16201622
"Default Device": "Default Device",
1621-
"No media permissions": "No media permissions",
1622-
"You may need to manually permit %(brand)s to access your microphone/webcam": "You may need to manually permit %(brand)s to access your microphone/webcam",
16231623
"Missing media permissions, click the button below to request.": "Missing media permissions, click the button below to request.",
16241624
"Request media permissions": "Request media permissions",
16251625
"Audio Output": "Audio Output",
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import { logger } from "matrix-js-sdk/src/logger";
18+
19+
import ErrorDialog from "../../components/views/dialogs/ErrorDialog";
20+
import { _t } from "../../languageHandler";
21+
import Modal from "../../Modal";
22+
import SdkConfig from "../../SdkConfig";
23+
24+
export const requestMediaPermissions = async (video = true): Promise<MediaStream | undefined> => {
25+
let stream: MediaStream | undefined;
26+
let error: any;
27+
28+
try {
29+
stream = await navigator.mediaDevices.getUserMedia({
30+
audio: true,
31+
video,
32+
});
33+
} catch (err: any) {
34+
// user likely doesn't have a webcam,
35+
// we should still allow to select a microphone
36+
if (video && err.name === "NotFoundError") {
37+
try {
38+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
39+
} catch (err) {
40+
error = err;
41+
}
42+
} else {
43+
error = err;
44+
}
45+
}
46+
if (error) {
47+
logger.log("Failed to list userMedia devices", error);
48+
const brand = SdkConfig.get().brand;
49+
Modal.createDialog(ErrorDialog, {
50+
title: _t('No media permissions'),
51+
description: _t(
52+
'You may need to manually permit %(brand)s to access your microphone/webcam',
53+
{ brand },
54+
),
55+
});
56+
}
57+
58+
return stream;
59+
};

test/components/views/messages/CallEvent-test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const CallEvent = wrapInMatrixClientContext(UnwrappedCallEvent);
4444

4545
describe("CallEvent", () => {
4646
useMockedCalls();
47-
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
4847
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
4948

5049
let client: Mocked<MatrixClient>;

test/components/views/rooms/RoomTile-test.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,6 @@ describe("RoomTile", () => {
4343
jest.spyOn(PlatformPeg, "get")
4444
.mockReturnValue({ overrideBrowserShortcuts: () => false } as unknown as BasePlatform);
4545
useMockedCalls();
46-
Object.defineProperty(navigator, "mediaDevices", {
47-
value: { enumerateDevices: async () => [] },
48-
});
4946

5047
let client: Mocked<MatrixClient>;
5148

test/components/views/voip/CallView-test.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,6 @@ const CallView = wrapInMatrixClientContext(_CallView);
4444

4545
describe("CallLobby", () => {
4646
useMockedCalls();
47-
Object.defineProperty(navigator, "mediaDevices", {
48-
value: {
49-
enumerateDevices: jest.fn(),
50-
getUserMedia: () => null,
51-
},
52-
});
5347
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
5448

5549
let client: Mocked<MatrixClient>;

test/components/views/voip/PipView-test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ import { mkVoiceBroadcastInfoStateEvent } from "../../../voice-broadcast/utils/t
5555

5656
describe("PipView", () => {
5757
useMockedCalls();
58-
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
5958
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => {});
6059

6160
let sdkContext: TestSdkContext;

test/setup/setupManualMocks.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,11 @@ fetchMock.catch("");
8686
fetchMock.get("/image-file-stub", "image file stub");
8787
// @ts-ignore
8888
window.fetch = fetchMock.sandbox();
89+
90+
// set up mediaDevices mock
91+
Object.defineProperty(navigator, "mediaDevices", {
92+
value: {
93+
enumerateDevices: jest.fn().mockResolvedValue([]),
94+
getUserMedia: jest.fn(),
95+
},
96+
});

test/stores/room-list/algorithms/Algorithm-test.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,6 @@ describe("Algorithm", () => {
8989
stop: () => {},
9090
} as unknown as ClientWidgetApi);
9191

92-
Object.defineProperty(navigator, "mediaDevices", {
93-
value: { enumerateDevices: async () => [] },
94-
});
95-
9692
// End of setup
9793

9894
expect(algorithm.getOrderedRooms()[DefaultTagID.Untagged]).toEqual([room, roomWithCall]);

test/toasts/IncomingCallToast-test.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import { getIncomingCallToastKey, IncomingCallToast } from "../../src/toasts/Inc
4242

4343
describe("IncomingCallEvent", () => {
4444
useMockedCalls();
45-
Object.defineProperty(navigator, "mediaDevices", { value: { enumerateDevices: () => [] } });
4645
jest.spyOn(HTMLMediaElement.prototype, "play").mockImplementation(async () => { });
4746

4847
let client: Mocked<MatrixClient>;

0 commit comments

Comments
 (0)