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

Refactor handling reconnection #468

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
94 changes: 70 additions & 24 deletions src/main/js/webrtc_adaptor.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@
*/
this.debug = false;

/**
* This is the flag to indicate if the stream is published or not after the connection fails
* @type {boolean}
*/
this.iceRestart = false;

/**
* This is the Stream Id for the publisher. One @WebRCTCAdaptor supports only one publishing
* session for now (23.02.2022).
Expand Down Expand Up @@ -628,6 +634,7 @@
}

tryAgain() {
Logger.debug("tryAgain is called");

const now = Date.now();
//to prevent too many trial from different paths
Expand All @@ -647,6 +654,7 @@
{
// notify that reconnection process started for publish
this.notifyEventListeners("reconnection_attempt_for_publisher", this.publishStreamId);
Logger.log("It will try to publish again for stream: " + this.publishStreamId + " because it is not stopped on purpose")

this.stop(this.publishStreamId);
setTimeout(() => {
Expand Down Expand Up @@ -1042,24 +1050,7 @@
this.remoteDescriptionSet[streamId] = false;
this.iceCandidateList[streamId] = new Array();
if (!this.playStreamId.includes(streamId)) {
if (this.mediaManager.localStream != null) {
this.mediaManager.localStream.getTracks().forEach(track => {

let rtpSender = this.remotePeerConnection[streamId].addTrack(track, this.mediaManager.localStream);
if (track.kind == 'video')
{
let parameters = rtpSender.getParameters();
parameters.degradationPreference = this.degradationPreference;
rtpSender.setParameters(parameters).then(() => {
Logger.info("Degradation Preference is set to " + this.degradationPreference);
}).catch((err) => {
Logger.warn("Degradation Preference cannot be set to " + this.degradationPreference)
});
}
//
//parameters.degradationPreference
});
}
this.addTracksIntoPeerConnection(streamId);
}
this.remotePeerConnection[streamId].onicecandidate = event => {
this.iceCandidateReceived(event, closedStreamId);
Expand All @@ -1068,8 +1059,18 @@
this.onTrack(event, closedStreamId);
}

this.remotePeerConnection[streamId].onnegotiationneeded = event => {
this.remotePeerConnection[streamId].onnegotiationneeded = async (event) => {
Logger.debug("onnegotiationneeded");
//If ice restart is not true, than server will handle negotiation
if (!this.iceRestart) {
return;
}
try {
await this.remotePeerConnection[streamId].setLocalDescription(await this.remotePeerConnection[streamId].createOffer({iceRestart: this.iceRestart}));
this.webSocketAdaptor.send({desc: this.remotePeerConnection[streamId].localDescription});

Check warning on line 1070 in src/main/js/webrtc_adaptor.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/webrtc_adaptor.js#L1068-L1070

Added lines #L1068 - L1070 were not covered by tests
} catch (error) {
Logger.error('Error during negotiation', error);

Check warning on line 1072 in src/main/js/webrtc_adaptor.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/webrtc_adaptor.js#L1072

Added line #L1072 was not covered by tests
}
}

if (this.dataChannelEnabled) {
Expand Down Expand Up @@ -1112,7 +1113,14 @@

this.remotePeerConnection[streamId].oniceconnectionstatechange = event => {
var obj = {state: this.remotePeerConnection[streamId].iceConnectionState, streamId: streamId};
if (obj.state == "failed" || obj.state == "disconnected" || obj.state == "closed") {
if (obj.state === "stable") {
this.iceRestart = false;

Check warning on line 1117 in src/main/js/webrtc_adaptor.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/webrtc_adaptor.js#L1116-L1117

Added lines #L1116 - L1117 were not covered by tests
}
if (obj.state === "failed") {
this.iceRestart = true;
this.remotePeerConnection[streamId].restartIce();

Check warning on line 1121 in src/main/js/webrtc_adaptor.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/webrtc_adaptor.js#L1119-L1121

Added lines #L1119 - L1121 were not covered by tests
}
if (obj.state === "disconnected" || obj.state === "closed") {

Check warning on line 1123 in src/main/js/webrtc_adaptor.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/webrtc_adaptor.js#L1123

Added line #L1123 was not covered by tests
this.reconnectIfRequired(3000);
}
this.notifyEventListeners("ice_connection_state_changed", obj);
Expand All @@ -1134,6 +1142,44 @@
return this.remotePeerConnection[streamId];
}

/**
* Called internally to stop tracks in PeerConnection.
* @param {string} streamId : unique id for the stream
*/
stopTracksInPeerConnection(streamId) {
if (this.remotePeerConnection[streamId] != null) {
this.remotePeerConnection[streamId].getSenders().forEach(sender => {
if (sender.track != null) {
sender.track.stop();
}
});
}
}

/**
* Called internally to add tracks into PeerConnection.
* @param {string} streamId : unique id for the stream
*/
addTracksIntoPeerConnection(streamId) {
if (this.mediaManager.localStream != null) {
this.mediaManager.localStream.getTracks().forEach(track => {

let rtpSender = this.remotePeerConnection[streamId].addTrack(track, this.mediaManager.localStream);
if (track.kind === 'video') {
let parameters = rtpSender.getParameters();
parameters.degradationPreference = this.degradationPreference;
rtpSender.setParameters(parameters).then(() => {
Logger.info("Degradation Preference is set to " + this.degradationPreference);
}).catch((err) => {
Logger.warn("Degradation Preference cannot be set to " + this.degradationPreference)

Check warning on line 1174 in src/main/js/webrtc_adaptor.js

View check run for this annotation

Codecov / codecov/patch

src/main/js/webrtc_adaptor.js#L1174

Added line #L1174 was not covered by tests
});
}
//
//parameters.degradationPreference
});
}
}

/**
* Called internally to close PeerConnection.
* @param {string} streamId : unique id for the stream
Expand All @@ -1149,10 +1195,10 @@
if (peerConnection.signalingState != "closed") {
peerConnection.close();
}
var playStreamIndex = this.playStreamId.indexOf(streamId);
if (playStreamIndex != -1) {
this.playStreamId.splice(playStreamIndex, 1);
}
}
var playStreamIndex = this.playStreamId.indexOf(streamId);
if (playStreamIndex != -1) {
this.playStreamId.splice(playStreamIndex, 1);
}
//this is for the stats
if (this.remotePeerConnectionStats[streamId] != null) {
Expand Down
109 changes: 109 additions & 0 deletions src/test/js/webrtc_adaptor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1656,4 +1656,113 @@ describe("WebRTCAdaptor", function () {

});

describe("stopTracksInPeerConnection", function () {
let adaptor;

beforeEach(function () {
adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
initializeComponents: false,
});
adaptor.mediaManager = {
updateVideoTrack: sinon.fake()
};
});

it("should stop all tracks when peer connection exists", function () {
const mockTrack = { stop: sinon.fake() };
const mockSender = { track: mockTrack };
const mockPeerConnection = { getSenders: sinon.fake.returns([mockSender]) };

adaptor.remotePeerConnection["stream1"] = mockPeerConnection;

adaptor.stopTracksInPeerConnection("stream1");

expect(mockTrack.stop.called).to.be.true;
});

it("should not throw error when peer connection does not exist", function () {
expect(() => adaptor.stopTracksInPeerConnection("stream1")).not.to.throw();
});

it("should not stop track when sender's track is null", function () {
const mockTrack = { stop: sinon.fake() };
const mockSender = { track: null };
const mockPeerConnection = { getSenders: sinon.fake.returns([mockSender]) };

adaptor.remotePeerConnection["stream1"] = mockPeerConnection;

adaptor.stopTracksInPeerConnection("stream1");

expect(mockTrack.stop.called).to.be.false;
});
});

describe("oniceconnectionstatechange", function () {
let adaptor;
let mockPeerConnection;

beforeEach(function () {
adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
initializeComponents: false,
});
mockPeerConnection = { iceConnectionState: "", restartIce: sinon.fake(), oniceconnectionstatechange: sinon.fake()};
adaptor.remotePeerConnection["stream1"] = mockPeerConnection;
});

it("should set iceRestart to false when state is stable", function () {
mockPeerConnection.iceConnectionState = "stable";

adaptor.remotePeerConnection["stream1"].oniceconnectionstatechange();

expect(adaptor.iceRestart).to.be.false;
});
});

describe("addTracksIntoPeerConnection", function () {
let adaptor;
let mockMediaManager;
let mockPeerConnection;

beforeEach(function () {
adaptor = new WebRTCAdaptor({
websocketURL: "ws://example.com",
initializeComponents: false,
});
mockMediaManager = { localStream: null };
mockPeerConnection = { addTrack: sinon.fake(), getParameters: sinon.fake.returns({}), setParameters: sinon.fake.returns(Promise.resolve()) };
adaptor.mediaManager = mockMediaManager;
adaptor.remotePeerConnection = { "stream1": mockPeerConnection };
mockPeerConnection.addTrack = sinon.fake.returns({
track: { kind: "video" },
getParameters: sinon.fake.returns({}),
setParameters: sinon.fake.returns(Promise.resolve())
});
});

it("should not add tracks when local stream is null", function () {
adaptor.addTracksIntoPeerConnection("stream1");
expect(mockPeerConnection.addTrack.called).to.be.false;
});

it("should add tracks when local stream is not null", function () {
const mockTrack = { kind: "video" };
mockMediaManager.localStream = { getTracks: sinon.fake.returns([mockTrack]) };

adaptor.addTracksIntoPeerConnection("stream1");

expect(mockPeerConnection.addTrack.calledWithExactly(mockTrack, mockMediaManager.localStream)).to.be.true;
});

it("should not set degradation preference for non-video tracks", function () {
const mockTrack = { kind: "audio" };
mockMediaManager.localStream = { getTracks: sinon.fake.returns([mockTrack]) };

adaptor.addTracksIntoPeerConnection("stream1");

expect(mockPeerConnection.setParameters.called).to.be.false;
});
});

});
Loading