Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPARK-532493 webrtc core should expose separate events for ice and connection state #78

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"codecov",
"commitlint",
"cpaas",
"createansweronsuccess",
"createofferonsuccess",
"dbaeumer",
"dependabot",
"eamodio",
Expand All @@ -25,22 +27,21 @@
"libauth",
"mkdir",
"negotiatedneeded",
"peerconnectionstatechange",
"preprocessors",
"prettierignore",
"rohit",
"sandboxed",
"saucelabs",
"setlocaldescriptiononsuccess",
"setremotedescriptiononsuccess",
"transpiled",
"typedoc",
"untracked",
"Wcme",
"WCME",
"webex",
"webrtc",
"createofferonsuccess",
"createansweronsuccess",
"setlocaldescriptiononsuccess",
"setremotedescriptiononsuccess"
"webrtc"
],
"flagWords": [],
"ignorePaths": [
Expand Down
35 changes: 23 additions & 12 deletions src/connection-state-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,53 @@ describe('ConnectionStateHandler', () => {
fakeConnectionState = 'new';
});

it('reads initial peer connection state', () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

expect(connStateHandler.getPeerConnectionState()).toBe('new');
});

it('reads initial ice connection state', () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

expect(connStateHandler.getIceConnectionState()).toBe('new');
});

it('reads initial connection state', () => {
expect.assertions(1);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

expect(connStateHandler.getConnectionState()).toStrictEqual(ConnectionState.New);
expect(connStateHandler.getConnectionState()).toBe('New');
k-wasniowski marked this conversation as resolved.
Show resolved Hide resolved
});

it('updates connection state on ice connection state change and emits the event', () => {
it('updates ice connection state on ice connection state change and emits the event', () => {
expect.assertions(2);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

connStateHandler.on(ConnectionStateHandler.Events.ConnectionStateChanged, (state) => {
expect(state).toStrictEqual(ConnectionState.Connecting);
connStateHandler.on(ConnectionStateHandler.Events.IceConnectionStateChanged, (state) => {
expect(state).toBe('checking');
});

fakeIceState = 'checking';
connStateHandler.onIceConnectionStateChange();

expect(connStateHandler.getConnectionState()).toStrictEqual(ConnectionState.Connecting);
expect(connStateHandler.getIceConnectionState()).toBe('checking');
});

it("updates connection state on RTCPeerConnection's connection state change", () => {
expect.assertions(2);
const connStateHandler = new ConnectionStateHandler(fakeCallback);

connStateHandler.on(ConnectionStateHandler.Events.ConnectionStateChanged, (state) => {
expect(state).toStrictEqual(ConnectionState.Connecting);
connStateHandler.on(ConnectionStateHandler.Events.PeerConnectionStateChanged, (state) => {
expect(state).toBe('connecting');
});

fakeConnectionState = 'connecting';
connStateHandler.onConnectionStateChange();
connStateHandler.onPeerConnectionStateChange();

expect(connStateHandler.getConnectionState()).toStrictEqual(ConnectionState.Connecting);
expect(connStateHandler.getPeerConnectionState()).toBe('connecting');
});

// test matrix for all possible combinations of iceConnectionState and connectionState
Expand Down Expand Up @@ -118,9 +132,6 @@ describe('ConnectionStateHandler', () => {
fakeConnectionState = connState;
fakeIceState = iceState;

// it's sufficient to trigger just one of the callbacks
connStateHandler.onConnectionStateChange();

expect(connStateHandler.getConnectionState()).toStrictEqual(expected);
})
);
Expand Down
57 changes: 35 additions & 22 deletions src/connection-state-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ export enum ConnectionState {
}

enum ConnectionStateEvents {
ConnectionStateChanged = 'ConnectionStateChanged',
PeerConnectionStateChanged = 'PeerConnectionStateChanged',
IceConnectionStateChanged = 'IceConnectionStateChanged',
}

interface ConnectionStateEventHandlers extends EventMap {
[ConnectionStateEvents.ConnectionStateChanged]: (state: ConnectionState) => void;
[ConnectionStateEvents.PeerConnectionStateChanged]: (state: RTCPeerConnectionState) => void;
[ConnectionStateEvents.IceConnectionStateChanged]: (state: RTCIceConnectionState) => void;
}

type GetCurrentStatesCallback = () => {
Expand All @@ -31,8 +33,6 @@ type GetCurrentStatesCallback = () => {
export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHandlers> {
static Events = ConnectionStateEvents;

private mediaConnectionState: ConnectionState;

private getCurrentStatesCallback: GetCurrentStatesCallback;

/**
Expand All @@ -44,33 +44,24 @@ export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHan
constructor(getCurrentStatesCallback: GetCurrentStatesCallback) {
super();
this.getCurrentStatesCallback = getCurrentStatesCallback;
this.mediaConnectionState = this.evaluateMediaConnectionState();
}

/**
* Handler for connection state change.
*/
public onConnectionStateChange(): void {
this.handleAnyConnectionStateChange();
public onPeerConnectionStateChange(): void {
const state = this.getPeerConnectionState();

this.emit(ConnectionStateEvents.PeerConnectionStateChanged, state);
}

/**
* Handler for ice connection state change.
*/
public onIceConnectionStateChange(): void {
this.handleAnyConnectionStateChange();
}

/**
* Method to be called whenever ice connection or dtls connection state is changed.
*/
private handleAnyConnectionStateChange() {
const newConnectionState = this.evaluateMediaConnectionState();
const state = this.getIceConnectionState();

if (newConnectionState !== this.mediaConnectionState) {
this.mediaConnectionState = newConnectionState;
this.emit(ConnectionStateEvents.ConnectionStateChanged, this.mediaConnectionState);
}
this.emit(ConnectionStateEvents.IceConnectionStateChanged, state);
}

/**
Expand All @@ -79,12 +70,12 @@ export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHan
*
* @returns Current overall connection state.
*/
private evaluateMediaConnectionState() {
private evaluateMediaConnectionState(): ConnectionState {
const { connectionState, iceState } = this.getCurrentStatesCallback();

const connectionStates = [connectionState, iceState];

let mediaConnectionState;
let mediaConnectionState: ConnectionState;

if (connectionStates.every((value) => value === 'new')) {
mediaConnectionState = ConnectionState.New;
Expand Down Expand Up @@ -112,7 +103,29 @@ export class ConnectionStateHandler extends EventEmitter<ConnectionStateEventHan
*
* @returns Current connection state.
*/
public getPeerConnectionState(): RTCPeerConnectionState {
const { connectionState } = this.getCurrentStatesCallback();

return connectionState;
}

/**
* Gets current ice connection state.
*
* @returns Current ice connection state.
*/
public getIceConnectionState(): RTCIceConnectionState {
const { iceState } = this.getCurrentStatesCallback();

return iceState;
}

/**
* Gets current overall connection state.
*
* @returns Current overall connection state.
*/
public getConnectionState(): ConnectionState {
return this.mediaConnectionState;
return this.evaluateMediaConnectionState();
}
}
35 changes: 26 additions & 9 deletions src/peer-connection.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BrowserInfo } from '@webex/web-capabilities';
import { MockedObjectDeep } from 'ts-jest';
import { ConnectionState, ConnectionStateHandler } from './connection-state-handler';
import { ConnectionStateHandler } from './connection-state-handler';
import { mocked } from './mocks/mock';
import { RTCPeerConnectionStub } from './mocks/rtc-peer-connection-stub';
import { PeerConnection } from './peer-connection';
Expand Down Expand Up @@ -219,32 +219,49 @@ describe('PeerConnection', () => {

mockPc.onconnectionstatechange();

expect(connectionStateHandler.onConnectionStateChange).toHaveBeenCalledTimes(1);
expect(connectionStateHandler.onPeerConnectionStateChange).toHaveBeenCalledTimes(1);
});
it('returns connection state from connection state handler when geConnectionState() is called', () => {
expect.assertions(2);
const connectionStateHandler = getInstantiatedConnectionStateHandler();
connectionStateHandler.getConnectionState.mockReturnValueOnce(ConnectionState.Connected);
connectionStateHandler.getConnectionState.mockReturnValueOnce('connected');

expect(pc.getConnectionState()).toStrictEqual(ConnectionState.Connected);
expect(pc.getConnectionState()).toBe('connected');
expect(connectionStateHandler.getConnectionState).toHaveBeenCalledTimes(1);
});
it("listens on ConnectionStateHandler's ConnectionStateChange event and emits it", () => {
it("listens on ConnectionStateHandler's PeerConnectionStateChange event and emits it", () => {
expect.assertions(2);
const connectionStateHandler = getInstantiatedConnectionStateHandler();

pc.on(PeerConnection.Events.ConnectionStateChange, (state) => {
expect(state).toStrictEqual(ConnectionState.Connecting);
pc.on(PeerConnection.Events.PeerConnectionStateChange, (state) => {
expect(state).toBe('connecting');
});

// verify that PeerConnection listens for the right event
expect(connectionStateHandler.on.mock.calls[0][0]).toStrictEqual(
ConnectionStateHandler.Events.ConnectionStateChanged
ConnectionStateHandler.Events.PeerConnectionStateChanged
);

// trigger the fake event from ConnectionStateHandler
const connectionStateHandlerListener = connectionStateHandler.on.mock.calls[0][1];
connectionStateHandlerListener(ConnectionState.Connecting);
connectionStateHandlerListener('connecting');
});
it("listens on ConnectionStateHandler's IceConnectionStateChange event and emits it", () => {
expect.assertions(2);
const connectionStateHandler = getInstantiatedConnectionStateHandler();

pc.on(PeerConnection.Events.IceConnectionStateChange, (state) => {
expect(state).toBe('checking');
});

// verify that PeerConnection listens for the right event
expect(connectionStateHandler.on.mock.calls[1][0]).toStrictEqual(
ConnectionStateHandler.Events.IceConnectionStateChanged
);

// trigger the fake event from ConnectionStateHandler
const connectionStateHandlerListener = connectionStateHandler.on.mock.calls[1][1];
connectionStateHandlerListener('checking');
});
});
describe('createAnswer', () => {
Expand Down
40 changes: 34 additions & 6 deletions src/peer-connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ type IceGatheringStateChangeEvent = {
enum PeerConnectionEvents {
IceGatheringStateChange = 'icegatheringstatechange',
IceCandidate = 'icecandidate',
ConnectionStateChange = 'connectionstatechange',
PeerConnectionStateChange = 'peerconnectionstatechange',
IceConnectionStateChange = 'iceconnectionstatechange',
CreateOfferOnSuccess = 'createofferonsuccess',
CreateAnswerOnSuccess = 'createansweronsuccess',
SetLocalDescriptionOnSuccess = 'setlocaldescriptiononsuccess',
Expand All @@ -38,7 +39,8 @@ enum PeerConnectionEvents {
interface PeerConnectionEventHandlers extends EventMap {
[PeerConnectionEvents.IceGatheringStateChange]: (ev: IceGatheringStateChangeEvent) => void;
[PeerConnectionEvents.IceCandidate]: (ev: RTCPeerConnectionIceEvent) => void;
[PeerConnectionEvents.ConnectionStateChange]: (state: ConnectionState) => void;
[PeerConnectionEvents.PeerConnectionStateChange]: (state: RTCPeerConnectionState) => void;
[PeerConnectionEvents.IceConnectionStateChange]: (state: RTCIceConnectionState) => void;
[PeerConnectionEvents.CreateOfferOnSuccess]: (offer: RTCSessionDescriptionInit) => void;
[PeerConnectionEvents.CreateAnswerOnSuccess]: (answer: RTCSessionDescriptionInit) => void;
[PeerConnectionEvents.SetLocalDescriptionOnSuccess]: (
Expand Down Expand Up @@ -79,9 +81,16 @@ class PeerConnection extends EventEmitter<PeerConnectionEventHandlers> {
});

this.connectionStateHandler.on(
ConnectionStateHandler.Events.ConnectionStateChanged,
(state: ConnectionState) => {
this.emit(PeerConnection.Events.ConnectionStateChange, state);
ConnectionStateHandler.Events.PeerConnectionStateChanged,
(state: RTCPeerConnectionState) => {
this.emit(PeerConnection.Events.PeerConnectionStateChange, state);
}
);

this.connectionStateHandler.on(
ConnectionStateHandler.Events.IceConnectionStateChanged,
(state: RTCIceConnectionState) => {
this.emit(PeerConnection.Events.IceConnectionStateChange, state);
}
);

Expand All @@ -91,7 +100,8 @@ class PeerConnection extends EventEmitter<PeerConnectionEventHandlers> {
this.connectionStateHandler.onIceConnectionStateChange();

// eslint-disable-next-line jsdoc/require-jsdoc
this.pc.onconnectionstatechange = () => this.connectionStateHandler.onConnectionStateChange();
this.pc.onconnectionstatechange = () =>
this.connectionStateHandler.onPeerConnectionStateChange();

// Subscribe to underlying PeerConnection events and emit them via the EventEmitter
/* eslint-disable jsdoc/require-jsdoc */
Expand Down Expand Up @@ -122,6 +132,24 @@ class PeerConnection extends EventEmitter<PeerConnectionEventHandlers> {
return this.connectionStateHandler.getConnectionState();
}

/**
* Gets the connection state of the underlying RTCPeerConnection.
*
* @returns The underlying RTCPeerConnection connection state.
*/
getPeerConnectionState(): RTCPeerConnectionState {
return this.connectionStateHandler.getPeerConnectionState();
}

/**
* Gets the ICE connection state of the underlying RTCPeerConnection.
*
* @returns The underlying RTCPeerConnection ICE connection state.
*/
getIceConnectionState(): RTCIceConnectionState {
return this.connectionStateHandler.getIceConnectionState();
}

/**
* Adds a new media track to the set of tracks which will be transmitted to the other peer.
*
Expand Down
Loading