diff --git a/.gitignore b/.gitignore index 6bc9edb40a3..98d71fcd60a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ out .vscode .vscode/ +.idea/ diff --git a/spec/unit/webrtc/groupCall.spec.ts b/spec/unit/webrtc/groupCall.spec.ts index 4b68100113e..02e3a18b495 100644 --- a/spec/unit/webrtc/groupCall.spec.ts +++ b/spec/unit/webrtc/groupCall.spec.ts @@ -109,7 +109,7 @@ const mockGetStateEvents = (type: EventType, userId?: string): MatrixEvent[] | M const ONE_HOUR = 1000 * 60 * 60; const createAndEnterGroupCall = async (cli: MatrixClient, room: Room): Promise => { - const groupCall = new GroupCall(cli, room, GroupCallType.Video, false, GroupCallIntent.Prompt, FAKE_CONF_ID); + const groupCall = new GroupCall(cli, room, GroupCallType.Video, false, GroupCallIntent.Prompt, false, FAKE_CONF_ID); await groupCall.create(); await groupCall.enter(); @@ -135,7 +135,7 @@ describe("Group Call", function () { mockClient = typedMockClient as unknown as MatrixClient; room = new Room(FAKE_ROOM_ID, mockClient, FAKE_USER_ID_1); - groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt); + groupCall = new GroupCall(mockClient, room, GroupCallType.Video, false, GroupCallIntent.Prompt, false); room.currentState.members[FAKE_USER_ID_1] = { userId: FAKE_USER_ID_1, membership: "join", @@ -484,7 +484,7 @@ describe("Group Call", function () { describe("PTT calls", () => { beforeEach(async () => { // replace groupcall with a PTT one - groupCall = new GroupCall(mockClient, room, GroupCallType.Video, true, GroupCallIntent.Prompt); + groupCall = new GroupCall(mockClient, room, GroupCallType.Video, true, GroupCallIntent.Prompt, false); await groupCall.create(); @@ -647,6 +647,7 @@ describe("Group Call", function () { GroupCallType.Video, false, GroupCallIntent.Prompt, + false, FAKE_CONF_ID, ); @@ -656,6 +657,7 @@ describe("Group Call", function () { GroupCallType.Video, false, GroupCallIntent.Prompt, + false, FAKE_CONF_ID, ); }); @@ -882,11 +884,27 @@ describe("Group Call", function () { expect(await groupCall.setMicrophoneMuted(false)).toBe(false); }); + it("returns false when no permission for audio stream", async () => { + const groupCall = await createAndEnterGroupCall(mockClient, room); + jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream").mockRejectedValueOnce( + new Error("No Permission"), + ); + expect(await groupCall.setMicrophoneMuted(false)).toBe(false); + }); + it("returns false when unmuting video with no video device", async () => { const groupCall = await createAndEnterGroupCall(mockClient, room); jest.spyOn(mockClient.getMediaHandler(), "hasVideoDevice").mockResolvedValue(false); expect(await groupCall.setLocalVideoMuted(false)).toBe(false); }); + + it("returns false when no permission for video stream", async () => { + const groupCall = await createAndEnterGroupCall(mockClient, room); + jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream").mockRejectedValueOnce( + new Error("No Permission"), + ); + expect(await groupCall.setLocalVideoMuted(false)).toBe(false); + }); }); describe("remote muting", () => { @@ -1465,6 +1483,7 @@ describe("Group Call", function () { GroupCallType.Video, false, GroupCallIntent.Prompt, + false, FAKE_CONF_ID, ); await groupCall.create(); diff --git a/spec/unit/webrtc/mediaHandler.spec.ts b/spec/unit/webrtc/mediaHandler.spec.ts index 1b3a815b00a..f50baec1f06 100644 --- a/spec/unit/webrtc/mediaHandler.spec.ts +++ b/spec/unit/webrtc/mediaHandler.spec.ts @@ -242,6 +242,11 @@ describe("Media Handler", function () { ); expect(await mediaHandler.hasAudioDevice()).toEqual(false); }); + + it("returns false if the system not permitting access audio inputs", async () => { + mockMediaDevices.enumerateDevices.mockRejectedValueOnce(new Error("No Permission")); + expect(await mediaHandler.hasAudioDevice()).toEqual(false); + }); }); describe("hasVideoDevice", () => { @@ -255,6 +260,11 @@ describe("Media Handler", function () { ); expect(await mediaHandler.hasVideoDevice()).toEqual(false); }); + + it("returns false if the system not permitting access video inputs", async () => { + mockMediaDevices.enumerateDevices.mockRejectedValueOnce(new Error("No Permission")); + expect(await mediaHandler.hasVideoDevice()).toEqual(false); + }); }); describe("getUserMediaStream", () => { diff --git a/src/client.ts b/src/client.ts index d59f5cc5d68..963c244b689 100644 --- a/src/client.ts +++ b/src/client.ts @@ -371,6 +371,13 @@ export interface ICreateClientOpts { * Defaults to a built-in English handler with basic pluralisation. */ roomNameGenerator?: (roomId: string, state: RoomNameState) => string | null; + + /** + * If true, participant can join group call without video and audio this has to be allowed. By default, a local + * media stream is needed to establish a group call. + * Default: false. + */ + isVoipWithNoMediaAllowed?: boolean; } export interface IMatrixClientCreateOpts extends ICreateClientOpts { @@ -1169,6 +1176,7 @@ export class MatrixClient extends TypedEventEmitter; + // Used to allow connection without Video and Audio. To establish a webrtc connection without media a Data channel is + // needed At the moment this property is true if we allow MatrixClient with isVoipWithNoMediaAllowed = true + private readonly isOnlyDataChannelAllowed: boolean; /** * Construct a new Matrix Call. @@ -420,6 +423,8 @@ export class MatrixCall extends TypedEventEmitter { - const devices = await navigator.mediaDevices.enumerateDevices(); - return devices.filter((device) => device.kind === "audioinput").length > 0; + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.filter((device) => device.kind === "audioinput").length > 0; + } catch (err) { + logger.log(`MediaHandler hasAudioDevice() calling navigator.mediaDevices.enumerateDevices with error`, err); + return false; + } } public async hasVideoDevice(): Promise { - const devices = await navigator.mediaDevices.enumerateDevices(); - return devices.filter((device) => device.kind === "videoinput").length > 0; + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + return devices.filter((device) => device.kind === "videoinput").length > 0; + } catch (err) { + logger.log(`MediaHandler hasVideoDevice() calling navigator.mediaDevices.enumerateDevices with error`, err); + return false; + } } /**