mirror of https://github.com/OpenVidu/openvidu.git
openvidu-browser: restore automatic Stream reconnection on ICE errors
parent
6ab6c22158
commit
1652311448
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue