diff --git a/packages/@webex/plugin-meetings/src/meeting/index.ts b/packages/@webex/plugin-meetings/src/meeting/index.ts index 6fa1a188e31..a1cd18ad353 100644 --- a/packages/@webex/plugin-meetings/src/meeting/index.ts +++ b/packages/@webex/plugin-meetings/src/meeting/index.ts @@ -4904,6 +4904,8 @@ export default class Meeting extends StatelessWebexPlugin { ); } + this.cleanUpBeforeReconnection(); + return this.reconnectionManager .reconnect(options, async () => { await this.waitForRemoteSDPAnswer(); @@ -7032,6 +7034,23 @@ export default class Meeting extends StatelessWebexPlugin { } } + private async cleanUpBeforeReconnection(): Promise { + try { + // when media fails, we want to upload a webrtc dump to see whats going on + // this function is async, but returns once the stats have been gathered + await this.forceSendStatsReport({callFrom: 'cleanUpBeforeReconnection'}); + + if (this.statsAnalyzer) { + await this.statsAnalyzer.stopAnalyzer(); + } + } catch (error) { + LoggerProxy.logger.error( + 'Meeting:index#cleanUpBeforeReconnection --> Error during cleanup: ', + error + ); + } + } + /** * Creates an instance of LocusMediaRequest for this meeting - it is needed for doing any calls * to Locus /media API (these are used for sending Roap messages and updating audio/video mute status). diff --git a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js index 75d4627b912..78d5fb58bc9 100644 --- a/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js +++ b/packages/@webex/plugin-meetings/test/unit/spec/meeting/index.js @@ -377,7 +377,10 @@ describe('plugin-meetings', () => { } ); assert.equal(newMeeting.correlationId, newMeeting.id); - assert.deepEqual(newMeeting.callStateForMetrics, {correlationId: newMeeting.id, sessionCorrelationId: ''}); + assert.deepEqual(newMeeting.callStateForMetrics, { + correlationId: newMeeting.id, + sessionCorrelationId: '', + }); }); it('correlationId can be provided in callStateForMetrics', () => { @@ -3808,12 +3811,12 @@ describe('plugin-meetings', () => { id: 'fake locus from mocked join request', locusUrl: 'fake locus url', mediaId: 'fake media id', - }) + }); sinon.stub(meeting.meetingRequest, 'joinMeeting').resolves({ headers: { trackingid: 'fake tracking id', - } - }) + }, + }); await meeting.join({enableMultistream: isMultistream}); }); @@ -4006,7 +4009,10 @@ describe('plugin-meetings', () => { assert.notCalled( meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream ); - assert.throws(meeting.publishStreams(localStreams), `Attempted to publish microphone stream with ended readyState, correlationId=${meeting.correlationId}`); + assert.throws( + meeting.publishStreams(localStreams), + `Attempted to publish microphone stream with ended readyState, correlationId=${meeting.correlationId}` + ); } else { assert.calledOnceWithExactly( meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream, @@ -4019,7 +4025,10 @@ describe('plugin-meetings', () => { assert.notCalled( meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream ); - assert.throws(meeting.publishStreams(localStreams), `Attempted to publish camera stream with ended readyState, correlationId=${meeting.correlationId}`); + assert.throws( + meeting.publishStreams(localStreams), + `Attempted to publish camera stream with ended readyState, correlationId=${meeting.correlationId}` + ); } else { assert.calledOnceWithExactly( meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream, @@ -4032,7 +4041,10 @@ describe('plugin-meetings', () => { assert.notCalled( meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream ); - assert.throws(meeting.publishStreams(localStreams), `Attempted to publish screenShare audio stream with ended readyState, correlationId=${meeting.correlationId}`); + assert.throws( + meeting.publishStreams(localStreams), + `Attempted to publish screenShare audio stream with ended readyState, correlationId=${meeting.correlationId}` + ); } else { assert.calledOnceWithExactly( meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream, @@ -4045,7 +4057,10 @@ describe('plugin-meetings', () => { assert.notCalled( meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream ); - assert.throws(meeting.publishStreams(localStreams), `Attempted to publish screenShare video stream with ended readyState, correlationId=${meeting.correlationId}`); + assert.throws( + meeting.publishStreams(localStreams), + `Attempted to publish screenShare video stream with ended readyState, correlationId=${meeting.correlationId}` + ); } else { assert.calledOnceWithExactly( meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream, @@ -4340,14 +4355,14 @@ describe('plugin-meetings', () => { const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging'); await meeting.addMedia({audioEnabled: false}); //calling handleDeviceLogging with audioEnaled as true adn videoEnabled as false - assert.calledWith(handleDeviceLoggingSpy,false,true); + assert.calledWith(handleDeviceLoggingSpy, false, true); }); it('addMedia() works correctly when video is disabled with no streams to publish', async () => { const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging'); await meeting.addMedia({videoEnabled: false}); //calling handleDeviceLogging audioEnabled as true videoEnabled as false - assert.calledWith(handleDeviceLoggingSpy,true,false); + assert.calledWith(handleDeviceLoggingSpy, true, false); }); it('addMedia() works correctly when video is disabled with no streams to publish', async () => { @@ -4416,12 +4431,11 @@ describe('plugin-meetings', () => { assert.calledTwice(locusMediaRequestStub); }); - it('addMedia() works correctly when both shareAudio and shareVideo is disabled with no streams publish', async () => { const handleDeviceLoggingSpy = sinon.spy(Meeting, 'handleDeviceLogging'); await meeting.addMedia({shareAudioEnabled: false, shareVideoEnabled: false}); //calling handleDeviceLogging with audioEnabled true and videoEnabled as true - assert.calledWith(handleDeviceLoggingSpy,true,true); + assert.calledWith(handleDeviceLoggingSpy, true, true); }); describe('publishStreams()/unpublishStreams() calls', () => { @@ -6998,7 +7012,10 @@ describe('plugin-meetings', () => { assert.deepEqual(meeting.callStateForMetrics, {correlationId, sessionCorrelationId: ''}); meeting.setCorrelationId(uuid1); assert.equal(meeting.correlationId, uuid1); - assert.deepEqual(meeting.callStateForMetrics, {correlationId: uuid1, sessionCorrelationId: ''}); + assert.deepEqual(meeting.callStateForMetrics, { + correlationId: uuid1, + sessionCorrelationId: '', + }); }); }); @@ -7670,11 +7687,11 @@ describe('plugin-meetings', () => { id: 'stream', getTracks: () => [{id: 'track', addEventListener: sinon.stub()}], }; - const simulateConnectionStateChange = (newState) => { + const simulateConnectionStateChange = async (newState) => { meeting.mediaProperties.webrtcMediaConnection.getConnectionState = sinon .stub() .returns(newState); - eventListeners[MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED](); + await eventListeners[MediaConnectionEventNames.PEER_CONNECTION_STATE_CHANGED](); }; beforeEach(() => { @@ -7930,7 +7947,7 @@ describe('plugin-meetings', () => { meeting.reconnectionManager = new ReconnectionManager(meeting); meeting.reconnectionManager.iceReconnected = sinon.stub().returns(undefined); meeting.setNetworkStatus = sinon.stub().returns(undefined); - meeting.statsAnalyzer = {startAnalyzer: sinon.stub()}; + meeting.statsAnalyzer = {startAnalyzer: sinon.stub(), stopAnalyzer: sinon.stub()}; meeting.mediaProperties.webrtcMediaConnection = { // mock the on() method and store all the listeners on: sinon.stub().callsFake((event, listener) => { @@ -8005,10 +8022,10 @@ describe('plugin-meetings', () => { }); describe('CONNECTION_STATE_CHANGED event when state = "Failed"', () => { - const mockFailedEvent = () => { + const mockFailedEvent = async () => { meeting.setupMediaConnectionListeners(); - simulateConnectionStateChange(ConnectionState.Failed); + await simulateConnectionStateChange(ConnectionState.Failed); }; const checkBehavioralMetricSent = (hasMediaConnectionConnectedAtLeastOnce = false) => { @@ -8038,6 +8055,22 @@ describe('plugin-meetings', () => { assert.notCalled(webex.internal.newMetrics.submitClientEvent); checkBehavioralMetricSent(true); }); + + it('stop stats analyzer during reconnection ', async () => { + meeting.hasMediaConnectionConnectedAtLeastOnce = true; + meeting.statsAnalyzer.stopAnalyzer = sinon.stub().resolves(); + meeting.reconnectionManager = { + reconnect: sinon.stub().resolves(), + resetReconnectionTimer: () => {} + }; + meeting.currentMediaStatus = { + video: true + }; + + await mockFailedEvent(); + + assert.calledOnce(meeting.statsAnalyzer.stopAnalyzer); + }); }); describe('should send correct metrics for ROAP_FAILURE event', () => {