mirror of https://github.com/OpenVidu/openvidu.git
Merge branch 'master' into fix-generateOffer
commit
36f3d305d3
|
@ -10,20 +10,20 @@
|
||||||
},
|
},
|
||||||
"description": "OpenVidu Browser",
|
"description": "OpenVidu Browser",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "14.14.32",
|
"@types/node": "15.12.2",
|
||||||
"@types/platform": "1.3.3",
|
"@types/platform": "1.3.3",
|
||||||
"browserify": "17.0.0",
|
"browserify": "17.0.0",
|
||||||
"grunt": "1.3.0",
|
"grunt": "1.4.1",
|
||||||
"grunt-cli": "1.3.2",
|
"grunt-cli": "1.4.3",
|
||||||
"grunt-contrib-copy": "1.0.0",
|
"grunt-contrib-copy": "1.0.0",
|
||||||
"grunt-contrib-sass": "2.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-contrib-watch": "1.1.0",
|
||||||
"grunt-postcss": "0.9.0",
|
"grunt-postcss": "0.9.0",
|
||||||
"grunt-string-replace": "1.3.1",
|
"grunt-string-replace": "1.3.1",
|
||||||
"grunt-ts": "6.0.0-beta.22",
|
"grunt-ts": "6.0.0-beta.22",
|
||||||
"terser": "5.6.0",
|
"terser": "5.7.0",
|
||||||
"tsify": "5.0.2",
|
"tsify": "5.0.4",
|
||||||
"tslint": "6.1.3",
|
"tslint": "6.1.3",
|
||||||
"typedoc": "0.19.2",
|
"typedoc": "0.19.2",
|
||||||
"typescript": "4.0.7"
|
"typescript": "4.0.7"
|
||||||
|
@ -38,9 +38,9 @@
|
||||||
"scripts": {
|
"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": "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",
|
"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"
|
"docs": "./generate-docs.sh"
|
||||||
},
|
},
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
"version": "2.17.0"
|
"version": "2.18.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,6 +67,7 @@ let platform: PlatformUtils;
|
||||||
export class OpenVidu {
|
export class OpenVidu {
|
||||||
|
|
||||||
private jsonRpcClient: any;
|
private jsonRpcClient: any;
|
||||||
|
private masterNodeHasCrashed = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
@ -104,6 +105,10 @@ export class OpenVidu {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
finalUserId: string;
|
finalUserId: string;
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
mediaServer: string;
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -744,7 +749,8 @@ export class OpenVidu {
|
||||||
onconnected: onConnectSucces,
|
onconnected: onConnectSucces,
|
||||||
ondisconnect: this.disconnectCallback.bind(this),
|
ondisconnect: this.disconnectCallback.bind(this),
|
||||||
onreconnecting: this.reconnectingCallback.bind(this),
|
onreconnecting: this.reconnectingCallback.bind(this),
|
||||||
onreconnected: this.reconnectedCallback.bind(this)
|
onreconnected: this.reconnectedCallback.bind(this),
|
||||||
|
ismasternodecrashed: this.isMasterNodeCrashed.bind(this)
|
||||||
},
|
},
|
||||||
rpc: {
|
rpc: {
|
||||||
requestTimeout: 10000,
|
requestTimeout: 10000,
|
||||||
|
@ -761,12 +767,30 @@ export class OpenVidu {
|
||||||
networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session),
|
networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session),
|
||||||
filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
|
filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
|
||||||
iceCandidate: this.session.recvIceCandidate.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);
|
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
|
* @hidden
|
||||||
*/
|
*/
|
||||||
|
@ -1009,10 +1033,14 @@ export class OpenVidu {
|
||||||
if (!!this.session.connection) {
|
if (!!this.session.connection) {
|
||||||
this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => {
|
this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => {
|
||||||
if (!!error) {
|
if (!!error) {
|
||||||
logger.error(error);
|
if (this.isMasterNodeCrashed()) {
|
||||||
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');
|
logger.warn('Master Node has crashed!');
|
||||||
this.session.onLostConnection("networkDisconnect");
|
} else {
|
||||||
this.jsonRpcClient.close(4101, "Reconnection fault");
|
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 {
|
} else {
|
||||||
this.jsonRpcClient.resetPing();
|
this.jsonRpcClient.resetPing();
|
||||||
this.session.onRecoveredConnection();
|
this.session.onRecoveredConnection();
|
||||||
|
@ -1030,6 +1058,10 @@ export class OpenVidu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isMasterNodeCrashed() {
|
||||||
|
return this.masterNodeHasCrashed;
|
||||||
|
}
|
||||||
|
|
||||||
private isRoomAvailable(): boolean {
|
private isRoomAvailable(): boolean {
|
||||||
if (this.session !== undefined && this.session instanceof Session) {
|
if (this.session !== undefined && this.session instanceof Session) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -86,10 +86,6 @@ export class Publisher extends StreamManager {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
screenShareResizeInterval: NodeJS.Timer;
|
screenShareResizeInterval: NodeJS.Timer;
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
IEAdapter: any;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
|
|
@ -197,7 +197,7 @@ export class Session extends EventDispatcher {
|
||||||
* #### Events dispatched
|
* #### Events dispatched
|
||||||
*
|
*
|
||||||
* The [[Session]] object of the local participant will dispatch a `sessionDisconnected` event.
|
* 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)).
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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)).
|
* 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.
|
* 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.
|
* 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.
|
* 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.
|
* 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)).
|
* 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.
|
* 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.
|
* 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 {
|
onParticipantLeft(msg): void {
|
||||||
|
|
||||||
if (this.remoteConnections.size > 0) {
|
if (this.remoteConnections.size > 0) {
|
||||||
this.getRemoteConnection(msg.connectionId).then(connection => {
|
this.getRemoteConnection(msg.connectionId, 'onParticipantLeft').then(connection => {
|
||||||
if (!!connection.stream) {
|
if (!!connection.stream) {
|
||||||
const stream = connection.stream;
|
const stream = connection.stream;
|
||||||
|
|
||||||
|
@ -823,7 +823,7 @@ export class Session extends EventDispatcher {
|
||||||
// Get the existing Connection created on 'onParticipantJoined' for
|
// Get the existing Connection created on 'onParticipantJoined' for
|
||||||
// existing participants or create a new one for new participants
|
// existing participants or create a new one for new participants
|
||||||
let connection: Connection;
|
let connection: Connection;
|
||||||
this.getRemoteConnection(response.id)
|
this.getRemoteConnection(response.id, 'onParticipantPublished')
|
||||||
|
|
||||||
.then(con => {
|
.then(con => {
|
||||||
// Update existing Connection
|
// Update existing Connection
|
||||||
|
@ -848,7 +848,7 @@ export class Session extends EventDispatcher {
|
||||||
// Your stream has been forcedly unpublished from the session
|
// Your stream has been forcedly unpublished from the session
|
||||||
this.stopPublisherStream(msg.reason);
|
this.stopPublisherStream(msg.reason);
|
||||||
} else {
|
} else {
|
||||||
this.getRemoteConnection(msg.connectionId)
|
this.getRemoteConnection(msg.connectionId, 'onParticipantUnpublished')
|
||||||
|
|
||||||
.then(connection => {
|
.then(connection => {
|
||||||
|
|
||||||
|
@ -965,7 +965,7 @@ export class Session extends EventDispatcher {
|
||||||
// Your stream has been forcedly changed (filter feature)
|
// Your stream has been forcedly changed (filter feature)
|
||||||
callback(this.connection);
|
callback(this.connection);
|
||||||
} else {
|
} else {
|
||||||
this.getRemoteConnection(msg.connectionId)
|
this.getRemoteConnection(msg.connectionId, 'onStreamPropertyChanged')
|
||||||
.then(connection => {
|
.then(connection => {
|
||||||
callback(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) => {
|
return new Promise<Connection>((resolve, reject) => {
|
||||||
const connection = this.remoteConnections.get(connectionId);
|
const connection = this.remoteConnections.get(connectionId);
|
||||||
if (!!connection) {
|
if (!!connection) {
|
||||||
|
@ -1419,9 +1419,8 @@ export class Session extends EventDispatcher {
|
||||||
resolve(connection);
|
resolve(connection);
|
||||||
} else {
|
} else {
|
||||||
// Remote connection not found. Reject with OpenViduError
|
// 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());
|
'Existing remote connections: ' + JSON.stringify(this.remoteConnections.keys());
|
||||||
|
|
||||||
reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
|
reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1487,6 +1486,7 @@ export class Session extends EventDispatcher {
|
||||||
}
|
}
|
||||||
this.openvidu.role = opts.role;
|
this.openvidu.role = opts.role;
|
||||||
this.openvidu.finalUserId = opts.finalUserId;
|
this.openvidu.finalUserId = opts.finalUserId;
|
||||||
|
this.openvidu.mediaServer = opts.mediaServer;
|
||||||
this.capabilities = {
|
this.capabilities = {
|
||||||
subscribe: true,
|
subscribe: true,
|
||||||
publish: this.openvidu.role !== 'SUBSCRIBER',
|
publish: this.openvidu.role !== 'SUBSCRIBER',
|
||||||
|
|
|
@ -214,6 +214,10 @@ export class Stream {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
ee = new EventEmitter();
|
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.
|
* Applies an audio/video filter to the stream.
|
||||||
*
|
*
|
||||||
|
@ -461,11 +478,13 @@ export class Stream {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
disposeWebRtcPeer(): void {
|
disposeWebRtcPeer(): void {
|
||||||
|
let webrtcId;
|
||||||
if (!!this.webRtcPeer) {
|
if (!!this.webRtcPeer) {
|
||||||
this.webRtcPeer.dispose();
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
|
const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
|
||||||
|
@ -802,10 +821,42 @@ export class Stream {
|
||||||
initWebRtcPeerSend(reconnect: boolean): Promise<void> {
|
initWebRtcPeerSend(reconnect: boolean): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
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
|
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) => {
|
const successOfferCallback = (sdpOfferParam) => {
|
||||||
logger.debug('Sending SDP offer to publish as '
|
logger.debug('Sending SDP offer to publish as '
|
||||||
+ this.streamId, sdpOfferParam);
|
+ this.streamId, sdpOfferParam);
|
||||||
|
@ -839,9 +890,9 @@ export class Stream {
|
||||||
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error.code === 401) {
|
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 {
|
} else {
|
||||||
reject('Error on publishVideo: ' + JSON.stringify(error));
|
finalReject('Error on publishVideo: ' + JSON.stringify(error));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
|
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
|
||||||
|
@ -861,10 +912,11 @@ export class Stream {
|
||||||
}
|
}
|
||||||
this.initWebRtcStats();
|
this.initWebRtcStats();
|
||||||
logger.info("'Publisher' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "published") + " to session");
|
logger.info("'Publisher' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "published") + " to session");
|
||||||
resolve();
|
|
||||||
|
finalResolve();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error);
|
finalReject(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -876,8 +928,8 @@ export class Stream {
|
||||||
video: this.hasVideo,
|
video: this.hasVideo,
|
||||||
},
|
},
|
||||||
simulcast: false,
|
simulcast: false,
|
||||||
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
|
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)]) },
|
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||||
iceServers: this.getIceServersConf(),
|
iceServers: this.getIceServersConf(),
|
||||||
mediaStream: this.mediaStream,
|
mediaStream: this.mediaStream,
|
||||||
};
|
};
|
||||||
|
@ -896,10 +948,10 @@ export class Stream {
|
||||||
.then(() => {
|
.then(() => {
|
||||||
successOfferCallback(sdpOffer.sdp);
|
successOfferCallback(sdpOffer.sdp);
|
||||||
}).catch(error => {
|
}).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 => {
|
}).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> {
|
initWebRtcPeerReceive(reconnect: boolean): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
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) => {
|
this.session.openvidu.sendRequest('prepareReceiveVideoFrom', { sender: this.streamId, reconnect }, (error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error)));
|
reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error)));
|
||||||
} else {
|
} else {
|
||||||
this.completeWebRtcPeerReceive(response.sdpOffer, reconnect)
|
this.completeWebRtcPeerReceive(reconnect, response.sdpOffer)
|
||||||
.then(() => {
|
.then(() => resolve()).catch(error => reject(error));
|
||||||
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
|
|
||||||
this.remotePeerSuccessfullyEstablished(reconnect);
|
|
||||||
this.initWebRtcStats();
|
|
||||||
resolve();
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -931,25 +1050,29 @@ export class Stream {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
completeWebRtcPeerReceive(sdpOffer: string, reconnect: boolean): Promise<void> {
|
completeWebRtcPeerReceive(reconnect: boolean, sdpOfferByServer?: string): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
logger.debug("'Session.subscribe(Stream)' called");
|
logger.debug("'Session.subscribe(Stream)' called");
|
||||||
|
|
||||||
const successAnswerCallback = (sdpAnswer) => {
|
const sendSdpToServer = (sdpString: string) => {
|
||||||
logger.debug('Sending SDP answer to subscribe to '
|
|
||||||
+ this.streamId, sdpAnswer);
|
logger.debug(`Sending local SDP ${(!!sdpOfferByServer ? 'answer' : 'offer')} to subscribe to ${this.streamId}`, sdpString);
|
||||||
|
|
||||||
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
|
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
|
||||||
const params = {};
|
const params = {};
|
||||||
params[reconnect ? 'stream' : 'sender'] = this.streamId;
|
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) => {
|
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
|
reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
|
||||||
} else {
|
} else {
|
||||||
resolve();
|
resolve(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -960,29 +1083,47 @@ export class Stream {
|
||||||
video: this.hasVideo,
|
video: this.hasVideo,
|
||||||
},
|
},
|
||||||
simulcast: false,
|
simulcast: false,
|
||||||
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
|
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)]) },
|
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||||
iceServers: this.getIceServersConf(),
|
iceServers: this.getIceServersConf(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (reconnect) {
|
||||||
|
this.disposeWebRtcPeer();
|
||||||
|
}
|
||||||
|
|
||||||
this.webRtcPeer = new WebRtcPeerRecvonly(config);
|
this.webRtcPeer = new WebRtcPeerRecvonly(config);
|
||||||
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
|
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
|
||||||
this.webRtcPeer.processRemoteOffer(sdpOffer)
|
|
||||||
.then(() => {
|
if (!!sdpOfferByServer) {
|
||||||
|
|
||||||
|
this.webRtcPeer.processRemoteOffer(sdpOfferByServer).then(() => {
|
||||||
this.webRtcPeer.createAnswer().then(sdpAnswer => {
|
this.webRtcPeer.createAnswer().then(sdpAnswer => {
|
||||||
this.webRtcPeer.processLocalAnswer(sdpAnswer)
|
this.webRtcPeer.processLocalAnswer(sdpAnswer).then(() => {
|
||||||
.then(() => {
|
sendSdpToServer(sdpAnswer.sdp!);
|
||||||
successAnswerCallback(sdpAnswer.sdp);
|
}).catch(error => {
|
||||||
}).catch(error => {
|
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
|
||||||
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
|
});
|
||||||
});
|
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(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)));
|
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 {
|
private initWebRtcStats(): void {
|
||||||
this.webRtcStats = new WebRtcStats(this);
|
this.webRtcStats = new WebRtcStats(this);
|
||||||
this.webRtcStats.initWebRtcStats();
|
this.webRtcStats.initWebRtcStats();
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Stream } from './Stream';
|
import { Stream } from './Stream';
|
||||||
|
import { Subscriber } from './Subscriber';
|
||||||
import { EventDispatcher } from './EventDispatcher';
|
import { EventDispatcher } from './EventDispatcher';
|
||||||
import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
|
import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
|
||||||
import { Event } from '../OpenViduInternal/Events/Event';
|
import { Event } from '../OpenViduInternal/Events/Event';
|
||||||
|
@ -24,6 +25,7 @@ import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'
|
||||||
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
||||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||||
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
||||||
|
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
@ -90,19 +92,23 @@ export class StreamManager extends EventDispatcher {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
firstVideoElement?: StreamManagerVideo;
|
protected firstVideoElement?: StreamManagerVideo;
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
lazyLaunchVideoElementCreatedEvent = false;
|
protected element: HTMLElement;
|
||||||
/**
|
|
||||||
* @hidden
|
|
||||||
*/
|
|
||||||
element: HTMLElement;
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
protected canPlayListener: EventListener;
|
protected canPlayListener: EventListener;
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
|
||||||
|
/**
|
||||||
|
* @hidden
|
||||||
|
*/
|
||||||
|
private lazyLaunchVideoElementCreatedEvent = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @hidden
|
||||||
|
@ -138,7 +144,11 @@ export class StreamManager extends EventDispatcher {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.canPlayListener = () => {
|
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()) {
|
if (!this.stream.displayMyRemote()) {
|
||||||
logger.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing');
|
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')]);
|
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');
|
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')]);
|
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)]);
|
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
||||||
};
|
};
|
||||||
|
@ -274,7 +281,7 @@ export class StreamManager extends EventDispatcher {
|
||||||
|
|
||||||
this.initializeVideoProperties(video);
|
this.initializeVideoProperties(video);
|
||||||
|
|
||||||
if (this.stream.isLocal() && this.stream.displayMyRemote()) {
|
if (!this.remote && this.stream.displayMyRemote()) {
|
||||||
if (video.srcObject !== this.stream.getMediaStream()) {
|
if (video.srcObject !== this.stream.getMediaStream()) {
|
||||||
video.srcObject = this.stream.getMediaStream();
|
video.srcObject = this.stream.getMediaStream();
|
||||||
}
|
}
|
||||||
|
@ -305,7 +312,7 @@ export class StreamManager extends EventDispatcher {
|
||||||
id: video.id,
|
id: video.id,
|
||||||
canplayListenerAdded: false
|
canplayListenerAdded: false
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info('New video element associated to ', this);
|
logger.info('New video element associated to ', this);
|
||||||
|
|
||||||
return returnNumber;
|
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)
|
* - `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)
|
* - `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 currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
|
||||||
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ?
|
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ?
|
||||||
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100);
|
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100);
|
||||||
|
@ -408,7 +415,7 @@ export class StreamManager extends EventDispatcher {
|
||||||
* @hidden
|
* @hidden
|
||||||
*/
|
*/
|
||||||
initializeVideoProperties(video: HTMLVideoElement): void {
|
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
|
// Avoid setting the MediaStream into the srcObject if remote subscription before publishing
|
||||||
if (video.srcObject !== this.stream.getMediaStream()) {
|
if (video.srcObject !== this.stream.getMediaStream()) {
|
||||||
// If srcObject already set don't do it again
|
// If srcObject already set don't do it again
|
||||||
|
@ -492,6 +499,7 @@ export class StreamManager extends EventDispatcher {
|
||||||
*/
|
*/
|
||||||
addPlayEventToFirstVideo() {
|
addPlayEventToFirstVideo() {
|
||||||
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) {
|
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].video.addEventListener('canplay', this.canPlayListener);
|
||||||
this.videos[0].canplayListenerAdded = true;
|
this.videos[0].canplayListenerAdded = true;
|
||||||
}
|
}
|
||||||
|
@ -533,6 +541,7 @@ export class StreamManager extends EventDispatcher {
|
||||||
*/
|
*/
|
||||||
removeSrcObject(streamManagerVideo: StreamManagerVideo) {
|
removeSrcObject(streamManagerVideo: StreamManagerVideo) {
|
||||||
streamManagerVideo.video.srcObject = null;
|
streamManagerVideo.video.srcObject = null;
|
||||||
|
this.deactivateStreamPlayingEventExceptionTimeout();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Private methods */
|
/* Private methods */
|
||||||
|
@ -557,4 +566,28 @@ export class StreamManager extends EventDispatcher {
|
||||||
video.style.webkitTransform = 'unset';
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -39,6 +39,7 @@ export class ConnectionEvent extends Event {
|
||||||
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
|
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
|
||||||
* - "sessionClosedByServer": the Session has been closed by the application
|
* - "sessionClosedByServer": the Session has been closed by the application
|
||||||
* - "networkDisconnect": the remote user network connection has dropped
|
* - "networkDisconnect": the remote user network connection has dropped
|
||||||
|
* - "nodeCrashed": a node has crashed in the server side
|
||||||
*
|
*
|
||||||
* For `connectionCreated` event an empty string
|
* For `connectionCreated` event an empty string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -60,14 +60,14 @@ export abstract class Event {
|
||||||
/**
|
/**
|
||||||
* Prevents the default behavior of the event. The following events have a default behavior:
|
* 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
|
* 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.
|
* by calling [[Subscriber.createVideoElement]]). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event.
|
||||||
*
|
*
|
||||||
* - `streamDestroyed`:
|
* - `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`
|
* - 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.
|
* 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
|
* 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.
|
* by calling [[Subscriber.createVideoElement]]). For every video removed, the Subscriber object will also dispatch a `videoElementDestroyed` event.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
import { Session } from '../../OpenVidu/Session';
|
import { Session } from '../../OpenVidu/Session';
|
||||||
import { Stream } from '../../OpenVidu/Stream';
|
import { Stream } from '../../OpenVidu/Stream';
|
||||||
|
import { Subscriber } from '../../OpenVidu/Subscriber';
|
||||||
import { Event } from './Event';
|
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)
|
* 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.
|
* 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.
|
* [[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)
|
* 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.
|
* 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.
|
* [[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.
|
* 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 {
|
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:
|
* Object affected by the exception. Depending on the [[ExceptionEvent.name]] property:
|
||||||
* - [[Session]]: `ICE_CANDIDATE_ERROR`
|
* - [[Session]]: `ICE_CANDIDATE_ERROR`
|
||||||
* - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
|
* - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
|
||||||
|
* - [[Subscriber]]: `NO_STREAM_PLAYING_EVENT`
|
||||||
*/
|
*/
|
||||||
origin: Session | Stream;
|
origin: Session | Stream | Subscriber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Informative description of the exception
|
* Informative description of the exception
|
||||||
|
@ -86,7 +117,7 @@ export class ExceptionEvent extends Event {
|
||||||
/**
|
/**
|
||||||
* @hidden
|
* @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');
|
super(false, session, 'exception');
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.origin = origin;
|
this.origin = origin;
|
||||||
|
|
|
@ -46,7 +46,7 @@ export class RecordingEvent extends Event {
|
||||||
* - "recordingStoppedByServer": the recording has been gracefully stopped by the application
|
* - "recordingStoppedByServer": the recording has been gracefully stopped by the application
|
||||||
* - "sessionClosedByServer": the Session has been closed 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)
|
* - "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
|
* For 'recordingStarted' empty string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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 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
|
* Session object will dispatch a `reconnected` event. If it fails, Session object will dispatch a SessionDisconnectedEvent
|
||||||
* with reason "networkDisconnect"
|
* 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;
|
reason: string;
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class StreamEvent extends Event {
|
||||||
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
|
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
|
||||||
* - "sessionClosedByServer": the Session has been closed by the application
|
* - "sessionClosedByServer": the Session has been closed by the application
|
||||||
* - "networkDisconnect": the user's network connection has dropped
|
* - "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
|
* For 'streamCreated' empty string
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,7 +21,10 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
|
||||||
/**
|
/**
|
||||||
* Defines the following events:
|
* Defines the following events:
|
||||||
* - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media
|
* - `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
|
* - `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
|
* 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)
|
* [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 100ms)
|
||||||
|
|
|
@ -31,4 +31,5 @@ export interface LocalConnectionOptions {
|
||||||
turnUsername: string;
|
turnUsername: string;
|
||||||
turnCredential: string;
|
turnCredential: string;
|
||||||
version: string;
|
version: string;
|
||||||
|
mediaServer: string;
|
||||||
}
|
}
|
|
@ -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]]
|
* 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
|
* 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;
|
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;
|
||||||
|
|
||||||
|
}
|
|
@ -270,6 +270,10 @@ function JsonRpcClient(configuration) {
|
||||||
pingNextNum = 0;
|
pingNextNum = 0;
|
||||||
usePing();
|
usePing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.getReadyState = function () {
|
||||||
|
return ws.getReadyState();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -66,8 +66,12 @@ function WebSocketWithReconnection(config) {
|
||||||
if (closing) {
|
if (closing) {
|
||||||
Logger.debug("Connection closed by user");
|
Logger.debug("Connection closed by user");
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("Connection closed unexpectecly. Reconnecting...");
|
if (config.ismasternodecrashed()) {
|
||||||
reconnect(MAX_RETRIES, 1);
|
Logger.error("Master Node has crashed. Stopping reconnection process");
|
||||||
|
} else {
|
||||||
|
Logger.debug("Connection closed unexpectedly. Reconnecting...");
|
||||||
|
reconnect(MAX_RETRIES, 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logger.debug("Close callback from previous websocket. Ignoring it");
|
Logger.debug("Close callback from previous websocket. Ignoring it");
|
||||||
|
@ -147,6 +151,10 @@ function WebSocketWithReconnection(config) {
|
||||||
};
|
};
|
||||||
registerMessageHandler();
|
registerMessageHandler();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.getReadyState = () => {
|
||||||
|
return ws.readyState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WebSocketWithReconnection;
|
module.exports = WebSocketWithReconnection;
|
|
@ -37,18 +37,19 @@ export interface WebRtcPeerConfiguration {
|
||||||
video: boolean
|
video: boolean
|
||||||
};
|
};
|
||||||
simulcast: boolean;
|
simulcast: boolean;
|
||||||
onicecandidate: (event: RTCIceCandidate) => void;
|
onIceCandidate: (event: RTCIceCandidate) => void;
|
||||||
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
|
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
|
||||||
iceServers: RTCIceServer[] | undefined;
|
|
||||||
|
iceServers?: RTCIceServer[];
|
||||||
mediaStream?: MediaStream | null;
|
mediaStream?: MediaStream | null;
|
||||||
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
||||||
id?: string;
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebRtcPeer {
|
export class WebRtcPeer {
|
||||||
public pc: RTCPeerConnection;
|
pc: RTCPeerConnection;
|
||||||
public remoteCandidatesQueue: RTCIceCandidate[] = [];
|
remoteCandidatesQueue: RTCIceCandidate[] = [];
|
||||||
public localCandidatesQueue: RTCIceCandidate[] = [];
|
localCandidatesQueue: RTCIceCandidate[] = [];
|
||||||
|
|
||||||
// Same as WebRtcPeerConfiguration but without optional fields.
|
// Same as WebRtcPeerConfiguration but without optional fields.
|
||||||
protected configuration: Required<WebRtcPeerConfiguration>;
|
protected configuration: Required<WebRtcPeerConfiguration>;
|
||||||
|
@ -61,10 +62,15 @@ export class WebRtcPeer {
|
||||||
|
|
||||||
this.configuration = {
|
this.configuration = {
|
||||||
...configuration,
|
...configuration,
|
||||||
iceServers: (!!configuration.iceServers && configuration.iceServers.length > 0) ? configuration.iceServers : freeice(),
|
iceServers:
|
||||||
mediaStream: !!configuration.mediaStream
|
!!configuration.iceServers &&
|
||||||
? configuration.mediaStream
|
configuration.iceServers.length > 0
|
||||||
: null,
|
? configuration.iceServers
|
||||||
|
: freeice(),
|
||||||
|
mediaStream:
|
||||||
|
configuration.mediaStream !== undefined
|
||||||
|
? configuration.mediaStream
|
||||||
|
: null,
|
||||||
mode: !!configuration.mode ? configuration.mode : "sendrecv",
|
mode: !!configuration.mode ? configuration.mode : "sendrecv",
|
||||||
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
|
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
|
||||||
};
|
};
|
||||||
|
@ -74,7 +80,7 @@ export class WebRtcPeer {
|
||||||
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
|
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
|
||||||
if (event.candidate != null) {
|
if (event.candidate != null) {
|
||||||
const candidate: RTCIceCandidate = event.candidate;
|
const candidate: RTCIceCandidate = event.candidate;
|
||||||
this.configuration.onicecandidate(candidate);
|
this.configuration.onIceCandidate(candidate);
|
||||||
if (candidate.candidate !== '') {
|
if (candidate.candidate !== '') {
|
||||||
this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: 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
|
* This method frees the resources used by WebRtcPeer
|
||||||
*/
|
*/
|
||||||
|
@ -310,12 +320,12 @@ export class WebRtcPeer {
|
||||||
// Possible network disconnection
|
// Possible network disconnection
|
||||||
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
|
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
|
||||||
logger.warn(msg1);
|
logger.warn(msg1);
|
||||||
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
||||||
break;
|
break;
|
||||||
case 'failed':
|
case 'failed':
|
||||||
const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"';
|
const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"';
|
||||||
logger.error(msg2);
|
logger.error(msg2);
|
||||||
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
||||||
break;
|
break;
|
||||||
case 'closed':
|
case 'closed':
|
||||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
|
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
|
||||||
|
|
|
@ -139,6 +139,8 @@ public class ProtocolElements {
|
||||||
// ENDTODO
|
// ENDTODO
|
||||||
|
|
||||||
public static final String VIDEODATA_METHOD = "videoData";
|
public static final String VIDEODATA_METHOD = "videoData";
|
||||||
|
|
||||||
|
public static final String ECHO_METHOD = "echo";
|
||||||
|
|
||||||
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
|
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
|
||||||
|
|
||||||
|
@ -150,6 +152,7 @@ public class ProtocolElements {
|
||||||
public static final String PARTICIPANTJOINED_VALUE_PARAM = "value";
|
public static final String PARTICIPANTJOINED_VALUE_PARAM = "value";
|
||||||
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
|
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
|
||||||
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
|
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_RECORD_PARAM = "record";
|
||||||
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
||||||
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>openvidu-java-client</artifactId>
|
<artifactId>openvidu-java-client</artifactId>
|
||||||
<version>2.17.0</version>
|
<version>2.18.0</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>OpenVidu Java Client</name>
|
<name>OpenVidu Java Client</name>
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -33,5 +33,5 @@
|
||||||
"docs": "./generate-docs.sh"
|
"docs": "./generate-docs.sh"
|
||||||
},
|
},
|
||||||
"typings": "lib/index.d.ts",
|
"typings": "lib/index.d.ts",
|
||||||
"version": "2.17.0"
|
"version": "2.18.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -206,10 +206,10 @@ export class OpenVidu {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
reject(error.request);
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// 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) {
|
} else if (error.request) {
|
||||||
// The request was made but no response was received `error.request` is an instance of XMLHttpRequest
|
// 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
|
// in the browser and an instance of http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// 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
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// 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
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// 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
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// 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
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// Something happened in setting up the request that triggered an Error
|
||||||
console.error('Error', error.message);
|
|
||||||
reject(new Error(error.message));
|
reject(new Error(error.message));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -649,10 +647,10 @@ export class OpenVidu {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// 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 {
|
try {
|
||||||
url = new URL(this.hostname);
|
url = new URL(this.hostname);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('URL format incorrect', error);
|
|
||||||
throw new Error('URL format incorrect: ' + error);
|
throw new Error('URL format incorrect: ' + error);
|
||||||
}
|
}
|
||||||
this.host = url.protocol + '//' + url.host;
|
this.host = url.protocol + '//' + url.host;
|
||||||
|
|
|
@ -511,11 +511,9 @@ export class Session {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
|
||||||
reject(new Error(error.request));
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// Something happened in setting up the request that triggered an Error
|
||||||
console.error('Error', error.message);
|
|
||||||
reject(new Error(error.message));
|
reject(new Error(error.message));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -642,11 +640,9 @@ export class Session {
|
||||||
// The request was made but no response was received
|
// The request was made but no response was received
|
||||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||||
// http.ClientRequest in node.js
|
// http.ClientRequest in node.js
|
||||||
console.error(error.request);
|
|
||||||
reject(new Error(error.request));
|
reject(new Error(error.request));
|
||||||
} else {
|
} else {
|
||||||
// Something happened in setting up the request that triggered an Error
|
// Something happened in setting up the request that triggered an Error
|
||||||
console.error('Error', error.message);
|
|
||||||
reject(new Error(error.message));
|
reject(new Error(error.message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
# 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
|
# when a codec can not be forced, transcoding will be allowed
|
||||||
|
# Values: VP8, H264, NONE
|
||||||
# Default value is VP8
|
# Default value is VP8
|
||||||
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||||
|
|
||||||
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
||||||
|
# Values: true | false
|
||||||
# Default value is false
|
# Default value is false
|
||||||
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ services:
|
||||||
#
|
#
|
||||||
# Default Application
|
# Default Application
|
||||||
#
|
#
|
||||||
# Openvidu-Call Version: 2.17.0
|
# Openvidu-Call Version: 2.18.0
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------
|
# --------------------------------------------------------------
|
||||||
app:
|
app:
|
||||||
image: openvidu/openvidu-call:2.18.0-beta8
|
image: openvidu/openvidu-call:2.18.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# This file will be overridden when update OpenVidu Platform
|
# This file will be overridden when update OpenVidu Platform
|
||||||
#
|
#
|
||||||
# Openvidu Version: 2.17.0
|
# Openvidu Version: 2.18.0
|
||||||
#
|
#
|
||||||
# Installation Mode: On Premises
|
# Installation Mode: On Premises
|
||||||
#
|
#
|
||||||
|
@ -22,7 +22,7 @@ version: '3.1'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
openvidu-server:
|
openvidu-server:
|
||||||
image: openvidu/openvidu-server:2.18.0-dev2
|
image: openvidu/openvidu-server:2.18.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
||||||
|
@ -65,7 +65,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: openvidu/openvidu-redis:2.0.0
|
image: openvidu/openvidu-redis:3.0.0
|
||||||
restart: always
|
restart: always
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
@ -75,7 +75,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
coturn:
|
coturn:
|
||||||
image: openvidu/openvidu-coturn:4.0.0-dev2
|
image: openvidu/openvidu-coturn:4.0.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
@ -96,7 +96,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: openvidu/openvidu-proxy:6.0.0-dev1
|
image: openvidu/openvidu-proxy:7.0.0-dev1
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -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
|
# 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
|
# when a codec can not be forced, transcoding will be allowed
|
||||||
|
# Values: VP8, H264, NONE
|
||||||
# Default value is VP8
|
# Default value is VP8
|
||||||
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||||
|
|
||||||
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
||||||
|
# Values: true | false
|
||||||
# Default value is false
|
# Default value is false
|
||||||
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||||
|
|
||||||
|
|
|
@ -9,11 +9,11 @@ services:
|
||||||
#
|
#
|
||||||
# Default Application
|
# Default Application
|
||||||
#
|
#
|
||||||
# Openvidu-Call Version: 2.17.0
|
# Openvidu-Call Version: 2.18.0
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------
|
# --------------------------------------------------------------
|
||||||
app:
|
app:
|
||||||
image: openvidu/openvidu-call:2.18.0-beta8
|
image: openvidu/openvidu-call:2.18.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# This file will be overridden when update OpenVidu Platform
|
# This file will be overridden when update OpenVidu Platform
|
||||||
#
|
#
|
||||||
# Openvidu Version: 2.17.0
|
# Openvidu Version: 2.18.0
|
||||||
#
|
#
|
||||||
# Installation Mode: On Premises
|
# Installation Mode: On Premises
|
||||||
#
|
#
|
||||||
|
@ -22,7 +22,7 @@ version: '3.1'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
openvidu-server:
|
openvidu-server:
|
||||||
image: openvidu/openvidu-server-pro:2.18.0-beta17
|
image: openvidu/openvidu-server-pro:2.18.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
||||||
|
@ -76,7 +76,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: openvidu/openvidu-redis:2.0.0
|
image: openvidu/openvidu-redis:3.0.0
|
||||||
restart: always
|
restart: always
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
@ -86,7 +86,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
coturn:
|
coturn:
|
||||||
image: openvidu/openvidu-coturn:4.0.0-dev2
|
image: openvidu/openvidu-coturn:4.0.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#
|
#
|
||||||
# This docker-compose file coordinates all services of OpenVidu CE Platform.
|
# 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
|
# Installation Mode: On Premises
|
||||||
#
|
#
|
||||||
|
@ -16,7 +16,7 @@ version: '3.1'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
media-node-controller:
|
media-node-controller:
|
||||||
image: openvidu/media-node-controller:4.0.0-dev1
|
image: openvidu/media-node-controller:4.0.0
|
||||||
restart: always
|
restart: always
|
||||||
ulimits:
|
ulimits:
|
||||||
core: -1
|
core: -1
|
||||||
|
|
|
@ -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
|
# 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
|
# when a codec can not be forced, transcoding will be allowed
|
||||||
|
# Values: VP8, H264, NONE
|
||||||
# Default value is VP8
|
# Default value is VP8
|
||||||
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||||
|
|
||||||
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
||||||
|
# Values: true | false
|
||||||
# Default value is false
|
# Default value is false
|
||||||
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||||
|
|
||||||
|
|
|
@ -7,12 +7,12 @@ DEBUG=${DEBUG:-false}
|
||||||
|
|
||||||
OUTPUT=$(mktemp -t openvidu-autodiscover-XXX --suffix .json)
|
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 \
|
--output text \
|
||||||
--filters "Name=instance-state-name,Values=running" \
|
--filters "Name=instance-state-name,Values=running" \
|
||||||
"Name=tag:ov-cluster-member,Values=kms" \
|
"Name=tag:ov-cluster-member,Values=kms" \
|
||||||
"Name=tag:ov-stack-name,Values=${AWS_STACK_NAME}" \
|
"Name=tag:ov-stack-name,Values=${AWS_STACK_NAME}" \
|
||||||
"Name=tag:ov-stack-region,Values=${AWS_DEFAULT_REGION}" \
|
"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] } )'
|
||||||
|
|
|
@ -8,4 +8,4 @@ DEBUG=${DEBUG:-false}
|
||||||
ID=$1
|
ID=$1
|
||||||
[ -z "${ID}" ] && { echo "Must provide instance ID"; exit 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
|
||||||
|
|
|
@ -23,7 +23,7 @@ exit_on_error () {
|
||||||
|
|
||||||
"UnauthorizedOperation")
|
"UnauthorizedOperation")
|
||||||
MSG_COD=$(cat ${ERROUTPUT} | awk -F: '{ print $3 }')
|
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
|
echo -e "Unauthorized " $(cat ${MSG_DEC}) >&2
|
||||||
exit 1
|
exit 1
|
||||||
|
@ -43,21 +43,21 @@ if [[ -n "${CUSTOM_VOLUME_SIZE}" ]]; then
|
||||||
AWS_VOLUME_SIZE="${CUSTOM_VOLUME_SIZE}"
|
AWS_VOLUME_SIZE="${CUSTOM_VOLUME_SIZE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 run-instances \
|
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 run-instances \
|
||||||
--image-id ${AWS_IMAGE_ID} --count 1 \
|
--image-id "${AWS_IMAGE_ID}" --count 1 \
|
||||||
--instance-type ${AWS_INSTANCE_TYPE} \
|
--instance-type "${AWS_INSTANCE_TYPE}" \
|
||||||
--key-name ${AWS_KEY_NAME} \
|
--key-name "${AWS_KEY_NAME}" \
|
||||||
--subnet-id ${AWS_SUBNET_ID} \
|
--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}'}]" \
|
--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}" \
|
--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}'}" \
|
--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
|
# Generating the output
|
||||||
KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
|
KMS_IP=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
|
||||||
KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
|
KMS_ID=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .InstanceId')
|
||||||
|
|
||||||
jq -n \
|
jq -n \
|
||||||
--arg id "${KMS_ID}" \
|
--arg id "${KMS_ID}" \
|
||||||
|
|
|
@ -9,11 +9,11 @@ services:
|
||||||
#
|
#
|
||||||
# Default Application
|
# Default Application
|
||||||
#
|
#
|
||||||
# Openvidu-Call Version: 2.17.0
|
# Openvidu-Call Version: 2.18.0
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------
|
# --------------------------------------------------------------
|
||||||
app:
|
app:
|
||||||
image: openvidu/openvidu-call:2.18.0-beta8
|
image: openvidu/openvidu-call:2.18.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
#
|
#
|
||||||
# This file will be overridden when update OpenVidu Platform
|
# This file will be overridden when update OpenVidu Platform
|
||||||
#
|
#
|
||||||
# Openvidu Version: 2.17.0
|
# Openvidu Version: 2.18.0
|
||||||
#
|
#
|
||||||
# Installation Mode: On Premises
|
# Installation Mode: On Premises
|
||||||
#
|
#
|
||||||
|
@ -22,7 +22,7 @@ version: '3.1'
|
||||||
services:
|
services:
|
||||||
|
|
||||||
openvidu-server:
|
openvidu-server:
|
||||||
image: openvidu/openvidu-server-pro:2.18.0-beta17
|
image: openvidu/openvidu-server-pro:2.18.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
||||||
|
@ -52,7 +52,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
image: openvidu/openvidu-redis:2.0.0
|
image: openvidu/openvidu-redis:3.0.0
|
||||||
restart: always
|
restart: always
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
@ -62,7 +62,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
coturn:
|
coturn:
|
||||||
image: openvidu/openvidu-coturn:4.0.0-dev2
|
image: openvidu/openvidu-coturn:4.0.0
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
environment:
|
environment:
|
||||||
|
@ -84,7 +84,7 @@ services:
|
||||||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||||
|
|
||||||
nginx:
|
nginx:
|
||||||
image: openvidu/openvidu-proxy:6.0.0-dev1
|
image: openvidu/openvidu-proxy:7.0.0-dev1
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
network_mode: host
|
network_mode: host
|
||||||
volumes:
|
volumes:
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
{xframe_options}
|
||||||
|
|
||||||
{app_upstream}
|
{app_upstream}
|
||||||
|
|
||||||
upstream openviduserver {
|
upstream openviduserver {
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
add_header X-Frame-Options SAMEORIGIN;
|
|
@ -1,4 +1,5 @@
|
||||||
add_header X-Frame-Options SAMEORIGIN;
|
{xframe_options}
|
||||||
|
|
||||||
add_header X-Content-Type-Options nosniff;
|
add_header X-Content-Type-Options nosniff;
|
||||||
add_header X-XSS-Protection "1; mode=block";
|
add_header X-XSS-Protection "1; mode=block";
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ CERTIFICATES_CONF="${CERTIFICATES_LIVE_FOLDER}/certificates.conf"
|
||||||
[ -z "${PUBLIC_IP}" ] && export PUBLIC_IP=auto-ipv4
|
[ -z "${PUBLIC_IP}" ] && export PUBLIC_IP=auto-ipv4
|
||||||
[ -z "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all
|
[ -z "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all
|
||||||
[ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all
|
[ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all
|
||||||
|
[ -z "${XFRAME_SAMEORIGIN}" ] && export XFRAME_SAMEORIGIN=false
|
||||||
|
|
||||||
# Show input enviroment variables
|
# Show input enviroment variables
|
||||||
printf "\n ======================================="
|
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/*
|
sed -e '/{app_config}/{r default_nginx_conf/global/app_config_default.conf' -e 'd}' -i /etc/nginx/conf.d/*
|
||||||
fi
|
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
|
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_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/*
|
sed -e '/{deprecated_api_pro}/{r default_nginx_conf/global/pro/deprecated_api_pro.conf' -e 'd}' -i /etc/nginx/conf.d/*
|
||||||
|
|
|
@ -4,7 +4,7 @@ MAINTAINER info@openvidu.io
|
||||||
ARG CHROME_VERSION
|
ARG CHROME_VERSION
|
||||||
|
|
||||||
# Install Chrome
|
# 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 \
|
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 \
|
&& apt install -y ./google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
||||||
&& rm google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
&& rm google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
||||||
|
|
|
@ -14,6 +14,7 @@ RUN apt-get update && apt-get -y upgrade && apt-get install -y \
|
||||||
pulseaudio \
|
pulseaudio \
|
||||||
xvfb \
|
xvfb \
|
||||||
jq \
|
jq \
|
||||||
|
fonts-noto \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Install chrome
|
# Install chrome
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>OpenVidu Server</name>
|
<name>OpenVidu Server</name>
|
||||||
<version>2.17.0</version>
|
<version>2.18.0</version>
|
||||||
<description>OpenVidu Server</description>
|
<description>OpenVidu Server</description>
|
||||||
<url>https://openvidu.io</url>
|
<url>https://openvidu.io</url>
|
||||||
|
|
||||||
|
|
|
@ -7183,6 +7183,11 @@
|
||||||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||||
"dev": true
|
"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": {
|
"json-parse-better-errors": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||||
|
@ -8728,14 +8733,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"openvidu-browser": {
|
"openvidu-browser": {
|
||||||
"version": "2.17.0",
|
"version": "2.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.17.0.tgz",
|
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.18.0.tgz",
|
||||||
"integrity": "sha512-wholLLrs2R1Rf05+QPOH4eIO9LaKO9UvoinGNHU9WoP32L9cU6WeqL0sH3gHhUdiYPqGwTNRPjC3qk/ltjUbDA==",
|
"integrity": "sha512-a9HPAG8p2vG9XGThPUbZWPI5sO1OFmrxKQdCZM3RLmasXQa2Lwj7X1Aicsznb7RtDBg/lD8NdNwAQjm4ZDt2Nw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"freeice": "2.2.2",
|
"freeice": "2.2.2",
|
||||||
"hark": "1.2.3",
|
"hark": "1.2.3",
|
||||||
|
"jsnlog": "2.30.0",
|
||||||
"platform": "1.3.6",
|
"platform": "1.3.6",
|
||||||
"uuid": "8.3.1",
|
"uuid": "8.3.2",
|
||||||
"wolfy87-eventemitter": "5.2.9"
|
"wolfy87-eventemitter": "5.2.9"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -13579,9 +13585,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
"version": "8.3.1",
|
"version": "8.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||||
},
|
},
|
||||||
"validate-npm-package-name": {
|
"validate-npm-package-name": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
|
|
|
@ -1,55 +1,55 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "11.2.4",
|
"@angular/animations": "11.2.4",
|
||||||
"@angular/cdk": "11.2.3",
|
"@angular/cdk": "11.2.3",
|
||||||
"@angular/common": "11.2.4",
|
"@angular/common": "11.2.4",
|
||||||
"@angular/compiler": "11.2.4",
|
"@angular/compiler": "11.2.4",
|
||||||
"@angular/core": "11.2.4",
|
"@angular/core": "11.2.4",
|
||||||
"@angular/flex-layout": "11.0.0-beta.33",
|
"@angular/flex-layout": "11.0.0-beta.33",
|
||||||
"@angular/forms": "11.2.4",
|
"@angular/forms": "11.2.4",
|
||||||
"@angular/material": "11.2.3",
|
"@angular/material": "11.2.3",
|
||||||
"@angular/platform-browser": "11.2.4",
|
"@angular/platform-browser": "11.2.4",
|
||||||
"@angular/platform-browser-dynamic": "11.2.4",
|
"@angular/platform-browser-dynamic": "11.2.4",
|
||||||
"@angular/router": "11.2.4",
|
"@angular/router": "11.2.4",
|
||||||
"core-js": "3.9.1",
|
"core-js": "3.9.1",
|
||||||
"jquery": "3.6.0",
|
"jquery": "3.6.0",
|
||||||
"openvidu-browser": "2.17.0",
|
"openvidu-browser": "2.18.0",
|
||||||
"rxjs": "6.6.6",
|
"rxjs": "6.6.6",
|
||||||
"tslib": "2.1.0",
|
"tslib": "2.1.0",
|
||||||
"zone.js": "0.11.4"
|
"zone.js": "0.11.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "0.1102.3",
|
"@angular-devkit/build-angular": "0.1102.3",
|
||||||
"@angular/cli": "11.2.3",
|
"@angular/cli": "11.2.3",
|
||||||
"@angular/compiler-cli": "11.2.4",
|
"@angular/compiler-cli": "11.2.4",
|
||||||
"@angular/language-service": "11.2.4",
|
"@angular/language-service": "11.2.4",
|
||||||
"@types/jasmine": "3.6.6",
|
"@types/jasmine": "3.6.6",
|
||||||
"@types/node": "14.14.32",
|
"@types/node": "14.14.32",
|
||||||
"codelyzer": "6.0.1",
|
"codelyzer": "6.0.1",
|
||||||
"jasmine-core": "3.6.0",
|
"jasmine-core": "3.6.0",
|
||||||
"jasmine-spec-reporter": "6.0.0",
|
"jasmine-spec-reporter": "6.0.0",
|
||||||
"karma": "6.1.2",
|
"karma": "6.1.2",
|
||||||
"karma-chrome-launcher": "3.1.0",
|
"karma-chrome-launcher": "3.1.0",
|
||||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||||
"karma-jasmine": "4.0.1",
|
"karma-jasmine": "4.0.1",
|
||||||
"karma-jasmine-html-reporter": "1.5.4",
|
"karma-jasmine-html-reporter": "1.5.4",
|
||||||
"protractor": "7.0.0",
|
"protractor": "7.0.0",
|
||||||
"ts-node": "9.1.1",
|
"ts-node": "9.1.1",
|
||||||
"tslint": "6.1.3",
|
"tslint": "6.1.3",
|
||||||
"typescript": "4.1.5"
|
"typescript": "4.1.5"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
|
"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",
|
"build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
|
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"test": "ng test"
|
"test": "ng test"
|
||||||
},
|
},
|
||||||
"version": "0.0.0"
|
"version": "0.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ import io.openvidu.java.client.VideoCodec;
|
||||||
import io.openvidu.server.OpenViduServer;
|
import io.openvidu.server.OpenViduServer;
|
||||||
import io.openvidu.server.cdr.CDREventName;
|
import io.openvidu.server.cdr.CDREventName;
|
||||||
import io.openvidu.server.config.Dotenv.DotenvFormatException;
|
import io.openvidu.server.config.Dotenv.DotenvFormatException;
|
||||||
|
import io.openvidu.server.core.MediaServer;
|
||||||
import io.openvidu.server.recording.RecordingNotification;
|
import io.openvidu.server.recording.RecordingNotification;
|
||||||
import io.openvidu.server.rest.RequestMappings;
|
import io.openvidu.server.rest.RequestMappings;
|
||||||
|
|
||||||
|
@ -246,6 +247,10 @@ public class OpenviduConfig {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MediaServer getMediaServer() {
|
||||||
|
return MediaServer.kurento;
|
||||||
|
}
|
||||||
|
|
||||||
public String getOpenViduRecordingPath() {
|
public String getOpenViduRecordingPath() {
|
||||||
return this.openviduRecordingPath;
|
return this.openviduRecordingPath;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,114 @@ package io.openvidu.server.core;
|
||||||
|
|
||||||
public enum EndReason {
|
public enum EndReason {
|
||||||
|
|
||||||
unsubscribe, unpublish, disconnect, forceUnpublishByUser, forceUnpublishByServer, forceDisconnectByUser,
|
/**
|
||||||
forceDisconnectByServer, lastParticipantLeft, recordingStoppedByServer, sessionClosedByServer, networkDisconnect,
|
* A user called the RPC operation to unsubscribe from a remote stream. Applies
|
||||||
mediaServerDisconnect, mediaServerReconnect, nodeCrashed, openviduServerStopped, automaticStop
|
* 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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package io.openvidu.server.core;
|
||||||
|
|
||||||
|
public enum MediaServer {
|
||||||
|
|
||||||
|
kurento, mediasoup
|
||||||
|
|
||||||
|
}
|
|
@ -153,15 +153,18 @@ public class SessionEventsHandler {
|
||||||
ProtocolElements.PARTICIPANTJOINED_METHOD, notifParams);
|
ProtocolElements.PARTICIPANTJOINED_METHOD, notifParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, participant.getParticipantPublicId());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, participant.getParticipantPublicId());
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_FINALUSERID_PARAM, participant.getFinalUserId());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_FINALUSERID_PARAM, participant.getFinalUserId());
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_CREATEDAT_PARAM, participant.getActiveAt());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_CREATEDAT_PARAM, participant.getActiveAt());
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata());
|
||||||
result.add(ProtocolElements.PARTICIPANTJOINED_VALUE_PARAM, resultArray);
|
result.add(ProtocolElements.PARTICIPANTJOINED_VALUE_PARAM, resultArray);
|
||||||
|
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_SESSION_PARAM, participant.getSessionId());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_SESSION_PARAM, participant.getSessionId());
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_VERSION_PARAM,
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_VERSION_PARAM,
|
||||||
openviduBuildConfig.getOpenViduServerVersion());
|
openviduBuildConfig.getOpenViduServerVersion());
|
||||||
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM,
|
||||||
|
this.openviduConfig.getMediaServer().name());
|
||||||
|
|
||||||
if (participant.getToken() != null) {
|
if (participant.getToken() != null) {
|
||||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
|
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
|
||||||
if (participant.getToken().getRole() != null) {
|
if (participant.getToken().getRole() != null) {
|
||||||
|
@ -638,6 +641,10 @@ public class SessionEventsHandler {
|
||||||
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject());
|
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
|
* This handler must be called before cleaning any sessions or recordings hosted
|
||||||
* by the crashed Media Node
|
* by the crashed Media Node
|
||||||
|
|
|
@ -116,15 +116,8 @@ public abstract class SessionManager {
|
||||||
public abstract void prepareSubscription(Participant participant, String senderPublicId, boolean reconnect,
|
public abstract void prepareSubscription(Participant participant, String senderPublicId, boolean reconnect,
|
||||||
Integer id);
|
Integer id);
|
||||||
|
|
||||||
// TODO: REMOVE ON 2.18.0
|
public abstract void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId,
|
||||||
public abstract void subscribe(Participant participant, String senderName, String sdpAnwser, Integer transactionId,
|
boolean initByServer);
|
||||||
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 unsubscribe(Participant participant, String senderName, Integer transactionId);
|
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,
|
public abstract Participant publishIpcam(Session session, MediaOptions mediaOptions,
|
||||||
ConnectionProperties connectionProperties) throws Exception;
|
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);
|
Integer transactionId);
|
||||||
|
|
||||||
// TODO: REMOVE ON 2.18.0
|
public abstract void reconnectSubscriber(Participant participant, String streamId, String sdpString,
|
||||||
public abstract void reconnectStream2170(Participant participant, String streamId, String sdpOffer,
|
Integer transactionId, boolean initByServer);
|
||||||
Integer transactionId);
|
|
||||||
// END TODO
|
|
||||||
|
|
||||||
public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId)
|
public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId)
|
||||||
throws OpenViduException;
|
throws OpenViduException;
|
||||||
|
@ -194,6 +185,10 @@ public abstract class SessionManager {
|
||||||
public abstract void onVideoData(Participant participant, Integer transactionId, Integer height, Integer width,
|
public abstract void onVideoData(Participant participant, Integer transactionId, Integer height, Integer width,
|
||||||
Boolean videoActive, Boolean audioActive);
|
Boolean videoActive, Boolean audioActive);
|
||||||
|
|
||||||
|
public void onEcho(String participantPrivateId, Integer requestId) {
|
||||||
|
sessionEventsHandler.onEcho(participantPrivateId, requestId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a Session given its id
|
* Returns a Session given its id
|
||||||
*
|
*
|
||||||
|
@ -308,8 +303,10 @@ public abstract class SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Session storeSessionNotActive(String sessionId, SessionProperties sessionProperties) {
|
public Session storeSessionNotActive(String sessionId, SessionProperties sessionProperties) {
|
||||||
Session sessionNotActive = new Session(sessionId, sessionProperties, openviduConfig, recordingManager);
|
Session sessionNotActive = this
|
||||||
return this.storeSessionNotActive(sessionNotActive);
|
.storeSessionNotActive(new Session(sessionId, sessionProperties, openviduConfig, recordingManager));
|
||||||
|
sessionEventsHandler.onSessionCreated(sessionNotActive);
|
||||||
|
return sessionNotActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Session storeSessionNotActive(Session sessionNotActive) {
|
public Session storeSessionNotActive(Session sessionNotActive) {
|
||||||
|
|
|
@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentMap;
|
import java.util.concurrent.ConcurrentMap;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReadWriteLock;
|
import java.util.concurrent.locks.Lock;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
@ -220,240 +220,100 @@ public class KurentoParticipant extends Participant {
|
||||||
}
|
}
|
||||||
|
|
||||||
KurentoParticipant kSender = (KurentoParticipant) sender;
|
KurentoParticipant kSender = (KurentoParticipant) sender;
|
||||||
|
if (kSender.streaming && kSender.getPublisher() != null) {
|
||||||
|
|
||||||
if (kSender.streaming && kSender.getPublisher() != null
|
final Lock closingReadLock = kSender.getPublisher().closingLock.readLock();
|
||||||
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
|
if (closingReadLock.tryLock()) {
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
|
|
||||||
senderName);
|
|
||||||
|
|
||||||
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
CountDownLatch subscriberLatch = new CountDownLatch(1);
|
|
||||||
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
|
SubscriberEndpoint subscriber = initializeSubscriberEndpoint(kSender);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
|
String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher());
|
||||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(),
|
||||||
"Timeout reached when creating subscriber endpoint");
|
sdpOffer);
|
||||||
}
|
log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}",
|
||||||
} catch (InterruptedException e) {
|
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
return sdpOffer;
|
||||||
"Interrupted when creating subscriber endpoint: " + e.getMessage());
|
} catch (KurentoServerException e) {
|
||||||
}
|
log.error("Exception preparing subscriber endpoint for user {}: {}",
|
||||||
if (oldMediaEndpoint != null) {
|
this.getParticipantPublicId(), e.getMessage());
|
||||||
log.warn(
|
this.subscribers.remove(senderName);
|
||||||
"PARTICIPANT {}: Two threads are trying to create at "
|
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
|
||||||
+ "the same time a subscriber endpoint for user {}",
|
|
||||||
this.getParticipantPublicId(), senderName);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (subscriber.getEndpoint() == null) {
|
} finally {
|
||||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
closingReadLock.unlock();
|
||||||
"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);
|
|
||||||
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();
|
final String senderName = sender.getParticipantPublicId();
|
||||||
|
|
||||||
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
|
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
|
||||||
senderName, this.session.getSessionId());
|
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())) {
|
if (senderName.equals(this.getParticipantPublicId())) {
|
||||||
log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", 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");
|
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media");
|
||||||
}
|
}
|
||||||
|
|
||||||
KurentoParticipant kSender = (KurentoParticipant) sender;
|
KurentoParticipant kSender = (KurentoParticipant) sender;
|
||||||
|
if (kSender.streaming && kSender.getPublisher() != null) {
|
||||||
|
|
||||||
if (kSender.streaming && kSender.getPublisher() != null
|
final Lock closingReadLock = kSender.getPublisher().closingLock.readLock();
|
||||||
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
|
if (closingReadLock.tryLock()) {
|
||||||
|
|
||||||
try {
|
|
||||||
final SubscriberEndpoint subscriber = getSubscriber(senderName);
|
|
||||||
if (subscriber.getEndpoint() == null) {
|
|
||||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
subscriber.subscribe(sdpAnswer, kSender.getPublisher());
|
|
||||||
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
|
|
||||||
senderName, this.session.getSessionId());
|
|
||||||
|
|
||||||
if (!silent
|
// If initialized by server SubscriberEndpoint was created on
|
||||||
&& !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
// prepareReceiveMediaFrom. If initialized by client must be created now
|
||||||
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
|
final SubscriberEndpoint subscriber = initByServer ? getSubscriber(senderName)
|
||||||
sender.getParticipantPublicId(), subscriber.createdAt());
|
: initializeSubscriberEndpoint(kSender);
|
||||||
}
|
|
||||||
} 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (subscriber.getEndpoint() == null) {
|
||||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||||
"Unable to create subscriber endpoint");
|
"Unable to create subscriber endpoint");
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
|
String sdpAnswer = subscriber.subscribe(sdpString, kSender.getPublisher());
|
||||||
|
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}",
|
||||||
subscriber.setEndpointName(subscriberEndpointName);
|
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||||
subscriber.getEndpoint().setName(subscriberEndpointName);
|
if (!silent && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
|
||||||
subscriber.setStreamId(kSender.getPublisherStreamId());
|
.equals(this.getParticipantPublicId())) {
|
||||||
|
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
|
||||||
endpointConfig.addEndpointListeners(subscriber, "subscriber");
|
sender.getParticipantPublicId(), subscriber.createdAt());
|
||||||
|
}
|
||||||
} catch (OpenViduException e) {
|
return sdpAnswer;
|
||||||
this.subscribers.remove(senderName);
|
} catch (KurentoServerException e) {
|
||||||
throw 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) {
|
public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason, boolean silent) {
|
||||||
|
@ -461,7 +321,8 @@ public class KurentoParticipant extends Participant {
|
||||||
final PublisherEndpoint pub = senderKurentoParticipant.publisher;
|
final PublisherEndpoint pub = senderKurentoParticipant.publisher;
|
||||||
if (pub != null) {
|
if (pub != null) {
|
||||||
try {
|
try {
|
||||||
if (pub.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
|
final Lock closingWriteLock = pub.closingLock.writeLock();
|
||||||
|
if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) {
|
||||||
try {
|
try {
|
||||||
log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(),
|
log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(),
|
||||||
senderName);
|
senderName);
|
||||||
|
@ -478,7 +339,7 @@ public class KurentoParticipant extends Participant {
|
||||||
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
pub.closingLock.writeLock().unlock();
|
closingWriteLock.unlock();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.error(
|
log.error(
|
||||||
|
@ -585,15 +446,66 @@ public class KurentoParticipant extends Participant {
|
||||||
return this.getParticipantPublicId() + "_" + senderParticipant.getPublisherStreamId();
|
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) {
|
private void releasePublisherEndpoint(EndReason reason, Long kmsDisconnectionTime) {
|
||||||
if (publisher != null && publisher.getEndpoint() != null) {
|
if (publisher != null && publisher.getEndpoint() != null) {
|
||||||
final ReadWriteLock closingLock = publisher.closingLock;
|
|
||||||
try {
|
try {
|
||||||
if (closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
|
final Lock closingWriteLock = publisher.closingLock.writeLock();
|
||||||
|
if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) {
|
||||||
try {
|
try {
|
||||||
this.releasePublisherEndpointAux(reason, kmsDisconnectionTime);
|
this.releasePublisherEndpointAux(reason, kmsDisconnectionTime);
|
||||||
} finally {
|
} finally {
|
||||||
closingLock.writeLock().unlock();
|
closingWriteLock.unlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
|
|
@ -31,6 +31,8 @@ import java.util.concurrent.TimeUnit;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.kurento.client.GenericMediaElement;
|
import org.kurento.client.GenericMediaElement;
|
||||||
import org.kurento.client.IceCandidate;
|
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.FinalUser;
|
||||||
import io.openvidu.server.core.IdentifierPrefixes;
|
import io.openvidu.server.core.IdentifierPrefixes;
|
||||||
import io.openvidu.server.core.MediaOptions;
|
import io.openvidu.server.core.MediaOptions;
|
||||||
|
import io.openvidu.server.core.MediaServer;
|
||||||
import io.openvidu.server.core.Participant;
|
import io.openvidu.server.core.Participant;
|
||||||
import io.openvidu.server.core.Session;
|
import io.openvidu.server.core.Session;
|
||||||
import io.openvidu.server.core.SessionManager;
|
import io.openvidu.server.core.SessionManager;
|
||||||
|
@ -111,9 +114,13 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
|
|
||||||
if (sessionNotActive == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) {
|
if (sessionNotActive == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) {
|
||||||
// Insecure user directly call joinRoom RPC method, without REST API use
|
// Insecure user directly call joinRoom RPC method, without REST API use
|
||||||
sessionNotActive = new Session(sessionId, new SessionProperties.Builder()
|
SessionProperties.Builder builder = new SessionProperties.Builder().mediaMode(MediaMode.ROUTED)
|
||||||
.mediaMode(MediaMode.ROUTED).recordingMode(RecordingMode.ALWAYS).build(), openviduConfig,
|
.recordingMode(RecordingMode.ALWAYS);
|
||||||
recordingManager);
|
// forcedVideoCodec to NONE if mediasoup
|
||||||
|
if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
|
||||||
|
builder.forcedVideoCodec(VideoCodec.NONE);
|
||||||
|
}
|
||||||
|
sessionNotActive = new Session(sessionId, builder.build(), openviduConfig, recordingManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -385,7 +392,7 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
// Modify sdp if forced codec is defined
|
// Modify sdp if forced codec is defined
|
||||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||||
kurentoOptions.sdpOffer = sdpMunging.forceCodec(kurentoOptions.sdpOffer, participant, true, false,
|
kurentoOptions.sdpOffer = sdpMunging.forceCodec(kurentoOptions.sdpOffer, participant, true, false,
|
||||||
isTranscodingAllowed, forcedVideoCodec, false);
|
isTranscodingAllowed, forcedVideoCodec);
|
||||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
||||||
WebrtcDebugEventOperation.publish, WebrtcDebugEventType.sdpOfferMunged, kurentoOptions.sdpOffer));
|
WebrtcDebugEventOperation.publish, WebrtcDebugEventType.sdpOfferMunged, kurentoOptions.sdpOffer));
|
||||||
}
|
}
|
||||||
|
@ -573,7 +580,7 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
// Modify server's SDPOffer if forced codec is defined
|
// Modify server's SDPOffer if forced codec is defined
|
||||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||||
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, false, false, isTranscodingAllowed,
|
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, false, false, isTranscodingAllowed,
|
||||||
forcedVideoCodec, true);
|
forcedVideoCodec);
|
||||||
|
|
||||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
||||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
||||||
|
@ -594,12 +601,12 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void subscribe(Participant participant, String senderName, String sdpAnswer, Integer transactionId,
|
public void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId,
|
||||||
boolean is2180) {
|
boolean initByServer) {
|
||||||
Session session = null;
|
Session session = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpAnswer={} ({})", senderName, sdpAnswer,
|
log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpString={} ({})", senderName, sdpString,
|
||||||
participant.getParticipantPublicId());
|
participant.getParticipantPublicId());
|
||||||
|
|
||||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||||
|
@ -625,48 +632,44 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
|
|
||||||
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(senderParticipant);
|
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(senderParticipant);
|
||||||
|
|
||||||
// TODO: REMOVE ON 2.18.0
|
if (initByServer) {
|
||||||
if (is2180) {
|
|
||||||
|
// 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,
|
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);
|
sessionEventsHandler.onSubscribe(participant, session, transactionId, null);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
// Client initiated negotiation. sdpString is the SDP Offer of the client
|
||||||
|
|
||||||
boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed();
|
boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed();
|
||||||
VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec();
|
VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec();
|
||||||
|
String sdpOffer = sdpString;
|
||||||
|
|
||||||
// Modify sdp if forced codec is defined
|
// Modify sdp if forced codec is defined
|
||||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||||
sdpAnswer = sdpMunging.forceCodec(sdpAnswer, participant, false, false, isTranscodingAllowed,
|
sdpOffer = sdpMunging.forceCodec(sdpString, participant, false, false, isTranscodingAllowed,
|
||||||
forcedVideoCodec, false);
|
forcedVideoCodec);
|
||||||
|
|
||||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
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);
|
String sdpAnswer = kParticipant.receiveMedia(senderParticipant, sdpOffer, false, false);
|
||||||
if (finalSdpAnswer == null) {
|
if (sdpAnswer == null) {
|
||||||
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||||
"Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId()
|
"Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId()
|
||||||
+ "' to '" + senderName + "'");
|
+ "' to '" + senderName + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
||||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, finalSdpAnswer));
|
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||||
sessionEventsHandler.onSubscribe(participant, session, finalSdpAnswer, transactionId, null);
|
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) {
|
} catch (OpenViduException e) {
|
||||||
log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, 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(),
|
log.info("No session '{}' exists yet. Created one on KMS '{}' with ip '{}'", session.getSessionId(),
|
||||||
kms.getId(), kms.getIp());
|
kms.getId(), kms.getIp());
|
||||||
|
|
||||||
sessionEventsHandler.onSessionCreated(session);
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1176,184 +1178,127 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
return kParticipant;
|
return kParticipant;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: REMOVE ON 2.18.0
|
|
||||||
@Override
|
@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;
|
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||||
KurentoSession kSession = kParticipant.getSession();
|
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();
|
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
|
||||||
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
|
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
|
||||||
|
|
||||||
boolean sdpOfferHasBeenMunged = false;
|
|
||||||
String originalSdpOffer = sdpOffer;
|
|
||||||
|
|
||||||
// Modify sdp if forced codec is defined
|
// Modify sdp if forced codec is defined
|
||||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||||
sdpOfferHasBeenMunged = true;
|
return sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
|
||||||
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
|
forcedVideoCodec);
|
||||||
forcedVideoCodec, false);
|
|
||||||
}
|
}
|
||||||
|
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,
|
String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, true);
|
||||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer));
|
|
||||||
if (sdpOfferHasBeenMunged) {
|
|
||||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
|
||||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reconnect publisher
|
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client,
|
||||||
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
|
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, sdpOffer));
|
||||||
.getMediaOptions();
|
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
|
private void reconnectSubscriber(KurentoSession kSession, KurentoParticipant kParticipant, String streamId,
|
||||||
PublisherEndpoint publisher = kParticipant.getPublisher();
|
String sdpString, Integer transactionId, boolean initByServer) {
|
||||||
final PassThrough passThru = publisher.disconnectFromPassThrough();
|
|
||||||
|
|
||||||
// 2) Destroy the broken PublisherEndpoint and nothing else
|
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
|
||||||
publisher.cancelStatsLoop.set(true);
|
if (senderPrivateId != null) {
|
||||||
kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint());
|
|
||||||
|
|
||||||
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
|
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
|
||||||
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
|
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
|
||||||
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,
|
if (initByServer) {
|
||||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
|
||||||
|
|
||||||
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
|
// Server initiated negotiation
|
||||||
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
|
|
||||||
new HashSet<Participant>(), transactionId, null);
|
|
||||||
|
|
||||||
} else {
|
final String sdpAnswer = sdpString;
|
||||||
|
|
||||||
// Reconnect subscriber
|
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||||
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
|
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||||
if (senderPrivateId != null) {
|
|
||||||
|
|
||||||
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
|
kParticipant.receiveMedia(sender, sdpAnswer, true, true);
|
||||||
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
|
|
||||||
|
|
||||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
|
||||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer,
|
kParticipant.getParticipantPublicId(), sdpAnswer);
|
||||||
originalSdpOffer));
|
|
||||||
if (sdpOfferHasBeenMunged) {
|
sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null);
|
||||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
|
||||||
|
} 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,
|
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOfferMunged,
|
||||||
sdpOffer));
|
sdpOffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
String sdpAnswer = kParticipant.receiveMediaFrom2170(sender, sdpOffer, true);
|
kParticipant.cancelReceivingMedia(sender, null, true);
|
||||||
|
String sdpAnswer = kParticipant.receiveMedia(sender, sdpOffer, true, false);
|
||||||
if (sdpAnswer == null) {
|
if (sdpAnswer == null) {
|
||||||
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||||
"Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'");
|
"Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
|
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));
|
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||||
|
|
||||||
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
|
sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null);
|
||||||
} else {
|
|
||||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
|
||||||
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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 {
|
} else {
|
||||||
|
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
||||||
// Reconnect subscriber
|
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
|
||||||
|
|
||||||
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() + "'");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1444,4 +1389,11 @@ public class KurentoSessionManager extends SessionManager {
|
||||||
return lessLoadedKms;
|
return lessLoadedKms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
super.close();
|
||||||
|
this.kmsManager.closeAllKurentoClients();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,31 +58,24 @@ public class SubscriberEndpoint extends MediaEndpoint {
|
||||||
offerOptions.setOfferToReceiveVideo(publisher.getMediaOptions().hasVideo());
|
offerOptions.setOfferToReceiveVideo(publisher.getMediaOptions().hasVideo());
|
||||||
String sdpOffer = generateOffer(offerOptions);
|
String sdpOffer = generateOffer(offerOptions);
|
||||||
|
|
||||||
|
gatherCandidates();
|
||||||
return sdpOffer;
|
return sdpOffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized String subscribe(String sdpAnswer, PublisherEndpoint publisher) {
|
public synchronized String subscribe(String sdpString, PublisherEndpoint publisher) {
|
||||||
// TODO: REMOVE ON 2.18.0
|
if (this.publisherStreamId == null) {
|
||||||
if (this.createdAt == null) {
|
// Client initiated negotiation
|
||||||
// 2.17.0
|
|
||||||
registerOnIceCandidateEventListener(publisher.getOwner().getParticipantPublicId());
|
registerOnIceCandidateEventListener(publisher.getOwner().getParticipantPublicId());
|
||||||
this.createdAt = System.currentTimeMillis();
|
this.createdAt = System.currentTimeMillis();
|
||||||
String realSdpAnswer = processOffer(sdpAnswer);
|
String realSdpAnswer = processOffer(sdpString);
|
||||||
gatherCandidates();
|
gatherCandidates();
|
||||||
publisher.connect(this.getEndpoint(), false);
|
publisher.connect(this.getEndpoint(), false);
|
||||||
this.publisherStreamId = publisher.getStreamId();
|
this.publisherStreamId = publisher.getStreamId();
|
||||||
return realSdpAnswer;
|
return realSdpAnswer;
|
||||||
} else {
|
} else {
|
||||||
// 2.18.0
|
// Server initiated negotiation
|
||||||
processAnswer(sdpAnswer);
|
return processAnswer(sdpString);
|
||||||
gatherCandidates();
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
// END TODO
|
|
||||||
|
|
||||||
// TODO: UNCOMMENT ON 2.18.0
|
|
||||||
// processAnswer(sdpAnswer);
|
|
||||||
// END TODO
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -31,7 +31,6 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.annotation.PreDestroy;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener;
|
import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener;
|
||||||
|
@ -343,8 +342,7 @@ public abstract class KmsManager {
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
protected abstract void postConstructInitKurentoClients();
|
protected abstract void postConstructInitKurentoClients();
|
||||||
|
|
||||||
@PreDestroy
|
public void closeAllKurentoClients() {
|
||||||
public void close() {
|
|
||||||
log.info("Closing all KurentoClients");
|
log.info("Closing all KurentoClients");
|
||||||
this.kmss.values().forEach(kms -> {
|
this.kmss.values().forEach(kms -> {
|
||||||
if (kms.getKurentoClientReconnectTimer() != null) {
|
if (kms.getKurentoClientReconnectTimer() != null) {
|
||||||
|
|
|
@ -78,12 +78,18 @@ public class Recording {
|
||||||
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
|
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
|
||||||
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
|
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
|
||||||
if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) {
|
if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) {
|
||||||
builder.resolution(json.get("resolution").getAsString());
|
if (json.has("resolution")) {
|
||||||
builder.frameRate(json.get("frameRate").getAsInt());
|
builder.resolution(json.get("resolution").getAsString());
|
||||||
RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
|
}
|
||||||
builder.recordingLayout(recordingLayout);
|
if (json.has("frameRate")) {
|
||||||
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
|
builder.frameRate(json.get("frameRate").getAsInt());
|
||||||
builder.customLayout(json.get("customLayout").getAsString());
|
}
|
||||||
|
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();
|
this.recordingProperties = builder.build();
|
||||||
|
|
|
@ -64,6 +64,7 @@ import io.openvidu.java.client.RecordingProperties;
|
||||||
import io.openvidu.server.cdr.CallDetailRecord;
|
import io.openvidu.server.cdr.CallDetailRecord;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
|
import io.openvidu.server.core.MediaServer;
|
||||||
import io.openvidu.server.core.Participant;
|
import io.openvidu.server.core.Participant;
|
||||||
import io.openvidu.server.core.Session;
|
import io.openvidu.server.core.Session;
|
||||||
import io.openvidu.server.core.SessionEventsHandler;
|
import io.openvidu.server.core.SessionEventsHandler;
|
||||||
|
@ -768,6 +769,8 @@ public class RecordingManager {
|
||||||
// Check Kurento Media Server write permissions in recording path
|
// Check Kurento Media Server write permissions in recording path
|
||||||
if (this.kmsManager.getKmss().isEmpty()) {
|
if (this.kmsManager.getKmss().isEmpty()) {
|
||||||
log.warn("No KMSs were defined in KMS_URIS array. Recording path check aborted");
|
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 {
|
} else {
|
||||||
Kms kms = null;
|
Kms kms = null;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -17,12 +17,12 @@
|
||||||
|
|
||||||
package io.openvidu.server.rest;
|
package io.openvidu.server.rest;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
@RestController
|
@Controller
|
||||||
@CrossOrigin
|
@CrossOrigin
|
||||||
@RequestMapping(RequestMappings.ACCEPT_CERTIFICATE)
|
@RequestMapping(RequestMappings.ACCEPT_CERTIFICATE)
|
||||||
public class CertificateRestController {
|
public class CertificateRestController {
|
||||||
|
|
|
@ -65,6 +65,7 @@ import io.openvidu.java.client.VideoCodec;
|
||||||
import io.openvidu.server.config.OpenviduConfig;
|
import io.openvidu.server.config.OpenviduConfig;
|
||||||
import io.openvidu.server.core.EndReason;
|
import io.openvidu.server.core.EndReason;
|
||||||
import io.openvidu.server.core.IdentifierPrefixes;
|
import io.openvidu.server.core.IdentifierPrefixes;
|
||||||
|
import io.openvidu.server.core.MediaServer;
|
||||||
import io.openvidu.server.core.Participant;
|
import io.openvidu.server.core.Participant;
|
||||||
import io.openvidu.server.core.Session;
|
import io.openvidu.server.core.Session;
|
||||||
import io.openvidu.server.core.SessionManager;
|
import io.openvidu.server.core.SessionManager;
|
||||||
|
@ -120,7 +121,7 @@ public class SessionRestController {
|
||||||
}
|
}
|
||||||
|
|
||||||
Session sessionNotActive = sessionManager.storeSessionNotActive(sessionId, sessionProperties);
|
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());
|
.map(Session::getSessionId).collect(Collectors.toList()).toString());
|
||||||
|
|
||||||
return new ResponseEntity<>(sessionNotActive.toJson(false, false).toString(), RestUtils.getResponseHeaders(),
|
return new ResponseEntity<>(sessionNotActive.toJson(false, false).toString(), RestUtils.getResponseHeaders(),
|
||||||
|
@ -756,10 +757,15 @@ public class SessionRestController {
|
||||||
}
|
}
|
||||||
builder = builder.customSessionId(customSessionId);
|
builder = builder.customSessionId(customSessionId);
|
||||||
}
|
}
|
||||||
if (forcedVideoCodec != null) {
|
// forcedVideoCodec to NONE if mediasoup
|
||||||
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
|
if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
|
||||||
|
builder = builder.forcedVideoCodec(VideoCodec.NONE);
|
||||||
} else {
|
} else {
|
||||||
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
|
if (forcedVideoCodec != null) {
|
||||||
|
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
|
||||||
|
} else {
|
||||||
|
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (allowTranscoding != null) {
|
if (allowTranscoding != null) {
|
||||||
builder = builder.allowTranscoding(allowTranscoding);
|
builder = builder.allowTranscoding(allowTranscoding);
|
||||||
|
|
|
@ -172,6 +172,9 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
case ProtocolElements.VIDEODATA_METHOD:
|
case ProtocolElements.VIDEODATA_METHOD:
|
||||||
updateVideoData(rpcConnection, request);
|
updateVideoData(rpcConnection, request);
|
||||||
break;
|
break;
|
||||||
|
case ProtocolElements.ECHO_METHOD:
|
||||||
|
echo(rpcConnection, request);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
log.error("Unrecognized request {}", request);
|
log.error("Unrecognized request {}", request);
|
||||||
break;
|
break;
|
||||||
|
@ -355,18 +358,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
|
|
||||||
String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
|
String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
|
||||||
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
|
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
|
||||||
boolean reconnect = false;
|
boolean reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
|
||||||
|
|
||||||
// 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
|
|
||||||
|
|
||||||
sessionManager.prepareSubscription(participant, senderPublicId, reconnect, request.getId());
|
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 senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
|
||||||
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
|
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
|
||||||
|
|
||||||
// TODO: REMOVE ON 2.18.0
|
|
||||||
if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM)) {
|
if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM)) {
|
||||||
// 2.17.0: initiative held by browser when subscribing
|
// Client initiated negotiation (comes with SDP Offer)
|
||||||
// The request comes with an SDPOffer
|
|
||||||
String sdpOffer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM);
|
String sdpOffer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM);
|
||||||
sessionManager.subscribe(participant, senderPublicId, sdpOffer, request.getId(), false);
|
sessionManager.subscribe(participant, senderPublicId, sdpOffer, request.getId(), false);
|
||||||
} else if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM)) {
|
} else if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM)) {
|
||||||
// 2.18.0: initiative held by server when subscribing
|
// Server initiated negotiation (comes with SDP Answer)
|
||||||
// This is the final call after prepareReceiveVidoFrom, comes with SDPAnswer
|
|
||||||
String sdpAnswer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM);
|
String sdpAnswer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM);
|
||||||
sessionManager.subscribe(participant, senderPublicId, sdpAnswer, request.getId(), true);
|
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) {
|
private void unsubscribeFromVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
|
||||||
|
@ -667,40 +646,28 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
||||||
} catch (OpenViduException e) {
|
} catch (OpenViduException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String streamId = getStringParam(request, ProtocolElements.RECONNECTSTREAM_STREAM_PARAM);
|
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)) {
|
if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM)) {
|
||||||
// 2.17.0
|
sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM)) {
|
} else if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM)) {
|
||||||
// 2.18.0
|
sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
|
||||||
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
|
|
||||||
|
|
||||||
// TODO: UNCOMMENT ON 2.18.0
|
try {
|
||||||
/*
|
if (isPublisher) {
|
||||||
* String sdpString = getStringParam(request,
|
sessionManager.reconnectPublisher(participant, streamId, sdpString, request.getId());
|
||||||
* ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); try {
|
} else {
|
||||||
* sessionManager.reconnectStream(participant, streamId, sdpString,
|
boolean initByServer = request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
|
||||||
* request.getId()); } catch (OpenViduException e) {
|
sessionManager.reconnectSubscriber(participant, streamId, sdpString, request.getId(), initByServer);
|
||||||
* this.notificationService.sendErrorResponse(participant.
|
}
|
||||||
* getParticipantPrivateId(), request.getId(), new JsonObject(), e); }
|
} catch (OpenViduException e) {
|
||||||
*/
|
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
|
||||||
// END TODO
|
new JsonObject(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateVideoData(RpcConnection rpcConnection, Request<JsonObject> request) {
|
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) {
|
public void leaveRoomAfterConnClosed(String participantPrivateId, EndReason reason) {
|
||||||
try {
|
try {
|
||||||
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,
|
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,
|
||||||
|
|
|
@ -68,7 +68,7 @@ public class SDPMunging {
|
||||||
* ordering of formats. Browsers (tested with Chrome 84) honor this change and
|
* 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.
|
* 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();
|
String codecStr = codec.name();
|
||||||
log.info("[setCodecPreference] codec: {}", codecStr);
|
log.info("[setCodecPreference] codec: {}", codecStr);
|
||||||
|
|
||||||
|
@ -156,9 +156,8 @@ public class SDPMunging {
|
||||||
lines[sl] = newLine.toString().trim();
|
lines[sl] = newLine.toString().trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyHeavyMunging) {
|
lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines);
|
||||||
lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines);
|
|
||||||
}
|
|
||||||
return String.join("\r\n", lines) + "\r\n";
|
return String.join("\r\n", lines) + "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,8 +165,7 @@ public class SDPMunging {
|
||||||
* Return a SDP modified to force a specific codec
|
* Return a SDP modified to force a specific codec
|
||||||
*/
|
*/
|
||||||
public String forceCodec(String sdp, Participant participant, boolean isPublisher, boolean isReconnecting,
|
public String forceCodec(String sdp, Participant participant, boolean isPublisher, boolean isReconnecting,
|
||||||
boolean isTranscodingAllowed, VideoCodec forcedVideoCodec, boolean applyHeavyMunging)
|
boolean isTranscodingAllowed, VideoCodec forcedVideoCodec) throws OpenViduException {
|
||||||
throws OpenViduException {
|
|
||||||
try {
|
try {
|
||||||
if (supportedVideoCodecs.contains(forcedVideoCodec)) {
|
if (supportedVideoCodecs.contains(forcedVideoCodec)) {
|
||||||
String mungedSdpOffer;
|
String mungedSdpOffer;
|
||||||
|
@ -178,7 +176,7 @@ public class SDPMunging {
|
||||||
participant.getParticipantPublicId(), participant.getSessionId(), isPublisher, !isPublisher,
|
participant.getParticipantPublicId(), participant.getSessionId(), isPublisher, !isPublisher,
|
||||||
isReconnecting, sdp);
|
isReconnecting, sdp);
|
||||||
|
|
||||||
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp, applyHeavyMunging);
|
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp);
|
||||||
|
|
||||||
log.debug(
|
log.debug(
|
||||||
"PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'."
|
"PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'."
|
||||||
|
|
|
@ -88,7 +88,7 @@ public class SDPMungingTest {
|
||||||
|
|
||||||
private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException {
|
private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException {
|
||||||
this.oldSdp = getSdpFile(sdpNameFile);
|
this.oldSdp = getSdpFile(sdpNameFile);
|
||||||
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp, false);
|
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp);
|
||||||
this.forceCodecPayloads = new ArrayList<>();
|
this.forceCodecPayloads = new ArrayList<>();
|
||||||
|
|
||||||
// Get all Payload-Type for video Codec
|
// Get all Payload-Type for video Codec
|
||||||
|
|
|
@ -344,6 +344,13 @@ public class OpenViduEventManager {
|
||||||
return dimension;
|
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) {
|
private boolean hasAudioTracks(WebElement videoElement, String parentSelector) {
|
||||||
String script = "return ((document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ")
|
String script = "return ((document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ")
|
||||||
+ "#" + videoElement.getAttribute("id") + "').srcObject.getAudioTracks().length > 0)"
|
+ "#" + videoElement.getAttribute("id") + "').srcObject.getAudioTracks().length > 0)"
|
||||||
|
|
|
@ -481,6 +481,38 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
||||||
gracefullyLeaveParticipants(4);
|
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
|
@Test
|
||||||
@DisplayName("Subscribe Unsubscribe")
|
@DisplayName("Subscribe Unsubscribe")
|
||||||
void subscribeUnsubscribeTest() throws Exception {
|
void subscribeUnsubscribeTest() throws Exception {
|
||||||
|
|
|
@ -1,49 +1,49 @@
|
||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "8.2.14",
|
"@angular/animations": "8.2.14",
|
||||||
"@angular/cdk": "8.2.3",
|
"@angular/cdk": "8.2.3",
|
||||||
"@angular/common": "8.2.14",
|
"@angular/common": "8.2.14",
|
||||||
"@angular/compiler": "8.2.14",
|
"@angular/compiler": "8.2.14",
|
||||||
"@angular/core": "8.2.14",
|
"@angular/core": "8.2.14",
|
||||||
"@angular/flex-layout": "8.0.0-beta.27",
|
"@angular/flex-layout": "8.0.0-beta.27",
|
||||||
"@angular/forms": "8.2.14",
|
"@angular/forms": "8.2.14",
|
||||||
"@angular/http": "7.2.15",
|
"@angular/http": "7.2.15",
|
||||||
"@angular/material": "8.2.3",
|
"@angular/material": "8.2.3",
|
||||||
"@angular/platform-browser": "8.2.14",
|
"@angular/platform-browser": "8.2.14",
|
||||||
"@angular/platform-browser-dynamic": "8.2.14",
|
"@angular/platform-browser-dynamic": "8.2.14",
|
||||||
"@angular/router": "8.2.14",
|
"@angular/router": "8.2.14",
|
||||||
"colormap": "2.3.1",
|
"colormap": "2.3.1",
|
||||||
"core-js": "3.4.7",
|
"core-js": "3.4.7",
|
||||||
"hammerjs": "2.0.8",
|
"hammerjs": "2.0.8",
|
||||||
"json-stringify-safe": "^5.0.1",
|
"json-stringify-safe": "^5.0.1",
|
||||||
"openvidu-browser": "2.17.0",
|
"openvidu-browser": "2.18.0",
|
||||||
"openvidu-node-client": "2.17.0",
|
"openvidu-node-client": "2.18.0",
|
||||||
"rxjs": "6.5.3",
|
"rxjs": "6.5.3",
|
||||||
"zone.js": "0.10.2"
|
"zone.js": "0.10.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "0.803.20",
|
"@angular-devkit/build-angular": "0.803.20",
|
||||||
"@angular/cli": "8.3.20",
|
"@angular/cli": "8.3.20",
|
||||||
"@angular/compiler-cli": "8.2.14",
|
"@angular/compiler-cli": "8.2.14",
|
||||||
"@angular/language-service": "8.2.14",
|
"@angular/language-service": "8.2.14",
|
||||||
"@types/jasmine": "3.5.0",
|
"@types/jasmine": "3.5.0",
|
||||||
"@types/jasminewd2": "2.0.8",
|
"@types/jasminewd2": "2.0.8",
|
||||||
"@types/node": "12.12.14",
|
"@types/node": "12.12.14",
|
||||||
"codelyzer": "5.2.0",
|
"codelyzer": "5.2.0",
|
||||||
"ts-node": "8.5.4",
|
"ts-node": "8.5.4",
|
||||||
"tslint": "5.20.1",
|
"tslint": "5.20.1",
|
||||||
"typescript": "3.5.3"
|
"typescript": "3.5.3"
|
||||||
},
|
},
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"name": "openvidu-testapp",
|
"name": "openvidu-testapp",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"e2e": "ng e2e",
|
"e2e": "ng e2e",
|
||||||
"lint": "ng lint",
|
"lint": "ng lint",
|
||||||
"ng": "ng",
|
"ng": "ng",
|
||||||
"start": "ng serve",
|
"start": "ng serve",
|
||||||
"test": "ng test"
|
"test": "ng test"
|
||||||
},
|
},
|
||||||
"version": "2.17.0"
|
"version": "2.18.0"
|
||||||
}
|
}
|
|
@ -6,7 +6,7 @@ import {
|
||||||
import {
|
import {
|
||||||
OpenVidu, Session, Subscriber, Publisher, Event, StreamEvent, ConnectionEvent,
|
OpenVidu, Session, Subscriber, Publisher, Event, StreamEvent, ConnectionEvent,
|
||||||
SessionDisconnectedEvent, SignalEvent, RecordingEvent,
|
SessionDisconnectedEvent, SignalEvent, RecordingEvent,
|
||||||
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent
|
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent, ExceptionEvent
|
||||||
} from 'openvidu-browser';
|
} from 'openvidu-browser';
|
||||||
import {
|
import {
|
||||||
OpenVidu as OpenViduAPI,
|
OpenVidu as OpenViduAPI,
|
||||||
|
@ -138,7 +138,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
publisherStartSpeaking: false,
|
publisherStartSpeaking: false,
|
||||||
publisherStopSpeaking: false,
|
publisherStopSpeaking: false,
|
||||||
reconnecting: true,
|
reconnecting: true,
|
||||||
reconnected: true
|
reconnected: true,
|
||||||
|
exception: true
|
||||||
};
|
};
|
||||||
|
|
||||||
// Session properties dialog
|
// Session properties dialog
|
||||||
|
@ -247,8 +248,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
signal: false,
|
signal: false,
|
||||||
publisherStartSpeaking: true,
|
publisherStartSpeaking: true,
|
||||||
publisherStopSpeaking: true,
|
publisherStopSpeaking: true,
|
||||||
reconnecting: true,
|
reconnecting: false,
|
||||||
reconnected: true
|
reconnected: false,
|
||||||
|
exception: false
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
this.session.connect(token, this.clientData)
|
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() {
|
syncInitPublisher() {
|
||||||
|
@ -665,7 +676,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
publisherStartSpeaking: this.sessionEvents.publisherStartSpeaking,
|
publisherStartSpeaking: this.sessionEvents.publisherStartSpeaking,
|
||||||
publisherStopSpeaking: this.sessionEvents.publisherStopSpeaking,
|
publisherStopSpeaking: this.sessionEvents.publisherStopSpeaking,
|
||||||
reconnecting: this.sessionEvents.reconnecting,
|
reconnecting: this.sessionEvents.reconnecting,
|
||||||
reconnected: this.sessionEvents.reconnected
|
reconnected: this.sessionEvents.reconnected,
|
||||||
|
exception: this.sessionEvents.exception
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(EventsDialogComponent, {
|
const dialogRef = this.dialog.open(EventsDialogComponent, {
|
||||||
|
@ -699,7 +711,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
||||||
publisherStartSpeaking: result.publisherStartSpeaking,
|
publisherStartSpeaking: result.publisherStartSpeaking,
|
||||||
publisherStopSpeaking: result.publisherStopSpeaking,
|
publisherStopSpeaking: result.publisherStopSpeaking,
|
||||||
reconnecting: result.reconnecting,
|
reconnecting: result.reconnecting,
|
||||||
reconnected: result.reconnected
|
reconnected: result.reconnected,
|
||||||
|
exception: result.exception
|
||||||
};
|
};
|
||||||
document.getElementById('session-events-btn-' + this.index).classList.remove('cdk-program-focused');
|
document.getElementById('session-events-btn-' + this.index).classList.remove('cdk-program-focused');
|
||||||
});
|
});
|
||||||
|
|
2
pom.xml
2
pom.xml
|
@ -62,7 +62,7 @@
|
||||||
|
|
||||||
<version.webdrivermanager>4.2.2</version.webdrivermanager>
|
<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.client>1.1.0</version.openvidu.client>
|
||||||
<version.openvidu.test.browsers>1.1.0</version.openvidu.test.browsers>
|
<version.openvidu.test.browsers>1.1.0</version.openvidu.test.browsers>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue