Skip to content

Commit ae849fd

Browse files
Minor VoIP stack improvements (#2946)
* Add `IGroupCallRoomState` Signed-off-by: Šimon Brandner <[email protected]> * Export values into `const`s Signed-off-by: Šimon Brandner <[email protected]> * Add `should correctly emit LengthChanged` Signed-off-by: Šimon Brandner <[email protected]> * Add `ICE disconnected timeout` Signed-off-by: Šimon Brandner <[email protected]> * Improve typing Signed-off-by: Šimon Brandner <[email protected]> * Don't cast `getContent()` Signed-off-by: Šimon Brandner <[email protected]> * Use `Date.now()` for call length Signed-off-by: Šimon Brandner <[email protected]> * Type fix Signed-off-by: Šimon Brandner <[email protected]> Signed-off-by: Šimon Brandner <[email protected]>
1 parent 39cf212 commit ae849fd

File tree

4 files changed

+81
-16
lines changed

4 files changed

+81
-16
lines changed

spec/test-utils/webrtc.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,12 +115,14 @@ export class MockRTCPeerConnection {
115115

116116
private negotiationNeededListener?: () => void;
117117
public iceCandidateListener?: (e: RTCPeerConnectionIceEvent) => void;
118+
public iceConnectionStateChangeListener?: () => void;
118119
public onTrackListener?: (e: RTCTrackEvent) => void;
119120
public needsNegotiation = false;
120121
public readyToNegotiate: Promise<void>;
121122
private onReadyToNegotiate?: () => void;
122123
public localDescription: RTCSessionDescription;
123124
public signalingState: RTCSignalingState = "stable";
125+
public iceConnectionState: RTCIceConnectionState = "connected";
124126
public transceivers: MockRTCRtpTransceiver[] = [];
125127

126128
public static triggerAllNegotiations(): void {
@@ -156,6 +158,8 @@ export class MockRTCPeerConnection {
156158
this.negotiationNeededListener = listener;
157159
} else if (type == 'icecandidate') {
158160
this.iceCandidateListener = listener;
161+
} else if (type === 'iceconnectionstatechange') {
162+
this.iceConnectionStateChangeListener = listener;
159163
} else if (type == 'track') {
160164
this.onTrackListener = listener;
161165
}

spec/unit/webrtc/call.spec.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,4 +1458,50 @@ describe('Call', function() {
14581458
expect(call.hasPeerConnection).toBe(true);
14591459
});
14601460
});
1461+
1462+
it("should correctly emit LengthChanged", async () => {
1463+
const advanceByArray = [2, 3, 5];
1464+
const lengthChangedListener = jest.fn();
1465+
1466+
jest.useFakeTimers();
1467+
call.addListener(CallEvent.LengthChanged, lengthChangedListener);
1468+
await fakeIncomingCall(client, call, "1");
1469+
(call.peerConn as unknown as MockRTCPeerConnection).iceConnectionStateChangeListener!();
1470+
1471+
let hasAdvancedBy = 0;
1472+
for (const advanceBy of advanceByArray) {
1473+
jest.advanceTimersByTime(advanceBy * 1000);
1474+
hasAdvancedBy += advanceBy;
1475+
1476+
expect(lengthChangedListener).toHaveBeenCalledTimes(hasAdvancedBy);
1477+
expect(lengthChangedListener).toBeCalledWith(hasAdvancedBy);
1478+
}
1479+
});
1480+
1481+
describe("ICE disconnected timeout", () => {
1482+
let mockPeerConn: MockRTCPeerConnection;
1483+
1484+
beforeEach(async () => {
1485+
jest.useFakeTimers();
1486+
jest.spyOn(call, "hangup");
1487+
1488+
await fakeIncomingCall(client, call, "1");
1489+
1490+
mockPeerConn = (call.peerConn as unknown as MockRTCPeerConnection);
1491+
mockPeerConn.iceConnectionState = "disconnected";
1492+
mockPeerConn.iceConnectionStateChangeListener!();
1493+
});
1494+
1495+
it("should hang up after being disconnected for 30 seconds", () => {
1496+
jest.advanceTimersByTime(31 * 1000);
1497+
expect(call.hangup).toHaveBeenCalledWith(CallErrorCode.IceFailed, false);
1498+
});
1499+
1500+
it("should not hangup if we've managed to re-connect", () => {
1501+
mockPeerConn.iceConnectionState = "connected";
1502+
mockPeerConn.iceConnectionStateChangeListener!();
1503+
jest.advanceTimersByTime(31 * 1000);
1504+
expect(call.hangup).not.toHaveBeenCalled();
1505+
});
1506+
});
14611507
});

src/webrtc/call.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,11 @@ const VOIP_PROTO_VERSION = "1";
253253
const FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
254254

255255
/** The length of time a call can be ringing for. */
256-
const CALL_TIMEOUT_MS = 60000;
256+
const CALL_TIMEOUT_MS = 60 * 1000; // ms
257+
/** The time after which we increment callLength */
258+
const CALL_LENGTH_INTERVAL = 1000; // ms
259+
/** The time after which we end the call, if ICE got disconnected */
260+
const ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms
257261

258262
export class CallError extends Error {
259263
public readonly code: string;
@@ -376,7 +380,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
376380
private remoteSDPStreamMetadata?: SDPStreamMetadata;
377381

378382
private callLengthInterval?: ReturnType<typeof setInterval>;
379-
private callLength = 0;
383+
private callStartTime?: number;
380384

381385
private opponentDeviceId?: string;
382386
private opponentDeviceInfo?: DeviceInfo;
@@ -2083,11 +2087,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
20832087
clearTimeout(this.iceDisconnectedTimeout);
20842088
this.state = CallState.Connected;
20852089

2086-
if (!this.callLengthInterval) {
2090+
if (!this.callLengthInterval && !this.callStartTime) {
2091+
this.callStartTime = Date.now();
2092+
20872093
this.callLengthInterval = setInterval(() => {
2088-
this.callLength++;
2089-
this.emit(CallEvent.LengthChanged, this.callLength);
2090-
}, 1000);
2094+
this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime!) / 1000));
2095+
}, CALL_LENGTH_INTERVAL);
20912096
}
20922097
} else if (this.peerConn?.iceConnectionState == 'failed') {
20932098
// Firefox for Android does not yet have support for restartIce()
@@ -2104,7 +2109,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
21042109
this.iceDisconnectedTimeout = setTimeout(() => {
21052110
logger.info(`Hanging up call ${this.callId} (ICE disconnected for too long)`);
21062111
this.hangup(CallErrorCode.IceFailed, false);
2107-
}, 30 * 1000);
2112+
}, ICE_DISCONNECTED_TIMEOUT);
21082113
this.state = CallState.Connecting;
21092114
}
21102115

src/webrtc/groupCall.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,17 @@ export interface IGroupCallDataChannelOptions {
121121
protocol: string;
122122
}
123123

124+
export interface IGroupCallRoomState {
125+
"m.intent": GroupCallIntent;
126+
"m.type": GroupCallType;
127+
"io.element.ptt"?: boolean;
128+
// TODO: Specify data-channels
129+
"dataChannelsEnabled"?: boolean;
130+
"dataChannelOptions"?: IGroupCallDataChannelOptions;
131+
}
132+
124133
export interface IGroupCallRoomMemberFeed {
125134
purpose: SDPStreamMetadataPurpose;
126-
// TODO: Sources for adaptive bitrate
127135
}
128136

129137
export interface IGroupCallRoomMemberDevice {
@@ -228,17 +236,19 @@ export class GroupCall extends TypedEventEmitter<
228236
this.client.groupCallEventHandler!.groupCalls.set(this.room.roomId, this);
229237
this.client.emit(GroupCallEventHandlerEvent.Outgoing, this);
230238

239+
const groupCallState: IGroupCallRoomState = {
240+
"m.intent": this.intent,
241+
"m.type": this.type,
242+
"io.element.ptt": this.isPtt,
243+
// TODO: Specify data-channels better
244+
"dataChannelsEnabled": this.dataChannelsEnabled,
245+
"dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined,
246+
};
247+
231248
await this.client.sendStateEvent(
232249
this.room.roomId,
233250
EventType.GroupCallPrefix,
234-
{
235-
"m.intent": this.intent,
236-
"m.type": this.type,
237-
"io.element.ptt": this.isPtt,
238-
// TODO: Specify datachannels
239-
"dataChannelsEnabled": this.dataChannelsEnabled,
240-
"dataChannelOptions": this.dataChannelOptions,
241-
},
251+
groupCallState,
242252
this.groupCallId,
243253
);
244254

0 commit comments

Comments
 (0)