openvidu-browser: restore automatic Stream reconnection on ICE errors

pull/763/head
pabloFuente 2022-11-16 13:16:16 +01:00
parent 6ab6c22158
commit 1652311448
1 changed files with 47 additions and 63 deletions

View File

@ -707,11 +707,11 @@ export class Stream {
this.stopWebRtcStats(); this.stopWebRtcStats();
logger.info( logger.info(
(!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + (!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') +
'RTCPeerConnection with id [' + 'RTCPeerConnection with id [' +
webrtcId + webrtcId +
"] from 'Stream' with id [" + "] from 'Stream' with id [" +
this.streamId + this.streamId +
'] is now closed' '] is now closed'
); );
} }
@ -1064,8 +1064,7 @@ export class Stream {
} }
if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) { if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
logger.warn( logger.warn(
`OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${ `OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${this.streamId
this.streamId
} (${this.isLocal() ? 'Publisher' : 'Subscriber'}) will force a reconnection` } (${this.isLocal() ? 'Publisher' : 'Subscriber'}) will force a reconnection`
); );
return true; return true;
@ -1102,8 +1101,7 @@ export class Stream {
} else { } else {
// Ongoing reconnection // Ongoing reconnection
console.warn( console.warn(
`Trying to reconnect stream ${this.streamId} (${ `Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) but an ongoing reconnection process is active. Waiting for response...` }) but an ongoing reconnection process is active. Waiting for response...`
); );
this.reconnectionEventEmitter.once('success', () => resolve()); this.reconnectionEventEmitter.once('success', () => resolve());
@ -1158,11 +1156,11 @@ export class Stream {
if (this.isSendVideo()) { if (this.isSendVideo()) {
typeOfVideo = typeOfVideo =
typeof MediaStreamTrack !== 'undefined' && typeof MediaStreamTrack !== 'undefined' &&
this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack this.outboundStreamOpts.publisherProperties.videoSource instanceof MediaStreamTrack
? TypeOfVideo.CUSTOM ? TypeOfVideo.CUSTOM
: this.isSendScreen() : this.isSendScreen()
? TypeOfVideo.SCREEN ? TypeOfVideo.SCREEN
: TypeOfVideo.CAMERA; : TypeOfVideo.CAMERA;
} }
params = { params = {
doLoopback: this.displayMyRemote() || false, doLoopback: this.displayMyRemote() || false,
@ -1207,10 +1205,10 @@ export class Stream {
this.initWebRtcStats(); this.initWebRtcStats();
logger.info( logger.info(
"'Publisher' (" + "'Publisher' (" +
this.streamId + this.streamId +
') successfully ' + ') successfully ' +
(reconnect ? 'reconnected' : 'published') + (reconnect ? 'reconnected' : 'published') +
' to session' ' to session'
); );
finalResolve(); finalResolve();
@ -1229,9 +1227,7 @@ export class Stream {
}, },
simulcast: this.outboundStreamOpts.publisherProperties.videoSimulcast ?? this.session.openvidu.videoSimulcast, simulcast: this.outboundStreamOpts.publisherProperties.videoSimulcast ?? this.session.openvidu.videoSimulcast,
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection), onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { onIceConnectionStateException: this.onIceConnectionStateExceptionHandler.bind(this),
this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]);
},
iceServers: this.getIceServersConf(), iceServers: this.getIceServersConf(),
mediaStream: this.mediaStream, mediaStream: this.mediaStream,
mediaServer: this.session.openvidu.mediaServer, mediaServer: this.session.openvidu.mediaServer,
@ -1290,11 +1286,11 @@ export class Stream {
finalRejectForSubscription(reconnect: boolean, error: any, reject: (reason?: any) => void) { finalRejectForSubscription(reconnect: boolean, error: any, reject: (reason?: any) => void) {
logger.error( logger.error(
"Error for 'Subscriber' (" + "Error for 'Subscriber' (" +
this.streamId + this.streamId +
') while trying to ' + ') while trying to ' +
(reconnect ? 'reconnect' : 'subscribe') + (reconnect ? 'reconnect' : 'subscribe') +
': ' + ': ' +
error.toString() error.toString()
); );
if (reconnect) { if (reconnect) {
this.reconnectionEventEmitter?.emitEvent('error', [error]); this.reconnectionEventEmitter?.emitEvent('error', [error]);
@ -1403,9 +1399,7 @@ export class Stream {
}, },
simulcast: false, simulcast: false,
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection), onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { onIceConnectionStateException: this.onIceConnectionStateExceptionHandler.bind(this),
this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]);
},
iceServers: this.getIceServersConf(), iceServers: this.getIceServersConf(),
mediaServer: this.session.openvidu.mediaServer, mediaServer: this.session.openvidu.mediaServer,
typeOfVideo: this.typeOfVideo ? TypeOfVideo[this.typeOfVideo] : undefined typeOfVideo: this.typeOfVideo ? TypeOfVideo[this.typeOfVideo] : undefined
@ -1539,8 +1533,7 @@ export class Stream {
private onIceConnectionFailed() { private onIceConnectionFailed() {
// Immediately reconnect, as this is a terminal error // Immediately reconnect, as this is a terminal error
logger.log( logger.log(
`[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${ `[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
})` })`
); );
this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED); this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED);
@ -1549,8 +1542,7 @@ export class Stream {
private onIceConnectionDisconnected() { private onIceConnectionDisconnected() {
// Wait to see if the ICE connection is able to reconnect // Wait to see if the ICE connection is able to reconnect
logger.log( logger.log(
`[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${ `[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${this.streamId
this.streamId
} (${this.isLocal() ? 'Publisher' : 'Subscriber'}) if not possible` } (${this.isLocal() ? 'Publisher' : 'Subscriber'}) if not possible`
); );
const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000; const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
@ -1559,16 +1551,14 @@ export class Stream {
case 'failed': case 'failed':
// Do nothing, as an ICE_CONNECTION_FAILED event will have already raised // Do nothing, as an ICE_CONNECTION_FAILED event will have already raised
logger.warn( logger.warn(
`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${ `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) is now failed after ICE_CONNECTION_DISCONNECTED` }) is now failed after ICE_CONNECTION_DISCONNECTED`
); );
break; break;
case 'connected': case 'connected':
case 'completed': case 'completed':
logger.log( logger.log(
`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${ `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}` }) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}`
); );
break; break;
@ -1578,8 +1568,7 @@ export class Stream {
case 'disconnected': case 'disconnected':
// Rest of states // Rest of states
logger.warn( logger.warn(
`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${ `[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}` }) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}`
); );
this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED); this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED);
@ -1595,23 +1584,20 @@ export class Stream {
case 'connected': case 'connected':
case 'completed': case 'completed':
logger.log( logger.log(
`[${event}] Stream ${this.streamId} (${ `[${event}] Stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}` }) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`
); );
break; break;
default: default:
logger.error( logger.error(
`[${event}] Stream ${this.streamId} (${ `[${event}] Stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}` }) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`
); );
break; break;
} }
} catch (error) { } catch (error) {
logger.error( logger.error(
`[${event}] Error reconnecting stream ${this.streamId} (${ `[${event}] Error reconnecting stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) after ${event}: ${error}` }) after ${event}: ${error}`
); );
} }
@ -1631,13 +1617,27 @@ export class Stream {
} }
} }
private async awaitWebRtcPeerConnectionState(timeout: number): Promise<RTCIceConnectionState> {
let state = this.getRTCPeerConnection().iceConnectionState;
const interval = 150;
const intervals = Math.ceil(timeout / interval);
for (let i = 0; i < intervals; i++) {
state = this.getRTCPeerConnection().iceConnectionState;
if (state === 'connected' || state === 'completed') {
break;
}
// Sleep
await new Promise((resolve) => setTimeout(resolve, interval));
}
return state;
}
private async reconnectStream(event: string) { private async reconnectStream(event: string) {
const isWsConnected = await this.isWebsocketConnected(event, 3000); const isWsConnected = await this.isWebsocketConnected(event, 3000);
if (isWsConnected) { if (isWsConnected) {
// There is connection to openvidu-server. The RTCPeerConnection is the only one broken // There is connection to openvidu-server. The RTCPeerConnection is the only one broken
logger.log( logger.log(
`[${event}] Trying to reconnect stream ${this.streamId} (${ `[${event}] Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber'
}) and the websocket is opened` }) and the websocket is opened`
); );
if (this.isLocal()) { if (this.isLocal()) {
@ -1648,9 +1648,8 @@ export class Stream {
} else { } else {
// There is no connection to openvidu-server. Nothing can be done. The automatic reconnection // There is no connection to openvidu-server. Nothing can be done. The automatic reconnection
// feature should handle a possible reconnection of RTCPeerConnection in case network comes back // feature should handle a possible reconnection of RTCPeerConnection in case network comes back
const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${ const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${this.isLocal() ? 'Publisher' : 'Subscriber'
this.isLocal() ? 'Publisher' : 'Subscriber' }) but the websocket wasn't opened`;
}) but the websocket wasn't opened`;
logger.error(errorMsg); logger.error(errorMsg);
throw Error(errorMsg); throw Error(errorMsg);
} }
@ -1680,21 +1679,6 @@ export class Stream {
}); });
} }
private async awaitWebRtcPeerConnectionState(timeout: number): Promise<RTCIceConnectionState> {
let state = this.getRTCPeerConnection().iceConnectionState;
const interval = 150;
const intervals = Math.ceil(timeout / interval);
for (let i = 0; i < intervals; i++) {
state = this.getRTCPeerConnection().iceConnectionState;
if (state === 'connected' || state === 'completed') {
break;
}
// Sleep
await new Promise((resolve) => setTimeout(resolve, interval));
}
return state;
}
/** /**
* @hidden * @hidden
*/ */