Skip to content

Commit 1561586

Browse files
authored
Merge pull request #519 from ant-media/reconnect-faster
Reconnect faster when connection is failed/closed
2 parents 404c515 + e353d95 commit 1561586

File tree

4 files changed

+191
-63
lines changed

4 files changed

+191
-63
lines changed

src/main/js/webrtc_adaptor.js

+80-56
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,11 @@ export class WebRTCAdaptor {
358358
* This is the time info for the last reconnection attempt
359359
*/
360360
this.lastReconnectiontionTrialTime = 0;
361+
362+
/**
363+
* TimerId for the pending try again call
364+
*/
365+
this.pendingTryAgainTimerId = -1;
361366

362367
/**
363368
* All media management works for teh local stream are made by @MediaManager class.
@@ -516,15 +521,8 @@ export class WebRTCAdaptor {
516521
}
517522
//init peer connection for reconnectIfRequired
518523
this.initPeerConnection(streamId, "publish");
519-
setTimeout(() => {
520-
//check if it is connected or not
521-
//this resolves if the server responds with some error message
522-
if (this.iceConnectionState(this.publishStreamId) != "checking" && this.iceConnectionState(this.publishStreamId) != "connected" && this.iceConnectionState(this.publishStreamId) != "completed") {
523-
//if it is not connected, try to reconnect
524-
this.reconnectIfRequired(0);
525-
}
526-
}, 3000);
527-
524+
525+
this.reconnectIfRequired(3000, false);
528526
}
529527

530528
sendPublishCommand(streamId, token, subscriberId, subscriberCode, streamName, mainTrack, metaData, role, videoEnabled, audioEnabled) {
@@ -605,43 +603,44 @@ export class WebRTCAdaptor {
605603

606604
//init peer connection for reconnectIfRequired
607605
this.initPeerConnection(streamId, "play");
608-
609-
setTimeout(() => {
610-
//check if it is connected or not
611-
//this resolves if the server responds with some error message
612-
if (this.iceConnectionState(streamId) != "checking" &&
613-
this.iceConnectionState(streamId) != "connected" &&
614-
this.iceConnectionState(streamId) != "completed") {
615-
//if it is not connected, try to reconnect
616-
this.reconnectIfRequired(0);
617-
}
618-
}, 3000);
606+
this.reconnectIfRequired(3000, false);
619607
}
620608

621609
/**
622610
* Reconnects to the stream if it is not stopped on purpose
623611
* @param {number} [delayMs]
624612
* @returns
625613
*/
626-
reconnectIfRequired(delayMs = 3000) {
614+
reconnectIfRequired(delayMs = 3000, forceReconnect = false) {
627615
if (this.reconnectIfRequiredFlag) {
628-
//It's important to run the following methods after 3000 ms because the stream may be stopped by the user in the meantime
629-
if (delayMs > 0) {
630-
setTimeout(() => {
631-
this.tryAgain();
632-
}, delayMs);
616+
if (delayMs <= 0) {
617+
delayMs = 500;
618+
//clear the timer because there is a demand to reconnect without delay
619+
clearTimeout(this.pendingTryAgainTimerId);
620+
this.pendingTryAgainTimerId = -1;
633621
}
634-
else {
635-
this.tryAgain()
622+
623+
if (this.pendingTryAgainTimerId == -1)
624+
{
625+
this.pendingTryAgainTimerId = setTimeout(() =>
626+
{
627+
this.pendingTryAgainTimerId = -1;
628+
this.tryAgain(forceReconnect);
629+
},
630+
delayMs);
636631
}
637632
}
638633
}
639634

640-
tryAgain() {
635+
tryAgain(forceReconnect) {
641636

642637
const now = Date.now();
643638
//to prevent too many trial from different paths
644-
if (now - this.lastReconnectiontionTrialTime < 3000) {
639+
const timeDiff = now - this.lastReconnectiontionTrialTime;;
640+
if (timeDiff < 3000 && forceReconnect == false) {
641+
//check again 1 seconds later if it is not stopped on purpose
642+
Logger.debug("Reconnection request received after "+ timeDiff+" ms. It should be at least 3000ms. It will try again after 1000ms");
643+
this.reconnectIfRequired(1000, forceReconnect);
645644
return;
646645
}
647646
this.lastReconnectiontionTrialTime = now;
@@ -650,10 +649,10 @@ export class WebRTCAdaptor {
650649
//if remotePeerConnection has a peer connection for the stream id, it means that it is not stopped on purpose
651650

652651
if (this.remotePeerConnection[this.publishStreamId] != null &&
652+
(forceReconnect ||
653653
//check connection status to not stop streaming an active stream
654-
this.iceConnectionState(this.publishStreamId) != "checking" &&
655-
this.iceConnectionState(this.publishStreamId) != "connected" &&
656-
this.iceConnectionState(this.publishStreamId) != "completed") {
654+
["checking", "connected", "completed"].indexOf(this.iceConnectionState(this.publishStreamId)) === -1)
655+
) {
657656
// notify that reconnection process started for publish
658657
this.notifyEventListeners("reconnection_attempt_for_publisher", this.publishStreamId);
659658

@@ -669,11 +668,12 @@ export class WebRTCAdaptor {
669668
//reconnect play
670669
for (var index in this.playStreamId) {
671670
let streamId = this.playStreamId[index];
672-
if (this.remotePeerConnection[streamId] != "null" &&
673-
//check connection status to not stop streaming an active stream
674-
this.iceConnectionState(streamId) != "checking" &&
675-
this.iceConnectionState(streamId) != "connected" &&
676-
this.iceConnectionState(streamId) != "completed") {
671+
if (this.remotePeerConnection[streamId] != null &&
672+
(forceReconnect ||
673+
//check connection status to not stop streaming an active stream
674+
["checking", "connected", "completed"].indexOf(this.iceConnectionState(streamId)) === -1
675+
)
676+
) {
677677
// notify that reconnection process started for play
678678
this.notifyEventListeners("reconnection_attempt_for_player", streamId);
679679

@@ -1136,27 +1136,38 @@ export class WebRTCAdaptor {
11361136

11371137
this.remotePeerConnection[streamId].oniceconnectionstatechange = event => {
11381138
var obj = { state: this.remotePeerConnection[streamId].iceConnectionState, streamId: streamId };
1139-
if (obj.state == "failed" || obj.state == "disconnected" || obj.state == "closed") {
1140-
this.reconnectIfRequired(3000);
1141-
}
1142-
this.notifyEventListeners("ice_connection_state_changed", obj);
1143-
1144-
//
1145-
if (!this.isPlayMode && !this.playStreamId.includes(streamId)) {
1146-
if (this.remotePeerConnection[streamId].iceConnectionState == "connected") {
11471139

1148-
this.mediaManager.changeBandwidth(this.mediaManager.bandwidth, streamId).then(() => {
1149-
Logger.debug("Bandwidth is changed to " + this.mediaManager.bandwidth);
1150-
})
1151-
.catch(e => Logger.warn(e));
1152-
}
1153-
}
1140+
this.oniceconnectionstatechangeCallback(obj);
11541141
}
11551142

11561143
}
11571144

11581145
return this.remotePeerConnection[streamId];
11591146
}
1147+
1148+
oniceconnectionstatechangeCallback(obj)
1149+
{
1150+
Logger.debug("ice connection state is " +obj.state + " for streamId: " + obj.streamId);
1151+
if (obj.state == "failed" || obj.state == "disconnected" || obj.state == "closed") {
1152+
//try immediately
1153+
Logger.debug("ice connection state is failed, disconnected or closed for streamId: " + obj.streamId + " it will try to reconnect immediately");
1154+
this.reconnectIfRequired(0, false);
1155+
}
1156+
this.notifyEventListeners("ice_connection_state_changed", obj);
1157+
1158+
//
1159+
if (!this.isPlayMode && !this.playStreamId.includes(obj.streamId)) {
1160+
if (this.remotePeerConnection[obj.streamId] != null && this.remotePeerConnection[obj.streamId].iceConnectionState == "connected") {
1161+
1162+
this.mediaManager.changeBandwidth(this.mediaManager.bandwidth, obj.streamId).then(() => {
1163+
Logger.debug("Bandwidth is changed to " + this.mediaManager.bandwidth);
1164+
})
1165+
.catch(e => Logger.warn(e));
1166+
}
1167+
}
1168+
}
1169+
1170+
11601171

11611172
/**
11621173
* Called internally to close PeerConnection.
@@ -1745,10 +1756,7 @@ export class WebRTCAdaptor {
17451756
websocket_url: this.websocketURL,
17461757
webrtcadaptor: this,
17471758
callback: (info, obj) => {
1748-
if (info == "closed") {
1749-
this.reconnectIfRequired();
1750-
}
1751-
this.notifyEventListeners(info, obj);
1759+
this.websocketCallback(info, obj)
17521760
},
17531761
callbackError: (error, message) => {
17541762
this.notifyErrorEventListeners(error, message)
@@ -1757,6 +1765,22 @@ export class WebRTCAdaptor {
17571765
});
17581766
}
17591767
}
1768+
1769+
websocketCallback(info, obj) {
1770+
1771+
if (info == "closed" || info == "server_will_stop") {
1772+
Logger.info("Critical response from server:"+ info +". It will reconnect immediately if there is an active connection");
1773+
1774+
//close websocket reconnect again
1775+
if (info == "server_will_stop") {
1776+
this.webSocketAdaptor.close();
1777+
}
1778+
//try with forcing reconnect because webrtc will be closed as well
1779+
this.reconnectIfRequired(0, true);
1780+
}
1781+
1782+
this.notifyEventListeners(info, obj);
1783+
}
17601784

17611785
/**
17621786
* Called to stop Web Socket connection

src/main/webapp/conference.html

+7-6
Original file line numberDiff line numberDiff line change
@@ -687,12 +687,12 @@ <h3 class="col text-muted">WebRTC Multitrack Conference</h3>
687687
var state = webRTCAdaptor
688688
.signallingState(publishStreamId);
689689
if (state != null
690-
&& state != "closed") {
690+
&& state != "closed")
691+
{
691692
var iceState = webRTCAdaptor
692693
.iceConnectionState(publishStreamId);
693-
if (iceState != null
694-
&& iceState != "failed"
695-
&& iceState != "disconnected") {
694+
if (iceState != null && iceState != "new" && iceState != "closed" && iceState != "failed" && iceState != "disconnected")
695+
{
696696
startAnimation();
697697
}
698698
}
@@ -864,10 +864,11 @@ <h3 class="col text-muted">WebRTC Multitrack Conference</h3>
864864
}
865865
else {
866866
//errorHandler(error, message);
867-
$('video').notify("Warning: " + errorHandler(error, message), {
867+
868+
$('#roomName').notify("Warning: " + errorHandler(error, message), {
868869
autoHideDelay: 5000,
869870
className: 'error',
870-
position: 'top right'
871+
position: 'top center'
871872
});
872873
}
873874

src/main/webapp/samples/publish_webrtc.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@
490490
var state = webRTCAdaptor.signallingState(streamId);
491491
if (state != null && state != "closed") {
492492
var iceState = webRTCAdaptor.iceConnectionState(streamId);
493-
if (iceState != null && iceState != "failed" && iceState != "disconnected") {
493+
if (iceState != null && iceState != "new" && iceState != "closed" && iceState != "failed" && iceState != "disconnected") {
494494
startAnimation();
495495
}
496496
else {

src/test/js/webrtc_adaptor.test.js

+103
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,116 @@ describe("WebRTCAdaptor", function() {
262262
expect(webSocketAdaptor.connecting).to.be.false;
263263

264264
});
265+
266+
267+
it("reconnectIfRequired", async function() {
268+
var adaptor = new WebRTCAdaptor({
269+
websocketURL: "ws://example.com",
270+
isPlayMode: true
271+
});
272+
273+
let tryAgain = sinon.replace(adaptor, "tryAgain", sinon.fake());
274+
275+
276+
adaptor.reconnectIfRequired(100);
277+
adaptor.reconnectIfRequired(200);
278+
clock.tick(300);
279+
280+
expect(tryAgain.calledOnce).to.be.true;
281+
282+
283+
284+
});
285+
286+
it("oniceconnectionstatechangeCallback", async function() {
287+
var adaptor = new WebRTCAdaptor({
288+
websocketURL: "ws://example.com",
289+
isPlayMode: true
290+
});
291+
292+
let reconnectIfRequired = sinon.replace(adaptor, "reconnectIfRequired", sinon.fake());
293+
var obj = { state: "failed", streamId: "streamId" };
294+
295+
var stopFake = sinon.replace(adaptor, "stop", sinon.fake());
296+
adaptor.oniceconnectionstatechangeCallback(obj);
297+
expect(reconnectIfRequired.calledOnce).to.be.true;
298+
expect(reconnectIfRequired.calledWithExactly(0, false)).to.be.true;
299+
300+
obj = { state: "closed", streamId: "streamId" };
301+
302+
adaptor.oniceconnectionstatechangeCallback(obj);
303+
expect(reconnectIfRequired.calledTwice).to.be.true;
304+
expect(reconnectIfRequired.calledWithExactly(0, false)).to.be.true;
305+
306+
obj = { state: "disconnected", streamId: "streamId" };
307+
adaptor.oniceconnectionstatechangeCallback(obj);
308+
expect(reconnectIfRequired.callCount).to.be.equal(3);
309+
310+
311+
obj = { state: "connected", streamId: "streamId" };
312+
adaptor.oniceconnectionstatechangeCallback(obj);
313+
expect(reconnectIfRequired.callCount).to.be.equal(3);
314+
315+
316+
});
317+
318+
it("websocketCallback", async function() {
319+
var adaptor = new WebRTCAdaptor({
320+
websocketURL: "ws://example.com",
321+
isPlayMode: true
322+
});
323+
324+
let reconnectIfRequired = sinon.replace(adaptor, "reconnectIfRequired", sinon.fake());
325+
326+
var stopFake = sinon.replace(adaptor, "stop", sinon.fake());
327+
adaptor.websocketCallback("closed");
328+
329+
expect(reconnectIfRequired.calledOnce).to.be.true;
330+
expect(reconnectIfRequired.calledWithExactly(0, true)).to.be.true;
331+
332+
333+
adaptor.websocketCallback("anyOtherThing");
334+
335+
//it should be still once
336+
expect(reconnectIfRequired.calledOnce).to.be.true;
337+
338+
339+
});
340+
341+
it("tryAgainForceReconnect", async function() {
342+
343+
var adaptor = new WebRTCAdaptor({
344+
websocketURL: "ws://example.com",
345+
isPlayMode: true
346+
});
347+
var streamId = "streamId";
348+
adaptor.publishStreamId = streamId;
349+
350+
let stop = sinon.replace(adaptor, "stop", sinon.fake());
351+
352+
var mockPC = sinon.mock(RTCPeerConnection);
353+
adaptor.remotePeerConnection[streamId] = mockPC
354+
mockPC.iceConnectionState = "connected";
355+
356+
adaptor.tryAgain(false);
357+
358+
expect(stop.calledOnce).to.be.false;
359+
360+
adaptor.tryAgain(true);
361+
expect(stop.calledOnce).to.be.true;
362+
363+
364+
});
365+
265366

266367

267368
it("Frequent try again call", async function() {
268369
var adaptor = new WebRTCAdaptor({
269370
websocketURL: "ws://example.com",
270371
isPlayMode: true
271372
});
373+
374+
expect(adaptor.pendingTryAgainTimerId).to.be.equal(-1);
272375
let webSocketAdaptor = sinon.mock(adaptor.webSocketAdaptor);
273376
let closeExpectation = webSocketAdaptor.expects("close");
274377

0 commit comments

Comments
 (0)