Skip to content

Commit fdb80ad

Browse files
Remove video track when muting video (#3028)
1 parent bba4a35 commit fdb80ad

File tree

5 files changed

+65
-29
lines changed

5 files changed

+65
-29
lines changed

spec/unit/webrtc/groupCall.spec.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,10 @@ describe("Group Call", function () {
810810
it("should mute local video when calling setLocalVideoMuted()", async () => {
811811
const groupCall = await createAndEnterGroupCall(mockClient, room);
812812

813-
groupCall.localCallFeed!.setAudioVideoMuted = jest.fn();
813+
jest.spyOn(mockClient.getMediaHandler(), "getUserMediaStream");
814+
jest.spyOn(groupCall, "updateLocalUsermediaStream");
815+
jest.spyOn(groupCall.localCallFeed!, "setAudioVideoMuted");
816+
814817
const setAVMutedArray: ((audioMuted: boolean | null, videoMuted: boolean | null) => void)[] = [];
815818
const tracksArray: MediaStreamTrack[] = [];
816819
const sendMetadataUpdateArray: (() => Promise<void>)[] = [];
@@ -824,7 +827,8 @@ describe("Group Call", function () {
824827
await groupCall.setLocalVideoMuted(true);
825828

826829
groupCall.localCallFeed!.stream.getVideoTracks().forEach((track) => expect(track.enabled).toBe(false));
827-
expect(groupCall.localCallFeed!.setAudioVideoMuted).toHaveBeenCalledWith(null, true);
830+
expect(mockClient.getMediaHandler().getUserMediaStream).toHaveBeenCalledWith(true, false);
831+
expect(groupCall.updateLocalUsermediaStream).toHaveBeenCalled();
828832
setAVMutedArray.forEach((f) => expect(f).toHaveBeenCalledWith(null, true));
829833
tracksArray.forEach((track) => expect(track.enabled).toBe(false));
830834
sendMetadataUpdateArray.forEach((f) => expect(f).toHaveBeenCalled());

spec/unit/webrtc/mediaHandler.spec.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -308,20 +308,18 @@ describe("Media Handler", function () {
308308
expect(stream2.isCloneOf(stream1)).toEqual(false);
309309
});
310310

311-
it("strips unwanted audio tracks from re-used stream", async () => {
312-
const stream1 = await mediaHandler.getUserMediaStream(true, true);
313-
const stream2 = (await mediaHandler.getUserMediaStream(false, true)) as unknown as MockMediaStream;
311+
it("creates new stream when we no longer want audio", async () => {
312+
await mediaHandler.getUserMediaStream(true, true);
313+
const stream = await mediaHandler.getUserMediaStream(false, true);
314314

315-
expect(stream2.isCloneOf(stream1)).toEqual(true);
316-
expect(stream2.getAudioTracks().length).toEqual(0);
315+
expect(stream.getAudioTracks().length).toEqual(0);
317316
});
318317

319-
it("strips unwanted video tracks from re-used stream", async () => {
320-
const stream1 = await mediaHandler.getUserMediaStream(true, true);
321-
const stream2 = (await mediaHandler.getUserMediaStream(true, false)) as unknown as MockMediaStream;
318+
it("creates new stream when we no longer want video", async () => {
319+
await mediaHandler.getUserMediaStream(true, true);
320+
const stream = await mediaHandler.getUserMediaStream(true, false);
322321

323-
expect(stream2.isCloneOf(stream1)).toEqual(true);
324-
expect(stream2.getVideoTracks().length).toEqual(0);
322+
expect(stream.getVideoTracks().length).toEqual(0);
325323
});
326324
});
327325

src/webrtc/call.ts

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,7 +1280,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
12801280
setTracksEnabled(stream.getAudioTracks(), audioEnabled);
12811281
setTracksEnabled(stream.getVideoTracks(), videoEnabled);
12821282

1283-
// We want to keep the same stream id, so we replace the tracks rather than the whole stream
1283+
// We want to keep the same stream id, so we replace the tracks rather
1284+
// than the whole stream.
1285+
1286+
// Firstly, we replace the tracks in our localUsermediaStream.
12841287
for (const track of this.localUsermediaStream!.getTracks()) {
12851288
this.localUsermediaStream!.removeTrack(track);
12861289
track.stop();
@@ -1289,10 +1292,23 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
12891292
this.localUsermediaStream!.addTrack(track);
12901293
}
12911294

1295+
// Secondly, we remove tracks that we no longer need from the peer
1296+
// connection, if any. This only happens when we mute the video atm.
1297+
// This will change the transceiver direction to "inactive" and
1298+
// therefore cause re-negotiation.
1299+
for (const kind of ["audio", "video"]) {
1300+
const sender = this.transceivers.get(getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, kind))?.sender;
1301+
// Only remove the track if we aren't going to immediately replace it
1302+
if (sender && !stream.getTracks().find((t) => t.kind === kind)) {
1303+
this.peerConn?.removeTrack(sender);
1304+
}
1305+
}
1306+
// Thirdly, we replace the old tracks, if possible.
12921307
for (const track of stream.getTracks()) {
12931308
const tKey = getTransceiverKey(SDPStreamMetadataPurpose.Usermedia, track.kind);
12941309

1295-
const oldSender = this.transceivers.get(tKey)?.sender;
1310+
const transceiver = this.transceivers.get(tKey);
1311+
const oldSender = transceiver?.sender;
12961312
let added = false;
12971313
if (oldSender) {
12981314
try {
@@ -1306,6 +1322,10 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
13061322
`) to peer connection`,
13071323
);
13081324
await oldSender.replaceTrack(track);
1325+
// Set the direction to indicate we're going to be sending.
1326+
// This is only necessary in the cases where we're upgrading
1327+
// the call to video after downgrading it.
1328+
transceiver.direction = transceiver.direction === "inactive" ? "sendonly" : "sendrecv";
13091329
added = true;
13101330
} catch (error) {
13111331
logger.warn(`replaceTrack failed: adding new transceiver instead`, error);
@@ -1349,7 +1369,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
13491369
await this.upgradeCall(false, true);
13501370
return this.isLocalVideoMuted();
13511371
}
1352-
this.localUsermediaFeed?.setAudioVideoMuted(null, muted);
1372+
if (this.opponentSupportsSDPStreamMetadata()) {
1373+
const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted);
1374+
await this.updateLocalUsermediaStream(stream);
1375+
} else {
1376+
this.localUsermediaFeed?.setAudioVideoMuted(null, muted);
1377+
}
13531378
this.updateMuteStatus();
13541379
await this.sendMetadataUpdate();
13551380
return this.isLocalVideoMuted();

src/webrtc/groupCall.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,9 @@ export class GroupCall extends TypedEventEmitter<
609609
logger.log(
610610
`groupCall ${this.groupCallId} setLocalVideoMuted stream ${this.localCallFeed.stream.id} muted ${muted}`,
611611
);
612+
613+
const stream = await this.client.getMediaHandler().getUserMediaStream(true, !muted);
614+
await this.updateLocalUsermediaStream(stream);
612615
this.localCallFeed.setAudioVideoMuted(null, muted);
613616
setTracksEnabled(this.localCallFeed.stream.getVideoTracks(), !muted);
614617
} else {

src/webrtc/mediaHandler.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -203,24 +203,30 @@ export class MediaHandler extends TypedEventEmitter<
203203

204204
let canReuseStream = true;
205205
if (this.localUserMediaStream) {
206+
// This figures out if we can reuse the current localUsermediaStream
207+
// based on whether or not the "mute state" (presence of tracks of a
208+
// given kind) matches what is being requested
209+
if (shouldRequestAudio !== this.localUserMediaStream.getAudioTracks().length > 0) {
210+
canReuseStream = false;
211+
}
212+
if (shouldRequestVideo !== this.localUserMediaStream.getVideoTracks().length > 0) {
213+
canReuseStream = false;
214+
}
215+
206216
// This code checks that the device ID is the same as the localUserMediaStream stream, but we update
207217
// the localUserMediaStream whenever the device ID changes (apart from when restoring) so it's not
208218
// clear why this would ever be different, unless there's a race.
209-
if (shouldRequestAudio) {
210-
if (
211-
this.localUserMediaStream.getAudioTracks().length === 0 ||
212-
this.localUserMediaStream.getAudioTracks()[0]?.getSettings()?.deviceId !== this.audioInput
213-
) {
214-
canReuseStream = false;
215-
}
219+
if (
220+
shouldRequestAudio &&
221+
this.localUserMediaStream.getAudioTracks()[0]?.getSettings()?.deviceId !== this.audioInput
222+
) {
223+
canReuseStream = false;
216224
}
217-
if (shouldRequestVideo) {
218-
if (
219-
this.localUserMediaStream.getVideoTracks().length === 0 ||
220-
this.localUserMediaStream.getVideoTracks()[0]?.getSettings()?.deviceId !== this.videoInput
221-
) {
222-
canReuseStream = false;
223-
}
225+
if (
226+
shouldRequestVideo &&
227+
this.localUserMediaStream.getVideoTracks()[0]?.getSettings()?.deviceId !== this.videoInput
228+
) {
229+
canReuseStream = false;
224230
}
225231
} else {
226232
canReuseStream = false;

0 commit comments

Comments
 (0)