Merge branch 'master' into fix-generateOffer

pull/577/head
Juan Navarro 2021-06-11 11:46:33 +02:00
commit 36f3d305d3
67 changed files with 1229 additions and 5118 deletions

View File

@ -10,20 +10,20 @@
},
"description": "OpenVidu Browser",
"devDependencies": {
"@types/node": "14.14.32",
"@types/node": "15.12.2",
"@types/platform": "1.3.3",
"browserify": "17.0.0",
"grunt": "1.3.0",
"grunt-cli": "1.3.2",
"grunt": "1.4.1",
"grunt-cli": "1.4.3",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-sass": "2.0.0",
"grunt-contrib-uglify": "5.0.0",
"grunt-contrib-uglify": "5.0.1",
"grunt-contrib-watch": "1.1.0",
"grunt-postcss": "0.9.0",
"grunt-string-replace": "1.3.1",
"grunt-ts": "6.0.0-beta.22",
"terser": "5.6.0",
"tsify": "5.0.2",
"terser": "5.7.0",
"tsify": "5.0.4",
"tslint": "6.1.3",
"typedoc": "0.19.2",
"typescript": "4.0.7"
@ -38,9 +38,9 @@
"scripts": {
"browserify": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/openvidu-browser-$VERSION.js -v",
"browserify-prod": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | ../node_modules/terser/bin/terser --source-map content=inline --output ../static/js/openvidu-browser-$VERSION.min.js",
"build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --lib dom,es5,es2015.promise,scripthost",
"build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --target es5 --lib dom,es5,es2015.promise,scripthost",
"docs": "./generate-docs.sh"
},
"types": "lib/index.d.ts",
"version": "2.17.0"
"version": "2.18.0"
}

View File

@ -67,6 +67,7 @@ let platform: PlatformUtils;
export class OpenVidu {
private jsonRpcClient: any;
private masterNodeHasCrashed = false;
/**
* @hidden
@ -104,6 +105,10 @@ export class OpenVidu {
* @hidden
*/
finalUserId: string;
/**
* @hidden
*/
mediaServer: string;
/**
* @hidden
*/
@ -744,7 +749,8 @@ export class OpenVidu {
onconnected: onConnectSucces,
ondisconnect: this.disconnectCallback.bind(this),
onreconnecting: this.reconnectingCallback.bind(this),
onreconnected: this.reconnectedCallback.bind(this)
onreconnected: this.reconnectedCallback.bind(this),
ismasternodecrashed: this.isMasterNodeCrashed.bind(this)
},
rpc: {
requestTimeout: 10000,
@ -761,12 +767,30 @@ export class OpenVidu {
networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session),
filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
iceCandidate: this.session.recvIceCandidate.bind(this.session),
mediaError: this.session.onMediaError.bind(this.session)
mediaError: this.session.onMediaError.bind(this.session),
masterNodeCrashedNotification: this.onMasterNodeCrashedNotification.bind(this)
}
};
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
}
/**
* @hidden
*/
onMasterNodeCrashedNotification(response): void {
console.error('Master Node has crashed');
this.masterNodeHasCrashed = true;
this.session.onLostConnection("nodeCrashed");
this.jsonRpcClient.close(4103, "Master Node has crashed");
}
/**
* @hidden
*/
getWsReadyState(): number {
return this.jsonRpcClient.getReadyState();
}
/**
* @hidden
*/
@ -1009,10 +1033,14 @@ export class OpenVidu {
if (!!this.session.connection) {
this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => {
if (!!error) {
logger.error(error);
logger.warn('Websocket was able to reconnect to OpenVidu Server, but your Connection was already destroyed due to timeout. You are no longer a participant of the Session and your media streams have been destroyed');
this.session.onLostConnection("networkDisconnect");
this.jsonRpcClient.close(4101, "Reconnection fault");
if (this.isMasterNodeCrashed()) {
logger.warn('Master Node has crashed!');
} else {
logger.error(error);
logger.warn('Websocket was able to reconnect to OpenVidu Server, but your Connection was already destroyed due to timeout. You are no longer a participant of the Session and your media streams have been destroyed');
this.session.onLostConnection("networkDisconnect");
this.jsonRpcClient.close(4101, "Reconnection fault");
}
} else {
this.jsonRpcClient.resetPing();
this.session.onRecoveredConnection();
@ -1030,6 +1058,10 @@ export class OpenVidu {
}
}
private isMasterNodeCrashed() {
return this.masterNodeHasCrashed;
}
private isRoomAvailable(): boolean {
if (this.session !== undefined && this.session instanceof Session) {
return true;

View File

@ -86,10 +86,6 @@ export class Publisher extends StreamManager {
* @hidden
*/
screenShareResizeInterval: NodeJS.Timer;
/**
* @hidden
*/
IEAdapter: any;
/**
* @hidden

View File

@ -197,7 +197,7 @@ export class Session extends EventDispatcher {
* #### Events dispatched
*
* The [[Session]] object of the local participant will dispatch a `sessionDisconnected` event.
* This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
* This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to each Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
* For every video removed, each Subscriber object will dispatch a `videoElementDestroyed` event.
* Call `event.preventDefault()` upon event `sessionDisconnected` to avoid this behavior and take care of disposing and cleaning all the Subscriber objects yourself.
@ -210,7 +210,7 @@ export class Session extends EventDispatcher {
* or/and `Session.disconnect()` in the previous session). See [[StreamEvent]] and [[VideoElementEvent]] to learn more.
*
* The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event if the disconnected participant was publishing.
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to that Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
* For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
* Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object yourself.
@ -437,7 +437,7 @@ export class Session extends EventDispatcher {
* Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session.
*
* The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event.
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) and
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) and
* delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
* For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
* Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object on your own.
@ -781,7 +781,7 @@ export class Session extends EventDispatcher {
onParticipantLeft(msg): void {
if (this.remoteConnections.size > 0) {
this.getRemoteConnection(msg.connectionId).then(connection => {
this.getRemoteConnection(msg.connectionId, 'onParticipantLeft').then(connection => {
if (!!connection.stream) {
const stream = connection.stream;
@ -823,7 +823,7 @@ export class Session extends EventDispatcher {
// Get the existing Connection created on 'onParticipantJoined' for
// existing participants or create a new one for new participants
let connection: Connection;
this.getRemoteConnection(response.id)
this.getRemoteConnection(response.id, 'onParticipantPublished')
.then(con => {
// Update existing Connection
@ -848,7 +848,7 @@ export class Session extends EventDispatcher {
// Your stream has been forcedly unpublished from the session
this.stopPublisherStream(msg.reason);
} else {
this.getRemoteConnection(msg.connectionId)
this.getRemoteConnection(msg.connectionId, 'onParticipantUnpublished')
.then(connection => {
@ -965,7 +965,7 @@ export class Session extends EventDispatcher {
// Your stream has been forcedly changed (filter feature)
callback(this.connection);
} else {
this.getRemoteConnection(msg.connectionId)
this.getRemoteConnection(msg.connectionId, 'onStreamPropertyChanged')
.then(connection => {
callback(connection);
})
@ -1411,7 +1411,7 @@ export class Session extends EventDispatcher {
});
}
private getRemoteConnection(connectionId: string): Promise<Connection> {
private getRemoteConnection(connectionId: string, operation: string): Promise<Connection> {
return new Promise<Connection>((resolve, reject) => {
const connection = this.remoteConnections.get(connectionId);
if (!!connection) {
@ -1419,9 +1419,8 @@ export class Session extends EventDispatcher {
resolve(connection);
} else {
// Remote connection not found. Reject with OpenViduError
const errorMessage = 'Remote connection ' + connectionId + " unknown when 'onParticipantLeft'. " +
const errorMessage = 'Remote connection ' + connectionId + " unknown when '" + operation + "'. " +
'Existing remote connections: ' + JSON.stringify(this.remoteConnections.keys());
reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
}
});
@ -1487,6 +1486,7 @@ export class Session extends EventDispatcher {
}
this.openvidu.role = opts.role;
this.openvidu.finalUserId = opts.finalUserId;
this.openvidu.mediaServer = opts.mediaServer;
this.capabilities = {
subscribe: true,
publish: this.openvidu.role !== 'SUBSCRIBER',

View File

@ -214,6 +214,10 @@ export class Stream {
* @hidden
*/
ee = new EventEmitter();
/**
* @hidden
*/
reconnectionEventEmitter: EventEmitter | undefined;
/**
@ -277,6 +281,19 @@ export class Stream {
}
/**
* Recreates the media connection with the server. This entails the disposal of the previous RTCPeerConnection and the re-negotiation
* of a new one, that will apply the same properties.
*
* This method can be useful in those situations were there the media connection breaks and OpenVidu is not able to recover on its own
* for any kind of unanticipated reason (see [Automatic reconnection](/en/latest/advanced-features/automatic-reconnection/)).
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the reconnection operation was successful and rejected with an Error object if not
*/
public reconnect(): Promise<void> {
return this.reconnectStream('API');
}
/**
* Applies an audio/video filter to the stream.
*
@ -461,11 +478,13 @@ export class Stream {
* @hidden
*/
disposeWebRtcPeer(): void {
let webrtcId;
if (!!this.webRtcPeer) {
this.webRtcPeer.dispose();
this.stopWebRtcStats();
webrtcId = this.webRtcPeer.id;
}
logger.info((!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + "WebRTCPeer from 'Stream' with id [" + this.streamId + '] is now closed');
this.stopWebRtcStats();
logger.info((!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + "RTCPeerConnection with id [" + webrtcId + "] from 'Stream' with id [" + this.streamId + '] is now closed');
}
/**
@ -774,7 +793,7 @@ export class Stream {
return false;
}
if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
logger.warn('OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ' + this.streamId + ' will force a reconnection');
logger.warn(`OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) will force a reconnection`);
return true;
}
const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
@ -802,10 +821,42 @@ export class Stream {
initWebRtcPeerSend(reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => {
if (!reconnect) {
if (reconnect) {
if (this.reconnectionEventEmitter == undefined) {
// There is no ongoing reconnection
this.reconnectionEventEmitter = new EventEmitter();
} else {
// Ongoing reconnection
console.warn(`Trying to reconnect stream ${this.streamId} (Publisher) but an ongoing reconnection process is active. Waiting for response...`);
this.reconnectionEventEmitter.once('success', () => {
resolve();
});
this.reconnectionEventEmitter.once('error', error => {
reject(error);
});
return;
}
} else {
// MediaStream will already have hark events for reconnected streams
this.initHarkEvents(); // Init hark events for the local stream
}
const finalResolve = () => {
if (reconnect) {
this.reconnectionEventEmitter?.emitEvent('success');
delete this.reconnectionEventEmitter;
}
resolve();
}
const finalReject = error => {
if (reconnect) {
this.reconnectionEventEmitter?.emitEvent('error', [error]);
delete this.reconnectionEventEmitter;
}
reject(error);
}
const successOfferCallback = (sdpOfferParam) => {
logger.debug('Sending SDP offer to publish as '
+ this.streamId, sdpOfferParam);
@ -839,9 +890,9 @@ export class Stream {
this.session.openvidu.sendRequest(method, params, (error, response) => {
if (error) {
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish"));
finalReject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish"));
} else {
reject('Error on publishVideo: ' + JSON.stringify(error));
finalReject('Error on publishVideo: ' + JSON.stringify(error));
}
} else {
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
@ -861,10 +912,11 @@ export class Stream {
}
this.initWebRtcStats();
logger.info("'Publisher' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "published") + " to session");
resolve();
finalResolve();
})
.catch(error => {
reject(error);
finalReject(error);
});
}
});
@ -876,8 +928,8 @@ export class Stream {
video: this.hasVideo,
},
simulcast: false,
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
iceServers: this.getIceServersConf(),
mediaStream: this.mediaStream,
};
@ -896,10 +948,10 @@ export class Stream {
.then(() => {
successOfferCallback(sdpOffer.sdp);
}).catch(error => {
reject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
finalReject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
});
}).catch(error => {
reject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
finalReject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
});
});
}
@ -909,20 +961,87 @@ export class Stream {
*/
initWebRtcPeerReceive(reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => {
if (reconnect) {
if (this.reconnectionEventEmitter == undefined) {
// There is no ongoing reconnection
this.reconnectionEventEmitter = new EventEmitter();
} else {
// Ongoing reconnection
console.warn(`Trying to reconnect stream ${this.streamId} (Subscriber) but an ongoing reconnection process is active. Waiting for response...`);
this.reconnectionEventEmitter.once('success', () => {
resolve();
});
this.reconnectionEventEmitter.once('error', error => {
reject(error);
});
return;
}
}
const finalResolve = () => {
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
this.remotePeerSuccessfullyEstablished(reconnect);
this.initWebRtcStats();
if (reconnect) {
this.reconnectionEventEmitter?.emitEvent('success');
delete this.reconnectionEventEmitter;
}
resolve();
}
const finalReject = error => {
if (reconnect) {
this.reconnectionEventEmitter?.emitEvent('error', [error]);
delete this.reconnectionEventEmitter;
}
reject(error);
}
if (this.session.openvidu.mediaServer === 'mediasoup') {
// Server initiates negotiation
this.initWebRtcPeerReceiveFromServer(reconnect)
.then(() => finalResolve())
.catch(error => finalReject(error));
} else {
// Client initiates negotiation
this.initWebRtcPeerReceiveFromClient(reconnect)
.then(() => finalResolve())
.catch(error => finalReject(error));
}
});
}
/**
* @hidden
*/
initWebRtcPeerReceiveFromClient(reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => {
this.completeWebRtcPeerReceive(reconnect).then(response => {
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
.then(() => resolve()).catch(error => reject(error));
}).catch(error => reject(error));
});
}
/**
* @hidden
*/
initWebRtcPeerReceiveFromServer(reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => {
// Server initiates negotiation
this.session.openvidu.sendRequest('prepareReceiveVideoFrom', { sender: this.streamId, reconnect }, (error, response) => {
if (error) {
reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error)));
} else {
this.completeWebRtcPeerReceive(response.sdpOffer, reconnect)
.then(() => {
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
this.remotePeerSuccessfullyEstablished(reconnect);
this.initWebRtcStats();
resolve();
})
.catch(error => {
reject(error);
});
this.completeWebRtcPeerReceive(reconnect, response.sdpOffer)
.then(() => resolve()).catch(error => reject(error));
}
});
});
@ -931,25 +1050,29 @@ export class Stream {
/**
* @hidden
*/
completeWebRtcPeerReceive(sdpOffer: string, reconnect: boolean): Promise<void> {
completeWebRtcPeerReceive(reconnect: boolean, sdpOfferByServer?: string): Promise<any> {
return new Promise((resolve, reject) => {
logger.debug("'Session.subscribe(Stream)' called");
const successAnswerCallback = (sdpAnswer) => {
logger.debug('Sending SDP answer to subscribe to '
+ this.streamId, sdpAnswer);
const sendSdpToServer = (sdpString: string) => {
logger.debug(`Sending local SDP ${(!!sdpOfferByServer ? 'answer' : 'offer')} to subscribe to ${this.streamId}`, sdpString);
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
const params = {};
params[reconnect ? 'stream' : 'sender'] = this.streamId;
params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpAnswer;
if (!!sdpOfferByServer) {
params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpString;
} else {
params['sdpOffer'] = sdpString;
}
this.session.openvidu.sendRequest(method, params, (error, response) => {
if (error) {
reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
} else {
resolve();
resolve(response);
}
});
};
@ -960,29 +1083,47 @@ export class Stream {
video: this.hasVideo,
},
simulcast: false,
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
iceServers: this.getIceServersConf(),
};
if (reconnect) {
this.disposeWebRtcPeer();
}
this.webRtcPeer = new WebRtcPeerRecvonly(config);
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
this.webRtcPeer.processRemoteOffer(sdpOffer)
.then(() => {
if (!!sdpOfferByServer) {
this.webRtcPeer.processRemoteOffer(sdpOfferByServer).then(() => {
this.webRtcPeer.createAnswer().then(sdpAnswer => {
this.webRtcPeer.processLocalAnswer(sdpAnswer)
.then(() => {
successAnswerCallback(sdpAnswer.sdp);
}).catch(error => {
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
});
this.webRtcPeer.processLocalAnswer(sdpAnswer).then(() => {
sendSdpToServer(sdpAnswer.sdp!);
}).catch(error => {
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
});
}).catch(error => {
reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error)));
});
})
.catch(error => {
}).catch(error => {
reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error)));
});
} else {
this.webRtcPeer.createOffer().then(sdpOffer => {
this.webRtcPeer.processLocalOffer(sdpOffer).then(() => {
sendSdpToServer(sdpOffer.sdp!);
}).catch(error => {
reject(new Error('(subscribe) SDP process local offer error: ' + JSON.stringify(error)));
});
}).catch(error => {
reject(new Error('(subscribe) SDP create offer error: ' + JSON.stringify(error)));
});
}
});
}
@ -1048,6 +1189,137 @@ export class Stream {
}
}
private onIceConnectionStateExceptionHandler(exceptionName: ExceptionEventName, message: string, data?: any): void {
switch (exceptionName) {
case ExceptionEventName.ICE_CONNECTION_FAILED:
this.onIceConnectionFailed();
break;
case ExceptionEventName.ICE_CONNECTION_DISCONNECTED:
this.onIceConnectionDisconnected();
break;
}
this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]);
}
private onIceConnectionFailed() {
// Immediately reconnect, as this is a terminal error
logger.log(`[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')})`);
this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED);
}
private onIceConnectionDisconnected() {
// Wait to see if the ICE connection is able to reconnect
logger.log(`[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) if not possible`);
const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
this.awaitWebRtcPeerConnectionState(timeout).then(state => {
switch (state) {
case 'failed':
// Do nothing, as an ICE_CONNECTION_FAILED event will have already raised
logger.warn(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) is now failed after ICE_CONNECTION_DISCONNECTED`);
break;
case 'connected':
case 'completed':
logger.log(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}`);
break;
case 'closed':
case 'checking':
case 'new':
case 'disconnected':
// Rest of states
logger.warn(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}`);
this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED);
break;
}
});
}
private async reconnectStreamAndLogResultingIceConnectionState(event: string) {
try {
const finalIceStateAfterReconnection = await this.reconnectStreamAndReturnIceConnectionState(event);
switch (finalIceStateAfterReconnection) {
case 'connected':
case 'completed':
logger.log(`[${event}] Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`);
break;
default:
logger.error(`[${event}] Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`);
break;
}
} catch (error) {
logger.error(`[${event}] Error reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) after ${event}: ${error}`);
}
}
private async reconnectStreamAndReturnIceConnectionState(event: string): Promise<RTCIceConnectionState> {
logger.log(`[${event}] Reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) after event ${event}`);
try {
await this.reconnectStream(event);
const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
return this.awaitWebRtcPeerConnectionState(timeout);
} catch (error) {
logger.warn(`[${event}] Error reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}). Reason: ${error}`);
return this.awaitWebRtcPeerConnectionState(1);
}
}
private async reconnectStream(event: string) {
const isWsConnected = await this.isWebsocketConnected(event, 3000);
if (isWsConnected) {
// There is connection to openvidu-server. The RTCPeerConnection is the only one broken
logger.log(`[${event}] Trying to reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) and the websocket is opened`);
if (this.isLocal()) {
return this.initWebRtcPeerSend(true);
} else {
return this.initWebRtcPeerReceive(true);
}
} else {
// 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
const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) but the websocket wasn't opened`;
logger.error(errorMsg);
throw Error(errorMsg);
}
}
private isWebsocketConnected(event: string, msResponseTimeout: number): Promise<boolean> {
return new Promise((resolve, reject) => {
const wsReadyState = this.session.openvidu.getWsReadyState();
if (wsReadyState === 1) {
const responseTimeout = setTimeout(() => {
console.warn(`[${event}] Websocket timeout of ${msResponseTimeout}ms`);
resolve(false);
}, msResponseTimeout);
this.session.openvidu.sendRequest('echo', {}, (error, response) => {
clearTimeout(responseTimeout);
if (!!error) {
console.warn(`[${event}] Websocket 'echo' returned error: ${error}`);
resolve(false);
} else {
resolve(true);
}
});
} else {
console.warn(`[${event}] Websocket readyState is ${wsReadyState}`);
resolve(false);
}
});
}
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 initWebRtcStats(): void {
this.webRtcStats = new WebRtcStats(this);
this.webRtcStats.initWebRtcStats();

View File

@ -16,6 +16,7 @@
*/
import { Stream } from './Stream';
import { Subscriber } from './Subscriber';
import { EventDispatcher } from './EventDispatcher';
import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
import { Event } from '../OpenViduInternal/Events/Event';
@ -24,6 +25,7 @@ import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
/**
* @hidden
@ -90,19 +92,23 @@ export class StreamManager extends EventDispatcher {
/**
* @hidden
*/
firstVideoElement?: StreamManagerVideo;
protected firstVideoElement?: StreamManagerVideo;
/**
* @hidden
*/
lazyLaunchVideoElementCreatedEvent = false;
/**
* @hidden
*/
element: HTMLElement;
protected element: HTMLElement;
/**
* @hidden
*/
protected canPlayListener: EventListener;
/**
* @hidden
*/
private streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
/**
* @hidden
*/
private lazyLaunchVideoElementCreatedEvent = false;
/**
* @hidden
@ -138,7 +144,11 @@ export class StreamManager extends EventDispatcher {
}
this.canPlayListener = () => {
if (this.stream.isLocal()) {
this.deactivateStreamPlayingEventExceptionTimeout();
if (this.remote) {
logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
} else {
if (!this.stream.displayMyRemote()) {
logger.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
@ -146,9 +156,6 @@ export class StreamManager extends EventDispatcher {
logger.info("Your own remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
}
} else {
logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
}
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
};
@ -274,7 +281,7 @@ export class StreamManager extends EventDispatcher {
this.initializeVideoProperties(video);
if (this.stream.isLocal() && this.stream.displayMyRemote()) {
if (!this.remote && this.stream.displayMyRemote()) {
if (video.srcObject !== this.stream.getMediaStream()) {
video.srcObject = this.stream.getMediaStream();
}
@ -305,7 +312,7 @@ export class StreamManager extends EventDispatcher {
id: video.id,
canplayListenerAdded: false
});
logger.info('New video element associated to ', this);
return returnNumber;
@ -386,7 +393,7 @@ export class StreamManager extends EventDispatcher {
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
*/
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions): void {
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void {
const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ?
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100);
@ -408,7 +415,7 @@ export class StreamManager extends EventDispatcher {
* @hidden
*/
initializeVideoProperties(video: HTMLVideoElement): void {
if (!(this.stream.isLocal() && this.stream.displayMyRemote())) {
if (!(!this.remote && this.stream.displayMyRemote())) {
// Avoid setting the MediaStream into the srcObject if remote subscription before publishing
if (video.srcObject !== this.stream.getMediaStream()) {
// If srcObject already set don't do it again
@ -492,6 +499,7 @@ export class StreamManager extends EventDispatcher {
*/
addPlayEventToFirstVideo() {
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) {
this.activateStreamPlayingEventExceptionTimeout();
this.videos[0].video.addEventListener('canplay', this.canPlayListener);
this.videos[0].canplayListenerAdded = true;
}
@ -533,6 +541,7 @@ export class StreamManager extends EventDispatcher {
*/
removeSrcObject(streamManagerVideo: StreamManagerVideo) {
streamManagerVideo.video.srcObject = null;
this.deactivateStreamPlayingEventExceptionTimeout();
}
/* Private methods */
@ -557,4 +566,28 @@ export class StreamManager extends EventDispatcher {
video.style.webkitTransform = 'unset';
}
private activateStreamPlayingEventExceptionTimeout() {
if (!this.remote) {
// ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers
return;
}
if (this.streamPlayingEventExceptionTimeout != null) {
// The timeout is already activated
return;
}
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
this.streamPlayingEventExceptionTimeout = setTimeout(() => {
const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms';
logger.warn(msg);
this.stream.session.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)]);
delete this.streamPlayingEventExceptionTimeout;
}, msTimeout);
}
private deactivateStreamPlayingEventExceptionTimeout() {
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
delete this.streamPlayingEventExceptionTimeout;
}
}

View File

@ -39,6 +39,7 @@ export class ConnectionEvent extends Event {
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the remote user network connection has dropped
* - "nodeCrashed": a node has crashed in the server side
*
* For `connectionCreated` event an empty string
*/

View File

@ -60,14 +60,14 @@ export abstract class Event {
/**
* Prevents the default behavior of the event. The following events have a default behavior:
*
* - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
* - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to each Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or
* by calling [[Subscriber.createVideoElement]]). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event.
*
* - `streamDestroyed`:
* - If dispatched by a [[Publisher]] (*you* have unpublished): automatically stops all media tracks and deletes any HTML video element associated to it (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement`
* in method [[OpenVidu.initPublisher]] or by calling [[Publisher.createVideoElement]]). For every video removed, the Publisher object will also dispatch a `videoElementDestroyed` event.
* - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
* - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to that Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or
* by calling [[Subscriber.createVideoElement]]). For every video removed, the Subscriber object will also dispatch a `videoElementDestroyed` event.
*/

View File

@ -17,6 +17,7 @@
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
import { Subscriber } from '../../OpenVidu/Subscriber';
import { Event } from './Event';
@ -36,7 +37,9 @@ export enum ExceptionEventName {
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `failed` status.
*
* This is a terminal error that won't have any kind of possible recovery.
* This is a terminal error that won't have any kind of possible recovery. If the client is still connected to OpenVidu Server,
* then an automatic reconnection process of the media stream is immediately performed. If the ICE connection has broken due to
* a total network drop, then no automatic reconnection process will be possible.
*
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
*/
@ -46,18 +49,45 @@ export enum ExceptionEventName {
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `disconnected` status.
*
* This is not a terminal error, and it is possible for the ICE connection to be reconnected.
* This is not a terminal error, and it is possible for the ICE connection to be reconnected. If the client is still connected to
* OpenVidu Server and after certain timeout the ICE connection has not reached a success or terminal status, then an automatic
* reconnection process of the media stream is performed. If the ICE connection has broken due to a total network drop, then no
* automatic reconnection process will be possible.
*
* You can customize the timeout for the reconnection attempt with property [[OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout]],
* which by default is 4000 milliseconds.
*
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
*/
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED'
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED',
/**
* A [[Subscriber]] object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to [[StreamManagerEvent]]
* category. It wraps Web API native event [canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event).
*
* OpenVidu Browser can take care of the video players (see [here](/en/latest/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)),
* or you can take care of video players on your own (see [here](/en/latest/cheatsheet/manage-videos/#you-take-care-of-the-video-players)).
* Either way, whenever a [[Subscriber]] object is commanded to attach its [[Stream]] to a video element, it is supposed to fire `streamPlaying`
* event shortly after. If it does not, then we can safely assume that something wrong has happened while playing the remote video and the
* application may be notified through this specific ExceptionEvent.
*
* The timeout can be configured with property [[OpenViduAdvancedConfiguration.noStreamPlayingEventExceptionTimeout]]. By default it is 4000 milliseconds.
*
* This is just an informative exception. It only means that a remote Stream that is supposed to be playing by a video player has not done so
* in a reasonable time. But the lack of the event can be caused by multiple reasons. If a Subscriber is not playing its Stream, the origin
* of the problem could be located at the Publisher side. Or may be caused by a transient network problem. But it also could be a problem with
* autoplay permissions. Bottom line, the cause can be very varied, and depending on the application the lack of the event could even be expected.
*
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Subscriber]] object.
*/
NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT'
}
/**
* Defines event `exception` dispatched by [[Session]] object.
*
* This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins.
* This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins. To see the different
* types of exceptions go to [[ExceptionEventName]].
*/
export class ExceptionEvent extends Event {
@ -70,8 +100,9 @@ export class ExceptionEvent extends Event {
* Object affected by the exception. Depending on the [[ExceptionEvent.name]] property:
* - [[Session]]: `ICE_CANDIDATE_ERROR`
* - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
* - [[Subscriber]]: `NO_STREAM_PLAYING_EVENT`
*/
origin: Session | Stream;
origin: Session | Stream | Subscriber;
/**
* Informative description of the exception
@ -86,7 +117,7 @@ export class ExceptionEvent extends Event {
/**
* @hidden
*/
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream, message: string, data?: any) {
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream | Subscriber, message: string, data?: any) {
super(false, session, 'exception');
this.name = name;
this.origin = origin;

View File

@ -46,7 +46,7 @@ export class RecordingEvent extends Event {
* - "recordingStoppedByServer": the recording has been gracefully stopped by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "automaticStop": see [Automatic stop of recordings](/en/stable/advanced-features/recording/#automatic-stop-of-recordings)
* - "mediaServerDisconnect": OpenVidu Media Node has crashed or lost its connection. A new Media Node instance is active and the recording has been stopped (no media streams are available in the new Media Node)
* - "nodeCrashed": a node has crashed in the server side
*
* For 'recordingStarted' empty string
*/

View File

@ -39,6 +39,8 @@ export class SessionDisconnectedEvent extends Event {
* Session object will always have previously dispatched a `reconnecting` event. If the reconnection process succeeds,
* Session object will dispatch a `reconnected` event. If it fails, Session object will dispatch a SessionDisconnectedEvent
* with reason "networkDisconnect"
* - "nodeCrashed": a node has crashed in the server side. You can use this reason to ask your application's backend to reconnect
* to a new session to replace the crashed one
*/
reason: string;

View File

@ -48,7 +48,7 @@ export class StreamEvent extends Event {
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the user's network connection has dropped
* - "mediaServerDisconnect": OpenVidu Media Node has crashed or lost its connection. A new Media Node instance is active and no media streams are available in the Media Node
* - "nodeCrashed": a node has crashed in the server side
*
* For 'streamCreated' empty string
*/

View File

@ -21,7 +21,10 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Defines the following events:
* - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media
* and has begun to play). This event will be dispatched when these 3 conditions are met 1) The StreamManager has no video associated in the DOM 2) It is associated to one video 3) That video starts playing
* and has begun to play). This event will be dispatched when these 3 conditions are met:
* 1. The StreamManager has no video associated in the DOM
* 2. It is associated to one video
* 3. That video starts playing. Internally the expected Web API event is [HTMLMediaElement.canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event)
* - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track
* changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of
* [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 100ms)

View File

@ -31,4 +31,5 @@ export interface LocalConnectionOptions {
turnUsername: string;
turnCredential: string;
version: string;
mediaServer: string;
}

View File

@ -39,7 +39,10 @@ export interface OpenViduAdvancedConfiguration {
*
* This sets the global default configuration that will affect all streams, but you can later customize these values for each specific stream by calling [[StreamManager.updatePublisherSpeakingEventsOptions]]
*/
publisherSpeakingEventsOptions?: any;
publisherSpeakingEventsOptions?: {
interval?: number;
threshold?: number;
};
/**
* Determines the automatic reconnection process policy. Whenever the client's network drops, OpenVidu Browser starts a reconnection process with OpenVidu Server. After network is recovered, OpenVidu Browser automatically
@ -52,4 +55,20 @@ export interface OpenViduAdvancedConfiguration {
*/
forceMediaReconnectionAfterNetworkDrop?: boolean;
}
/**
* The milliseconds that must elapse after triggering [[ExceptionEvent]] of name [`ICE_CONNECTION_DISCONNECTED`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#ice_connection_disconnected) to perform an automatic reconnection process of the affected media stream.
* This automatic reconnection process can only take place if the client still has network connection to OpenVidu Server. If the ICE connection has broken because of a total network drop,
* then no reconnection process will be possible at all.
*
* Default to `4000`.
*/
iceConnectionDisconnectedExceptionTimeout?: number;
/**
* The milliseconds that must elapse for the [[ExceptionEvent]] of name [`NO_STREAM_PLAYING_EVENT`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#no_stream_playing_event) to be fired.
*
* Default to `4000`.
*/
noStreamPlayingEventExceptionTimeout?: number;
}

View File

@ -270,6 +270,10 @@ function JsonRpcClient(configuration) {
pingNextNum = 0;
usePing();
}
this.getReadyState = function () {
return ws.getReadyState();
}
}

View File

@ -66,8 +66,12 @@ function WebSocketWithReconnection(config) {
if (closing) {
Logger.debug("Connection closed by user");
} else {
Logger.debug("Connection closed unexpectecly. Reconnecting...");
reconnect(MAX_RETRIES, 1);
if (config.ismasternodecrashed()) {
Logger.error("Master Node has crashed. Stopping reconnection process");
} else {
Logger.debug("Connection closed unexpectedly. Reconnecting...");
reconnect(MAX_RETRIES, 1);
}
}
} else {
Logger.debug("Close callback from previous websocket. Ignoring it");
@ -147,6 +151,10 @@ function WebSocketWithReconnection(config) {
};
registerMessageHandler();
};
this.getReadyState = () => {
return ws.readyState;
}
}
module.exports = WebSocketWithReconnection;

View File

@ -37,18 +37,19 @@ export interface WebRtcPeerConfiguration {
video: boolean
};
simulcast: boolean;
onicecandidate: (event: RTCIceCandidate) => void;
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
iceServers: RTCIceServer[] | undefined;
onIceCandidate: (event: RTCIceCandidate) => void;
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
iceServers?: RTCIceServer[];
mediaStream?: MediaStream | null;
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
id?: string;
}
export class WebRtcPeer {
public pc: RTCPeerConnection;
public remoteCandidatesQueue: RTCIceCandidate[] = [];
public localCandidatesQueue: RTCIceCandidate[] = [];
pc: RTCPeerConnection;
remoteCandidatesQueue: RTCIceCandidate[] = [];
localCandidatesQueue: RTCIceCandidate[] = [];
// Same as WebRtcPeerConfiguration but without optional fields.
protected configuration: Required<WebRtcPeerConfiguration>;
@ -61,10 +62,15 @@ export class WebRtcPeer {
this.configuration = {
...configuration,
iceServers: (!!configuration.iceServers && configuration.iceServers.length > 0) ? configuration.iceServers : freeice(),
mediaStream: !!configuration.mediaStream
? configuration.mediaStream
: null,
iceServers:
!!configuration.iceServers &&
configuration.iceServers.length > 0
? configuration.iceServers
: freeice(),
mediaStream:
configuration.mediaStream !== undefined
? configuration.mediaStream
: null,
mode: !!configuration.mode ? configuration.mode : "sendrecv",
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
};
@ -74,7 +80,7 @@ export class WebRtcPeer {
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
if (event.candidate != null) {
const candidate: RTCIceCandidate = event.candidate;
this.configuration.onicecandidate(candidate);
this.configuration.onIceCandidate(candidate);
if (candidate.candidate !== '') {
this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: candidate.candidate });
}
@ -91,6 +97,10 @@ export class WebRtcPeer {
});
}
get id(): string {
return this.configuration.id;
}
/**
* This method frees the resources used by WebRtcPeer
*/
@ -310,12 +320,12 @@ export class WebRtcPeer {
// Possible network disconnection
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
logger.warn(msg1);
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
break;
case 'failed':
const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"';
logger.error(msg2);
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
break;
case 'closed':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');

View File

@ -139,6 +139,8 @@ public class ProtocolElements {
// ENDTODO
public static final String VIDEODATA_METHOD = "videoData";
public static final String ECHO_METHOD = "echo";
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
@ -150,6 +152,7 @@ public class ProtocolElements {
public static final String PARTICIPANTJOINED_VALUE_PARAM = "value";
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
public static final String PARTICIPANTJOINED_MEDIASERVER_PARAM = "mediaServer";
public static final String PARTICIPANTJOINED_RECORD_PARAM = "record";
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";

View File

@ -10,7 +10,7 @@
</parent>
<artifactId>openvidu-java-client</artifactId>
<version>2.17.0</version>
<version>2.18.0</version>
<packaging>jar</packaging>
<name>OpenVidu Java Client</name>

File diff suppressed because it is too large Load Diff

View File

@ -33,5 +33,5 @@
"docs": "./generate-docs.sh"
},
"typings": "lib/index.d.ts",
"version": "2.17.0"
"version": "2.18.0"
}

View File

@ -206,10 +206,10 @@ export class OpenVidu {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(error.message);
}
});
});
@ -259,10 +259,10 @@ export class OpenVidu {
} else if (error.request) {
// The request was made but no response was received `error.request` is an instance of XMLHttpRequest
// in the browser and an instance of http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
});
@ -304,10 +304,10 @@ export class OpenVidu {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
});
@ -352,10 +352,10 @@ export class OpenVidu {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
});
@ -398,10 +398,10 @@ export class OpenVidu {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
});
@ -479,11 +479,9 @@ export class OpenVidu {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(error);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
@ -649,10 +647,10 @@ export class OpenVidu {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
});
@ -668,7 +666,6 @@ export class OpenVidu {
try {
url = new URL(this.hostname);
} catch (error) {
console.error('URL format incorrect', error);
throw new Error('URL format incorrect: ' + error);
}
this.host = url.protocol + '//' + url.host;

View File

@ -511,11 +511,9 @@ export class Session {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
});
@ -642,11 +640,9 @@ export class Session {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.error(error.request);
reject(new Error(error.request));
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error', error.message);
reject(new Error(error.message));
}
}

View File

@ -128,10 +128,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
# when a codec can not be forced, transcoding will be allowed
# Values: VP8, H264, NONE
# Default value is VP8
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
# Values: true | false
# Default value is false
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false

View File

@ -9,11 +9,11 @@ services:
#
# Default Application
#
# Openvidu-Call Version: 2.17.0
# Openvidu-Call Version: 2.18.0
#
# --------------------------------------------------------------
app:
image: openvidu/openvidu-call:2.18.0-beta8
image: openvidu/openvidu-call:2.18.0
restart: on-failure
network_mode: host
environment:

View File

@ -11,7 +11,7 @@
#
# This file will be overridden when update OpenVidu Platform
#
# Openvidu Version: 2.17.0
# Openvidu Version: 2.18.0
#
# Installation Mode: On Premises
#
@ -22,7 +22,7 @@ version: '3.1'
services:
openvidu-server:
image: openvidu/openvidu-server:2.18.0-dev2
image: openvidu/openvidu-server:2.18.0
restart: on-failure
network_mode: host
entrypoint: ['/usr/local/bin/entrypoint.sh']
@ -65,7 +65,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
redis:
image: openvidu/openvidu-redis:2.0.0
image: openvidu/openvidu-redis:3.0.0
restart: always
network_mode: host
environment:
@ -75,7 +75,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
coturn:
image: openvidu/openvidu-coturn:4.0.0-dev2
image: openvidu/openvidu-coturn:4.0.0
restart: on-failure
network_mode: host
environment:
@ -96,7 +96,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
nginx:
image: openvidu/openvidu-proxy:6.0.0-dev1
image: openvidu/openvidu-proxy:7.0.0-dev1
restart: on-failure
network_mode: host
volumes:

View File

@ -243,10 +243,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
# when a codec can not be forced, transcoding will be allowed
# Values: VP8, H264, NONE
# Default value is VP8
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
# Values: true | false
# Default value is false
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false

View File

@ -9,11 +9,11 @@ services:
#
# Default Application
#
# Openvidu-Call Version: 2.17.0
# Openvidu-Call Version: 2.18.0
#
# --------------------------------------------------------------
app:
image: openvidu/openvidu-call:2.18.0-beta8
image: openvidu/openvidu-call:2.18.0
restart: on-failure
network_mode: host
environment:

View File

@ -11,7 +11,7 @@
#
# This file will be overridden when update OpenVidu Platform
#
# Openvidu Version: 2.17.0
# Openvidu Version: 2.18.0
#
# Installation Mode: On Premises
#
@ -22,7 +22,7 @@ version: '3.1'
services:
openvidu-server:
image: openvidu/openvidu-server-pro:2.18.0-beta17
image: openvidu/openvidu-server-pro:2.18.0
restart: on-failure
network_mode: host
entrypoint: ['/usr/local/bin/entrypoint.sh']
@ -76,7 +76,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
redis:
image: openvidu/openvidu-redis:2.0.0
image: openvidu/openvidu-redis:3.0.0
restart: always
network_mode: host
environment:
@ -86,7 +86,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
coturn:
image: openvidu/openvidu-coturn:4.0.0-dev2
image: openvidu/openvidu-coturn:4.0.0
restart: on-failure
network_mode: host
environment:

View File

@ -6,7 +6,7 @@
#
# This docker-compose file coordinates all services of OpenVidu CE Platform.
#
# Openvidu Version: 2.17.0
# Openvidu Version: 2.18.0
#
# Installation Mode: On Premises
#
@ -16,7 +16,7 @@ version: '3.1'
services:
media-node-controller:
image: openvidu/media-node-controller:4.0.0-dev1
image: openvidu/media-node-controller:4.0.0
restart: always
ulimits:
core: -1

View File

@ -243,10 +243,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
# when a codec can not be forced, transcoding will be allowed
# Values: VP8, H264, NONE
# Default value is VP8
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
# Values: true | false
# Default value is false
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false

View File

@ -7,12 +7,12 @@ DEBUG=${DEBUG:-false}
OUTPUT=$(mktemp -t openvidu-autodiscover-XXX --suffix .json)
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 describe-instances \
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 describe-instances \
--output text \
--filters "Name=instance-state-name,Values=running" \
"Name=tag:ov-cluster-member,Values=kms" \
"Name=tag:ov-stack-name,Values=${AWS_STACK_NAME}" \
"Name=tag:ov-stack-region,Values=${AWS_DEFAULT_REGION}" \
--query 'Reservations[*].Instances[*].{id:InstanceId,ip:PrivateIpAddress}' > ${OUTPUT}
--query 'Reservations[*].Instances[*].{id:InstanceId,ip:PrivateIpAddress}' > "${OUTPUT}"
cat ${OUTPUT} | jq --raw-input --slurp 'split("\n") | map(split("\t")) | .[0:-1] | map( { "id": .[0], "ip": .[1] } )'
cat "${OUTPUT}" | jq --raw-input --slurp 'split("\n") | map(split("\t")) | .[0:-1] | map( { "id": .[0], "ip": .[1] } )'

View File

@ -8,4 +8,4 @@ DEBUG=${DEBUG:-false}
ID=$1
[ -z "${ID}" ] && { echo "Must provide instance ID"; exit 1; }
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 terminate-instances --instance-ids ${ID} --output json
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 terminate-instances --instance-ids "${ID}" --output json

View File

@ -23,7 +23,7 @@ exit_on_error () {
"UnauthorizedOperation")
MSG_COD=$(cat ${ERROUTPUT} | awk -F: '{ print $3 }')
MSG_DEC=$(docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} sts decode-authorization-message --encoded-message ${MSG_COD})
MSG_DEC=$(docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" sts decode-authorization-message --encoded-message "${MSG_COD}")
echo -e "Unauthorized " $(cat ${MSG_DEC}) >&2
exit 1
@ -43,21 +43,21 @@ if [[ -n "${CUSTOM_VOLUME_SIZE}" ]]; then
AWS_VOLUME_SIZE="${CUSTOM_VOLUME_SIZE}"
fi
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 run-instances \
--image-id ${AWS_IMAGE_ID} --count 1 \
--instance-type ${AWS_INSTANCE_TYPE} \
--key-name ${AWS_KEY_NAME} \
--subnet-id ${AWS_SUBNET_ID} \
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 run-instances \
--image-id "${AWS_IMAGE_ID}" --count 1 \
--instance-type "${AWS_INSTANCE_TYPE}" \
--key-name "${AWS_KEY_NAME}" \
--subnet-id "${AWS_SUBNET_ID}" \
--tag-specifications "ResourceType=instance,Tags=[{Key='Name',Value='Kurento Media Server'},{Key='ov-cluster-member',Value='kms'},{Key='ov-stack-name',Value='${AWS_STACK_NAME}'},{Key='ov-stack-region',Value='${AWS_DEFAULT_REGION}'}]" \
--iam-instance-profile Name="OpenViduInstanceProfile-${AWS_STACK_NAME}-${AWS_DEFAULT_REGION}" \
--block-device-mappings "DeviceName=/dev/sda1,Ebs={DeleteOnTermination=True,VolumeType='gp2',VolumeSize='${AWS_VOLUME_SIZE}'}" \
--security-group-ids ${AWS_SECURITY_GROUP} > ${OUTPUT} 2> ${ERROUTPUT}
--security-group-ids "${AWS_SECURITY_GROUP}" > "${OUTPUT}" 2> "${ERROUTPUT}"
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 wait instance-running --instance-ids $(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 wait instance-running --instance-ids $(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
# Generating the output
KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
KMS_IP=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
KMS_ID=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .InstanceId')
jq -n \
--arg id "${KMS_ID}" \

View File

@ -9,11 +9,11 @@ services:
#
# Default Application
#
# Openvidu-Call Version: 2.17.0
# Openvidu-Call Version: 2.18.0
#
# --------------------------------------------------------------
app:
image: openvidu/openvidu-call:2.18.0-beta8
image: openvidu/openvidu-call:2.18.0
restart: on-failure
network_mode: host
environment:

View File

@ -11,7 +11,7 @@
#
# This file will be overridden when update OpenVidu Platform
#
# Openvidu Version: 2.17.0
# Openvidu Version: 2.18.0
#
# Installation Mode: On Premises
#
@ -22,7 +22,7 @@ version: '3.1'
services:
openvidu-server:
image: openvidu/openvidu-server-pro:2.18.0-beta17
image: openvidu/openvidu-server-pro:2.18.0
restart: on-failure
network_mode: host
entrypoint: ['/usr/local/bin/entrypoint.sh']
@ -52,7 +52,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
redis:
image: openvidu/openvidu-redis:2.0.0
image: openvidu/openvidu-redis:3.0.0
restart: always
network_mode: host
environment:
@ -62,7 +62,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
coturn:
image: openvidu/openvidu-coturn:4.0.0-dev2
image: openvidu/openvidu-coturn:4.0.0
restart: on-failure
network_mode: host
environment:
@ -84,7 +84,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
nginx:
image: openvidu/openvidu-proxy:6.0.0-dev1
image: openvidu/openvidu-proxy:7.0.0-dev1
restart: on-failure
network_mode: host
volumes:

View File

@ -1,3 +1,5 @@
{xframe_options}
{app_upstream}
upstream openviduserver {

View File

@ -0,0 +1 @@
add_header X-Frame-Options SAMEORIGIN;

View File

@ -1,4 +1,5 @@
add_header X-Frame-Options SAMEORIGIN;
{xframe_options}
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

View File

@ -47,6 +47,7 @@ CERTIFICATES_CONF="${CERTIFICATES_LIVE_FOLDER}/certificates.conf"
[ -z "${PUBLIC_IP}" ] && export PUBLIC_IP=auto-ipv4
[ -z "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all
[ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all
[ -z "${XFRAME_SAMEORIGIN}" ] && export XFRAME_SAMEORIGIN=false
# Show input enviroment variables
printf "\n ======================================="
@ -228,6 +229,12 @@ elif [[ "${WITH_APP}" == "false" ]]; then
sed -e '/{app_config}/{r default_nginx_conf/global/app_config_default.conf' -e 'd}' -i /etc/nginx/conf.d/*
fi
if [[ "${XFRAME_SAMEORIGIN}" == "true" ]]; then
sed -e '/{xframe_options}/{r default_nginx_conf/global/xframe_sameorigin.conf' -e 'd}' -i /etc/nginx/conf.d/*
elif [[ "${XFRAME_SAMEORIGIN}" == "false" ]]; then
sed -i '/{xframe_options}/d' /etc/nginx/conf.d/*
fi
if [[ "${SUPPORT_DEPRECATED_API}" == "true" ]]; then
sed -e '/{deprecated_api_ce}/{r default_nginx_conf/global/ce/deprecated_api_ce.conf' -e 'd}' -i /etc/nginx/conf.d/*
sed -e '/{deprecated_api_pro}/{r default_nginx_conf/global/pro/deprecated_api_pro.conf' -e 'd}' -i /etc/nginx/conf.d/*

View File

@ -4,7 +4,7 @@ MAINTAINER info@openvidu.io
ARG CHROME_VERSION
# Install Chrome
RUN apt-get update && apt-get -y upgrade && apt-get install -y wget sudo
RUN apt-get update && apt-get -y upgrade && apt-get install -y wget sudo fonts-noto
RUN wget http://dl.google.com/linux/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& apt install -y ./google-chrome-stable_${CHROME_VERSION}_amd64.deb \
&& rm google-chrome-stable_${CHROME_VERSION}_amd64.deb \

View File

@ -14,6 +14,7 @@ RUN apt-get update && apt-get -y upgrade && apt-get install -y \
pulseaudio \
xvfb \
jq \
fonts-noto \
&& rm -rf /var/lib/apt/lists/*
# Install chrome

View File

@ -12,7 +12,7 @@
<packaging>jar</packaging>
<name>OpenVidu Server</name>
<version>2.17.0</version>
<version>2.18.0</version>
<description>OpenVidu Server</description>
<url>https://openvidu.io</url>

View File

@ -7183,6 +7183,11 @@
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
"dev": true
},
"jsnlog": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/jsnlog/-/jsnlog-2.30.0.tgz",
"integrity": "sha512-o3ROQVkhek+dkc7/9TXlB4TNtxUpYsRLOBJHZYk3Vy0B5zRBmfv9tyr56PrjcgEXuy06ARgfLTANY0+ImhzzGA=="
},
"json-parse-better-errors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
@ -8728,14 +8733,15 @@
}
},
"openvidu-browser": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.17.0.tgz",
"integrity": "sha512-wholLLrs2R1Rf05+QPOH4eIO9LaKO9UvoinGNHU9WoP32L9cU6WeqL0sH3gHhUdiYPqGwTNRPjC3qk/ltjUbDA==",
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.18.0.tgz",
"integrity": "sha512-a9HPAG8p2vG9XGThPUbZWPI5sO1OFmrxKQdCZM3RLmasXQa2Lwj7X1Aicsznb7RtDBg/lD8NdNwAQjm4ZDt2Nw==",
"requires": {
"freeice": "2.2.2",
"hark": "1.2.3",
"jsnlog": "2.30.0",
"platform": "1.3.6",
"uuid": "8.3.1",
"uuid": "8.3.2",
"wolfy87-eventemitter": "5.2.9"
}
},
@ -13579,9 +13585,9 @@
"dev": true
},
"uuid": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"validate-npm-package-name": {
"version": "3.0.0",

View File

@ -1,55 +1,55 @@
{
"dependencies": {
"@angular/animations": "11.2.4",
"@angular/cdk": "11.2.3",
"@angular/common": "11.2.4",
"@angular/compiler": "11.2.4",
"@angular/core": "11.2.4",
"@angular/flex-layout": "11.0.0-beta.33",
"@angular/forms": "11.2.4",
"@angular/material": "11.2.3",
"@angular/platform-browser": "11.2.4",
"@angular/platform-browser-dynamic": "11.2.4",
"@angular/router": "11.2.4",
"core-js": "3.9.1",
"jquery": "3.6.0",
"openvidu-browser": "2.17.0",
"rxjs": "6.6.6",
"tslib": "2.1.0",
"zone.js": "0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.1102.3",
"@angular/cli": "11.2.3",
"@angular/compiler-cli": "11.2.4",
"@angular/language-service": "11.2.4",
"@types/jasmine": "3.6.6",
"@types/node": "14.14.32",
"codelyzer": "6.0.1",
"jasmine-core": "3.6.0",
"jasmine-spec-reporter": "6.0.0",
"karma": "6.1.2",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.5.4",
"protractor": "7.0.0",
"ts-node": "9.1.1",
"tslint": "6.1.3",
"typescript": "4.1.5"
},
"license": "Apache-2.0",
"name": "frontend",
"private": true,
"scripts": {
"build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
"build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
"e2e": "ng e2e",
"lint": "ng lint",
"ng": "ng",
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
"start": "ng serve",
"test": "ng test"
},
"version": "0.0.0"
"dependencies": {
"@angular/animations": "11.2.4",
"@angular/cdk": "11.2.3",
"@angular/common": "11.2.4",
"@angular/compiler": "11.2.4",
"@angular/core": "11.2.4",
"@angular/flex-layout": "11.0.0-beta.33",
"@angular/forms": "11.2.4",
"@angular/material": "11.2.3",
"@angular/platform-browser": "11.2.4",
"@angular/platform-browser-dynamic": "11.2.4",
"@angular/router": "11.2.4",
"core-js": "3.9.1",
"jquery": "3.6.0",
"openvidu-browser": "2.18.0",
"rxjs": "6.6.6",
"tslib": "2.1.0",
"zone.js": "0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.1102.3",
"@angular/cli": "11.2.3",
"@angular/compiler-cli": "11.2.4",
"@angular/language-service": "11.2.4",
"@types/jasmine": "3.6.6",
"@types/node": "14.14.32",
"codelyzer": "6.0.1",
"jasmine-core": "3.6.0",
"jasmine-spec-reporter": "6.0.0",
"karma": "6.1.2",
"karma-chrome-launcher": "3.1.0",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "4.0.1",
"karma-jasmine-html-reporter": "1.5.4",
"protractor": "7.0.0",
"ts-node": "9.1.1",
"tslint": "6.1.3",
"typescript": "4.1.5"
},
"license": "Apache-2.0",
"name": "frontend",
"private": true,
"scripts": {
"build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
"build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
"e2e": "ng e2e",
"lint": "ng lint",
"ng": "ng",
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
"start": "ng serve",
"test": "ng test"
},
"version": "0.0.0"
}

View File

@ -61,6 +61,7 @@ import io.openvidu.java.client.VideoCodec;
import io.openvidu.server.OpenViduServer;
import io.openvidu.server.cdr.CDREventName;
import io.openvidu.server.config.Dotenv.DotenvFormatException;
import io.openvidu.server.core.MediaServer;
import io.openvidu.server.recording.RecordingNotification;
import io.openvidu.server.rest.RequestMappings;
@ -246,6 +247,10 @@ public class OpenviduConfig {
return false;
}
public MediaServer getMediaServer() {
return MediaServer.kurento;
}
public String getOpenViduRecordingPath() {
return this.openviduRecordingPath;
}

View File

@ -19,8 +19,114 @@ package io.openvidu.server.core;
public enum EndReason {
unsubscribe, unpublish, disconnect, forceUnpublishByUser, forceUnpublishByServer, forceDisconnectByUser,
forceDisconnectByServer, lastParticipantLeft, recordingStoppedByServer, sessionClosedByServer, networkDisconnect,
mediaServerDisconnect, mediaServerReconnect, nodeCrashed, openviduServerStopped, automaticStop
/**
* A user called the RPC operation to unsubscribe from a remote stream. Applies
* to webrtcConnectionDestroyed
*/
unsubscribe,
/**
* A user called the RPC operation to unpublish a local stream. Applies to
* webrtcConnectionDestroyed
*/
unpublish,
/**
* A user called the RPC operation to leave the session. Applies to
* webrtcConnectionDestroyed and participantLeft. Can trigger other events with
* lastParticipantLeft
*/
disconnect,
/**
* A user called the RPC operation to force the unpublishing of a remote stream.
* Applies to webrtcConnectionDestroyed
*/
forceUnpublishByUser,
/**
* The server application called the REST operation to force the unpublishing of
* a user's stream. Applies to webrtcConnectionDestroyed
*/
forceUnpublishByServer,
/**
* A user called the RPC operation to force the disconnection of a remote user.
* Applies to webrtcConnectionDestroyed and participantLeft. Can trigger other
* events with lastParticipantLeft
*/
forceDisconnectByUser,
/**
* The server application called the REST operation to force the disconnection
* of a user. Applies to webrtcConnectionDestroyed and participantLeft. Can
* trigger other events with lastParticipantLeft
*/
forceDisconnectByServer,
/**
* The last participant left the session, which caused the session to be closed.
* Applies to webrtcConnectionDestroyed, participantLeft, recordingStatusChanged
* and sessionDestroyed. Can be triggered from other events with other end
* reasons (disconnect, forceDisconnectByUser, forceDisconnectByServer,
* networkDisconnect)
*/
lastParticipantLeft,
/**
* The server application called the REST operation to stop a recording. Applies
* to recordingStatusChanged
*/
recordingStoppedByServer,
/**
* The server application called the REST operation to close a session. Applies
* to webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and
* sessionDestroyed
*/
sessionClosedByServer,
/**
* A user left the session because of a network disconnection. Applies to
* webrtcConnectionDestroyed and participantLeft. Can trigger other events with
* lastParticipantLeft
*/
networkDisconnect,
/**
* A media server disconnected. This is reserved for Media Nodes being
* gracefully removed from an OpenVidu Pro cluster. Applies to
* webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and
* sessionDestroyed
*/
mediaServerDisconnect,
/**
* A media server disconnected, but was able to reconnect again. Nevertheless
* all of the media endpoints were destroyed in the process. Applies to
* webrtcConnectionDestroyed and recordingStatusChanged
*/
mediaServerReconnect,
/**
* A node has crashed. For now this means a Media Node has crashed. Applies to
* webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and
* sessionDestroyed
*/
nodeCrashed,
/**
* OpenVidu Server has gracefully stopped. This is reserved for OpenVidu Pro
* restart operation. Applies to webrtcConnectionDestroyed, participantLeft,
* recordingStatusChanged and sessionDestroyed
*/
openviduServerStopped,
/**
* A recording has been stopped automatically
* (https://docs.openvidu.io/en/latest/advanced-features/recording/#automatic-stop-of-recordings).
* Applies to recordingStatusChanged
*/
automaticStop
}

View File

@ -0,0 +1,7 @@
package io.openvidu.server.core;
public enum MediaServer {
kurento, mediasoup
}

View File

@ -153,15 +153,18 @@ public class SessionEventsHandler {
ProtocolElements.PARTICIPANTJOINED_METHOD, notifParams);
}
}
result.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, participant.getParticipantPublicId());
result.addProperty(ProtocolElements.PARTICIPANTJOINED_FINALUSERID_PARAM, participant.getFinalUserId());
result.addProperty(ProtocolElements.PARTICIPANTJOINED_CREATEDAT_PARAM, participant.getActiveAt());
result.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata());
result.add(ProtocolElements.PARTICIPANTJOINED_VALUE_PARAM, resultArray);
result.addProperty(ProtocolElements.PARTICIPANTJOINED_SESSION_PARAM, participant.getSessionId());
result.addProperty(ProtocolElements.PARTICIPANTJOINED_VERSION_PARAM,
openviduBuildConfig.getOpenViduServerVersion());
result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM,
this.openviduConfig.getMediaServer().name());
if (participant.getToken() != null) {
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
if (participant.getToken().getRole() != null) {
@ -638,6 +641,10 @@ public class SessionEventsHandler {
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject());
}
public void onEcho(String participantPrivateId, Integer transactionId) {
rpcNotificationService.sendResponse(participantPrivateId, transactionId, new JsonObject());
}
/**
* This handler must be called before cleaning any sessions or recordings hosted
* by the crashed Media Node

View File

@ -116,15 +116,8 @@ public abstract class SessionManager {
public abstract void prepareSubscription(Participant participant, String senderPublicId, boolean reconnect,
Integer id);
// TODO: REMOVE ON 2.18.0
public abstract void subscribe(Participant participant, String senderName, String sdpAnwser, Integer transactionId,
boolean is2180);
// END TODO
// TODO: UNCOMMENT ON 2.18.0
// public abstract void subscribe(Participant participant, String senderName,
// String sdpAnwser, Integer transactionId);
// END TODO
public abstract void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId,
boolean initByServer);
public abstract void unsubscribe(Participant participant, String senderName, Integer transactionId);
@ -180,13 +173,11 @@ public abstract class SessionManager {
public abstract Participant publishIpcam(Session session, MediaOptions mediaOptions,
ConnectionProperties connectionProperties) throws Exception;
public abstract void reconnectStream(Participant participant, String streamId, String sdpOffer,
public abstract void reconnectPublisher(Participant participant, String streamId, String sdpOffer,
Integer transactionId);
// TODO: REMOVE ON 2.18.0
public abstract void reconnectStream2170(Participant participant, String streamId, String sdpOffer,
Integer transactionId);
// END TODO
public abstract void reconnectSubscriber(Participant participant, String streamId, String sdpString,
Integer transactionId, boolean initByServer);
public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId)
throws OpenViduException;
@ -194,6 +185,10 @@ public abstract class SessionManager {
public abstract void onVideoData(Participant participant, Integer transactionId, Integer height, Integer width,
Boolean videoActive, Boolean audioActive);
public void onEcho(String participantPrivateId, Integer requestId) {
sessionEventsHandler.onEcho(participantPrivateId, requestId);
}
/**
* Returns a Session given its id
*
@ -308,8 +303,10 @@ public abstract class SessionManager {
}
public Session storeSessionNotActive(String sessionId, SessionProperties sessionProperties) {
Session sessionNotActive = new Session(sessionId, sessionProperties, openviduConfig, recordingManager);
return this.storeSessionNotActive(sessionNotActive);
Session sessionNotActive = this
.storeSessionNotActive(new Session(sessionId, sessionProperties, openviduConfig, recordingManager));
sessionEventsHandler.onSessionCreated(sessionNotActive);
return sessionNotActive;
}
public Session storeSessionNotActive(Session sessionNotActive) {

View File

@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.Lock;
import java.util.function.Function;
import org.apache.commons.lang3.RandomStringUtils;
@ -220,240 +220,100 @@ public class KurentoParticipant extends Participant {
}
KurentoParticipant kSender = (KurentoParticipant) sender;
if (kSender.streaming && kSender.getPublisher() != null) {
if (kSender.streaming && kSender.getPublisher() != null
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
try {
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
senderName);
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
final Lock closingReadLock = kSender.getPublisher().closingLock.readLock();
if (closingReadLock.tryLock()) {
try {
CountDownLatch subscriberLatch = new CountDownLatch(1);
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
SubscriberEndpoint subscriber = initializeSubscriberEndpoint(kSender);
try {
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Timeout reached when creating subscriber endpoint");
}
} catch (InterruptedException e) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Interrupted when creating subscriber endpoint: " + e.getMessage());
}
if (oldMediaEndpoint != null) {
log.warn(
"PARTICIPANT {}: Two threads are trying to create at "
+ "the same time a subscriber endpoint for user {}",
this.getParticipantPublicId(), senderName);
String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher());
log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(),
sdpOffer);
log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}",
this.getParticipantPublicId(), senderName, this.session.getSessionId());
return sdpOffer;
} catch (KurentoServerException e) {
log.error("Exception preparing subscriber endpoint for user {}: {}",
this.getParticipantPublicId(), e.getMessage());
this.subscribers.remove(senderName);
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
return null;
}
if (subscriber.getEndpoint() == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint");
}
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
subscriber.setEndpointName(subscriberEndpointName);
subscriber.getEndpoint().setName(subscriberEndpointName);
subscriber.setStreamId(kSender.getPublisherStreamId());
endpointConfig.addEndpointListeners(subscriber, "subscriber");
} catch (OpenViduException e) {
this.subscribers.remove(senderName);
throw e;
} finally {
closingReadLock.unlock();
}
log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(),
senderName);
try {
String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher());
log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(), sdpOffer);
log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}",
this.getParticipantPublicId(), senderName, this.session.getSessionId());
return sdpOffer;
} catch (KurentoServerException e) {
log.error("Exception preparing subscriber endpoint for user {}: {}", this.getParticipantPublicId(),
e.getMessage());
this.subscribers.remove(senderName);
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
return null;
}
} finally {
kSender.getPublisher().closingLock.readLock().unlock();
}
} else {
log.error(
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
senderName, sender.getSessionId(), this.participantPublicId);
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName
+ "is closed");
}
log.error(
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
senderName, sender.getSessionId(), this.participantPublicId);
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + "is closed");
}
public void receiveMediaFrom2180(Participant sender, String sdpAnswer, boolean silent) {
public String receiveMedia(Participant sender, String sdpString, boolean silent, boolean initByServer) {
final String senderName = sender.getParticipantPublicId();
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
senderName, this.session.getSessionId());
log.trace("PARTICIPANT {}: SdpAnswer for {} is {}", this.getParticipantPublicId(), senderName, sdpAnswer);
log.trace("PARTICIPANT {}: Sdp string for {} is {}", this.getParticipantPublicId(), senderName, sdpString);
if (senderName.equals(this.getParticipantPublicId())) {
log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", this.getParticipantPublicId());
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media");
}
KurentoParticipant kSender = (KurentoParticipant) sender;
if (kSender.streaming && kSender.getPublisher() != null) {
if (kSender.streaming && kSender.getPublisher() != null
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
try {
final SubscriberEndpoint subscriber = getSubscriber(senderName);
if (subscriber.getEndpoint() == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint");
}
final Lock closingReadLock = kSender.getPublisher().closingLock.readLock();
if (closingReadLock.tryLock()) {
try {
subscriber.subscribe(sdpAnswer, kSender.getPublisher());
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
senderName, this.session.getSessionId());
if (!silent
&& !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
sender.getParticipantPublicId(), subscriber.createdAt());
}
} catch (KurentoServerException e) {
// TODO Check object status when KurentoClient sets this info in the object
if (e.getCode() == 40101) {
log.warn(
"Publisher endpoint was already released when trying to connect a subscriber endpoint to it",
e);
} else {
log.error("Exception connecting subscriber endpoint to publisher endpoint", e);
}
this.subscribers.remove(senderName);
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
}
} finally {
kSender.getPublisher().closingLock.readLock().unlock();
}
} else {
log.error(
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
senderName, sender.getSessionId(), this.participantPublicId);
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName
+ "is closed");
}
}
// If initialized by server SubscriberEndpoint was created on
// prepareReceiveMediaFrom. If initialized by client must be created now
final SubscriberEndpoint subscriber = initByServer ? getSubscriber(senderName)
: initializeSubscriberEndpoint(kSender);
public String receiveMediaFrom2170(Participant sender, String sdpOffer, boolean silent) {
final String senderName = sender.getParticipantPublicId();
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
senderName, this.session.getSessionId());
log.trace("PARTICIPANT {}: SdpOffer for {} is {}", this.getParticipantPublicId(), senderName, sdpOffer);
if (senderName.equals(this.getParticipantPublicId())) {
log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", this.getParticipantPublicId());
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media");
}
KurentoParticipant kSender = (KurentoParticipant) sender;
if (kSender.streaming && kSender.getPublisher() != null
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
try {
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
senderName);
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
try {
CountDownLatch subscriberLatch = new CountDownLatch(1);
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
try {
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Timeout reached when creating subscriber endpoint");
}
} catch (InterruptedException e) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Interrupted when creating subscriber endpoint: " + e.getMessage());
}
if (oldMediaEndpoint != null) {
log.warn(
"PARTICIPANT {}: Two threads are trying to create at "
+ "the same time a subscriber endpoint for user {}",
this.getParticipantPublicId(), senderName);
return null;
}
if (subscriber.getEndpoint() == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint");
}
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
subscriber.setEndpointName(subscriberEndpointName);
subscriber.getEndpoint().setName(subscriberEndpointName);
subscriber.setStreamId(kSender.getPublisherStreamId());
endpointConfig.addEndpointListeners(subscriber, "subscriber");
} catch (OpenViduException e) {
this.subscribers.remove(senderName);
throw e;
try {
String sdpAnswer = subscriber.subscribe(sdpString, kSender.getPublisher());
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}",
this.getParticipantPublicId(), senderName, this.session.getSessionId());
if (!silent && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
.equals(this.getParticipantPublicId())) {
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
sender.getParticipantPublicId(), subscriber.createdAt());
}
return sdpAnswer;
} catch (KurentoServerException e) {
// TODO Check object status when KurentoClient sets this info in the object
if (e.getCode() == 40101) {
log.warn(
"Publisher endpoint was already released when trying to connect a subscriber endpoint to it",
e);
} else {
log.error("Exception connecting subscriber endpoint to publisher endpoint", e);
}
this.subscribers.remove(senderName);
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
return null;
}
} finally {
closingReadLock.unlock();
}
log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(),
senderName);
try {
String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher());
log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer);
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
senderName, this.session.getSessionId());
if (!silent
&& !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
sender.getParticipantPublicId(), subscriber.createdAt());
}
return sdpAnswer;
} catch (KurentoServerException e) {
// TODO Check object status when KurentoClient sets this info in the object
if (e.getCode() == 40101) {
log.warn(
"Publisher endpoint was already released when trying to connect a subscriber endpoint to it",
e);
} else {
log.error("Exception connecting subscriber endpoint to publisher endpoint", e);
}
this.subscribers.remove(senderName);
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
return null;
}
} finally {
kSender.getPublisher().closingLock.readLock().unlock();
}
} else {
log.error(
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
senderName, sender.getSessionId(), this.participantPublicId);
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName
+ "is closed");
}
log.error(
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
senderName, sender.getSessionId(), this.participantPublicId);
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + "is closed");
}
public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason, boolean silent) {
@ -461,7 +321,8 @@ public class KurentoParticipant extends Participant {
final PublisherEndpoint pub = senderKurentoParticipant.publisher;
if (pub != null) {
try {
if (pub.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
final Lock closingWriteLock = pub.closingLock.writeLock();
if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) {
try {
log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(),
senderName);
@ -478,7 +339,7 @@ public class KurentoParticipant extends Participant {
this.getParticipantPublicId(), senderName, this.session.getSessionId());
}
} finally {
pub.closingLock.writeLock().unlock();
closingWriteLock.unlock();
}
} else {
log.error(
@ -585,15 +446,66 @@ public class KurentoParticipant extends Participant {
return this.getParticipantPublicId() + "_" + senderParticipant.getPublisherStreamId();
}
private SubscriberEndpoint initializeSubscriberEndpoint(Participant kSender) {
String senderName = kSender.getParticipantPublicId();
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
senderName);
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
try {
CountDownLatch subscriberLatch = new CountDownLatch(1);
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
try {
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Timeout reached when creating subscriber endpoint");
}
} catch (InterruptedException e) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
"Interrupted when creating subscriber endpoint: " + e.getMessage());
}
if (oldMediaEndpoint != null) {
log.warn(
"PARTICIPANT {}: Two threads are trying to create at "
+ "the same time a subscriber endpoint for user {}",
this.getParticipantPublicId(), senderName);
return null;
}
if (subscriber.getEndpoint() == null) {
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint");
}
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
subscriber.setEndpointName(subscriberEndpointName);
subscriber.getEndpoint().setName(subscriberEndpointName);
subscriber.setStreamId(kSender.getPublisherStreamId());
endpointConfig.addEndpointListeners(subscriber, "subscriber");
} catch (OpenViduException e) {
this.subscribers.remove(senderName);
throw e;
}
log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), senderName);
return subscriber;
}
private void releasePublisherEndpoint(EndReason reason, Long kmsDisconnectionTime) {
if (publisher != null && publisher.getEndpoint() != null) {
final ReadWriteLock closingLock = publisher.closingLock;
try {
if (closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
final Lock closingWriteLock = publisher.closingLock.writeLock();
if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) {
try {
this.releasePublisherEndpointAux(reason, kmsDisconnectionTime);
} finally {
closingLock.writeLock().unlock();
closingWriteLock.unlock();
}
}
} catch (InterruptedException e) {

View File

@ -31,6 +31,8 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.annotation.PreDestroy;
import org.apache.commons.lang3.RandomStringUtils;
import org.kurento.client.GenericMediaElement;
import org.kurento.client.IceCandidate;
@ -65,6 +67,7 @@ import io.openvidu.server.core.EndReason;
import io.openvidu.server.core.FinalUser;
import io.openvidu.server.core.IdentifierPrefixes;
import io.openvidu.server.core.MediaOptions;
import io.openvidu.server.core.MediaServer;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
import io.openvidu.server.core.SessionManager;
@ -111,9 +114,13 @@ public class KurentoSessionManager extends SessionManager {
if (sessionNotActive == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) {
// Insecure user directly call joinRoom RPC method, without REST API use
sessionNotActive = new Session(sessionId, new SessionProperties.Builder()
.mediaMode(MediaMode.ROUTED).recordingMode(RecordingMode.ALWAYS).build(), openviduConfig,
recordingManager);
SessionProperties.Builder builder = new SessionProperties.Builder().mediaMode(MediaMode.ROUTED)
.recordingMode(RecordingMode.ALWAYS);
// forcedVideoCodec to NONE if mediasoup
if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
builder.forcedVideoCodec(VideoCodec.NONE);
}
sessionNotActive = new Session(sessionId, builder.build(), openviduConfig, recordingManager);
}
try {
@ -385,7 +392,7 @@ public class KurentoSessionManager extends SessionManager {
// Modify sdp if forced codec is defined
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
kurentoOptions.sdpOffer = sdpMunging.forceCodec(kurentoOptions.sdpOffer, participant, true, false,
isTranscodingAllowed, forcedVideoCodec, false);
isTranscodingAllowed, forcedVideoCodec);
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.publish, WebrtcDebugEventType.sdpOfferMunged, kurentoOptions.sdpOffer));
}
@ -573,7 +580,7 @@ public class KurentoSessionManager extends SessionManager {
// Modify server's SDPOffer if forced codec is defined
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, false, false, isTranscodingAllowed,
forcedVideoCodec, true);
forcedVideoCodec);
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
@ -594,12 +601,12 @@ public class KurentoSessionManager extends SessionManager {
}
@Override
public void subscribe(Participant participant, String senderName, String sdpAnswer, Integer transactionId,
boolean is2180) {
public void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId,
boolean initByServer) {
Session session = null;
try {
log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpAnswer={} ({})", senderName, sdpAnswer,
log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpString={} ({})", senderName, sdpString,
participant.getParticipantPublicId());
KurentoParticipant kParticipant = (KurentoParticipant) participant;
@ -625,48 +632,44 @@ public class KurentoSessionManager extends SessionManager {
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(senderParticipant);
// TODO: REMOVE ON 2.18.0
if (is2180) {
if (initByServer) {
// Server initiated negotiation. sdpString is the SDP Answer of the client
// Client's SDPAnswer to the server's SDPOffer
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpString));
kParticipant.receiveMediaFrom2180(senderParticipant, sdpAnswer, false);
kParticipant.receiveMedia(senderParticipant, sdpString, false, true);
sessionEventsHandler.onSubscribe(participant, session, transactionId, null);
} else {
// Client initiated negotiation. sdpString is the SDP Offer of the client
boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed();
VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec();
String sdpOffer = sdpString;
// Modify sdp if forced codec is defined
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
sdpAnswer = sdpMunging.forceCodec(sdpAnswer, participant, false, false, isTranscodingAllowed,
forcedVideoCodec, false);
sdpOffer = sdpMunging.forceCodec(sdpString, participant, false, false, isTranscodingAllowed,
forcedVideoCodec);
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpAnswer));
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
}
String finalSdpAnswer = kParticipant.receiveMediaFrom2170(senderParticipant, sdpAnswer, false);
if (finalSdpAnswer == null) {
String sdpAnswer = kParticipant.receiveMedia(senderParticipant, sdpOffer, false, false);
if (sdpAnswer == null) {
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
"Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId()
+ "' to '" + senderName + "'");
}
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, finalSdpAnswer));
sessionEventsHandler.onSubscribe(participant, session, finalSdpAnswer, transactionId, null);
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
sessionEventsHandler.onSubscribe(participant, session, sdpAnswer, transactionId, null);
}
// END TODO
// TODO: UNCOMMENT ON 2.18.0
// CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
// WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
// String remoteSdpAnswer = kParticipant.receiveMediaFrom(senderParticipant, sdpAnswer, false);
// sessionEventsHandler.onSubscribe(participant, session, transactionId, null);
// END TODO
} catch (OpenViduException e) {
log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, e);
@ -774,7 +777,6 @@ public class KurentoSessionManager extends SessionManager {
log.info("No session '{}' exists yet. Created one on KMS '{}' with ip '{}'", session.getSessionId(),
kms.getId(), kms.getIp());
sessionEventsHandler.onSessionCreated(session);
return session;
}
@ -1176,184 +1178,127 @@ public class KurentoSessionManager extends SessionManager {
return kParticipant;
}
// TODO: REMOVE ON 2.18.0
@Override
public void reconnectStream2170(Participant participant, String streamId, String sdpOffer, Integer transactionId) {
public void reconnectPublisher(Participant participant, String streamId, String sdpString, Integer transactionId) {
KurentoParticipant kParticipant = (KurentoParticipant) participant;
KurentoSession kSession = kParticipant.getSession();
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
reconnectPublisher(kSession, kParticipant, streamId, sdpString, transactionId);
}
@Override
public void reconnectSubscriber(Participant participant, String streamId, String sdpString, Integer transactionId,
boolean initByServer) {
KurentoParticipant kParticipant = (KurentoParticipant) participant;
KurentoSession kSession = kParticipant.getSession();
reconnectSubscriber(kSession, kParticipant, streamId, sdpString, transactionId, initByServer);
}
private String mungeSdpOffer(Session kSession, Participant participant, String sdpOffer, boolean isPublisher) {
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
boolean sdpOfferHasBeenMunged = false;
String originalSdpOffer = sdpOffer;
// Modify sdp if forced codec is defined
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
sdpOfferHasBeenMunged = true;
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
forcedVideoCodec, false);
return sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
forcedVideoCodec);
}
return null;
}
if (isPublisher) {
private void reconnectPublisher(KurentoSession kSession, KurentoParticipant kParticipant, String streamId,
String sdpOffer, Integer transactionId) {
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer));
if (sdpOfferHasBeenMunged) {
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
}
String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, true);
// Reconnect publisher
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
.getMediaOptions();
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, sdpOffer));
if (sdpOfferMunged != null) {
sdpOffer = sdpOfferMunged;
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
}
// Reconnect publisher
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher().getMediaOptions();
// 1) Disconnect broken PublisherEndpoint from its PassThrough
PublisherEndpoint publisher = kParticipant.getPublisher();
final PassThrough passThru = publisher.disconnectFromPassThrough();
// 2) Destroy the broken PublisherEndpoint and nothing else
publisher.cancelStatsLoop.set(true);
kParticipant.releaseElement(kParticipant.getParticipantPublicId(), publisher.getEndpoint());
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true);
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", kParticipant.getParticipantPublicId(),
sdpAnswer);
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.server,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
sessionEventsHandler.onPublishMedia(kParticipant, kParticipant.getPublisherStreamId(),
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
new HashSet<Participant>(), transactionId, null);
}
// 1) Disconnect broken PublisherEndpoint from its PassThrough
PublisherEndpoint publisher = kParticipant.getPublisher();
final PassThrough passThru = publisher.disconnectFromPassThrough();
private void reconnectSubscriber(KurentoSession kSession, KurentoParticipant kParticipant, String streamId,
String sdpString, Integer transactionId, boolean initByServer) {
// 2) Destroy the broken PublisherEndpoint and nothing else
publisher.cancelStatsLoop.set(true);
kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint());
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
if (senderPrivateId != null) {
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true);
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(),
sdpAnswer);
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.server,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
if (initByServer) {
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
new HashSet<Participant>(), transactionId, null);
// Server initiated negotiation
} else {
final String sdpAnswer = sdpString;
// Reconnect subscriber
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
if (senderPrivateId != null) {
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
kParticipant.receiveMedia(sender, sdpAnswer, true, true);
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer,
originalSdpOffer));
if (sdpOfferHasBeenMunged) {
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
kParticipant.getParticipantPublicId(), sdpAnswer);
sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null);
} else {
// Client initiated negotiation
String sdpOffer = sdpString;
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer, sdpOffer));
String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, false);
if (sdpOfferMunged != null) {
sdpOffer = sdpOfferMunged;
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOfferMunged,
sdpOffer));
}
String sdpAnswer = kParticipant.receiveMediaFrom2170(sender, sdpOffer, true);
kParticipant.cancelReceivingMedia(sender, null, true);
String sdpAnswer = kParticipant.receiveMedia(sender, sdpOffer, true, false);
if (sdpAnswer == null) {
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
"Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'");
}
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
participant.getParticipantPublicId(), sdpAnswer);
kParticipant.getParticipantPublicId(), sdpAnswer);
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
} else {
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null);
}
}
}
// END TODO
@Override
public void reconnectStream(Participant participant, String streamId, String sdpOfferOrAnswer,
Integer transactionId) {
KurentoParticipant kParticipant = (KurentoParticipant) participant;
KurentoSession kSession = kParticipant.getSession();
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
if (isPublisher) {
// Reconnect publisher
String sdpOffer = sdpOfferOrAnswer;
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
boolean sdpOfferHasBeenMunged = false;
final String originalSdpOffer = sdpOffer;
// Modify sdp if forced codec is defined
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
sdpOfferHasBeenMunged = true;
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
forcedVideoCodec, false);
}
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer));
if (sdpOfferHasBeenMunged) {
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
}
// Reconnect publisher
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
.getMediaOptions();
// 1) Disconnect broken PublisherEndpoint from its PassThrough
PublisherEndpoint publisher = kParticipant.getPublisher();
final PassThrough passThru = publisher.disconnectFromPassThrough();
// 2) Destroy the broken PublisherEndpoint and nothing else
publisher.cancelStatsLoop.set(true);
kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint());
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true);
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(),
sdpAnswer);
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.server,
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
new HashSet<Participant>(), transactionId, null);
} else {
// Reconnect subscriber
final String sdpAnswer = sdpOfferOrAnswer;
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
if (senderPrivateId != null) {
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
kParticipant.receiveMediaFrom2180(sender, sdpAnswer, true);
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
participant.getParticipantPublicId(), sdpAnswer);
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
} else {
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
}
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
}
}
@ -1444,4 +1389,11 @@ public class KurentoSessionManager extends SessionManager {
return lessLoadedKms;
}
@PreDestroy
@Override
public void close() {
super.close();
this.kmsManager.closeAllKurentoClients();
}
}

View File

@ -58,31 +58,24 @@ public class SubscriberEndpoint extends MediaEndpoint {
offerOptions.setOfferToReceiveVideo(publisher.getMediaOptions().hasVideo());
String sdpOffer = generateOffer(offerOptions);
gatherCandidates();
return sdpOffer;
}
public synchronized String subscribe(String sdpAnswer, PublisherEndpoint publisher) {
// TODO: REMOVE ON 2.18.0
if (this.createdAt == null) {
// 2.17.0
public synchronized String subscribe(String sdpString, PublisherEndpoint publisher) {
if (this.publisherStreamId == null) {
// Client initiated negotiation
registerOnIceCandidateEventListener(publisher.getOwner().getParticipantPublicId());
this.createdAt = System.currentTimeMillis();
String realSdpAnswer = processOffer(sdpAnswer);
String realSdpAnswer = processOffer(sdpString);
gatherCandidates();
publisher.connect(this.getEndpoint(), false);
this.publisherStreamId = publisher.getStreamId();
return realSdpAnswer;
} else {
// 2.18.0
processAnswer(sdpAnswer);
gatherCandidates();
return null;
// Server initiated negotiation
return processAnswer(sdpString);
}
// END TODO
// TODO: UNCOMMENT ON 2.18.0
// processAnswer(sdpAnswer);
// END TODO
}
@Override

View File

@ -31,7 +31,6 @@ import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.apache.commons.lang3.RandomStringUtils;
import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener;
@ -343,8 +342,7 @@ public abstract class KmsManager {
@PostConstruct
protected abstract void postConstructInitKurentoClients();
@PreDestroy
public void close() {
public void closeAllKurentoClients() {
log.info("Closing all KurentoClients");
this.kmss.values().forEach(kms -> {
if (kms.getKurentoClientReconnectTimer() != null) {

View File

@ -78,12 +78,18 @@ public class Recording {
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) {
builder.resolution(json.get("resolution").getAsString());
builder.frameRate(json.get("frameRate").getAsInt());
RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
builder.recordingLayout(recordingLayout);
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
builder.customLayout(json.get("customLayout").getAsString());
if (json.has("resolution")) {
builder.resolution(json.get("resolution").getAsString());
}
if (json.has("frameRate")) {
builder.frameRate(json.get("frameRate").getAsInt());
}
if (json.has("recordingLayout")) {
RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
builder.recordingLayout(recordingLayout);
if (RecordingLayout.CUSTOM.equals(recordingLayout) && json.has("customLayout")) {
builder.customLayout(json.get("customLayout").getAsString());
}
}
}
this.recordingProperties = builder.build();

View File

@ -64,6 +64,7 @@ import io.openvidu.java.client.RecordingProperties;
import io.openvidu.server.cdr.CallDetailRecord;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.EndReason;
import io.openvidu.server.core.MediaServer;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
import io.openvidu.server.core.SessionEventsHandler;
@ -768,6 +769,8 @@ public class RecordingManager {
// Check Kurento Media Server write permissions in recording path
if (this.kmsManager.getKmss().isEmpty()) {
log.warn("No KMSs were defined in KMS_URIS array. Recording path check aborted");
} else if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
log.warn("Using mediasoup. Recording path check aborted");
} else {
Kms kms = null;
try {

View File

@ -17,12 +17,12 @@
package io.openvidu.server.rest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Controller
@CrossOrigin
@RequestMapping(RequestMappings.ACCEPT_CERTIFICATE)
public class CertificateRestController {

View File

@ -65,6 +65,7 @@ import io.openvidu.java.client.VideoCodec;
import io.openvidu.server.config.OpenviduConfig;
import io.openvidu.server.core.EndReason;
import io.openvidu.server.core.IdentifierPrefixes;
import io.openvidu.server.core.MediaServer;
import io.openvidu.server.core.Participant;
import io.openvidu.server.core.Session;
import io.openvidu.server.core.SessionManager;
@ -120,7 +121,7 @@ public class SessionRestController {
}
Session sessionNotActive = sessionManager.storeSessionNotActive(sessionId, sessionProperties);
log.info("New session {} initialized {}", sessionId, this.sessionManager.getSessionsWithNotActive().stream()
log.info("New session {} created {}", sessionId, this.sessionManager.getSessionsWithNotActive().stream()
.map(Session::getSessionId).collect(Collectors.toList()).toString());
return new ResponseEntity<>(sessionNotActive.toJson(false, false).toString(), RestUtils.getResponseHeaders(),
@ -756,10 +757,15 @@ public class SessionRestController {
}
builder = builder.customSessionId(customSessionId);
}
if (forcedVideoCodec != null) {
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
// forcedVideoCodec to NONE if mediasoup
if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
builder = builder.forcedVideoCodec(VideoCodec.NONE);
} else {
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
if (forcedVideoCodec != null) {
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
} else {
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
}
}
if (allowTranscoding != null) {
builder = builder.allowTranscoding(allowTranscoding);

View File

@ -172,6 +172,9 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
case ProtocolElements.VIDEODATA_METHOD:
updateVideoData(rpcConnection, request);
break;
case ProtocolElements.ECHO_METHOD:
echo(rpcConnection, request);
break;
default:
log.error("Unrecognized request {}", request);
break;
@ -355,18 +358,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
boolean reconnect = false;
// TODO: REMOVE ON 2.18.0
if (request.getParams().has(ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM)) {
reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
}
// END TODO
// TODO: UNCOMMENT ON 2.18.0
// boolean reconnect = getBooleanParam(request,
// ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
// END TODO
boolean reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
sessionManager.prepareSubscription(participant, senderPublicId, reconnect, request.getId());
}
@ -382,28 +374,15 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
// TODO: REMOVE ON 2.18.0
if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM)) {
// 2.17.0: initiative held by browser when subscribing
// The request comes with an SDPOffer
// Client initiated negotiation (comes with SDP Offer)
String sdpOffer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM);
sessionManager.subscribe(participant, senderPublicId, sdpOffer, request.getId(), false);
} else if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM)) {
// 2.18.0: initiative held by server when subscribing
// This is the final call after prepareReceiveVidoFrom, comes with SDPAnswer
// Server initiated negotiation (comes with SDP Answer)
String sdpAnswer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM);
sessionManager.subscribe(participant, senderPublicId, sdpAnswer, request.getId(), true);
}
// END TODO
// TODO: UNCOMMENT ON 2.18.0
/*
* String sdpAnswer = getStringParam(request,
* ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM);
* sessionManager.subscribe(participant, senderPublicId, sdpAnswer,
* request.getId());
*/
// END TODO
}
private void unsubscribeFromVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
@ -667,40 +646,28 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
} catch (OpenViduException e) {
return;
}
String streamId = getStringParam(request, ProtocolElements.RECONNECTSTREAM_STREAM_PARAM);
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
// TODO: REMOVE ON 2.18.0
String sdpString = null;
if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM)) {
// 2.17.0
try {
String sdpOffer = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM);
sessionManager.reconnectStream(participant, streamId, sdpOffer, request.getId());
} catch (OpenViduException e) {
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
new JsonObject(), e);
}
sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM);
} else if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM)) {
// 2.18.0
String sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
try {
sessionManager.reconnectStream(participant, streamId, sdpString, request.getId());
} catch (OpenViduException e) {
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
new JsonObject(), e);
}
sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
}
// END TODO
// TODO: UNCOMMENT ON 2.18.0
/*
* String sdpString = getStringParam(request,
* ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); try {
* sessionManager.reconnectStream(participant, streamId, sdpString,
* request.getId()); } catch (OpenViduException e) {
* this.notificationService.sendErrorResponse(participant.
* getParticipantPrivateId(), request.getId(), new JsonObject(), e); }
*/
// END TODO
try {
if (isPublisher) {
sessionManager.reconnectPublisher(participant, streamId, sdpString, request.getId());
} else {
boolean initByServer = request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
sessionManager.reconnectSubscriber(participant, streamId, sdpString, request.getId(), initByServer);
}
} catch (OpenViduException e) {
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
new JsonObject(), e);
}
}
private void updateVideoData(RpcConnection rpcConnection, Request<JsonObject> request) {
@ -717,6 +684,10 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
}
}
private void echo(RpcConnection rpcConnection, Request<JsonObject> request) {
sessionManager.onEcho(rpcConnection.getParticipantPrivateId(), request.getId());
}
public void leaveRoomAfterConnClosed(String participantPrivateId, EndReason reason) {
try {
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,

View File

@ -68,7 +68,7 @@ public class SDPMunging {
* ordering of formats. Browsers (tested with Chrome 84) honor this change and
* use the first codec provided in the answer, so this operation actually works.
*/
public String setCodecPreference(VideoCodec codec, String sdp, boolean applyHeavyMunging) throws OpenViduException {
public String setCodecPreference(VideoCodec codec, String sdp) throws OpenViduException {
String codecStr = codec.name();
log.info("[setCodecPreference] codec: {}", codecStr);
@ -156,9 +156,8 @@ public class SDPMunging {
lines[sl] = newLine.toString().trim();
}
if (applyHeavyMunging) {
lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines);
}
lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines);
return String.join("\r\n", lines) + "\r\n";
}
@ -166,8 +165,7 @@ public class SDPMunging {
* Return a SDP modified to force a specific codec
*/
public String forceCodec(String sdp, Participant participant, boolean isPublisher, boolean isReconnecting,
boolean isTranscodingAllowed, VideoCodec forcedVideoCodec, boolean applyHeavyMunging)
throws OpenViduException {
boolean isTranscodingAllowed, VideoCodec forcedVideoCodec) throws OpenViduException {
try {
if (supportedVideoCodecs.contains(forcedVideoCodec)) {
String mungedSdpOffer;
@ -178,7 +176,7 @@ public class SDPMunging {
participant.getParticipantPublicId(), participant.getSessionId(), isPublisher, !isPublisher,
isReconnecting, sdp);
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp, applyHeavyMunging);
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp);
log.debug(
"PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'."

View File

@ -88,7 +88,7 @@ public class SDPMungingTest {
private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException {
this.oldSdp = getSdpFile(sdpNameFile);
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp, false);
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp);
this.forceCodecPayloads = new ArrayList<>();
// Get all Payload-Type for video Codec

View File

@ -344,6 +344,13 @@ public class OpenViduEventManager {
return dimension;
}
public void stopVideoTracksOfVideoElement(WebElement videoElement, String parentSelector) {
String script = "return (document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ")
+ "#" + videoElement.getAttribute("id")
+ "').srcObject.getVideoTracks().forEach(track => track.stop()))";
((JavascriptExecutor) driver).executeScript(script);
}
private boolean hasAudioTracks(WebElement videoElement, String parentSelector) {
String script = "return ((document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ")
+ "#" + videoElement.getAttribute("id") + "').srcObject.getAudioTracks().length > 0)"

View File

@ -481,6 +481,38 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
gracefullyLeaveParticipants(4);
}
@Test
@DisplayName("ExceptionEvent test")
void exceptionEventTest() throws Exception {
setupBrowser("chrome");
log.info("ExceptionEvent test");
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .send-audio-checkbox")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .join-btn")).click();
user.getEventManager().waitUntilEventReaches("streamCreated", 1);
user.getEventManager().waitUntilEventReaches("streamPlaying", 1);
// Stop video track
WebElement video = user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 video"));
this.user.getEventManager().stopVideoTracksOfVideoElement(video, "#openvidu-instance-0");
user.getDriver().findElement(By.id("add-user-btn")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .publish-checkbox")).click();
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click();
user.getEventManager().waitUntilEventReaches("exception", 1);
Assert.assertTrue("Wrong ExceptionEvent type", user.getDriver()
.findElement(By.cssSelector("#openvidu-instance-1 .mat-expansion-panel:last-child .event-content"))
.getAttribute("textContent").equals("NO_STREAM_PLAYING_EVENT"));
gracefullyLeaveParticipants(2);
}
@Test
@DisplayName("Subscribe Unsubscribe")
void subscribeUnsubscribeTest() throws Exception {

View File

@ -1,49 +1,49 @@
{
"dependencies": {
"@angular/animations": "8.2.14",
"@angular/cdk": "8.2.3",
"@angular/common": "8.2.14",
"@angular/compiler": "8.2.14",
"@angular/core": "8.2.14",
"@angular/flex-layout": "8.0.0-beta.27",
"@angular/forms": "8.2.14",
"@angular/http": "7.2.15",
"@angular/material": "8.2.3",
"@angular/platform-browser": "8.2.14",
"@angular/platform-browser-dynamic": "8.2.14",
"@angular/router": "8.2.14",
"colormap": "2.3.1",
"core-js": "3.4.7",
"hammerjs": "2.0.8",
"json-stringify-safe": "^5.0.1",
"openvidu-browser": "2.17.0",
"openvidu-node-client": "2.17.0",
"rxjs": "6.5.3",
"zone.js": "0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.803.20",
"@angular/cli": "8.3.20",
"@angular/compiler-cli": "8.2.14",
"@angular/language-service": "8.2.14",
"@types/jasmine": "3.5.0",
"@types/jasminewd2": "2.0.8",
"@types/node": "12.12.14",
"codelyzer": "5.2.0",
"ts-node": "8.5.4",
"tslint": "5.20.1",
"typescript": "3.5.3"
},
"license": "Apache-2.0",
"name": "openvidu-testapp",
"private": true,
"scripts": {
"build": "ng build",
"e2e": "ng e2e",
"lint": "ng lint",
"ng": "ng",
"start": "ng serve",
"test": "ng test"
},
"version": "2.17.0"
}
"dependencies": {
"@angular/animations": "8.2.14",
"@angular/cdk": "8.2.3",
"@angular/common": "8.2.14",
"@angular/compiler": "8.2.14",
"@angular/core": "8.2.14",
"@angular/flex-layout": "8.0.0-beta.27",
"@angular/forms": "8.2.14",
"@angular/http": "7.2.15",
"@angular/material": "8.2.3",
"@angular/platform-browser": "8.2.14",
"@angular/platform-browser-dynamic": "8.2.14",
"@angular/router": "8.2.14",
"colormap": "2.3.1",
"core-js": "3.4.7",
"hammerjs": "2.0.8",
"json-stringify-safe": "^5.0.1",
"openvidu-browser": "2.18.0",
"openvidu-node-client": "2.18.0",
"rxjs": "6.5.3",
"zone.js": "0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "0.803.20",
"@angular/cli": "8.3.20",
"@angular/compiler-cli": "8.2.14",
"@angular/language-service": "8.2.14",
"@types/jasmine": "3.5.0",
"@types/jasminewd2": "2.0.8",
"@types/node": "12.12.14",
"codelyzer": "5.2.0",
"ts-node": "8.5.4",
"tslint": "5.20.1",
"typescript": "3.5.3"
},
"license": "Apache-2.0",
"name": "openvidu-testapp",
"private": true,
"scripts": {
"build": "ng build",
"e2e": "ng e2e",
"lint": "ng lint",
"ng": "ng",
"start": "ng serve",
"test": "ng test"
},
"version": "2.18.0"
}

View File

@ -6,7 +6,7 @@ import {
import {
OpenVidu, Session, Subscriber, Publisher, Event, StreamEvent, ConnectionEvent,
SessionDisconnectedEvent, SignalEvent, RecordingEvent,
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent, ExceptionEvent
} from 'openvidu-browser';
import {
OpenVidu as OpenViduAPI,
@ -138,7 +138,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
publisherStartSpeaking: false,
publisherStopSpeaking: false,
reconnecting: true,
reconnected: true
reconnected: true,
exception: true
};
// Session properties dialog
@ -247,8 +248,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
signal: false,
publisherStartSpeaking: true,
publisherStopSpeaking: true,
reconnecting: true,
reconnected: true
reconnecting: false,
reconnected: false,
exception: false
}, true);
this.session.connect(token, this.clientData)
@ -508,6 +510,15 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
}
}
if (this.sessionEvents.exception !== oldValues.exception || firstTime) {
this.session.off('exception');
if (this.sessionEvents.exception) {
this.session.on('exception', (event: ExceptionEvent) => {
this.updateEventList('exception', event.name, event);
});
}
}
}
syncInitPublisher() {
@ -665,7 +676,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
publisherStartSpeaking: this.sessionEvents.publisherStartSpeaking,
publisherStopSpeaking: this.sessionEvents.publisherStopSpeaking,
reconnecting: this.sessionEvents.reconnecting,
reconnected: this.sessionEvents.reconnected
reconnected: this.sessionEvents.reconnected,
exception: this.sessionEvents.exception
};
const dialogRef = this.dialog.open(EventsDialogComponent, {
@ -699,7 +711,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
publisherStartSpeaking: result.publisherStartSpeaking,
publisherStopSpeaking: result.publisherStopSpeaking,
reconnecting: result.reconnecting,
reconnected: result.reconnected
reconnected: result.reconnected,
exception: result.exception
};
document.getElementById('session-events-btn-' + this.index).classList.remove('cdk-program-focused');
});

View File

@ -62,7 +62,7 @@
<version.webdrivermanager>4.2.2</version.webdrivermanager>
<version.openvidu.java.client>2.17.0</version.openvidu.java.client>
<version.openvidu.java.client>2.18.0</version.openvidu.java.client>
<version.openvidu.client>1.1.0</version.openvidu.client>
<version.openvidu.test.browsers>1.1.0</version.openvidu.test.browsers>