From 424ad2510ad0229a0e8f4905f8c647eaf3587726 Mon Sep 17 00:00:00 2001 From: Mustafa BOLEKEN Date: Mon, 11 Nov 2024 11:49:58 +0300 Subject: [PATCH 1/2] Trigger ice restart in case of the network changed --- src/main/js/webrtc_adaptor.js | 25 +++++++++++++++++++++++-- src/test/js/webrtc_adaptor.test.js | 22 ++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/main/js/webrtc_adaptor.js b/src/main/js/webrtc_adaptor.js index 0baf34d5..09a17108 100644 --- a/src/main/js/webrtc_adaptor.js +++ b/src/main/js/webrtc_adaptor.js @@ -162,6 +162,12 @@ export class WebRTCAdaptor { */ 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). @@ -1092,8 +1098,18 @@ export class WebRTCAdaptor { this.onTrack(event, closedStreamId); } - this.remotePeerConnection[streamId].onnegotiationneeded = event => { + this.remotePeerConnection[streamId].onnegotiationneeded = async event => { Logger.debug("onnegotiationneeded"); + //If ice restart is not true, then 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}); + } catch (error) { + Logger.error('Error during negotiation', error); + } } if (this.dataChannelEnabled) { @@ -1136,7 +1152,12 @@ export class WebRTCAdaptor { 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; + } else if (obj.state === "failed") { + this.iceRestart = true; + this.remotePeerConnection[streamId].restartIce(); + } else if (obj.state === "disconnected" || obj.state === "closed") { this.reconnectIfRequired(3000); } this.notifyEventListeners("ice_connection_state_changed", obj); diff --git a/src/test/js/webrtc_adaptor.test.js b/src/test/js/webrtc_adaptor.test.js index be34a32d..0bae8b1a 100644 --- a/src/test/js/webrtc_adaptor.test.js +++ b/src/test/js/webrtc_adaptor.test.js @@ -1923,6 +1923,28 @@ describe("WebRTCAdaptor", function() { }); + 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; + }); + }); + }); From 5ff035bc1dcb44a18060861fd95acd974b97bcab Mon Sep 17 00:00:00 2001 From: Mustafa BOLEKEN Date: Mon, 11 Nov 2024 12:40:43 +0300 Subject: [PATCH 2/2] Add unit tests --- src/test/js/webrtc_adaptor.test.js | 81 ++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/test/js/webrtc_adaptor.test.js b/src/test/js/webrtc_adaptor.test.js index 0bae8b1a..7892150e 100644 --- a/src/test/js/webrtc_adaptor.test.js +++ b/src/test/js/webrtc_adaptor.test.js @@ -1945,6 +1945,87 @@ describe("WebRTCAdaptor", function() { }); }); + it("should handle negotiation when iceRestart is true", async function() { + let adaptor; + let mockPeerConnection; + + beforeEach(function () { + adaptor = new WebRTCAdaptor({ + websocketURL: "ws://example.com", + initializeComponents: false, + }); + mockPeerConnection = { + setLocalDescription: sinon.fake.resolves(), + createOffer: sinon.fake.resolves({ sdp: "mockSdp" }), + localDescription: { sdp: "mockSdp" } + }; + adaptor.remotePeerConnection["stream1"] = mockPeerConnection; + }); + + const streamId = "stream1"; + adaptor.remotePeerConnection[streamId] = mockPeerConnection; + adaptor.iceRestart = true; + + await adaptor.remotePeerConnection[streamId].onnegotiationneeded(); + + expect(mockPeerConnection.createOffer.calledWith({ iceRestart: true })).to.be.true; + expect(mockPeerConnection.setLocalDescription.calledWith({ sdp: "mockSdp" })).to.be.true; + expect(adaptor.webSocketAdaptor.send.calledWith({ desc: { sdp: "mockSdp" } })).to.be.true; + }); + + it("should not handle negotiation when iceRestart is false", async function() { + let adaptor; + let mockPeerConnection; + + beforeEach(function () { + adaptor = new WebRTCAdaptor({ + websocketURL: "ws://example.com", + initializeComponents: false, + }); + mockPeerConnection = { + setLocalDescription: sinon.fake(), + createOffer: sinon.fake() + }; + adaptor.remotePeerConnection["stream1"] = mockPeerConnection; + }); + + const streamId = "stream1"; + adaptor.remotePeerConnection[streamId] = mockPeerConnection; + adaptor.iceRestart = false; + + await adaptor.remotePeerConnection[streamId].onnegotiationneeded(); + + expect(mockPeerConnection.createOffer.called).to.be.false; + expect(mockPeerConnection.setLocalDescription.called).to.be.false; + expect(adaptor.webSocketAdaptor.send.called).to.be.false; + }); + + it("should log error during negotiation", async function() { + let adaptor; + let mockPeerConnection; + + beforeEach(function () { + adaptor = new WebRTCAdaptor({ + websocketURL: "ws://example.com", + initializeComponents: false, + }); + mockPeerConnection = { + setLocalDescription: sinon.fake.rejects(new Error("negotiation error")), + createOffer: sinon.fake.resolves({ sdp: "mockSdp" }) + }; + adaptor.remotePeerConnection["stream1"] = mockPeerConnection; + }); + + const streamId = "stream1"; + adaptor.remotePeerConnection[streamId] = mockPeerConnection; + adaptor.iceRestart = true; + const loggerSpy = sinon.spy(Logger, "error"); + + await adaptor.remotePeerConnection[streamId].onnegotiationneeded(); + + expect(loggerSpy.calledWith('Error during negotiation', sinon.match.instanceOf(Error))).to.be.true; + loggerSpy.restore(); + }); });