diff --git a/spec/test-utils/webrtc.ts b/spec/test-utils/webrtc.ts index 80ccbef26..e2118efa4 100644 --- a/spec/test-utils/webrtc.ts +++ b/spec/test-utils/webrtc.ts @@ -56,6 +56,9 @@ export const DUMMY_SDP = ( "a=ssrc:3619738545 cname:2RWtmqhXLdoF4sOi\r\n" ); +export const USERMEDIA_STREAM_ID = "mock_stream_from_media_handler"; +export const SCREENSHARE_STREAM_ID = "mock_screen_stream_from_media_handler"; + class MockMediaStreamAudioSourceNode { connect() {} } @@ -128,6 +131,10 @@ export class MockRTCPeerConnection { return new MockRTCRtpSender(track); } + removeTrack() { + this.needsNegotiation = true; + } + doNegotiation() { if (this.needsNegotiation && this.negotiationNeededListener) { this.needsNegotiation = false; @@ -222,7 +229,7 @@ export class MockMediaHandler { if (audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio")); if (video) tracks.push(new MockMediaStreamTrack("video_track", "video")); - const stream = new MockMediaStream("mock_stream_from_media_handler", tracks); + const stream = new MockMediaStream(USERMEDIA_STREAM_ID, tracks); this.userMediaStreams.push(stream); return stream; } @@ -233,7 +240,7 @@ export class MockMediaHandler { const tracks = [new MockMediaStreamTrack("video_track", "video")]; if (opts?.audio) tracks.push(new MockMediaStreamTrack("audio_track", "audio")); - const stream = new MockMediaStream("mock_screen_stream_from_media_handler", tracks); + const stream = new MockMediaStream(SCREENSHARE_STREAM_ID, tracks); this.screensharingStreams.push(stream); return stream; } diff --git a/spec/unit/webrtc/call.spec.ts b/spec/unit/webrtc/call.spec.ts index f7c51d10c..cac83f4a8 100644 --- a/spec/unit/webrtc/call.spec.ts +++ b/spec/unit/webrtc/call.spec.ts @@ -30,6 +30,8 @@ import { MockMediaStream, MockMediaStreamTrack, installWebRTCMocks, + MockRTCPeerConnection, + SCREENSHARE_STREAM_ID, } from "../../test-utils/webrtc"; import { CallFeed } from "../../../src/webrtc/callFeed"; import { EventType, MatrixEvent } from "../../../src"; @@ -117,6 +119,9 @@ describe('Call', function() { }); afterEach(function() { + // Hangup to stop timers + call.hangup(CallErrorCode.UserHangup, true); + client.stop(); global.navigator = prevNavigator; global.window = prevWindow; @@ -178,9 +183,6 @@ describe('Call', function() { getSender: () => "@test:foo", }); expect(call.peerConn.addIceCandidate.mock.calls.length).toBe(1); - - // Hangup to stop timers - call.hangup(CallErrorCode.UserHangup, true); }); it('should add candidates received before answer if party ID is correct', async function() { @@ -283,9 +285,6 @@ describe('Call', function() { const ident = call.getRemoteAssertedIdentity(); expect(ident.id).toEqual("@steve:example.com"); expect(ident.displayName).toEqual("Steve Gibbons"); - - // Hangup to stop timers - call.hangup(CallErrorCode.UserHangup, true); }); it("should map SDPStreamMetadata to feeds", async () => { @@ -734,16 +733,18 @@ describe('Call', function() { describe("ignoring streams with ids for which we already have a feed", () => { const STREAM_ID = "stream_id"; - const FEEDS_CHANGED_CALLBACK = jest.fn(); + let FEEDS_CHANGED_CALLBACK: jest.Mock; beforeEach(async () => { + FEEDS_CHANGED_CALLBACK = jest.fn(); + await startVoiceCall(client, call); call.on(CallEvent.FeedsChanged, FEEDS_CHANGED_CALLBACK); jest.spyOn(call, "pushLocalFeed"); }); afterEach(() => { - FEEDS_CHANGED_CALLBACK.mockReset(); + call.off(CallEvent.FeedsChanged, FEEDS_CHANGED_CALLBACK); }); it("should ignore stream passed to pushRemoteFeed()", async () => { @@ -941,4 +942,70 @@ describe('Call', function() { expect(call.state).toEqual(CallState.Ended); }); + + describe("Screen sharing", () => { + beforeEach(async () => { + await startVoiceCall(client, call); + + await call.onAnswerReceived({ + getContent: () => { + return { + "version": 1, + "call_id": call.callId, + "party_id": 'party_id', + "answer": { + sdp: DUMMY_SDP, + }, + "org.matrix.msc3077.sdp_stream_metadata": { + "foo": { + "purpose": "m.usermedia", + "audio_muted": false, + "video_muted": false, + }, + }, + }; + }, + getSender: () => "@test:foo", + }); + }); + + afterEach(() => { + // Hangup to stop timers + call.hangup(CallErrorCode.UserHangup, true); + }); + + it("enables and disables screensharing", async () => { + await call.setScreensharingEnabled(true); + + expect(call.feeds.filter(f => f.purpose == SDPStreamMetadataPurpose.Screenshare).length).toEqual(1); + + client.client.sendEvent.mockReset(); + const sendNegotiatePromise = new Promise(resolve => { + client.client.sendEvent.mockImplementationOnce(() => { + resolve(); + }); + }); + + MockRTCPeerConnection.triggerAllNegotiations(); + await sendNegotiatePromise; + + expect(client.client.sendEvent).toHaveBeenCalledWith( + FAKE_ROOM_ID, + EventType.CallNegotiate, + expect.objectContaining({ + "version": "1", + "call_id": call.callId, + "org.matrix.msc3077.sdp_stream_metadata": expect.objectContaining({ + [SCREENSHARE_STREAM_ID]: expect.objectContaining({ + purpose: SDPStreamMetadataPurpose.Screenshare, + }), + }), + }), + ); + + await call.setScreensharingEnabled(false); + + expect(call.feeds.filter(f => f.purpose == SDPStreamMetadataPurpose.Screenshare).length).toEqual(0); + }); + }); }); diff --git a/src/webrtc/call.ts b/src/webrtc/call.ts index 8e14e06a8..4889c9421 100644 --- a/src/webrtc/call.ts +++ b/src/webrtc/call.ts @@ -2240,7 +2240,7 @@ export class MatrixCall extends TypedEventEmitter