mirror of https://github.com/OpenVidu/openvidu.git
Merge branch 'master' into fix-generateOffer
commit
36f3d305d3
|
@ -10,20 +10,20 @@
|
|||
},
|
||||
"description": "OpenVidu Browser",
|
||||
"devDependencies": {
|
||||
"@types/node": "14.14.32",
|
||||
"@types/node": "15.12.2",
|
||||
"@types/platform": "1.3.3",
|
||||
"browserify": "17.0.0",
|
||||
"grunt": "1.3.0",
|
||||
"grunt-cli": "1.3.2",
|
||||
"grunt": "1.4.1",
|
||||
"grunt-cli": "1.4.3",
|
||||
"grunt-contrib-copy": "1.0.0",
|
||||
"grunt-contrib-sass": "2.0.0",
|
||||
"grunt-contrib-uglify": "5.0.0",
|
||||
"grunt-contrib-uglify": "5.0.1",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"grunt-postcss": "0.9.0",
|
||||
"grunt-string-replace": "1.3.1",
|
||||
"grunt-ts": "6.0.0-beta.22",
|
||||
"terser": "5.6.0",
|
||||
"tsify": "5.0.2",
|
||||
"terser": "5.7.0",
|
||||
"tsify": "5.0.4",
|
||||
"tslint": "6.1.3",
|
||||
"typedoc": "0.19.2",
|
||||
"typescript": "4.0.7"
|
||||
|
@ -38,9 +38,9 @@
|
|||
"scripts": {
|
||||
"browserify": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/openvidu-browser-$VERSION.js -v",
|
||||
"browserify-prod": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | ../node_modules/terser/bin/terser --source-map content=inline --output ../static/js/openvidu-browser-$VERSION.min.js",
|
||||
"build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --lib dom,es5,es2015.promise,scripthost",
|
||||
"build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --target es5 --lib dom,es5,es2015.promise,scripthost",
|
||||
"docs": "./generate-docs.sh"
|
||||
},
|
||||
"types": "lib/index.d.ts",
|
||||
"version": "2.17.0"
|
||||
"version": "2.18.0"
|
||||
}
|
||||
|
|
|
@ -67,6 +67,7 @@ let platform: PlatformUtils;
|
|||
export class OpenVidu {
|
||||
|
||||
private jsonRpcClient: any;
|
||||
private masterNodeHasCrashed = false;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
|
@ -104,6 +105,10 @@ export class OpenVidu {
|
|||
* @hidden
|
||||
*/
|
||||
finalUserId: string;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
mediaServer: string;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
|
@ -744,7 +749,8 @@ export class OpenVidu {
|
|||
onconnected: onConnectSucces,
|
||||
ondisconnect: this.disconnectCallback.bind(this),
|
||||
onreconnecting: this.reconnectingCallback.bind(this),
|
||||
onreconnected: this.reconnectedCallback.bind(this)
|
||||
onreconnected: this.reconnectedCallback.bind(this),
|
||||
ismasternodecrashed: this.isMasterNodeCrashed.bind(this)
|
||||
},
|
||||
rpc: {
|
||||
requestTimeout: 10000,
|
||||
|
@ -761,12 +767,30 @@ export class OpenVidu {
|
|||
networkQualityLevelChanged: this.session.onNetworkQualityLevelChangedChanged.bind(this.session),
|
||||
filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
|
||||
iceCandidate: this.session.recvIceCandidate.bind(this.session),
|
||||
mediaError: this.session.onMediaError.bind(this.session)
|
||||
mediaError: this.session.onMediaError.bind(this.session),
|
||||
masterNodeCrashedNotification: this.onMasterNodeCrashedNotification.bind(this)
|
||||
}
|
||||
};
|
||||
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
onMasterNodeCrashedNotification(response): void {
|
||||
console.error('Master Node has crashed');
|
||||
this.masterNodeHasCrashed = true;
|
||||
this.session.onLostConnection("nodeCrashed");
|
||||
this.jsonRpcClient.close(4103, "Master Node has crashed");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
getWsReadyState(): number {
|
||||
return this.jsonRpcClient.getReadyState();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
|
@ -1009,10 +1033,14 @@ export class OpenVidu {
|
|||
if (!!this.session.connection) {
|
||||
this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => {
|
||||
if (!!error) {
|
||||
logger.error(error);
|
||||
logger.warn('Websocket was able to reconnect to OpenVidu Server, but your Connection was already destroyed due to timeout. You are no longer a participant of the Session and your media streams have been destroyed');
|
||||
this.session.onLostConnection("networkDisconnect");
|
||||
this.jsonRpcClient.close(4101, "Reconnection fault");
|
||||
if (this.isMasterNodeCrashed()) {
|
||||
logger.warn('Master Node has crashed!');
|
||||
} else {
|
||||
logger.error(error);
|
||||
logger.warn('Websocket was able to reconnect to OpenVidu Server, but your Connection was already destroyed due to timeout. You are no longer a participant of the Session and your media streams have been destroyed');
|
||||
this.session.onLostConnection("networkDisconnect");
|
||||
this.jsonRpcClient.close(4101, "Reconnection fault");
|
||||
}
|
||||
} else {
|
||||
this.jsonRpcClient.resetPing();
|
||||
this.session.onRecoveredConnection();
|
||||
|
@ -1030,6 +1058,10 @@ export class OpenVidu {
|
|||
}
|
||||
}
|
||||
|
||||
private isMasterNodeCrashed() {
|
||||
return this.masterNodeHasCrashed;
|
||||
}
|
||||
|
||||
private isRoomAvailable(): boolean {
|
||||
if (this.session !== undefined && this.session instanceof Session) {
|
||||
return true;
|
||||
|
|
|
@ -86,10 +86,6 @@ export class Publisher extends StreamManager {
|
|||
* @hidden
|
||||
*/
|
||||
screenShareResizeInterval: NodeJS.Timer;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
IEAdapter: any;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
|
|
|
@ -197,7 +197,7 @@ export class Session extends EventDispatcher {
|
|||
* #### Events dispatched
|
||||
*
|
||||
* The [[Session]] object of the local participant will dispatch a `sessionDisconnected` event.
|
||||
* This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
|
||||
* This event will automatically unsubscribe the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
|
||||
* and also deletes any HTML video element associated to each Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
|
||||
* For every video removed, each Subscriber object will dispatch a `videoElementDestroyed` event.
|
||||
* Call `event.preventDefault()` upon event `sessionDisconnected` to avoid this behavior and take care of disposing and cleaning all the Subscriber objects yourself.
|
||||
|
@ -210,7 +210,7 @@ export class Session extends EventDispatcher {
|
|||
* or/and `Session.disconnect()` in the previous session). See [[StreamEvent]] and [[VideoElementEvent]] to learn more.
|
||||
*
|
||||
* The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event if the disconnected participant was publishing.
|
||||
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
|
||||
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
|
||||
* and also deletes any HTML video element associated to that Subscriber (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
|
||||
* For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
|
||||
* Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object yourself.
|
||||
|
@ -437,7 +437,7 @@ export class Session extends EventDispatcher {
|
|||
* Call `event.preventDefault()` upon event `streamDestroyed` if you want to clean the Publisher object on your own or re-publish it in a different Session.
|
||||
*
|
||||
* The [[Session]] object of every other participant connected to the session will dispatch a `streamDestroyed` event.
|
||||
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks) and
|
||||
* This event will automatically unsubscribe the Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks) and
|
||||
* delete any HTML video element associated to it (only those [created by OpenVidu Browser](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)).
|
||||
* For every video removed, the Subscriber object will dispatch a `videoElementDestroyed` event.
|
||||
* Call `event.preventDefault()` upon event `streamDestroyed` to avoid this default behavior and take care of disposing and cleaning the Subscriber object on your own.
|
||||
|
@ -781,7 +781,7 @@ export class Session extends EventDispatcher {
|
|||
onParticipantLeft(msg): void {
|
||||
|
||||
if (this.remoteConnections.size > 0) {
|
||||
this.getRemoteConnection(msg.connectionId).then(connection => {
|
||||
this.getRemoteConnection(msg.connectionId, 'onParticipantLeft').then(connection => {
|
||||
if (!!connection.stream) {
|
||||
const stream = connection.stream;
|
||||
|
||||
|
@ -823,7 +823,7 @@ export class Session extends EventDispatcher {
|
|||
// Get the existing Connection created on 'onParticipantJoined' for
|
||||
// existing participants or create a new one for new participants
|
||||
let connection: Connection;
|
||||
this.getRemoteConnection(response.id)
|
||||
this.getRemoteConnection(response.id, 'onParticipantPublished')
|
||||
|
||||
.then(con => {
|
||||
// Update existing Connection
|
||||
|
@ -848,7 +848,7 @@ export class Session extends EventDispatcher {
|
|||
// Your stream has been forcedly unpublished from the session
|
||||
this.stopPublisherStream(msg.reason);
|
||||
} else {
|
||||
this.getRemoteConnection(msg.connectionId)
|
||||
this.getRemoteConnection(msg.connectionId, 'onParticipantUnpublished')
|
||||
|
||||
.then(connection => {
|
||||
|
||||
|
@ -965,7 +965,7 @@ export class Session extends EventDispatcher {
|
|||
// Your stream has been forcedly changed (filter feature)
|
||||
callback(this.connection);
|
||||
} else {
|
||||
this.getRemoteConnection(msg.connectionId)
|
||||
this.getRemoteConnection(msg.connectionId, 'onStreamPropertyChanged')
|
||||
.then(connection => {
|
||||
callback(connection);
|
||||
})
|
||||
|
@ -1411,7 +1411,7 @@ export class Session extends EventDispatcher {
|
|||
});
|
||||
}
|
||||
|
||||
private getRemoteConnection(connectionId: string): Promise<Connection> {
|
||||
private getRemoteConnection(connectionId: string, operation: string): Promise<Connection> {
|
||||
return new Promise<Connection>((resolve, reject) => {
|
||||
const connection = this.remoteConnections.get(connectionId);
|
||||
if (!!connection) {
|
||||
|
@ -1419,9 +1419,8 @@ export class Session extends EventDispatcher {
|
|||
resolve(connection);
|
||||
} else {
|
||||
// Remote connection not found. Reject with OpenViduError
|
||||
const errorMessage = 'Remote connection ' + connectionId + " unknown when 'onParticipantLeft'. " +
|
||||
const errorMessage = 'Remote connection ' + connectionId + " unknown when '" + operation + "'. " +
|
||||
'Existing remote connections: ' + JSON.stringify(this.remoteConnections.keys());
|
||||
|
||||
reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
|
||||
}
|
||||
});
|
||||
|
@ -1487,6 +1486,7 @@ export class Session extends EventDispatcher {
|
|||
}
|
||||
this.openvidu.role = opts.role;
|
||||
this.openvidu.finalUserId = opts.finalUserId;
|
||||
this.openvidu.mediaServer = opts.mediaServer;
|
||||
this.capabilities = {
|
||||
subscribe: true,
|
||||
publish: this.openvidu.role !== 'SUBSCRIBER',
|
||||
|
|
|
@ -214,6 +214,10 @@ export class Stream {
|
|||
* @hidden
|
||||
*/
|
||||
ee = new EventEmitter();
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
reconnectionEventEmitter: EventEmitter | undefined;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -277,6 +281,19 @@ export class Stream {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recreates the media connection with the server. This entails the disposal of the previous RTCPeerConnection and the re-negotiation
|
||||
* of a new one, that will apply the same properties.
|
||||
*
|
||||
* This method can be useful in those situations were there the media connection breaks and OpenVidu is not able to recover on its own
|
||||
* for any kind of unanticipated reason (see [Automatic reconnection](/en/latest/advanced-features/automatic-reconnection/)).
|
||||
*
|
||||
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the reconnection operation was successful and rejected with an Error object if not
|
||||
*/
|
||||
public reconnect(): Promise<void> {
|
||||
return this.reconnectStream('API');
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies an audio/video filter to the stream.
|
||||
*
|
||||
|
@ -461,11 +478,13 @@ export class Stream {
|
|||
* @hidden
|
||||
*/
|
||||
disposeWebRtcPeer(): void {
|
||||
let webrtcId;
|
||||
if (!!this.webRtcPeer) {
|
||||
this.webRtcPeer.dispose();
|
||||
this.stopWebRtcStats();
|
||||
webrtcId = this.webRtcPeer.id;
|
||||
}
|
||||
logger.info((!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + "WebRTCPeer from 'Stream' with id [" + this.streamId + '] is now closed');
|
||||
this.stopWebRtcStats();
|
||||
logger.info((!!this.outboundStreamOpts ? 'Outbound ' : 'Inbound ') + "RTCPeerConnection with id [" + webrtcId + "] from 'Stream' with id [" + this.streamId + '] is now closed');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -774,7 +793,7 @@ export class Stream {
|
|||
return false;
|
||||
}
|
||||
if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
|
||||
logger.warn('OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ' + this.streamId + ' will force a reconnection');
|
||||
logger.warn(`OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) will force a reconnection`);
|
||||
return true;
|
||||
}
|
||||
const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
|
||||
|
@ -802,10 +821,42 @@ export class Stream {
|
|||
initWebRtcPeerSend(reconnect: boolean): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (!reconnect) {
|
||||
if (reconnect) {
|
||||
if (this.reconnectionEventEmitter == undefined) {
|
||||
// There is no ongoing reconnection
|
||||
this.reconnectionEventEmitter = new EventEmitter();
|
||||
} else {
|
||||
// Ongoing reconnection
|
||||
console.warn(`Trying to reconnect stream ${this.streamId} (Publisher) but an ongoing reconnection process is active. Waiting for response...`);
|
||||
this.reconnectionEventEmitter.once('success', () => {
|
||||
resolve();
|
||||
});
|
||||
this.reconnectionEventEmitter.once('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// MediaStream will already have hark events for reconnected streams
|
||||
this.initHarkEvents(); // Init hark events for the local stream
|
||||
}
|
||||
|
||||
const finalResolve = () => {
|
||||
if (reconnect) {
|
||||
this.reconnectionEventEmitter?.emitEvent('success');
|
||||
delete this.reconnectionEventEmitter;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
const finalReject = error => {
|
||||
if (reconnect) {
|
||||
this.reconnectionEventEmitter?.emitEvent('error', [error]);
|
||||
delete this.reconnectionEventEmitter;
|
||||
}
|
||||
reject(error);
|
||||
}
|
||||
|
||||
const successOfferCallback = (sdpOfferParam) => {
|
||||
logger.debug('Sending SDP offer to publish as '
|
||||
+ this.streamId, sdpOfferParam);
|
||||
|
@ -839,9 +890,9 @@ export class Stream {
|
|||
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
||||
if (error) {
|
||||
if (error.code === 401) {
|
||||
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish"));
|
||||
finalReject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to publish"));
|
||||
} else {
|
||||
reject('Error on publishVideo: ' + JSON.stringify(error));
|
||||
finalReject('Error on publishVideo: ' + JSON.stringify(error));
|
||||
}
|
||||
} else {
|
||||
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
|
||||
|
@ -861,10 +912,11 @@ export class Stream {
|
|||
}
|
||||
this.initWebRtcStats();
|
||||
logger.info("'Publisher' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "published") + " to session");
|
||||
resolve();
|
||||
|
||||
finalResolve();
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
finalReject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -876,8 +928,8 @@ export class Stream {
|
|||
video: this.hasVideo,
|
||||
},
|
||||
simulcast: false,
|
||||
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
|
||||
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
|
||||
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||
iceServers: this.getIceServersConf(),
|
||||
mediaStream: this.mediaStream,
|
||||
};
|
||||
|
@ -896,10 +948,10 @@ export class Stream {
|
|||
.then(() => {
|
||||
successOfferCallback(sdpOffer.sdp);
|
||||
}).catch(error => {
|
||||
reject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
|
||||
finalReject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
}).catch(error => {
|
||||
reject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
|
||||
finalReject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -909,20 +961,87 @@ export class Stream {
|
|||
*/
|
||||
initWebRtcPeerReceive(reconnect: boolean): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if (reconnect) {
|
||||
if (this.reconnectionEventEmitter == undefined) {
|
||||
// There is no ongoing reconnection
|
||||
this.reconnectionEventEmitter = new EventEmitter();
|
||||
} else {
|
||||
// Ongoing reconnection
|
||||
console.warn(`Trying to reconnect stream ${this.streamId} (Subscriber) but an ongoing reconnection process is active. Waiting for response...`);
|
||||
this.reconnectionEventEmitter.once('success', () => {
|
||||
resolve();
|
||||
});
|
||||
this.reconnectionEventEmitter.once('error', error => {
|
||||
reject(error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const finalResolve = () => {
|
||||
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
|
||||
this.remotePeerSuccessfullyEstablished(reconnect);
|
||||
this.initWebRtcStats();
|
||||
if (reconnect) {
|
||||
this.reconnectionEventEmitter?.emitEvent('success');
|
||||
delete this.reconnectionEventEmitter;
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
|
||||
const finalReject = error => {
|
||||
if (reconnect) {
|
||||
this.reconnectionEventEmitter?.emitEvent('error', [error]);
|
||||
delete this.reconnectionEventEmitter;
|
||||
}
|
||||
reject(error);
|
||||
}
|
||||
|
||||
if (this.session.openvidu.mediaServer === 'mediasoup') {
|
||||
|
||||
// Server initiates negotiation
|
||||
|
||||
this.initWebRtcPeerReceiveFromServer(reconnect)
|
||||
.then(() => finalResolve())
|
||||
.catch(error => finalReject(error));
|
||||
|
||||
} else {
|
||||
|
||||
// Client initiates negotiation
|
||||
|
||||
this.initWebRtcPeerReceiveFromClient(reconnect)
|
||||
.then(() => finalResolve())
|
||||
.catch(error => finalReject(error));
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initWebRtcPeerReceiveFromClient(reconnect: boolean): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.completeWebRtcPeerReceive(reconnect).then(response => {
|
||||
this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
|
||||
.then(() => resolve()).catch(error => reject(error));
|
||||
}).catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initWebRtcPeerReceiveFromServer(reconnect: boolean): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Server initiates negotiation
|
||||
this.session.openvidu.sendRequest('prepareReceiveVideoFrom', { sender: this.streamId, reconnect }, (error, response) => {
|
||||
if (error) {
|
||||
reject(new Error('Error on prepareReceiveVideoFrom: ' + JSON.stringify(error)));
|
||||
} else {
|
||||
this.completeWebRtcPeerReceive(response.sdpOffer, reconnect)
|
||||
.then(() => {
|
||||
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
|
||||
this.remotePeerSuccessfullyEstablished(reconnect);
|
||||
this.initWebRtcStats();
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
this.completeWebRtcPeerReceive(reconnect, response.sdpOffer)
|
||||
.then(() => resolve()).catch(error => reject(error));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -931,25 +1050,29 @@ export class Stream {
|
|||
/**
|
||||
* @hidden
|
||||
*/
|
||||
completeWebRtcPeerReceive(sdpOffer: string, reconnect: boolean): Promise<void> {
|
||||
completeWebRtcPeerReceive(reconnect: boolean, sdpOfferByServer?: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
logger.debug("'Session.subscribe(Stream)' called");
|
||||
|
||||
const successAnswerCallback = (sdpAnswer) => {
|
||||
logger.debug('Sending SDP answer to subscribe to '
|
||||
+ this.streamId, sdpAnswer);
|
||||
const sendSdpToServer = (sdpString: string) => {
|
||||
|
||||
logger.debug(`Sending local SDP ${(!!sdpOfferByServer ? 'answer' : 'offer')} to subscribe to ${this.streamId}`, sdpString);
|
||||
|
||||
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
|
||||
const params = {};
|
||||
params[reconnect ? 'stream' : 'sender'] = this.streamId;
|
||||
params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpAnswer;
|
||||
if (!!sdpOfferByServer) {
|
||||
params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpString;
|
||||
} else {
|
||||
params['sdpOffer'] = sdpString;
|
||||
}
|
||||
|
||||
this.session.openvidu.sendRequest(method, params, (error, response) => {
|
||||
if (error) {
|
||||
reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
|
||||
} else {
|
||||
resolve();
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -960,29 +1083,47 @@ export class Stream {
|
|||
video: this.hasVideo,
|
||||
},
|
||||
simulcast: false,
|
||||
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
|
||||
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||
onIceCandidate: this.connection.sendIceCandidate.bind(this.connection),
|
||||
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
|
||||
iceServers: this.getIceServersConf(),
|
||||
};
|
||||
|
||||
if (reconnect) {
|
||||
this.disposeWebRtcPeer();
|
||||
}
|
||||
|
||||
this.webRtcPeer = new WebRtcPeerRecvonly(config);
|
||||
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
|
||||
this.webRtcPeer.processRemoteOffer(sdpOffer)
|
||||
.then(() => {
|
||||
|
||||
if (!!sdpOfferByServer) {
|
||||
|
||||
this.webRtcPeer.processRemoteOffer(sdpOfferByServer).then(() => {
|
||||
this.webRtcPeer.createAnswer().then(sdpAnswer => {
|
||||
this.webRtcPeer.processLocalAnswer(sdpAnswer)
|
||||
.then(() => {
|
||||
successAnswerCallback(sdpAnswer.sdp);
|
||||
}).catch(error => {
|
||||
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
this.webRtcPeer.processLocalAnswer(sdpAnswer).then(() => {
|
||||
sendSdpToServer(sdpAnswer.sdp!);
|
||||
}).catch(error => {
|
||||
reject(new Error('(subscribe) SDP process local answer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
}).catch(error => {
|
||||
reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
}).catch(error => {
|
||||
reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
this.webRtcPeer.createOffer().then(sdpOffer => {
|
||||
this.webRtcPeer.processLocalOffer(sdpOffer).then(() => {
|
||||
sendSdpToServer(sdpOffer.sdp!);
|
||||
}).catch(error => {
|
||||
reject(new Error('(subscribe) SDP process local offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
}).catch(error => {
|
||||
reject(new Error('(subscribe) SDP create offer error: ' + JSON.stringify(error)));
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1048,6 +1189,137 @@ export class Stream {
|
|||
}
|
||||
}
|
||||
|
||||
private onIceConnectionStateExceptionHandler(exceptionName: ExceptionEventName, message: string, data?: any): void {
|
||||
switch (exceptionName) {
|
||||
case ExceptionEventName.ICE_CONNECTION_FAILED:
|
||||
this.onIceConnectionFailed();
|
||||
break;
|
||||
case ExceptionEventName.ICE_CONNECTION_DISCONNECTED:
|
||||
this.onIceConnectionDisconnected();
|
||||
break;
|
||||
}
|
||||
this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]);
|
||||
}
|
||||
|
||||
private onIceConnectionFailed() {
|
||||
// Immediately reconnect, as this is a terminal error
|
||||
logger.log(`[ICE_CONNECTION_FAILED] Handling ICE_CONNECTION_FAILED event. Reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')})`);
|
||||
this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_FAILED);
|
||||
}
|
||||
|
||||
private onIceConnectionDisconnected() {
|
||||
// Wait to see if the ICE connection is able to reconnect
|
||||
logger.log(`[ICE_CONNECTION_DISCONNECTED] Handling ICE_CONNECTION_DISCONNECTED event. Waiting for ICE to be restored and reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) if not possible`);
|
||||
const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
|
||||
this.awaitWebRtcPeerConnectionState(timeout).then(state => {
|
||||
switch (state) {
|
||||
case 'failed':
|
||||
// Do nothing, as an ICE_CONNECTION_FAILED event will have already raised
|
||||
logger.warn(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) is now failed after ICE_CONNECTION_DISCONNECTED`);
|
||||
break;
|
||||
case 'connected':
|
||||
case 'completed':
|
||||
logger.log(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) automatically restored after ICE_CONNECTION_DISCONNECTED. Current ICE connection state: ${state}`);
|
||||
break;
|
||||
case 'closed':
|
||||
case 'checking':
|
||||
case 'new':
|
||||
case 'disconnected':
|
||||
// Rest of states
|
||||
logger.warn(`[ICE_CONNECTION_DISCONNECTED] ICE connection of stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) couldn't be restored after ICE_CONNECTION_DISCONNECTED event. Current ICE connection state after ${timeout} ms: ${state}`);
|
||||
this.reconnectStreamAndLogResultingIceConnectionState(ExceptionEventName.ICE_CONNECTION_DISCONNECTED);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async reconnectStreamAndLogResultingIceConnectionState(event: string) {
|
||||
try {
|
||||
const finalIceStateAfterReconnection = await this.reconnectStreamAndReturnIceConnectionState(event);
|
||||
switch (finalIceStateAfterReconnection) {
|
||||
case 'connected':
|
||||
case 'completed':
|
||||
logger.log(`[${event}] Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) successfully reconnected after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`);
|
||||
break;
|
||||
default:
|
||||
logger.error(`[${event}] Stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) failed to reconnect after ${event}. Current ICE connection state: ${finalIceStateAfterReconnection}`);
|
||||
break;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`[${event}] Error reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) after ${event}: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async reconnectStreamAndReturnIceConnectionState(event: string): Promise<RTCIceConnectionState> {
|
||||
logger.log(`[${event}] Reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) after event ${event}`);
|
||||
try {
|
||||
await this.reconnectStream(event);
|
||||
const timeout = this.session.openvidu.advancedConfiguration.iceConnectionDisconnectedExceptionTimeout || 4000;
|
||||
return this.awaitWebRtcPeerConnectionState(timeout);
|
||||
} catch (error) {
|
||||
logger.warn(`[${event}] Error reconnecting stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}). Reason: ${error}`);
|
||||
return this.awaitWebRtcPeerConnectionState(1);
|
||||
}
|
||||
}
|
||||
|
||||
private async reconnectStream(event: string) {
|
||||
const isWsConnected = await this.isWebsocketConnected(event, 3000);
|
||||
if (isWsConnected) {
|
||||
// There is connection to openvidu-server. The RTCPeerConnection is the only one broken
|
||||
logger.log(`[${event}] Trying to reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) and the websocket is opened`);
|
||||
if (this.isLocal()) {
|
||||
return this.initWebRtcPeerSend(true);
|
||||
} else {
|
||||
return this.initWebRtcPeerReceive(true);
|
||||
}
|
||||
} else {
|
||||
// There is no connection to openvidu-server. Nothing can be done. The automatic reconnection
|
||||
// feature should handle a possible reconnection of RTCPeerConnection in case network comes back
|
||||
const errorMsg = `[${event}] Trying to reconnect stream ${this.streamId} (${(this.isLocal() ? 'Publisher' : 'Subscriber')}) but the websocket wasn't opened`;
|
||||
logger.error(errorMsg);
|
||||
throw Error(errorMsg);
|
||||
}
|
||||
}
|
||||
|
||||
private isWebsocketConnected(event: string, msResponseTimeout: number): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const wsReadyState = this.session.openvidu.getWsReadyState();
|
||||
if (wsReadyState === 1) {
|
||||
const responseTimeout = setTimeout(() => {
|
||||
console.warn(`[${event}] Websocket timeout of ${msResponseTimeout}ms`);
|
||||
resolve(false);
|
||||
}, msResponseTimeout);
|
||||
this.session.openvidu.sendRequest('echo', {}, (error, response) => {
|
||||
clearTimeout(responseTimeout);
|
||||
if (!!error) {
|
||||
console.warn(`[${event}] Websocket 'echo' returned error: ${error}`);
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.warn(`[${event}] Websocket readyState is ${wsReadyState}`);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async awaitWebRtcPeerConnectionState(timeout: number): Promise<RTCIceConnectionState> {
|
||||
let state = this.getRTCPeerConnection().iceConnectionState;
|
||||
const interval = 150;
|
||||
const intervals = Math.ceil(timeout / interval);
|
||||
for (let i = 0; i < intervals; i++) {
|
||||
state = this.getRTCPeerConnection().iceConnectionState;
|
||||
if (state === 'connected' || state === 'completed') {
|
||||
break;
|
||||
}
|
||||
// Sleep
|
||||
await new Promise((resolve) => setTimeout(resolve, interval));
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
private initWebRtcStats(): void {
|
||||
this.webRtcStats = new WebRtcStats(this);
|
||||
this.webRtcStats.initWebRtcStats();
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
*/
|
||||
|
||||
import { Stream } from './Stream';
|
||||
import { Subscriber } from './Subscriber';
|
||||
import { EventDispatcher } from './EventDispatcher';
|
||||
import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
|
||||
import { Event } from '../OpenViduInternal/Events/Event';
|
||||
|
@ -24,6 +25,7 @@ import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent'
|
|||
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
|
||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
||||
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
|
@ -90,19 +92,23 @@ export class StreamManager extends EventDispatcher {
|
|||
/**
|
||||
* @hidden
|
||||
*/
|
||||
firstVideoElement?: StreamManagerVideo;
|
||||
protected firstVideoElement?: StreamManagerVideo;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
lazyLaunchVideoElementCreatedEvent = false;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
element: HTMLElement;
|
||||
protected element: HTMLElement;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
protected canPlayListener: EventListener;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
private streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
private lazyLaunchVideoElementCreatedEvent = false;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
|
@ -138,7 +144,11 @@ export class StreamManager extends EventDispatcher {
|
|||
}
|
||||
|
||||
this.canPlayListener = () => {
|
||||
if (this.stream.isLocal()) {
|
||||
this.deactivateStreamPlayingEventExceptionTimeout();
|
||||
if (this.remote) {
|
||||
logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
|
||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
|
||||
} else {
|
||||
if (!this.stream.displayMyRemote()) {
|
||||
logger.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing');
|
||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
|
||||
|
@ -146,9 +156,6 @@ export class StreamManager extends EventDispatcher {
|
|||
logger.info("Your own remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
|
||||
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
|
||||
}
|
||||
} else {
|
||||
logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
|
||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
|
||||
}
|
||||
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
||||
};
|
||||
|
@ -274,7 +281,7 @@ export class StreamManager extends EventDispatcher {
|
|||
|
||||
this.initializeVideoProperties(video);
|
||||
|
||||
if (this.stream.isLocal() && this.stream.displayMyRemote()) {
|
||||
if (!this.remote && this.stream.displayMyRemote()) {
|
||||
if (video.srcObject !== this.stream.getMediaStream()) {
|
||||
video.srcObject = this.stream.getMediaStream();
|
||||
}
|
||||
|
@ -305,7 +312,7 @@ export class StreamManager extends EventDispatcher {
|
|||
id: video.id,
|
||||
canplayListenerAdded: false
|
||||
});
|
||||
|
||||
|
||||
logger.info('New video element associated to ', this);
|
||||
|
||||
return returnNumber;
|
||||
|
@ -386,7 +393,7 @@ export class StreamManager extends EventDispatcher {
|
|||
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
|
||||
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
|
||||
*/
|
||||
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions): void {
|
||||
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void {
|
||||
const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
|
||||
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ?
|
||||
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100);
|
||||
|
@ -408,7 +415,7 @@ export class StreamManager extends EventDispatcher {
|
|||
* @hidden
|
||||
*/
|
||||
initializeVideoProperties(video: HTMLVideoElement): void {
|
||||
if (!(this.stream.isLocal() && this.stream.displayMyRemote())) {
|
||||
if (!(!this.remote && this.stream.displayMyRemote())) {
|
||||
// Avoid setting the MediaStream into the srcObject if remote subscription before publishing
|
||||
if (video.srcObject !== this.stream.getMediaStream()) {
|
||||
// If srcObject already set don't do it again
|
||||
|
@ -492,6 +499,7 @@ export class StreamManager extends EventDispatcher {
|
|||
*/
|
||||
addPlayEventToFirstVideo() {
|
||||
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) {
|
||||
this.activateStreamPlayingEventExceptionTimeout();
|
||||
this.videos[0].video.addEventListener('canplay', this.canPlayListener);
|
||||
this.videos[0].canplayListenerAdded = true;
|
||||
}
|
||||
|
@ -533,6 +541,7 @@ export class StreamManager extends EventDispatcher {
|
|||
*/
|
||||
removeSrcObject(streamManagerVideo: StreamManagerVideo) {
|
||||
streamManagerVideo.video.srcObject = null;
|
||||
this.deactivateStreamPlayingEventExceptionTimeout();
|
||||
}
|
||||
|
||||
/* Private methods */
|
||||
|
@ -557,4 +566,28 @@ export class StreamManager extends EventDispatcher {
|
|||
video.style.webkitTransform = 'unset';
|
||||
}
|
||||
|
||||
private activateStreamPlayingEventExceptionTimeout() {
|
||||
if (!this.remote) {
|
||||
// ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers
|
||||
return;
|
||||
}
|
||||
if (this.streamPlayingEventExceptionTimeout != null) {
|
||||
// The timeout is already activated
|
||||
return;
|
||||
}
|
||||
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
|
||||
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
|
||||
this.streamPlayingEventExceptionTimeout = setTimeout(() => {
|
||||
const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms';
|
||||
logger.warn(msg);
|
||||
this.stream.session.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)]);
|
||||
delete this.streamPlayingEventExceptionTimeout;
|
||||
}, msTimeout);
|
||||
}
|
||||
|
||||
private deactivateStreamPlayingEventExceptionTimeout() {
|
||||
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
|
||||
delete this.streamPlayingEventExceptionTimeout;
|
||||
}
|
||||
|
||||
}
|
|
@ -39,6 +39,7 @@ export class ConnectionEvent extends Event {
|
|||
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
|
||||
* - "sessionClosedByServer": the Session has been closed by the application
|
||||
* - "networkDisconnect": the remote user network connection has dropped
|
||||
* - "nodeCrashed": a node has crashed in the server side
|
||||
*
|
||||
* For `connectionCreated` event an empty string
|
||||
*/
|
||||
|
|
|
@ -60,14 +60,14 @@ export abstract class Event {
|
|||
/**
|
||||
* Prevents the default behavior of the event. The following events have a default behavior:
|
||||
*
|
||||
* - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
|
||||
* - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
|
||||
* and also deletes any HTML video element associated to each Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or
|
||||
* by calling [[Subscriber.createVideoElement]]). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event.
|
||||
*
|
||||
* - `streamDestroyed`:
|
||||
* - If dispatched by a [[Publisher]] (*you* have unpublished): automatically stops all media tracks and deletes any HTML video element associated to it (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement`
|
||||
* in method [[OpenVidu.initPublisher]] or by calling [[Publisher.createVideoElement]]). For every video removed, the Publisher object will also dispatch a `videoElementDestroyed` event.
|
||||
* - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
|
||||
* - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
|
||||
* and also deletes any HTML video element associated to that Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or
|
||||
* by calling [[Subscriber.createVideoElement]]). For every video removed, the Subscriber object will also dispatch a `videoElementDestroyed` event.
|
||||
*/
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
import { Session } from '../../OpenVidu/Session';
|
||||
import { Stream } from '../../OpenVidu/Stream';
|
||||
import { Subscriber } from '../../OpenVidu/Subscriber';
|
||||
import { Event } from './Event';
|
||||
|
||||
|
||||
|
@ -36,7 +37,9 @@ export enum ExceptionEventName {
|
|||
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
|
||||
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `failed` status.
|
||||
*
|
||||
* This is a terminal error that won't have any kind of possible recovery.
|
||||
* This is a terminal error that won't have any kind of possible recovery. If the client is still connected to OpenVidu Server,
|
||||
* then an automatic reconnection process of the media stream is immediately performed. If the ICE connection has broken due to
|
||||
* a total network drop, then no automatic reconnection process will be possible.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
|
||||
*/
|
||||
|
@ -46,18 +49,45 @@ export enum ExceptionEventName {
|
|||
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
|
||||
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `disconnected` status.
|
||||
*
|
||||
* This is not a terminal error, and it is possible for the ICE connection to be reconnected.
|
||||
* This is not a terminal error, and it is possible for the ICE connection to be reconnected. If the client is still connected to
|
||||
* OpenVidu Server and after certain timeout the ICE connection has not reached a success or terminal status, then an automatic
|
||||
* reconnection process of the media stream is performed. If the ICE connection has broken due to a total network drop, then no
|
||||
* automatic reconnection process will be possible.
|
||||
*
|
||||
* You can customize the timeout for the reconnection attempt with property [[OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout]],
|
||||
* which by default is 4000 milliseconds.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
|
||||
*/
|
||||
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED'
|
||||
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED',
|
||||
|
||||
/**
|
||||
* A [[Subscriber]] object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to [[StreamManagerEvent]]
|
||||
* category. It wraps Web API native event [canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event).
|
||||
*
|
||||
* OpenVidu Browser can take care of the video players (see [here](/en/latest/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)),
|
||||
* or you can take care of video players on your own (see [here](/en/latest/cheatsheet/manage-videos/#you-take-care-of-the-video-players)).
|
||||
* Either way, whenever a [[Subscriber]] object is commanded to attach its [[Stream]] to a video element, it is supposed to fire `streamPlaying`
|
||||
* event shortly after. If it does not, then we can safely assume that something wrong has happened while playing the remote video and the
|
||||
* application may be notified through this specific ExceptionEvent.
|
||||
*
|
||||
* The timeout can be configured with property [[OpenViduAdvancedConfiguration.noStreamPlayingEventExceptionTimeout]]. By default it is 4000 milliseconds.
|
||||
*
|
||||
* This is just an informative exception. It only means that a remote Stream that is supposed to be playing by a video player has not done so
|
||||
* in a reasonable time. But the lack of the event can be caused by multiple reasons. If a Subscriber is not playing its Stream, the origin
|
||||
* of the problem could be located at the Publisher side. Or may be caused by a transient network problem. But it also could be a problem with
|
||||
* autoplay permissions. Bottom line, the cause can be very varied, and depending on the application the lack of the event could even be expected.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Subscriber]] object.
|
||||
*/
|
||||
NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT'
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines event `exception` dispatched by [[Session]] object.
|
||||
*
|
||||
* This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins.
|
||||
* This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins. To see the different
|
||||
* types of exceptions go to [[ExceptionEventName]].
|
||||
*/
|
||||
export class ExceptionEvent extends Event {
|
||||
|
||||
|
@ -70,8 +100,9 @@ export class ExceptionEvent extends Event {
|
|||
* Object affected by the exception. Depending on the [[ExceptionEvent.name]] property:
|
||||
* - [[Session]]: `ICE_CANDIDATE_ERROR`
|
||||
* - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
|
||||
* - [[Subscriber]]: `NO_STREAM_PLAYING_EVENT`
|
||||
*/
|
||||
origin: Session | Stream;
|
||||
origin: Session | Stream | Subscriber;
|
||||
|
||||
/**
|
||||
* Informative description of the exception
|
||||
|
@ -86,7 +117,7 @@ export class ExceptionEvent extends Event {
|
|||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream, message: string, data?: any) {
|
||||
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream | Subscriber, message: string, data?: any) {
|
||||
super(false, session, 'exception');
|
||||
this.name = name;
|
||||
this.origin = origin;
|
||||
|
|
|
@ -46,7 +46,7 @@ export class RecordingEvent extends Event {
|
|||
* - "recordingStoppedByServer": the recording has been gracefully stopped by the application
|
||||
* - "sessionClosedByServer": the Session has been closed by the application
|
||||
* - "automaticStop": see [Automatic stop of recordings](/en/stable/advanced-features/recording/#automatic-stop-of-recordings)
|
||||
* - "mediaServerDisconnect": OpenVidu Media Node has crashed or lost its connection. A new Media Node instance is active and the recording has been stopped (no media streams are available in the new Media Node)
|
||||
* - "nodeCrashed": a node has crashed in the server side
|
||||
*
|
||||
* For 'recordingStarted' empty string
|
||||
*/
|
||||
|
|
|
@ -39,6 +39,8 @@ export class SessionDisconnectedEvent extends Event {
|
|||
* Session object will always have previously dispatched a `reconnecting` event. If the reconnection process succeeds,
|
||||
* Session object will dispatch a `reconnected` event. If it fails, Session object will dispatch a SessionDisconnectedEvent
|
||||
* with reason "networkDisconnect"
|
||||
* - "nodeCrashed": a node has crashed in the server side. You can use this reason to ask your application's backend to reconnect
|
||||
* to a new session to replace the crashed one
|
||||
*/
|
||||
reason: string;
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ export class StreamEvent extends Event {
|
|||
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
|
||||
* - "sessionClosedByServer": the Session has been closed by the application
|
||||
* - "networkDisconnect": the user's network connection has dropped
|
||||
* - "mediaServerDisconnect": OpenVidu Media Node has crashed or lost its connection. A new Media Node instance is active and no media streams are available in the Media Node
|
||||
* - "nodeCrashed": a node has crashed in the server side
|
||||
*
|
||||
* For 'streamCreated' empty string
|
||||
*/
|
||||
|
|
|
@ -21,7 +21,10 @@ import { StreamManager } from '../../OpenVidu/StreamManager';
|
|||
/**
|
||||
* Defines the following events:
|
||||
* - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media
|
||||
* and has begun to play). This event will be dispatched when these 3 conditions are met 1) The StreamManager has no video associated in the DOM 2) It is associated to one video 3) That video starts playing
|
||||
* and has begun to play). This event will be dispatched when these 3 conditions are met:
|
||||
* 1. The StreamManager has no video associated in the DOM
|
||||
* 2. It is associated to one video
|
||||
* 3. That video starts playing. Internally the expected Web API event is [HTMLMediaElement.canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event)
|
||||
* - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track
|
||||
* changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of
|
||||
* [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 100ms)
|
||||
|
|
|
@ -31,4 +31,5 @@ export interface LocalConnectionOptions {
|
|||
turnUsername: string;
|
||||
turnCredential: 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]]
|
||||
*/
|
||||
publisherSpeakingEventsOptions?: any;
|
||||
publisherSpeakingEventsOptions?: {
|
||||
interval?: number;
|
||||
threshold?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the automatic reconnection process policy. Whenever the client's network drops, OpenVidu Browser starts a reconnection process with OpenVidu Server. After network is recovered, OpenVidu Browser automatically
|
||||
|
@ -52,4 +55,20 @@ export interface OpenViduAdvancedConfiguration {
|
|||
*/
|
||||
forceMediaReconnectionAfterNetworkDrop?: boolean;
|
||||
|
||||
}
|
||||
/**
|
||||
* The milliseconds that must elapse after triggering [[ExceptionEvent]] of name [`ICE_CONNECTION_DISCONNECTED`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#ice_connection_disconnected) to perform an automatic reconnection process of the affected media stream.
|
||||
* This automatic reconnection process can only take place if the client still has network connection to OpenVidu Server. If the ICE connection has broken because of a total network drop,
|
||||
* then no reconnection process will be possible at all.
|
||||
*
|
||||
* Default to `4000`.
|
||||
*/
|
||||
iceConnectionDisconnectedExceptionTimeout?: number;
|
||||
|
||||
/**
|
||||
* The milliseconds that must elapse for the [[ExceptionEvent]] of name [`NO_STREAM_PLAYING_EVENT`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#no_stream_playing_event) to be fired.
|
||||
*
|
||||
* Default to `4000`.
|
||||
*/
|
||||
noStreamPlayingEventExceptionTimeout?: number;
|
||||
|
||||
}
|
|
@ -270,6 +270,10 @@ function JsonRpcClient(configuration) {
|
|||
pingNextNum = 0;
|
||||
usePing();
|
||||
}
|
||||
|
||||
this.getReadyState = function () {
|
||||
return ws.getReadyState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -66,8 +66,12 @@ function WebSocketWithReconnection(config) {
|
|||
if (closing) {
|
||||
Logger.debug("Connection closed by user");
|
||||
} else {
|
||||
Logger.debug("Connection closed unexpectecly. Reconnecting...");
|
||||
reconnect(MAX_RETRIES, 1);
|
||||
if (config.ismasternodecrashed()) {
|
||||
Logger.error("Master Node has crashed. Stopping reconnection process");
|
||||
} else {
|
||||
Logger.debug("Connection closed unexpectedly. Reconnecting...");
|
||||
reconnect(MAX_RETRIES, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.debug("Close callback from previous websocket. Ignoring it");
|
||||
|
@ -147,6 +151,10 @@ function WebSocketWithReconnection(config) {
|
|||
};
|
||||
registerMessageHandler();
|
||||
};
|
||||
|
||||
this.getReadyState = () => {
|
||||
return ws.readyState;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketWithReconnection;
|
|
@ -37,18 +37,19 @@ export interface WebRtcPeerConfiguration {
|
|||
video: boolean
|
||||
};
|
||||
simulcast: boolean;
|
||||
onicecandidate: (event: RTCIceCandidate) => void;
|
||||
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
|
||||
iceServers: RTCIceServer[] | undefined;
|
||||
onIceCandidate: (event: RTCIceCandidate) => void;
|
||||
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
|
||||
|
||||
iceServers?: RTCIceServer[];
|
||||
mediaStream?: MediaStream | null;
|
||||
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export class WebRtcPeer {
|
||||
public pc: RTCPeerConnection;
|
||||
public remoteCandidatesQueue: RTCIceCandidate[] = [];
|
||||
public localCandidatesQueue: RTCIceCandidate[] = [];
|
||||
pc: RTCPeerConnection;
|
||||
remoteCandidatesQueue: RTCIceCandidate[] = [];
|
||||
localCandidatesQueue: RTCIceCandidate[] = [];
|
||||
|
||||
// Same as WebRtcPeerConfiguration but without optional fields.
|
||||
protected configuration: Required<WebRtcPeerConfiguration>;
|
||||
|
@ -61,10 +62,15 @@ export class WebRtcPeer {
|
|||
|
||||
this.configuration = {
|
||||
...configuration,
|
||||
iceServers: (!!configuration.iceServers && configuration.iceServers.length > 0) ? configuration.iceServers : freeice(),
|
||||
mediaStream: !!configuration.mediaStream
|
||||
? configuration.mediaStream
|
||||
: null,
|
||||
iceServers:
|
||||
!!configuration.iceServers &&
|
||||
configuration.iceServers.length > 0
|
||||
? configuration.iceServers
|
||||
: freeice(),
|
||||
mediaStream:
|
||||
configuration.mediaStream !== undefined
|
||||
? configuration.mediaStream
|
||||
: null,
|
||||
mode: !!configuration.mode ? configuration.mode : "sendrecv",
|
||||
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
|
||||
};
|
||||
|
@ -74,7 +80,7 @@ export class WebRtcPeer {
|
|||
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
|
||||
if (event.candidate != null) {
|
||||
const candidate: RTCIceCandidate = event.candidate;
|
||||
this.configuration.onicecandidate(candidate);
|
||||
this.configuration.onIceCandidate(candidate);
|
||||
if (candidate.candidate !== '') {
|
||||
this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: candidate.candidate });
|
||||
}
|
||||
|
@ -91,6 +97,10 @@ export class WebRtcPeer {
|
|||
});
|
||||
}
|
||||
|
||||
get id(): string {
|
||||
return this.configuration.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method frees the resources used by WebRtcPeer
|
||||
*/
|
||||
|
@ -310,12 +320,12 @@ export class WebRtcPeer {
|
|||
// Possible network disconnection
|
||||
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
|
||||
logger.warn(msg1);
|
||||
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
||||
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
||||
break;
|
||||
case 'failed':
|
||||
const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"';
|
||||
logger.error(msg2);
|
||||
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
||||
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
||||
break;
|
||||
case 'closed':
|
||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
|
||||
|
|
|
@ -139,6 +139,8 @@ public class ProtocolElements {
|
|||
// ENDTODO
|
||||
|
||||
public static final String VIDEODATA_METHOD = "videoData";
|
||||
|
||||
public static final String ECHO_METHOD = "echo";
|
||||
|
||||
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
|
||||
|
||||
|
@ -150,6 +152,7 @@ public class ProtocolElements {
|
|||
public static final String PARTICIPANTJOINED_VALUE_PARAM = "value";
|
||||
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
|
||||
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
|
||||
public static final String PARTICIPANTJOINED_MEDIASERVER_PARAM = "mediaServer";
|
||||
public static final String PARTICIPANTJOINED_RECORD_PARAM = "record";
|
||||
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
||||
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</parent>
|
||||
|
||||
<artifactId>openvidu-java-client</artifactId>
|
||||
<version>2.17.0</version>
|
||||
<version>2.18.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>OpenVidu Java Client</name>
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -33,5 +33,5 @@
|
|||
"docs": "./generate-docs.sh"
|
||||
},
|
||||
"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
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(error.request);
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(error.message);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -259,10 +259,10 @@ export class OpenVidu {
|
|||
} else if (error.request) {
|
||||
// The request was made but no response was received `error.request` is an instance of XMLHttpRequest
|
||||
// in the browser and an instance of http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -304,10 +304,10 @@ export class OpenVidu {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -352,10 +352,10 @@ export class OpenVidu {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -398,10 +398,10 @@ export class OpenVidu {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -479,11 +479,9 @@ export class OpenVidu {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(error);
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
|
@ -649,10 +647,10 @@ export class OpenVidu {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -668,7 +666,6 @@ export class OpenVidu {
|
|||
try {
|
||||
url = new URL(this.hostname);
|
||||
} catch (error) {
|
||||
console.error('URL format incorrect', error);
|
||||
throw new Error('URL format incorrect: ' + error);
|
||||
}
|
||||
this.host = url.protocol + '//' + url.host;
|
||||
|
|
|
@ -511,11 +511,9 @@ export class Session {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
});
|
||||
|
@ -642,11 +640,9 @@ export class Session {
|
|||
// The request was made but no response was received
|
||||
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
|
||||
// http.ClientRequest in node.js
|
||||
console.error(error.request);
|
||||
reject(new Error(error.request));
|
||||
} else {
|
||||
// Something happened in setting up the request that triggered an Error
|
||||
console.error('Error', error.message);
|
||||
reject(new Error(error.message));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,10 +128,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
|||
|
||||
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
|
||||
# when a codec can not be forced, transcoding will be allowed
|
||||
# Values: VP8, H264, NONE
|
||||
# Default value is VP8
|
||||
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||
|
||||
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
||||
# Values: true | false
|
||||
# Default value is false
|
||||
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ services:
|
|||
#
|
||||
# Default Application
|
||||
#
|
||||
# Openvidu-Call Version: 2.17.0
|
||||
# Openvidu-Call Version: 2.18.0
|
||||
#
|
||||
# --------------------------------------------------------------
|
||||
app:
|
||||
image: openvidu/openvidu-call:2.18.0-beta8
|
||||
image: openvidu/openvidu-call:2.18.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
environment:
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# This file will be overridden when update OpenVidu Platform
|
||||
#
|
||||
# Openvidu Version: 2.17.0
|
||||
# Openvidu Version: 2.18.0
|
||||
#
|
||||
# Installation Mode: On Premises
|
||||
#
|
||||
|
@ -22,7 +22,7 @@ version: '3.1'
|
|||
services:
|
||||
|
||||
openvidu-server:
|
||||
image: openvidu/openvidu-server:2.18.0-dev2
|
||||
image: openvidu/openvidu-server:2.18.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
||||
|
@ -65,7 +65,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
redis:
|
||||
image: openvidu/openvidu-redis:2.0.0
|
||||
image: openvidu/openvidu-redis:3.0.0
|
||||
restart: always
|
||||
network_mode: host
|
||||
environment:
|
||||
|
@ -75,7 +75,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
coturn:
|
||||
image: openvidu/openvidu-coturn:4.0.0-dev2
|
||||
image: openvidu/openvidu-coturn:4.0.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
environment:
|
||||
|
@ -96,7 +96,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
nginx:
|
||||
image: openvidu/openvidu-proxy:6.0.0-dev1
|
||||
image: openvidu/openvidu-proxy:7.0.0-dev1
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
volumes:
|
||||
|
|
|
@ -243,10 +243,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
|||
|
||||
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
|
||||
# when a codec can not be forced, transcoding will be allowed
|
||||
# Values: VP8, H264, NONE
|
||||
# Default value is VP8
|
||||
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||
|
||||
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
||||
# Values: true | false
|
||||
# Default value is false
|
||||
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||
|
||||
|
|
|
@ -9,11 +9,11 @@ services:
|
|||
#
|
||||
# Default Application
|
||||
#
|
||||
# Openvidu-Call Version: 2.17.0
|
||||
# Openvidu-Call Version: 2.18.0
|
||||
#
|
||||
# --------------------------------------------------------------
|
||||
app:
|
||||
image: openvidu/openvidu-call:2.18.0-beta8
|
||||
image: openvidu/openvidu-call:2.18.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
environment:
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# This file will be overridden when update OpenVidu Platform
|
||||
#
|
||||
# Openvidu Version: 2.17.0
|
||||
# Openvidu Version: 2.18.0
|
||||
#
|
||||
# Installation Mode: On Premises
|
||||
#
|
||||
|
@ -22,7 +22,7 @@ version: '3.1'
|
|||
services:
|
||||
|
||||
openvidu-server:
|
||||
image: openvidu/openvidu-server-pro:2.18.0-beta17
|
||||
image: openvidu/openvidu-server-pro:2.18.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
||||
|
@ -76,7 +76,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
redis:
|
||||
image: openvidu/openvidu-redis:2.0.0
|
||||
image: openvidu/openvidu-redis:3.0.0
|
||||
restart: always
|
||||
network_mode: host
|
||||
environment:
|
||||
|
@ -86,7 +86,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
coturn:
|
||||
image: openvidu/openvidu-coturn:4.0.0-dev2
|
||||
image: openvidu/openvidu-coturn:4.0.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
environment:
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
# This docker-compose file coordinates all services of OpenVidu CE Platform.
|
||||
#
|
||||
# Openvidu Version: 2.17.0
|
||||
# Openvidu Version: 2.18.0
|
||||
#
|
||||
# Installation Mode: On Premises
|
||||
#
|
||||
|
@ -16,7 +16,7 @@ version: '3.1'
|
|||
|
||||
services:
|
||||
media-node-controller:
|
||||
image: openvidu/media-node-controller:4.0.0-dev1
|
||||
image: openvidu/media-node-controller:4.0.0
|
||||
restart: always
|
||||
ulimits:
|
||||
core: -1
|
||||
|
|
|
@ -243,10 +243,12 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
|
|||
|
||||
# All sessions of OpenVidu will try to force this codec. If OPENVIDU_STREAMS_ALLOW_TRANSCODING=true
|
||||
# when a codec can not be forced, transcoding will be allowed
|
||||
# Values: VP8, H264, NONE
|
||||
# Default value is VP8
|
||||
# OPENVIDU_STREAMS_FORCED_VIDEO_CODEC=VP8
|
||||
|
||||
# Allow transcoding if codec specified in OPENVIDU_STREAMS_FORCED_VIDEO_CODEC can not be applied
|
||||
# Values: true | false
|
||||
# Default value is false
|
||||
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@ DEBUG=${DEBUG:-false}
|
|||
|
||||
OUTPUT=$(mktemp -t openvidu-autodiscover-XXX --suffix .json)
|
||||
|
||||
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 describe-instances \
|
||||
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 describe-instances \
|
||||
--output text \
|
||||
--filters "Name=instance-state-name,Values=running" \
|
||||
"Name=tag:ov-cluster-member,Values=kms" \
|
||||
"Name=tag:ov-stack-name,Values=${AWS_STACK_NAME}" \
|
||||
"Name=tag:ov-stack-region,Values=${AWS_DEFAULT_REGION}" \
|
||||
--query 'Reservations[*].Instances[*].{id:InstanceId,ip:PrivateIpAddress}' > ${OUTPUT}
|
||||
--query 'Reservations[*].Instances[*].{id:InstanceId,ip:PrivateIpAddress}' > "${OUTPUT}"
|
||||
|
||||
cat ${OUTPUT} | jq --raw-input --slurp 'split("\n") | map(split("\t")) | .[0:-1] | map( { "id": .[0], "ip": .[1] } )'
|
||||
cat "${OUTPUT}" | jq --raw-input --slurp 'split("\n") | map(split("\t")) | .[0:-1] | map( { "id": .[0], "ip": .[1] } )'
|
||||
|
|
|
@ -8,4 +8,4 @@ DEBUG=${DEBUG:-false}
|
|||
ID=$1
|
||||
[ -z "${ID}" ] && { echo "Must provide instance ID"; exit 1; }
|
||||
|
||||
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 terminate-instances --instance-ids ${ID} --output json
|
||||
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 terminate-instances --instance-ids "${ID}" --output json
|
||||
|
|
|
@ -23,7 +23,7 @@ exit_on_error () {
|
|||
|
||||
"UnauthorizedOperation")
|
||||
MSG_COD=$(cat ${ERROUTPUT} | awk -F: '{ print $3 }')
|
||||
MSG_DEC=$(docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} sts decode-authorization-message --encoded-message ${MSG_COD})
|
||||
MSG_DEC=$(docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" sts decode-authorization-message --encoded-message "${MSG_COD}")
|
||||
|
||||
echo -e "Unauthorized " $(cat ${MSG_DEC}) >&2
|
||||
exit 1
|
||||
|
@ -43,21 +43,21 @@ if [[ -n "${CUSTOM_VOLUME_SIZE}" ]]; then
|
|||
AWS_VOLUME_SIZE="${CUSTOM_VOLUME_SIZE}"
|
||||
fi
|
||||
|
||||
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 run-instances \
|
||||
--image-id ${AWS_IMAGE_ID} --count 1 \
|
||||
--instance-type ${AWS_INSTANCE_TYPE} \
|
||||
--key-name ${AWS_KEY_NAME} \
|
||||
--subnet-id ${AWS_SUBNET_ID} \
|
||||
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 run-instances \
|
||||
--image-id "${AWS_IMAGE_ID}" --count 1 \
|
||||
--instance-type "${AWS_INSTANCE_TYPE}" \
|
||||
--key-name "${AWS_KEY_NAME}" \
|
||||
--subnet-id "${AWS_SUBNET_ID}" \
|
||||
--tag-specifications "ResourceType=instance,Tags=[{Key='Name',Value='Kurento Media Server'},{Key='ov-cluster-member',Value='kms'},{Key='ov-stack-name',Value='${AWS_STACK_NAME}'},{Key='ov-stack-region',Value='${AWS_DEFAULT_REGION}'}]" \
|
||||
--iam-instance-profile Name="OpenViduInstanceProfile-${AWS_STACK_NAME}-${AWS_DEFAULT_REGION}" \
|
||||
--block-device-mappings "DeviceName=/dev/sda1,Ebs={DeleteOnTermination=True,VolumeType='gp2',VolumeSize='${AWS_VOLUME_SIZE}'}" \
|
||||
--security-group-ids ${AWS_SECURITY_GROUP} > ${OUTPUT} 2> ${ERROUTPUT}
|
||||
--security-group-ids "${AWS_SECURITY_GROUP}" > "${OUTPUT}" 2> "${ERROUTPUT}"
|
||||
|
||||
docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 wait instance-running --instance-ids $(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
|
||||
docker run --rm amazon/aws-cli:"${AWS_CLI_DOCKER_TAG}" ec2 wait instance-running --instance-ids $(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
|
||||
|
||||
# Generating the output
|
||||
KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
|
||||
KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
|
||||
KMS_IP=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
|
||||
KMS_ID=$(cat "${OUTPUT}" | jq --raw-output ' .Instances[] | .InstanceId')
|
||||
|
||||
jq -n \
|
||||
--arg id "${KMS_ID}" \
|
||||
|
|
|
@ -9,11 +9,11 @@ services:
|
|||
#
|
||||
# Default Application
|
||||
#
|
||||
# Openvidu-Call Version: 2.17.0
|
||||
# Openvidu-Call Version: 2.18.0
|
||||
#
|
||||
# --------------------------------------------------------------
|
||||
app:
|
||||
image: openvidu/openvidu-call:2.18.0-beta8
|
||||
image: openvidu/openvidu-call:2.18.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
environment:
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# This file will be overridden when update OpenVidu Platform
|
||||
#
|
||||
# Openvidu Version: 2.17.0
|
||||
# Openvidu Version: 2.18.0
|
||||
#
|
||||
# Installation Mode: On Premises
|
||||
#
|
||||
|
@ -22,7 +22,7 @@ version: '3.1'
|
|||
services:
|
||||
|
||||
openvidu-server:
|
||||
image: openvidu/openvidu-server-pro:2.18.0-beta17
|
||||
image: openvidu/openvidu-server-pro:2.18.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
entrypoint: ['/usr/local/bin/entrypoint.sh']
|
||||
|
@ -52,7 +52,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
redis:
|
||||
image: openvidu/openvidu-redis:2.0.0
|
||||
image: openvidu/openvidu-redis:3.0.0
|
||||
restart: always
|
||||
network_mode: host
|
||||
environment:
|
||||
|
@ -62,7 +62,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
coturn:
|
||||
image: openvidu/openvidu-coturn:4.0.0-dev2
|
||||
image: openvidu/openvidu-coturn:4.0.0
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
environment:
|
||||
|
@ -84,7 +84,7 @@ services:
|
|||
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
|
||||
|
||||
nginx:
|
||||
image: openvidu/openvidu-proxy:6.0.0-dev1
|
||||
image: openvidu/openvidu-proxy:7.0.0-dev1
|
||||
restart: on-failure
|
||||
network_mode: host
|
||||
volumes:
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
{xframe_options}
|
||||
|
||||
{app_upstream}
|
||||
|
||||
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-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 "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all
|
||||
[ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all
|
||||
[ -z "${XFRAME_SAMEORIGIN}" ] && export XFRAME_SAMEORIGIN=false
|
||||
|
||||
# Show input enviroment variables
|
||||
printf "\n ======================================="
|
||||
|
@ -228,6 +229,12 @@ elif [[ "${WITH_APP}" == "false" ]]; then
|
|||
sed -e '/{app_config}/{r default_nginx_conf/global/app_config_default.conf' -e 'd}' -i /etc/nginx/conf.d/*
|
||||
fi
|
||||
|
||||
if [[ "${XFRAME_SAMEORIGIN}" == "true" ]]; then
|
||||
sed -e '/{xframe_options}/{r default_nginx_conf/global/xframe_sameorigin.conf' -e 'd}' -i /etc/nginx/conf.d/*
|
||||
elif [[ "${XFRAME_SAMEORIGIN}" == "false" ]]; then
|
||||
sed -i '/{xframe_options}/d' /etc/nginx/conf.d/*
|
||||
fi
|
||||
|
||||
if [[ "${SUPPORT_DEPRECATED_API}" == "true" ]]; then
|
||||
sed -e '/{deprecated_api_ce}/{r default_nginx_conf/global/ce/deprecated_api_ce.conf' -e 'd}' -i /etc/nginx/conf.d/*
|
||||
sed -e '/{deprecated_api_pro}/{r default_nginx_conf/global/pro/deprecated_api_pro.conf' -e 'd}' -i /etc/nginx/conf.d/*
|
||||
|
|
|
@ -4,7 +4,7 @@ MAINTAINER info@openvidu.io
|
|||
ARG CHROME_VERSION
|
||||
|
||||
# Install Chrome
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get install -y wget sudo
|
||||
RUN apt-get update && apt-get -y upgrade && apt-get install -y wget sudo fonts-noto
|
||||
RUN wget http://dl.google.com/linux/deb/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
||||
&& apt install -y ./google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
||||
&& rm google-chrome-stable_${CHROME_VERSION}_amd64.deb \
|
||||
|
|
|
@ -14,6 +14,7 @@ RUN apt-get update && apt-get -y upgrade && apt-get install -y \
|
|||
pulseaudio \
|
||||
xvfb \
|
||||
jq \
|
||||
fonts-noto \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install chrome
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<name>OpenVidu Server</name>
|
||||
<version>2.17.0</version>
|
||||
<version>2.18.0</version>
|
||||
<description>OpenVidu Server</description>
|
||||
<url>https://openvidu.io</url>
|
||||
|
||||
|
|
|
@ -7183,6 +7183,11 @@
|
|||
"integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
|
||||
"dev": true
|
||||
},
|
||||
"jsnlog": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/jsnlog/-/jsnlog-2.30.0.tgz",
|
||||
"integrity": "sha512-o3ROQVkhek+dkc7/9TXlB4TNtxUpYsRLOBJHZYk3Vy0B5zRBmfv9tyr56PrjcgEXuy06ARgfLTANY0+ImhzzGA=="
|
||||
},
|
||||
"json-parse-better-errors": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
|
||||
|
@ -8728,14 +8733,15 @@
|
|||
}
|
||||
},
|
||||
"openvidu-browser": {
|
||||
"version": "2.17.0",
|
||||
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.17.0.tgz",
|
||||
"integrity": "sha512-wholLLrs2R1Rf05+QPOH4eIO9LaKO9UvoinGNHU9WoP32L9cU6WeqL0sH3gHhUdiYPqGwTNRPjC3qk/ltjUbDA==",
|
||||
"version": "2.18.0",
|
||||
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.18.0.tgz",
|
||||
"integrity": "sha512-a9HPAG8p2vG9XGThPUbZWPI5sO1OFmrxKQdCZM3RLmasXQa2Lwj7X1Aicsznb7RtDBg/lD8NdNwAQjm4ZDt2Nw==",
|
||||
"requires": {
|
||||
"freeice": "2.2.2",
|
||||
"hark": "1.2.3",
|
||||
"jsnlog": "2.30.0",
|
||||
"platform": "1.3.6",
|
||||
"uuid": "8.3.1",
|
||||
"uuid": "8.3.2",
|
||||
"wolfy87-eventemitter": "5.2.9"
|
||||
}
|
||||
},
|
||||
|
@ -13579,9 +13585,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.1",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz",
|
||||
"integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg=="
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"validate-npm-package-name": {
|
||||
"version": "3.0.0",
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@angular/animations": "11.2.4",
|
||||
"@angular/cdk": "11.2.3",
|
||||
"@angular/common": "11.2.4",
|
||||
"@angular/compiler": "11.2.4",
|
||||
"@angular/core": "11.2.4",
|
||||
"@angular/flex-layout": "11.0.0-beta.33",
|
||||
"@angular/forms": "11.2.4",
|
||||
"@angular/material": "11.2.3",
|
||||
"@angular/platform-browser": "11.2.4",
|
||||
"@angular/platform-browser-dynamic": "11.2.4",
|
||||
"@angular/router": "11.2.4",
|
||||
"core-js": "3.9.1",
|
||||
"jquery": "3.6.0",
|
||||
"openvidu-browser": "2.17.0",
|
||||
"rxjs": "6.6.6",
|
||||
"tslib": "2.1.0",
|
||||
"zone.js": "0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.1102.3",
|
||||
"@angular/cli": "11.2.3",
|
||||
"@angular/compiler-cli": "11.2.4",
|
||||
"@angular/language-service": "11.2.4",
|
||||
"@types/jasmine": "3.6.6",
|
||||
"@types/node": "14.14.32",
|
||||
"codelyzer": "6.0.1",
|
||||
"jasmine-core": "3.6.0",
|
||||
"jasmine-spec-reporter": "6.0.0",
|
||||
"karma": "6.1.2",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-jasmine-html-reporter": "1.5.4",
|
||||
"protractor": "7.0.0",
|
||||
"ts-node": "9.1.1",
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "4.1.5"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
|
||||
"build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
|
||||
"start": "ng serve",
|
||||
"test": "ng test"
|
||||
},
|
||||
"version": "0.0.0"
|
||||
"dependencies": {
|
||||
"@angular/animations": "11.2.4",
|
||||
"@angular/cdk": "11.2.3",
|
||||
"@angular/common": "11.2.4",
|
||||
"@angular/compiler": "11.2.4",
|
||||
"@angular/core": "11.2.4",
|
||||
"@angular/flex-layout": "11.0.0-beta.33",
|
||||
"@angular/forms": "11.2.4",
|
||||
"@angular/material": "11.2.3",
|
||||
"@angular/platform-browser": "11.2.4",
|
||||
"@angular/platform-browser-dynamic": "11.2.4",
|
||||
"@angular/router": "11.2.4",
|
||||
"core-js": "3.9.1",
|
||||
"jquery": "3.6.0",
|
||||
"openvidu-browser": "2.18.0",
|
||||
"rxjs": "6.6.6",
|
||||
"tslib": "2.1.0",
|
||||
"zone.js": "0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.1102.3",
|
||||
"@angular/cli": "11.2.3",
|
||||
"@angular/compiler-cli": "11.2.4",
|
||||
"@angular/language-service": "11.2.4",
|
||||
"@types/jasmine": "3.6.6",
|
||||
"@types/node": "14.14.32",
|
||||
"codelyzer": "6.0.1",
|
||||
"jasmine-core": "3.6.0",
|
||||
"jasmine-spec-reporter": "6.0.0",
|
||||
"karma": "6.1.2",
|
||||
"karma-chrome-launcher": "3.1.0",
|
||||
"karma-coverage-istanbul-reporter": "3.0.3",
|
||||
"karma-jasmine": "4.0.1",
|
||||
"karma-jasmine-html-reporter": "1.5.4",
|
||||
"protractor": "7.0.0",
|
||||
"ts-node": "9.1.1",
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "4.1.5"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"name": "frontend",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "./node_modules/@angular/cli/bin/ng build --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
|
||||
"build-prod": "./node_modules/@angular/cli/bin/ng build --prod --base-href /dashboard/ --output-path ../main/resources/static/dashboard",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points",
|
||||
"start": "ng serve",
|
||||
"test": "ng test"
|
||||
},
|
||||
"version": "0.0.0"
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ import io.openvidu.java.client.VideoCodec;
|
|||
import io.openvidu.server.OpenViduServer;
|
||||
import io.openvidu.server.cdr.CDREventName;
|
||||
import io.openvidu.server.config.Dotenv.DotenvFormatException;
|
||||
import io.openvidu.server.core.MediaServer;
|
||||
import io.openvidu.server.recording.RecordingNotification;
|
||||
import io.openvidu.server.rest.RequestMappings;
|
||||
|
||||
|
@ -246,6 +247,10 @@ public class OpenviduConfig {
|
|||
return false;
|
||||
}
|
||||
|
||||
public MediaServer getMediaServer() {
|
||||
return MediaServer.kurento;
|
||||
}
|
||||
|
||||
public String getOpenViduRecordingPath() {
|
||||
return this.openviduRecordingPath;
|
||||
}
|
||||
|
|
|
@ -19,8 +19,114 @@ package io.openvidu.server.core;
|
|||
|
||||
public enum EndReason {
|
||||
|
||||
unsubscribe, unpublish, disconnect, forceUnpublishByUser, forceUnpublishByServer, forceDisconnectByUser,
|
||||
forceDisconnectByServer, lastParticipantLeft, recordingStoppedByServer, sessionClosedByServer, networkDisconnect,
|
||||
mediaServerDisconnect, mediaServerReconnect, nodeCrashed, openviduServerStopped, automaticStop
|
||||
/**
|
||||
* A user called the RPC operation to unsubscribe from a remote stream. Applies
|
||||
* to webrtcConnectionDestroyed
|
||||
*/
|
||||
unsubscribe,
|
||||
|
||||
/**
|
||||
* A user called the RPC operation to unpublish a local stream. Applies to
|
||||
* webrtcConnectionDestroyed
|
||||
*/
|
||||
unpublish,
|
||||
|
||||
/**
|
||||
* A user called the RPC operation to leave the session. Applies to
|
||||
* webrtcConnectionDestroyed and participantLeft. Can trigger other events with
|
||||
* lastParticipantLeft
|
||||
*/
|
||||
disconnect,
|
||||
|
||||
/**
|
||||
* A user called the RPC operation to force the unpublishing of a remote stream.
|
||||
* Applies to webrtcConnectionDestroyed
|
||||
*/
|
||||
forceUnpublishByUser,
|
||||
|
||||
/**
|
||||
* The server application called the REST operation to force the unpublishing of
|
||||
* a user's stream. Applies to webrtcConnectionDestroyed
|
||||
*/
|
||||
forceUnpublishByServer,
|
||||
|
||||
/**
|
||||
* A user called the RPC operation to force the disconnection of a remote user.
|
||||
* Applies to webrtcConnectionDestroyed and participantLeft. Can trigger other
|
||||
* events with lastParticipantLeft
|
||||
*/
|
||||
forceDisconnectByUser,
|
||||
|
||||
/**
|
||||
* The server application called the REST operation to force the disconnection
|
||||
* of a user. Applies to webrtcConnectionDestroyed and participantLeft. Can
|
||||
* trigger other events with lastParticipantLeft
|
||||
*/
|
||||
forceDisconnectByServer,
|
||||
|
||||
/**
|
||||
* The last participant left the session, which caused the session to be closed.
|
||||
* Applies to webrtcConnectionDestroyed, participantLeft, recordingStatusChanged
|
||||
* and sessionDestroyed. Can be triggered from other events with other end
|
||||
* reasons (disconnect, forceDisconnectByUser, forceDisconnectByServer,
|
||||
* networkDisconnect)
|
||||
*/
|
||||
lastParticipantLeft,
|
||||
|
||||
/**
|
||||
* The server application called the REST operation to stop a recording. Applies
|
||||
* to recordingStatusChanged
|
||||
*/
|
||||
recordingStoppedByServer,
|
||||
|
||||
/**
|
||||
* The server application called the REST operation to close a session. Applies
|
||||
* to webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and
|
||||
* sessionDestroyed
|
||||
*/
|
||||
sessionClosedByServer,
|
||||
|
||||
/**
|
||||
* A user left the session because of a network disconnection. Applies to
|
||||
* webrtcConnectionDestroyed and participantLeft. Can trigger other events with
|
||||
* lastParticipantLeft
|
||||
*/
|
||||
networkDisconnect,
|
||||
|
||||
/**
|
||||
* A media server disconnected. This is reserved for Media Nodes being
|
||||
* gracefully removed from an OpenVidu Pro cluster. Applies to
|
||||
* webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and
|
||||
* sessionDestroyed
|
||||
*/
|
||||
mediaServerDisconnect,
|
||||
|
||||
/**
|
||||
* A media server disconnected, but was able to reconnect again. Nevertheless
|
||||
* all of the media endpoints were destroyed in the process. Applies to
|
||||
* webrtcConnectionDestroyed and recordingStatusChanged
|
||||
*/
|
||||
mediaServerReconnect,
|
||||
|
||||
/**
|
||||
* A node has crashed. For now this means a Media Node has crashed. Applies to
|
||||
* webrtcConnectionDestroyed, participantLeft, recordingStatusChanged and
|
||||
* sessionDestroyed
|
||||
*/
|
||||
nodeCrashed,
|
||||
|
||||
/**
|
||||
* OpenVidu Server has gracefully stopped. This is reserved for OpenVidu Pro
|
||||
* restart operation. Applies to webrtcConnectionDestroyed, participantLeft,
|
||||
* recordingStatusChanged and sessionDestroyed
|
||||
*/
|
||||
openviduServerStopped,
|
||||
|
||||
/**
|
||||
* A recording has been stopped automatically
|
||||
* (https://docs.openvidu.io/en/latest/advanced-features/recording/#automatic-stop-of-recordings).
|
||||
* Applies to recordingStatusChanged
|
||||
*/
|
||||
automaticStop
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_USER_PARAM, participant.getParticipantPublicId());
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_FINALUSERID_PARAM, participant.getFinalUserId());
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_CREATEDAT_PARAM, participant.getActiveAt());
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_METADATA_PARAM, participant.getFullMetadata());
|
||||
result.add(ProtocolElements.PARTICIPANTJOINED_VALUE_PARAM, resultArray);
|
||||
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_SESSION_PARAM, participant.getSessionId());
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_VERSION_PARAM,
|
||||
openviduBuildConfig.getOpenViduServerVersion());
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_MEDIASERVER_PARAM,
|
||||
this.openviduConfig.getMediaServer().name());
|
||||
|
||||
if (participant.getToken() != null) {
|
||||
result.addProperty(ProtocolElements.PARTICIPANTJOINED_RECORD_PARAM, participant.getToken().record());
|
||||
if (participant.getToken().getRole() != null) {
|
||||
|
@ -638,6 +641,10 @@ public class SessionEventsHandler {
|
|||
rpcNotificationService.sendResponse(participant.getParticipantPrivateId(), transactionId, new JsonObject());
|
||||
}
|
||||
|
||||
public void onEcho(String participantPrivateId, Integer transactionId) {
|
||||
rpcNotificationService.sendResponse(participantPrivateId, transactionId, new JsonObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* This handler must be called before cleaning any sessions or recordings hosted
|
||||
* by the crashed Media Node
|
||||
|
|
|
@ -116,15 +116,8 @@ public abstract class SessionManager {
|
|||
public abstract void prepareSubscription(Participant participant, String senderPublicId, boolean reconnect,
|
||||
Integer id);
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
public abstract void subscribe(Participant participant, String senderName, String sdpAnwser, Integer transactionId,
|
||||
boolean is2180);
|
||||
// END TODO
|
||||
|
||||
// TODO: UNCOMMENT ON 2.18.0
|
||||
// public abstract void subscribe(Participant participant, String senderName,
|
||||
// String sdpAnwser, Integer transactionId);
|
||||
// END TODO
|
||||
public abstract void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId,
|
||||
boolean initByServer);
|
||||
|
||||
public abstract void unsubscribe(Participant participant, String senderName, Integer transactionId);
|
||||
|
||||
|
@ -180,13 +173,11 @@ public abstract class SessionManager {
|
|||
public abstract Participant publishIpcam(Session session, MediaOptions mediaOptions,
|
||||
ConnectionProperties connectionProperties) throws Exception;
|
||||
|
||||
public abstract void reconnectStream(Participant participant, String streamId, String sdpOffer,
|
||||
public abstract void reconnectPublisher(Participant participant, String streamId, String sdpOffer,
|
||||
Integer transactionId);
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
public abstract void reconnectStream2170(Participant participant, String streamId, String sdpOffer,
|
||||
Integer transactionId);
|
||||
// END TODO
|
||||
public abstract void reconnectSubscriber(Participant participant, String streamId, String sdpString,
|
||||
Integer transactionId, boolean initByServer);
|
||||
|
||||
public abstract String getParticipantPrivateIdFromStreamId(String sessionId, String streamId)
|
||||
throws OpenViduException;
|
||||
|
@ -194,6 +185,10 @@ public abstract class SessionManager {
|
|||
public abstract void onVideoData(Participant participant, Integer transactionId, Integer height, Integer width,
|
||||
Boolean videoActive, Boolean audioActive);
|
||||
|
||||
public void onEcho(String participantPrivateId, Integer requestId) {
|
||||
sessionEventsHandler.onEcho(participantPrivateId, requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Session given its id
|
||||
*
|
||||
|
@ -308,8 +303,10 @@ public abstract class SessionManager {
|
|||
}
|
||||
|
||||
public Session storeSessionNotActive(String sessionId, SessionProperties sessionProperties) {
|
||||
Session sessionNotActive = new Session(sessionId, sessionProperties, openviduConfig, recordingManager);
|
||||
return this.storeSessionNotActive(sessionNotActive);
|
||||
Session sessionNotActive = this
|
||||
.storeSessionNotActive(new Session(sessionId, sessionProperties, openviduConfig, recordingManager));
|
||||
sessionEventsHandler.onSessionCreated(sessionNotActive);
|
||||
return sessionNotActive;
|
||||
}
|
||||
|
||||
public Session storeSessionNotActive(Session sessionNotActive) {
|
||||
|
|
|
@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
|
@ -220,240 +220,100 @@ public class KurentoParticipant extends Participant {
|
|||
}
|
||||
|
||||
KurentoParticipant kSender = (KurentoParticipant) sender;
|
||||
if (kSender.streaming && kSender.getPublisher() != null) {
|
||||
|
||||
if (kSender.streaming && kSender.getPublisher() != null
|
||||
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
|
||||
|
||||
try {
|
||||
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
|
||||
senderName);
|
||||
|
||||
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
|
||||
|
||||
final Lock closingReadLock = kSender.getPublisher().closingLock.readLock();
|
||||
if (closingReadLock.tryLock()) {
|
||||
try {
|
||||
CountDownLatch subscriberLatch = new CountDownLatch(1);
|
||||
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
|
||||
|
||||
SubscriberEndpoint subscriber = initializeSubscriberEndpoint(kSender);
|
||||
|
||||
try {
|
||||
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Timeout reached when creating subscriber endpoint");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Interrupted when creating subscriber endpoint: " + e.getMessage());
|
||||
}
|
||||
if (oldMediaEndpoint != null) {
|
||||
log.warn(
|
||||
"PARTICIPANT {}: Two threads are trying to create at "
|
||||
+ "the same time a subscriber endpoint for user {}",
|
||||
this.getParticipantPublicId(), senderName);
|
||||
String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher());
|
||||
log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(),
|
||||
sdpOffer);
|
||||
log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}",
|
||||
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||
return sdpOffer;
|
||||
} catch (KurentoServerException e) {
|
||||
log.error("Exception preparing subscriber endpoint for user {}: {}",
|
||||
this.getParticipantPublicId(), e.getMessage());
|
||||
this.subscribers.remove(senderName);
|
||||
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
|
||||
return null;
|
||||
}
|
||||
if (subscriber.getEndpoint() == null) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint");
|
||||
}
|
||||
|
||||
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
|
||||
|
||||
subscriber.setEndpointName(subscriberEndpointName);
|
||||
subscriber.getEndpoint().setName(subscriberEndpointName);
|
||||
subscriber.setStreamId(kSender.getPublisherStreamId());
|
||||
|
||||
endpointConfig.addEndpointListeners(subscriber, "subscriber");
|
||||
|
||||
} catch (OpenViduException e) {
|
||||
this.subscribers.remove(senderName);
|
||||
throw e;
|
||||
} finally {
|
||||
closingReadLock.unlock();
|
||||
}
|
||||
|
||||
log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(),
|
||||
senderName);
|
||||
try {
|
||||
String sdpOffer = subscriber.prepareSubscription(kSender.getPublisher());
|
||||
log.trace("PARTICIPANT {}: Subscribing SdpOffer is {}", this.getParticipantPublicId(), sdpOffer);
|
||||
log.info("PARTICIPANT {}: offer prepared to receive media from {} in room {}",
|
||||
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||
return sdpOffer;
|
||||
} catch (KurentoServerException e) {
|
||||
log.error("Exception preparing subscriber endpoint for user {}: {}", this.getParticipantPublicId(),
|
||||
e.getMessage());
|
||||
this.subscribers.remove(senderName);
|
||||
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
kSender.getPublisher().closingLock.readLock().unlock();
|
||||
}
|
||||
} else {
|
||||
log.error(
|
||||
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
|
||||
senderName, sender.getSessionId(), this.participantPublicId);
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName
|
||||
+ "is closed");
|
||||
}
|
||||
log.error(
|
||||
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
|
||||
senderName, sender.getSessionId(), this.participantPublicId);
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + "is closed");
|
||||
}
|
||||
|
||||
public void receiveMediaFrom2180(Participant sender, String sdpAnswer, boolean silent) {
|
||||
public String receiveMedia(Participant sender, String sdpString, boolean silent, boolean initByServer) {
|
||||
final String senderName = sender.getParticipantPublicId();
|
||||
|
||||
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
|
||||
senderName, this.session.getSessionId());
|
||||
log.trace("PARTICIPANT {}: SdpAnswer for {} is {}", this.getParticipantPublicId(), senderName, sdpAnswer);
|
||||
log.trace("PARTICIPANT {}: Sdp string for {} is {}", this.getParticipantPublicId(), senderName, sdpString);
|
||||
|
||||
if (senderName.equals(this.getParticipantPublicId())) {
|
||||
log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", this.getParticipantPublicId());
|
||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media");
|
||||
}
|
||||
|
||||
KurentoParticipant kSender = (KurentoParticipant) sender;
|
||||
if (kSender.streaming && kSender.getPublisher() != null) {
|
||||
|
||||
if (kSender.streaming && kSender.getPublisher() != null
|
||||
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
|
||||
|
||||
try {
|
||||
final SubscriberEndpoint subscriber = getSubscriber(senderName);
|
||||
if (subscriber.getEndpoint() == null) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint");
|
||||
}
|
||||
|
||||
final Lock closingReadLock = kSender.getPublisher().closingLock.readLock();
|
||||
if (closingReadLock.tryLock()) {
|
||||
try {
|
||||
subscriber.subscribe(sdpAnswer, kSender.getPublisher());
|
||||
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
|
||||
senderName, this.session.getSessionId());
|
||||
|
||||
if (!silent
|
||||
&& !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
||||
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
|
||||
sender.getParticipantPublicId(), subscriber.createdAt());
|
||||
}
|
||||
} catch (KurentoServerException e) {
|
||||
// TODO Check object status when KurentoClient sets this info in the object
|
||||
if (e.getCode() == 40101) {
|
||||
log.warn(
|
||||
"Publisher endpoint was already released when trying to connect a subscriber endpoint to it",
|
||||
e);
|
||||
} else {
|
||||
log.error("Exception connecting subscriber endpoint to publisher endpoint", e);
|
||||
}
|
||||
this.subscribers.remove(senderName);
|
||||
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
|
||||
}
|
||||
} finally {
|
||||
kSender.getPublisher().closingLock.readLock().unlock();
|
||||
}
|
||||
} else {
|
||||
log.error(
|
||||
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
|
||||
senderName, sender.getSessionId(), this.participantPublicId);
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName
|
||||
+ "is closed");
|
||||
}
|
||||
}
|
||||
// If initialized by server SubscriberEndpoint was created on
|
||||
// prepareReceiveMediaFrom. If initialized by client must be created now
|
||||
final SubscriberEndpoint subscriber = initByServer ? getSubscriber(senderName)
|
||||
: initializeSubscriberEndpoint(kSender);
|
||||
|
||||
public String receiveMediaFrom2170(Participant sender, String sdpOffer, boolean silent) {
|
||||
final String senderName = sender.getParticipantPublicId();
|
||||
|
||||
log.info("PARTICIPANT {}: Request to receive media from {} in room {}", this.getParticipantPublicId(),
|
||||
senderName, this.session.getSessionId());
|
||||
log.trace("PARTICIPANT {}: SdpOffer for {} is {}", this.getParticipantPublicId(), senderName, sdpOffer);
|
||||
|
||||
if (senderName.equals(this.getParticipantPublicId())) {
|
||||
log.warn("PARTICIPANT {}: trying to configure loopback by subscribing", this.getParticipantPublicId());
|
||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE, "Can loopback only when publishing media");
|
||||
}
|
||||
|
||||
KurentoParticipant kSender = (KurentoParticipant) sender;
|
||||
|
||||
if (kSender.streaming && kSender.getPublisher() != null
|
||||
&& kSender.getPublisher().closingLock.readLock().tryLock()) {
|
||||
|
||||
try {
|
||||
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
|
||||
senderName);
|
||||
|
||||
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
|
||||
|
||||
try {
|
||||
CountDownLatch subscriberLatch = new CountDownLatch(1);
|
||||
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
|
||||
|
||||
try {
|
||||
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Timeout reached when creating subscriber endpoint");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Interrupted when creating subscriber endpoint: " + e.getMessage());
|
||||
}
|
||||
if (oldMediaEndpoint != null) {
|
||||
log.warn(
|
||||
"PARTICIPANT {}: Two threads are trying to create at "
|
||||
+ "the same time a subscriber endpoint for user {}",
|
||||
this.getParticipantPublicId(), senderName);
|
||||
return null;
|
||||
}
|
||||
if (subscriber.getEndpoint() == null) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint");
|
||||
}
|
||||
|
||||
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
|
||||
|
||||
subscriber.setEndpointName(subscriberEndpointName);
|
||||
subscriber.getEndpoint().setName(subscriberEndpointName);
|
||||
subscriber.setStreamId(kSender.getPublisherStreamId());
|
||||
|
||||
endpointConfig.addEndpointListeners(subscriber, "subscriber");
|
||||
|
||||
} catch (OpenViduException e) {
|
||||
this.subscribers.remove(senderName);
|
||||
throw e;
|
||||
try {
|
||||
String sdpAnswer = subscriber.subscribe(sdpString, kSender.getPublisher());
|
||||
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}",
|
||||
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||
if (!silent && !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID
|
||||
.equals(this.getParticipantPublicId())) {
|
||||
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
|
||||
sender.getParticipantPublicId(), subscriber.createdAt());
|
||||
}
|
||||
return sdpAnswer;
|
||||
} catch (KurentoServerException e) {
|
||||
// TODO Check object status when KurentoClient sets this info in the object
|
||||
if (e.getCode() == 40101) {
|
||||
log.warn(
|
||||
"Publisher endpoint was already released when trying to connect a subscriber endpoint to it",
|
||||
e);
|
||||
} else {
|
||||
log.error("Exception connecting subscriber endpoint to publisher endpoint", e);
|
||||
}
|
||||
this.subscribers.remove(senderName);
|
||||
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
closingReadLock.unlock();
|
||||
}
|
||||
|
||||
log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(),
|
||||
senderName);
|
||||
try {
|
||||
String sdpAnswer = subscriber.subscribe(sdpOffer, kSender.getPublisher());
|
||||
log.trace("PARTICIPANT {}: Subscribing SdpAnswer is {}", this.getParticipantPublicId(), sdpAnswer);
|
||||
log.info("PARTICIPANT {}: Is now receiving video from {} in room {}", this.getParticipantPublicId(),
|
||||
senderName, this.session.getSessionId());
|
||||
|
||||
if (!silent
|
||||
&& !ProtocolElements.RECORDER_PARTICIPANT_PUBLICID.equals(this.getParticipantPublicId())) {
|
||||
endpointConfig.getCdr().recordNewSubscriber(this, sender.getPublisherStreamId(),
|
||||
sender.getParticipantPublicId(), subscriber.createdAt());
|
||||
}
|
||||
|
||||
return sdpAnswer;
|
||||
} catch (KurentoServerException e) {
|
||||
// TODO Check object status when KurentoClient sets this info in the object
|
||||
if (e.getCode() == 40101) {
|
||||
log.warn(
|
||||
"Publisher endpoint was already released when trying to connect a subscriber endpoint to it",
|
||||
e);
|
||||
} else {
|
||||
log.error("Exception connecting subscriber endpoint to publisher endpoint", e);
|
||||
}
|
||||
this.subscribers.remove(senderName);
|
||||
releaseSubscriberEndpoint(senderName, (KurentoParticipant) sender, subscriber, null, false);
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
kSender.getPublisher().closingLock.readLock().unlock();
|
||||
}
|
||||
} else {
|
||||
log.error(
|
||||
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
|
||||
senderName, sender.getSessionId(), this.participantPublicId);
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName
|
||||
+ "is closed");
|
||||
}
|
||||
log.error(
|
||||
"PublisherEndpoint of participant {} of session {} is closed. Participant {} couldn't subscribe to it ",
|
||||
senderName, sender.getSessionId(), this.participantPublicId);
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Unable to create subscriber endpoint. Publisher endpoint of participant " + senderName + "is closed");
|
||||
}
|
||||
|
||||
public void cancelReceivingMedia(KurentoParticipant senderKurentoParticipant, EndReason reason, boolean silent) {
|
||||
|
@ -461,7 +321,8 @@ public class KurentoParticipant extends Participant {
|
|||
final PublisherEndpoint pub = senderKurentoParticipant.publisher;
|
||||
if (pub != null) {
|
||||
try {
|
||||
if (pub.closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
|
||||
final Lock closingWriteLock = pub.closingLock.writeLock();
|
||||
if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) {
|
||||
try {
|
||||
log.info("PARTICIPANT {}: cancel receiving media from {}", this.getParticipantPublicId(),
|
||||
senderName);
|
||||
|
@ -478,7 +339,7 @@ public class KurentoParticipant extends Participant {
|
|||
this.getParticipantPublicId(), senderName, this.session.getSessionId());
|
||||
}
|
||||
} finally {
|
||||
pub.closingLock.writeLock().unlock();
|
||||
closingWriteLock.unlock();
|
||||
}
|
||||
} else {
|
||||
log.error(
|
||||
|
@ -585,15 +446,66 @@ public class KurentoParticipant extends Participant {
|
|||
return this.getParticipantPublicId() + "_" + senderParticipant.getPublisherStreamId();
|
||||
}
|
||||
|
||||
private SubscriberEndpoint initializeSubscriberEndpoint(Participant kSender) {
|
||||
|
||||
String senderName = kSender.getParticipantPublicId();
|
||||
|
||||
log.debug("PARTICIPANT {}: Creating a subscriber endpoint to user {}", this.getParticipantPublicId(),
|
||||
senderName);
|
||||
|
||||
SubscriberEndpoint subscriber = getNewOrExistingSubscriber(senderName);
|
||||
|
||||
try {
|
||||
CountDownLatch subscriberLatch = new CountDownLatch(1);
|
||||
Endpoint oldMediaEndpoint = subscriber.createEndpoint(subscriberLatch);
|
||||
|
||||
try {
|
||||
if (!subscriberLatch.await(KurentoSession.ASYNC_LATCH_TIMEOUT, TimeUnit.SECONDS)) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Timeout reached when creating subscriber endpoint");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE,
|
||||
"Interrupted when creating subscriber endpoint: " + e.getMessage());
|
||||
}
|
||||
if (oldMediaEndpoint != null) {
|
||||
log.warn(
|
||||
"PARTICIPANT {}: Two threads are trying to create at "
|
||||
+ "the same time a subscriber endpoint for user {}",
|
||||
this.getParticipantPublicId(), senderName);
|
||||
return null;
|
||||
}
|
||||
if (subscriber.getEndpoint() == null) {
|
||||
throw new OpenViduException(Code.MEDIA_ENDPOINT_ERROR_CODE, "Unable to create subscriber endpoint");
|
||||
}
|
||||
|
||||
String subscriberEndpointName = calculateSubscriberEndpointName(kSender);
|
||||
|
||||
subscriber.setEndpointName(subscriberEndpointName);
|
||||
subscriber.getEndpoint().setName(subscriberEndpointName);
|
||||
subscriber.setStreamId(kSender.getPublisherStreamId());
|
||||
|
||||
endpointConfig.addEndpointListeners(subscriber, "subscriber");
|
||||
|
||||
} catch (OpenViduException e) {
|
||||
this.subscribers.remove(senderName);
|
||||
throw e;
|
||||
}
|
||||
|
||||
log.debug("PARTICIPANT {}: Created subscriber endpoint for user {}", this.getParticipantPublicId(), senderName);
|
||||
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
private void releasePublisherEndpoint(EndReason reason, Long kmsDisconnectionTime) {
|
||||
if (publisher != null && publisher.getEndpoint() != null) {
|
||||
final ReadWriteLock closingLock = publisher.closingLock;
|
||||
try {
|
||||
if (closingLock.writeLock().tryLock(15, TimeUnit.SECONDS)) {
|
||||
final Lock closingWriteLock = publisher.closingLock.writeLock();
|
||||
if (closingWriteLock.tryLock(15, TimeUnit.SECONDS)) {
|
||||
try {
|
||||
this.releasePublisherEndpointAux(reason, kmsDisconnectionTime);
|
||||
} finally {
|
||||
closingLock.writeLock().unlock();
|
||||
closingWriteLock.unlock();
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
|
|
|
@ -31,6 +31,8 @@ import java.util.concurrent.TimeUnit;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.kurento.client.GenericMediaElement;
|
||||
import org.kurento.client.IceCandidate;
|
||||
|
@ -65,6 +67,7 @@ import io.openvidu.server.core.EndReason;
|
|||
import io.openvidu.server.core.FinalUser;
|
||||
import io.openvidu.server.core.IdentifierPrefixes;
|
||||
import io.openvidu.server.core.MediaOptions;
|
||||
import io.openvidu.server.core.MediaServer;
|
||||
import io.openvidu.server.core.Participant;
|
||||
import io.openvidu.server.core.Session;
|
||||
import io.openvidu.server.core.SessionManager;
|
||||
|
@ -111,9 +114,13 @@ public class KurentoSessionManager extends SessionManager {
|
|||
|
||||
if (sessionNotActive == null && this.isInsecureParticipant(participant.getParticipantPrivateId())) {
|
||||
// Insecure user directly call joinRoom RPC method, without REST API use
|
||||
sessionNotActive = new Session(sessionId, new SessionProperties.Builder()
|
||||
.mediaMode(MediaMode.ROUTED).recordingMode(RecordingMode.ALWAYS).build(), openviduConfig,
|
||||
recordingManager);
|
||||
SessionProperties.Builder builder = new SessionProperties.Builder().mediaMode(MediaMode.ROUTED)
|
||||
.recordingMode(RecordingMode.ALWAYS);
|
||||
// forcedVideoCodec to NONE if mediasoup
|
||||
if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
|
||||
builder.forcedVideoCodec(VideoCodec.NONE);
|
||||
}
|
||||
sessionNotActive = new Session(sessionId, builder.build(), openviduConfig, recordingManager);
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -385,7 +392,7 @@ public class KurentoSessionManager extends SessionManager {
|
|||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
kurentoOptions.sdpOffer = sdpMunging.forceCodec(kurentoOptions.sdpOffer, participant, true, false,
|
||||
isTranscodingAllowed, forcedVideoCodec, false);
|
||||
isTranscodingAllowed, forcedVideoCodec);
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.publish, WebrtcDebugEventType.sdpOfferMunged, kurentoOptions.sdpOffer));
|
||||
}
|
||||
|
@ -573,7 +580,7 @@ public class KurentoSessionManager extends SessionManager {
|
|||
// Modify server's SDPOffer if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, false, false, isTranscodingAllowed,
|
||||
forcedVideoCodec, true);
|
||||
forcedVideoCodec);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
||||
|
@ -594,12 +601,12 @@ public class KurentoSessionManager extends SessionManager {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(Participant participant, String senderName, String sdpAnswer, Integer transactionId,
|
||||
boolean is2180) {
|
||||
public void subscribe(Participant participant, String senderName, String sdpString, Integer transactionId,
|
||||
boolean initByServer) {
|
||||
Session session = null;
|
||||
|
||||
try {
|
||||
log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpAnswer={} ({})", senderName, sdpAnswer,
|
||||
log.debug("Request [SUBSCRIBE] remoteParticipant={} sdpString={} ({})", senderName, sdpString,
|
||||
participant.getParticipantPublicId());
|
||||
|
||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
|
@ -625,48 +632,44 @@ public class KurentoSessionManager extends SessionManager {
|
|||
|
||||
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(senderParticipant);
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
if (is2180) {
|
||||
if (initByServer) {
|
||||
|
||||
// Server initiated negotiation. sdpString is the SDP Answer of the client
|
||||
|
||||
// Client's SDPAnswer to the server's SDPOffer
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpString));
|
||||
|
||||
kParticipant.receiveMediaFrom2180(senderParticipant, sdpAnswer, false);
|
||||
kParticipant.receiveMedia(senderParticipant, sdpString, false, true);
|
||||
sessionEventsHandler.onSubscribe(participant, session, transactionId, null);
|
||||
|
||||
} else {
|
||||
|
||||
// Client initiated negotiation. sdpString is the SDP Offer of the client
|
||||
|
||||
boolean isTranscodingAllowed = session.getSessionProperties().isTranscodingAllowed();
|
||||
VideoCodec forcedVideoCodec = session.getSessionProperties().forcedVideoCodec();
|
||||
String sdpOffer = sdpString;
|
||||
|
||||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
sdpAnswer = sdpMunging.forceCodec(sdpAnswer, participant, false, false, isTranscodingAllowed,
|
||||
forcedVideoCodec, false);
|
||||
sdpOffer = sdpMunging.forceCodec(sdpString, participant, false, false, isTranscodingAllowed,
|
||||
forcedVideoCodec);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpAnswer));
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
||||
}
|
||||
|
||||
String finalSdpAnswer = kParticipant.receiveMediaFrom2170(senderParticipant, sdpAnswer, false);
|
||||
if (finalSdpAnswer == null) {
|
||||
String sdpAnswer = kParticipant.receiveMedia(senderParticipant, sdpOffer, false, false);
|
||||
if (sdpAnswer == null) {
|
||||
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||
"Unable to generate SDP answer when subscribing '" + participant.getParticipantPublicId()
|
||||
+ "' to '" + senderName + "'");
|
||||
}
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, finalSdpAnswer));
|
||||
sessionEventsHandler.onSubscribe(participant, session, finalSdpAnswer, transactionId, null);
|
||||
WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
sessionEventsHandler.onSubscribe(participant, session, sdpAnswer, transactionId, null);
|
||||
}
|
||||
// END TODO
|
||||
|
||||
// TODO: UNCOMMENT ON 2.18.0
|
||||
// CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
// WebrtcDebugEventOperation.subscribe, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
// String remoteSdpAnswer = kParticipant.receiveMediaFrom(senderParticipant, sdpAnswer, false);
|
||||
// sessionEventsHandler.onSubscribe(participant, session, transactionId, null);
|
||||
// END TODO
|
||||
|
||||
} catch (OpenViduException e) {
|
||||
log.error("PARTICIPANT {}: Error subscribing to {}", participant.getParticipantPublicId(), senderName, e);
|
||||
|
@ -774,7 +777,6 @@ public class KurentoSessionManager extends SessionManager {
|
|||
log.info("No session '{}' exists yet. Created one on KMS '{}' with ip '{}'", session.getSessionId(),
|
||||
kms.getId(), kms.getIp());
|
||||
|
||||
sessionEventsHandler.onSessionCreated(session);
|
||||
return session;
|
||||
}
|
||||
|
||||
|
@ -1176,184 +1178,127 @@ public class KurentoSessionManager extends SessionManager {
|
|||
return kParticipant;
|
||||
}
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
@Override
|
||||
public void reconnectStream2170(Participant participant, String streamId, String sdpOffer, Integer transactionId) {
|
||||
public void reconnectPublisher(Participant participant, String streamId, String sdpString, Integer transactionId) {
|
||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
|
||||
reconnectPublisher(kSession, kParticipant, streamId, sdpString, transactionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reconnectSubscriber(Participant participant, String streamId, String sdpString, Integer transactionId,
|
||||
boolean initByServer) {
|
||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
reconnectSubscriber(kSession, kParticipant, streamId, sdpString, transactionId, initByServer);
|
||||
}
|
||||
|
||||
private String mungeSdpOffer(Session kSession, Participant participant, String sdpOffer, boolean isPublisher) {
|
||||
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
|
||||
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
|
||||
|
||||
boolean sdpOfferHasBeenMunged = false;
|
||||
String originalSdpOffer = sdpOffer;
|
||||
|
||||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
sdpOfferHasBeenMunged = true;
|
||||
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
|
||||
forcedVideoCodec, false);
|
||||
return sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
|
||||
forcedVideoCodec);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isPublisher) {
|
||||
private void reconnectPublisher(KurentoSession kSession, KurentoParticipant kParticipant, String streamId,
|
||||
String sdpOffer, Integer transactionId) {
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer));
|
||||
if (sdpOfferHasBeenMunged) {
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
||||
}
|
||||
String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, true);
|
||||
|
||||
// Reconnect publisher
|
||||
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
|
||||
.getMediaOptions();
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, sdpOffer));
|
||||
if (sdpOfferMunged != null) {
|
||||
sdpOffer = sdpOfferMunged;
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
||||
}
|
||||
// Reconnect publisher
|
||||
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher().getMediaOptions();
|
||||
// 1) Disconnect broken PublisherEndpoint from its PassThrough
|
||||
PublisherEndpoint publisher = kParticipant.getPublisher();
|
||||
final PassThrough passThru = publisher.disconnectFromPassThrough();
|
||||
// 2) Destroy the broken PublisherEndpoint and nothing else
|
||||
publisher.cancelStatsLoop.set(true);
|
||||
kParticipant.releaseElement(kParticipant.getParticipantPublicId(), publisher.getEndpoint());
|
||||
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
|
||||
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
|
||||
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
|
||||
String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true);
|
||||
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", kParticipant.getParticipantPublicId(),
|
||||
sdpAnswer);
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, streamId, WebrtcDebugEventIssuer.server,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
sessionEventsHandler.onPublishMedia(kParticipant, kParticipant.getPublisherStreamId(),
|
||||
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
|
||||
new HashSet<Participant>(), transactionId, null);
|
||||
}
|
||||
|
||||
// 1) Disconnect broken PublisherEndpoint from its PassThrough
|
||||
PublisherEndpoint publisher = kParticipant.getPublisher();
|
||||
final PassThrough passThru = publisher.disconnectFromPassThrough();
|
||||
private void reconnectSubscriber(KurentoSession kSession, KurentoParticipant kParticipant, String streamId,
|
||||
String sdpString, Integer transactionId, boolean initByServer) {
|
||||
|
||||
// 2) Destroy the broken PublisherEndpoint and nothing else
|
||||
publisher.cancelStatsLoop.set(true);
|
||||
kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint());
|
||||
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
|
||||
if (senderPrivateId != null) {
|
||||
|
||||
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
|
||||
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
|
||||
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
|
||||
String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true);
|
||||
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(),
|
||||
sdpAnswer);
|
||||
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
|
||||
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.server,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
if (initByServer) {
|
||||
|
||||
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
|
||||
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
|
||||
new HashSet<Participant>(), transactionId, null);
|
||||
// Server initiated negotiation
|
||||
|
||||
} else {
|
||||
final String sdpAnswer = sdpString;
|
||||
|
||||
// Reconnect subscriber
|
||||
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
|
||||
if (senderPrivateId != null) {
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
|
||||
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
|
||||
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
|
||||
kParticipant.receiveMedia(sender, sdpAnswer, true, true);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer,
|
||||
originalSdpOffer));
|
||||
if (sdpOfferHasBeenMunged) {
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
|
||||
kParticipant.getParticipantPublicId(), sdpAnswer);
|
||||
|
||||
sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null);
|
||||
|
||||
} else {
|
||||
|
||||
// Client initiated negotiation
|
||||
|
||||
String sdpOffer = sdpString;
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOffer, sdpOffer));
|
||||
|
||||
String sdpOfferMunged = mungeSdpOffer(kSession, kParticipant, sdpOffer, false);
|
||||
if (sdpOfferMunged != null) {
|
||||
sdpOffer = sdpOfferMunged;
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpOfferMunged,
|
||||
sdpOffer));
|
||||
}
|
||||
|
||||
String sdpAnswer = kParticipant.receiveMediaFrom2170(sender, sdpOffer, true);
|
||||
kParticipant.cancelReceivingMedia(sender, null, true);
|
||||
String sdpAnswer = kParticipant.receiveMedia(sender, sdpOffer, true, false);
|
||||
if (sdpAnswer == null) {
|
||||
throw new OpenViduException(Code.MEDIA_SDP_ERROR_CODE,
|
||||
"Unable to generate SDP answer when reconnecting subscriber to '" + streamId + "'");
|
||||
}
|
||||
|
||||
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
|
||||
participant.getParticipantPublicId(), sdpAnswer);
|
||||
kParticipant.getParticipantPublicId(), sdpAnswer);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
||||
CDR.log(new WebrtcDebugEvent(kParticipant, subscriberEndpointName, WebrtcDebugEventIssuer.server,
|
||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
|
||||
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
|
||||
} else {
|
||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
||||
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
|
||||
sessionEventsHandler.onSubscribe(kParticipant, kSession, sdpAnswer, transactionId, null);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// END TODO
|
||||
|
||||
@Override
|
||||
public void reconnectStream(Participant participant, String streamId, String sdpOfferOrAnswer,
|
||||
Integer transactionId) {
|
||||
KurentoParticipant kParticipant = (KurentoParticipant) participant;
|
||||
KurentoSession kSession = kParticipant.getSession();
|
||||
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
|
||||
|
||||
if (isPublisher) {
|
||||
|
||||
// Reconnect publisher
|
||||
|
||||
String sdpOffer = sdpOfferOrAnswer;
|
||||
|
||||
boolean isTranscodingAllowed = kSession.getSessionProperties().isTranscodingAllowed();
|
||||
VideoCodec forcedVideoCodec = kSession.getSessionProperties().forcedVideoCodec();
|
||||
|
||||
boolean sdpOfferHasBeenMunged = false;
|
||||
final String originalSdpOffer = sdpOffer;
|
||||
|
||||
// Modify sdp if forced codec is defined
|
||||
if (forcedVideoCodec != VideoCodec.NONE && !participant.isIpcam()) {
|
||||
sdpOfferHasBeenMunged = true;
|
||||
sdpOffer = sdpMunging.forceCodec(sdpOffer, participant, isPublisher, true, isTranscodingAllowed,
|
||||
forcedVideoCodec, false);
|
||||
}
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOffer, originalSdpOffer));
|
||||
if (sdpOfferHasBeenMunged) {
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpOfferMunged, sdpOffer));
|
||||
}
|
||||
|
||||
// Reconnect publisher
|
||||
final KurentoMediaOptions kurentoOptions = (KurentoMediaOptions) kParticipant.getPublisher()
|
||||
.getMediaOptions();
|
||||
|
||||
// 1) Disconnect broken PublisherEndpoint from its PassThrough
|
||||
PublisherEndpoint publisher = kParticipant.getPublisher();
|
||||
final PassThrough passThru = publisher.disconnectFromPassThrough();
|
||||
|
||||
// 2) Destroy the broken PublisherEndpoint and nothing else
|
||||
publisher.cancelStatsLoop.set(true);
|
||||
kParticipant.releaseElement(participant.getParticipantPublicId(), publisher.getEndpoint());
|
||||
|
||||
// 3) Create a new PublisherEndpoint connecting it to the previous PassThrough
|
||||
kParticipant.resetPublisherEndpoint(kurentoOptions, passThru);
|
||||
kParticipant.createPublishingEndpoint(kurentoOptions, streamId);
|
||||
String sdpAnswer = kParticipant.publishToRoom(sdpOffer, kurentoOptions.doLoopback, true);
|
||||
log.debug("SDP Answer for publishing reconnection PARTICIPANT {}: {}", participant.getParticipantPublicId(),
|
||||
sdpAnswer);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, streamId, WebrtcDebugEventIssuer.server,
|
||||
WebrtcDebugEventOperation.reconnectPublisher, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
|
||||
sessionEventsHandler.onPublishMedia(participant, participant.getPublisherStreamId(),
|
||||
kParticipant.getPublisher().createdAt(), kSession.getSessionId(), kurentoOptions, sdpAnswer,
|
||||
new HashSet<Participant>(), transactionId, null);
|
||||
|
||||
} else {
|
||||
|
||||
// Reconnect subscriber
|
||||
|
||||
final String sdpAnswer = sdpOfferOrAnswer;
|
||||
|
||||
String senderPrivateId = kSession.getParticipantPrivateIdFromStreamId(streamId);
|
||||
if (senderPrivateId != null) {
|
||||
|
||||
KurentoParticipant sender = (KurentoParticipant) kSession.getParticipantByPrivateId(senderPrivateId);
|
||||
String subscriberEndpointName = kParticipant.calculateSubscriberEndpointName(sender);
|
||||
|
||||
CDR.log(new WebrtcDebugEvent(participant, subscriberEndpointName, WebrtcDebugEventIssuer.client,
|
||||
WebrtcDebugEventOperation.reconnectSubscriber, WebrtcDebugEventType.sdpAnswer, sdpAnswer));
|
||||
|
||||
kParticipant.receiveMediaFrom2180(sender, sdpAnswer, true);
|
||||
|
||||
log.debug("SDP Answer for subscribing reconnection PARTICIPANT {}: {}",
|
||||
participant.getParticipantPublicId(), sdpAnswer);
|
||||
|
||||
sessionEventsHandler.onSubscribe(participant, kSession, sdpAnswer, transactionId, null);
|
||||
|
||||
} else {
|
||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
||||
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
|
||||
}
|
||||
throw new OpenViduException(Code.USER_NOT_STREAMING_ERROR_CODE,
|
||||
"Stream '" + streamId + "' does not exist in Session '" + kSession.getSessionId() + "'");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1444,4 +1389,11 @@ public class KurentoSessionManager extends SessionManager {
|
|||
return lessLoadedKms;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
@Override
|
||||
public void close() {
|
||||
super.close();
|
||||
this.kmsManager.closeAllKurentoClients();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -58,31 +58,24 @@ public class SubscriberEndpoint extends MediaEndpoint {
|
|||
offerOptions.setOfferToReceiveVideo(publisher.getMediaOptions().hasVideo());
|
||||
String sdpOffer = generateOffer(offerOptions);
|
||||
|
||||
gatherCandidates();
|
||||
return sdpOffer;
|
||||
}
|
||||
|
||||
public synchronized String subscribe(String sdpAnswer, PublisherEndpoint publisher) {
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
if (this.createdAt == null) {
|
||||
// 2.17.0
|
||||
public synchronized String subscribe(String sdpString, PublisherEndpoint publisher) {
|
||||
if (this.publisherStreamId == null) {
|
||||
// Client initiated negotiation
|
||||
registerOnIceCandidateEventListener(publisher.getOwner().getParticipantPublicId());
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
String realSdpAnswer = processOffer(sdpAnswer);
|
||||
String realSdpAnswer = processOffer(sdpString);
|
||||
gatherCandidates();
|
||||
publisher.connect(this.getEndpoint(), false);
|
||||
this.publisherStreamId = publisher.getStreamId();
|
||||
return realSdpAnswer;
|
||||
} else {
|
||||
// 2.18.0
|
||||
processAnswer(sdpAnswer);
|
||||
gatherCandidates();
|
||||
return null;
|
||||
// Server initiated negotiation
|
||||
return processAnswer(sdpString);
|
||||
}
|
||||
// END TODO
|
||||
|
||||
// TODO: UNCOMMENT ON 2.18.0
|
||||
// processAnswer(sdpAnswer);
|
||||
// END TODO
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -31,7 +31,6 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener;
|
||||
|
@ -343,8 +342,7 @@ public abstract class KmsManager {
|
|||
@PostConstruct
|
||||
protected abstract void postConstructInitKurentoClients();
|
||||
|
||||
@PreDestroy
|
||||
public void close() {
|
||||
public void closeAllKurentoClients() {
|
||||
log.info("Closing all KurentoClients");
|
||||
this.kmss.values().forEach(kms -> {
|
||||
if (kms.getKurentoClientReconnectTimer() != null) {
|
||||
|
|
|
@ -78,12 +78,18 @@ public class Recording {
|
|||
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
|
||||
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
|
||||
if (RecordingUtils.IS_COMPOSED(outputMode) && hasVideo) {
|
||||
builder.resolution(json.get("resolution").getAsString());
|
||||
builder.frameRate(json.get("frameRate").getAsInt());
|
||||
RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
|
||||
builder.recordingLayout(recordingLayout);
|
||||
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
|
||||
builder.customLayout(json.get("customLayout").getAsString());
|
||||
if (json.has("resolution")) {
|
||||
builder.resolution(json.get("resolution").getAsString());
|
||||
}
|
||||
if (json.has("frameRate")) {
|
||||
builder.frameRate(json.get("frameRate").getAsInt());
|
||||
}
|
||||
if (json.has("recordingLayout")) {
|
||||
RecordingLayout recordingLayout = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
|
||||
builder.recordingLayout(recordingLayout);
|
||||
if (RecordingLayout.CUSTOM.equals(recordingLayout) && json.has("customLayout")) {
|
||||
builder.customLayout(json.get("customLayout").getAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
this.recordingProperties = builder.build();
|
||||
|
|
|
@ -64,6 +64,7 @@ import io.openvidu.java.client.RecordingProperties;
|
|||
import io.openvidu.server.cdr.CallDetailRecord;
|
||||
import io.openvidu.server.config.OpenviduConfig;
|
||||
import io.openvidu.server.core.EndReason;
|
||||
import io.openvidu.server.core.MediaServer;
|
||||
import io.openvidu.server.core.Participant;
|
||||
import io.openvidu.server.core.Session;
|
||||
import io.openvidu.server.core.SessionEventsHandler;
|
||||
|
@ -768,6 +769,8 @@ public class RecordingManager {
|
|||
// Check Kurento Media Server write permissions in recording path
|
||||
if (this.kmsManager.getKmss().isEmpty()) {
|
||||
log.warn("No KMSs were defined in KMS_URIS array. Recording path check aborted");
|
||||
} else if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
|
||||
log.warn("Using mediasoup. Recording path check aborted");
|
||||
} else {
|
||||
Kms kms = null;
|
||||
try {
|
||||
|
|
|
@ -17,12 +17,12 @@
|
|||
|
||||
package io.openvidu.server.rest;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.CrossOrigin;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@Controller
|
||||
@CrossOrigin
|
||||
@RequestMapping(RequestMappings.ACCEPT_CERTIFICATE)
|
||||
public class CertificateRestController {
|
||||
|
|
|
@ -65,6 +65,7 @@ import io.openvidu.java.client.VideoCodec;
|
|||
import io.openvidu.server.config.OpenviduConfig;
|
||||
import io.openvidu.server.core.EndReason;
|
||||
import io.openvidu.server.core.IdentifierPrefixes;
|
||||
import io.openvidu.server.core.MediaServer;
|
||||
import io.openvidu.server.core.Participant;
|
||||
import io.openvidu.server.core.Session;
|
||||
import io.openvidu.server.core.SessionManager;
|
||||
|
@ -120,7 +121,7 @@ public class SessionRestController {
|
|||
}
|
||||
|
||||
Session sessionNotActive = sessionManager.storeSessionNotActive(sessionId, sessionProperties);
|
||||
log.info("New session {} initialized {}", sessionId, this.sessionManager.getSessionsWithNotActive().stream()
|
||||
log.info("New session {} created {}", sessionId, this.sessionManager.getSessionsWithNotActive().stream()
|
||||
.map(Session::getSessionId).collect(Collectors.toList()).toString());
|
||||
|
||||
return new ResponseEntity<>(sessionNotActive.toJson(false, false).toString(), RestUtils.getResponseHeaders(),
|
||||
|
@ -756,10 +757,15 @@ public class SessionRestController {
|
|||
}
|
||||
builder = builder.customSessionId(customSessionId);
|
||||
}
|
||||
if (forcedVideoCodec != null) {
|
||||
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
|
||||
// forcedVideoCodec to NONE if mediasoup
|
||||
if (MediaServer.mediasoup.equals(openviduConfig.getMediaServer())) {
|
||||
builder = builder.forcedVideoCodec(VideoCodec.NONE);
|
||||
} else {
|
||||
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
|
||||
if (forcedVideoCodec != null) {
|
||||
builder = builder.forcedVideoCodec(VideoCodec.valueOf(forcedVideoCodec));
|
||||
} else {
|
||||
builder = builder.forcedVideoCodec(openviduConfig.getOpenviduForcedCodec());
|
||||
}
|
||||
}
|
||||
if (allowTranscoding != null) {
|
||||
builder = builder.allowTranscoding(allowTranscoding);
|
||||
|
|
|
@ -172,6 +172,9 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
|||
case ProtocolElements.VIDEODATA_METHOD:
|
||||
updateVideoData(rpcConnection, request);
|
||||
break;
|
||||
case ProtocolElements.ECHO_METHOD:
|
||||
echo(rpcConnection, request);
|
||||
break;
|
||||
default:
|
||||
log.error("Unrecognized request {}", request);
|
||||
break;
|
||||
|
@ -355,18 +358,7 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
|||
|
||||
String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
|
||||
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
|
||||
boolean reconnect = false;
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
if (request.getParams().has(ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM)) {
|
||||
reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
|
||||
}
|
||||
// END TODO
|
||||
|
||||
// TODO: UNCOMMENT ON 2.18.0
|
||||
// boolean reconnect = getBooleanParam(request,
|
||||
// ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
|
||||
// END TODO
|
||||
boolean reconnect = getBooleanParam(request, ProtocolElements.PREPARERECEIVEVIDEO_RECONNECT_PARAM);
|
||||
|
||||
sessionManager.prepareSubscription(participant, senderPublicId, reconnect, request.getId());
|
||||
}
|
||||
|
@ -382,28 +374,15 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
|||
String senderStreamId = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SENDER_PARAM);
|
||||
String senderPublicId = parseSenderPublicIdFromStreamId(senderStreamId);
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM)) {
|
||||
// 2.17.0: initiative held by browser when subscribing
|
||||
// The request comes with an SDPOffer
|
||||
// Client initiated negotiation (comes with SDP Offer)
|
||||
String sdpOffer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM);
|
||||
sessionManager.subscribe(participant, senderPublicId, sdpOffer, request.getId(), false);
|
||||
} else if (request.getParams().has(ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM)) {
|
||||
// 2.18.0: initiative held by server when subscribing
|
||||
// This is the final call after prepareReceiveVidoFrom, comes with SDPAnswer
|
||||
// Server initiated negotiation (comes with SDP Answer)
|
||||
String sdpAnswer = getStringParam(request, ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM);
|
||||
sessionManager.subscribe(participant, senderPublicId, sdpAnswer, request.getId(), true);
|
||||
}
|
||||
// END TODO
|
||||
|
||||
// TODO: UNCOMMENT ON 2.18.0
|
||||
/*
|
||||
* String sdpAnswer = getStringParam(request,
|
||||
* ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM);
|
||||
* sessionManager.subscribe(participant, senderPublicId, sdpAnswer,
|
||||
* request.getId());
|
||||
*/
|
||||
// END TODO
|
||||
}
|
||||
|
||||
private void unsubscribeFromVideo(RpcConnection rpcConnection, Request<JsonObject> request) {
|
||||
|
@ -667,40 +646,28 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
|||
} catch (OpenViduException e) {
|
||||
return;
|
||||
}
|
||||
|
||||
String streamId = getStringParam(request, ProtocolElements.RECONNECTSTREAM_STREAM_PARAM);
|
||||
boolean isPublisher = streamId.equals(participant.getPublisherStreamId());
|
||||
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
String sdpString = null;
|
||||
if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM)) {
|
||||
// 2.17.0
|
||||
try {
|
||||
String sdpOffer = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM);
|
||||
sessionManager.reconnectStream(participant, streamId, sdpOffer, request.getId());
|
||||
} catch (OpenViduException e) {
|
||||
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
|
||||
new JsonObject(), e);
|
||||
}
|
||||
sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPOFFER_PARAM);
|
||||
} else if (request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM)) {
|
||||
// 2.18.0
|
||||
String sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
|
||||
try {
|
||||
sessionManager.reconnectStream(participant, streamId, sdpString, request.getId());
|
||||
} catch (OpenViduException e) {
|
||||
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
|
||||
new JsonObject(), e);
|
||||
}
|
||||
sdpString = getStringParam(request, ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
|
||||
}
|
||||
// END TODO
|
||||
|
||||
// TODO: UNCOMMENT ON 2.18.0
|
||||
/*
|
||||
* String sdpString = getStringParam(request,
|
||||
* ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM); try {
|
||||
* sessionManager.reconnectStream(participant, streamId, sdpString,
|
||||
* request.getId()); } catch (OpenViduException e) {
|
||||
* this.notificationService.sendErrorResponse(participant.
|
||||
* getParticipantPrivateId(), request.getId(), new JsonObject(), e); }
|
||||
*/
|
||||
// END TODO
|
||||
try {
|
||||
if (isPublisher) {
|
||||
sessionManager.reconnectPublisher(participant, streamId, sdpString, request.getId());
|
||||
} else {
|
||||
boolean initByServer = request.getParams().has(ProtocolElements.RECONNECTSTREAM_SDPSTRING_PARAM);
|
||||
sessionManager.reconnectSubscriber(participant, streamId, sdpString, request.getId(), initByServer);
|
||||
}
|
||||
} catch (OpenViduException e) {
|
||||
this.notificationService.sendErrorResponse(participant.getParticipantPrivateId(), request.getId(),
|
||||
new JsonObject(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVideoData(RpcConnection rpcConnection, Request<JsonObject> request) {
|
||||
|
@ -717,6 +684,10 @@ public class RpcHandler extends DefaultJsonRpcHandler<JsonObject> {
|
|||
}
|
||||
}
|
||||
|
||||
private void echo(RpcConnection rpcConnection, Request<JsonObject> request) {
|
||||
sessionManager.onEcho(rpcConnection.getParticipantPrivateId(), request.getId());
|
||||
}
|
||||
|
||||
public void leaveRoomAfterConnClosed(String participantPrivateId, EndReason reason) {
|
||||
try {
|
||||
sessionManager.evictParticipant(this.sessionManager.getParticipant(participantPrivateId), null, null,
|
||||
|
|
|
@ -68,7 +68,7 @@ public class SDPMunging {
|
|||
* ordering of formats. Browsers (tested with Chrome 84) honor this change and
|
||||
* use the first codec provided in the answer, so this operation actually works.
|
||||
*/
|
||||
public String setCodecPreference(VideoCodec codec, String sdp, boolean applyHeavyMunging) throws OpenViduException {
|
||||
public String setCodecPreference(VideoCodec codec, String sdp) throws OpenViduException {
|
||||
String codecStr = codec.name();
|
||||
log.info("[setCodecPreference] codec: {}", codecStr);
|
||||
|
||||
|
@ -156,9 +156,8 @@ public class SDPMunging {
|
|||
lines[sl] = newLine.toString().trim();
|
||||
}
|
||||
|
||||
if (applyHeavyMunging) {
|
||||
lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines);
|
||||
}
|
||||
lines = cleanLinesWithRemovedCodecs(unusedCodecPts, lines);
|
||||
|
||||
return String.join("\r\n", lines) + "\r\n";
|
||||
}
|
||||
|
||||
|
@ -166,8 +165,7 @@ public class SDPMunging {
|
|||
* Return a SDP modified to force a specific codec
|
||||
*/
|
||||
public String forceCodec(String sdp, Participant participant, boolean isPublisher, boolean isReconnecting,
|
||||
boolean isTranscodingAllowed, VideoCodec forcedVideoCodec, boolean applyHeavyMunging)
|
||||
throws OpenViduException {
|
||||
boolean isTranscodingAllowed, VideoCodec forcedVideoCodec) throws OpenViduException {
|
||||
try {
|
||||
if (supportedVideoCodecs.contains(forcedVideoCodec)) {
|
||||
String mungedSdpOffer;
|
||||
|
@ -178,7 +176,7 @@ public class SDPMunging {
|
|||
participant.getParticipantPublicId(), participant.getSessionId(), isPublisher, !isPublisher,
|
||||
isReconnecting, sdp);
|
||||
|
||||
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp, applyHeavyMunging);
|
||||
mungedSdpOffer = this.setCodecPreference(forcedVideoCodec, sdp);
|
||||
|
||||
log.debug(
|
||||
"PARTICIPANT '{}' in Session '{}'. Is Publisher: '{}'. Is Subscriber: '{}'."
|
||||
|
|
|
@ -88,7 +88,7 @@ public class SDPMungingTest {
|
|||
|
||||
private void initTestsSetCodecPrevalence(VideoCodec codec, String sdpNameFile) throws IOException {
|
||||
this.oldSdp = getSdpFile(sdpNameFile);
|
||||
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp, false);
|
||||
this.newSdp = this.sdpMungin.setCodecPreference(codec, oldSdp);
|
||||
this.forceCodecPayloads = new ArrayList<>();
|
||||
|
||||
// Get all Payload-Type for video Codec
|
||||
|
|
|
@ -344,6 +344,13 @@ public class OpenViduEventManager {
|
|||
return dimension;
|
||||
}
|
||||
|
||||
public void stopVideoTracksOfVideoElement(WebElement videoElement, String parentSelector) {
|
||||
String script = "return (document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ")
|
||||
+ "#" + videoElement.getAttribute("id")
|
||||
+ "').srcObject.getVideoTracks().forEach(track => track.stop()))";
|
||||
((JavascriptExecutor) driver).executeScript(script);
|
||||
}
|
||||
|
||||
private boolean hasAudioTracks(WebElement videoElement, String parentSelector) {
|
||||
String script = "return ((document.querySelector('" + parentSelector + (parentSelector.isEmpty() ? "" : " ")
|
||||
+ "#" + videoElement.getAttribute("id") + "').srcObject.getAudioTracks().length > 0)"
|
||||
|
|
|
@ -481,6 +481,38 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestAppE2eTest {
|
|||
gracefullyLeaveParticipants(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("ExceptionEvent test")
|
||||
void exceptionEventTest() throws Exception {
|
||||
|
||||
setupBrowser("chrome");
|
||||
|
||||
log.info("ExceptionEvent test");
|
||||
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .send-audio-checkbox")).click();
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 .join-btn")).click();
|
||||
|
||||
user.getEventManager().waitUntilEventReaches("streamCreated", 1);
|
||||
user.getEventManager().waitUntilEventReaches("streamPlaying", 1);
|
||||
|
||||
// Stop video track
|
||||
WebElement video = user.getDriver().findElement(By.cssSelector("#openvidu-instance-0 video"));
|
||||
this.user.getEventManager().stopVideoTracksOfVideoElement(video, "#openvidu-instance-0");
|
||||
|
||||
user.getDriver().findElement(By.id("add-user-btn")).click();
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .publish-checkbox")).click();
|
||||
user.getDriver().findElement(By.cssSelector("#openvidu-instance-1 .join-btn")).click();
|
||||
|
||||
user.getEventManager().waitUntilEventReaches("exception", 1);
|
||||
|
||||
Assert.assertTrue("Wrong ExceptionEvent type", user.getDriver()
|
||||
.findElement(By.cssSelector("#openvidu-instance-1 .mat-expansion-panel:last-child .event-content"))
|
||||
.getAttribute("textContent").equals("NO_STREAM_PLAYING_EVENT"));
|
||||
|
||||
gracefullyLeaveParticipants(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Subscribe Unsubscribe")
|
||||
void subscribeUnsubscribeTest() throws Exception {
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@angular/animations": "8.2.14",
|
||||
"@angular/cdk": "8.2.3",
|
||||
"@angular/common": "8.2.14",
|
||||
"@angular/compiler": "8.2.14",
|
||||
"@angular/core": "8.2.14",
|
||||
"@angular/flex-layout": "8.0.0-beta.27",
|
||||
"@angular/forms": "8.2.14",
|
||||
"@angular/http": "7.2.15",
|
||||
"@angular/material": "8.2.3",
|
||||
"@angular/platform-browser": "8.2.14",
|
||||
"@angular/platform-browser-dynamic": "8.2.14",
|
||||
"@angular/router": "8.2.14",
|
||||
"colormap": "2.3.1",
|
||||
"core-js": "3.4.7",
|
||||
"hammerjs": "2.0.8",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"openvidu-browser": "2.17.0",
|
||||
"openvidu-node-client": "2.17.0",
|
||||
"rxjs": "6.5.3",
|
||||
"zone.js": "0.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.803.20",
|
||||
"@angular/cli": "8.3.20",
|
||||
"@angular/compiler-cli": "8.2.14",
|
||||
"@angular/language-service": "8.2.14",
|
||||
"@types/jasmine": "3.5.0",
|
||||
"@types/jasminewd2": "2.0.8",
|
||||
"@types/node": "12.12.14",
|
||||
"codelyzer": "5.2.0",
|
||||
"ts-node": "8.5.4",
|
||||
"tslint": "5.20.1",
|
||||
"typescript": "3.5.3"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"name": "openvidu-testapp",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "ng build",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"test": "ng test"
|
||||
},
|
||||
"version": "2.17.0"
|
||||
}
|
||||
"dependencies": {
|
||||
"@angular/animations": "8.2.14",
|
||||
"@angular/cdk": "8.2.3",
|
||||
"@angular/common": "8.2.14",
|
||||
"@angular/compiler": "8.2.14",
|
||||
"@angular/core": "8.2.14",
|
||||
"@angular/flex-layout": "8.0.0-beta.27",
|
||||
"@angular/forms": "8.2.14",
|
||||
"@angular/http": "7.2.15",
|
||||
"@angular/material": "8.2.3",
|
||||
"@angular/platform-browser": "8.2.14",
|
||||
"@angular/platform-browser-dynamic": "8.2.14",
|
||||
"@angular/router": "8.2.14",
|
||||
"colormap": "2.3.1",
|
||||
"core-js": "3.4.7",
|
||||
"hammerjs": "2.0.8",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"openvidu-browser": "2.18.0",
|
||||
"openvidu-node-client": "2.18.0",
|
||||
"rxjs": "6.5.3",
|
||||
"zone.js": "0.10.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "0.803.20",
|
||||
"@angular/cli": "8.3.20",
|
||||
"@angular/compiler-cli": "8.2.14",
|
||||
"@angular/language-service": "8.2.14",
|
||||
"@types/jasmine": "3.5.0",
|
||||
"@types/jasminewd2": "2.0.8",
|
||||
"@types/node": "12.12.14",
|
||||
"codelyzer": "5.2.0",
|
||||
"ts-node": "8.5.4",
|
||||
"tslint": "5.20.1",
|
||||
"typescript": "3.5.3"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"name": "openvidu-testapp",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "ng build",
|
||||
"e2e": "ng e2e",
|
||||
"lint": "ng lint",
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"test": "ng test"
|
||||
},
|
||||
"version": "2.18.0"
|
||||
}
|
|
@ -6,7 +6,7 @@ import {
|
|||
import {
|
||||
OpenVidu, Session, Subscriber, Publisher, Event, StreamEvent, ConnectionEvent,
|
||||
SessionDisconnectedEvent, SignalEvent, RecordingEvent,
|
||||
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent
|
||||
PublisherSpeakingEvent, PublisherProperties, StreamPropertyChangedEvent, ConnectionPropertyChangedEvent, OpenViduError, NetworkQualityLevelChangedEvent, ExceptionEvent
|
||||
} from 'openvidu-browser';
|
||||
import {
|
||||
OpenVidu as OpenViduAPI,
|
||||
|
@ -138,7 +138,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
publisherStartSpeaking: false,
|
||||
publisherStopSpeaking: false,
|
||||
reconnecting: true,
|
||||
reconnected: true
|
||||
reconnected: true,
|
||||
exception: true
|
||||
};
|
||||
|
||||
// Session properties dialog
|
||||
|
@ -247,8 +248,9 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
signal: false,
|
||||
publisherStartSpeaking: true,
|
||||
publisherStopSpeaking: true,
|
||||
reconnecting: true,
|
||||
reconnected: true
|
||||
reconnecting: false,
|
||||
reconnected: false,
|
||||
exception: false
|
||||
}, true);
|
||||
|
||||
this.session.connect(token, this.clientData)
|
||||
|
@ -508,6 +510,15 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
if (this.sessionEvents.exception !== oldValues.exception || firstTime) {
|
||||
this.session.off('exception');
|
||||
if (this.sessionEvents.exception) {
|
||||
this.session.on('exception', (event: ExceptionEvent) => {
|
||||
this.updateEventList('exception', event.name, event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
syncInitPublisher() {
|
||||
|
@ -665,7 +676,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
publisherStartSpeaking: this.sessionEvents.publisherStartSpeaking,
|
||||
publisherStopSpeaking: this.sessionEvents.publisherStopSpeaking,
|
||||
reconnecting: this.sessionEvents.reconnecting,
|
||||
reconnected: this.sessionEvents.reconnected
|
||||
reconnected: this.sessionEvents.reconnected,
|
||||
exception: this.sessionEvents.exception
|
||||
};
|
||||
|
||||
const dialogRef = this.dialog.open(EventsDialogComponent, {
|
||||
|
@ -699,7 +711,8 @@ export class OpenviduInstanceComponent implements OnInit, OnChanges, OnDestroy {
|
|||
publisherStartSpeaking: result.publisherStartSpeaking,
|
||||
publisherStopSpeaking: result.publisherStopSpeaking,
|
||||
reconnecting: result.reconnecting,
|
||||
reconnected: result.reconnected
|
||||
reconnected: result.reconnected,
|
||||
exception: result.exception
|
||||
};
|
||||
document.getElementById('session-events-btn-' + this.index).classList.remove('cdk-program-focused');
|
||||
});
|
||||
|
|
2
pom.xml
2
pom.xml
|
@ -62,7 +62,7 @@
|
|||
|
||||
<version.webdrivermanager>4.2.2</version.webdrivermanager>
|
||||
|
||||
<version.openvidu.java.client>2.17.0</version.openvidu.java.client>
|
||||
<version.openvidu.java.client>2.18.0</version.openvidu.java.client>
|
||||
<version.openvidu.client>1.1.0</version.openvidu.client>
|
||||
<version.openvidu.test.browsers>1.1.0</version.openvidu.test.browsers>
|
||||
|
||||
|
|
Loading…
Reference in New Issue