diff --git a/src/main/js/webrtc_adaptor.js b/src/main/js/webrtc_adaptor.js index cad4b229..5de31a7a 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). @@ -628,6 +634,7 @@ export class WebRTCAdaptor { } tryAgain() { + Logger.debug("tryAgain is called"); const now = Date.now(); //to prevent too many trial from different paths @@ -647,6 +654,7 @@ export class WebRTCAdaptor { { // 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(() => { @@ -1042,24 +1050,7 @@ export class WebRTCAdaptor { 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); @@ -1068,8 +1059,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, 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}); + } catch (error) { + Logger.error('Error during negotiation', error); + } } if (this.dataChannelEnabled) { @@ -1112,7 +1113,14 @@ 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; + } + if (obj.state === "failed") { + this.iceRestart = true; + this.remotePeerConnection[streamId].restartIce(); + } + if (obj.state === "disconnected" || obj.state === "closed") { this.reconnectIfRequired(3000); } this.notifyEventListeners("ice_connection_state_changed", obj); @@ -1134,6 +1142,44 @@ export class WebRTCAdaptor { 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) + }); + } + // + //parameters.degradationPreference + }); + } + } + /** * Called internally to close PeerConnection. * @param {string} streamId : unique id for the stream @@ -1149,10 +1195,10 @@ export class WebRTCAdaptor { 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) { diff --git a/src/test/js/webrtc_adaptor.test.js b/src/test/js/webrtc_adaptor.test.js index 504c97b2..4ed056f2 100644 --- a/src/test/js/webrtc_adaptor.test.js +++ b/src/test/js/webrtc_adaptor.test.js @@ -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; + }); + }); + });