You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-09 10:22:46 +03:00
Minor VoIP stack improvements (#2946)
* Add `IGroupCallRoomState` Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Export values into `const`s Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Add `should correctly emit LengthChanged` Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Add `ICE disconnected timeout` Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Improve typing Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Don't cast `getContent()` Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Use `Date.now()` for call length Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> * Type fix Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com> Signed-off-by: Šimon Brandner <simon.bra.ag@gmail.com>
This commit is contained in:
@@ -115,12 +115,14 @@ export class MockRTCPeerConnection {
|
||||
|
||||
private negotiationNeededListener?: () => void;
|
||||
public iceCandidateListener?: (e: RTCPeerConnectionIceEvent) => void;
|
||||
public iceConnectionStateChangeListener?: () => void;
|
||||
public onTrackListener?: (e: RTCTrackEvent) => void;
|
||||
public needsNegotiation = false;
|
||||
public readyToNegotiate: Promise<void>;
|
||||
private onReadyToNegotiate?: () => void;
|
||||
public localDescription: RTCSessionDescription;
|
||||
public signalingState: RTCSignalingState = "stable";
|
||||
public iceConnectionState: RTCIceConnectionState = "connected";
|
||||
public transceivers: MockRTCRtpTransceiver[] = [];
|
||||
|
||||
public static triggerAllNegotiations(): void {
|
||||
@@ -156,6 +158,8 @@ export class MockRTCPeerConnection {
|
||||
this.negotiationNeededListener = listener;
|
||||
} else if (type == 'icecandidate') {
|
||||
this.iceCandidateListener = listener;
|
||||
} else if (type === 'iceconnectionstatechange') {
|
||||
this.iceConnectionStateChangeListener = listener;
|
||||
} else if (type == 'track') {
|
||||
this.onTrackListener = listener;
|
||||
}
|
||||
|
@@ -1458,4 +1458,50 @@ describe('Call', function() {
|
||||
expect(call.hasPeerConnection).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("should correctly emit LengthChanged", async () => {
|
||||
const advanceByArray = [2, 3, 5];
|
||||
const lengthChangedListener = jest.fn();
|
||||
|
||||
jest.useFakeTimers();
|
||||
call.addListener(CallEvent.LengthChanged, lengthChangedListener);
|
||||
await fakeIncomingCall(client, call, "1");
|
||||
(call.peerConn as unknown as MockRTCPeerConnection).iceConnectionStateChangeListener!();
|
||||
|
||||
let hasAdvancedBy = 0;
|
||||
for (const advanceBy of advanceByArray) {
|
||||
jest.advanceTimersByTime(advanceBy * 1000);
|
||||
hasAdvancedBy += advanceBy;
|
||||
|
||||
expect(lengthChangedListener).toHaveBeenCalledTimes(hasAdvancedBy);
|
||||
expect(lengthChangedListener).toBeCalledWith(hasAdvancedBy);
|
||||
}
|
||||
});
|
||||
|
||||
describe("ICE disconnected timeout", () => {
|
||||
let mockPeerConn: MockRTCPeerConnection;
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.useFakeTimers();
|
||||
jest.spyOn(call, "hangup");
|
||||
|
||||
await fakeIncomingCall(client, call, "1");
|
||||
|
||||
mockPeerConn = (call.peerConn as unknown as MockRTCPeerConnection);
|
||||
mockPeerConn.iceConnectionState = "disconnected";
|
||||
mockPeerConn.iceConnectionStateChangeListener!();
|
||||
});
|
||||
|
||||
it("should hang up after being disconnected for 30 seconds", () => {
|
||||
jest.advanceTimersByTime(31 * 1000);
|
||||
expect(call.hangup).toHaveBeenCalledWith(CallErrorCode.IceFailed, false);
|
||||
});
|
||||
|
||||
it("should not hangup if we've managed to re-connect", () => {
|
||||
mockPeerConn.iceConnectionState = "connected";
|
||||
mockPeerConn.iceConnectionStateChangeListener!();
|
||||
jest.advanceTimersByTime(31 * 1000);
|
||||
expect(call.hangup).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -253,7 +253,11 @@ const VOIP_PROTO_VERSION = "1";
|
||||
const FALLBACK_ICE_SERVER = 'stun:turn.matrix.org';
|
||||
|
||||
/** The length of time a call can be ringing for. */
|
||||
const CALL_TIMEOUT_MS = 60000;
|
||||
const CALL_TIMEOUT_MS = 60 * 1000; // ms
|
||||
/** The time after which we increment callLength */
|
||||
const CALL_LENGTH_INTERVAL = 1000; // ms
|
||||
/** The time after which we end the call, if ICE got disconnected */
|
||||
const ICE_DISCONNECTED_TIMEOUT = 30 * 1000; // ms
|
||||
|
||||
export class CallError extends Error {
|
||||
public readonly code: string;
|
||||
@@ -376,7 +380,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
private remoteSDPStreamMetadata?: SDPStreamMetadata;
|
||||
|
||||
private callLengthInterval?: ReturnType<typeof setInterval>;
|
||||
private callLength = 0;
|
||||
private callStartTime?: number;
|
||||
|
||||
private opponentDeviceId?: string;
|
||||
private opponentDeviceInfo?: DeviceInfo;
|
||||
@@ -2083,11 +2087,12 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
clearTimeout(this.iceDisconnectedTimeout);
|
||||
this.state = CallState.Connected;
|
||||
|
||||
if (!this.callLengthInterval) {
|
||||
if (!this.callLengthInterval && !this.callStartTime) {
|
||||
this.callStartTime = Date.now();
|
||||
|
||||
this.callLengthInterval = setInterval(() => {
|
||||
this.callLength++;
|
||||
this.emit(CallEvent.LengthChanged, this.callLength);
|
||||
}, 1000);
|
||||
this.emit(CallEvent.LengthChanged, Math.round((Date.now() - this.callStartTime!) / 1000));
|
||||
}, CALL_LENGTH_INTERVAL);
|
||||
}
|
||||
} else if (this.peerConn?.iceConnectionState == 'failed') {
|
||||
// Firefox for Android does not yet have support for restartIce()
|
||||
@@ -2104,7 +2109,7 @@ export class MatrixCall extends TypedEventEmitter<CallEvent, CallEventHandlerMap
|
||||
this.iceDisconnectedTimeout = setTimeout(() => {
|
||||
logger.info(`Hanging up call ${this.callId} (ICE disconnected for too long)`);
|
||||
this.hangup(CallErrorCode.IceFailed, false);
|
||||
}, 30 * 1000);
|
||||
}, ICE_DISCONNECTED_TIMEOUT);
|
||||
this.state = CallState.Connecting;
|
||||
}
|
||||
|
||||
|
@@ -121,9 +121,17 @@ export interface IGroupCallDataChannelOptions {
|
||||
protocol: string;
|
||||
}
|
||||
|
||||
export interface IGroupCallRoomState {
|
||||
"m.intent": GroupCallIntent;
|
||||
"m.type": GroupCallType;
|
||||
"io.element.ptt"?: boolean;
|
||||
// TODO: Specify data-channels
|
||||
"dataChannelsEnabled"?: boolean;
|
||||
"dataChannelOptions"?: IGroupCallDataChannelOptions;
|
||||
}
|
||||
|
||||
export interface IGroupCallRoomMemberFeed {
|
||||
purpose: SDPStreamMetadataPurpose;
|
||||
// TODO: Sources for adaptive bitrate
|
||||
}
|
||||
|
||||
export interface IGroupCallRoomMemberDevice {
|
||||
@@ -228,17 +236,19 @@ export class GroupCall extends TypedEventEmitter<
|
||||
this.client.groupCallEventHandler!.groupCalls.set(this.room.roomId, this);
|
||||
this.client.emit(GroupCallEventHandlerEvent.Outgoing, this);
|
||||
|
||||
const groupCallState: IGroupCallRoomState = {
|
||||
"m.intent": this.intent,
|
||||
"m.type": this.type,
|
||||
"io.element.ptt": this.isPtt,
|
||||
// TODO: Specify data-channels better
|
||||
"dataChannelsEnabled": this.dataChannelsEnabled,
|
||||
"dataChannelOptions": this.dataChannelsEnabled ? this.dataChannelOptions : undefined,
|
||||
};
|
||||
|
||||
await this.client.sendStateEvent(
|
||||
this.room.roomId,
|
||||
EventType.GroupCallPrefix,
|
||||
{
|
||||
"m.intent": this.intent,
|
||||
"m.type": this.type,
|
||||
"io.element.ptt": this.isPtt,
|
||||
// TODO: Specify datachannels
|
||||
"dataChannelsEnabled": this.dataChannelsEnabled,
|
||||
"dataChannelOptions": this.dataChannelOptions,
|
||||
},
|
||||
groupCallState,
|
||||
this.groupCallId,
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user