Merge branch 'master' into fix-generateOffer

pull/577/head
Juan Navarro 2021-05-19 18:54:51 +02:00
commit 84a5683b56
210 changed files with 41331 additions and 28063 deletions

View File

@ -5,7 +5,7 @@
[![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Docker badge](https://img.shields.io/docker/pulls/openvidu/openvidu-server-kms.svg)](https://hub.docker.com/r/openvidu/openvidu-server-kms) [![Docker badge](https://img.shields.io/docker/pulls/openvidu/openvidu-server-kms.svg)](https://hub.docker.com/r/openvidu/openvidu-server-kms)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu) [![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![Twitter Follow](https://img.shields.io/twitter/follow/openvidu.svg?style=social)](https://twitter.com/openvidu) [![Twitter Follow](https://img.shields.io/twitter/follow/openvidu.svg?style=social)](https://twitter.com/openvidu)
[![][OpenViduLogo]](https://openvidu.io) [![][OpenViduLogo]](https://openvidu.io)
@ -15,6 +15,10 @@ openvidu
Visit [openvidu.io](https://openvidu.io) Visit [openvidu.io](https://openvidu.io)
## Community Forum
Visit [OpenVidu Community Forum](https://openvidu.discourse.group/)
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120 [OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120
## Contributors ## Contributors

View File

@ -15,7 +15,8 @@ module.exports = {
"**/OpenViduInternal/WebRtcStats/WebRtcStats.ts", "**/OpenViduInternal/WebRtcStats/WebRtcStats.ts",
"**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts", "**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts",
"**/OpenViduInternal/ScreenSharing/**", "**/OpenViduInternal/ScreenSharing/**",
"**/OpenViduInternal/KurentoUtils/**" "**/OpenViduInternal/KurentoUtils/**",
"**/OpenViduInternal/Logger/**",
], ],
excludeExternals: true, excludeExternals: true,
excludePrivate: true, excludePrivate: true,

View File

@ -3,13 +3,14 @@
"dependencies": { "dependencies": {
"freeice": "2.2.2", "freeice": "2.2.2",
"hark": "1.2.3", "hark": "1.2.3",
"jsnlog": "2.30.0",
"platform": "1.3.6", "platform": "1.3.6",
"uuid": "8.3.1", "uuid": "8.3.2",
"wolfy87-eventemitter": "5.2.9" "wolfy87-eventemitter": "5.2.9"
}, },
"description": "OpenVidu Browser", "description": "OpenVidu Browser",
"devDependencies": { "devDependencies": {
"@types/node": "14.14.7", "@types/node": "14.14.32",
"@types/platform": "1.3.3", "@types/platform": "1.3.3",
"browserify": "17.0.0", "browserify": "17.0.0",
"grunt": "1.3.0", "grunt": "1.3.0",
@ -21,11 +22,11 @@
"grunt-postcss": "0.9.0", "grunt-postcss": "0.9.0",
"grunt-string-replace": "1.3.1", "grunt-string-replace": "1.3.1",
"grunt-ts": "6.0.0-beta.22", "grunt-ts": "6.0.0-beta.22",
"terser": "5.3.8", "terser": "5.6.0",
"tsify": "5.0.2", "tsify": "5.0.2",
"tslint": "6.1.3", "tslint": "6.1.3",
"typedoc": "0.19.2", "typedoc": "0.19.2",
"typescript": "4.0.5" "typescript": "4.0.7"
}, },
"license": "Apache-2.0", "license": "Apache-2.0",
"main": "lib/index.js", "main": "lib/index.js",
@ -41,5 +42,5 @@
"docs": "./generate-docs.sh" "docs": "./generate-docs.sh"
}, },
"types": "lib/index.d.ts", "types": "lib/index.d.ts",
"version": "2.16.0" "version": "2.17.0"
} }

View File

@ -22,6 +22,7 @@ import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions'; import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer'; import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger'; import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
/** /**
* @hidden * @hidden
@ -142,8 +143,8 @@ export class Connection {
sdpMLineIndex: candidate.sdpMLineIndex sdpMLineIndex: candidate.sdpMLineIndex
}, (error, response) => { }, (error, response) => {
if (error) { if (error) {
logger.error('Error sending ICE candidate: ' logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
+ JSON.stringify(error)); this.session.emitEvent('exception', [new ExceptionEvent(this.session, ExceptionEventName.ICE_CANDIDATE_ERROR, this.session, "There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side", error)]);
} }
}); });
} }

View File

@ -19,7 +19,6 @@ import { Stream } from './Stream';
import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent'; import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError'; import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { ObjMap } from '../OpenViduInternal/Interfaces/Private/ObjMap';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger'; import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
/** /**
@ -66,7 +65,7 @@ export class Filter {
/** /**
* @hidden * @hidden
*/ */
handlers: ObjMap<(event: FilterEvent) => void> = {}; handlers: Map<string, (event: FilterEvent) => void> = new Map();
/** /**
* @hidden * @hidden
@ -90,7 +89,7 @@ export class Filter {
* @param method Name of the method * @param method Name of the method
* @param params Parameters of the method * @param params Parameters of the method
*/ */
execMethod(method: string, params: Object): Promise<any> { execMethod(method: string, params: Object): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info('Executing filter method to stream ' + this.stream.streamId); logger.info('Executing filter method to stream ' + this.stream.streamId);
let stringParams; let stringParams;
@ -138,7 +137,7 @@ export class Filter {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully attached to the filter and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully attached to the filter and rejected with an Error object if not
*/ */
addEventListener(eventType: string, handler: (event: FilterEvent) => void): Promise<any> { addEventListener(eventType: string, handler: (event: FilterEvent) => void): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info('Adding filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId); logger.info('Adding filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
this.stream.session.openvidu.sendRequest( this.stream.session.openvidu.sendRequest(
@ -153,7 +152,7 @@ export class Filter {
reject(error); reject(error);
} }
} else { } else {
this.handlers[eventType] = handler; this.handlers.set(eventType, handler);
logger.info('Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId); logger.info('Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId);
resolve(); resolve();
} }
@ -170,7 +169,7 @@ export class Filter {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully removed from the filter and rejected with an Error object in other case * @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully removed from the filter and rejected with an Error object in other case
*/ */
removeEventListener(eventType: string): Promise<any> { removeEventListener(eventType: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
logger.info('Removing filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId); logger.info('Removing filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
this.stream.session.openvidu.sendRequest( this.stream.session.openvidu.sendRequest(
@ -185,7 +184,7 @@ export class Filter {
reject(error); reject(error);
} }
} else { } else {
delete this.handlers[eventType]; this.handlers.delete(eventType);
logger.info('Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId); logger.info('Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId);
resolve(); resolve();
} }

View File

@ -74,7 +74,7 @@ export class LocalRecorder {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully started and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully started and rejected with an Error object if not
*/ */
record(mimeType?: string): Promise<any> { record(mimeType?: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (typeof MediaRecorder === 'undefined') { if (typeof MediaRecorder === 'undefined') {
@ -146,7 +146,7 @@ export class LocalRecorder {
* Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED` * Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not
*/ */
stop(): Promise<any> { stop(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) { if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) {
@ -168,7 +168,7 @@ export class LocalRecorder {
* Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED` * Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not
*/ */
pause(): Promise<any> { pause(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (this.state !== LocalRecorderState.RECORDING) { if (this.state !== LocalRecorderState.RECORDING) {
@ -187,7 +187,7 @@ export class LocalRecorder {
* Resumes the recording of the Stream. [[state]] property must be `PAUSED`. After method succeeds is set to `RECORDING` * Resumes the recording of the Stream. [[state]] property must be `PAUSED`. After method succeeds is set to `RECORDING`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully resumed and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully resumed and rejected with an Error object if not
*/ */
resume(): Promise<any> { resume(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
if (this.state !== LocalRecorderState.PAUSED) { if (this.state !== LocalRecorderState.PAUSED) {

View File

@ -32,6 +32,7 @@ import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto'; import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing'; import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
import { OpenViduLoggerConfiguration } from "../OpenViduInternal/Logger/OpenViduLoggerConfiguration";
/** /**
* @hidden * @hidden
*/ */
@ -99,6 +100,10 @@ export class OpenVidu {
* @hidden * @hidden
*/ */
role: string; role: string;
/**
* @hidden
*/
finalUserId: string;
/** /**
* @hidden * @hidden
*/ */
@ -106,7 +111,13 @@ export class OpenVidu {
/** /**
* @hidden * @hidden
*/ */
webrtcStatsInterval: number = 0; webrtcStatsInterval: number = -1;
/**
* @hidden
*/
sendBrowserLogs: OpenViduLoggerConfiguration = OpenViduLoggerConfiguration.disabled;
/** /**
* @hidden * @hidden
*/ */
@ -116,55 +127,49 @@ export class OpenVidu {
*/ */
ee = new EventEmitter() ee = new EventEmitter()
onOrientationChanged(handler): void {
(<any>window).addEventListener('orientationchange', handler);
}
constructor() { constructor() {
platform = PlatformUtils.getInstance(); platform = PlatformUtils.getInstance();
this.libraryVersion = packageJson.version; this.libraryVersion = packageJson.version;
logger.info("'OpenVidu' initialized"); logger.info("OpenVidu initialized");
logger.info("openvidu-browser version: " + this.libraryVersion); logger.info('Platform detected: ' + platform.getDescription());
logger.info('openvidu-browser version: ' + this.libraryVersion);
if (platform.isMobileDevice()) { if (platform.isMobileDevice() || platform.isReactNative()) {
// Listen to orientationchange only on mobile devices // Listen to orientationchange only on mobile devices
(<any>window).addEventListener('orientationchange', () => { this.onOrientationChanged(() => {
this.publishers.forEach(publisher => { this.publishers.forEach(publisher => {
if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo && !!publisher.stream.streamManager.videos[0]) { if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo) {
this.sendNewVideoDimensionsIfRequired(publisher, 'deviceRotated', 75, 10);
}
});
});
}
}
sendNewVideoDimensionsIfRequired(publisher: Publisher, reason: string, WAIT_INTERVAL: number, MAX_ATTEMPTS: number) {
let attempts = 0; let attempts = 0;
const oldWidth = publisher.stream.videoDimensions.width; const oldWidth = publisher.stream.videoDimensions.width;
const oldHeight = publisher.stream.videoDimensions.height; const oldHeight = publisher.stream.videoDimensions.height;
const getNewVideoDimensions = (): Promise<{ newWidth: number, newHeight: number }> => { const repeatUntilChangeOrMaxAttempts: NodeJS.Timeout = setInterval(() => {
return new Promise((resolve, reject) => {
if (platform.isIonicIos()) {
// iOS Ionic. Limitation: must get new dimensions from an existing video element already inserted into DOM
resolve({
newWidth: publisher.stream.streamManager.videos[0].video.videoWidth,
newHeight: publisher.stream.streamManager.videos[0].video.videoHeight
});
} else {
// Rest of platforms
// New resolution got from different places for Chrome and Firefox. Chrome needs a videoWidth and videoHeight of a videoElement.
// Firefox needs getSettings from the videoTrack
const firefoxSettings = publisher.stream.getMediaStream().getVideoTracks()[0].getSettings();
const newWidth = <number>((platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) ? firefoxSettings.width : publisher.videoReference.videoWidth);
const newHeight = <number>((platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) ? firefoxSettings.height : publisher.videoReference.videoHeight);
resolve({ newWidth, newHeight });
}
});
};
const repeatUntilChange = setInterval(() => {
getNewVideoDimensions().then(newDimensions => {
sendStreamPropertyChangedEvent(oldWidth, oldHeight, newDimensions.newWidth, newDimensions.newHeight);
});
}, 75);
const sendStreamPropertyChangedEvent = (oldWidth, oldHeight, newWidth, newHeight) => {
attempts++; attempts++;
if (attempts > 10) { if (attempts > MAX_ATTEMPTS) {
clearTimeout(repeatUntilChange); clearTimeout(repeatUntilChangeOrMaxAttempts);
} }
if (newWidth !== oldWidth || newHeight !== oldHeight) { publisher.getVideoDimensions(publisher.stream.getMediaStream()).then(newDimensions => {
if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
clearTimeout(repeatUntilChangeOrMaxAttempts);
this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);
}
});
}, WAIT_INTERVAL);
}
sendVideoDimensionsChangedEvent(publisher: Publisher, reason: string, oldWidth: number, oldHeight: number, newWidth: number, newHeight: number) {
publisher.stream.videoDimensions = { publisher.stream.videoDimensions = {
width: newWidth || 0, width: newWidth || 0,
height: newHeight || 0 height: newHeight || 0
@ -175,26 +180,18 @@ export class OpenVidu {
streamId: publisher.stream.streamId, streamId: publisher.stream.streamId,
property: 'videoDimensions', property: 'videoDimensions',
newValue: JSON.stringify(publisher.stream.videoDimensions), newValue: JSON.stringify(publisher.stream.videoDimensions),
reason: 'deviceRotated' reason
}, },
(error, response) => { (error, response) => {
if (error) { if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error); logger.error("Error sending 'streamPropertyChanged' event", error);
} else { } else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, 'deviceRotated')]); this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, reason)]);
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, 'deviceRotated')]); publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, reason)]);
this.session.sendVideoData(publisher); this.session.sendVideoData(publisher);
} }
}); });
clearTimeout(repeatUntilChange);
}
}; };
}
});
});
}
}
/** /**
* Returns new session * Returns new session

View File

@ -51,7 +51,7 @@ let platform: PlatformUtils;
* - accessDialogClosed * - accessDialogClosed
* - streamCreated ([[StreamEvent]]) * - streamCreated ([[StreamEvent]])
* - streamDestroyed ([[StreamEvent]]) * - streamDestroyed ([[StreamEvent]])
* - streamPropertyChanged ([[StreamPropertyChangedEvent]]) * - _All events inherited from [[StreamManager]] class_
*/ */
export class Publisher extends StreamManager { export class Publisher extends StreamManager {
@ -295,16 +295,19 @@ export class Publisher extends StreamManager {
* *
* You can get this new MediaStreamTrack by using the native Web API or simply with [[OpenVidu.getUserMedia]] method. * You can get this new MediaStreamTrack by using the native Web API or simply with [[OpenVidu.getUserMedia]] method.
* *
* **WARNING: this method has been proven to work, but there may be some combinations of published/replaced tracks that may be incompatible between them and break the connection in OpenVidu Server. A complete renegotiation may be the only solution in this case** * **WARNING: this method has been proven to work in the majority of cases, but there may be some combinations of published/replaced tracks that may be incompatible
* between them and break the connection in OpenVidu Server. A complete renegotiation may be the only solution in this case.
* Visit [RTCRtpSender.replaceTrack](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack) documentation for further details.**
* *
* @param track The [MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) object to replace the current one. If it is an audio track, the current audio track will be the replaced one. If it * @param track The [MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) object to replace the current one.
* is a video track, the current video track will be the replaced one. * If it is an audio track, the current audio track will be the replaced one. If it is a video track, the current video track will be the replaced one.
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the track was successfully replaced and rejected with an Error object in other case * @returns A Promise (to which you can optionally subscribe to) that is resolved if the track was successfully replaced and rejected with an Error object in other case
*/ */
replaceTrack(track: MediaStreamTrack): Promise<any> { async replaceTrack(track: MediaStreamTrack): Promise<void> {
const replaceMediaStreamTrack = () => { const replaceTrackInMediaStream = (): Promise<void> => {
return new Promise((resolve, reject) => {
const mediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream(); const mediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
let removedTrack: MediaStreamTrack; let removedTrack: MediaStreamTrack;
if (track.kind === 'video') { if (track.kind === 'video') {
@ -315,52 +318,76 @@ export class Publisher extends StreamManager {
mediaStream.removeTrack(removedTrack); mediaStream.removeTrack(removedTrack);
removedTrack.stop(); removedTrack.stop();
mediaStream.addTrack(track); mediaStream.addTrack(track);
if (track.kind === 'video' && this.stream.isLocalStreamPublished) {
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
this.session.sendVideoData(this.stream.streamManager, 5, true, 5); this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
} }
resolve();
});
}
const replaceTrackInRtcRtpSender = (): Promise<void> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.stream.isLocalStreamPublished) {
// Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders(); const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
let sender: RTCRtpSender | undefined; let sender: RTCRtpSender | undefined;
if (track.kind === 'video') { if (track.kind === 'video') {
sender = senders.find(s => !!s.track && s.track.kind === 'video'); sender = senders.find(s => !!s.track && s.track.kind === 'video');
if (!sender) { if (!sender) {
reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object')) reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
return;
} }
} else if (track.kind === 'audio') { } else if (track.kind === 'audio') {
sender = senders.find(s => !!s.track && s.track.kind === 'audio'); sender = senders.find(s => !!s.track && s.track.kind === 'audio');
if (!sender) { if (!sender) {
reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object')) reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
return;
} }
} else { } else {
reject(new Error('Unknown track kind ' + track.kind)); reject(new Error('Unknown track kind ' + track.kind));
return;
} }
(<any>sender).replaceTrack(track).then(() => { (sender as RTCRtpSender).replaceTrack(track).then(() => {
replaceMediaStreamTrack();
resolve(); resolve();
}).catch(error => { }).catch(error => {
reject(error); reject(error);
}); });
} else {
// Publisher not published. Simply modify local MediaStream tracks
replaceMediaStreamTrack();
resolve();
}
}); });
} }
// Set field "enabled" of the new track to the previous value
const trackOriginalEnabledValue: boolean = track.enabled;
if (track.kind === 'video') {
track.enabled = this.stream.videoActive;
} else if (track.kind === 'audio') {
track.enabled = this.stream.audioActive;
}
try {
if (this.stream.isLocalStreamPublished) {
// Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
// If it has not been published yet, replacing it on the MediaStream object is enough
await replaceTrackInRtcRtpSender();
return await replaceTrackInMediaStream();
} else {
// Publisher not published. Simply replace the track on the local MediaStream
return await replaceTrackInMediaStream();
}
} catch (error) {
track.enabled = trackOriginalEnabledValue;
throw error;
}
}
/* Hidden methods */ /* Hidden methods */
/** /**
* @hidden * @hidden
*/ */
initialize(): Promise<any> { initialize(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let constraints: MediaStreamConstraints = {}; let constraints: MediaStreamConstraints = {};
let constraintsAux: MediaStreamConstraints = {}; let constraintsAux: MediaStreamConstraints = {};
const timeForDialogEvent = 1250; const timeForDialogEvent = 1500;
let startTime; let startTime;
const errorCallback = (openViduError: OpenViduError) => { const errorCallback = (openViduError: OpenViduError) => {
@ -403,102 +430,38 @@ export class Publisher extends StreamManager {
delete this.firstVideoElement; delete this.firstVideoElement;
if (this.stream.isSendVideo()) { if (this.stream.isSendVideo()) {
if (!this.stream.isSendScreen()) { // Has video track
this.getVideoDimensions(mediaStream).then(dimensions => {
if (platform.isIonicIos() || platform.isSafariBrowser()) {
// iOS Ionic or Safari. Limitation: cannot set videoDimensions directly, as the videoReference is not loaded
// if not added to DOM. Must add it to DOM and wait for videoWidth and videoHeight properties to be defined
this.videoReference.style.display = 'none';
document.body.appendChild(this.videoReference);
const videoDimensionsSet = () => {
this.stream.videoDimensions = { this.stream.videoDimensions = {
width: this.videoReference.videoWidth, width: dimensions.width,
height: this.videoReference.videoHeight height: dimensions.height
};
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
document.body.removeChild(this.videoReference);
}; };
let interval; if (this.stream.isSendScreen()) {
this.videoReference.addEventListener('loadedmetadata', () => { // Set interval to listen for screen resize events
if (this.videoReference.videoWidth === 0) {
interval = setInterval(() => {
if (this.videoReference.videoWidth !== 0) {
clearInterval(interval);
videoDimensionsSet();
}
}, 40);
} else {
videoDimensionsSet();
}
});
} else {
// Rest of platforms
// With no screen share, video dimension can be set directly from MediaStream (getSettings)
// Orientation must be checked for mobile devices (width and height are reversed)
const { width, height } = this.getVideoDimensions(mediaStream);
if (platform.isMobileDevice() && (window.innerHeight > window.innerWidth)) {
// Mobile portrait mode
this.stream.videoDimensions = {
width: height || 0,
height: width || 0
};
} else {
this.stream.videoDimensions = {
width: width || 0,
height: height || 0
};
}
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
}
} else {
// With screen share, video dimension must be got from a video element (onloadedmetadata event)
this.videoReference.addEventListener('loadedmetadata', () => {
this.stream.videoDimensions = {
width: this.videoReference.videoWidth,
height: this.videoReference.videoHeight
};
this.screenShareResizeInterval = setInterval(() => { this.screenShareResizeInterval = setInterval(() => {
const firefoxSettings = mediaStream.getVideoTracks()[0].getSettings(); const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
const newWidth = (platform.isChromeBrowser() || platform.isOperaBrowser()) ? this.videoReference.videoWidth : firefoxSettings.width; const newWidth = settings.width;
const newHeight = (platform.isChromeBrowser() || platform.isOperaBrowser()) ? this.videoReference.videoHeight : firefoxSettings.height; const newHeight = settings.height;
if (this.stream.isLocalStreamPublished && if (this.stream.isLocalStreamPublished &&
(newWidth !== this.stream.videoDimensions.width || (newWidth !== this.stream.videoDimensions.width || newHeight !== this.stream.videoDimensions.height)) {
newHeight !== this.stream.videoDimensions.height)) { this.openvidu.sendVideoDimensionsChangedEvent(
const oldValue = { width: this.stream.videoDimensions.width, height: this.stream.videoDimensions.height }; this,
this.stream.videoDimensions = { 'screenResized',
width: newWidth || 0, this.stream.videoDimensions.width,
height: newHeight || 0 this.stream.videoDimensions.height,
}; newWidth || 0,
this.session.openvidu.sendRequest( newHeight || 0
'streamPropertyChanged', );
{
streamId: this.stream.streamId,
property: 'videoDimensions',
newValue: JSON.stringify(this.stream.videoDimensions),
reason: 'screenResized'
},
(error, response) => {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoDimensions', this.stream.videoDimensions, oldValue, 'screenResized')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoDimensions', this.stream.videoDimensions, oldValue, 'screenResized')]);
this.session.sendVideoData(this.stream.streamManager);
} }
}); }, 650);
} }
}, 500);
this.stream.isLocalStreamReadyToPublish = true; this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []); this.stream.ee.emitEvent('stream-ready-to-publish', []);
}); });
}
} else { } else {
// Only audio track (no videoDimensions)
this.stream.isLocalStreamReadyToPublish = true; this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []); this.stream.ee.emitEvent('stream-ready-to-publish', []);
} }
@ -665,9 +628,66 @@ export class Publisher extends StreamManager {
/** /**
* @hidden * @hidden
*
* To obtain the videoDimensions we wait for the video reference to have enough metadata
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
* use the HTMLVideoElement properties videoWidth and videoHeight
*/ */
getVideoDimensions(mediaStream: MediaStream): MediaTrackSettings { getVideoDimensions(mediaStream: MediaStream): Promise<{ width: number, height: number }> {
return mediaStream.getVideoTracks()[0].getSettings(); return new Promise((resolve, reject) => {
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari();
let loadedmetadataListener;
const resolveDimensions = () => {
let width: number;
let height: number;
if (typeof this.stream.getMediaStream().getVideoTracks()[0].getSettings === 'function') {
const settings = this.stream.getMediaStream().getVideoTracks()[0].getSettings();
width = settings.width || this.videoReference.videoWidth;
height = settings.height || this.videoReference.videoHeight;
} else {
logger.warn('MediaStreamTrack does not have getSettings method on ' + platform.getDescription());
width = this.videoReference.videoWidth;
height = this.videoReference.videoHeight;
}
if (loadedmetadataListener != null) {
this.videoReference.removeEventListener('loadedmetadata', loadedmetadataListener);
}
if (requiresDomInsertion) {
document.body.removeChild(this.videoReference);
}
resolve({ width, height });
}
if (this.videoReference.readyState >= 1) {
// The video already has metadata available
// No need of loadedmetadata event
resolveDimensions();
} else {
// The video does not have metadata available yet
// Must listen to loadedmetadata event
loadedmetadataListener = () => {
if (!this.videoReference.videoWidth) {
let interval = setInterval(() => {
if (!!this.videoReference.videoWidth) {
clearInterval(interval);
resolveDimensions();
}
}, 40);
} else {
resolveDimensions();
}
};
this.videoReference.addEventListener('loadedmetadata', loadedmetadataListener);
if (requiresDomInsertion) {
document.body.appendChild(this.videoReference);
}
}
});
} }
/** /**
@ -684,17 +704,15 @@ export class Publisher extends StreamManager {
*/ */
initializeVideoReference(mediaStream: MediaStream) { initializeVideoReference(mediaStream: MediaStream) {
this.videoReference = document.createElement('video'); this.videoReference = document.createElement('video');
this.videoReference.setAttribute('muted', 'true');
this.videoReference.style.display = 'none';
if (platform.isSafariBrowser()) { if (platform.isSafariBrowser()) {
this.videoReference.setAttribute('playsinline', 'true'); this.videoReference.setAttribute('playsinline', 'true');
} }
this.stream.setMediaStream(mediaStream); this.stream.setMediaStream(mediaStream);
if (!!this.firstVideoElement) { if (!!this.firstVideoElement) {
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode); this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
} }
this.videoReference.srcObject = mediaStream; this.videoReference.srcObject = mediaStream;
} }

View File

@ -28,9 +28,9 @@ import { SignalOptions } from '../OpenViduInternal/Interfaces/Public/SignalOptio
import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties'; import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions'; import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
import { LocalConnectionOptions } from '../OpenViduInternal/Interfaces/Private/LocalConnectionOptions'; import { LocalConnectionOptions } from '../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
import { ObjMap } from '../OpenViduInternal/Interfaces/Private/ObjMap';
import { SessionOptions } from '../OpenViduInternal/Interfaces/Private/SessionOptions'; import { SessionOptions } from '../OpenViduInternal/Interfaces/Private/SessionOptions';
import { ConnectionEvent } from '../OpenViduInternal/Events/ConnectionEvent'; import { ConnectionEvent } from '../OpenViduInternal/Events/ConnectionEvent';
import { ExceptionEvent } from '../OpenViduInternal/Events/ExceptionEvent';
import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent'; import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent';
import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent'; import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent';
import { RecordingEvent } from '../OpenViduInternal/Events/RecordingEvent'; import { RecordingEvent } from '../OpenViduInternal/Events/RecordingEvent';
@ -77,6 +77,7 @@ let platform: PlatformUtils;
* - networkQualityLevelChanged ([[NetworkQualityLevelChangedEvent]]) * - networkQualityLevelChanged ([[NetworkQualityLevelChangedEvent]])
* - reconnecting * - reconnecting
* - reconnected * - reconnected
* - exception ([[ExceptionEvent]])
*/ */
export class Session extends EventDispatcher { export class Session extends EventDispatcher {
@ -105,20 +106,12 @@ export class Session extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
remoteStreamsCreated: ObjMap<boolean> = {}; remoteStreamsCreated: Map<string, boolean> = new Map();
/** /**
* @hidden * @hidden
*/ */
isFirstIonicIosSubscriber = true; remoteConnections: Map<string, Connection> = new Map();
/**
* @hidden
*/
countDownForIonicIosSubscribersActive = true;
/**
* @hidden
*/
remoteConnections: ObjMap<Connection> = {};
/** /**
* @hidden * @hidden
*/ */
@ -127,22 +120,6 @@ export class Session extends EventDispatcher {
* @hidden * @hidden
*/ */
options: SessionOptions; options: SessionOptions;
/**
* @hidden
*/
startSpeakingEventsEnabled = false;
/**
* @hidden
*/
startSpeakingEventsEnabledOnce = false;
/**
* @hidden
*/
stopSpeakingEventsEnabled = false;
/**
* @hidden
*/
stopSpeakingEventsEnabledOnce = false;
/** /**
* @hidden * @hidden
*/ */
@ -191,7 +168,7 @@ export class Session extends EventDispatcher {
* @returns A Promise to which you must subscribe that is resolved if the the connection to the Session was successful and rejected with an Error object if not * @returns A Promise to which you must subscribe that is resolved if the the connection to the Session was successful and rejected with an Error object if not
* *
*/ */
connect(token: string, metadata?: any): Promise<any> { connect(token: string, metadata?: any): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.processToken(token); this.processToken(token);
@ -281,13 +258,20 @@ export class Session extends EventDispatcher {
}; };
} }
let completionHandler: (error: Error | undefined) => void; let completionHandler: ((error: Error | undefined) => void) | undefined = undefined;
if (!!param3 && (typeof param3 === 'function')) { if (!!param3 && (typeof param3 === 'function')) {
completionHandler = param3; completionHandler = param3;
} else if (!!param4) { } else if (!!param4) {
completionHandler = param4; completionHandler = param4;
} }
if (!this.sessionConnected()) {
if (completionHandler !== undefined) {
completionHandler(this.notConnectedError());
}
throw this.notConnectedError();
}
logger.info('Subscribing to ' + stream.connection.connectionId); logger.info('Subscribing to ' + stream.connection.connectionId);
stream.subscribe() stream.subscribe()
@ -319,6 +303,10 @@ export class Session extends EventDispatcher {
subscribeAsync(stream: Stream, targetElement: string | HTMLElement, properties?: SubscriberProperties): Promise<Subscriber> { subscribeAsync(stream: Stream, targetElement: string | HTMLElement, properties?: SubscriberProperties): Promise<Subscriber> {
return new Promise<Subscriber>((resolve, reject) => { return new Promise<Subscriber>((resolve, reject) => {
if (!this.sessionConnected()) {
reject(this.notConnectedError());
}
let subscriber: Subscriber; let subscriber: Subscriber;
const callback = (error: Error) => { const callback = (error: Error) => {
@ -349,7 +337,13 @@ export class Session extends EventDispatcher {
* *
* See [[VideoElementEvent]] to learn more * See [[VideoElementEvent]] to learn more
*/ */
unsubscribe(subscriber: Subscriber): void { unsubscribe(subscriber: Subscriber): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.sessionConnected()) {
reject(this.notConnectedError());
} else {
const connectionId = subscriber.stream.connection.connectionId; const connectionId = subscriber.stream.connection.connectionId;
logger.info('Unsubscribing from ' + connectionId); logger.info('Unsubscribing from ' + connectionId);
@ -359,15 +353,19 @@ export class Session extends EventDispatcher {
{ sender: subscriber.stream.connection.connectionId }, { sender: subscriber.stream.connection.connectionId },
(error, response) => { (error, response) => {
if (error) { if (error) {
logger.error('Error unsubscribing from ' + connectionId, error); logger.error('Error unsubscribing from ' + connectionId);
reject(error);
} else { } else {
logger.info('Unsubscribed correctly from ' + connectionId); logger.info('Unsubscribed correctly from ' + connectionId);
} subscriber.stream.streamManager.removeAllVideos();
subscriber.stream.disposeWebRtcPeer(); subscriber.stream.disposeWebRtcPeer();
subscriber.stream.disposeMediaStream(); subscriber.stream.disposeMediaStream();
resolve();
}
} }
); );
subscriber.stream.streamManager.removeAllVideos(); }
});
} }
@ -384,8 +382,13 @@ export class Session extends EventDispatcher {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved only after the publisher was successfully published and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the publisher was successfully published and rejected with an Error object if not
*/ */
publish(publisher: Publisher): Promise<any> { publish(publisher: Publisher): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.sessionConnected()) {
reject(this.notConnectedError());
}
publisher.session = this; publisher.session = this;
publisher.stream.session = this; publisher.stream.session = this;
@ -441,36 +444,47 @@ export class Session extends EventDispatcher {
* *
* See [[StreamEvent]] and [[VideoElementEvent]] to learn more. * See [[StreamEvent]] and [[VideoElementEvent]] to learn more.
*/ */
unpublish(publisher: Publisher): void { unpublish(publisher: Publisher): Promise<void> {
return new Promise((resolve, reject) => {
if (!this.sessionConnected()) {
throw this.notConnectedError()
}
const stream = publisher.stream; const stream = publisher.stream;
if (!stream.connection) { if (!stream.connection) {
logger.error('The associated Connection object of this Publisher is null', stream); reject(new Error('The associated Connection object of this Publisher is null'));
return;
} else if (stream.connection !== this.connection) { } else if (stream.connection !== this.connection) {
logger.error('The associated Connection object of this Publisher is not your local Connection.' + reject(new Error('The associated Connection object of this Publisher is not your local Connection.' +
"Only moderators can force unpublish on remote Streams via 'forceUnpublish' method", stream); "Only moderators can force unpublish on remote Streams via 'forceUnpublish' method"));
return;
} else { } else {
logger.info('Unpublishing local media (' + stream.connection.connectionId + ')'); logger.info('Unpublishing local media (' + stream.connection.connectionId + ')');
this.openvidu.sendRequest('unpublishVideo', (error, response) => { this.openvidu.sendRequest('unpublishVideo', (error, response) => {
if (error) { if (error) {
logger.error(error); reject(error);
} else { } else {
logger.info('Media unpublished correctly'); logger.info('Media unpublished correctly');
}
});
stream.disposeWebRtcPeer(); stream.disposeWebRtcPeer();
if (stream.connection.stream == stream) {
// The Connection.stream may have changed if Session.publish was called with other Publisher
delete stream.connection.stream; delete stream.connection.stream;
}
const streamEvent = new StreamEvent(true, publisher, 'streamDestroyed', publisher.stream, 'unpublish'); const streamEvent = new StreamEvent(true, publisher, 'streamDestroyed', publisher.stream, 'unpublish');
publisher.emitEvent('streamDestroyed', [streamEvent]); publisher.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior(); streamEvent.callDefaultBehavior();
resolve();
} }
});
}
});
} }
@ -491,8 +505,13 @@ export class Session extends EventDispatcher {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved only after the participant has been successfully evicted from the session and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the participant has been successfully evicted from the session and rejected with an Error object if not
*/ */
forceDisconnect(connection: Connection): Promise<any> { forceDisconnect(connection: Connection): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.sessionConnected()) {
reject(this.notConnectedError());
}
logger.info('Forcing disconnect for connection ' + connection.connectionId); logger.info('Forcing disconnect for connection ' + connection.connectionId);
this.openvidu.sendRequest( this.openvidu.sendRequest(
'forceDisconnect', 'forceDisconnect',
@ -530,8 +549,13 @@ export class Session extends EventDispatcher {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved only after the remote Stream has been successfully unpublished from the session and rejected with an Error object if not * @returns A Promise (to which you can optionally subscribe to) that is resolved only after the remote Stream has been successfully unpublished from the session and rejected with an Error object if not
*/ */
forceUnpublish(stream: Stream): Promise<any> { forceUnpublish(stream: Stream): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.sessionConnected()) {
reject(this.notConnectedError());
}
logger.info('Forcing unpublish for stream ' + stream.streamId); logger.info('Forcing unpublish for stream ' + stream.streamId);
this.openvidu.sendRequest( this.openvidu.sendRequest(
'forceUnpublish', 'forceUnpublish',
@ -566,9 +590,13 @@ export class Session extends EventDispatcher {
* mean that openvidu-server could resend the message to all the listed receivers._ * mean that openvidu-server could resend the message to all the listed receivers._
*/ */
/* tslint:disable:no-string-literal */ /* tslint:disable:no-string-literal */
signal(signal: SignalOptions): Promise<any> { signal(signal: SignalOptions): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.sessionConnected()) {
reject(this.notConnectedError());
}
const signalMessage = {}; const signalMessage = {};
if (signal.to && signal.to.length > 0) { if (signal.to && signal.to.length > 0) {
@ -610,28 +638,32 @@ export class Session extends EventDispatcher {
/** /**
* See [[EventDispatcher.on]] * See [[EventDispatcher.on]]
*/ */
on(type: string, handler: (event: SessionDisconnectedEvent | SignalEvent | StreamEvent | ConnectionEvent | PublisherSpeakingEvent | RecordingEvent | NetworkQualityLevelChangedEvent) => void): EventDispatcher { on(type: string, handler: (event: SessionDisconnectedEvent | SignalEvent | StreamEvent | ConnectionEvent | PublisherSpeakingEvent | RecordingEvent | NetworkQualityLevelChangedEvent | ExceptionEvent) => void): EventDispatcher {
super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler); super.onAux(type, "Event '" + type + "' triggered by 'Session'", handler);
if (type === 'publisherStartSpeaking') { if (type === 'publisherStartSpeaking') {
this.startSpeakingEventsEnabled = true; // If there are already available remote streams with audio, enable hark 'speaking' event in all of them
// If there are already available remote streams, enable hark 'speaking' event in all of them this.remoteConnections.forEach(remoteConnection => {
for (const connectionId in this.remoteConnections) { if (!!remoteConnection.stream?.hasAudio) {
const str = this.remoteConnections[connectionId].stream; remoteConnection.stream.enableHarkSpeakingEvent();
if (!!str && str.hasAudio) {
str.enableStartSpeakingEvent();
} }
});
if (!!this.connection?.stream?.hasAudio) {
// If connected to the Session and publishing with audio, also enable hark 'speaking' event for the Publisher
this.connection.stream.enableHarkSpeakingEvent();
} }
} }
if (type === 'publisherStopSpeaking') { if (type === 'publisherStopSpeaking') {
this.stopSpeakingEventsEnabled = true; // If there are already available remote streams with audio, enable hark 'stopped_speaking' event in all of them
// If there are already available remote streams, enable hark 'stopped_speaking' event in all of them this.remoteConnections.forEach(remoteConnection => {
for (const connectionId in this.remoteConnections) { if (!!remoteConnection.stream?.hasAudio) {
const str = this.remoteConnections[connectionId].stream; remoteConnection.stream.enableHarkStoppedSpeakingEvent();
if (!!str && str.hasAudio) {
str.enableStopSpeakingEvent();
} }
});
if (!!this.connection?.stream?.hasAudio) {
// If connected to the Session and publishing with audio, also enable hark 'stopped_speaking' event for the Publisher
this.connection.stream.enableHarkStoppedSpeakingEvent();
} }
} }
@ -642,28 +674,32 @@ export class Session extends EventDispatcher {
/** /**
* See [[EventDispatcher.once]] * See [[EventDispatcher.once]]
*/ */
once(type: string, handler: (event: SessionDisconnectedEvent | SignalEvent | StreamEvent | ConnectionEvent | PublisherSpeakingEvent | RecordingEvent | NetworkQualityLevelChangedEvent) => void): Session { once(type: string, handler: (event: SessionDisconnectedEvent | SignalEvent | StreamEvent | ConnectionEvent | PublisherSpeakingEvent | RecordingEvent | NetworkQualityLevelChangedEvent | ExceptionEvent) => void): Session {
super.onceAux(type, "Event '" + type + "' triggered once by 'Session'", handler); super.onceAux(type, "Event '" + type + "' triggered once by 'Session'", handler);
if (type === 'publisherStartSpeaking') { if (type === 'publisherStartSpeaking') {
this.startSpeakingEventsEnabledOnce = true; // If there are already available remote streams with audio, enable hark 'speaking' event (once) in all of them once
// If there are already available remote streams, enable hark 'speaking' event in all of them once this.remoteConnections.forEach(remoteConnection => {
for (const connectionId in this.remoteConnections) { if (!!remoteConnection.stream?.hasAudio) {
const str = this.remoteConnections[connectionId].stream; remoteConnection.stream.enableOnceHarkSpeakingEvent();
if (!!str && str.hasAudio) {
str.enableOnceStartSpeakingEvent();
} }
});
if (!!this.connection?.stream?.hasAudio) {
// If connected to the Session and publishing with audio, also enable hark 'speaking' event (once) for the Publisher
this.connection.stream.enableOnceHarkSpeakingEvent();
} }
} }
if (type === 'publisherStopSpeaking') { if (type === 'publisherStopSpeaking') {
this.stopSpeakingEventsEnabledOnce = true; // If there are already available remote streams with audio, enable hark 'stopped_speaking' event (once) in all of them once
// If there are already available remote streams, enable hark 'stopped_speaking' event in all of them once this.remoteConnections.forEach(remoteConnection => {
for (const connectionId in this.remoteConnections) { if (!!remoteConnection.stream?.hasAudio) {
const str = this.remoteConnections[connectionId].stream; remoteConnection.stream.enableOnceHarkStoppedSpeakingEvent();
if (!!str && str.hasAudio) {
str.enableOnceStopSpeakingEvent();
} }
});
if (!!this.connection?.stream?.hasAudio) {
// If connected to the Session and publishing with audio, also enable hark 'stopped_speaking' event (once) for the Publisher
this.connection.stream.enableOnceHarkStoppedSpeakingEvent();
} }
} }
@ -674,32 +710,44 @@ export class Session extends EventDispatcher {
/** /**
* See [[EventDispatcher.off]] * See [[EventDispatcher.off]]
*/ */
off(type: string, handler?: (event: SessionDisconnectedEvent | SignalEvent | StreamEvent | ConnectionEvent | PublisherSpeakingEvent | RecordingEvent | NetworkQualityLevelChangedEvent) => void): Session { off(type: string, handler?: (event: SessionDisconnectedEvent | SignalEvent | StreamEvent | ConnectionEvent | PublisherSpeakingEvent | RecordingEvent | NetworkQualityLevelChangedEvent | ExceptionEvent) => void): Session {
super.off(type, handler); super.off(type, handler);
if (type === 'publisherStartSpeaking') { if (type === 'publisherStartSpeaking') {
let remainingStartSpeakingListeners = this.ee.getListeners(type).length; // Check if Session object still has some listener for the event
if (remainingStartSpeakingListeners === 0) { if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false)) {
this.startSpeakingEventsEnabled = false; this.remoteConnections.forEach(remoteConnection => {
// If there are already available remote streams, disable hark in all of them if (!!remoteConnection.stream?.streamManager) {
for (const connectionId in this.remoteConnections) { // Check if Subscriber object still has some listener for the event
const str = this.remoteConnections[connectionId].stream; if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false, remoteConnection.stream.streamManager)) {
if (!!str) { remoteConnection.stream.disableHarkSpeakingEvent(false);
str.disableStartSpeakingEvent(false); }
}
});
if (!!this.connection?.stream?.streamManager) {
// Check if Publisher object still has some listener for the event
if (!this.anySpeechEventListenerEnabled('publisherStartSpeaking', false, this.connection.stream.streamManager)) {
this.connection.stream.disableHarkSpeakingEvent(false);
} }
} }
} }
} }
if (type === 'publisherStopSpeaking') { if (type === 'publisherStopSpeaking') {
let remainingStopSpeakingListeners = this.ee.getListeners(type).length; // Check if Session object still has some listener for the event
if (remainingStopSpeakingListeners === 0) { if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false)) {
this.stopSpeakingEventsEnabled = false; this.remoteConnections.forEach(remoteConnection => {
// If there are already available remote streams, disable hark in all of them if (!!remoteConnection.stream?.streamManager) {
for (const connectionId in this.remoteConnections) { // Check if Subscriber object still has some listener for the event
const str = this.remoteConnections[connectionId].stream; if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false, remoteConnection.stream.streamManager)) {
if (!!str) { remoteConnection.stream.disableHarkStoppedSpeakingEvent(false);
str.disableStopSpeakingEvent(false); }
}
});
if (!!this.connection?.stream?.streamManager) {
// Check if Publisher object still has some listener for the event
if (!this.anySpeechEventListenerEnabled('publisherStopSpeaking', false, this.connection.stream.streamManager)) {
this.connection.stream.disableHarkStoppedSpeakingEvent(false);
} }
} }
} }
@ -722,7 +770,7 @@ export class Session extends EventDispatcher {
}) })
.catch(openViduError => { .catch(openViduError => {
const connection = new Connection(this, response); const connection = new Connection(this, response);
this.remoteConnections[response.id] = connection; this.remoteConnections.set(response.id, connection);
this.ee.emitEvent('connectionCreated', [new ConnectionEvent(false, this, 'connectionCreated', connection, '')]); this.ee.emitEvent('connectionCreated', [new ConnectionEvent(false, this, 'connectionCreated', connection, '')]);
}); });
} }
@ -731,10 +779,9 @@ export class Session extends EventDispatcher {
* @hidden * @hidden
*/ */
onParticipantLeft(msg): void { onParticipantLeft(msg): void {
this.getRemoteConnection(msg.connectionId, 'Remote connection ' + msg.connectionId + " unknown when 'onParticipantLeft'. " +
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
.then(connection => { if (this.remoteConnections.size > 0) {
this.getRemoteConnection(msg.connectionId).then(connection => {
if (!!connection.stream) { if (!!connection.stream) {
const stream = connection.stream; const stream = connection.stream;
@ -742,20 +789,16 @@ export class Session extends EventDispatcher {
this.ee.emitEvent('streamDestroyed', [streamEvent]); this.ee.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior(); streamEvent.callDefaultBehavior();
delete this.remoteStreamsCreated[stream.streamId]; this.remoteStreamsCreated.delete(stream.streamId);
if (Object.keys(this.remoteStreamsCreated).length === 0) {
this.isFirstIonicIosSubscriber = true;
this.countDownForIonicIosSubscribersActive = true;
} }
} this.remoteConnections.delete(connection.connectionId);
delete this.remoteConnections[connection.connectionId];
this.ee.emitEvent('connectionDestroyed', [new ConnectionEvent(false, this, 'connectionDestroyed', connection, msg.reason)]); this.ee.emitEvent('connectionDestroyed', [new ConnectionEvent(false, this, 'connectionDestroyed', connection, msg.reason)]);
}) })
.catch(openViduError => { .catch(openViduError => {
logger.error(openViduError); logger.error(openViduError);
}); });
} }
}
/** /**
* @hidden * @hidden
@ -764,9 +807,9 @@ export class Session extends EventDispatcher {
const afterConnectionFound = (connection) => { const afterConnectionFound = (connection) => {
this.remoteConnections[connection.connectionId] = connection; this.remoteConnections.set(connection.connectionId, connection);
if (!this.remoteStreamsCreated[connection.stream.streamId]) { if (!this.remoteStreamsCreated.get(connection.stream.streamId)) {
// Avoid race condition between stream.subscribe() in "onParticipantPublished" and in "joinRoom" rpc callback // Avoid race condition between stream.subscribe() in "onParticipantPublished" and in "joinRoom" rpc callback
// This condition is false if openvidu-server sends "participantPublished" event to a subscriber participant that has // This condition is false if openvidu-server sends "participantPublished" event to a subscriber participant that has
// already subscribed to certain stream in the callback of "joinRoom" method // already subscribed to certain stream in the callback of "joinRoom" method
@ -774,14 +817,13 @@ export class Session extends EventDispatcher {
this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', connection.stream, '')]); this.ee.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', connection.stream, '')]);
} }
this.remoteStreamsCreated[connection.stream.streamId] = true; this.remoteStreamsCreated.set(connection.stream.streamId, true);
}; };
// Get the existing Connection created on 'onParticipantJoined' for // Get the existing Connection created on 'onParticipantJoined' for
// existing participants or create a new one for new participants // existing participants or create a new one for new participants
let connection: Connection; let connection: Connection;
this.getRemoteConnection(response.id, "Remote connection '" + response.id + "' unknown when 'onParticipantPublished'. " + this.getRemoteConnection(response.id)
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
.then(con => { .then(con => {
// Update existing Connection // Update existing Connection
@ -806,8 +848,7 @@ export class Session extends EventDispatcher {
// Your stream has been forcedly unpublished from the session // Your stream has been forcedly unpublished from the session
this.stopPublisherStream(msg.reason); this.stopPublisherStream(msg.reason);
} else { } else {
this.getRemoteConnection(msg.connectionId, "Remote connection '" + msg.connectionId + "' unknown when 'onParticipantUnpublished'. " + this.getRemoteConnection(msg.connectionId)
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
.then(connection => { .then(connection => {
@ -817,12 +858,7 @@ export class Session extends EventDispatcher {
// Deleting the remote stream // Deleting the remote stream
const streamId: string = connection.stream!.streamId; const streamId: string = connection.stream!.streamId;
delete this.remoteStreamsCreated[streamId]; this.remoteStreamsCreated.delete(streamId);
if (Object.keys(this.remoteStreamsCreated).length === 0) {
this.isFirstIonicIosSubscriber = true;
this.countDownForIonicIosSubscribersActive = true;
}
connection.removeStream(streamId); connection.removeStream(streamId);
}) })
@ -855,8 +891,8 @@ export class Session extends EventDispatcher {
if (!!msg.from) { if (!!msg.from) {
// Signal sent by other client // Signal sent by other client
this.getConnection(msg.from, "Connection '" + msg.from + "' unknow when 'onNewMessage'. Existing remote connections: " this.getConnection(msg.from, "Connection '" + msg.from + "' unknown when 'onNewMessage'. Existing remote connections: "
+ JSON.stringify(Object.keys(this.remoteConnections)) + '. Existing local connection: ' + this.connection.connectionId) + JSON.stringify(this.remoteConnections.keys()) + '. Existing local connection: ' + this.connection.connectionId)
.then(connection => { .then(connection => {
this.ee.emitEvent('signal', [new SignalEvent(this, strippedType, msg.data, connection)]); this.ee.emitEvent('signal', [new SignalEvent(this, strippedType, msg.data, connection)]);
@ -929,8 +965,7 @@ export class Session extends EventDispatcher {
// Your stream has been forcedly changed (filter feature) // Your stream has been forcedly changed (filter feature)
callback(this.connection); callback(this.connection);
} else { } else {
this.getRemoteConnection(msg.connectionId, 'Remote connection ' + msg.connectionId + " unknown when 'onStreamPropertyChanged'. " + this.getRemoteConnection(msg.connectionId)
'Existing remote connections: ' + JSON.stringify(Object.keys(this.remoteConnections)))
.then(connection => { .then(connection => {
callback(connection); callback(connection);
}) })
@ -1087,7 +1122,7 @@ export class Session extends EventDispatcher {
.then(connection => { .then(connection => {
logger.info('Filter event dispatched'); logger.info('Filter event dispatched');
const stream: Stream = connection.stream!; const stream: Stream = connection.stream!;
stream.filter!.handlers[response.eventType](new FilterEvent(stream.filter!, response.eventType, response.data)); stream.filter!.handlers.get(response.eventType)?.call(this, new FilterEvent(stream.filter!, response.eventType, response.data));
}); });
} }
@ -1104,13 +1139,13 @@ export class Session extends EventDispatcher {
someReconnection = true; someReconnection = true;
} }
// Re-establish Subscriber streams // Re-establish Subscriber streams
for (let remoteConnection of Object.values(this.remoteConnections)) { this.remoteConnections.forEach(remoteConnection => {
if (!!remoteConnection.stream && remoteConnection.stream.streamIceConnectionStateBroken()) { if (!!remoteConnection.stream && remoteConnection.stream.streamIceConnectionStateBroken()) {
logger.warn('Re-establishing Subscriber ' + remoteConnection.stream.streamId); logger.warn('Re-establishing Subscriber ' + remoteConnection.stream.streamId);
remoteConnection.stream.initWebRtcPeerReceive(true); remoteConnection.stream.initWebRtcPeerReceive(true);
someReconnection = true; someReconnection = true;
} }
} });
if (!someReconnection) { if (!someReconnection) {
logger.info('There were no media streams in need of a reconnection'); logger.info('There were no media streams in need of a reconnection');
} }
@ -1155,6 +1190,7 @@ export class Session extends EventDispatcher {
} else { } else {
logger.warn('You were not connected to the session ' + this.sessionId); logger.warn('You were not connected to the session ' + this.sessionId);
} }
logger.flush();
} }
/** /**
@ -1172,15 +1208,20 @@ export class Session extends EventDispatcher {
return joinParams; return joinParams;
} }
/**
* @hidden
*/
sendVideoData(streamManager: StreamManager, intervalSeconds: number = 1, doInterval: boolean = false, maxLoops: number = 1) { sendVideoData(streamManager: StreamManager, intervalSeconds: number = 1, doInterval: boolean = false, maxLoops: number = 1) {
if ( if (
platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() ||
platform.isOperaMobileBrowser() || platform.isEdgeBrowser() || platform.isElectron() || platform.isOperaMobileBrowser() || platform.isEdgeBrowser() || platform.isEdgeMobileBrowser() || platform.isElectron() ||
(platform.isSafariBrowser() && !platform.isIonicIos()) || platform.isAndroidBrowser() || (platform.isSafariBrowser() && !platform.isIonicIos()) || platform.isAndroidBrowser() ||
platform.isSamsungBrowser() || platform.isIonicAndroid() || (platform.isIPhoneOrIPad() && platform.isIOSWithSafari()) platform.isSamsungBrowser() || platform.isIonicAndroid() || platform.isIOSWithSafari()
) { ) {
const obtainAndSendVideo = async () => { const obtainAndSendVideo = async () => {
const statsMap = await streamManager.stream.getRTCPeerConnection().getStats(); const pc = streamManager.stream.getRTCPeerConnection();
if (pc.connectionState === 'connected') {
const statsMap = await pc.getStats();
const arr: any[] = []; const arr: any[] = [];
statsMap.forEach(stats => { statsMap.forEach(stats => {
if (("frameWidth" in stats) && ("frameHeight" in stats) && (arr.length === 0)) { if (("frameWidth" in stats) && ("frameHeight" in stats) && (arr.length === 0)) {
@ -1200,13 +1241,14 @@ export class Session extends EventDispatcher {
}); });
} }
} }
}
if (doInterval) { if (doInterval) {
let loops = 1; let loops = 1;
this.videoDataInterval = setInterval(() => { this.videoDataInterval = setInterval(() => {
if (loops < maxLoops) { if (loops < maxLoops) {
loops++; loops++;
obtainAndSendVideo(); obtainAndSendVideo();
}else { } else {
clearInterval(this.videoDataInterval); clearInterval(this.videoDataInterval);
} }
}, intervalSeconds * 1000); }, intervalSeconds * 1000);
@ -1230,9 +1272,44 @@ export class Session extends EventDispatcher {
} }
} }
/**
* @hidden
*/
sessionConnected() {
return this.connection != null;
}
/**
* @hidden
*/
notConnectedError(): OpenViduError {
return new OpenViduError(OpenViduErrorName.OPENVIDU_NOT_CONNECTED, "There is no connection to the session. Method 'Session.connect' must be successfully completed first");
}
/**
* @hidden
*/
anySpeechEventListenerEnabled(event: string, onlyOnce: boolean, streamManager?: StreamManager): boolean {
let handlersInSession = this.ee.getListeners(event);
if (onlyOnce) {
handlersInSession = handlersInSession.filter(h => (h as any).once);
}
let listenersInSession = handlersInSession.length;
if (listenersInSession > 0) return true;
let listenersInStreamManager = 0;
if (!!streamManager) {
let handlersInStreamManager = streamManager.ee.getListeners(event);
if (onlyOnce) {
handlersInStreamManager = handlersInStreamManager.filter(h => (h as any).once);
}
listenersInStreamManager = handlersInStreamManager.length;
}
return listenersInStreamManager > 0;
}
/* Private methods */ /* Private methods */
private connectAux(token: string): Promise<any> { private connectAux(token: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.openvidu.startWs((error) => { this.openvidu.startWs((error) => {
if (!!error) { if (!!error) {
@ -1246,8 +1323,12 @@ export class Session extends EventDispatcher {
reject(error); reject(error);
} else { } else {
// Process join room response
this.processJoinRoomResponse(response); this.processJoinRoomResponse(response);
// Configure JSNLogs
OpenViduLogger.configureJSNLog(this.openvidu, token);
// Initialize local Connection object with values returned by openvidu-server // Initialize local Connection object with values returned by openvidu-server
this.connection = new Connection(this, response); this.connection = new Connection(this, response);
@ -1259,10 +1340,10 @@ export class Session extends EventDispatcher {
const existingParticipants: RemoteConnectionOptions[] = response.value; const existingParticipants: RemoteConnectionOptions[] = response.value;
existingParticipants.forEach((remoteConnectionOptions: RemoteConnectionOptions) => { existingParticipants.forEach((remoteConnectionOptions: RemoteConnectionOptions) => {
const connection = new Connection(this, remoteConnectionOptions); const connection = new Connection(this, remoteConnectionOptions);
this.remoteConnections[connection.connectionId] = connection; this.remoteConnections.set(connection.connectionId, connection);
events.connections.push(connection); events.connections.push(connection);
if (!!connection.stream) { if (!!connection.stream) {
this.remoteStreamsCreated[connection.stream.streamId] = true; this.remoteStreamsCreated.set(connection.stream.streamId, true);
events.streams.push(connection.stream); events.streams.push(connection.stream);
} }
}); });
@ -1314,7 +1395,7 @@ export class Session extends EventDispatcher {
protected getConnection(connectionId: string, errorMessage: string): Promise<Connection> { protected getConnection(connectionId: string, errorMessage: string): Promise<Connection> {
return new Promise<Connection>((resolve, reject) => { return new Promise<Connection>((resolve, reject) => {
const connection = this.remoteConnections[connectionId]; const connection = this.remoteConnections.get(connectionId);
if (!!connection) { if (!!connection) {
// Resolve remote connection // Resolve remote connection
resolve(connection); resolve(connection);
@ -1330,14 +1411,17 @@ export class Session extends EventDispatcher {
}); });
} }
private getRemoteConnection(connectionId: string, errorMessage: string): Promise<Connection> { private getRemoteConnection(connectionId: string): Promise<Connection> {
return new Promise<Connection>((resolve, reject) => { return new Promise<Connection>((resolve, reject) => {
const connection = this.remoteConnections[connectionId]; const connection = this.remoteConnections.get(connectionId);
if (!!connection) { if (!!connection) {
// Resolve remote connection // Resolve remote connection
resolve(connection); resolve(connection);
} else { } else {
// Remote connection not found. Reject with OpenViduError // Remote connection not found. Reject with OpenViduError
const errorMessage = 'Remote connection ' + connectionId + " unknown when 'onParticipantLeft'. " +
'Existing remote connections: ' + JSON.stringify(this.remoteConnections.keys());
reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage)); reject(new OpenViduError(OpenViduErrorName.GENERIC_ERROR, errorMessage));
} }
}); });
@ -1369,6 +1453,7 @@ export class Session extends EventDispatcher {
const secret = queryParams['secret']; const secret = queryParams['secret'];
const recorder = queryParams['recorder']; const recorder = queryParams['recorder'];
const webrtcStatsInterval = queryParams['webrtcStatsInterval']; const webrtcStatsInterval = queryParams['webrtcStatsInterval'];
const sendBrowserLogs = queryParams['sendBrowserLogs'];
if (!!secret) { if (!!secret) {
this.openvidu.secret = secret; this.openvidu.secret = secret;
@ -1379,10 +1464,12 @@ export class Session extends EventDispatcher {
if (!!webrtcStatsInterval) { if (!!webrtcStatsInterval) {
this.openvidu.webrtcStatsInterval = +webrtcStatsInterval; this.openvidu.webrtcStatsInterval = +webrtcStatsInterval;
} }
if (!!sendBrowserLogs) {
this.openvidu.sendBrowserLogs = sendBrowserLogs;
}
this.openvidu.wsUri = 'wss://' + url.host + '/openvidu'; this.openvidu.wsUri = 'wss://' + url.host + '/openvidu';
this.openvidu.httpUri = 'https://' + url.host; this.openvidu.httpUri = 'https://' + url.host;
} else { } else {
logger.error('Token "' + token + '" is not valid') logger.error('Token "' + token + '" is not valid')
} }
@ -1391,17 +1478,15 @@ export class Session extends EventDispatcher {
private processJoinRoomResponse(opts: LocalConnectionOptions) { private processJoinRoomResponse(opts: LocalConnectionOptions) {
this.sessionId = opts.session; this.sessionId = opts.session;
if (opts.coturnIp != null && opts.turnUsername != null && opts.turnCredential != null) { if (opts.coturnIp != null && opts.turnUsername != null && opts.turnCredential != null) {
const stunUrl = 'stun:' + opts.coturnIp + ':3478';
const turnUrl1 = 'turn:' + opts.coturnIp + ':3478'; const turnUrl1 = 'turn:' + opts.coturnIp + ':3478';
const turnUrl2 = turnUrl1 + '?transport=tcp';
this.openvidu.iceServers = [ this.openvidu.iceServers = [
{ urls: [stunUrl] }, { urls: [turnUrl1], username: opts.turnUsername, credential: opts.turnCredential }
{ urls: [turnUrl1, turnUrl2], username: opts.turnUsername, credential: opts.turnCredential }
]; ];
logger.log("STUN/TURN server IP: " + opts.coturnIp); logger.log("STUN/TURN server IP: " + opts.coturnIp);
logger.log('TURN temp credentials [' + opts.turnUsername + ':' + opts.turnCredential + ']'); logger.log('TURN temp credentials [' + opts.turnUsername + ':' + opts.turnCredential + ']');
} }
this.openvidu.role = opts.role; this.openvidu.role = opts.role;
this.openvidu.finalUserId = opts.finalUserId;
this.capabilities = { this.capabilities = {
subscribe: true, subscribe: true,
publish: this.openvidu.role !== 'SUBSCRIBER', publish: this.openvidu.role !== 'SUBSCRIBER',

View File

@ -16,16 +16,15 @@
*/ */
import { Connection } from './Connection'; import { Connection } from './Connection';
import { Event } from '../OpenViduInternal/Events/Event';
import { Filter } from './Filter'; import { Filter } from './Filter';
import { Session } from './Session'; import { Session } from './Session';
import { StreamManager } from './StreamManager'; import { StreamManager } from './StreamManager';
import { Subscriber } from './Subscriber'; import { Subscriber } from './Subscriber';
import { EventDispatcher } from './EventDispatcher';
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions'; import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions'; import { OutboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/OutboundStreamOptions';
import { WebRtcPeer, WebRtcPeerSendonly, WebRtcPeerRecvonly, WebRtcPeerSendrecv } from '../OpenViduInternal/WebRtcPeer/WebRtcPeer'; import { WebRtcPeer, WebRtcPeerSendonly, WebRtcPeerRecvonly, WebRtcPeerSendrecv, WebRtcPeerConfiguration } from '../OpenViduInternal/WebRtcPeer/WebRtcPeer';
import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats'; import { WebRtcStats } from '../OpenViduInternal/WebRtcStats/WebRtcStats';
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent'; import { PublisherSpeakingEvent } from '../OpenViduInternal/Events/PublisherSpeakingEvent';
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent'; import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent'; import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
@ -37,6 +36,10 @@ import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
* @hidden * @hidden
*/ */
import hark = require('hark'); import hark = require('hark');
/**
* @hidden
*/
import EventEmitter = require('wolfy87-eventemitter');
/** /**
* @hidden * @hidden
*/ */
@ -52,7 +55,7 @@ let platform: PlatformUtils;
* Each [[Publisher]] and [[Subscriber]] has an attribute of type Stream, as they give access * Each [[Publisher]] and [[Subscriber]] has an attribute of type Stream, as they give access
* to one of them (sending and receiving it, respectively) * to one of them (sending and receiving it, respectively)
*/ */
export class Stream extends EventDispatcher { export class Stream {
/** /**
* The Connection object that is publishing the stream * The Connection object that is publishing the stream
@ -178,27 +181,27 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
publisherStartSpeakingEventEnabled = false; harkSpeakingEnabled = false;
/** /**
* @hidden * @hidden
*/ */
publisherStartSpeakingEventEnabledOnce = false; harkSpeakingEnabledOnce = false;
/** /**
* @hidden * @hidden
*/ */
publisherStopSpeakingEventEnabled = false; harkStoppedSpeakingEnabled = false;
/** /**
* @hidden * @hidden
*/ */
publisherStopSpeakingEventEnabledOnce = false; harkStoppedSpeakingEnabledOnce = false;
/** /**
* @hidden * @hidden
*/ */
volumeChangeEventEnabled = false; harkVolumeChangeEnabled = false;
/** /**
* @hidden * @hidden
*/ */
volumeChangeEventEnabledOnce = false; harkVolumeChangeEnabledOnce = false;
/** /**
* @hidden * @hidden
*/ */
@ -207,6 +210,10 @@ export class Stream extends EventDispatcher {
* @hidden * @hidden
*/ */
localMediaStreamWhenSubscribedToRemote?: MediaStream; localMediaStreamWhenSubscribedToRemote?: MediaStream;
/**
* @hidden
*/
ee = new EventEmitter();
/** /**
@ -214,7 +221,6 @@ export class Stream extends EventDispatcher {
*/ */
constructor(session: Session, options: InboundStreamOptions | OutboundStreamOptions | {}) { constructor(session: Session, options: InboundStreamOptions | OutboundStreamOptions | {}) {
super();
platform = PlatformUtils.getInstance(); platform = PlatformUtils.getInstance();
this.session = session; this.session = session;
@ -271,33 +277,6 @@ export class Stream extends EventDispatcher {
} }
/**
* See [[EventDispatcher.on]]
*/
on(type: string, handler: (event: Event) => void): EventDispatcher {
super.onAux(type, "Event '" + type + "' triggered by stream '" + this.streamId + "'", handler);
return this;
}
/**
* See [[EventDispatcher.once]]
*/
once(type: string, handler: (event: Event) => void): EventDispatcher {
super.onceAux(type, "Event '" + type + "' triggered once by stream '" + this.streamId + "'", handler);
return this;
}
/**
* See [[EventDispatcher.off]]
*/
off(type: string, handler?: (event: Event) => void): EventDispatcher {
super.off(type, handler);
return this;
}
/** /**
* Applies an audio/video filter to the stream. * Applies an audio/video filter to the stream.
* *
@ -308,14 +287,20 @@ export class Stream extends EventDispatcher {
*/ */
applyFilter(type: string, options: Object): Promise<Filter> { applyFilter(type: string, options: Object): Promise<Filter> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.session.sessionConnected()) {
reject(this.session.notConnectedError());
}
logger.info('Applying filter to stream ' + this.streamId); logger.info('Applying filter to stream ' + this.streamId);
options = !!options ? options : {}; options = options != null ? options : {};
if (typeof options !== 'string') { let optionsString = options;
options = JSON.stringify(options); if (typeof optionsString !== 'string') {
optionsString = JSON.stringify(optionsString);
} }
this.session.openvidu.sendRequest( this.session.openvidu.sendRequest(
'applyFilter', 'applyFilter',
{ streamId: this.streamId, type, options }, { streamId: this.streamId, type, options: optionsString },
(error, response) => { (error, response) => {
if (error) { if (error) {
logger.error('Error applying filter for Stream ' + this.streamId, error); logger.error('Error applying filter for Stream ' + this.streamId, error);
@ -343,8 +328,13 @@ export class Stream extends EventDispatcher {
* *
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the previously applied filter has been successfully removed and rejected with an Error object in other case * @returns A Promise (to which you can optionally subscribe to) that is resolved if the previously applied filter has been successfully removed and rejected with an Error object in other case
*/ */
removeFilter(): Promise<any> { removeFilter(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!this.session.sessionConnected()) {
reject(this.session.notConnectedError());
}
logger.info('Removing filter of stream ' + this.streamId); logger.info('Removing filter of stream ' + this.streamId);
this.session.openvidu.sendRequest( this.session.openvidu.sendRequest(
'removeFilter', 'removeFilter',
@ -428,7 +418,7 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
subscribe(): Promise<any> { subscribe(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.initWebRtcPeerReceive(false) this.initWebRtcPeerReceive(false)
.then(() => { .then(() => {
@ -443,7 +433,7 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
publish(): Promise<any> { publish(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.isLocalStreamReadyToPublish) { if (this.isLocalStreamReadyToPublish) {
this.initWebRtcPeerSend(false) this.initWebRtcPeerSend(false)
@ -550,13 +540,14 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
enableStartSpeakingEvent(): void { enableHarkSpeakingEvent(): void {
this.setSpeechEventIfNotExists(); this.setHarkListenerIfNotExists();
if (!this.publisherStartSpeakingEventEnabled) { if (!this.harkSpeakingEnabled) {
this.publisherStartSpeakingEventEnabled = true; this.harkSpeakingEnabled = true;
this.speechEvent.on('speaking', () => { this.speechEvent.on('speaking', () => {
this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]);
this.publisherStartSpeakingEventEnabledOnce = false; // Disable 'once' version if 'on' version was triggered this.streamManager.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.streamManager, 'publisherStartSpeaking', this.connection, this.streamId)]);
this.harkSpeakingEnabledOnce = false; // Disable 'once' version if 'on' version was triggered
}); });
} }
} }
@ -564,16 +555,17 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
enableOnceStartSpeakingEvent(): void { enableOnceHarkSpeakingEvent(): void {
this.setSpeechEventIfNotExists(); this.setHarkListenerIfNotExists();
if (!this.publisherStartSpeakingEventEnabledOnce) { if (!this.harkSpeakingEnabledOnce) {
this.publisherStartSpeakingEventEnabledOnce = true; this.harkSpeakingEnabledOnce = true;
this.speechEvent.once('speaking', () => { this.speechEvent.once('speaking', () => {
if (this.publisherStartSpeakingEventEnabledOnce) { if (this.harkSpeakingEnabledOnce) {
// If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event // If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]); this.session.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStartSpeaking', this.connection, this.streamId)]);
this.streamManager.emitEvent('publisherStartSpeaking', [new PublisherSpeakingEvent(this.streamManager, 'publisherStartSpeaking', this.connection, this.streamId)]);
} }
this.disableStartSpeakingEvent(true); this.disableHarkSpeakingEvent(true);
}); });
} }
} }
@ -581,22 +573,22 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
disableStartSpeakingEvent(disabledByOnce: boolean): void { disableHarkSpeakingEvent(disabledByOnce: boolean): void {
if (!!this.speechEvent) { if (!!this.speechEvent) {
this.publisherStartSpeakingEventEnabledOnce = false; this.harkSpeakingEnabledOnce = false;
if (disabledByOnce) { if (disabledByOnce) {
if (this.publisherStartSpeakingEventEnabled) { if (this.harkSpeakingEnabled) {
// The 'on' version of this same event is enabled too. Do not remove the hark listener // The 'on' version of this same event is enabled too. Do not remove the hark listener
return; return;
} }
} else { } else {
this.publisherStartSpeakingEventEnabled = false; this.harkSpeakingEnabled = false;
} }
// Shutting down the hark event // Shutting down the hark event
if (this.volumeChangeEventEnabled || if (this.harkVolumeChangeEnabled ||
this.volumeChangeEventEnabledOnce || this.harkVolumeChangeEnabledOnce ||
this.publisherStopSpeakingEventEnabled || this.harkStoppedSpeakingEnabled ||
this.publisherStopSpeakingEventEnabledOnce) { this.harkStoppedSpeakingEnabledOnce) {
// Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
this.speechEvent.off('speaking'); this.speechEvent.off('speaking');
} else { } else {
@ -610,13 +602,14 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
enableStopSpeakingEvent(): void { enableHarkStoppedSpeakingEvent(): void {
this.setSpeechEventIfNotExists(); this.setHarkListenerIfNotExists();
if (!this.publisherStopSpeakingEventEnabled) { if (!this.harkStoppedSpeakingEnabled) {
this.publisherStopSpeakingEventEnabled = true; this.harkStoppedSpeakingEnabled = true;
this.speechEvent.on('stopped_speaking', () => { this.speechEvent.on('stopped_speaking', () => {
this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]); this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]);
this.publisherStopSpeakingEventEnabledOnce = false; // Disable 'once' version if 'on' version was triggered this.streamManager.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.streamManager, 'publisherStopSpeaking', this.connection, this.streamId)]);
this.harkStoppedSpeakingEnabledOnce = false; // Disable 'once' version if 'on' version was triggered
}); });
} }
} }
@ -624,16 +617,17 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
enableOnceStopSpeakingEvent(): void { enableOnceHarkStoppedSpeakingEvent(): void {
this.setSpeechEventIfNotExists(); this.setHarkListenerIfNotExists();
if (!this.publisherStopSpeakingEventEnabledOnce) { if (!this.harkStoppedSpeakingEnabledOnce) {
this.publisherStopSpeakingEventEnabledOnce = true; this.harkStoppedSpeakingEnabledOnce = true;
this.speechEvent.once('stopped_speaking', () => { this.speechEvent.once('stopped_speaking', () => {
if (this.publisherStopSpeakingEventEnabledOnce) { if (this.harkStoppedSpeakingEnabledOnce) {
// If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event // If the listener has been disabled in the meantime (for example by the 'on' version) do not trigger the event
this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]); this.session.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.session, 'publisherStopSpeaking', this.connection, this.streamId)]);
this.streamManager.emitEvent('publisherStopSpeaking', [new PublisherSpeakingEvent(this.streamManager, 'publisherStopSpeaking', this.connection, this.streamId)]);
} }
this.disableStopSpeakingEvent(true); this.disableHarkStoppedSpeakingEvent(true);
}); });
} }
} }
@ -641,23 +635,23 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
disableStopSpeakingEvent(disabledByOnce: boolean): void { disableHarkStoppedSpeakingEvent(disabledByOnce: boolean): void {
if (!!this.speechEvent) { if (!!this.speechEvent) {
this.publisherStopSpeakingEventEnabledOnce = false; this.harkStoppedSpeakingEnabledOnce = false;
if (disabledByOnce) { if (disabledByOnce) {
if (this.publisherStopSpeakingEventEnabled) { if (this.harkStoppedSpeakingEnabled) {
// We are cancelling the 'once' listener for this event, but the 'on' version // We are cancelling the 'once' listener for this event, but the 'on' version
// of this same event is enabled too. Do not remove the hark listener // of this same event is enabled too. Do not remove the hark listener
return; return;
} }
} else { } else {
this.publisherStopSpeakingEventEnabled = false; this.harkStoppedSpeakingEnabled = false;
} }
// Shutting down the hark event // Shutting down the hark event
if (this.volumeChangeEventEnabled || if (this.harkVolumeChangeEnabled ||
this.volumeChangeEventEnabledOnce || this.harkVolumeChangeEnabledOnce ||
this.publisherStartSpeakingEventEnabled || this.harkSpeakingEnabled ||
this.publisherStartSpeakingEventEnabledOnce) { this.harkSpeakingEnabledOnce) {
// Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
this.speechEvent.off('stopped_speaking'); this.speechEvent.off('stopped_speaking');
} else { } else {
@ -671,10 +665,10 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
enableVolumeChangeEvent(force: boolean): void { enableHarkVolumeChangeEvent(force: boolean): void {
if (this.setSpeechEventIfNotExists()) { if (this.setHarkListenerIfNotExists()) {
if (!this.volumeChangeEventEnabled || force) { if (!this.harkVolumeChangeEnabled || force) {
this.volumeChangeEventEnabled = true; this.harkVolumeChangeEnabled = true;
this.speechEvent.on('volume_change', harkEvent => { this.speechEvent.on('volume_change', harkEvent => {
const oldValue = this.speechEvent.oldVolumeValue; const oldValue = this.speechEvent.oldVolumeValue;
const value = { newValue: harkEvent, oldValue }; const value = { newValue: harkEvent, oldValue };
@ -684,51 +678,51 @@ export class Stream extends EventDispatcher {
} }
} else { } else {
// This way whenever the MediaStream object is available, the event listener will be automatically added // This way whenever the MediaStream object is available, the event listener will be automatically added
this.volumeChangeEventEnabled = true; this.harkVolumeChangeEnabled = true;
} }
} }
/** /**
* @hidden * @hidden
*/ */
enableOnceVolumeChangeEvent(force: boolean): void { enableOnceHarkVolumeChangeEvent(force: boolean): void {
if (this.setSpeechEventIfNotExists()) { if (this.setHarkListenerIfNotExists()) {
if (!this.volumeChangeEventEnabledOnce || force) { if (!this.harkVolumeChangeEnabledOnce || force) {
this.volumeChangeEventEnabledOnce = true; this.harkVolumeChangeEnabledOnce = true;
this.speechEvent.once('volume_change', harkEvent => { this.speechEvent.once('volume_change', harkEvent => {
const oldValue = this.speechEvent.oldVolumeValue; const oldValue = this.speechEvent.oldVolumeValue;
const value = { newValue: harkEvent, oldValue }; const value = { newValue: harkEvent, oldValue };
this.speechEvent.oldVolumeValue = harkEvent; this.speechEvent.oldVolumeValue = harkEvent;
this.disableVolumeChangeEvent(true); this.disableHarkVolumeChangeEvent(true);
this.streamManager.emitEvent('streamAudioVolumeChange', [new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)]); this.streamManager.emitEvent('streamAudioVolumeChange', [new StreamManagerEvent(this.streamManager, 'streamAudioVolumeChange', value)]);
}); });
} }
} else { } else {
// This way whenever the MediaStream object is available, the event listener will be automatically added // This way whenever the MediaStream object is available, the event listener will be automatically added
this.volumeChangeEventEnabledOnce = true; this.harkVolumeChangeEnabledOnce = true;
} }
} }
/** /**
* @hidden * @hidden
*/ */
disableVolumeChangeEvent(disabledByOnce: boolean): void { disableHarkVolumeChangeEvent(disabledByOnce: boolean): void {
if (!!this.speechEvent) { if (!!this.speechEvent) {
this.volumeChangeEventEnabledOnce = false; this.harkVolumeChangeEnabledOnce = false;
if (disabledByOnce) { if (disabledByOnce) {
if (this.volumeChangeEventEnabled) { if (this.harkVolumeChangeEnabled) {
// We are cancelling the 'once' listener for this event, but the 'on' version // We are cancelling the 'once' listener for this event, but the 'on' version
// of this same event is enabled too. Do not remove the hark listener // of this same event is enabled too. Do not remove the hark listener
return; return;
} }
} else { } else {
this.volumeChangeEventEnabled = false; this.harkVolumeChangeEnabled = false;
} }
// Shutting down the hark event // Shutting down the hark event
if (this.publisherStartSpeakingEventEnabled || if (this.harkSpeakingEnabled ||
this.publisherStartSpeakingEventEnabledOnce || this.harkSpeakingEnabledOnce ||
this.publisherStopSpeakingEventEnabled || this.harkStoppedSpeakingEnabled ||
this.publisherStopSpeakingEventEnabledOnce) { this.harkStoppedSpeakingEnabledOnce) {
// Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener // Some other hark event is enabled. Cannot stop the hark process, just remove the specific listener
this.speechEvent.off('volume_change'); this.speechEvent.off('volume_change');
} else { } else {
@ -780,16 +774,16 @@ export class Stream extends EventDispatcher {
return false; return false;
} }
if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) { if (this.isLocal() && !!this.session.openvidu.advancedConfiguration.forceMediaReconnectionAfterNetworkDrop) {
logger.warn('OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Publisher stream ' + this.streamId + 'will force a reconnection'); logger.warn('OpenVidu Browser advanced configuration option "forceMediaReconnectionAfterNetworkDrop" is enabled. Stream ' + this.streamId + ' will force a reconnection');
return true; return true;
} }
const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState; const iceConnectionState: RTCIceConnectionState = this.getRTCPeerConnection().iceConnectionState;
return iceConnectionState === 'disconnected' || iceConnectionState === 'failed'; return iceConnectionState !== 'connected' && iceConnectionState !== 'completed';
} }
/* Private methods */ /* Private methods */
private setSpeechEventIfNotExists(): boolean { private setHarkListenerIfNotExists(): boolean {
if (!!this.mediaStream) { if (!!this.mediaStream) {
if (!this.speechEvent) { if (!this.speechEvent) {
const harkOptions = !!this.harkOptions ? this.harkOptions : (this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {}); const harkOptions = !!this.harkOptions ? this.harkOptions : (this.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
@ -805,7 +799,7 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
initWebRtcPeerSend(reconnect: boolean): Promise<any> { initWebRtcPeerSend(reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!reconnect) { if (!reconnect) {
@ -817,15 +811,16 @@ export class Stream extends EventDispatcher {
video: this.isSendVideo() video: this.isSendVideo()
}; };
const options = { const options: WebRtcPeerConfiguration = {
mediaStream: this.mediaStream, mediaStream: this.mediaStream,
mediaConstraints: userMediaConstraints, mediaConstraints: userMediaConstraints,
onicecandidate: this.connection.sendIceCandidate.bind(this.connection), onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
iceServers: this.getIceServersConf(), iceServers: this.getIceServersConf(),
simulcast: false simulcast: false
}; };
const successCallback = (sdpOfferParam) => { const successOfferCallback = (sdpOfferParam) => {
logger.debug('Sending SDP offer to publish as ' logger.debug('Sending SDP offer to publish as '
+ this.streamId, sdpOfferParam); + this.streamId, sdpOfferParam);
@ -833,7 +828,8 @@ export class Stream extends EventDispatcher {
let params; let params;
if (reconnect) { if (reconnect) {
params = { params = {
stream: this.streamId stream: this.streamId,
sdpString: sdpOfferParam
} }
} else { } else {
let typeOfVideo = ''; let typeOfVideo = '';
@ -849,10 +845,10 @@ export class Stream extends EventDispatcher {
typeOfVideo, typeOfVideo,
frameRate: !!this.frameRate ? this.frameRate : -1, frameRate: !!this.frameRate ? this.frameRate : -1,
videoDimensions: JSON.stringify(this.videoDimensions), videoDimensions: JSON.stringify(this.videoDimensions),
filter: this.outboundStreamOpts.publisherProperties.filter filter: this.outboundStreamOpts.publisherProperties.filter,
sdpOffer: sdpOfferParam
} }
} }
params['sdpOffer'] = sdpOfferParam;
this.session.openvidu.sendRequest(method, params, (error, response) => { this.session.openvidu.sendRequest(method, params, (error, response) => {
if (error) { if (error) {
@ -862,7 +858,7 @@ export class Stream extends EventDispatcher {
reject('Error on publishVideo: ' + JSON.stringify(error)); reject('Error on publishVideo: ' + JSON.stringify(error));
} }
} else { } else {
this.webRtcPeer.processAnswer(response.sdpAnswer, false) this.webRtcPeer.processRemoteAnswer(response.sdpAnswer)
.then(() => { .then(() => {
this.streamId = response.id; this.streamId = response.id;
this.creationTime = response.createdAt; this.creationTime = response.createdAt;
@ -870,7 +866,7 @@ export class Stream extends EventDispatcher {
this.publishedOnce = true; this.publishedOnce = true;
if (this.displayMyRemote()) { if (this.displayMyRemote()) {
this.localMediaStreamWhenSubscribedToRemote = this.mediaStream; this.localMediaStreamWhenSubscribedToRemote = this.mediaStream;
this.remotePeerSuccessfullyEstablished(); this.remotePeerSuccessfullyEstablished(reconnect);
} }
if (reconnect) { if (reconnect) {
this.ee.emitEvent('stream-reconnected-by-publisher', []); this.ee.emitEvent('stream-reconnected-by-publisher', []);
@ -897,10 +893,15 @@ export class Stream extends EventDispatcher {
this.webRtcPeer = new WebRtcPeerSendonly(options); this.webRtcPeer = new WebRtcPeerSendonly(options);
} }
this.webRtcPeer.addIceConnectionStateChangeListener('publisher of ' + this.connection.connectionId); this.webRtcPeer.addIceConnectionStateChangeListener('publisher of ' + this.connection.connectionId);
this.webRtcPeer.generateOffer().then(sdpOffer => { this.webRtcPeer.createOffer().then(sdpOffer => {
successCallback(sdpOffer); this.webRtcPeer.processLocalOffer(sdpOffer)
.then(() => {
successOfferCallback(sdpOffer.sdp);
}).catch(error => { }).catch(error => {
reject(new Error('(publish) SDP offer error: ' + JSON.stringify(error))); reject(new Error('(publish) SDP process local offer error: ' + JSON.stringify(error)));
});
}).catch(error => {
reject(new Error('(publish) SDP create offer error: ' + JSON.stringify(error)));
}); });
}); });
} }
@ -908,7 +909,31 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
initWebRtcPeerReceive(reconnect: boolean): Promise<any> { initWebRtcPeerReceive(reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => {
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);
});
}
});
});
}
/**
* @hidden
*/
completeWebRtcPeerReceive(sdpOffer: string, reconnect: boolean): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const offerConstraints = { const offerConstraints = {
@ -919,55 +944,47 @@ export class Stream extends EventDispatcher {
offerConstraints); offerConstraints);
const options = { const options = {
onicecandidate: this.connection.sendIceCandidate.bind(this.connection), onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => { this.session.emitEvent('exception', [new ExceptionEvent(this.session, exceptionName, this, message, data)]) },
mediaConstraints: offerConstraints, mediaConstraints: offerConstraints,
iceServers: this.getIceServersConf(), iceServers: this.getIceServersConf(),
simulcast: false simulcast: false
}; };
const successCallback = (sdpOfferParam) => { const successAnswerCallback = (sdpAnswer) => {
logger.debug('Sending SDP offer to subscribe to ' logger.debug('Sending SDP answer to subscribe to '
+ this.streamId, sdpOfferParam); + this.streamId, sdpAnswer);
const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom'; const method = reconnect ? 'reconnectStream' : 'receiveVideoFrom';
const params = { sdpOffer: sdpOfferParam }; const params = {};
params[reconnect ? 'stream' : 'sender'] = this.streamId; params[reconnect ? 'stream' : 'sender'] = this.streamId;
params[reconnect ? 'sdpString' : 'sdpAnswer'] = sdpAnswer;
this.session.openvidu.sendRequest(method, params, (error, response) => { this.session.openvidu.sendRequest(method, params, (error, response) => {
if (error) { if (error) {
reject(new Error('Error on recvVideoFrom: ' + JSON.stringify(error))); reject(new Error('Error on ' + method + ' : ' + JSON.stringify(error)));
} else { } else {
// Ios Ionic. Limitation: some bug in iosrtc cordova plugin makes it necessary
// to add a timeout before calling PeerConnection#setRemoteDescription during
// some time (400 ms) from the moment first subscriber stream is received
if (this.session.isFirstIonicIosSubscriber) {
this.session.isFirstIonicIosSubscriber = false;
setTimeout(() => {
// After 400 ms Ionic iOS subscribers won't need to run
// PeerConnection#setRemoteDescription after 250 ms timeout anymore
this.session.countDownForIonicIosSubscribersActive = false;
}, 400);
}
const needsTimeoutOnProcessAnswer = this.session.countDownForIonicIosSubscribersActive;
this.webRtcPeer.processAnswer(response.sdpAnswer, needsTimeoutOnProcessAnswer).then(() => {
logger.info("'Subscriber' (" + this.streamId + ") successfully " + (reconnect ? "reconnected" : "subscribed"));
this.remotePeerSuccessfullyEstablished();
this.initWebRtcStats();
resolve(); resolve();
}).catch(error => {
reject(error);
});
} }
}); });
}; };
this.webRtcPeer = new WebRtcPeerRecvonly(options); this.webRtcPeer = new WebRtcPeerRecvonly(options);
this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId); this.webRtcPeer.addIceConnectionStateChangeListener(this.streamId);
this.webRtcPeer.generateOffer() this.webRtcPeer.processRemoteOffer(sdpOffer)
.then(sdpOffer => { .then(() => {
successCallback(sdpOffer); 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)));
});
}).catch(error => {
reject(new Error('(subscribe) SDP create answer error: ' + JSON.stringify(error)));
});
}) })
.catch(error => { .catch(error => {
reject(new Error('(subscribe) SDP offer error: ' + JSON.stringify(error))); reject(new Error('(subscribe) SDP process remote offer error: ' + JSON.stringify(error)));
}); });
}); });
} }
@ -975,7 +992,13 @@ export class Stream extends EventDispatcher {
/** /**
* @hidden * @hidden
*/ */
remotePeerSuccessfullyEstablished(): void { remotePeerSuccessfullyEstablished(reconnect: boolean): void {
if (reconnect && this.mediaStream != null) {
// Now we can destroy the existing MediaStream
this.disposeMediaStream();
}
this.mediaStream = new MediaStream(); this.mediaStream = new MediaStream();
let receiver: RTCRtpReceiver; let receiver: RTCRtpReceiver;
for (receiver of this.webRtcPeer.pc.getReceivers()) { for (receiver of this.webRtcPeer.pc.getReceivers()) {
@ -990,11 +1013,11 @@ export class Stream extends EventDispatcher {
if (this.streamManager instanceof Subscriber) { if (this.streamManager instanceof Subscriber) {
// Apply SubscriberProperties.subscribeToAudio and SubscriberProperties.subscribeToVideo // Apply SubscriberProperties.subscribeToAudio and SubscriberProperties.subscribeToVideo
if (!!this.mediaStream.getAudioTracks()[0]) { if (!!this.mediaStream.getAudioTracks()[0]) {
const enabled = !!((<Subscriber>this.streamManager).properties.subscribeToAudio); const enabled = reconnect ? this.audioActive : !!((this.streamManager as Subscriber).properties.subscribeToAudio);
this.mediaStream.getAudioTracks()[0].enabled = enabled; this.mediaStream.getAudioTracks()[0].enabled = enabled;
} }
if (!!this.mediaStream.getVideoTracks()[0]) { if (!!this.mediaStream.getVideoTracks()[0]) {
const enabled = !!((<Subscriber>this.streamManager).properties.subscribeToVideo); const enabled = reconnect ? this.videoActive : !!((this.streamManager as Subscriber).properties.subscribeToVideo);
this.mediaStream.getVideoTracks()[0].enabled = enabled; this.mediaStream.getVideoTracks()[0].enabled = enabled;
} }
} }
@ -1007,27 +1030,23 @@ export class Stream extends EventDispatcher {
private initHarkEvents(): void { private initHarkEvents(): void {
if (!!this.mediaStream!.getAudioTracks()[0]) { if (!!this.mediaStream!.getAudioTracks()[0]) {
// Hark events can only be set if audio track is available // Hark events can only be set if audio track is available
if (this.streamManager.remote) { if (this.session.anySpeechEventListenerEnabled('publisherStartSpeaking', true, this.streamManager)) {
// publisherStartSpeaking/publisherStopSpeaking is only defined for remote streams this.enableOnceHarkSpeakingEvent();
if (this.session.startSpeakingEventsEnabled) {
this.enableStartSpeakingEvent();
} }
if (this.session.startSpeakingEventsEnabledOnce) { if (this.session.anySpeechEventListenerEnabled('publisherStartSpeaking', false, this.streamManager)) {
this.enableOnceStartSpeakingEvent(); this.enableHarkSpeakingEvent();
} }
if (this.session.stopSpeakingEventsEnabled) { if (this.session.anySpeechEventListenerEnabled('publisherStopSpeaking', true, this.streamManager)) {
this.enableStopSpeakingEvent(); this.enableOnceHarkStoppedSpeakingEvent();
} }
if (this.session.stopSpeakingEventsEnabledOnce) { if (this.session.anySpeechEventListenerEnabled('publisherStopSpeaking', false, this.streamManager)) {
this.enableOnceStopSpeakingEvent(); this.enableHarkStoppedSpeakingEvent();
} }
if (this.harkVolumeChangeEnabledOnce) {
this.enableOnceHarkVolumeChangeEvent(true);
} }
// streamAudioVolumeChange event is defined for both Publishers and Subscribers if (this.harkVolumeChangeEnabled) {
if (this.volumeChangeEventEnabled) { this.enableHarkVolumeChangeEvent(true);
this.enableVolumeChangeEvent(true);
}
if (this.volumeChangeEventEnabledOnce) {
this.enableOnceVolumeChangeEvent(true);
} }
} }
} }

View File

@ -48,6 +48,9 @@ let platform: PlatformUtils;
* - videoElementCreated ([[VideoElementEvent]]) * - videoElementCreated ([[VideoElementEvent]])
* - videoElementDestroyed ([[VideoElementEvent]]) * - videoElementDestroyed ([[VideoElementEvent]])
* - streamPlaying ([[StreamManagerEvent]]) * - streamPlaying ([[StreamManagerEvent]])
* - streamPropertyChanged ([[StreamPropertyChangedEvent]])
* - publisherStartSpeaking ([[PublisherSpeakingEvent]])
* - publisherStopSpeaking ([[PublisherSpeakingEvent]])
* - streamAudioVolumeChange ([[StreamManagerEvent]]) * - streamAudioVolumeChange ([[StreamManagerEvent]])
* *
*/ */
@ -174,8 +177,16 @@ export class StreamManager extends EventDispatcher {
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
} }
} }
if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) { if (this.stream.hasAudio) {
this.stream.enableVolumeChangeEvent(false); if (type === 'publisherStartSpeaking') {
this.stream.enableHarkSpeakingEvent();
}
if (type === 'publisherStopSpeaking') {
this.stream.enableHarkStoppedSpeakingEvent();
}
if (type === 'streamAudioVolumeChange') {
this.stream.enableHarkVolumeChangeEvent(false);
}
} }
return this; return this;
} }
@ -202,8 +213,16 @@ export class StreamManager extends EventDispatcher {
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]); this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
} }
} }
if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) { if (this.stream.hasAudio) {
this.stream.enableOnceVolumeChangeEvent(false); if (type === 'publisherStartSpeaking') {
this.stream.enableOnceHarkSpeakingEvent();
}
if (type === 'publisherStopSpeaking') {
this.stream.enableOnceHarkStoppedSpeakingEvent();
}
if (type === 'streamAudioVolumeChange') {
this.stream.enableOnceHarkVolumeChangeEvent(false);
}
} }
return this; return this;
} }
@ -215,10 +234,25 @@ export class StreamManager extends EventDispatcher {
super.off(type, handler); super.off(type, handler);
if (type === 'publisherStartSpeaking') {
// Both StreamManager and Session can have "publisherStartSpeaking" event listeners
const remainingStartSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
if (remainingStartSpeakingEventListeners === 0) {
this.stream.disableHarkSpeakingEvent(false);
}
}
if (type === 'publisherStopSpeaking') {
// Both StreamManager and Session can have "publisherStopSpeaking" event listeners
const remainingStopSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
if (remainingStopSpeakingEventListeners === 0) {
this.stream.disableHarkStoppedSpeakingEvent(false);
}
}
if (type === 'streamAudioVolumeChange') { if (type === 'streamAudioVolumeChange') {
let remainingVolumeEventListeners = this.ee.getListeners(type).length; // Only StreamManager can have "streamAudioVolumeChange" event listeners
const remainingVolumeEventListeners = this.ee.getListeners(type).length;
if (remainingVolumeEventListeners === 0) { if (remainingVolumeEventListeners === 0) {
this.stream.disableVolumeChangeEvent(false); this.stream.disableHarkVolumeChangeEvent(false);
} }
} }
@ -419,9 +453,10 @@ export class StreamManager extends EventDispatcher {
this.videos.forEach(streamManagerVideo => { this.videos.forEach(streamManagerVideo => {
// Remove oncanplay event listener (only OpenVidu browser listener, not the user ones) // Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
if(!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener){ if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener); streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
} streamManagerVideo.canplayListenerAdded = false; }
streamManagerVideo.canplayListenerAdded = false;
if (!!streamManagerVideo.targetElement) { if (!!streamManagerVideo.targetElement) {
// Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher // Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event // and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event

View File

@ -27,6 +27,10 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
/** /**
* Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method * Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method
*
* ### Available event listeners (and events dispatched)
*
* - _All events inherited from [[StreamManager]] class_
*/ */
export class Subscriber extends StreamManager { export class Subscriber extends StreamManager {

View File

@ -102,12 +102,13 @@ export enum OpenViduErrorName {
OPENVIDU_PERMISSION_DENIED = 'OPENVIDU_PERMISSION_DENIED', OPENVIDU_PERMISSION_DENIED = 'OPENVIDU_PERMISSION_DENIED',
/** /**
* _Not in use yet_ * There is no connection to the Session. This error will be thrown when any method requiring a connection to
* openvidu-server is called before successfully calling method [[Session.connect]]
*/ */
OPENVIDU_NOT_CONNECTED = 'OPENVIDU_NOT_CONNECTED', OPENVIDU_NOT_CONNECTED = 'OPENVIDU_NOT_CONNECTED',
/** /**
* _Not in use yet_ * Generic error
*/ */
GENERIC_ERROR = 'GENERIC_ERROR' GENERIC_ERROR = 'GENERIC_ERROR'
} }
@ -117,7 +118,14 @@ export enum OpenViduErrorName {
*/ */
export class OpenViduError { export class OpenViduError {
/**
* Uniquely identifying name of the error
*/
name: OpenViduErrorName; name: OpenViduErrorName;
/**
* Full description of the error
*/
message: string; message: string;
/** /**

View File

@ -0,0 +1,103 @@
/*
* (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
import { Event } from './Event';
/**
* Defines property [[ExceptionEvent.name]]
*/
export enum ExceptionEventName {
/**
* There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side.
*
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Session]] object.
*/
ICE_CANDIDATE_ERROR = 'ICE_CANDIDATE_ERROR',
/**
* 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.
*
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
*/
ICE_CONNECTION_FAILED = 'ICE_CONNECTION_FAILED',
/**
* 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.
*
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
*/
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED'
}
/**
* 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.
*/
export class ExceptionEvent extends Event {
/**
* Name of the exception
*/
name: ExceptionEventName;
/**
* Object affected by the exception. Depending on the [[ExceptionEvent.name]] property:
* - [[Session]]: `ICE_CANDIDATE_ERROR`
* - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
*/
origin: Session | Stream;
/**
* Informative description of the exception
*/
message: string;
/**
* Any extra information associated to the exception
*/
data?: any;
/**
* @hidden
*/
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream, message: string, data?: any) {
super(false, session, 'exception');
this.name = name;
this.origin = origin;
this.message = message;
this.data = data;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -17,16 +17,17 @@
import { Event } from './Event'; import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection'; import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../..'; import { Session } from '../../OpenVidu/Session';
import { StreamManager } from '../../OpenVidu/StreamManager';
/** /**
* Defines the following events: * Defines the following events:
* - `publisherStartSpeaking`: dispatched by [[Session]] when a remote user has started speaking * - `publisherStartSpeaking`: dispatched by [[Session]] and [[StreamManager]] when a user has started speaking
* - `publisherStopSpeaking`: dispatched by [[Session]] when a remote user has stopped speaking * - `publisherStopSpeaking`: dispatched by [[Session]] and [[StreamManager]] when a user has stopped speaking
* *
* More information: * More information:
* - This events will only be triggered for **remote streams that have audio tracks** ([[Stream.hasAudio]] must be true) * - This events will only be triggered for **streams that have audio tracks** ([[Stream.hasAudio]] must be true)
* - You can further configure how the events are dispatched by setting property `publisherSpeakingEventsOptions` in the call of [[OpenVidu.setAdvancedConfiguration]] * - You can further configure how the events are dispatched by setting property `publisherSpeakingEventsOptions` in the call of [[OpenVidu.setAdvancedConfiguration]]
*/ */
export class PublisherSpeakingEvent extends Event { export class PublisherSpeakingEvent extends Event {
@ -44,7 +45,7 @@ export class PublisherSpeakingEvent extends Event {
/** /**
* @hidden * @hidden
*/ */
constructor(target: Session, type: string, connection: Connection, streamId: string) { constructor(target: Session | StreamManager, type: string, connection: Connection, streamId: string) {
super(false, target, type); super(false, target, type);
this.type = type; this.type = type;
this.connection = connection; this.connection = connection;

View File

@ -60,18 +60,22 @@ export class SessionDisconnectedEvent extends Event {
const session = <Session>this.target; const session = <Session>this.target;
// Dispose and delete all remote Connections // Dispose and delete all remote Connections
for (const connectionId in session.remoteConnections) { session.remoteConnections.forEach(remoteConnection => {
if (!!session.remoteConnections[connectionId].stream) { const connectionId = remoteConnection.connectionId;
session.remoteConnections[connectionId].stream!.disposeWebRtcPeer(); if (!!session.remoteConnections.get(connectionId)?.stream) {
session.remoteConnections[connectionId].stream!.disposeMediaStream(); session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
if (session.remoteConnections[connectionId].stream!.streamManager) { session.remoteConnections.get(connectionId)?.stream!.disposeMediaStream();
session.remoteConnections[connectionId].stream!.streamManager.removeAllVideos(); if (session.remoteConnections.get(connectionId)?.stream!.streamManager) {
session.remoteConnections.get(connectionId)?.stream!.streamManager.removeAllVideos();
} }
delete session.remoteStreamsCreated[session.remoteConnections[connectionId].stream!.streamId]; const streamId = session.remoteConnections.get(connectionId)?.stream?.streamId;
session.remoteConnections[connectionId].dispose(); if (!!streamId) {
session.remoteStreamsCreated.delete(streamId);
} }
delete session.remoteConnections[connectionId]; session.remoteConnections.get(connectionId)?.dispose();
} }
session.remoteConnections.delete(connectionId);
});
} }
} }

View File

@ -97,10 +97,10 @@ export class StreamEvent extends Event {
if (this.stream.streamManager) this.stream.streamManager.removeAllVideos(); if (this.stream.streamManager) this.stream.streamManager.removeAllVideos();
// Delete stream from Session.remoteStreamsCreated map // Delete stream from Session.remoteStreamsCreated map
delete this.stream.session.remoteStreamsCreated[this.stream.streamId]; this.stream.session.remoteStreamsCreated.delete(this.stream.streamId);
// Delete StreamOptionsServer from remote Connection // Delete StreamOptionsServer from remote Connection
const remoteConnection = this.stream.session.remoteConnections[this.stream.connection.connectionId]; const remoteConnection = this.stream.session.remoteConnections.get(this.stream.connection.connectionId);
if (!!remoteConnection && !!remoteConnection.remoteOptions) { if (!!remoteConnection && !!remoteConnection.remoteOptions) {
const streamOptionsServer = remoteConnection.remoteOptions.streams; const streamOptionsServer = remoteConnection.remoteOptions.streams;
for (let i = streamOptionsServer.length - 1; i >= 0; --i) { for (let i = streamOptionsServer.length - 1; i >= 0; --i) {

View File

@ -41,7 +41,7 @@ export class StreamPropertyChangedEvent extends Event {
* Cause of the change on the stream's property: * Cause of the change on the stream's property:
* - For `videoActive`: `"publishVideo"` * - For `videoActive`: `"publishVideo"`
* - For `audioActive`: `"publishAudio"` * - For `audioActive`: `"publishAudio"`
* - For `videoDimensions`: `"deviceRotated"` or `"screenResized"` * - For `videoDimensions`: `"deviceRotated"`, `"screenResized"` or `"trackReplaced"`
* - For `filter`: `"applyFilter"`, `"execFilterMethod"` or `"removeFilter"` * - For `filter`: `"applyFilter"`, `"execFilterMethod"` or `"removeFilter"`
*/ */
reason: string; reason: string;

View File

@ -19,6 +19,7 @@ import { RemoteConnectionOptions } from './RemoteConnectionOptions';
export interface LocalConnectionOptions { export interface LocalConnectionOptions {
id: string; id: string;
finalUserId: string;
createdAt: number; createdAt: number;
metadata: string; metadata: string;
value: RemoteConnectionOptions[]; value: RemoteConnectionOptions[];

View File

@ -1,18 +0,0 @@
/*
* (C) Copyright 2017-2020 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export interface ObjMap<T> { [s: string]: T; }

View File

@ -1,49 +1,45 @@
function Mapper() function Mapper() {
{
var sources = {}; var sources = {};
this.forEach = function(callback) this.forEach = function (callback) {
{ for (var key in sources) {
for(var key in sources)
{
var source = sources[key]; var source = sources[key];
for(var key2 in source) for (var key2 in source)
callback(source[key2]); callback(source[key2]);
}; };
}; };
this.get = function(id, source) this.get = function (id, source) {
{
var ids = sources[source]; var ids = sources[source];
if(ids == undefined) if (ids == undefined)
return undefined; return undefined;
return ids[id]; return ids[id];
}; };
this.remove = function(id, source) this.remove = function (id, source) {
{
var ids = sources[source]; var ids = sources[source];
if(ids == undefined) if (ids == undefined)
return; return;
delete ids[id]; delete ids[id];
// Check it's empty // Check it's empty
for(var i in ids){return false} for (var i in ids) {
return false
}
delete sources[source]; delete sources[source];
}; };
this.set = function(value, id, source) this.set = function (value, id, source) {
{ if (value == undefined)
if(value == undefined)
return this.remove(id, source); return this.remove(id, source);
var ids = sources[source]; var ids = sources[source];
if(ids == undefined) if (ids == undefined)
sources[source] = ids = {}; sources[source] = ids = {};
ids[id] = value; ids[id] = value;
@ -51,10 +47,9 @@ function Mapper()
}; };
Mapper.prototype.pop = function(id, source) Mapper.prototype.pop = function (id, source) {
{
var value = this.get(id, source); var value = this.get(id, source);
if(value == undefined) if (value == undefined)
return undefined; return undefined;
this.remove(id, source); this.remove(id, source);

View File

@ -161,9 +161,6 @@ function JsonRpcClient(configuration) {
}); });
this.send = function (method, params, callback) { this.send = function (method, params, callback) {
if (method !== 'ping') {
Logger.debug('Request: method:' + method + " params:" + JSON.stringify(params));
}
var requestTime = Date.now(); var requestTime = Date.now();

View File

@ -17,5 +17,4 @@
var WebSocketWithReconnection = require('./webSocketWithReconnection'); var WebSocketWithReconnection = require('./webSocketWithReconnection');
exports.WebSocketWithReconnection = WebSocketWithReconnection; exports.WebSocketWithReconnection = WebSocketWithReconnection;

View File

@ -16,7 +16,8 @@
"use strict"; "use strict";
var Logger = console; var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
var Logger = OpenViduLogger.getInstance();
var MAX_RETRIES = 2000; // Forever... var MAX_RETRIES = 2000; // Forever...
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times... var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...

View File

@ -17,21 +17,17 @@
var defineProperty_IE8 = false var defineProperty_IE8 = false
if(Object.defineProperty) if (Object.defineProperty) {
{ try {
try
{
Object.defineProperty({}, "x", {}); Object.defineProperty({}, "x", {});
} } catch (e) {
catch(e)
{
defineProperty_IE8 = true defineProperty_IE8 = true
} }
} }
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) { if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) { Function.prototype.bind = function (oThis) {
if (typeof this !== 'function') { if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5 // closest thing possible to the ECMAScript 5
// internal IsCallable function // internal IsCallable function
@ -40,11 +36,11 @@ if (!Function.prototype.bind) {
var aArgs = Array.prototype.slice.call(arguments, 1), var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this, fToBind = this,
fNOP = function() {}, fNOP = function () {},
fBound = function() { fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis return fToBind.apply(this instanceof fNOP && oThis ?
? this this :
: oThis, oThis,
aArgs.concat(Array.prototype.slice.call(arguments))); aArgs.concat(Array.prototype.slice.call(arguments)));
}; };
@ -67,17 +63,14 @@ var Mapper = require('./Mapper');
var BASE_TIMEOUT = 5000; var BASE_TIMEOUT = 5000;
function unifyResponseMethods(responseMethods) function unifyResponseMethods(responseMethods) {
{ if (!responseMethods) return {};
if(!responseMethods) return {};
for(var key in responseMethods) for (var key in responseMethods) {
{
var value = responseMethods[key]; var value = responseMethods[key];
if(typeof value == 'string') if (typeof value == 'string')
responseMethods[key] = responseMethods[key] = {
{
response: value response: value
} }
}; };
@ -85,35 +78,34 @@ function unifyResponseMethods(responseMethods)
return responseMethods; return responseMethods;
}; };
function unifyTransport(transport) function unifyTransport(transport) {
{ if (!transport) return;
if(!transport) return;
// Transport as a function // Transport as a function
if(transport instanceof Function) if (transport instanceof Function)
return {send: transport}; return {
send: transport
};
// WebSocket & DataChannel // WebSocket & DataChannel
if(transport.send instanceof Function) if (transport.send instanceof Function)
return transport; return transport;
// Message API (Inter-window & WebWorker) // Message API (Inter-window & WebWorker)
if(transport.postMessage instanceof Function) if (transport.postMessage instanceof Function) {
{
transport.send = transport.postMessage; transport.send = transport.postMessage;
return transport; return transport;
} }
// Stream API // Stream API
if(transport.write instanceof Function) if (transport.write instanceof Function) {
{
transport.send = transport.write; transport.send = transport.write;
return transport; return transport;
} }
// Transports that only can receive messages, but not send // Transports that only can receive messages, but not send
if(transport.onmessage !== undefined) return; if (transport.onmessage !== undefined) return;
if(transport.pause instanceof Function) return; if (transport.pause instanceof Function) return;
throw new SyntaxError("Transport is not a function nor a valid object"); throw new SyntaxError("Transport is not a function nor a valid object");
}; };
@ -129,17 +121,19 @@ function unifyTransport(transport)
* @param {String} method -method of the notification * @param {String} method -method of the notification
* @param params - parameters of the notification * @param params - parameters of the notification
*/ */
function RpcNotification(method, params) function RpcNotification(method, params) {
{ if (defineProperty_IE8) {
if(defineProperty_IE8)
{
this.method = method this.method = method
this.params = params this.params = params
} } else {
else Object.defineProperty(this, 'method', {
{ value: method,
Object.defineProperty(this, 'method', {value: method, enumerable: true}); enumerable: true
Object.defineProperty(this, 'params', {value: params, enumerable: true}); });
Object.defineProperty(this, 'params', {
value: params,
enumerable: true
});
} }
}; };
@ -157,22 +151,20 @@ function RpcNotification(method, params)
* *
* @param {Function} [onRequest] * @param {Function} [onRequest]
*/ */
function RpcBuilder(packer, options, transport, onRequest) function RpcBuilder(packer, options, transport, onRequest) {
{
var self = this; var self = this;
if(!packer) if (!packer)
throw new SyntaxError('Packer is not defined'); throw new SyntaxError('Packer is not defined');
if(!packer.pack || !packer.unpack) if (!packer.pack || !packer.unpack)
throw new SyntaxError('Packer is invalid'); throw new SyntaxError('Packer is invalid');
var responseMethods = unifyResponseMethods(packer.responseMethods); var responseMethods = unifyResponseMethods(packer.responseMethods);
if(options instanceof Function) if (options instanceof Function) {
{ if (transport != undefined)
if(transport != undefined)
throw new SyntaxError("There can't be parameters after onRequest"); throw new SyntaxError("There can't be parameters after onRequest");
onRequest = options; onRequest = options;
@ -180,9 +172,8 @@ function RpcBuilder(packer, options, transport, onRequest)
options = undefined; options = undefined;
}; };
if(options && options.send instanceof Function) if (options && options.send instanceof Function) {
{ if (transport && !(transport instanceof Function))
if(transport && !(transport instanceof Function))
throw new SyntaxError("Only a function can be after transport"); throw new SyntaxError("Only a function can be after transport");
onRequest = transport; onRequest = transport;
@ -190,17 +181,16 @@ function RpcBuilder(packer, options, transport, onRequest)
options = undefined; options = undefined;
}; };
if(transport instanceof Function) if (transport instanceof Function) {
{ if (onRequest != undefined)
if(onRequest != undefined)
throw new SyntaxError("There can't be parameters after onRequest"); throw new SyntaxError("There can't be parameters after onRequest");
onRequest = transport; onRequest = transport;
transport = undefined; transport = undefined;
}; };
if(transport && transport.send instanceof Function) if (transport && transport.send instanceof Function)
if(onRequest && !(onRequest instanceof Function)) if (onRequest && !(onRequest instanceof Function))
throw new SyntaxError("Only a function can be after transport"); throw new SyntaxError("Only a function can be after transport");
options = options || {}; options = options || {};
@ -208,59 +198,55 @@ function RpcBuilder(packer, options, transport, onRequest)
EventEmitter.call(this); EventEmitter.call(this);
if(onRequest) if (onRequest)
this.on('request', onRequest); this.on('request', onRequest);
if(defineProperty_IE8) if (defineProperty_IE8)
this.peerID = options.peerID this.peerID = options.peerID
else else
Object.defineProperty(this, 'peerID', {value: options.peerID}); Object.defineProperty(this, 'peerID', {
value: options.peerID
});
var max_retries = options.max_retries || 0; var max_retries = options.max_retries || 0;
function transportMessage(event) function transportMessage(event) {
{
self.decode(event.data || event); self.decode(event.data || event);
}; };
this.getTransport = function() this.getTransport = function () {
{
return transport; return transport;
} }
this.setTransport = function(value) this.setTransport = function (value) {
{
// Remove listener from old transport // Remove listener from old transport
if(transport) if (transport) {
{
// W3C transports // W3C transports
if(transport.removeEventListener) if (transport.removeEventListener)
transport.removeEventListener('message', transportMessage); transport.removeEventListener('message', transportMessage);
// Node.js Streams API // Node.js Streams API
else if(transport.removeListener) else if (transport.removeListener)
transport.removeListener('data', transportMessage); transport.removeListener('data', transportMessage);
}; };
// Set listener on new transport // Set listener on new transport
if(value) if (value) {
{
// W3C transports // W3C transports
if(value.addEventListener) if (value.addEventListener)
value.addEventListener('message', transportMessage); value.addEventListener('message', transportMessage);
// Node.js Streams API // Node.js Streams API
else if(value.addListener) else if (value.addListener)
value.addListener('data', transportMessage); value.addListener('data', transportMessage);
}; };
transport = unifyTransport(value); transport = unifyTransport(value);
} }
if(!defineProperty_IE8) if (!defineProperty_IE8)
Object.defineProperty(this, 'transport', Object.defineProperty(this, 'transport', {
{
get: this.getTransport.bind(this), get: this.getTransport.bind(this),
set: this.setTransport.bind(this) set: this.setTransport.bind(this)
}) })
@ -286,14 +272,11 @@ function RpcBuilder(packer, options, transport, onRequest)
/** /**
* Store the response to prevent to process duplicate request later * Store the response to prevent to process duplicate request later
*/ */
function storeResponse(message, id, dest) function storeResponse(message, id, dest) {
{ var response = {
var response =
{
message: message, message: message,
/** Timeout to auto-clean old responses */ /** Timeout to auto-clean old responses */
timeout: setTimeout(function() timeout: setTimeout(function () {
{
responses.remove(id, dest); responses.remove(id, dest);
}, },
response_timeout) response_timeout)
@ -305,10 +288,8 @@ function RpcBuilder(packer, options, transport, onRequest)
/** /**
* Store the response to ignore duplicated messages later * Store the response to ignore duplicated messages later
*/ */
function storeProcessedResponse(ack, from) function storeProcessedResponse(ack, from) {
{ var timeout = setTimeout(function () {
var timeout = setTimeout(function()
{
processedResponses.remove(ack, from); processedResponses.remove(ack, from);
}, },
duplicates_timeout); duplicates_timeout);
@ -330,22 +311,18 @@ function RpcBuilder(packer, options, transport, onRequest)
* @param {Integer} id - identifier of the request * @param {Integer} id - identifier of the request
* @param [from] - source of the notification * @param [from] - source of the notification
*/ */
function RpcRequest(method, params, id, from, transport) function RpcRequest(method, params, id, from, transport) {
{
RpcNotification.call(this, method, params); RpcNotification.call(this, method, params);
this.getTransport = function() this.getTransport = function () {
{
return transport; return transport;
} }
this.setTransport = function(value) this.setTransport = function (value) {
{
transport = unifyTransport(value); transport = unifyTransport(value);
} }
if(!defineProperty_IE8) if (!defineProperty_IE8)
Object.defineProperty(this, 'transport', Object.defineProperty(this, 'transport', {
{
get: this.getTransport.bind(this), get: this.getTransport.bind(this),
set: this.setTransport.bind(this) set: this.setTransport.bind(this)
}) })
@ -355,13 +332,11 @@ function RpcBuilder(packer, options, transport, onRequest)
/** /**
* @constant {Boolean} duplicated * @constant {Boolean} duplicated
*/ */
if(!(transport || self.getTransport())) if (!(transport || self.getTransport())) {
{ if (defineProperty_IE8)
if(defineProperty_IE8)
this.duplicated = Boolean(response) this.duplicated = Boolean(response)
else else
Object.defineProperty(this, 'duplicated', Object.defineProperty(this, 'duplicated', {
{
value: Boolean(response) value: Boolean(response)
}); });
} }
@ -378,23 +353,18 @@ function RpcBuilder(packer, options, transport, onRequest)
* *
* @returns {string} * @returns {string}
*/ */
this.reply = function(error, result, transport) this.reply = function (error, result, transport) {
{
// Fix optional parameters // Fix optional parameters
if(error instanceof Function || error && error.send instanceof Function) if (error instanceof Function || error && error.send instanceof Function) {
{ if (result != undefined)
if(result != undefined)
throw new SyntaxError("There can't be parameters after callback"); throw new SyntaxError("There can't be parameters after callback");
transport = error; transport = error;
result = null; result = null;
error = undefined; error = undefined;
} } else if (result instanceof Function ||
result && result.send instanceof Function) {
else if(result instanceof Function if (transport != undefined)
|| result && result.send instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback"); throw new SyntaxError("There can't be parameters after callback");
transport = result; transport = result;
@ -404,56 +374,47 @@ function RpcBuilder(packer, options, transport, onRequest)
transport = unifyTransport(transport); transport = unifyTransport(transport);
// Duplicated request, remove old response timeout // Duplicated request, remove old response timeout
if(response) if (response)
clearTimeout(response.timeout); clearTimeout(response.timeout);
if(from != undefined) if (from != undefined) {
{ if (error)
if(error)
error.dest = from; error.dest = from;
if(result) if (result)
result.dest = from; result.dest = from;
}; };
var message; var message;
// New request or overriden one, create new response with provided data // New request or overriden one, create new response with provided data
if(error || result != undefined) if (error || result != undefined) {
{ if (self.peerID != undefined) {
if(self.peerID != undefined) if (error)
{
if(error)
error.from = self.peerID; error.from = self.peerID;
else else
result.from = self.peerID; result.from = self.peerID;
} }
// Protocol indicates that responses has own request methods // Protocol indicates that responses has own request methods
if(responseMethod) if (responseMethod) {
{ if (responseMethod.error == undefined && error)
if(responseMethod.error == undefined && error) message = {
message =
{
error: error error: error
}; };
else else {
{ var method = error ?
var method = error responseMethod.error :
? responseMethod.error responseMethod.response;
: responseMethod.response;
message = message = {
{
method: method, method: method,
params: error || result params: error || result
}; };
} }
} } else
else message = {
message =
{
error: error, error: error,
result: result result: result
}; };
@ -462,12 +423,14 @@ function RpcBuilder(packer, options, transport, onRequest)
} }
// Duplicate & not-overriden request, re-send old response // Duplicate & not-overriden request, re-send old response
else if(response) else if (response)
message = response.message; message = response.message;
// New empty reply, response null value // New empty reply, response null value
else else
message = packer.pack({result: null}, id); message = packer.pack({
result: null
}, id);
// Store the response to prevent to process a duplicated request later // Store the response to prevent to process a duplicated request later
storeResponse(message, id, from); storeResponse(message, id, from);
@ -475,7 +438,7 @@ function RpcBuilder(packer, options, transport, onRequest)
// Return the stored response so it can be directly send back // Return the stored response so it can be directly send back
transport = transport || this.getTransport() || self.getTransport(); transport = transport || this.getTransport() || self.getTransport();
if(transport) if (transport)
return transport.send(message); return transport.send(message);
return message; return message;
@ -484,15 +447,14 @@ function RpcBuilder(packer, options, transport, onRequest)
inherits(RpcRequest, RpcNotification); inherits(RpcRequest, RpcNotification);
function cancel(message) function cancel(message) {
{
var key = message2Key[message]; var key = message2Key[message];
if(!key) return; if (!key) return;
delete message2Key[message]; delete message2Key[message];
var request = requests.pop(key.id, key.dest); var request = requests.pop(key.id, key.dest);
if(!request) return; if (!request) return;
clearTimeout(request.timeout); clearTimeout(request.timeout);
@ -505,20 +467,18 @@ function RpcBuilder(packer, options, transport, onRequest)
* *
* If `message` is not given, cancel all the request * If `message` is not given, cancel all the request
*/ */
this.cancel = function(message) this.cancel = function (message) {
{ if (message) return cancel(message);
if(message) return cancel(message);
for(var message in message2Key) for (var message in message2Key)
cancel(message); cancel(message);
}; };
this.close = function() this.close = function () {
{
// Prevent to receive new messages // Prevent to receive new messages
var transport = this.getTransport(); var transport = this.getTransport();
if(transport && transport.close) if (transport && transport.close)
transport.close(4003, "Cancel request"); transport.close(4003, "Cancel request");
// Request & processed responses // Request & processed responses
@ -527,8 +487,7 @@ function RpcBuilder(packer, options, transport, onRequest)
processedResponses.forEach(clearTimeout); processedResponses.forEach(clearTimeout);
// Responses // Responses
responses.forEach(function(response) responses.forEach(function (response) {
{
clearTimeout(response.timeout); clearTimeout(response.timeout);
}); });
}; };
@ -546,76 +505,62 @@ function RpcBuilder(packer, options, transport, onRequest)
* *
* @returns {string} A raw JsonRPC 2.0 request or notification string * @returns {string} A raw JsonRPC 2.0 request or notification string
*/ */
this.encode = function(method, params, dest, transport, callback) this.encode = function (method, params, dest, transport, callback) {
{
// Fix optional parameters // Fix optional parameters
if(params instanceof Function) if (params instanceof Function) {
{ if (dest != undefined)
if(dest != undefined)
throw new SyntaxError("There can't be parameters after callback"); throw new SyntaxError("There can't be parameters after callback");
callback = params; callback = params;
transport = undefined; transport = undefined;
dest = undefined; dest = undefined;
params = undefined; params = undefined;
} } else if (dest instanceof Function) {
if (transport != undefined)
else if(dest instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback"); throw new SyntaxError("There can't be parameters after callback");
callback = dest; callback = dest;
transport = undefined; transport = undefined;
dest = undefined; dest = undefined;
} } else if (transport instanceof Function) {
if (callback != undefined)
else if(transport instanceof Function)
{
if(callback != undefined)
throw new SyntaxError("There can't be parameters after callback"); throw new SyntaxError("There can't be parameters after callback");
callback = transport; callback = transport;
transport = undefined; transport = undefined;
}; };
if(self.peerID != undefined) if (self.peerID != undefined) {
{
params = params || {}; params = params || {};
params.from = self.peerID; params.from = self.peerID;
}; };
if(dest != undefined) if (dest != undefined) {
{
params = params || {}; params = params || {};
params.dest = dest; params.dest = dest;
}; };
// Encode message // Encode message
var message = var message = {
{
method: method, method: method,
params: params params: params
}; };
if(callback) if (callback) {
{
var id = requestID++; var id = requestID++;
var retried = 0; var retried = 0;
message = packer.pack(message, id); message = packer.pack(message, id);
function dispatchCallback(error, result) function dispatchCallback(error, result) {
{
self.cancel(message); self.cancel(message);
callback(error, result); callback(error, result);
}; };
var request = var request = {
{
message: message, message: message,
callback: dispatchCallback, callback: dispatchCallback,
responseMethods: responseMethods[method] || {} responseMethods: responseMethods[method] || {}
@ -623,25 +568,26 @@ function RpcBuilder(packer, options, transport, onRequest)
var encode_transport = unifyTransport(transport); var encode_transport = unifyTransport(transport);
function sendRequest(transport) function sendRequest(transport) {
{
var rt = (method === 'ping' ? ping_request_timeout : request_timeout); var rt = (method === 'ping' ? ping_request_timeout : request_timeout);
request.timeout = setTimeout(timeout, rt*Math.pow(2, retried++)); request.timeout = setTimeout(timeout, rt * Math.pow(2, retried++));
message2Key[message] = {id: id, dest: dest}; message2Key[message] = {
id: id,
dest: dest
};
requests.set(request, id, dest); requests.set(request, id, dest);
transport = transport || encode_transport || self.getTransport(); transport = transport || encode_transport || self.getTransport();
if(transport) if (transport)
return transport.send(message); return transport.send(message);
return message; return message;
}; };
function retry(transport) function retry(transport) {
{
transport = unifyTransport(transport); transport = unifyTransport(transport);
console.warn(retried+' retry for request message:',message); console.warn(retried + ' retry for request message:', message);
var timeout = processedResponses.pop(id, dest); var timeout = processedResponses.pop(id, dest);
clearTimeout(timeout); clearTimeout(timeout);
@ -649,9 +595,8 @@ function RpcBuilder(packer, options, transport, onRequest)
return sendRequest(transport); return sendRequest(transport);
}; };
function timeout() function timeout() {
{ if (retried < max_retries)
if(retried < max_retries)
return retry(transport); return retry(transport);
var error = new Error('Request has timed out'); var error = new Error('Request has timed out');
@ -669,7 +614,7 @@ function RpcBuilder(packer, options, transport, onRequest)
message = packer.pack(message); message = packer.pack(message);
transport = transport || this.getTransport(); transport = transport || this.getTransport();
if(transport) if (transport)
return transport.send(message); return transport.send(message);
return message; return message;
@ -686,17 +631,13 @@ function RpcBuilder(packer, options, transport, onRequest)
* *
* @throws {TypeError} - Message is not defined * @throws {TypeError} - Message is not defined
*/ */
this.decode = function(message, transport) this.decode = function (message, transport) {
{ if (!message)
if(!message)
throw new TypeError("Message is not defined"); throw new TypeError("Message is not defined");
try try {
{
message = packer.unpack(message); message = packer.unpack(message);
} } catch (e) {
catch(e)
{
// Ignore invalid messages // Ignore invalid messages
return console.debug(e, message); return console.debug(e, message);
}; };
@ -710,43 +651,38 @@ function RpcBuilder(packer, options, transport, onRequest)
var dest = params.dest; var dest = params.dest;
// Ignore messages send by us // Ignore messages send by us
if(self.peerID != undefined && from == self.peerID) return; if (self.peerID != undefined && from == self.peerID) return;
// Notification // Notification
if(id == undefined && ack == undefined) if (id == undefined && ack == undefined) {
{
var notification = new RpcNotification(method, params); var notification = new RpcNotification(method, params);
if(self.emit('request', notification)) return; if (self.emit('request', notification)) return;
return notification; return notification;
}; };
function processRequest() function processRequest() {
{
// If we have a transport and it's a duplicated request, reply inmediatly // If we have a transport and it's a duplicated request, reply inmediatly
transport = unifyTransport(transport) || self.getTransport(); transport = unifyTransport(transport) || self.getTransport();
if(transport) if (transport) {
{
var response = responses.get(id, from); var response = responses.get(id, from);
if(response) if (response)
return transport.send(response.message); return transport.send(response.message);
}; };
var idAck = (id != undefined) ? id : ack; var idAck = (id != undefined) ? id : ack;
var request = new RpcRequest(method, params, idAck, from, transport); var request = new RpcRequest(method, params, idAck, from, transport);
if(self.emit('request', request)) return; if (self.emit('request', request)) return;
return request; return request;
}; };
function processResponse(request, error, result) function processResponse(request, error, result) {
{
request.callback(error, result); request.callback(error, result);
}; };
function duplicatedResponse(timeout) function duplicatedResponse(timeout) {
{
console.warn("Response already processed", message); console.warn("Response already processed", message);
// Update duplicated responses timeout // Update duplicated responses timeout
@ -756,27 +692,24 @@ function RpcBuilder(packer, options, transport, onRequest)
// Request, or response with own method // Request, or response with own method
if(method) if (method) {
{
// Check if it's a response with own method // Check if it's a response with own method
if(dest == undefined || dest == self.peerID) if (dest == undefined || dest == self.peerID) {
{
var request = requests.get(ack, from); var request = requests.get(ack, from);
if(request) if (request) {
{
var responseMethods = request.responseMethods; var responseMethods = request.responseMethods;
if(method == responseMethods.error) if (method == responseMethods.error)
return processResponse(request, params); return processResponse(request, params);
if(method == responseMethods.response) if (method == responseMethods.response)
return processResponse(request, null, params); return processResponse(request, null, params);
return processRequest(); return processRequest();
} }
var processed = processedResponses.get(ack, from); var processed = processedResponses.get(ack, from);
if(processed) if (processed)
return duplicatedResponse(processed); return duplicatedResponse(processed);
} }
@ -788,15 +721,14 @@ function RpcBuilder(packer, options, transport, onRequest)
var result = message.result; var result = message.result;
// Ignore responses not send to us // Ignore responses not send to us
if(error && error.dest && error.dest != self.peerID) return; if (error && error.dest && error.dest != self.peerID) return;
if(result && result.dest && result.dest != self.peerID) return; if (result && result.dest && result.dest != self.peerID) return;
// Response // Response
var request = requests.get(ack, from); var request = requests.get(ack, from);
if(!request) if (!request) {
{
var processed = processedResponses.get(ack, from); var processed = processedResponses.get(ack, from);
if(processed) if (processed)
return duplicatedResponse(processed); return duplicatedResponse(processed);
return console.warn("No callback was defined for this message", message); return console.warn("No callback was defined for this message", message);

View File

@ -10,37 +10,31 @@
* *
* @return {String} - the stringified JsonRPC 2.0 message * @return {String} - the stringified JsonRPC 2.0 message
*/ */
function pack(message, id) function pack(message, id) {
{ var result = {
var result =
{
jsonrpc: "2.0" jsonrpc: "2.0"
}; };
// Request // Request
if(message.method) if (message.method) {
{
result.method = message.method; result.method = message.method;
if(message.params) if (message.params)
result.params = message.params; result.params = message.params;
// Request is a notification // Request is a notification
if(id != undefined) if (id != undefined)
result.id = id; result.id = id;
} }
// Response // Response
else if(id != undefined) else if (id != undefined) {
{ if (message.error) {
if(message.error) if (message.result !== undefined)
{
if(message.result !== undefined)
throw new TypeError("Both result and error are defined"); throw new TypeError("Both result and error are defined");
result.error = message.error; result.error = message.error;
} } else if (message.result !== undefined)
else if(message.result !== undefined)
result.result = message.result; result.result = message.result;
else else
throw new TypeError("No result or error is defined"); throw new TypeError("No result or error is defined");
@ -60,35 +54,33 @@ function pack(message, id)
* *
* @return {Object} - object filled with the JsonRPC 2.0 message content * @return {Object} - object filled with the JsonRPC 2.0 message content
*/ */
function unpack(message) function unpack(message) {
{
var result = message; var result = message;
if(typeof message === 'string' || message instanceof String) { if (typeof message === 'string' || message instanceof String) {
result = JSON.parse(message); result = JSON.parse(message);
} }
// Check if it's a valid message // Check if it's a valid message
var version = result.jsonrpc; var version = result.jsonrpc;
if(version !== '2.0') if (version !== '2.0')
throw new TypeError("Invalid JsonRPC version '" + version + "': " + message); throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
// Response // Response
if(result.method == undefined) if (result.method == undefined) {
{ if (result.id == undefined)
if(result.id == undefined) throw new TypeError("Invalid message: " + message);
throw new TypeError("Invalid message: "+message);
var result_defined = result.result !== undefined; var result_defined = result.result !== undefined;
var error_defined = result.error !== undefined; var error_defined = result.error !== undefined;
// Check only result or error is defined, not both or none // Check only result or error is defined, not both or none
if(result_defined && error_defined) if (result_defined && error_defined)
throw new TypeError("Both result and error are defined: "+message); throw new TypeError("Both result and error are defined: " + message);
if(!result_defined && !error_defined) if (!result_defined && !error_defined)
throw new TypeError("No result or error is defined: "+message); throw new TypeError("No result or error is defined: " + message);
result.ack = result.id; result.ack = result.id;
delete result.id; delete result.id;

View File

@ -1,13 +1,10 @@
function pack(message) function pack(message) {
{
throw new TypeError("Not yet implemented"); throw new TypeError("Not yet implemented");
}; };
function unpack(message) function unpack(message) {
{
throw new TypeError("Not yet implemented"); throw new TypeError("Not yet implemented");
}; };
exports.pack = pack; exports.pack = pack;
exports.unpack = unpack; exports.unpack = unpack;

View File

@ -1,12 +1,124 @@
import {JL} from 'jsnlog'
import {OpenVidu} from "../../OpenVidu/OpenVidu";
import {OpenViduLoggerConfiguration} from "./OpenViduLoggerConfiguration";
import JSNLogAjaxAppender = JL.JSNLogAjaxAppender;
export class OpenViduLogger { export class OpenViduLogger {
private static instance: OpenViduLogger; private static instance: OpenViduLogger;
private JSNLOG_URL: string = "/openvidu/elk/openvidu-browser-logs";
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100;
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
private MAX_LENGTH_STRING_JSON: number = 1000;
private logger: Console = window.console; private logger: Console = window.console;
private LOG_FNS = [this.logger.log, this.logger.debug, this.logger.info, this.logger.warn, this.logger.error]; private LOG_FNS = [this.logger.log, this.logger.debug, this.logger.info, this.logger.warn, this.logger.error];
private currentAppender: any;
private isProdMode = false; private isProdMode = false;
private isJSNLogSetup = false;
// This two variables are used to restart JSNLog
// on different sessions and different userIds
private loggingSessionId: string | undefined;
private loggingFinalUserId: string | undefined;
private constructor() {} private constructor() {}
static configureJSNLog(openVidu: OpenVidu, token: string) {
try {
// If instance is created and it is OpenVidu Pro
if (this.instance && openVidu.webrtcStatsInterval > -1
// If logs are enabled
&& openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug
// Only reconfigure it if session or finalUserId has changed
&& this.instance.canConfigureJSNLog(openVidu, this.instance)) {
// isJSNLogSetup will not be true until completed setup
this.instance.isJSNLogSetup = false;
this.instance.info("Configuring JSNLogs.");
const finalUserId = openVidu.finalUserId;
const sessionId = openVidu.session.sessionId;
const beforeSendCallback = (xhr) => {
// If 401 or 403 or 404 modify ready and status so JSNLog don't retry to send logs
// https://github.com/mperdeck/jsnlog.js/blob/v2.30.0/jsnlog.ts#L805-L818
const parentReadyStateFunction = xhr.onreadystatechange;
xhr.onreadystatechange = () => {
if ((xhr.status == 401) || (xhr.status == 403) || (xhr.status == 404)) {
Object.defineProperty( xhr, "readyState", {value: 4});
Object.defineProperty( xhr, "status", {value: 200});
}
parentReadyStateFunction();
}
// Headers to identify and authenticate logs
xhr.setRequestHeader('Authorization', "Basic " + btoa(`${finalUserId}%/%${sessionId}` + ":" + token));
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
// Additional headers for OpenVidu
xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
xhr.setRequestHeader('OV-Session-Id', sessionId);
xhr.setRequestHeader('OV-Token', token);
}
// Creation of the appender.
this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
this.instance.currentAppender.setOptions({
beforeSend: beforeSendCallback,
maxBatchSize: 1000,
batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES
});
// Avoid circular dependencies
const logSerializer = (obj): string => {
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === "object" && value != null) {
if (seen.has(value) || value instanceof HTMLElement) {
return;
}
seen.add(value);
}
return value;
};
};
// Cut long messages
const stringifyJson = JSON.stringify(obj, getCircularReplacer());
if (stringifyJson.length > this.instance.MAX_LENGTH_STRING_JSON) {
return `${stringifyJson.substring(0, this.instance.MAX_LENGTH_STRING_JSON)}...`;
}
return stringifyJson;
};
// Initialize JL to send logs
JL.setOptions({
defaultAjaxUrl: openVidu.httpUri + this.instance.JSNLOG_URL,
serialize: logSerializer
});
JL().setOptions({
appenders: [this.instance.currentAppender]
});
this.instance.isJSNLogSetup = true;
this.instance.loggingSessionId = sessionId;
this.instance.loggingFinalUserId = finalUserId;
this.instance.info("JSNLog configured.");
}
} catch (e) {
console.error("Error configuring JSNLog: ");
console.error(e);
this.instance.isJSNLogSetup = false;
this.instance.loggingSessionId = undefined;
this.instance.loggingFinalUserId = undefined;
this.instance.currentAppender = undefined;
}
}
static getInstance(): OpenViduLogger { static getInstance(): OpenViduLogger {
if(!OpenViduLogger.instance){ if(!OpenViduLogger.instance){
OpenViduLogger.instance = new OpenViduLogger(); OpenViduLogger.instance = new OpenViduLogger();
@ -14,10 +126,21 @@ export class OpenViduLogger {
return OpenViduLogger.instance; return OpenViduLogger.instance;
} }
private isDebugLogEnabled() {
return this.isJSNLogSetup;
}
private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean {
return openVidu.session.sessionId != logger.loggingSessionId
}
log(...args: any[]){ log(...args: any[]){
if (!this.isProdMode) { if (!this.isProdMode) {
this.LOG_FNS[0].apply(this.logger, arguments); this.LOG_FNS[0].apply(this.logger, arguments);
} }
if (this.isDebugLogEnabled()) {
JL().info(arguments);
}
} }
debug(...args: any[]) { debug(...args: any[]) {
@ -30,19 +153,35 @@ export class OpenViduLogger {
if (!this.isProdMode) { if (!this.isProdMode) {
this.LOG_FNS[2].apply(this.logger, arguments); this.LOG_FNS[2].apply(this.logger, arguments);
} }
if (this.isDebugLogEnabled()) {
JL().info(arguments);
}
} }
warn(...args: any[]) { warn(...args: any[]) {
if (!this.isProdMode) { if (!this.isProdMode) {
this.LOG_FNS[3].apply(this.logger, arguments); this.LOG_FNS[3].apply(this.logger, arguments);
} }
if (this.isDebugLogEnabled()) {
JL().warn(arguments);
}
} }
error(...args: any[]) { error(...args: any[]) {
this.LOG_FNS[4].apply(this.logger, arguments); this.LOG_FNS[4].apply(this.logger, arguments);
if (this.isDebugLogEnabled()) {
JL().error(arguments);
}
}
flush() {
if(this.isDebugLogEnabled() && this.currentAppender != null) {
this.currentAppender.sendBatch();
}
} }
enableProdMode(){ enableProdMode(){
this.isProdMode = true; this.isProdMode = true;
} }
} }

View File

@ -0,0 +1,4 @@
export enum OpenViduLoggerConfiguration {
disabled = 'disabled',
debug = 'debug'
}

View File

@ -2,7 +2,7 @@ import platform = require("platform");
export class PlatformUtils { export class PlatformUtils {
protected static instance: PlatformUtils; protected static instance: PlatformUtils;
constructor() {} constructor() { }
static getInstance(): PlatformUtils { static getInstance(): PlatformUtils {
if (!this.instance) { if (!this.instance) {
@ -118,7 +118,7 @@ export class PlatformUtils {
*/ */
public isIOSWithSafari(): boolean { public isIOSWithSafari(): boolean {
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent; const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
return ( return this.isIPhoneOrIPad() && (
/\b(\w*Apple\w*)\b/.test(navigator.vendor) && /\b(\w*Apple\w*)\b/.test(navigator.vendor) &&
/\b(\w*Safari\w*)\b/.test(userAgent) && /\b(\w*Safari\w*)\b/.test(userAgent) &&
!/\b(\w*CriOS\w*)\b/.test(userAgent) && !/\b(\w*CriOS\w*)\b/.test(userAgent) &&
@ -156,6 +156,18 @@ export class PlatformUtils {
return false; return false;
} }
/**
* @hidden
*/
public isChromium(): boolean {
return this.isChromeBrowser() || this.isChromeMobileBrowser() ||
this.isOperaBrowser() || this.isOperaMobileBrowser() ||
this.isEdgeBrowser() || this.isEdgeMobileBrowser() ||
this.isSamsungBrowser() ||
this.isIonicAndroid() || this.isIonicIos() ||
this.isElectron();
}
/** /**
* @hidden * @hidden
*/ */

View File

@ -16,7 +16,8 @@
*/ */
import freeice = require('freeice'); import freeice = require('freeice');
import uuid = require('uuid'); import { v4 as uuidv4 } from 'uuid';
import { ExceptionEventName } from '../Events/ExceptionEvent';
import { OpenViduLogger } from '../Logger/OpenViduLogger'; import { OpenViduLogger } from '../Logger/OpenViduLogger';
import { PlatformUtils } from '../Utils/Platform'; import { PlatformUtils } from '../Utils/Platform';
@ -36,8 +37,9 @@ export interface WebRtcPeerConfiguration {
video: boolean video: boolean
}; };
simulcast: boolean; simulcast: boolean;
onicecandidate: (event) => void; onicecandidate: (event: RTCIceCandidate) => void;
iceServers?: RTCIceServer[]; onexception: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
iceServers: RTCIceServer[] | undefined;
mediaStream?: MediaStream | null; mediaStream?: MediaStream | null;
mode?: 'sendonly' | 'recvonly' | 'sendrecv'; mode?: 'sendonly' | 'recvonly' | 'sendrecv';
id?: string; id?: string;
@ -69,49 +71,23 @@ export class WebRtcPeer {
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers }); this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
this.pc.onicecandidate = event => { this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
if (!!event.candidate) { if (event.candidate != null) {
const candidate: RTCIceCandidate = event.candidate; const candidate: RTCIceCandidate = event.candidate;
if (candidate) { this.configuration.onicecandidate(candidate);
if (candidate.candidate !== '') {
this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: candidate.candidate }); this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: candidate.candidate });
this.candidategatheringdone = false;
this.configuration.onicecandidate(event.candidate);
} else if (!this.candidategatheringdone) {
this.candidategatheringdone = true;
} }
} }
}; });
this.pc.onsignalingstatechange = () => { this.pc.addEventListener('signalingstatechange', () => {
if (this.pc.signalingState === 'stable') { if (this.pc.signalingState === 'stable') {
while (this.iceCandidateList.length > 0) { while (this.iceCandidateList.length > 0) {
let candidate = this.iceCandidateList.shift(); let candidate = this.iceCandidateList.shift();
this.pc.addIceCandidate(<RTCIceCandidate>candidate); this.pc.addIceCandidate(<RTCIceCandidate>candidate);
} }
} }
};
this.start();
}
/**
* This function creates the RTCPeerConnection object taking into account the
* properties received in the constructor. It starts the SDP negotiation
* process: generates the SDP offer and invokes the onsdpoffer callback. This
* callback is expected to send the SDP offer, in order to obtain an SDP
* answer from another peer.
*/
start(): Promise<any> {
return new Promise((resolve, reject) => {
if (this.pc.signalingState === 'closed') {
reject('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
}
if (!!this.configuration.mediaStream) {
for (const track of this.configuration.mediaStream.getTracks()) {
this.pc.addTrack(track, this.configuration.mediaStream);
}
resolve();
}
}); });
} }
@ -131,20 +107,20 @@ export class WebRtcPeer {
} }
/** /**
* Function that creates an offer, sets it as local description and returns the offer param * Creates an SDP offer from the local RTCPeerConnection to send to the other peer
* to send to OpenVidu Server (will be the remote description of other peer) * Only if the negotiation was initiated by the this peer
*/ */
generateOffer(): Promise<string> { createOffer(): Promise<RTCSessionDescriptionInit> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const useAudio = this.configuration.mediaConstraints.audio; const hasAudio = this.configuration.mediaConstraints.audio;
const useVideo = this.configuration.mediaConstraints.video; const hasVideo = this.configuration.mediaConstraints.video;
let offerPromise: Promise<RTCSessionDescriptionInit>; let offerPromise: Promise<RTCSessionDescriptionInit>;
// TODO: Delete this conditional when all supported browsers are // TODO: Delete this conditional when all supported browsers are
// modern enough to implement the getTransceivers() method. // modern enough to implement the Transceiver methods.
if ("getTransceivers" in this.pc) { if ("addTransceiver" in this.pc) {
logger.debug("[generateOffer] Method pc.getTransceivers() is available; using it"); logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it");
// At this point, all "send" audio/video tracks have been added // At this point, all "send" audio/video tracks have been added
// with pc.addTrack(), which in modern versions of libwebrtc // with pc.addTrack(), which in modern versions of libwebrtc
@ -183,13 +159,13 @@ export class WebRtcPeer {
); );
} }
if (useAudio) { if (hasAudio) {
this.pc.addTransceiver("audio", { this.pc.addTransceiver("audio", {
direction: this.configuration.mode, direction: this.configuration.mode,
}); });
} }
if (useVideo) { if (hasVideo) {
this.pc.addTransceiver("video", { this.pc.addTransceiver("video", {
direction: this.configuration.mode, direction: this.configuration.mode,
}); });
@ -206,9 +182,9 @@ export class WebRtcPeer {
const constraints: RTCOfferOptions = { const constraints: RTCOfferOptions = {
offerToReceiveAudio: offerToReceiveAudio:
this.configuration.mode !== "sendonly" && useAudio, this.configuration.mode !== "sendonly" && hasAudio,
offerToReceiveVideo: offerToReceiveVideo:
this.configuration.mode !== "sendonly" && useVideo, this.configuration.mode !== "sendonly" && hasVideo,
}; };
logger.debug( logger.debug(
@ -244,10 +220,94 @@ export class WebRtcPeer {
} }
/** /**
* Function invoked when a SDP answer is received. Final step in SDP negotiation, the peer * Creates an SDP answer from the local RTCPeerConnection to send to the other peer
* just needs to set the answer as its remote description * Only if the negotiation was initiated by the other peer
*/ */
processAnswer(sdpAnswer: string, needsTimeoutOnProcessAnswer: boolean): Promise<string> { createAnswer(): Promise<RTCSessionDescriptionInit> {
return new Promise((resolve, reject) => {
let offerAudio, offerVideo = true;
if (!!this.configuration.mediaConstraints) {
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ?
this.configuration.mediaConstraints.audio : true;
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ?
this.configuration.mediaConstraints.video : true;
}
const constraints: RTCOfferOptions = {
offerToReceiveAudio: offerAudio,
offerToReceiveVideo: offerVideo
};
this.pc.createAnswer(constraints).then(sdpAnswer => {
resolve(sdpAnswer);
}).catch(error => {
reject(error);
});
});
}
/**
* This peer initiated negotiation. Step 1/4 of SDP offer-answer protocol
*/
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => {
this.pc.setLocalDescription(offer)
.then(() => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
logger.debug('Local description set', localDescription.sdp);
resolve();
} else {
reject('Local description is not defined');
}
})
.catch(error => {
reject(error);
});
});
}
/**
* Other peer initiated negotiation. Step 2/4 of SDP offer-answer protocol
*/
processRemoteOffer(sdpOffer: string): Promise<void> {
return new Promise((resolve, reject) => {
const offer: RTCSessionDescriptionInit = {
type: 'offer',
sdp: sdpOffer
};
logger.debug('SDP offer received, setting remote description', offer);
if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed when trying to set remote description');
}
this.setRemoteDescription(offer)
.then(() => {
resolve();
})
.catch(error => {
reject(error);
});
});
}
/**
* Other peer initiated negotiation. Step 3/4 of SDP offer-answer protocol
*/
processLocalAnswer(answer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => {
logger.debug('SDP answer created, setting local description');
if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed when trying to set local description');
}
this.pc.setLocalDescription(answer)
.then(() => resolve())
.catch(error => reject(error));
});
}
/**
* This peer initiated negotiation. Step 4/4 of SDP offer-answer protocol
*/
processRemoteAnswer(sdpAnswer: string): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const answer: RTCSessionDescriptionInit = { const answer: RTCSessionDescriptionInit = {
type: 'answer', type: 'answer',
@ -256,34 +316,19 @@ export class WebRtcPeer {
logger.debug('SDP answer received, setting remote description'); logger.debug('SDP answer received, setting remote description');
if (this.pc.signalingState === 'closed') { if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed'); reject('RTCPeerConnection is closed when trying to set remote description');
} }
this.setRemoteDescription(answer)
this.setRemoteDescription(answer, needsTimeoutOnProcessAnswer, resolve, reject); .then(() => resolve())
.catch(error => reject(error));
}); });
} }
/** /**
* @hidden * @hidden
*/ */
setRemoteDescription(answer: RTCSessionDescriptionInit, needsTimeoutOnProcessAnswer: boolean, resolve: (value?: string | PromiseLike<string> | undefined) => void, reject: (reason?: any) => void) { async setRemoteDescription(sdp: RTCSessionDescriptionInit): Promise<void> {
if (platform.isIonicIos()) { return this.pc.setRemoteDescription(sdp);
// Ionic iOS platform
if (needsTimeoutOnProcessAnswer) {
// 400 ms have not elapsed yet since first remote stream triggered Stream#initWebRtcPeerReceive
setTimeout(() => {
logger.info('setRemoteDescription run after timeout for Ionic iOS device');
this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error));
}, 250);
} else {
// 400 ms have elapsed
this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error));
}
} else {
// Rest of platforms
this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error));
}
} }
/** /**
@ -313,15 +358,19 @@ export class WebRtcPeer {
} }
addIceConnectionStateChangeListener(otherId: string) { addIceConnectionStateChangeListener(otherId: string) {
this.pc.oniceconnectionstatechange = () => { this.pc.addEventListener('iceconnectionstatechange', () => {
const iceConnectionState: RTCIceConnectionState = this.pc.iceConnectionState; const iceConnectionState: RTCIceConnectionState = this.pc.iceConnectionState;
switch (iceConnectionState) { switch (iceConnectionState) {
case 'disconnected': case 'disconnected':
// Possible network disconnection // Possible network disconnection
logger.warn('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection'); const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
logger.warn(msg1);
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
break; break;
case 'failed': case 'failed':
logger.error('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"'); const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.id + ' (' + otherId + ') to "failed"';
logger.error(msg2);
this.configuration.onexception(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
break; break;
case 'closed': case 'closed':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"'); logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
@ -339,14 +388,14 @@ export class WebRtcPeer {
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"'); logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"');
break; break;
} }
} });
} }
/** /**
* @hidden * @hidden
*/ */
generateUniqueId(): string { generateUniqueId(): string {
return uuid.v4(); return uuidv4();
} }
} }

View File

@ -29,39 +29,76 @@ const logger: OpenViduLogger = OpenViduLogger.getInstance();
*/ */
let platform: PlatformUtils; let platform: PlatformUtils;
interface WebrtcStatsConfig {
interval: number,
httpEndpoint: string
}
interface JSONStatsResponse {
'@timestamp': string,
participant_id: string,
session_id: string,
platform: string,
platform_description: string,
stream: string,
webrtc_stats: IWebrtcStats
}
/**
* Common WebRtcSTats for latest Chromium and Firefox versions
*/
interface IWebrtcStats {
inbound?: {
audio: {
bytesReceived: number,
packetsReceived: number,
packetsLost: number,
jitter: number
} | {},
video: {
bytesReceived: number,
packetsReceived: number,
packetsLost: number,
jitter?: number, // Firefox
jitterBufferDelay?: number, // Chrome
framesDecoded: number,
firCount: number,
nackCount: number,
pliCount: number,
frameHeight?: number, // Chrome
frameWidth?: number, // Chrome
framesDropped?: number, // Chrome
framesReceived?: number // Chrome
} | {}
},
outbound?: {
audio: {
bytesSent: number,
packetsSent: number,
} | {},
video: {
bytesSent: number,
packetsSent: number,
firCount: number,
framesEncoded: number,
nackCount: number,
pliCount: number,
qpSum: number,
frameHeight?: number, // Chrome
frameWidth?: number, // Chrome
framesSent?: number // Chrome
} | {}
}
};
export class WebRtcStats { export class WebRtcStats {
private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
private webRtcStatsEnabled = false; private webRtcStatsEnabled = false;
private webRtcStatsIntervalId: NodeJS.Timer; private webRtcStatsIntervalId: NodeJS.Timer;
private statsInterval = 1; private statsInterval = 1;
private stats: any = { private POST_URL: string;
inbound: {
audio: {
bytesReceived: 0,
packetsReceived: 0,
packetsLost: 0
},
video: {
bytesReceived: 0,
packetsReceived: 0,
packetsLost: 0,
framesDecoded: 0,
nackCount: 0
}
},
outbound: {
audio: {
bytesSent: 0,
packetsSent: 0,
},
video: {
bytesSent: 0,
packetsSent: 0,
framesEncoded: 0,
nackCount: 0
}
}
};
constructor(private stream: Stream) { constructor(private stream: Stream) {
platform = PlatformUtils.getInstance(); platform = PlatformUtils.getInstance();
@ -73,28 +110,174 @@ export class WebRtcStats {
public initWebRtcStats(): void { public initWebRtcStats(): void {
const elastestInstrumentation = localStorage.getItem('elastest-instrumentation'); const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
if (!!elastestInstrumentation) {
// ElasTest instrumentation object found in local storage
logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
if (!!webrtcObj) {
this.webRtcStatsEnabled = true; this.webRtcStatsEnabled = true;
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
// webrtc object found in local storage
logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
const instrumentation = JSON.parse(elastestInstrumentation); this.POST_URL = webrtcStatsConfig.httpEndpoint;
this.statsInterval = instrumentation.webrtc.interval; // Interval in seconds this.statsInterval = webrtcStatsConfig.interval; // Interval in seconds
logger.warn('localStorage item: ' + JSON.stringify(instrumentation)); this.webRtcStatsIntervalId = setInterval(async () => {
await this.sendStatsToHttpEndpoint();
this.webRtcStatsIntervalId = setInterval(() => {
this.sendStatsToHttpEndpoint(instrumentation);
}, this.statsInterval * 1000); }, this.statsInterval * 1000);
return; } else {
logger.debug('WebRtc stats not enabled');
}
} }
logger.debug('WebRtc stats not enabled'); // {
// "localCandidate": {
// "id": "RTCIceCandidate_/r4P1y2Q",
// "timestamp": 1616080155617,
// "type": "local-candidate",
// "transportId": "RTCTransport_0_1",
// "isRemote": false,
// "networkType": "wifi",
// "ip": "123.45.67.89",
// "port": 63340,
// "protocol": "udp",
// "candidateType": "srflx",
// "priority": 1686052607,
// "deleted": false,
// "raw": [
// "candidate:3345412921 1 udp 1686052607 123.45.67.89 63340 typ srflx raddr 192.168.1.31 rport 63340 generation 0 ufrag 0ZtT network-id 1 network-cost 10",
// "candidate:58094482 1 udp 41885695 98.76.54.32 44431 typ relay raddr 123.45.67.89 rport 63340 generation 0 ufrag 0ZtT network-id 1 network-cost 10"
// ]
// },
// "remoteCandidate": {
// "id": "RTCIceCandidate_1YO18gph",
// "timestamp": 1616080155617,
// "type": "remote-candidate",
// "transportId": "RTCTransport_0_1",
// "isRemote": true,
// "ip": "12.34.56.78",
// "port": 64989,
// "protocol": "udp",
// "candidateType": "srflx",
// "priority": 1679819263,
// "deleted": false,
// "raw": [
// "candidate:16 1 UDP 1679819263 12.34.56.78 64989 typ srflx raddr 172.19.0.1 rport 64989",
// "candidate:16 1 UDP 1679819263 12.34.56.78 64989 typ srflx raddr 172.19.0.1 rport 64989"
// ]
// }
// }
// Have been tested in:
// - Linux Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.218
// - Firefox 86
// - Microsoft Edge 91.0.825.0
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Windows Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.232
// - Firefox 86.0.1
// - Microsoft Edge 89.0.774.54
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - MacOS Desktop:
// - Chrome 89.0.4389.90
// - Firefox 87.0
// - Opera 75.0.3969.93
// - Microsoft Edge 89.0.774.57
// - Safari 14.0 (14610.1.28.1.9)
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Android:
// - Chrome Mobile 89.0.4389.90
// - Opera 62.3.3146.57763
// - Firefox Mobile 86.6.1
// - Microsoft Edge Mobile 46.02.4.5147
// - Ionic 5
// - React Native 0.64
// - iOS:
// - Safari Mobile
// - ¿Ionic?
// - ¿React Native?
public getSelectedIceCandidateInfo(): Promise<any> {
return new Promise(async (resolve, reject) => {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
let transportStat;
const candidatePairs: Map<string, any> = new Map();
const localCandidates: Map<string, any> = new Map();
const remoteCandidates: Map<string, any> = new Map();
statsReport.forEach((stat: any) => {
if (stat.type === 'transport' && (platform.isChromium() || platform.isSafariBrowser() || platform.isReactNative())) {
transportStat = stat;
}
switch (stat.type) {
case 'candidate-pair':
candidatePairs.set(stat.id, stat);
break;
case 'local-candidate':
localCandidates.set(stat.id, stat);
break;
case 'remote-candidate':
remoteCandidates.set(stat.id, stat);
break;
}
});
let selectedCandidatePair;
if (transportStat != null) {
const selectedCandidatePairId = transportStat.selectedCandidatePairId
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
} else {
// This is basically Firefox
const length = candidatePairs.size;
const iterator = candidatePairs.values();
for (let i = 0; i < length; i++) {
const candidatePair = iterator.next().value;
if (candidatePair['selected']) {
selectedCandidatePair = candidatePair;
break;
}
}
}
const localCandidateId = selectedCandidatePair.localCandidateId;
const remoteCandidateId = selectedCandidatePair.remoteCandidateId;
let finalLocalCandidate = localCandidates.get(localCandidateId);
if (!!finalLocalCandidate) {
const candList = this.stream.getLocalIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate &&
(c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
c.candidate.indexOf(finalLocalCandidate.port) >= 0);
});
finalLocalCandidate.raw = [];
for (let c of cand) {
finalLocalCandidate.raw.push(c.candidate);
}
} else {
finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
}
let finalRemoteCandidate = remoteCandidates.get(remoteCandidateId);
if (!!finalRemoteCandidate) {
const candList = this.stream.getRemoteIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate &&
(c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
c.candidate.indexOf(finalRemoteCandidate.port) >= 0);
});
finalRemoteCandidate.raw = [];
for (let c of cand) {
finalRemoteCandidate.raw.push(c.candidate);
}
} else {
finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
}
resolve({
localCandidate: finalLocalCandidate,
remoteCandidate: finalRemoteCandidate
});
});
} }
public stopWebRtcStats() { public stopWebRtcStats() {
@ -104,319 +287,146 @@ export class WebRtcStats {
} }
} }
public getSelectedIceCandidateInfo(): Promise<any> { private async sendStats(url: string, response: JSONStatsResponse): Promise<void> {
return new Promise((resolve, reject) => { try {
this.getStatsAgnostic(this.stream.getRTCPeerConnection(), const configuration: RequestInit = {
(stats) => { headers: {
if (platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isOperaMobileBrowser()) { 'Content-type': 'application/json'
let localCandidateId, remoteCandidateId, googCandidatePair;
const localCandidates = {};
const remoteCandidates = {};
for (const key in stats) {
const stat = stats[key];
if (stat.type === 'localcandidate') {
localCandidates[stat.id] = stat;
} else if (stat.type === 'remotecandidate') {
remoteCandidates[stat.id] = stat;
} else if (stat.type === 'googCandidatePair' && (stat.googActiveConnection === 'true')) {
googCandidatePair = stat;
localCandidateId = stat.localCandidateId;
remoteCandidateId = stat.remoteCandidateId;
}
}
let finalLocalCandidate = localCandidates[localCandidateId];
if (!!finalLocalCandidate) {
const candList = this.stream.getLocalIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate &&
c.candidate.indexOf(finalLocalCandidate.ipAddress) >= 0 &&
c.candidate.indexOf(finalLocalCandidate.portNumber) >= 0 &&
c.candidate.indexOf(finalLocalCandidate.priority) >= 0);
});
finalLocalCandidate.raw = !!cand[0] ? cand[0].candidate : 'ERROR: Cannot find local candidate in list of sent ICE candidates';
} else {
finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
}
let finalRemoteCandidate = remoteCandidates[remoteCandidateId];
if (!!finalRemoteCandidate) {
const candList = this.stream.getRemoteIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate &&
c.candidate.indexOf(finalRemoteCandidate.ipAddress) >= 0 &&
c.candidate.indexOf(finalRemoteCandidate.portNumber) >= 0 &&
c.candidate.indexOf(finalRemoteCandidate.priority) >= 0);
});
finalRemoteCandidate.raw = !!cand[0] ? cand[0].candidate : 'ERROR: Cannot find remote candidate in list of received ICE candidates';
} else {
finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
}
resolve({
googCandidatePair,
localCandidate: finalLocalCandidate,
remoteCandidate: finalRemoteCandidate
});
} else {
reject('Selected ICE candidate info only available for Chrome');
}
}, },
(error) => { body: JSON.stringify(response),
reject(error); method: 'POST',
};
await fetch(url, configuration);
} catch (error) {
logger.error(error);
}
}
private async sendStatsToHttpEndpoint(): Promise<void> {
try {
const webrtcStats: IWebrtcStats = await this.getCommonStats();
const response = this.generateJSONStatsResponse(webrtcStats);
await this.sendStats(this.POST_URL, response);
} catch (error) {
logger.log(error);
}
}
// Have been tested in:
// - Linux Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.218
// - Firefox 86
// - Microsoft Edge 91.0.825.0
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Windows Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.232
// - Firefox 86.0.1
// - Microsoft Edge 89.0.774.54
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - MacOS Desktop:
// - Chrome 89.0.4389.90
// - Opera 75.0.3969.93
// - Firefox 87.0
// - Microsoft Edge 89.0.774.57
// - Safari 14.0 (14610.1.28.1.9)
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Android:
// - Chrome Mobile 89.0.4389.90
// - Opera 62.3.3146.57763
// - Firefox Mobile 86.6.1
// - Microsoft Edge Mobile 46.02.4.5147
// - Ionic 5
// - React Native 0.64
// - iOS:
// - Safari Mobile
// - ¿Ionic?
// - ¿React Native?
public async getCommonStats(): Promise<IWebrtcStats> {
return new Promise(async (resolve, reject) => {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
const response = this.getWebRtcStatsResponseOutline();
const videoTrackStats = ['framesReceived', 'framesDropped', 'framesSent', 'frameHeight', 'frameWidth'];
statsReport.forEach((stat: any) => {
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
const addStat = (direction: string, key: string): void => {
if (stat[key] != null && response[direction] != null) {
if (!mediaType && (videoTrackStats.indexOf(key) > -1)) {
mediaType = 'video';
}
if (direction != null && mediaType != null && key != null && response[direction] != null && response[direction][mediaType] != null) {
response[direction][mediaType][key] = Number(stat[key]);
}
}
}
switch (stat.type) {
case "outbound-rtp":
addStat('outbound', 'bytesSent');
addStat('outbound', 'packetsSent');
addStat('outbound', 'framesEncoded');
addStat('outbound', 'nackCount');
addStat('outbound', 'firCount');
addStat('outbound', 'pliCount');
addStat('outbound', 'qpSum');
break;
case "inbound-rtp":
addStat('inbound', 'bytesReceived');
addStat('inbound', 'packetsReceived');
addStat('inbound', 'packetsLost');
addStat('inbound', 'jitter');
addStat('inbound', 'framesDecoded');
addStat('inbound', 'nackCount');
addStat('inbound', 'firCount');
addStat('inbound', 'pliCount');
break;
case 'track':
addStat('inbound', 'jitterBufferDelay');
addStat('inbound', 'framesReceived');
addStat('outbound', 'framesDropped');
addStat('outbound', 'framesSent');
addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameHeight');
addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameWidth');
break;
}
}); });
return resolve(response);
}); });
} }
private sendStatsToHttpEndpoint(instrumentation): void { private generateJSONStatsResponse(stats: IWebrtcStats): JSONStatsResponse {
return {
const sendPost = (json) => { '@timestamp': new Date().toISOString(),
const http: XMLHttpRequest = new XMLHttpRequest(); participant_id: this.stream.connection.data,
const url: string = instrumentation.webrtc.httpEndpoint; session_id: this.stream.session.sessionId,
http.open('POST', url, true); platform: platform.getName(),
platform_description: platform.getDescription(),
http.setRequestHeader('Content-type', 'application/json'); stream: 'webRTC',
webrtc_stats: stats
http.onreadystatechange = () => { // Call a function when the state changes.
if (http.readyState === 4 && http.status === 200) {
logger.log('WebRtc stats successfully sent to ' + url + ' for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
}
}; };
http.send(json);
};
const f = (stats) => {
if (platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) {
stats.forEach((stat) => {
let json = {};
if ((stat.type === 'inbound-rtp') &&
(
// Avoid firefox empty outbound-rtp statistics
stat.nackCount !== null &&
stat.isRemote === false &&
stat.id.startsWith('inbound') &&
stat.remoteId.startsWith('inbound')
)) {
const metricId = 'webrtc_inbound_' + stat.mediaType + '_' + stat.ssrc;
const jit = stat.jitter * 1000;
const metrics = {
bytesReceived: (stat.bytesReceived - this.stats.inbound[stat.mediaType].bytesReceived) / this.statsInterval,
jitter: jit,
packetsReceived: (stat.packetsReceived - this.stats.inbound[stat.mediaType].packetsReceived) / this.statsInterval,
packetsLost: (stat.packetsLost - this.stats.inbound[stat.mediaType].packetsLost) / this.statsInterval
};
const units = {
bytesReceived: 'bytes',
jitter: 'ms',
packetsReceived: 'packets',
packetsLost: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesDecoded'] = (stat.framesDecoded - this.stats.inbound.video.framesDecoded) / this.statsInterval;
metrics['nackCount'] = (stat.nackCount - this.stats.inbound.video.nackCount) / this.statsInterval;
units['framesDecoded'] = 'frames';
units['nackCount'] = 'packets';
this.stats.inbound.video.framesDecoded = stat.framesDecoded;
this.stats.inbound.video.nackCount = stat.nackCount;
} }
this.stats.inbound[stat.mediaType].bytesReceived = stat.bytesReceived; private getWebRtcStatsResponseOutline(): IWebrtcStats {
this.stats.inbound[stat.mediaType].packetsReceived = stat.packetsReceived; if (this.stream.isLocal()) {
this.stats.inbound[stat.mediaType].packetsLost = stat.packetsLost; return {
outbound: {
json = { audio: {},
'@timestamp': new Date(stat.timestamp).toISOString(), video: {}
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
} else if ((stat.type === 'outbound-rtp') &&
(
// Avoid firefox empty inbound-rtp statistics
stat.isRemote === false &&
stat.id.toLowerCase().includes('outbound')
)) {
const metricId = 'webrtc_outbound_' + stat.mediaType + '_' + stat.ssrc;
const metrics = {
bytesSent: (stat.bytesSent - this.stats.outbound[stat.mediaType].bytesSent) / this.statsInterval,
packetsSent: (stat.packetsSent - this.stats.outbound[stat.mediaType].packetsSent) / this.statsInterval
};
const units = {
bytesSent: 'bytes',
packetsSent: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesEncoded'] = (stat.framesEncoded - this.stats.outbound.video.framesEncoded) / this.statsInterval;
units['framesEncoded'] = 'frames';
this.stats.outbound.video.framesEncoded = stat.framesEncoded;
}
this.stats.outbound[stat.mediaType].bytesSent = stat.bytesSent;
this.stats.outbound[stat.mediaType].packetsSent = stat.packetsSent;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
}
});
} else if (platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isOperaMobileBrowser()) {
for (const key of Object.keys(stats)) {
const stat = stats[key];
if (stat.type === 'ssrc') {
let json = {};
if ('bytesReceived' in stat && (
(stat.mediaType === 'audio' && 'audioOutputLevel' in stat) ||
(stat.mediaType === 'video' && 'qpSum' in stat)
)) {
// inbound-rtp
const metricId = 'webrtc_inbound_' + stat.mediaType + '_' + stat.ssrc;
const metrics = {
bytesReceived: (stat.bytesReceived - this.stats.inbound[stat.mediaType].bytesReceived) / this.statsInterval,
jitter: stat.googJitterBufferMs,
packetsReceived: (stat.packetsReceived - this.stats.inbound[stat.mediaType].packetsReceived) / this.statsInterval,
packetsLost: (stat.packetsLost - this.stats.inbound[stat.mediaType].packetsLost) / this.statsInterval
};
const units = {
bytesReceived: 'bytes',
jitter: 'ms',
packetsReceived: 'packets',
packetsLost: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesDecoded'] = (stat.framesDecoded - this.stats.inbound.video.framesDecoded) / this.statsInterval;
metrics['nackCount'] = (stat.googNacksSent - this.stats.inbound.video.nackCount) / this.statsInterval;
units['framesDecoded'] = 'frames';
units['nackCount'] = 'packets';
this.stats.inbound.video.framesDecoded = stat.framesDecoded;
this.stats.inbound.video.nackCount = stat.googNacksSent;
}
this.stats.inbound[stat.mediaType].bytesReceived = stat.bytesReceived;
this.stats.inbound[stat.mediaType].packetsReceived = stat.packetsReceived;
this.stats.inbound[stat.mediaType].packetsLost = stat.packetsLost;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
} else if ('bytesSent' in stat) {
// outbound-rtp
const metricId = 'webrtc_outbound_' + stat.mediaType + '_' + stat.ssrc;
const metrics = {
bytesSent: (stat.bytesSent - this.stats.outbound[stat.mediaType].bytesSent) / this.statsInterval,
packetsSent: (stat.packetsSent - this.stats.outbound[stat.mediaType].packetsSent) / this.statsInterval
};
const units = {
bytesSent: 'bytes',
packetsSent: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesEncoded'] = (stat.framesEncoded - this.stats.outbound.video.framesEncoded) / this.statsInterval;
units['framesEncoded'] = 'frames';
this.stats.outbound.video.framesEncoded = stat.framesEncoded;
}
this.stats.outbound[stat.mediaType].bytesSent = stat.bytesSent;
this.stats.outbound[stat.mediaType].packetsSent = stat.packetsSent;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
}
}
}
} }
}; };
} else {
this.getStatsAgnostic(this.stream.getRTCPeerConnection(), f, (error) => { logger.log(error); }); return {
inbound: {
audio: {},
video: {}
} }
private standardizeReport(response) {
logger.log(response);
const standardReport = {};
if (platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) {
Object.keys(response).forEach(key => {
logger.log(response[key]);
});
return response;
}
response.result().forEach(report => {
const standardStats = {
id: report.id,
timestamp: report.timestamp,
type: report.type
}; };
report.names().forEach((name) => {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
}
private getStatsAgnostic(pc, successCb, failureCb) {
if (platform.isFirefoxBrowser() || platform.isFirefoxMobileBrowser()) {
// getStats takes args in different order in Chrome and Firefox
return pc.getStats(null).then(response => {
const report = this.standardizeReport(response);
successCb(report);
}).catch(failureCb);
} else if (platform.isChromeBrowser() || platform.isChromeMobileBrowser() || platform.isOperaBrowser() || platform.isOperaMobileBrowser()) {
// In Chrome, the first two arguments are reversed
return pc.getStats((response) => {
const report = this.standardizeReport(response);
successCb(report);
}, null, failureCb);
} }
} }

View File

@ -25,6 +25,7 @@ export { StreamPropertyChangedEvent } from './OpenViduInternal/Events/StreamProp
export { ConnectionPropertyChangedEvent } from './OpenViduInternal/Events/ConnectionPropertyChangedEvent'; export { ConnectionPropertyChangedEvent } from './OpenViduInternal/Events/ConnectionPropertyChangedEvent';
export { FilterEvent } from './OpenViduInternal/Events/FilterEvent'; export { FilterEvent } from './OpenViduInternal/Events/FilterEvent';
export { NetworkQualityLevelChangedEvent } from './OpenViduInternal/Events/NetworkQualityLevelChangedEvent'; export { NetworkQualityLevelChangedEvent } from './OpenViduInternal/Events/NetworkQualityLevelChangedEvent';
export { ExceptionEvent } from './OpenViduInternal/Events/ExceptionEvent';
export { Capabilities } from './OpenViduInternal/Interfaces/Public/Capabilities'; export { Capabilities } from './OpenViduInternal/Interfaces/Public/Capabilities';
export { Device } from './OpenViduInternal/Interfaces/Public/Device'; export { Device } from './OpenViduInternal/Interfaces/Public/Device';

View File

@ -1,7 +1,7 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/openvidu/) [![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/openvidu/)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu) [![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](https://openvidu.io) [![][OpenViduLogo]](https://openvidu.io)

View File

@ -36,7 +36,7 @@ public class OpenViduException extends JsonRpcErrorException {
ROOM_CANNOT_BE_CREATED_ERROR_CODE(204), ROOM_CLOSED_ERROR_CODE(203), ROOM_NOT_FOUND_ERROR_CODE(202), ROOM_CANNOT_BE_CREATED_ERROR_CODE(204), ROOM_CLOSED_ERROR_CODE(203), ROOM_NOT_FOUND_ERROR_CODE(202),
ROOM_GENERIC_ERROR_CODE(201), ROOM_GENERIC_ERROR_CODE(201),
USER_NOT_STREAMING_ERROR_CODE(105), EXISTING_USER_IN_ROOM_ERROR_CODE(104), USER_CLOSED_ERROR_CODE(103), USER_ALREADY_STREAMING_ERROR_CODE(106), USER_NOT_STREAMING_ERROR_CODE(105), EXISTING_USER_IN_ROOM_ERROR_CODE(104), USER_CLOSED_ERROR_CODE(103),
USER_NOT_FOUND_ERROR_CODE(102), USER_GENERIC_ERROR_CODE(10), USER_NOT_FOUND_ERROR_CODE(102), USER_GENERIC_ERROR_CODE(10),
USER_UNAUTHORIZED_ERROR_CODE(401), ROLE_NOT_FOUND_ERROR_CODE(402), SESSIONID_CANNOT_BE_CREATED_ERROR_CODE(403), USER_UNAUTHORIZED_ERROR_CODE(401), ROLE_NOT_FOUND_ERROR_CODE(402), SESSIONID_CANNOT_BE_CREATED_ERROR_CODE(403),

View File

@ -70,6 +70,10 @@ public class ProtocolElements {
public static final String UNPUBLISHVIDEO_METHOD = "unpublishVideo"; public static final String UNPUBLISHVIDEO_METHOD = "unpublishVideo";
public static final String PREPARERECEIVEVIDEO_METHOD = "prepareReceiveVideoFrom";
public static final String PREPARERECEIVEVIDEO_SDPOFFER_PARAM = "sdpOffer";
public static final String PREPARERECEIVEVIDEO_RECONNECT_PARAM = "reconnect";
public static final String RECEIVEVIDEO_METHOD = "receiveVideoFrom"; public static final String RECEIVEVIDEO_METHOD = "receiveVideoFrom";
public static final String RECEIVEVIDEO_SDPOFFER_PARAM = "sdpOffer"; public static final String RECEIVEVIDEO_SDPOFFER_PARAM = "sdpOffer";
public static final String RECEIVEVIDEO_SENDER_PARAM = "sender"; public static final String RECEIVEVIDEO_SENDER_PARAM = "sender";
@ -129,7 +133,10 @@ public class ProtocolElements {
public static final String RECONNECTSTREAM_METHOD = "reconnectStream"; public static final String RECONNECTSTREAM_METHOD = "reconnectStream";
public static final String RECONNECTSTREAM_STREAM_PARAM = "stream"; public static final String RECONNECTSTREAM_STREAM_PARAM = "stream";
public static final String RECONNECTSTREAM_SDPSTRING_PARAM = "sdpString";
// TODO: REMOVE ON 2.18.0
public static final String RECONNECTSTREAM_SDPOFFER_PARAM = "sdpOffer"; public static final String RECONNECTSTREAM_SDPOFFER_PARAM = "sdpOffer";
// ENDTODO
public static final String VIDEODATA_METHOD = "videoData"; public static final String VIDEODATA_METHOD = "videoData";
@ -137,6 +144,7 @@ public class ProtocolElements {
public static final String PARTICIPANTJOINED_METHOD = "participantJoined"; public static final String PARTICIPANTJOINED_METHOD = "participantJoined";
public static final String PARTICIPANTJOINED_USER_PARAM = "id"; public static final String PARTICIPANTJOINED_USER_PARAM = "id";
public static final String PARTICIPANTJOINED_FINALUSERID_PARAM = "finalUserId";
public static final String PARTICIPANTJOINED_CREATEDAT_PARAM = "createdAt"; public static final String PARTICIPANTJOINED_CREATEDAT_PARAM = "createdAt";
public static final String PARTICIPANTJOINED_METADATA_PARAM = "metadata"; public static final String PARTICIPANTJOINED_METADATA_PARAM = "metadata";
public static final String PARTICIPANTJOINED_VALUE_PARAM = "value"; public static final String PARTICIPANTJOINED_VALUE_PARAM = "value";

View File

@ -1,6 +1,6 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu) [![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](https://openvidu.io) [![][OpenViduLogo]](https://openvidu.io)

View File

@ -10,7 +10,7 @@
</parent> </parent>
<artifactId>openvidu-java-client</artifactId> <artifactId>openvidu-java-client</artifactId>
<version>2.16.0</version> <version>2.17.0</version>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>OpenVidu Java Client</name> <name>OpenVidu Java Client</name>
@ -138,6 +138,7 @@
<version>${version.javadoc.plugin}</version> <version>${version.javadoc.plugin}</version>
<configuration> <configuration>
<show>public</show> <show>public</show>
<javadocExecutable>${java.home}/bin/javadoc</javadocExecutable>
</configuration> </configuration>
<executions> <executions>
<execution> <execution>
@ -159,6 +160,12 @@
<goals> <goals>
<goal>sign</goal> <goal>sign</goal>
</goals> </goals>
<configuration>
<gpgArguments>
<arg>--pinentry-mode</arg>
<arg>loopback</arg>
</gpgArguments>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

View File

@ -18,7 +18,6 @@
package io.openvidu.java.client; package io.openvidu.java.client;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
@ -196,32 +195,10 @@ public class OpenVidu {
HttpPost request = new HttpPost(this.hostname + API_RECORDINGS_START); HttpPost request = new HttpPost(this.hostname + API_RECORDINGS_START);
JsonObject json = new JsonObject(); JsonObject json = properties.toJson();
json.addProperty("session", sessionId); json.addProperty("session", sessionId);
json.addProperty("name", properties.name());
json.addProperty("outputMode", properties.outputMode() != null ? properties.outputMode().name() : null);
json.addProperty("hasAudio", properties.hasAudio());
json.addProperty("hasVideo", properties.hasVideo());
json.addProperty("shmSize", properties.shmSize());
json.addProperty("mediaNode", properties.mediaNode());
if ((properties.outputMode() == null || Recording.OutputMode.COMPOSED.equals(properties.outputMode()) StringEntity params = new StringEntity(json.toString(), "UTF-8");
|| (Recording.OutputMode.COMPOSED_QUICK_START.equals(properties.outputMode())))
&& properties.hasVideo()) {
json.addProperty("resolution", properties.resolution());
json.addProperty("recordingLayout",
(properties.recordingLayout() != null) ? properties.recordingLayout().name() : "");
if (RecordingLayout.CUSTOM.equals(properties.recordingLayout())) {
json.addProperty("customLayout", (properties.customLayout() != null) ? properties.customLayout() : "");
}
}
StringEntity params = null;
try {
params = new StringEntity(json.toString());
} catch (UnsupportedEncodingException e1) {
throw new OpenViduJavaClientException(e1.getMessage(), e1.getCause());
}
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params); request.setEntity(params);
@ -258,12 +235,10 @@ public class OpenVidu {
* Starts the recording of a {@link io.openvidu.java.client.Session} * Starts the recording of a {@link io.openvidu.java.client.Session}
* *
* @param sessionId The sessionId of the session you want to start recording * @param sessionId The sessionId of the session you want to start recording
* @param name The name you want to give to the video file. You can access * @param name The name you want to give to the video file.
* this same value in your clients on recording events * <strong>WARNING: this parameter follows an overwriting
* (recordingStarted, recordingStopped). <strong>WARNING: this * policy.</strong> If you name two recordings the same, the
* parameter follows an overwriting policy.</strong> If you * newest MP4 file will overwrite the oldest one
* name two recordings the same, the newest MP4 file will
* overwrite the oldest one
* *
* @return The started recording. If this method successfully returns the * @return The started recording. If this method successfully returns the
* Recording object it means that the recording can be stopped with * Recording object it means that the recording can be stopped with
@ -613,7 +588,7 @@ public class OpenVidu {
private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException { private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException {
try { try {
JsonObject json = new Gson().fromJson(EntityUtils.toString(response.getEntity()), JsonObject.class); JsonObject json = new Gson().fromJson(EntityUtils.toString(response.getEntity(), "UTF-8"), JsonObject.class);
return json; return json;
} catch (JsonSyntaxException | ParseException | IOException e) { } catch (JsonSyntaxException | ParseException | IOException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); throw new OpenViduJavaClientException(e.getMessage(), e.getCause());

View File

@ -120,22 +120,7 @@ public class Recording {
} }
this.status = Recording.Status.valueOf(json.get("status").getAsString()); this.status = Recording.Status.valueOf(json.get("status").getAsString());
boolean hasAudio = json.get("hasAudio").getAsBoolean(); this.recordingProperties = RecordingProperties.fromJson(json);
boolean hasVideo = json.get("hasVideo").getAsBoolean();
OutputMode outputMode = OutputMode.valueOf(json.get("outputMode").getAsString());
RecordingProperties.Builder builder = new RecordingProperties.Builder().name(json.get("name").getAsString())
.outputMode(outputMode).hasAudio(hasAudio).hasVideo(hasVideo);
if ((OutputMode.COMPOSED.equals(outputMode) || OutputMode.COMPOSED_QUICK_START.equals(outputMode))
&& hasVideo) {
builder.resolution(json.get("resolution").getAsString());
builder.recordingLayout(RecordingLayout.valueOf(json.get("recordingLayout").getAsString()));
JsonElement customLayout = json.get("customLayout");
if (customLayout != null) {
builder.customLayout(customLayout.getAsString());
}
}
this.recordingProperties = builder.build();
} }
/** /**
@ -153,14 +138,28 @@ public class Recording {
} }
/** /**
* Name of the recording. The video file will be named after this property. You * Name of the recording. The video file will be named after this property
* can access this same value in your clients on recording events
* (<code>recordingStarted</code>, <code>recordingStopped</code>)
*/ */
public String getName() { public String getName() {
return this.recordingProperties.name(); return this.recordingProperties.name();
} }
/**
* <code>true</code> if the recording has an audio track, <code>false</code>
* otherwise (currently fixed to true)
*/
public boolean hasAudio() {
return this.recordingProperties.hasAudio();
}
/**
* <code>true</code> if the recording has a video track, <code>false</code>
* otherwise (currently fixed to true)
*/
public boolean hasVideo() {
return this.recordingProperties.hasVideo();
}
/** /**
* Mode of recording: COMPOSED for a single archive in a grid layout or * Mode of recording: COMPOSED for a single archive in a grid layout or
* INDIVIDUAL for one archive for each stream * INDIVIDUAL for one archive for each stream
@ -170,15 +169,22 @@ public class Recording {
} }
/** /**
* The layout used in this recording. Only defined if OutputMode is COMPOSED * The layout used in this recording. Only applicable if
* {@link io.openvidu.java.client.Recording.OutputMode} is
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} and
* {@link Recording#hasVideo()} is true
*/ */
public RecordingLayout getRecordingLayout() { public RecordingLayout getRecordingLayout() {
return this.recordingProperties.recordingLayout(); return this.recordingProperties.recordingLayout();
} }
/** /**
* The custom layout used in this recording. Only defined if if OutputMode is * The custom layout used in this recording. Only applicable if
* COMPOSED and * {@link io.openvidu.java.client.Recording.OutputMode} is
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START},
* {@link Recording#hasVideo()} is true and
* {@link io.openvidu.java.client.RecordingProperties.Builder#customLayout(String)} * {@link io.openvidu.java.client.RecordingProperties.Builder#customLayout(String)}
* has been called * has been called
*/ */
@ -186,6 +192,15 @@ public class Recording {
return this.recordingProperties.customLayout(); return this.recordingProperties.customLayout();
} }
/**
* Whether failed streams were ignored when the recording process started or
* not. Only applicable if {@link io.openvidu.java.client.Recording.OutputMode}
* is {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL}
*/
public boolean ignoreFailedStreams() {
return this.recordingProperties.ignoreFailedStreams();
}
/** /**
* Session associated to the recording * Session associated to the recording
*/ */
@ -227,28 +242,25 @@ public class Recording {
} }
/** /**
* Resolution of the video file. Only defined if OutputMode of the Recording is * Resolution of the video file. Only applicable if
* set to {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or * {@link io.openvidu.java.client.Recording.OutputMode} is
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} * {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} and
* {@link Recording#hasVideo()} is true
*/ */
public String getResolution() { public String getResolution() {
return this.recordingProperties.resolution(); return this.recordingProperties.resolution();
} }
/** /**
* <code>true</code> if the recording has an audio track, <code>false</code> * Frame rate of the video file. Only applicable if
* otherwise (currently fixed to true) * {@link io.openvidu.java.client.Recording.OutputMode} is
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} and
* {@link Recording#hasVideo()} is true
*/ */
public boolean hasAudio() { public Integer getFrameRate() {
return this.recordingProperties.hasAudio(); return this.recordingProperties.frameRate();
}
/**
* <code>true</code> if the recording has a video track, <code>false</code>
* otherwise (currently fixed to true)
*/
public boolean hasVideo() {
return this.recordingProperties.hasVideo();
} }
} }

View File

@ -19,9 +19,7 @@ package io.openvidu.java.client;
/** /**
* See * See
* {@link io.openvidu.java.client.SessionProperties.Builder#defaultRecordingLayout(RecordingLayout)} * {@link io.openvidu.java.client.RecordingProperties#recordingLayout(RecordingLayout)}
* and
* {@link io.openvidu.java.client.RecordingProperties.Builder#recordingLayout(RecordingLayout)}
*/ */
public enum RecordingLayout { public enum RecordingLayout {
@ -46,8 +44,8 @@ public enum RecordingLayout {
HORIZONTAL_PRESENTATION, HORIZONTAL_PRESENTATION,
/** /**
* Use your own custom recording layout. See * Use your own custom recording layout. See <a href=
* <a href="https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts" * "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts"
* target="_blank">Custom recording layouts</a> to learn more * target="_blank">Custom recording layouts</a> to learn more
*/ */
CUSTOM CUSTOM

View File

@ -17,20 +17,42 @@
package io.openvidu.java.client; package io.openvidu.java.client;
import com.google.gson.JsonObject;
import io.openvidu.java.client.Recording.OutputMode;
/** /**
* See * See
* {@link io.openvidu.java.client.OpenVidu#startRecording(String, RecordingProperties)} * {@link io.openvidu.java.client.OpenVidu#startRecording(String, RecordingProperties)}
*/ */
public class RecordingProperties { public class RecordingProperties {
private String name; public static class DefaultValues {
private Recording.OutputMode outputMode; public static final Boolean hasAudio = true;
public static final Boolean hasVideo = true;
public static final Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
public static final RecordingLayout recordingLayout = RecordingLayout.BEST_FIT;
public static final String resolution = "1280x720";
public static final Integer frameRate = 25;
public static final Long shmSize = 536870912L;
public static final Boolean ignoreFailedStreams = false;
}
// For all
private String name = "";
private Boolean hasAudio = true;
private Boolean hasVideo = true;
private Recording.OutputMode outputMode = Recording.OutputMode.COMPOSED;
// For COMPOSED/COMPOSED_QUICK_START + hasVideo
private RecordingLayout recordingLayout; private RecordingLayout recordingLayout;
private String customLayout;
private String resolution; private String resolution;
private boolean hasAudio; private Integer frameRate;
private boolean hasVideo; private Long shmSize;
private long shmSize; // For COMPOSED recording // For COMPOSED/COMPOSED_QUICK_START + hasVideo + RecordingLayout.CUSTOM
private String customLayout;
// For INDIVIDUAL
private Boolean ignoreFailedStreams;
// For OpenVidu Pro
private String mediaNode; private String mediaNode;
/** /**
@ -39,97 +61,51 @@ public class RecordingProperties {
public static class Builder { public static class Builder {
private String name = ""; private String name = "";
private Recording.OutputMode outputMode; private Boolean hasAudio = DefaultValues.hasAudio;
private Boolean hasVideo = DefaultValues.hasVideo;
private Recording.OutputMode outputMode = DefaultValues.outputMode;
private RecordingLayout recordingLayout; private RecordingLayout recordingLayout;
private String customLayout;
private String resolution; private String resolution;
private boolean hasAudio = true; private Integer frameRate;
private boolean hasVideo = true; private Long shmSize;
private long shmSize = 536870912L; private String customLayout;
private Boolean ignoreFailedStreams = DefaultValues.ignoreFailedStreams;
private String mediaNode; private String mediaNode;
public Builder() {
}
public Builder(RecordingProperties props) {
this.name = props.name();
this.hasAudio = props.hasAudio();
this.hasVideo = props.hasVideo();
this.outputMode = props.outputMode();
this.recordingLayout = props.recordingLayout();
this.resolution = props.resolution();
this.frameRate = props.frameRate();
this.shmSize = props.shmSize();
this.customLayout = props.customLayout();
this.ignoreFailedStreams = props.ignoreFailedStreams();
this.mediaNode = props.mediaNode();
}
/** /**
* Builder for {@link io.openvidu.java.client.RecordingProperties} * Builder for {@link io.openvidu.java.client.RecordingProperties}
*/ */
public RecordingProperties build() { public RecordingProperties build() {
return new RecordingProperties(this.name, this.outputMode, this.recordingLayout, this.customLayout, return new RecordingProperties(this.name, this.hasAudio, this.hasVideo, this.outputMode,
this.resolution, this.hasAudio, this.hasVideo, this.shmSize, this.mediaNode); this.recordingLayout, this.resolution, this.frameRate, this.shmSize, this.customLayout,
this.ignoreFailedStreams, this.mediaNode);
} }
/** /**
* Call this method to set the name of the video file. You can access this same * Call this method to set the name of the video file
* value in your clients on recording events (<code>recordingStarted</code>,
* <code>recordingStopped</code>)
*/ */
public RecordingProperties.Builder name(String name) { public RecordingProperties.Builder name(String name) {
this.name = name; this.name = name;
return this; return this;
} }
/**
* Call this method to set the mode of recording: COMPOSED for a single archive
* in a grid layout or INDIVIDUAL for one archive for each stream
*/
public RecordingProperties.Builder outputMode(Recording.OutputMode outputMode) {
this.outputMode = outputMode;
return this;
}
/**
* Call this method to set the layout to be used in the recording. Will only
* have effect if
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode)}
* has been called with value
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
*/
public RecordingProperties.Builder recordingLayout(RecordingLayout layout) {
this.recordingLayout = layout;
return this;
}
/**
* If setting
* {@link io.openvidu.java.client.RecordingProperties.Builder#recordingLayout(RecordingLayout)}
* to {@link io.openvidu.java.client.RecordingLayout#CUSTOM} you can call this
* method to set the relative path to the specific custom layout you want to
* use.<br>
* Will only have effect if
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode)
* Builder.outputMode()} has been called with value
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED
* OutputMode.COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START
* OutputMode.COMPOSED_QUICK_START}.<br>
* See <a href=
* "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts"
* target="_blank">Custom recording layouts</a> to learn more
*/
public RecordingProperties.Builder customLayout(String path) {
this.customLayout = path;
return this;
}
/**
* Call this method to specify the recording resolution. Must be a string with
* format "WIDTHxHEIGHT", being both WIDTH and HEIGHT the number of pixels
* between 100 and 1999.<br>
* Will only have effect if
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode)
* Builder.outputMode()} has been called with value
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED
* OutputMode.COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START
* OutputMode.COMPOSED_QUICK_START}. For
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL
* OutputMode.INDIVIDUAL} all individual video files will have the native
* resolution of the published stream
*/
public RecordingProperties.Builder resolution(String resolution) {
this.resolution = resolution;
return this;
}
/** /**
* Call this method to specify whether to record audio or not. Cannot be set to * Call this method to specify whether to record audio or not. Cannot be set to
* false at the same time as * false at the same time as
@ -151,15 +127,112 @@ public class RecordingProperties {
} }
/** /**
* If COMPOSED recording, call this method to specify the amount of shared * Call this method to set the mode of recording:
* memory reserved for the recording process in bytes. Minimum 134217728 (128 * {@link Recording.OutputMode#COMPOSED} or
* MB). Property ignored if INDIVIDUAL recording * {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} for
* a single archive in a grid layout or {@link Recording.OutputMode#INDIVIDUAL}
* for one archive for each stream.
*/
public RecordingProperties.Builder outputMode(Recording.OutputMode outputMode) {
this.outputMode = outputMode;
return this;
}
/**
* Call this method to set the layout to be used in the recording. Will only
* have effect for {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED}
* or {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true.
*/
public RecordingProperties.Builder recordingLayout(RecordingLayout layout) {
this.recordingLayout = layout;
return this;
}
/**
* Call this method to specify the recording resolution. Must be a string with
* format "WIDTHxHEIGHT", being both WIDTH and HEIGHT the number of pixels
* between 100 and 1999.<br>
* Will only have effect for
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true. Property
* ignored for INDIVIDUAL recordings and audio-only recordings. For
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} all
* individual video files will have the native resolution of the published
* stream.
*/
public RecordingProperties.Builder resolution(String resolution) {
this.resolution = resolution;
return this;
}
/**
* Call this method to specify the recording frame rate. Will only have effect
* for {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true. Property
* ignored for INDIVIDUAL recordings and audio-only recordings. For
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} all
* individual video files will have the native frame rate of the published
* stream.
*/
public RecordingProperties.Builder frameRate(int frameRate) {
this.frameRate = frameRate;
return this;
}
/**
* Call this method to specify the amount of shared memory reserved for the
* recording process in bytes. Minimum 134217728 (128MB).<br>
* Will only have effect for
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true. Property
* ignored for INDIVIDUAL recordings and audio-only recordings
*/ */
public RecordingProperties.Builder shmSize(long shmSize) { public RecordingProperties.Builder shmSize(long shmSize) {
this.shmSize = shmSize; this.shmSize = shmSize;
return this; return this;
} }
/**
* If setting
* {@link io.openvidu.java.client.RecordingProperties.Builder#recordingLayout(RecordingLayout)}
* to {@link io.openvidu.java.client.RecordingLayout#CUSTOM} you can call this
* method to set the relative path to the specific custom layout you want to
* use.<br>
* See <a href=
* "https://docs.openvidu.io/en/stable/advanced-features/recording#custom-recording-layouts"
* target="_blank">Custom recording layouts</a> to learn more
*/
public RecordingProperties.Builder customLayout(String path) {
this.customLayout = path;
return this;
}
/**
* Call this method to specify whether to ignore failed streams or not when
* starting the recording. This property only applies to
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} recordings.
* For this type of recordings, when calling
* {@link io.openvidu.java.client.OpenVidu#startRecording(String, RecordingProperties)}
* by default all the streams available at the moment the recording process
* starts must be healthy and properly sending media. If some stream that should
* be sending media is broken, then the recording process fails after a 10s
* timeout. In this way your application is notified that some stream is not
* being recorded, so it can retry the process again. But you can disable this
* rollback behavior and simply ignore any failed stream, which will be
* susceptible to be recorded in the future if media starts flowing as expected
* at any point. The downside of this behavior is that you will have no
* guarantee that all streams present at the beginning of a recording are
* actually being recorded.
*/
public RecordingProperties.Builder ignoreFailedStreams(boolean ignoreFailedStreams) {
this.ignoreFailedStreams = ignoreFailedStreams;
return this;
}
/** /**
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank" * <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank"
* style="display: inline-block; background-color: rgb(0, 136, 170); color: * style="display: inline-block; background-color: rgb(0, 136, 170); color:
@ -167,8 +240,12 @@ public class RecordingProperties {
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat, * 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
* sans-serif">PRO</a> Call this method to force the recording to be hosted in * sans-serif">PRO</a> Call this method to force the recording to be hosted in
* the Media Node with identifier <code>mediaNodeId</code>. This property only * the Media Node with identifier <code>mediaNodeId</code>. This property only
* applies to COMPOSED recordings and is ignored for INDIVIDUAL recordings, that * applies to {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* are always hosted in the same Media Node hosting its Session * {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true and is ignored
* for {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL}
* recordings and audio-only recordings, that are always hosted in the same
* Media Node hosting its Session
*/ */
public RecordingProperties.Builder mediaNode(String mediaNodeId) { public RecordingProperties.Builder mediaNode(String mediaNodeId) {
this.mediaNode = mediaNodeId; this.mediaNode = mediaNodeId;
@ -177,29 +254,58 @@ public class RecordingProperties {
} }
protected RecordingProperties(String name, Recording.OutputMode outputMode, RecordingLayout layout, protected RecordingProperties(String name, Boolean hasAudio, Boolean hasVideo, Recording.OutputMode outputMode,
String customLayout, String resolution, boolean hasAudio, boolean hasVideo, long shmSize, RecordingLayout layout, String resolution, Integer frameRate, Long shmSize, String customLayout,
String mediaNode) { Boolean ignoreFailedStreams, String mediaNode) {
this.name = name; this.name = name != null ? name : "";
this.outputMode = outputMode; this.hasAudio = hasAudio != null ? hasAudio : DefaultValues.hasAudio;
this.recordingLayout = layout; this.hasVideo = hasVideo != null ? hasVideo : DefaultValues.hasVideo;
this.outputMode = outputMode != null ? outputMode : DefaultValues.outputMode;
if ((OutputMode.COMPOSED.equals(this.outputMode) || OutputMode.COMPOSED_QUICK_START.equals(this.outputMode))
&& this.hasVideo) {
this.recordingLayout = layout != null ? layout : DefaultValues.recordingLayout;
this.resolution = resolution != null ? resolution : DefaultValues.resolution;
this.frameRate = frameRate != null ? frameRate : DefaultValues.frameRate;
this.shmSize = shmSize != null ? shmSize : DefaultValues.shmSize;
if (RecordingLayout.CUSTOM.equals(this.recordingLayout)) {
this.customLayout = customLayout; this.customLayout = customLayout;
this.resolution = resolution; }
this.hasAudio = hasAudio; }
this.hasVideo = hasVideo; if (OutputMode.INDIVIDUAL.equals(this.outputMode)) {
this.shmSize = shmSize; this.ignoreFailedStreams = ignoreFailedStreams;
}
this.mediaNode = mediaNode; this.mediaNode = mediaNode;
} }
/** /**
* Defines the name you want to give to the video file. You can access this same * Defines the name you want to give to the video file
* value in your clients on recording events (<code>recordingStarted</code>,
* <code>recordingStopped</code>)
*/ */
public String name() { public String name() {
return this.name; return this.name;
} }
/**
* Defines whether to record audio or not. Cannot be set to false at the same
* time as {@link RecordingProperties#hasVideo()}.<br>
* <br>
*
* Default to true
*/
public Boolean hasAudio() {
return this.hasAudio;
}
/**
* Defines whether to record video or not. Cannot be set to false at the same
* time as {@link RecordingProperties#hasAudio()}.<br>
* <br>
*
* Default to true
*/
public Boolean hasVideo() {
return this.hasVideo;
}
/** /**
* Defines the mode of recording: {@link Recording.OutputMode#COMPOSED} or * Defines the mode of recording: {@link Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} for * {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START} for
@ -215,12 +321,11 @@ public class RecordingProperties {
/** /**
* Defines the layout to be used in the recording.<br> * Defines the layout to be used in the recording.<br>
* Will only have effect if * Will only have effect for
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode) * {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* Builder.outputMode()} has been called with value * {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* {@link Recording.OutputMode#COMPOSED OutputMode.COMPOSED} or * recordings with {@link RecordingProperties#hasVideo()} to true. Property
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START * ignored for INDIVIDUAL recordings and audio-only recordings<br>
* OutputMode.COMPOSED_QUICK_START}.<br>
* <br> * <br>
* *
* Default to {@link RecordingLayout#BEST_FIT RecordingLayout.BEST_FIT} * Default to {@link RecordingLayout#BEST_FIT RecordingLayout.BEST_FIT}
@ -229,6 +334,58 @@ public class RecordingProperties {
return this.recordingLayout; return this.recordingLayout;
} }
/**
* Defines the resolution of the recorded video.<br>
* Will only have effect for
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true. Property
* ignored for INDIVIDUAL recordings and audio-only recordings. For
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} all
* individual video files will have the native resolution of the published
* stream.<br>
* <br>
*
* Default to "1280x720"
*/
public String resolution() {
return this.resolution;
}
/**
* Defines the frame rate of the recorded video.<br>
* Will only have effect for
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true. Property
* ignored for INDIVIDUAL recordings and audio-only recordings. For
* {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} all
* individual video files will have the native frame rate of the published
* stream.<br>
* <br>
*
* Default to 25
*/
public Integer frameRate() {
return this.frameRate;
}
/**
* The amount of shared memory reserved for the recording process in bytes.
* Minimum 134217728 (128MB).<br>
* Will only have effect for
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
* recordings with {@link RecordingProperties#hasVideo()} to true. Property
* ignored for INDIVIDUAL recordings and audio-only recordings<br>
* <br>
*
* Default to 536870912 (512 MB)
*/
public Long shmSize() {
return this.shmSize;
}
/** /**
* If {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} is * If {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} is
* set to {@link io.openvidu.java.client.RecordingLayout#CUSTOM}, this property * set to {@link io.openvidu.java.client.RecordingLayout#CUSTOM}, this property
@ -242,55 +399,27 @@ public class RecordingProperties {
} }
/** /**
* Defines the resolution of the recorded video.<br> * Defines whether to ignore failed streams or not when starting the recording.
* Will only have effect if * This property only applies to
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode)} * {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} recordings.
* has been called with value * For this type of recordings, when calling
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or * {@link io.openvidu.java.client.OpenVidu#startRecording(String, RecordingProperties)}
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}. * by default all the streams available at the moment the recording process
* For {@link io.openvidu.java.client.Recording.OutputMode#INDIVIDUAL} all * starts must be healthy and properly sending media. If some stream that should
* individual video files will have the native resolution of the published * be sending media is broken, then the recording process fails after a 10s
* stream.<br> * timeout. In this way your application is notified that some stream is not
* being recorded, so it can retry the process again. But you can disable this
* rollback behavior and simply ignore any failed stream, which will be
* susceptible to be recorded in the future if media starts flowing as expected
* at any point. The downside of this behavior is that you will have no
* guarantee that all streams present at the beginning of a recording are
* actually being recorded.<br>
* <br> * <br>
* *
* Default to "1920x1080" * Default to false
*/ */
public String resolution() { public Boolean ignoreFailedStreams() {
return this.resolution; return this.ignoreFailedStreams;
}
/**
* Defines whether to record audio or not. Cannot be set to false at the same
* time as {@link RecordingProperties#hasVideo()}.<br>
* <br>
*
* Default to true
*/
public boolean hasAudio() {
return this.hasAudio;
}
/**
* Defines whether to record video or not. Cannot be set to false at the same
* time as {@link RecordingProperties#hasAudio()}.<br>
* <br>
*
* Default to true
*/
public boolean hasVideo() {
return this.hasVideo;
}
/**
* If COMPOSED recording, the amount of shared memory reserved for the recording
* process in bytes. Minimum 134217728 (128MB). Property ignored if INDIVIDUAL
* recording<br>
* <br>
*
* Default to 536870912 (512 MB)
*/
public long shmSize() {
return this.shmSize;
} }
/** /**
@ -300,11 +429,105 @@ public class RecordingProperties {
* 3px; font-size: 13px; line-height:21px; font-family: Montserrat, * 3px; font-size: 13px; line-height:21px; font-family: Montserrat,
* sans-serif">PRO</a> The Media Node where to host the recording. The default * sans-serif">PRO</a> The Media Node where to host the recording. The default
* option if this property is not defined is the same Media Node hosting the * option if this property is not defined is the same Media Node hosting the
* Session to record. This property only applies to COMPOSED recordings and is * Session to record. This property only applies to COMPOSED or
* ignored for INDIVIDUAL recordings * COMPOSED_QUICK_START recordings with {@link RecordingProperties#hasVideo()}
* to true and is ignored for INDIVIDUAL recordings and audio-only recordings
*/ */
public String mediaNode() { public String mediaNode() {
return this.mediaNode; return this.mediaNode;
} }
/**
* @hidden
*/
public JsonObject toJson() {
JsonObject json = new JsonObject();
json.addProperty("name", name);
json.addProperty("hasAudio", hasAudio != null ? hasAudio : DefaultValues.hasAudio);
json.addProperty("hasVideo", hasVideo != null ? hasVideo : DefaultValues.hasVideo);
json.addProperty("outputMode", outputMode != null ? outputMode.name() : DefaultValues.outputMode.name());
if ((OutputMode.COMPOSED.equals(outputMode) || OutputMode.COMPOSED_QUICK_START.equals(outputMode))
&& hasVideo) {
json.addProperty("recordingLayout",
recordingLayout != null ? recordingLayout.name() : DefaultValues.recordingLayout.name());
json.addProperty("resolution", resolution != null ? resolution : DefaultValues.resolution);
json.addProperty("frameRate", frameRate != null ? frameRate : DefaultValues.frameRate);
json.addProperty("shmSize", shmSize != null ? shmSize : DefaultValues.shmSize);
if (RecordingLayout.CUSTOM.equals(recordingLayout)) {
json.addProperty("customLayout", customLayout != null ? customLayout : "");
}
}
if (OutputMode.INDIVIDUAL.equals(outputMode)) {
json.addProperty("ignoreFailedStreams",
ignoreFailedStreams != null ? ignoreFailedStreams : DefaultValues.ignoreFailedStreams);
}
if (this.mediaNode != null) {
json.addProperty("mediaNode", mediaNode);
}
return json;
}
/**
* @hidden
*/
public static RecordingProperties fromJson(JsonObject json) {
Boolean hasVideoAux = true;
Recording.OutputMode outputModeAux = null;
RecordingLayout recordingLayoutAux = null;
Builder builder = new RecordingProperties.Builder();
if (json.has("name")) {
builder.name(json.get("name").getAsString());
}
if (json.has("hasAudio")) {
builder.hasAudio(json.get("hasAudio").getAsBoolean());
}
if (json.has("hasVideo")) {
hasVideoAux = json.get("hasVideo").getAsBoolean();
builder.hasVideo(hasVideoAux);
}
if (json.has("outputMode")) {
outputModeAux = OutputMode.valueOf(json.get("outputMode").getAsString());
builder.outputMode(outputModeAux);
}
if ((OutputMode.COMPOSED.equals(outputModeAux) || OutputMode.COMPOSED_QUICK_START.equals(outputModeAux))
&& hasVideoAux) {
if (json.has("recordingLayout")) {
recordingLayoutAux = RecordingLayout.valueOf(json.get("recordingLayout").getAsString());
builder.recordingLayout(recordingLayoutAux);
}
if (json.has("resolution")) {
builder.resolution(json.get("resolution").getAsString());
}
if (json.has("frameRate")) {
builder.frameRate(json.get("frameRate").getAsInt());
}
if (json.has("shmSize")) {
builder.shmSize(json.get("shmSize").getAsLong());
}
if (RecordingLayout.CUSTOM.equals(recordingLayoutAux)) {
if (json.has("customLayout")) {
builder.customLayout(json.get("customLayout").getAsString());
}
}
}
if (json.has("ignoreFailedStreams") && OutputMode.INDIVIDUAL.equals(outputModeAux)) {
builder.ignoreFailedStreams(json.get("ignoreFailedStreams").getAsBoolean());
}
if (json.has("mediaNode")) {
String mediaNodeId = null;
if (json.get("mediaNode").isJsonObject()) {
mediaNodeId = json.get("mediaNode").getAsJsonObject().get("id").getAsString();
} else if (json.get("mediaNode").isJsonPrimitive()) {
mediaNodeId = json.get("mediaNode").getAsString();
}
if (mediaNodeId != null && !mediaNodeId.isEmpty()) {
builder.mediaNode(mediaNodeId);
}
}
return builder.build();
}
} }

View File

@ -18,7 +18,6 @@
package io.openvidu.java.client; package io.openvidu.java.client;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -121,13 +120,7 @@ public class Session {
} }
HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_TOKENS); HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_TOKENS);
StringEntity params = new StringEntity(tokenOptions.toJsonObject(sessionId).toString(), "UTF-8");
StringEntity params;
try {
params = new StringEntity(tokenOptions.toJsonObject(sessionId).toString());
} catch (UnsupportedEncodingException e1) {
throw new OpenViduJavaClientException(e1.getMessage(), e1.getCause());
}
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params); request.setEntity(params);
@ -190,13 +183,7 @@ public class Session {
HttpPost request = new HttpPost( HttpPost request = new HttpPost(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection"); this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection");
StringEntity params = new StringEntity(connectionProperties.toJson(sessionId).toString(), "UTF-8");
StringEntity params;
try {
params = new StringEntity(connectionProperties.toJson(sessionId).toString());
} catch (UnsupportedEncodingException e1) {
throw new OpenViduJavaClientException(e1.getMessage(), e1.getCause());
}
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params); request.setEntity(params);
@ -506,13 +493,8 @@ public class Session {
HttpPatch request = new HttpPatch( HttpPatch request = new HttpPatch(
this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + connectionId); this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + connectionId);
StringEntity params = new StringEntity(connectionProperties.toJson(this.sessionId).toString(), "UTF-8");
StringEntity params;
try {
params = new StringEntity(connectionProperties.toJson(this.sessionId).toString());
} catch (UnsupportedEncodingException e1) {
throw new OpenViduJavaClientException(e1.getMessage(), e1.getCause());
}
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params); request.setEntity(params);
@ -661,12 +643,7 @@ public class Session {
} }
HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_SESSIONS); HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_SESSIONS);
StringEntity params = null; StringEntity params = new StringEntity(properties.toJson().toString(), "UTF-8");
try {
params = new StringEntity(properties.toJson().toString());
} catch (UnsupportedEncodingException e1) {
throw new OpenViduJavaClientException(e1.getMessage(), e1.getCause());
}
request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json");
request.setEntity(params); request.setEntity(params);
@ -684,22 +661,17 @@ public class Session {
this.sessionId = responseJson.get("id").getAsString(); this.sessionId = responseJson.get("id").getAsString();
this.createdAt = responseJson.get("createdAt").getAsLong(); this.createdAt = responseJson.get("createdAt").getAsLong();
// forcedVideoCodec and allowTranscoding values are configured in OpenVidu Server // forcedVideoCodec and allowTranscoding values are configured in OpenVidu
// via configuration or session // Server via configuration or session
VideoCodec forcedVideoCodec = VideoCodec.valueOf(responseJson.get("forcedVideoCodec").getAsString()); VideoCodec forcedVideoCodec = VideoCodec.valueOf(responseJson.get("forcedVideoCodec").getAsString());
Boolean allowTranscoding = responseJson.get("allowTranscoding").getAsBoolean(); Boolean allowTranscoding = responseJson.get("allowTranscoding").getAsBoolean();
SessionProperties responseProperties = new SessionProperties.Builder() SessionProperties responseProperties = new SessionProperties.Builder()
.customSessionId(properties.customSessionId()) .customSessionId(properties.customSessionId()).mediaMode(properties.mediaMode())
.mediaMode(properties.mediaMode())
.recordingMode(properties.recordingMode()) .recordingMode(properties.recordingMode())
.defaultOutputMode(properties.defaultOutputMode()) .defaultRecordingProperties(properties.defaultRecordingProperties())
.defaultRecordingLayout(properties.defaultRecordingLayout()) .mediaNode(properties.mediaNode()).forcedVideoCodec(forcedVideoCodec)
.defaultCustomLayout(properties.defaultCustomLayout()) .allowTranscoding(allowTranscoding).build();
.mediaNode(properties.mediaNode())
.forcedVideoCodec(forcedVideoCodec)
.allowTranscoding(allowTranscoding)
.build();
this.properties = responseProperties; this.properties = responseProperties;
log.info("Session '{}' created", this.sessionId); log.info("Session '{}' created", this.sessionId);
@ -717,7 +689,7 @@ public class Session {
private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException { private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException {
JsonObject json; JsonObject json;
try { try {
json = new Gson().fromJson(EntityUtils.toString(response.getEntity()), JsonObject.class); json = new Gson().fromJson(EntityUtils.toString(response.getEntity(), "UTF-8"), JsonObject.class);
} catch (JsonSyntaxException | IOException e) { } catch (JsonSyntaxException | IOException e) {
throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); throw new OpenViduJavaClientException(e.getMessage(), e.getCause());
} }
@ -734,13 +706,10 @@ public class Session {
this.recording = json.get("recording").getAsBoolean(); this.recording = json.get("recording").getAsBoolean();
SessionProperties.Builder builder = new SessionProperties.Builder() SessionProperties.Builder builder = new SessionProperties.Builder()
.mediaMode(MediaMode.valueOf(json.get("mediaMode").getAsString())) .mediaMode(MediaMode.valueOf(json.get("mediaMode").getAsString()))
.recordingMode(RecordingMode.valueOf(json.get("recordingMode").getAsString())) .recordingMode(RecordingMode.valueOf(json.get("recordingMode").getAsString()));
.defaultOutputMode(Recording.OutputMode.valueOf(json.get("defaultOutputMode").getAsString())); if (json.has("defaultRecordingProperties")) {
if (json.has("defaultRecordingLayout")) { builder.defaultRecordingProperties(
builder.defaultRecordingLayout(RecordingLayout.valueOf(json.get("defaultRecordingLayout").getAsString())); RecordingProperties.fromJson(json.get("defaultRecordingProperties").getAsJsonObject()));
}
if (json.has("defaultCustomLayout")) {
builder.defaultCustomLayout(json.get("defaultCustomLayout").getAsString());
} }
if (json.has("customSessionId")) { if (json.has("customSessionId")) {
builder.customSessionId(json.get("customSessionId").getAsString()); builder.customSessionId(json.get("customSessionId").getAsString());
@ -791,10 +760,10 @@ public class Session {
json.addProperty("recording", this.recording); json.addProperty("recording", this.recording);
json.addProperty("mediaMode", this.properties.mediaMode().name()); json.addProperty("mediaMode", this.properties.mediaMode().name());
json.addProperty("recordingMode", this.properties.recordingMode().name()); json.addProperty("recordingMode", this.properties.recordingMode().name());
json.addProperty("defaultOutputMode", this.properties.defaultOutputMode().name()); if (this.properties.defaultRecordingProperties() != null) {
json.addProperty("defaultRecordingLayout", this.properties.defaultRecordingLayout().name()); json.add("defaultRecordingProperties", this.properties.defaultRecordingProperties().toJson());
json.addProperty("defaultCustomLayout", this.properties.defaultCustomLayout()); }
if(this.properties.forcedVideoCodec() != null) { if (this.properties.forcedVideoCodec() != null) {
json.addProperty("forcedVideoCodec", this.properties.forcedVideoCodec().name()); json.addProperty("forcedVideoCodec", this.properties.forcedVideoCodec().name());
} }
if (this.properties.isTranscodingAllowed() != null) { if (this.properties.isTranscodingAllowed() != null) {

View File

@ -19,8 +19,6 @@ package io.openvidu.java.client;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.openvidu.java.client.Recording.OutputMode;
/** /**
* See {@link io.openvidu.java.client.OpenVidu#createSession(SessionProperties)} * See {@link io.openvidu.java.client.OpenVidu#createSession(SessionProperties)}
*/ */
@ -28,9 +26,7 @@ public class SessionProperties {
private MediaMode mediaMode; private MediaMode mediaMode;
private RecordingMode recordingMode; private RecordingMode recordingMode;
private OutputMode defaultOutputMode; private RecordingProperties defaultRecordingProperties;
private RecordingLayout defaultRecordingLayout;
private String defaultCustomLayout;
private String customSessionId; private String customSessionId;
private String mediaNode; private String mediaNode;
private VideoCodec forcedVideoCodec; private VideoCodec forcedVideoCodec;
@ -43,30 +39,26 @@ public class SessionProperties {
private MediaMode mediaMode = MediaMode.ROUTED; private MediaMode mediaMode = MediaMode.ROUTED;
private RecordingMode recordingMode = RecordingMode.MANUAL; private RecordingMode recordingMode = RecordingMode.MANUAL;
private OutputMode defaultOutputMode = OutputMode.COMPOSED; private RecordingProperties defaultRecordingProperties = new RecordingProperties.Builder().build();
private RecordingLayout defaultRecordingLayout = RecordingLayout.BEST_FIT;
private String defaultCustomLayout = "";
private String customSessionId = ""; private String customSessionId = "";
private String mediaNode; private String mediaNode;
private VideoCodec forcedVideoCodec; private VideoCodec forcedVideoCodec = VideoCodec.VP8;
private Boolean allowTranscoding; private Boolean allowTranscoding = false;
/** /**
* Returns the {@link io.openvidu.java.client.SessionProperties} object properly * Returns the {@link io.openvidu.java.client.SessionProperties} object properly
* configured * configured
*/ */
public SessionProperties build() { public SessionProperties build() {
return new SessionProperties(this.mediaMode, this.recordingMode, this.defaultOutputMode, return new SessionProperties(this.mediaMode, this.recordingMode, this.defaultRecordingProperties,
this.defaultRecordingLayout, this.defaultCustomLayout, this.customSessionId, this.mediaNode, this.customSessionId, this.mediaNode, this.forcedVideoCodec, this.allowTranscoding);
this.forcedVideoCodec, this.allowTranscoding);
} }
/** /**
* Call this method to set how the media streams will be sent and received by * Call this method to set how the media streams will be sent and received by
* your clients: routed through OpenVidu Media Node * your clients: routed through OpenVidu Media Node
* (<code>MediaMode.ROUTED</code>) or attempting direct p2p connections * (<code>MediaMode.ROUTED</code>) or attempting direct p2p connections
* (<code>MediaMode.RELAYED</code>, <i>not available yet</i>) * (<code>MediaMode.RELAYED</code>, <i>not available yet</i>)<br>
*
* Default value is <code>MediaMode.ROUTED</code> * Default value is <code>MediaMode.ROUTED</code>
*/ */
public SessionProperties.Builder mediaMode(MediaMode mediaMode) { public SessionProperties.Builder mediaMode(MediaMode mediaMode) {
@ -85,53 +77,14 @@ public class SessionProperties {
} }
/** /**
* Call this method to set the the default value used to initialize property * Call this method to set the default recording properties of this session. You
* {@link io.openvidu.java.client.RecordingProperties#outputMode()} of every * can easily override this value later when starting a
* recording of this session. You can easily override this value later when * {@link io.openvidu.java.client.Recording} by providing new
* starting a {@link io.openvidu.java.client.Recording} by calling * {@link RecordingProperties}<br>
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode)} * Default values defined in {@link RecordingProperties} class.
* with any other value.<br>
* Default value is {@link Recording.OutputMode#COMPOSED}
*/ */
public SessionProperties.Builder defaultOutputMode(OutputMode outputMode) { public SessionProperties.Builder defaultRecordingProperties(RecordingProperties defaultRecordingProperties) {
this.defaultOutputMode = outputMode; this.defaultRecordingProperties = defaultRecordingProperties;
return this;
}
/**
* Call this method to set the the default value used to initialize property
* {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} of
* every recording of this session. You can easily override this value later
* when starting a {@link io.openvidu.java.client.Recording} by calling
* {@link io.openvidu.java.client.RecordingProperties.Builder#recordingLayout(RecordingLayout)}
* with any other value.<br>
* Default value is {@link RecordingLayout#BEST_FIT}<br>
* <br>
* Recording layouts are only applicable to recordings with OutputMode
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
*/
public SessionProperties.Builder defaultRecordingLayout(RecordingLayout layout) {
this.defaultRecordingLayout = layout;
return this;
}
/**
* Call this method to set the default value used to initialize property
* {@link io.openvidu.java.client.RecordingProperties#customLayout()} of every
* recording of this session. You can easily override this value later when
* starting a {@link io.openvidu.java.client.Recording} by calling
* {@link io.openvidu.java.client.RecordingProperties.Builder#customLayout(String)}
* with any other value.<br>
* <br>
*
* Custom layouts are only applicable to recordings with OutputMode
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} (or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START})
* and RecordingLayout {@link io.openvidu.java.client.RecordingLayout#CUSTOM}
*/
public SessionProperties.Builder defaultCustomLayout(String path) {
this.defaultCustomLayout = path;
return this; return this;
} }
@ -161,12 +114,13 @@ public class SessionProperties {
} }
/** /**
* Call this method to define which video codec do you want to be forcibly used for this session. * Call this method to define which video codec do you want to be forcibly used
* This allows browsers/clients to use the same codec avoiding transcoding in the media server. * for this session. This allows browsers/clients to use the same codec avoiding
* If the browser/client is not compatible with the specified codec and {@link #allowTranscoding(Boolean)} * transcoding in the media server. If the browser/client is not compatible with
* is <code>false</code> and exception will occur. * the specified codec and {@link #allowTranscoding(Boolean)} is
* * <code>false</code> and exception will occur. If forcedVideoCodec is set to
* If forcedVideoCodec is set to NONE, no codec will be forced. * NONE, no codec will be forced.<br>
* Default value is {@link VideoCodec#VP8}
*/ */
public SessionProperties.Builder forcedVideoCodec(VideoCodec forcedVideoCodec) { public SessionProperties.Builder forcedVideoCodec(VideoCodec forcedVideoCodec) {
this.forcedVideoCodec = forcedVideoCodec; this.forcedVideoCodec = forcedVideoCodec;
@ -174,8 +128,10 @@ public class SessionProperties {
} }
/** /**
* Call this method to define if you want to allow transcoding in the media server or not * Call this method to define if you want to allow transcoding in the media
* when {@link #forcedVideoCodec(VideoCodec)} is not compatible with the browser/client. * server or not when {@link #forcedVideoCodec(VideoCodec)} is not compatible
* with the browser/client.<br>
* Default value is false
*/ */
public SessionProperties.Builder allowTranscoding(Boolean allowTranscoding) { public SessionProperties.Builder allowTranscoding(Boolean allowTranscoding) {
this.allowTranscoding = allowTranscoding; this.allowTranscoding = allowTranscoding;
@ -187,21 +143,17 @@ public class SessionProperties {
protected SessionProperties() { protected SessionProperties() {
this.mediaMode = MediaMode.ROUTED; this.mediaMode = MediaMode.ROUTED;
this.recordingMode = RecordingMode.MANUAL; this.recordingMode = RecordingMode.MANUAL;
this.defaultOutputMode = OutputMode.COMPOSED; this.defaultRecordingProperties = new RecordingProperties.Builder().build();
this.defaultRecordingLayout = RecordingLayout.BEST_FIT;
this.defaultCustomLayout = "";
this.customSessionId = ""; this.customSessionId = "";
this.mediaNode = ""; this.mediaNode = "";
} }
private SessionProperties(MediaMode mediaMode, RecordingMode recordingMode, OutputMode outputMode, private SessionProperties(MediaMode mediaMode, RecordingMode recordingMode,
RecordingLayout layout, String defaultCustomLayout, String customSessionId, String mediaNode, RecordingProperties defaultRecordingProperties, String customSessionId, String mediaNode,
VideoCodec forcedVideoCodec, Boolean allowTranscoding) { VideoCodec forcedVideoCodec, Boolean allowTranscoding) {
this.mediaMode = mediaMode; this.mediaMode = mediaMode;
this.recordingMode = recordingMode; this.recordingMode = recordingMode;
this.defaultOutputMode = outputMode; this.defaultRecordingProperties = defaultRecordingProperties;
this.defaultRecordingLayout = layout;
this.defaultCustomLayout = defaultCustomLayout;
this.customSessionId = customSessionId; this.customSessionId = customSessionId;
this.mediaNode = mediaNode; this.mediaNode = mediaNode;
this.forcedVideoCodec = forcedVideoCodec; this.forcedVideoCodec = forcedVideoCodec;
@ -227,46 +179,13 @@ public class SessionProperties {
} }
/** /**
* Defines the default value used to initialize property * Defines the default recording properties of this session. You can easily
* {@link io.openvidu.java.client.RecordingProperties#outputMode()} of every * override this value later when starting a
* recording of this session. You can easily override this value later when * {@link io.openvidu.java.client.Recording} by providing new
* starting a {@link io.openvidu.java.client.Recording} by calling * {@link RecordingProperties}
* {@link io.openvidu.java.client.RecordingProperties.Builder#outputMode(Recording.OutputMode)}
* with any other value
*/ */
public OutputMode defaultOutputMode() { public RecordingProperties defaultRecordingProperties() {
return this.defaultOutputMode; return this.defaultRecordingProperties;
}
/**
* Defines the default value used to initialize property
* {@link io.openvidu.java.client.RecordingProperties#recordingLayout()} of
* every recording of this session. You can easily override this value later
* when starting a {@link io.openvidu.java.client.Recording} by calling
* {@link io.openvidu.java.client.RecordingProperties.Builder#recordingLayout(RecordingLayout)}
* with any other value.<br>
* Recording layouts are only applicable to recordings with OutputMode
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START}
*/
public RecordingLayout defaultRecordingLayout() {
return this.defaultRecordingLayout;
}
/**
* Defines the default value used to initialize property
* {@link io.openvidu.java.client.RecordingProperties#customLayout()} of every
* recording of this session. You can easily override this value later when
* starting a {@link io.openvidu.java.client.Recording} by calling
* {@link io.openvidu.java.client.RecordingProperties.Builder#customLayout(String)}
* with any other value.<br>
* Custom layouts are only applicable to recordings with OutputMode
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED} (or
* {@link io.openvidu.java.client.Recording.OutputMode#COMPOSED_QUICK_START})
* and RecordingLayout {@link io.openvidu.java.client.RecordingLayout#CUSTOM}
*/
public String defaultCustomLayout() {
return this.defaultCustomLayout;
} }
/** /**
@ -301,8 +220,8 @@ public class SessionProperties {
} }
/** /**
* Defines if transcoding is allowed or not when {@link #forcedVideoCodec} * Defines if transcoding is allowed or not when {@link #forcedVideoCodec} is
* is not a compatible codec with the browser/client. * not a compatible codec with the browser/client.
*/ */
public Boolean isTranscodingAllowed() { public Boolean isTranscodingAllowed() {
return this.allowTranscoding; return this.allowTranscoding;
@ -312,10 +231,8 @@ public class SessionProperties {
JsonObject json = new JsonObject(); JsonObject json = new JsonObject();
json.addProperty("mediaMode", mediaMode().name()); json.addProperty("mediaMode", mediaMode().name());
json.addProperty("recordingMode", recordingMode().name()); json.addProperty("recordingMode", recordingMode().name());
json.addProperty("defaultOutputMode", defaultOutputMode().name());
json.addProperty("defaultRecordingLayout", defaultRecordingLayout().name());
json.addProperty("defaultCustomLayout", defaultCustomLayout());
json.addProperty("customSessionId", customSessionId()); json.addProperty("customSessionId", customSessionId());
json.add("defaultRecordingProperties", defaultRecordingProperties.toJson());
if (mediaNode() != null) { if (mediaNode() != null) {
JsonObject mediaNodeJson = new JsonObject(); JsonObject mediaNodeJson = new JsonObject();
mediaNodeJson.addProperty("id", mediaNode()); mediaNodeJson.addProperty("id", mediaNode());

View File

@ -1,6 +1,6 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu) [![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](https://openvidu.io) [![][OpenViduLogo]](https://openvidu.io)

File diff suppressed because it is too large Load Diff

View File

@ -2,21 +2,21 @@
"author": "OpenVidu", "author": "OpenVidu",
"dependencies": { "dependencies": {
"axios": "0.21.1", "axios": "0.21.1",
"buffer": "6.0.2" "buffer": "6.0.3"
}, },
"description": "OpenVidu Node Client", "description": "OpenVidu Node Client",
"devDependencies": { "devDependencies": {
"@types/node": "14.14.7", "@types/node": "14.14.37",
"grunt": "1.3.0", "grunt": "1.3.0",
"grunt-cli": "1.3.2", "grunt-cli": "1.4.2",
"grunt-contrib-copy": "1.0.0", "grunt-contrib-copy": "1.0.0",
"grunt-contrib-sass": "2.0.0", "grunt-contrib-sass": "2.0.0",
"grunt-contrib-uglify": "5.0.0", "grunt-contrib-uglify": "5.0.1",
"grunt-contrib-watch": "1.1.0", "grunt-contrib-watch": "1.1.0",
"grunt-postcss": "0.9.0", "grunt-postcss": "0.9.0",
"grunt-string-replace": "1.3.1", "grunt-string-replace": "1.3.1",
"grunt-ts": "6.0.0-beta.22", "grunt-ts": "6.0.0-beta.22",
"ts-node": "9.0.0", "ts-node": "9.1.1",
"tslint": "6.1.3", "tslint": "6.1.3",
"typedoc": "0.19.2", "typedoc": "0.19.2",
"typescript": "3.8.3" "typescript": "3.8.3"
@ -33,5 +33,5 @@
"docs": "./generate-docs.sh" "docs": "./generate-docs.sh"
}, },
"typings": "lib/index.d.ts", "typings": "lib/index.d.ts",
"version": "2.16.0" "version": "2.17.0"
} }

View File

@ -126,6 +126,7 @@ export class OpenVidu {
* *
* @param sessionId The `sessionId` of the [[Session]] you want to start recording * @param sessionId The `sessionId` of the [[Session]] you want to start recording
* @param name The name you want to give to the video file. You can access this same value in your clients on recording events (`recordingStarted`, `recordingStopped`) * @param name The name you want to give to the video file. You can access this same value in your clients on recording events (`recordingStarted`, `recordingStopped`)
* @param properties Custom RecordingProperties to apply to this Recording. This will override the global default values set to the Session with [[SessionProperties.defaultRecordingProperties]]
* *
* @returns A Promise that is resolved to the [[Recording]] if it successfully started (the recording can be stopped with guarantees) and rejected with an Error * @returns A Promise that is resolved to the [[Recording]] if it successfully started (the recording can be stopped with guarantees) and rejected with an Error
* object if not. This Error object has as `message` property with the following values: * object if not. This Error object has as `message` property with the following values:
@ -141,32 +142,29 @@ export class OpenVidu {
let data; let data;
if (!!param2) { if (param2 != null) {
if (!(typeof param2 === 'string')) { if (typeof param2 === 'string') {
const properties = <RecordingProperties>param2;
data = {
session: sessionId,
name: !!properties.name ? properties.name : '',
outputMode: properties.outputMode,
hasAudio: properties.hasAudio != null ? properties.hasAudio : null,
hasVideo: properties.hasVideo != null ? properties.hasVideo : null,
shmSize: properties.shmSize,
mediaNode: properties.mediaNode
};
if ((data.hasVideo == null || data.hasVideo) && (data.outputMode == null || data.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED]
|| data.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED_QUICK_START])) {
data.resolution = properties.resolution;
data.recordingLayout = !!properties.recordingLayout ? properties.recordingLayout : '';
if (data.recordingLayout.toString() === RecordingLayout[RecordingLayout.CUSTOM]) {
data.customLayout = !!properties.customLayout ? properties.customLayout : '';
}
}
data = JSON.stringify(data);
} else {
data = JSON.stringify({ data = JSON.stringify({
session: sessionId, session: sessionId,
name: param2 name: param2
}); });
} else {
const properties: RecordingProperties = param2 as RecordingProperties;
data = {
session: sessionId,
name: properties.name,
outputMode: properties.outputMode,
recordingLayout: properties.recordingLayout,
customLayout: properties.customLayout,
ignoreFailedStreams: properties.ignoreFailedStreams,
resolution: properties.resolution,
frameRate: properties.frameRate,
hasAudio: properties.hasAudio,
hasVideo: properties.hasVideo,
shmSize: properties.shmSize,
mediaNode: properties.mediaNode
};
data = JSON.stringify(data);
} }
} else { } else {
data = JSON.stringify({ data = JSON.stringify({
@ -509,8 +507,8 @@ export class OpenVidu {
events: publisherExtended.events, events: publisherExtended.events,
localCandidate: publisherExtended.localCandidate, localCandidate: publisherExtended.localCandidate,
remoteCandidate: publisherExtended.remoteCandidate, remoteCandidate: publisherExtended.remoteCandidate,
receivedCandidates: publisherExtended.receivedCandidates, clientIceCandidates: publisherExtended.clientIceCandidates,
gatheredCandidates: publisherExtended.gatheredCandidates, serverIceCandidates: publisherExtended.serverIceCandidates,
webrtcEndpointName: publisherExtended.webrtcEndpointName, webrtcEndpointName: publisherExtended.webrtcEndpointName,
localSdp: publisherExtended.localSdp, localSdp: publisherExtended.localSdp,
remoteSdp: publisherExtended.remoteSdp remoteSdp: publisherExtended.remoteSdp
@ -535,8 +533,8 @@ export class OpenVidu {
events: subscriberExtended.events, events: subscriberExtended.events,
localCandidate: subscriberExtended.localCandidate, localCandidate: subscriberExtended.localCandidate,
remoteCandidate: subscriberExtended.remoteCandidate, remoteCandidate: subscriberExtended.remoteCandidate,
receivedCandidates: subscriberExtended.receivedCandidates, clientIceCandidates: subscriberExtended.clientIceCandidates,
gatheredCandidates: subscriberExtended.gatheredCandidates, serverIceCandidates: subscriberExtended.serverIceCandidates,
webrtcEndpointName: subscriberExtended.webrtcEndpointName, webrtcEndpointName: subscriberExtended.webrtcEndpointName,
localSdp: subscriberExtended.localSdp, localSdp: subscriberExtended.localSdp,
remoteSdp: subscriberExtended.remoteSdp remoteSdp: subscriberExtended.remoteSdp

View File

@ -77,19 +77,26 @@ export class Recording {
this.url = json['url']; this.url = json['url'];
this.status = json['status']; this.status = json['status'];
this.properties = { this.properties = {
name: !!(json['name']) ? json['name'] : this.id, name: (json['name'] != null) ? json['name'] : this.id,
outputMode: !!(json['outputMode']) ? json['outputMode'] : Recording.OutputMode.COMPOSED, hasAudio: (json['hasAudio'] != null) ? !!json['hasAudio'] : Recording.DefaultRecordingPropertiesValues.hasAudio,
hasAudio: !!(json['hasAudio']), hasVideo: (json['hasVideo'] != null) ? !!json['hasVideo'] : Recording.DefaultRecordingPropertiesValues.hasVideo,
hasVideo: !!json['hasVideo'] outputMode: (json['outputMode'] != null) ? json['outputMode'] : Recording.DefaultRecordingPropertiesValues.outputMode,
mediaNode: json['mediaNode']
}; };
if (this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED] if ((this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED]
|| this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED_QUICK_START]) { || this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.COMPOSED_QUICK_START])
this.properties.resolution = !!(json['resolution']) ? json['resolution'] : '1920x1080'; && this.properties.hasVideo) {
this.properties.recordingLayout = !!(json['recordingLayout']) ? json['recordingLayout'] : RecordingLayout.BEST_FIT; this.properties.recordingLayout = (json['recordingLayout'] != null) ? json['recordingLayout'] : Recording.DefaultRecordingPropertiesValues.recordingLayout;
this.properties.resolution = (json['resolution'] != null) ? json['resolution'] : Recording.DefaultRecordingPropertiesValues.resolution;
this.properties.frameRate = (json['frameRate'] != null) ? Number(json['frameRate']) : Recording.DefaultRecordingPropertiesValues.frameRate;
this.properties.shmSize = (json['shmSize'] != null) ? Number(json['shmSize']) : Recording.DefaultRecordingPropertiesValues.shmSize;
if (this.properties.recordingLayout.toString() === RecordingLayout[RecordingLayout.CUSTOM]) { if (this.properties.recordingLayout.toString() === RecordingLayout[RecordingLayout.CUSTOM]) {
this.properties.customLayout = json['customLayout']; this.properties.customLayout = (json['customLayout'] != null) ? json['customLayout'] : '';
} }
} }
if (this.properties.outputMode.toString() === Recording.OutputMode[Recording.OutputMode.INDIVIDUAL]) {
this.properties.ignoreFailedStreams = (json['ignoreFailedStreams'] != null) ? !!json['ignoreFailedStreams'] : Recording.DefaultRecordingPropertiesValues.ignoreFailedStreams;
}
} }
/* tslint:enable:no-string-literal */ /* tslint:enable:no-string-literal */
} }
@ -165,4 +172,19 @@ export namespace Recording {
*/ */
INDIVIDUAL = 'INDIVIDUAL' INDIVIDUAL = 'INDIVIDUAL'
} }
/**
* @hidden
*/
export class DefaultRecordingPropertiesValues {
static readonly hasAudio: boolean = true;
static readonly hasVideo: boolean = true;
static readonly outputMode: Recording.OutputMode = Recording.OutputMode.COMPOSED;
static readonly recordingLayout: RecordingLayout = RecordingLayout.BEST_FIT;
static readonly resolution: string = "1280x720";
static readonly frameRate: number = 25;
static readonly shmSize: number = 536870912;
static readonly ignoreFailedStreams: boolean = false;
}
} }

View File

@ -16,7 +16,7 @@
*/ */
/** /**
* See [[SessionProperties.defaultRecordingLayout]] and [[RecordingProperties.recordingLayout]] * See [[RecordingProperties.recordingLayout]]
*/ */
export enum RecordingLayout { export enum RecordingLayout {

View File

@ -30,50 +30,85 @@ export interface RecordingProperties {
*/ */
name?: string; name?: string;
/**
* The mode of recording: COMPOSED for a single archive in a grid layout or INDIVIDUAL for one archive for each stream
*/
outputMode?: Recording.OutputMode;
/**
* The layout to be used in the recording.<br>
* Will only have effect if [[RecordingProperties.outputMode]] is `COMPOSED` or `COMPOSED_QUICK_START`
*/
recordingLayout?: RecordingLayout;
/**
* The relative path to the specific custom layout you want to use.<br>
* Will only have effect if [[RecordingProperties.outputMode]] is `COMPOSED` (or `COMPOSED_QUICK_START`) and [[RecordingProperties.recordingLayout]] is `CUSTOM`<br>
* See [Custom recording layouts](/en/stable/advanced-features/recording#custom-recording-layouts) to learn more
*/
customLayout?: string;
/**
* Recording video file resolution. Must be a string with format "WIDTHxHEIGHT",
* being both WIDTH and HEIGHT the number of pixels between 100 and 1999.<br>
* Will only have effect if [[RecordingProperties.outputMode]]
* is set to [[Recording.OutputMode.COMPOSED]] or [[Recording.OutputMode.COMPOSED_QUICK_START]].
* For [[Recording.OutputMode.INDIVIDUAL]] all
* individual video files will have the native resolution of the published stream
*/
resolution?: string;
/** /**
* Whether or not to record audio. Cannot be set to false at the same time as [[RecordingProperties.hasVideo]] * Whether or not to record audio. Cannot be set to false at the same time as [[RecordingProperties.hasVideo]]
*
* Default to true
*/ */
hasAudio?: boolean; hasAudio?: boolean;
/** /**
* Whether or not to record video. Cannot be set to false at the same time as [[RecordingProperties.hasAudio]] * Whether or not to record video. Cannot be set to false at the same time as [[RecordingProperties.hasAudio]]
*
* Default to true
*/ */
hasVideo?: boolean; hasVideo?: boolean;
/** /**
* If COMPOSED recording, the amount of shared memory reserved for the recording process in bytes. * The mode of recording: COMPOSED for a single archive in a grid layout or INDIVIDUAL for one archive for each stream
* Minimum 134217728 (128MB). Property ignored if INDIVIDUAL recording. Default to 536870912 (512 MB) *
* Default to [[Recording.OutputMode.COMPOSED]]
*/
outputMode?: Recording.OutputMode;
/**
* The layout to be used in the recording.<br>
* Will only have effect if [[RecordingProperties.outputMode]] is set to [[Recording.OutputMode.COMPOSED]] or [[Recording.OutputMode.COMPOSED_QUICK_START]]
*
* Default to [[RecordingLayout.BEST_FIT]]
*/
recordingLayout?: RecordingLayout;
/**
* Recording video file resolution. Must be a string with format "WIDTHxHEIGHT",
* being both WIDTH and HEIGHT the number of pixels between 100 and 1999.<br>
* Will only have effect if [[RecordingProperties.outputMode]] is set to [[Recording.OutputMode.COMPOSED]] or [[Recording.OutputMode.COMPOSED_QUICK_START]]
* and [[RecordingProperties.hasVideo]] is set to true. For [[Recording.OutputMode.INDIVIDUAL]] all individual video files will have the native resolution of the published stream.
*
* Default to "1280x720"
*/
resolution?: string;
/**
* Recording video file frame rate.<br>
* Will only have effect if [[RecordingProperties.outputMode]] is set to [[Recording.OutputMode.COMPOSED]] or [[Recording.OutputMode.COMPOSED_QUICK_START]]
* and [[RecordingProperties.hasVideo]] is set to true. For [[Recording.OutputMode.INDIVIDUAL]] all individual video files will have the native frame rate of the published stream.
*
* Default to 25
*/
frameRate?: number;
/**
* The amount of shared memory reserved for the recording process in bytes.
* Will only have effect if [[RecordingProperties.outputMode]] is set to [[Recording.OutputMode.COMPOSED]] or [[Recording.OutputMode.COMPOSED_QUICK_START]]
* and [[RecordingProperties.hasVideo]] is set to true. Property ignored for INDIVIDUAL recordings and audio-only recordings.
* Minimum 134217728 (128MB).
*
* Default to 536870912 (512 MB)
*/ */
shmSize?: number; shmSize?: number;
/**
* The relative path to the specific custom layout you want to use.<br>
* Will only have effect if [[RecordingProperties.outputMode]] is set to [[Recording.OutputMode.COMPOSED]] or [[Recording.OutputMode.COMPOSED_QUICK_START]]
* and [[RecordingProperties.recordingLayout]] is set to [[RecordingLayout.CUSTOM]]<br>
* See [Custom recording layouts](/en/stable/advanced-features/recording#custom-recording-layouts) to learn more.
*/
customLayout?: string;
/**
* Whether to ignore failed streams or not when starting the recording. This property only applies if [[RecordingProperties.outputMode]] is set to [[Recording.OutputMode.INDIVIDUAL]].
* For this type of recordings, when calling [[OpenVidu.startRecording]] by default all the streams available at the moment the recording process starts must be healthy
* and properly sending media. If some stream that should be sending media is broken, then the recording process fails after a 10s timeout. In this way your application is notified
* that some stream is not being recorded, so it can retry the process again.
*
* But you can disable this rollback behavior and simply ignore any failed stream, which will be susceptible to be recorded in the future if media starts flowing as expected at any point.
* The downside of this behavior is that you will have no guarantee that all streams present at the beginning of a recording are actually being recorded.
*
* Default to false
*/
ignoreFailedStreams?: boolean;
/** /**
* **This feature is part of OpenVidu Pro tier** <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size: 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a> * **This feature is part of OpenVidu Pro tier** <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size: 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a>
* *

View File

@ -16,6 +16,7 @@
*/ */
import axios, { AxiosError } from 'axios'; import axios, { AxiosError } from 'axios';
import { VideoCodec } from './VideoCodec';
import { Connection } from './Connection'; import { Connection } from './Connection';
import { ConnectionProperties } from './ConnectionProperties'; import { ConnectionProperties } from './ConnectionProperties';
import { MediaMode } from './MediaMode'; import { MediaMode } from './MediaMode';
@ -26,6 +27,7 @@ import { RecordingLayout } from './RecordingLayout';
import { RecordingMode } from './RecordingMode'; import { RecordingMode } from './RecordingMode';
import { SessionProperties } from './SessionProperties'; import { SessionProperties } from './SessionProperties';
import { TokenOptions } from './TokenOptions'; import { TokenOptions } from './TokenOptions';
import { RecordingProperties } from 'RecordingProperties';
export class Session { export class Session {
@ -90,12 +92,7 @@ export class Session {
// Empty parameter // Empty parameter
this.properties = {}; this.properties = {};
} }
this.properties.mediaMode = !!this.properties.mediaMode ? this.properties.mediaMode : MediaMode.ROUTED; this.sanitizeDefaultSessionProperties(this.properties);
this.properties.recordingMode = !!this.properties.recordingMode ? this.properties.recordingMode : RecordingMode.MANUAL;
this.properties.defaultOutputMode = !!this.properties.defaultOutputMode ? this.properties.defaultOutputMode : Recording.OutputMode.COMPOSED;
this.properties.defaultRecordingLayout = !!this.properties.defaultRecordingLayout ? this.properties.defaultRecordingLayout : RecordingLayout.BEST_FIT;
this.properties.forcedVideoCodec = !!this.properties.forcedVideoCodec ? this.properties.forcedVideoCodec : undefined;
this.properties.allowTranscoding = this.properties.allowTranscoding != null ? this.properties.allowTranscoding : undefined;
} }
/** /**
@ -145,10 +142,15 @@ export class Session {
public createConnection(connectionProperties?: ConnectionProperties): Promise<Connection> { public createConnection(connectionProperties?: ConnectionProperties): Promise<Connection> {
return new Promise<Connection>((resolve, reject) => { return new Promise<Connection>((resolve, reject) => {
const data = JSON.stringify({ const data = JSON.stringify({
role: (!!connectionProperties && !!connectionProperties.role) ? connectionProperties.role : null, type: (!!connectionProperties && !!connectionProperties.type) ? connectionProperties.type : null,
data: (!!connectionProperties && !!connectionProperties.data) ? connectionProperties.data : null, data: (!!connectionProperties && !!connectionProperties.data) ? connectionProperties.data : null,
record: !!connectionProperties ? connectionProperties.record : null, record: !!connectionProperties ? connectionProperties.record : null,
kurentoOptions: (!!connectionProperties && !!connectionProperties.kurentoOptions) ? connectionProperties.kurentoOptions : null role: (!!connectionProperties && !!connectionProperties.role) ? connectionProperties.role : null,
kurentoOptions: (!!connectionProperties && !!connectionProperties.kurentoOptions) ? connectionProperties.kurentoOptions : null,
rtspUri: (!!connectionProperties && !!connectionProperties.rtspUri) ? connectionProperties.rtspUri : null,
adaptativeBitrate: !!connectionProperties ? connectionProperties.adaptativeBitrate : null,
onlyPlayWithSubscribers: !!connectionProperties ? connectionProperties.onlyPlayWithSubscribers : null,
networkCache: (!!connectionProperties && (connectionProperties.networkCache != null)) ? connectionProperties.networkCache : null
}); });
axios.post( axios.post(
this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection', this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection',
@ -184,8 +186,8 @@ export class Session {
* *
* @returns A Promise that is resolved if the session has been closed successfully and rejected with an Error object if not * @returns A Promise that is resolved if the session has been closed successfully and rejected with an Error object if not
*/ */
public close(): Promise<any> { public close(): Promise<void> {
return new Promise<any>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
axios.delete( axios.delete(
this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId, this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId,
{ {
@ -267,8 +269,8 @@ export class Session {
* *
* @returns A Promise that is resolved if the Connection was successfully removed from the Session and rejected with an Error object if not * @returns A Promise that is resolved if the Connection was successfully removed from the Session and rejected with an Error object if not
*/ */
public forceDisconnect(connection: string | Connection): Promise<any> { public forceDisconnect(connection: string | Connection): Promise<void> {
return new Promise<any>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const connectionId: string = typeof connection === 'string' ? connection : (<Connection>connection).connectionId; const connectionId: string = typeof connection === 'string' ? connection : (<Connection>connection).connectionId;
axios.delete( axios.delete(
this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection/' + connectionId, this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/connection/' + connectionId,
@ -339,8 +341,8 @@ export class Session {
* *
* @returns A Promise that is resolved if the stream was successfully unpublished and rejected with an Error object if not * @returns A Promise that is resolved if the stream was successfully unpublished and rejected with an Error object if not
*/ */
public forceUnpublish(publisher: string | Publisher): Promise<any> { public forceUnpublish(publisher: string | Publisher): Promise<void> {
return new Promise<any>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const streamId: string = typeof publisher === 'string' ? publisher : (<Publisher>publisher).streamId; const streamId: string = typeof publisher === 'string' ? publisher : (<Publisher>publisher).streamId;
axios.delete( axios.delete(
this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/stream/' + streamId, this.ov.host + OpenVidu.API_SESSIONS + '/' + this.sessionId + '/stream/' + streamId,
@ -461,18 +463,11 @@ export class Session {
resolve(this.sessionId); resolve(this.sessionId);
} }
const mediaMode = !!this.properties.mediaMode ? this.properties.mediaMode : MediaMode.ROUTED; this.sanitizeDefaultSessionProperties(this.properties);
const recordingMode = !!this.properties.recordingMode ? this.properties.recordingMode : RecordingMode.MANUAL;
const defaultOutputMode = !!this.properties.defaultOutputMode ? this.properties.defaultOutputMode : Recording.OutputMode.COMPOSED;
const defaultRecordingLayout = !!this.properties.defaultRecordingLayout ? this.properties.defaultRecordingLayout : RecordingLayout.BEST_FIT;
const defaultCustomLayout = !!this.properties.defaultCustomLayout ? this.properties.defaultCustomLayout : '';
const customSessionId = !!this.properties.customSessionId ? this.properties.customSessionId : '';
const mediaNode = !!this.properties.mediaNode ? this.properties.mediaNode : undefined;
const forcedVideoCodec = !!this.properties.forcedVideoCodec ? this.properties.forcedVideoCodec : undefined;
const allowTranscoding = this.properties.allowTranscoding != null ? this.properties.allowTranscoding : undefined;
const data = JSON.stringify({mediaMode, recordingMode, defaultOutputMode, defaultRecordingLayout, defaultCustomLayout, const data = JSON.stringify(
customSessionId, mediaNode, forcedVideoCodec, allowTranscoding}); this.properties
);
axios.post( axios.post(
this.ov.host + OpenVidu.API_SESSIONS, this.ov.host + OpenVidu.API_SESSIONS,
@ -489,15 +484,14 @@ export class Session {
// SUCCESS response from openvidu-server. Resolve token // SUCCESS response from openvidu-server. Resolve token
this.sessionId = res.data.id; this.sessionId = res.data.id;
this.createdAt = res.data.createdAt; this.createdAt = res.data.createdAt;
this.properties.mediaMode = mediaMode; this.properties.mediaMode = res.data.mediaMode;
this.properties.recordingMode = recordingMode; this.properties.recordingMode = res.data.recordingMode;
this.properties.defaultOutputMode = defaultOutputMode; this.properties.customSessionId = res.data.customSessionId;
this.properties.defaultRecordingLayout = defaultRecordingLayout; this.properties.defaultRecordingProperties = res.data.defaultRecordingProperties;
this.properties.defaultCustomLayout = defaultCustomLayout; this.properties.mediaNode = res.data.mediaNode;
this.properties.customSessionId = customSessionId;
this.properties.mediaNode = mediaNode;
this.properties.forcedVideoCodec = res.data.forcedVideoCodec; this.properties.forcedVideoCodec = res.data.forcedVideoCodec;
this.properties.allowTranscoding = res.data.allowTranscoding; this.properties.allowTranscoding = res.data.allowTranscoding;
this.sanitizeDefaultSessionProperties(this.properties);
resolve(this.sessionId); resolve(this.sessionId);
} else { } else {
// ERROR response from openvidu-server. Resolve HTTP status // ERROR response from openvidu-server. Resolve HTTP status
@ -539,21 +533,17 @@ export class Session {
customSessionId: json.customSessionId, customSessionId: json.customSessionId,
mediaMode: json.mediaMode, mediaMode: json.mediaMode,
recordingMode: json.recordingMode, recordingMode: json.recordingMode,
defaultOutputMode: json.defaultOutputMode, defaultRecordingProperties: json.defaultRecordingProperties,
defaultRecordingLayout: json.defaultRecordingLayout,
defaultCustomLayout: json.defaultCustomLayout,
forcedVideoCodec: json.forcedVideoCodec, forcedVideoCodec: json.forcedVideoCodec,
allowTranscoding: json.allowTranscoding allowTranscoding: json.allowTranscoding
}; };
if (json.defaultRecordingLayout == null) { this.sanitizeDefaultSessionProperties(this.properties);
delete this.properties.defaultRecordingLayout; if (json.defaultRecordingProperties == null) {
delete this.properties.defaultRecordingProperties;
} }
if (json.customSessionId == null) { if (json.customSessionId == null) {
delete this.properties.customSessionId; delete this.properties.customSessionId;
} }
if (json.defaultCustomLayout == null) {
delete this.properties.defaultCustomLayout;
}
if (json.mediaNode == null) { if (json.mediaNode == null) {
delete this.properties.mediaNode; delete this.properties.mediaNode;
} }
@ -661,4 +651,52 @@ export class Session {
} }
} }
/**
* @hidden
*/
private sanitizeDefaultSessionProperties(props: SessionProperties) {
props.mediaMode = (props.mediaMode != null) ? props.mediaMode : MediaMode.ROUTED;
props.recordingMode = (props.recordingMode != null) ? props.recordingMode : RecordingMode.MANUAL;
props.customSessionId = (props.customSessionId != null) ? props.customSessionId : '';
props.mediaNode = (props.mediaNode != null) ? props.mediaNode : undefined;
props.forcedVideoCodec = (props.forcedVideoCodec != null) ? props.forcedVideoCodec : VideoCodec.VP8;
props.allowTranscoding = (props.allowTranscoding != null) ? props.allowTranscoding : false;
if (!props.defaultRecordingProperties) {
props.defaultRecordingProperties = {};
}
props.defaultRecordingProperties.name = (props.defaultRecordingProperties?.name != null) ? props.defaultRecordingProperties.name : '';
props.defaultRecordingProperties.hasAudio = (props.defaultRecordingProperties?.hasAudio != null) ? props.defaultRecordingProperties.hasAudio : Recording.DefaultRecordingPropertiesValues.hasAudio;
props.defaultRecordingProperties.hasVideo = (props.defaultRecordingProperties?.hasVideo != null) ? props.defaultRecordingProperties.hasVideo : Recording.DefaultRecordingPropertiesValues.hasVideo;
props.defaultRecordingProperties.outputMode = (props.defaultRecordingProperties?.outputMode != null) ? props.defaultRecordingProperties.outputMode : Recording.DefaultRecordingPropertiesValues.outputMode;
props.defaultRecordingProperties.mediaNode = props.defaultRecordingProperties?.mediaNode;
if ((props.defaultRecordingProperties.outputMode === Recording.OutputMode.COMPOSED || props.defaultRecordingProperties.outputMode == Recording.OutputMode.COMPOSED_QUICK_START) && props.defaultRecordingProperties.hasVideo) {
props.defaultRecordingProperties.recordingLayout = (props.defaultRecordingProperties.recordingLayout != null) ? props.defaultRecordingProperties.recordingLayout : Recording.DefaultRecordingPropertiesValues.recordingLayout;
props.defaultRecordingProperties.resolution = (props.defaultRecordingProperties.resolution != null) ? props.defaultRecordingProperties.resolution : Recording.DefaultRecordingPropertiesValues.resolution;
props.defaultRecordingProperties.frameRate = (props.defaultRecordingProperties.frameRate != null) ? props.defaultRecordingProperties.frameRate : Recording.DefaultRecordingPropertiesValues.frameRate;
props.defaultRecordingProperties.shmSize = (props.defaultRecordingProperties.shmSize != null) ? props.defaultRecordingProperties.shmSize : Recording.DefaultRecordingPropertiesValues.shmSize;
if (props.defaultRecordingProperties.recordingLayout === RecordingLayout.CUSTOM) {
props.defaultRecordingProperties.customLayout = (props.defaultRecordingProperties.customLayout != null) ? props.defaultRecordingProperties.customLayout : '';
}
}
if (props.defaultRecordingProperties.outputMode === Recording.OutputMode.INDIVIDUAL) {
props.defaultRecordingProperties.ignoreFailedStreams = (props.defaultRecordingProperties?.ignoreFailedStreams != null) ? props.defaultRecordingProperties.ignoreFailedStreams : Recording.DefaultRecordingPropertiesValues.ignoreFailedStreams;
}
this.formatMediaNodeObjectIfNecessary(props.defaultRecordingProperties);
this.formatMediaNodeObjectIfNecessary(props);
}
/**
* @hidden
*/
private formatMediaNodeObjectIfNecessary(properties: RecordingProperties | SessionProperties) {
if (properties.mediaNode != null) {
if (typeof properties.mediaNode === 'string') {
properties.mediaNode = { id: properties.mediaNode };
}
}
}
} }

View File

@ -16,9 +16,9 @@
*/ */
import { MediaMode } from './MediaMode'; import { MediaMode } from './MediaMode';
import { Recording } from './Recording'; import { RecordingProperties } from './RecordingProperties';
import { RecordingLayout } from './RecordingLayout';
import { RecordingMode } from './RecordingMode'; import { RecordingMode } from './RecordingMode';
import { VideoCodec } from './VideoCodec';
/** /**
* See [[OpenVidu.createSession]] * See [[OpenVidu.createSession]]
@ -28,35 +28,25 @@ export interface SessionProperties {
/** /**
* How the media streams will be sent and received by your clients: routed through OpenVidu Media Node * How the media streams will be sent and received by your clients: routed through OpenVidu Media Node
* (`MediaMode.ROUTED`) or attempting direct p2p connections (`MediaMode.RELAYED`, _not available yet_) * (`MediaMode.ROUTED`) or attempting direct p2p connections (`MediaMode.RELAYED`, _not available yet_)
*
* Default to [[MediaMode.ROUTED]]
*/ */
mediaMode?: MediaMode; mediaMode?: MediaMode;
/** /**
* Whether the Session will be automatically recorded (`RecordingMode.ALWAYS`) or not (`RecordingMode.MANUAL`) * Whether the Session will be automatically recorded (`RecordingMode.ALWAYS`) or not (`RecordingMode.MANUAL`)
*
* Default to [[RecordingMode.MANUAL]]
*/ */
recordingMode?: RecordingMode; recordingMode?: RecordingMode;
/** /**
* Default value used to initialize property [[RecordingProperties.outputMode]] of every recording of this session. * Default recording properties of this session. You can easily override this value later when starting a
* [[Recording]] by providing new [[RecordingProperties]]
* *
* You can easily override this value later by setting [[RecordingProperties.outputMode]] to any other value * Default values defined in [[RecordingProperties]] class
*/ */
defaultOutputMode?: Recording.OutputMode; defaultRecordingProperties?: RecordingProperties;
/**
* Default value used to initialize property [[RecordingProperties.recordingLayout]] of every recording of this session.
*
* You can easily override this value later by setting [[RecordingProperties.recordingLayout]] to any other value
*/
defaultRecordingLayout?: RecordingLayout;
/**
* Default value used to initialize property [[RecordingProperties.customLayout]] of every recording of this session.
* This property can only be defined if [[SessionProperties.defaultRecordingLayout]] is set to [[RecordingLayout.CUSTOM]].
*
* You can easily override this value later by setting [[RecordingProperties.customLayout]] to any other value
*/
defaultCustomLayout?: string;
/** /**
* Fix the sessionId that will be assigned to the session with this parameter. You can take advantage of this property * Fix the sessionId that will be assigned to the session with this parameter. You can take advantage of this property
@ -79,16 +69,18 @@ export interface SessionProperties {
/** /**
* It defines which video codec do you want to be forcibly used for this session. * It defines which video codec do you want to be forcibly used for this session.
* This allows browsers/clients to use the same codec avoiding transcoding in the media server. * This allows browsers/clients to use the same codec avoiding transcoding in the media server.
* If the browser/client is not compatible with the specified codec and [[allowTranscoding]] * If the browser/client is not compatible with the specified codec and [[allowTranscoding]] is <code>false</code>
* is <code>false</code> and exception will occur. * and exception will occur. If forcedVideoCodec is set to [[VideoCodec.NONE]], no codec will be forced.
* *
* If forcedVideoCodec is set to NONE, no codec will be forced. * Default to [[VideoCodec.VP8]]
*/ */
forcedVideoCodec?: string; forcedVideoCodec?: VideoCodec;
/** /**
* It defines if you want to allow transcoding in the media server or not * It defines if you want to allow transcoding in the media server or not
* when [[forcedVideoCodec]] is not compatible with the browser/client. * when [[forcedVideoCodec]] is not compatible with the browser/client.
*
* Default to false
*/ */
allowTranscoding?: boolean; allowTranscoding?: boolean;

View File

@ -1,7 +1,7 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable) [![Documentation Status](https://readthedocs.org/projects/openviduio-docs/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/openvidu/) [![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/openvidu/)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu) [![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](https://openvidu.io) [![][OpenViduLogo]](https://openvidu.io)

View File

@ -302,14 +302,14 @@ Resources:
export HOME="/root" export HOME="/root"
# Replace .env variables # Replace .env variables
/usr/local/bin/feedGroupVars.sh || { echo "[Openvidu] Parameters incorrect/insufficient"; exit 1; } /usr/local/bin/feedGroupVars.sh || { echo "[OpenVidu] Parameters incorrect/insufficient"; exit 1; }
# Launch on reboot # Launch on reboot
echo "@reboot /usr/local/bin/restartCE.sh" | crontab echo "@reboot /usr/local/bin/restartCE.sh" | crontab
# Download certs if "WichCert" mode # Download certs if "WichCert" mode
if [ "${WhichCert}" == "owncert" ]; then if [ "${WhichCert}" == "owncert" ]; then
/usr/local/bin/buildCerts.sh || { echo "[Openvidu] error with the certificate files"; exit 1; } /usr/local/bin/buildCerts.sh || { echo "[OpenVidu] error with the certificate files"; exit 1; }
fi fi
# Start openvidu application # Start openvidu application
@ -323,7 +323,7 @@ Resources:
# Send info to openvidu # Send info to openvidu
if [ "${WantToSendInfo}" == "true" ]; then if [ "${WantToSendInfo}" == "true" ]; then
/usr/local/bin/ping.sh /usr/local/bin/ping.sh || true
fi fi
rm /usr/local/bin/ping.sh rm /usr/local/bin/ping.sh
@ -331,7 +331,7 @@ Resources:
/usr/local/bin/check_app_ready.sh /usr/local/bin/check_app_ready.sh
# Start up the cfn-hup daemon to listen for changes to the Web Server metadata # Start up the cfn-hup daemon to listen for changes to the Web Server metadata
/usr/local/bin/cfn-hup -v || { echo "[Openvidu] Failed to start cfn-hup"; exit 1; } /usr/local/bin/cfn-hup -v || { echo "[OpenVidu] Failed to start cfn-hup"; exit 1; }
# sending the finish call # sending the finish call
/usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region} /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}
@ -366,30 +366,58 @@ Resources:
FromPort: 22 FromPort: 22
ToPort: 22 ToPort: 22
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIpv6: ::/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 80 FromPort: 80
ToPort: 80 ToPort: 80
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIpv6: ::/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 443 FromPort: 443
ToPort: 443 ToPort: 443
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 3478
ToPort: 3478
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 3478
ToPort: 3478
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 3478 FromPort: 3478
ToPort: 3478 ToPort: 3478
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 3478 FromPort: 3478
ToPort: 3478 ToPort: 3478
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 40000
ToPort: 57000
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 40000
ToPort: 57000
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 40000 FromPort: 40000
ToPort: 57000 ToPort: 57000
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 40000 FromPort: 40000
ToPort: 57000 ToPort: 57000
CidrIp: 0.0.0.0/0 CidrIpv6: ::/0
Outputs: Outputs:
OpenViduServerURL: OpenViduServerURL:

View File

@ -94,7 +94,7 @@ Resources:
# Openvidu recording # Openvidu recording
docker pull openvidu/openvidu-recording:OPENVIDU_RECORDING_DOCKER_TAG docker pull openvidu/openvidu-recording:OPENVIDU_RECORDING_DOCKER_TAG
# Openvidu CE images # OpenVidu CE images
cd /opt/openvidu cd /opt/openvidu
docker-compose pull docker-compose pull
mode: "000755" mode: "000755"
@ -119,11 +119,11 @@ Resources:
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenviduServerCE cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenviduServerCE
/usr/local/bin/installDockerAndDockerCompose.sh || { echo "[Openvidu] error installing docker and compose"; exit 1; } /usr/local/bin/installDockerAndDockerCompose.sh || { echo "[OpenVidu] error installing docker and compose"; exit 1; }
/usr/local/bin/installOpenviduCE.sh || { echo "[Openvidu] error installing Openvidu CE"; exit 1; } /usr/local/bin/installOpenviduCE.sh || { echo "[OpenVidu] error installing OpenVidu CE"; exit 1; }
/usr/local/bin/getDockerImages.sh || { echo "[Openvidu] error getting docker images"; exit 1; } /usr/local/bin/getDockerImages.sh || { echo "[OpenVidu] error getting docker images"; exit 1; }
# sending the finish call # sending the finish call
/usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region} /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}

View File

@ -148,7 +148,7 @@ OPENVIDU_WEBHOOK=false
# List of events that will be sent by OpenVidu Webhook service # List of events that will be sent by OpenVidu Webhook service
# Default value is all available events # Default value is all available events
OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged] OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged,nodeCrashed]
# How often the garbage collector of non active sessions runs. # How often the garbage collector of non active sessions runs.
# This helps cleaning up sessions that have been initialized through # This helps cleaning up sessions that have been initialized through
@ -174,7 +174,7 @@ OPENVIDU_CDR_PATH=/opt/openvidu/cdr
# -------------------------- # --------------------------
# Docker hub kurento media server: https://hub.docker.com/r/kurento/kurento-media-server # Docker hub kurento media server: https://hub.docker.com/r/kurento/kurento-media-server
# Uncomment the next line and define this variable with KMS image that you want use # Uncomment the next line and define this variable with KMS image that you want use
# KMS_IMAGE=kurento/kurento-media-server:6.15.0 # KMS_IMAGE=kurento/kurento-media-server:6.16.0
# Kurento Media Server Level logs # Kurento Media Server Level logs
# ------------------------------- # -------------------------------

View File

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

View File

@ -11,7 +11,7 @@
# #
# This file will be overridden when update OpenVidu Platform # This file will be overridden when update OpenVidu Platform
# #
# Openvidu Version: 2.16.0 # Openvidu Version: 2.17.0
# #
# Installation Mode: On Premises # Installation Mode: On Premises
# #
@ -22,7 +22,7 @@ version: '3.1'
services: services:
openvidu-server: openvidu-server:
image: openvidu/openvidu-server:2.17.0-dev2 image: openvidu/openvidu-server:2.18.0-dev2
restart: on-failure restart: on-failure
network_mode: host network_mode: host
entrypoint: ['/usr/local/bin/entrypoint.sh'] entrypoint: ['/usr/local/bin/entrypoint.sh']
@ -45,7 +45,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
kms: kms:
image: ${KMS_IMAGE:-kurento/kurento-media-server:6.15.0} image: ${KMS_IMAGE:-kurento/kurento-media-server:6.16.0}
restart: always restart: always
network_mode: host network_mode: host
ulimits: ulimits:
@ -65,7 +65,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
redis: redis:
image: openvidu/openvidu-redis:2.0.0-dev2 image: openvidu/openvidu-redis:2.0.0
restart: always restart: always
network_mode: host network_mode: host
environment: environment:
@ -75,23 +75,28 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
coturn: coturn:
image: openvidu/openvidu-coturn:3.0.0-dev2 image: openvidu/openvidu-coturn:4.0.0-dev2
restart: on-failure restart: on-failure
network_mode: host network_mode: host
environment: environment:
- REDIS_IP=127.0.0.1 - REDIS_IP=127.0.0.1
- TURN_LISTEN_PORT=3478
- DB_NAME=0 - DB_NAME=0
- DB_PASSWORD=${OPENVIDU_SECRET} - DB_PASSWORD=${OPENVIDU_SECRET}
- MIN_PORT=57001 command:
- MAX_PORT=65535 - --log-file=stdout
- ENABLE_COTURN_LOGS=true - --listening-port=3478
- --fingerprint
- --lt-cred-mech
- --min-port=57001
- --max-port=65535
- --realm=openvidu
- --verbose
logging: logging:
options: options:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
nginx: nginx:
image: openvidu/openvidu-proxy:5.0.0-dev2 image: openvidu/openvidu-proxy:6.0.0-dev1
restart: on-failure restart: on-failure
network_mode: host network_mode: host
volumes: volumes:

View File

@ -16,7 +16,7 @@ fatal_error() {
new_ov_installation() { new_ov_installation() {
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Install Openvidu CE %s' "${OPENVIDU_VERSION}" printf '\n Install OpenVidu CE %s' "${OPENVIDU_VERSION}"
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
@ -25,7 +25,7 @@ new_ov_installation() {
mkdir "${OPENVIDU_FOLDER}" || fatal_error "Error while creating the folder '${OPENVIDU_FOLDER}'" mkdir "${OPENVIDU_FOLDER}" || fatal_error "Error while creating the folder '${OPENVIDU_FOLDER}'"
# Download necessary files # Download necessary files
printf '\n => Downloading Openvidu CE files:' printf '\n => Downloading OpenVidu CE files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/ce/docker-compose/.env \ curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/ce/docker-compose/.env \
--output "${OPENVIDU_FOLDER}/.env" || fatal_error "Error when downloading the file '.env'" --output "${OPENVIDU_FOLDER}/.env" || fatal_error "Error when downloading the file '.env'"
@ -72,7 +72,7 @@ new_ov_installation() {
printf '\n $ ./openvidu start' printf '\n $ ./openvidu start'
printf '\n' printf '\n'
printf '\n For more information, check:' printf '\n For more information, check:'
printf "\n https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/deployment/deploying-on-premises/" printf '\n https://docs.openvidu.io/en/%s/deployment/deploying-on-premises/' "${OPENVIDU_VERSION//v}"
printf '\n' printf '\n'
printf '\n' printf '\n'
exit 0 exit 0
@ -104,7 +104,7 @@ upgrade_ov() {
[ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found" [ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found"
# Uppgrade Openvidu # Upgrade Openvidu
OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
[ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version" [ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version"
@ -116,7 +116,7 @@ upgrade_ov() {
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Upgrade Openvidu CE %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}" printf '\n Upgrade OpenVidu CE %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}"
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
@ -132,7 +132,7 @@ upgrade_ov() {
mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'" mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'"
# Download necessary files # Download necessary files
printf '\n => Downloading new Openvidu CE files:' printf '\n => Downloading new OpenVidu CE files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/ce/docker-compose/docker-compose.yml \ curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/ce/docker-compose/docker-compose.yml \
--output "${TMP_FOLDER}/docker-compose.yml" || fatal_error "Error when downloading the file 'docker-compose.yml'" --output "${TMP_FOLDER}/docker-compose.yml" || fatal_error "Error when downloading the file 'docker-compose.yml'"
@ -150,24 +150,24 @@ upgrade_ov() {
--output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'" --output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'"
printf '\n - openvidu' printf '\n - openvidu'
# Dowloading new images and stoped actual Openvidu # Downloading new images and stopped actual Openvidu
printf '\n => Dowloading new images...' printf '\n => Downloading new images...'
printf '\n' printf '\n'
sleep 1 sleep 1
printf "\n => Moving to 'tmp' folder..." printf "\n => Moving to 'tmp' folder..."
printf '\n' printf '\n'
cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder" cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder"
docker-compose pull | true docker-compose pull || true
printf '\n => Stoping Openvidu...' printf '\n => Stopping Openvidu...'
printf '\n' printf '\n'
sleep 1 sleep 1
printf "\n => Moving to 'openvidu' folder..." printf "\n => Moving to 'openvidu' folder..."
printf '\n' printf '\n'
cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder" cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder"
docker-compose down | true docker-compose down || true
printf '\n' printf '\n'
printf '\n => Moving to working dir...' printf '\n => Moving to working dir...'
@ -179,7 +179,7 @@ upgrade_ov() {
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'" mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'"
printf '\n - docker-compose.yml' printf '\n - docker-compose.yml'
if [ ! -z "${USE_OV_CALL}" ]; then if [ -n "${USE_OV_CALL}" ]; then
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.override.yml'" mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml' printf '\n - docker-compose.override.yml'
fi fi
@ -201,7 +201,7 @@ upgrade_ov() {
mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'" mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'"
printf '\n - docker-compose.yml' printf '\n - docker-compose.yml'
if [ ! -z "${USE_OV_CALL}" ]; then if [ -n "${USE_OV_CALL}" ]; then
mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.override.yml'" mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml' printf '\n - docker-compose.override.yml'
else else
@ -224,7 +224,7 @@ upgrade_ov() {
# Define old mode: On Premise or Cloud Formation # Define old mode: On Premise or Cloud Formation
OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }') OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }')
[ ! -z "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" [ -n "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml"
# Ready to use # Ready to use
printf '\n' printf '\n'
@ -249,8 +249,8 @@ upgrade_ov() {
printf "\n If you want to rollback, all the files from the previous installation have been copied to folder '.old-%s'" "${OPENVIDU_PREVIOUS_VERSION}" printf "\n If you want to rollback, all the files from the previous installation have been copied to folder '.old-%s'" "${OPENVIDU_PREVIOUS_VERSION}"
printf '\n' printf '\n'
printf '\n For more information, check:' printf '\n For more information, check:'
printf "\n https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/deployment/deploying-on-premises/" printf '\n https://docs.openvidu.io/en/%s/deployment/deploying-on-premises/' "${OPENVIDU_VERSION//v}"
printf "\n https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/deployment/upgrading/" printf '\n https://docs.openvidu.io/en/%s/deployment/upgrading/' "${OPENVIDU_VERSION//v}"
printf '\n' printf '\n'
printf '\n' printf '\n'
} }
@ -273,7 +273,7 @@ else
fi fi
# Check type of installation # Check type of installation
if [[ ! -z "$1" && "$1" == "upgrade" ]]; then if [[ -n "$1" && "$1" == "upgrade" ]]; then
upgrade_ov upgrade_ov
else else
new_ov_installation new_ov_installation

View File

@ -4,7 +4,7 @@ upgrade_ov() {
UPGRADE_SCRIPT_URL="https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_OVVERSION.sh" UPGRADE_SCRIPT_URL="https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_OVVERSION.sh"
HTTP_STATUS=$(curl -s -o /dev/null -I -w "%{http_code}" ${UPGRADE_SCRIPT_URL//OVVERSION/$1}) HTTP_STATUS=$(curl -s -o /dev/null -I -w "%{http_code}" ${UPGRADE_SCRIPT_URL//OVVERSION/$1})
printf " => Upgrading Openvidu CE to '%s' version" "$1" printf " => Upgrading OpenVidu CE to '%s' version" "$1"
if [ "$HTTP_STATUS" == "200" ]; then if [ "$HTTP_STATUS" == "200" ]; then
printf "\n => Downloading and upgrading new version" printf "\n => Downloading and upgrading new version"
@ -13,7 +13,7 @@ upgrade_ov() {
curl --silent ${UPGRADE_SCRIPT_URL//OVVERSION/$1} | bash -s upgrade curl --silent ${UPGRADE_SCRIPT_URL//OVVERSION/$1} | bash -s upgrade
else else
printf "\n =======¡ERROR!=======" printf "\n =======¡ERROR!======="
printf "\n Openvidu CE Version '%s' not exist" "$1" printf "\n OpenVidu CE Version '%s' not exist" "$1"
printf "\n" printf "\n"
exit 0 exit 0
fi fi
@ -28,7 +28,7 @@ collect_basic_information() {
OV_VERSION=$(grep 'Openvidu Version:' "${OV_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OV_VERSION=$(grep 'Openvidu Version:' "${OV_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}') CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}')
if [ ! -z "$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OV_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')" ]; then if [ -n "$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OV_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')" ]; then
OV_CALL_VERSION=$(grep -E 'Openvidu-Call Version:' "${OV_FOLDER}/docker-compose.override.yml" | awk '{ print $4 }') OV_CALL_VERSION=$(grep -E 'Openvidu-Call Version:' "${OV_FOLDER}/docker-compose.override.yml" | awk '{ print $4 }')
fi fi
[ -z "${OV_CALL_VERSION}" ] && OV_CALL_VERSION="No present" [ -z "${OV_CALL_VERSION}" ] && OV_CALL_VERSION="No present"
@ -71,7 +71,7 @@ generate_report() {
REPORT_CREATION_DATE=$(date +"%d-%m-%Y") REPORT_CREATION_DATE=$(date +"%d-%m-%Y")
REPORT_CREATION_TIME=$(date +"%H:%M:%S") REPORT_CREATION_TIME=$(date +"%H:%M:%S")
REPORT_NAME="openvidu-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt" REPORT_NAME="openvidu-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt"
REPORT_OUPUT="${OV_FOLDER}/${REPORT_NAME}" REPORT_OUTPUT="${OV_FOLDER}/${REPORT_NAME}"
{ {
printf "\n =======================================" printf "\n ======================================="
@ -150,10 +150,10 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker logs $CONTAINER docker logs "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
@ -177,19 +177,19 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n =======================================" printf "\n ======================================="
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker exec $CONTAINER env docker exec "$CONTAINER" env
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
done done
} >> "${REPORT_OUPUT}" 2>&1 } >> "${REPORT_OUTPUT}" 2>&1
printf "\n Generation of the report completed with success" printf "\n Generation of the report completed with success"
printf "\n You can get your report at path '%s'" "${REPORT_OUPUT}" printf "\n You can get your report at path '%s'" "${REPORT_OUTPUT}"
printf "\n" printf "\n"
} }
@ -198,10 +198,10 @@ usage() {
printf "\n\nAvailable Commands:" printf "\n\nAvailable Commands:"
printf "\n\tstart\t\t\tStart all services" printf "\n\tstart\t\t\tStart all services"
printf "\n\tstop\t\t\tStop all services" printf "\n\tstop\t\t\tStop all services"
printf "\n\trestart\t\t\tRestart all stoped and running services" printf "\n\trestart\t\t\tRestart all stopped and running services"
printf "\n\tlogs [-f]\t\tShow openvidu logs." printf "\n\tlogs [-f]\t\tShow openvidu logs."
printf "\n\tkms-logs [-f]\t\tShow kms logs" printf "\n\tkms-logs [-f]\t\tShow kms logs"
printf "\n\tupgrade\t\t\tUpgrade to the lastest Openvidu version" printf "\n\tupgrade\t\t\tUpgrade to the latest Openvidu version"
printf "\n\tupgrade [version]\tUpgrade to the specific Openvidu version" printf "\n\tupgrade [version]\tUpgrade to the specific Openvidu version"
printf "\n\tversion\t\t\tShow version of Openvidu Server" printf "\n\tversion\t\t\tShow version of Openvidu Server"
printf "\n\treport\t\t\tGenerate a report with the current status of Openvidu" printf "\n\treport\t\t\tGenerate a report with the current status of Openvidu"
@ -245,7 +245,7 @@ case $1 in
;; ;;
kms-logs) kms-logs)
kurento_logs $2 kurento_logs "$2"
;; ;;
upgrade) upgrade)
@ -255,7 +255,7 @@ case $1 in
UPGRADE_VERSION="$2" UPGRADE_VERSION="$2"
fi fi
read -r -p " You're about to update Openvidu CE to '${UPGRADE_VERSION}' version. Are you sure? [y/N]: " response read -r -p " You're about to update OpenVidu CE to '${UPGRADE_VERSION}' version. Are you sure? [y/N]: " response
case "$response" in case "$response" in
[yY][eE][sS]|[yY]) [yY][eE][sS]|[yY])
upgrade_ov "${UPGRADE_VERSION}" upgrade_ov "${UPGRADE_VERSION}"

View File

@ -120,11 +120,11 @@ Resources:
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource KurentoMediaServer cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource KurentoMediaServer
/usr/local/bin/installDockerAndDockerCompose.sh || { echo "[Openvidu] error installing software"; exit 1; } /usr/local/bin/installDockerAndDockerCompose.sh || { echo "[OpenVidu] error installing software"; exit 1; }
/usr/local/bin/installMediaNode.sh || { echo "[Openvidu] error installing Media Node"; exit 1; } /usr/local/bin/installMediaNode.sh || { echo "[OpenVidu] error installing Media Node"; exit 1; }
/usr/local/bin/runMediaNode.sh || { echo "[Openvidu] error running Media Node"; exit 1; } /usr/local/bin/runMediaNode.sh || { echo "[OpenVidu] error running Media Node"; exit 1; }
# sending the finish call # sending the finish call
/usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region} /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}

View File

@ -91,7 +91,7 @@ Resources:
# Openvidu recording # Openvidu recording
docker pull openvidu/openvidu-recording:OPENVIDU_RECORDING_DOCKER_TAG docker pull openvidu/openvidu-recording:OPENVIDU_RECORDING_DOCKER_TAG
# Openvidu PRO images # OpenVidu Pro images
cd /opt/openvidu cd /opt/openvidu
docker-compose pull docker-compose pull
mode: "000755" mode: "000755"
@ -122,11 +122,11 @@ Resources:
cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenviduServerPro cfn-init --region ${AWS::Region} --stack ${AWS::StackId} --resource OpenviduServerPro
/usr/local/bin/installDockerAndDockerCompose.sh || { echo "[Openvidu] error installing docker and compose"; exit 1; } /usr/local/bin/installDockerAndDockerCompose.sh || { echo "[OpenVidu] error installing docker and compose"; exit 1; }
/usr/local/bin/installOpenviduServerPRO.sh || { echo "[Openvidu] error installing Openvidu Server PRO"; exit 1; } /usr/local/bin/installOpenviduServerPRO.sh || { echo "[OpenVidu] error installing Openvidu Server PRO"; exit 1; }
/usr/local/bin/getDockerImages.sh || { echo "[Openvidu] error getting docker images"; exit 1; } /usr/local/bin/getDockerImages.sh || { echo "[OpenVidu] error getting docker images"; exit 1; }
# sending the finish call # sending the finish call
/usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region} /usr/local/bin/cfn-signal -e $? --stack ${AWS::StackId} --resource WaitCondition --region ${AWS::Region}

View File

@ -1,6 +1,6 @@
--- ---
AWSTemplateFormatVersion: 2010-09-09 AWSTemplateFormatVersion: 2010-09-09
Description: Openvidu Pro CloudFormation template Description: OpenVidu Pro CloudFormation template
Parameters: Parameters:
@ -483,13 +483,13 @@ Resources:
echo $PublicHostname > /usr/share/openvidu/old-host-name echo $PublicHostname > /usr/share/openvidu/old-host-name
fi fi
# Openvidu Pro mode # OpenVidu Pro mode
sed -i "s/OPENVIDU_PRO_CLUSTER_MODE=manual/OPENVIDU_PRO_CLUSTER_MODE=auto/" $WORKINGDIR/.env sed -i "s/OPENVIDU_PRO_CLUSTER_MODE=manual/OPENVIDU_PRO_CLUSTER_MODE=auto/" $WORKINGDIR/.env
# Openvidu Pro Media Nodes # OpenVidu Pro Media Nodes
sed -i "s/#OPENVIDU_PRO_CLUSTER_MEDIA_NODES=/OPENVIDU_PRO_CLUSTER_MEDIA_NODES=${MediaNodesStartNumber}/" $WORKINGDIR/.env sed -i "s/#OPENVIDU_PRO_CLUSTER_MEDIA_NODES=/OPENVIDU_PRO_CLUSTER_MEDIA_NODES=${MediaNodesStartNumber}/" $WORKINGDIR/.env
# Openvidu Pro enviroment # OpenVidu Pro enviroment
sed -i "s/OPENVIDU_PRO_CLUSTER_ENVIRONMENT=on_premise/OPENVIDU_PRO_CLUSTER_ENVIRONMENT=aws/" $WORKINGDIR/.env sed -i "s/OPENVIDU_PRO_CLUSTER_ENVIRONMENT=on_premise/OPENVIDU_PRO_CLUSTER_ENVIRONMENT=aws/" $WORKINGDIR/.env
# Replace certificated type # Replace certificated type
@ -507,15 +507,18 @@ Resources:
sed -i "s/ELASTICSEARCH_PASSWORD=/ELASTICSEARCH_PASSWORD=${ElasticsearchPassword}/" $WORKINGDIR/.env sed -i "s/ELASTICSEARCH_PASSWORD=/ELASTICSEARCH_PASSWORD=${ElasticsearchPassword}/" $WORKINGDIR/.env
# Replace vars AWS # Replace vars AWS
INSTANCE_ID=$(curl http://169.254.169.254/latest/meta-data/instance-id)
sed -i "s/#AWS_DEFAULT_REGION=/AWS_DEFAULT_REGION=${AWS::Region}/" $WORKINGDIR/.env sed -i "s/#AWS_DEFAULT_REGION=/AWS_DEFAULT_REGION=${AWS::Region}/" $WORKINGDIR/.env
sed -i "s/#AWS_IMAGE_ID=/AWS_IMAGE_ID=${kmsAmi}/" $WORKINGDIR/.env sed -i "s/#AWS_IMAGE_ID=/AWS_IMAGE_ID=${kmsAmi}/" $WORKINGDIR/.env
sed -i "s/#AWS_INSTANCE_TYPE=/AWS_INSTANCE_TYPE=${AwsInstanceTypeKMS}/" $WORKINGDIR/.env sed -i "s/#AWS_INSTANCE_TYPE=/AWS_INSTANCE_TYPE=${AwsInstanceTypeKMS}/" $WORKINGDIR/.env
sed -i "s/#AWS_INSTANCE_ID=/AWS_INSTANCE_ID=$INSTANCE_ID/" $WORKINGDIR/.env
sed -i "s/#AWS_KEY_NAME=/AWS_KEY_NAME=${KeyName}/" $WORKINGDIR/.env sed -i "s/#AWS_KEY_NAME=/AWS_KEY_NAME=${KeyName}/" $WORKINGDIR/.env
sed -i "s/#AWS_SUBNET_ID=/AWS_SUBNET_ID=${OpenViduSubnet}/" $WORKINGDIR/.env sed -i "s/#AWS_SUBNET_ID=/AWS_SUBNET_ID=${OpenViduSubnet}/" $WORKINGDIR/.env
sed -i "s/#AWS_STACK_ID=/AWS_STACK_ID=$(echo ${AWS::StackId} | sed 's#/#\\/#g')/" $WORKINGDIR/.env sed -i "s/#AWS_STACK_ID=/AWS_STACK_ID=$(echo ${AWS::StackId} | sed 's#/#\\/#g')/" $WORKINGDIR/.env
sed -i "s/#AWS_STACK_NAME=/AWS_STACK_NAME=${AWS::StackName}/" $WORKINGDIR/.env sed -i "s/#AWS_STACK_NAME=/AWS_STACK_NAME=${AWS::StackName}/" $WORKINGDIR/.env
sed -i "s/#AWS_CLI_DOCKER_TAG=/AWS_CLI_DOCKER_TAG=AWS_DOCKER_TAG/" $WORKINGDIR/.env sed -i "s/#AWS_CLI_DOCKER_TAG=/AWS_CLI_DOCKER_TAG=AWS_DOCKER_TAG/" $WORKINGDIR/.env
sed -i "s/#AWS_VOLUME_SIZE=/AWS_VOLUME_SIZE=50/" $WORKINGDIR/.env sed -i "s/#AWS_VOLUME_SIZE=/AWS_VOLUME_SIZE=50/" $WORKINGDIR/.env
sed -i "s/#OPENVIDU_PRO_AWS_REGION=/OPENVIDU_PRO_AWS_REGION=${AWS::Region}/" $WORKINGDIR/.env
# Get security group id of kms and use it as env variable # Get security group id of kms and use it as env variable
SECGRPIDKMS=$(/usr/local/bin/getSecurityGroupKms.sh) SECGRPIDKMS=$(/usr/local/bin/getSecurityGroupKms.sh)
@ -647,17 +650,17 @@ Resources:
export HOME="/root" export HOME="/root"
# Replace .env variables # Replace .env variables
/usr/local/bin/feedGroupVars.sh || { echo "[Openvidu] Parameters incorrect/insufficient"; exit 1; } /usr/local/bin/feedGroupVars.sh || { echo "[OpenVidu] Parameters incorrect/insufficient"; exit 1; }
# Create security groups # Create security groups
/usr/local/bin/create_security_group_rules.sh || { echo "[Openvidu] Error creating security groups"; exit 1; } /usr/local/bin/create_security_group_rules.sh || { echo "[OpenVidu] Error creating security groups"; exit 1; }
# Launch on reboot # Launch on reboot
echo "@reboot /usr/local/bin/restartPRO.sh" | crontab echo "@reboot /usr/local/bin/restartPRO.sh" | crontab
# Download certs if "WichCert" mode # Download certs if "WichCert" mode
if [ "${WhichCert}" == "owncert" ]; then if [ "${WhichCert}" == "owncert" ]; then
/usr/local/bin/buildCerts.sh || { echo "[Openvidu] error with the certificate files"; exit 1; } /usr/local/bin/buildCerts.sh || { echo "[OpenVidu] error with the certificate files"; exit 1; }
fi fi
# Start openvidu application # Start openvidu application
@ -690,23 +693,43 @@ Resources:
FromPort: 22 FromPort: 22
ToPort: 22 ToPort: 22
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIpv6: ::/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 40000 FromPort: 40000
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 40000
ToPort: 65535
CidrIp: 0.0.0.0/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 40000 FromPort: 40000
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIpv6: ::/0
SecurityGroupEgress: SecurityGroupEgress:
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 1 FromPort: 1
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 1 FromPort: 1
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
OpenViduSecurityGroup: OpenViduSecurityGroup:
Type: 'AWS::EC2::SecurityGroup' Type: 'AWS::EC2::SecurityGroup'
@ -719,39 +742,75 @@ Resources:
FromPort: 22 FromPort: 22
ToPort: 22 ToPort: 22
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIpv6: ::/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 80 FromPort: 80
ToPort: 80 ToPort: 80
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIpv6: ::/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 443 FromPort: 443
ToPort: 443 ToPort: 443
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 3478
ToPort: 3478
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 3478
ToPort: 3478
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 3478 FromPort: 3478
ToPort: 3478 ToPort: 3478
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 3478 FromPort: 3478
ToPort: 3478 ToPort: 3478
CidrIpv6: ::/0
- IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 40000
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: tcp
FromPort: 40000 FromPort: 40000
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 40000 FromPort: 40000
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIpv6: ::/0
SecurityGroupEgress: SecurityGroupEgress:
- IpProtocol: tcp - IpProtocol: tcp
FromPort: 1 FromPort: 1
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
- IpProtocol: udp - IpProtocol: udp
FromPort: 1 FromPort: 1
ToPort: 65535 ToPort: 65535
CidrIp: 0.0.0.0/0 CidrIp: 0.0.0.0/0
- IpProtocol: udp
FromPort: 1
ToPort: 65535
CidrIpv6: ::/0
WaitCondition: WaitCondition:
Type: AWS::CloudFormation::WaitCondition Type: AWS::CloudFormation::WaitCondition

View File

@ -9,7 +9,7 @@
# For example: 198.51.100.1, or openvidu.example.com # For example: 198.51.100.1, or openvidu.example.com
DOMAIN_OR_PUBLIC_IP= DOMAIN_OR_PUBLIC_IP=
# OpenVidu PRO License # OpenVidu Pro License
OPENVIDU_PRO_LICENSE= OPENVIDU_PRO_LICENSE=
# OpenVidu SECRET used for apps to connect to OpenVidu server and users to access to OpenVidu Dashboard # OpenVidu SECRET used for apps to connect to OpenVidu server and users to access to OpenVidu Dashboard
@ -110,7 +110,7 @@ OPENVIDU_PRO_CLUSTER_ENVIRONMENT=on_premise
# Whether to enable or disable autoscaling. With autoscaling the number of Media Nodes will # Whether to enable or disable autoscaling. With autoscaling the number of Media Nodes will
# be automatically adjusted according to existing load # be automatically adjusted according to existing load
# Values: true | false # Values: true | false
# OPENVIDU_PRO_CLUSTER_AUTOSCALING=false OPENVIDU_PRO_CLUSTER_AUTOSCALING=false
# How often the autoscaling algorithm runs, in seconds # How often the autoscaling algorithm runs, in seconds
# Type number >= 0 # Type number >= 0
@ -188,7 +188,7 @@ OPENVIDU_PRO_CLUSTER_LOAD_STRATEGY=streams
# AWS region in which the S3 bucket is located (e.g. eu-west-1). If not provided, # AWS region in which the S3 bucket is located (e.g. eu-west-1). If not provided,
# the region will try to be discovered automatically, although this is not always possible. # the region will try to be discovered automatically, although this is not always possible.
# This property is only taken into account if OPENVIDU_PRO_RECORDING_STORAGE=s3 # This property is only taken into account if OPENVIDU_PRO_RECORDING_STORAGE=s3
# OPENVIDU_PRO_AWS_REGION= #OPENVIDU_PRO_AWS_REGION=
# Whether to enable recording module or not # Whether to enable recording module or not
OPENVIDU_RECORDING=false OPENVIDU_RECORDING=false
@ -250,6 +250,10 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
# Default value is false # Default value is false
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false # OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
# Send openvidu-browser logs of clients to Elasticsearch
# Default value is 'disabled'
# OPENVIDU_BROWSER_LOGS=disabled
# true to enable OpenVidu Webhook service. false' otherwise # true to enable OpenVidu Webhook service. false' otherwise
# Values: true | false # Values: true | false
OPENVIDU_WEBHOOK=false OPENVIDU_WEBHOOK=false
@ -263,7 +267,7 @@ OPENVIDU_WEBHOOK=false
# List of events that will be sent by OpenVidu Webhook service # List of events that will be sent by OpenVidu Webhook service
# Default value is all available events # Default value is all available events
OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged] OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged,nodeCrashed]
# How often the garbage collector of non active sessions runs. # How often the garbage collector of non active sessions runs.
# This helps cleaning up sessions that have been initialized through # This helps cleaning up sessions that have been initialized through
@ -324,7 +328,7 @@ ELASTICSEARCH_PASSWORD=
# Uncomment the next line and define this variable with KMS image that you want use # Uncomment the next line and define this variable with KMS image that you want use
# By default, KMS_IMAGE is defined in media nodes and it does not need to be specified unless # By default, KMS_IMAGE is defined in media nodes and it does not need to be specified unless
# you want to use a specific version of KMS # you want to use a specific version of KMS
# KMS_IMAGE=kurento/kurento-media-server:6.15.0 # KMS_IMAGE=kurento/kurento-media-server:6.16.0
# Uncomment the next line and define this variable to change # Uncomment the next line and define this variable to change
# the verbosity level of the logs of KMS # the verbosity level of the logs of KMS
@ -337,6 +341,7 @@ ELASTICSEARCH_PASSWORD=
#AWS_DEFAULT_REGION= #AWS_DEFAULT_REGION=
#AWS_IMAGE_ID= #AWS_IMAGE_ID=
#AWS_INSTANCE_TYPE= #AWS_INSTANCE_TYPE=
#AWS_INSTANCE_ID=
#AWS_KEY_NAME= #AWS_KEY_NAME=
#AWS_SUBNET_ID= #AWS_SUBNET_ID=
#AWS_SECURITY_GROUP= #AWS_SECURITY_GROUP=
@ -350,3 +355,4 @@ ELASTICSEARCH_PASSWORD=
RM_REDIS_IP= RM_REDIS_IP=
RM_REDIS_PORT= RM_REDIS_PORT=
RM_SQS_QUEUE= RM_SQS_QUEUE=
RM_CLOUDFORMATION_ARN=

View File

@ -2,6 +2,11 @@ filebeat.inputs:
- type: container - type: container
paths: paths:
- '/var/lib/docker/containers/*/*.log' - '/var/lib/docker/containers/*/*.log'
fields:
cluster_id: ${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}
node_id: master_${AWS_INSTANCE_ID:${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}}
node_role: masternode
fields_under_root: true
processors: processors:
- add_docker_metadata: - add_docker_metadata:
@ -25,9 +30,8 @@ processors:
container.image.name: docker.elastic.co/beats/filebeat-oss container.image.name: docker.elastic.co/beats/filebeat-oss
- contains: - contains:
container.image.name: docker.elastic.co/beats/metricbeat-oss container.image.name: docker.elastic.co/beats/metricbeat-oss
- add_fields: - contains:
fields: container.image.name: openvidu/openvidu-server-pro
cluster-id: ${OPENVIDU_PRO_CLUSTER_ID:undefined}
output: output:
elasticsearch: elasticsearch:
@ -41,7 +45,10 @@ output:
when.or: when.or:
- contains: - contains:
container.image.name: openvidu/openvidu-proxy container.image.name: openvidu/openvidu-proxy
- index: "filebeat-openvidu-recording-%{+yyyy.MM.dd}"
when.or:
- contains:
container.image.name: openvidu/openvidu-recording
logging.json: true logging.json: true
logging.metrics.enabled: false logging.metrics.enabled: false

View File

@ -1,10 +1,40 @@
metricbeat.modules: metricbeat.modules:
- module: nginx - module: nginx
metricsets: ["stubstatus"] metricsets: ["stubstatus"]
enabled: true enabled: true
period: 10s period: ${OPENVIDU_PRO_STATS_MONITORING_INTERVAL}s
hosts: ["http://127.0.0.1"] hosts: ["http://127.0.0.1"]
server_status_path: "nginx_status" server_status_path: "nginx_status"
- module: system
metricsets:
- cpu
- diskio
- memory
- network
- filesystem
- fsstat
- process_summary
- uptime
filesystem.ignore_types: [nfs, smbfs, autofs, devtmpfs, devpts, hugetlbfs, tmpfs, sysfs, securityfs, cgroup2, cgroup, pstore, debugfs, configfs, fusectl, proc, fuse.lxcfs, squashfs]
processes: ['.*']
processors:
- drop_event:
when:
or:
- regexp:
system.network.name: '^(veth|lo|docker|br-)($|)'
- regexp:
system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host)($|/)'
- regexp:
system.filesystem.mount_point: '^/hostfs/(sys|cgroup|proc|dev|etc|host)($|/)'
enabled: true
period: ${OPENVIDU_PRO_STATS_MONITORING_INTERVAL}s
cpu.metrics: [normalized_percentages]
fields:
cluster_id: ${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}
node_id: master_${AWS_INSTANCE_ID:${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}}
node_role: masternode
output: output:
elasticsearch: elasticsearch:
hosts: ["${OPENVIDU_PRO_ELASTICSEARCH_HOST}"] hosts: ["${OPENVIDU_PRO_ELASTICSEARCH_HOST}"]
setup.ilm.enabled: false

View File

@ -35,6 +35,14 @@ exit_on_error () {
esac esac
} }
# Check custom parameters
if [[ -n "${CUSTOM_INSTANCE_TYPE}" ]]; then
AWS_INSTANCE_TYPE="${CUSTOM_INSTANCE_TYPE}"
fi
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 \ docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 run-instances \
--image-id ${AWS_IMAGE_ID} --count 1 \ --image-id ${AWS_IMAGE_ID} --count 1 \
--instance-type ${AWS_INSTANCE_TYPE} \ --instance-type ${AWS_INSTANCE_TYPE} \
@ -51,18 +59,6 @@ docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 wait instance-running -
KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress') KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId') KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
# Wait media-node controller
attempt_counter=0
max_attempts=10
until $(curl --output /dev/null --silent --head --fail -u OPENVIDUAPP:${OPENVIDU_SECRET} http://${KMS_IP}:3000/media-node/status); do
if [ ${attempt_counter} -eq ${max_attempts} ];then
exit 1
fi
attempt_counter=$(($attempt_counter+1))
sleep 5
done
jq -n \ jq -n \
--arg id "${KMS_ID}" \ --arg id "${KMS_ID}" \
--arg ip "${KMS_IP}" \ --arg ip "${KMS_IP}" \

View File

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

View File

@ -11,7 +11,7 @@
# #
# This file will be overridden when update OpenVidu Platform # This file will be overridden when update OpenVidu Platform
# #
# Openvidu Version: 2.16.0 # Openvidu Version: 2.17.0
# #
# Installation Mode: On Premises # Installation Mode: On Premises
# #
@ -22,7 +22,7 @@ version: '3.1'
services: services:
openvidu-server: openvidu-server:
image: openvidu/openvidu-server-pro:2.17.0-dev5 image: openvidu/openvidu-server-pro:2.18.0-beta17
restart: on-failure restart: on-failure
network_mode: host network_mode: host
entrypoint: ['/usr/local/bin/entrypoint.sh'] entrypoint: ['/usr/local/bin/entrypoint.sh']
@ -41,7 +41,7 @@ services:
- KMS_URIS=[] - KMS_URIS=[]
- OPENVIDU_WEBHOOK=false - OPENVIDU_WEBHOOK=false
- OPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook - OPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook
- OPENVIDU_PRO_REPLICATION_MANAGER_WEBHOOK=http://127.0.0.1:4443/openvidu/replication-manager-webhook?OPENVIDU_SECRET=${OPENVIDU_SECRET} - MULTI_MASTER_REPLICATION_MANAGER_WEBHOOK=http://127.0.0.1:4443/openvidu/replication-manager-webhook?OPENVIDU_SECRET=${OPENVIDU_SECRET}
- COTURN_REDIS_IP=127.0.0.1 - COTURN_REDIS_IP=127.0.0.1
- COTURN_REDIS_PASSWORD=${OPENVIDU_SECRET} - COTURN_REDIS_PASSWORD=${OPENVIDU_SECRET}
- COTURN_IP=${COTURN_IP:-auto-ipv4} - COTURN_IP=${COTURN_IP:-auto-ipv4}
@ -55,8 +55,8 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
replication-manager: replication-manager:
image: openvidu/replication-manager:1.0.0-dev5 image: openvidu/replication-manager:1.0.0-dev6
restart: on-failure restart: always
network_mode: host network_mode: host
environment: environment:
- SERVER_PORT=4443 - SERVER_PORT=4443
@ -66,15 +66,17 @@ services:
- REDIS_HOST=${RM_REDIS_IP} - REDIS_HOST=${RM_REDIS_IP}
- REDIS_PORT=${RM_REDIS_PORT} - REDIS_PORT=${RM_REDIS_PORT}
- REDIS_PASS=${OPENVIDU_SECRET} - REDIS_PASS=${OPENVIDU_SECRET}
- RM_CLOUDFORMATION_ARN=${RM_CLOUDFORMATION_ARN}
- REDIS_TIMEOUT=5 - REDIS_TIMEOUT=5
- REDIS_DB=replicationmanager - REDIS_DB=replicationmanager
- MEDIANODE_AS_NOTIFICATION_QUEUE=${RM_SQS_QUEUE} - MEDIANODE_AS_NOTIFICATION_QUEUE=${RM_SQS_QUEUE}
- AWS_INSTANCE_ID=${AWS_INSTANCE_ID}
logging: logging:
options: options:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
redis: redis:
image: openvidu/openvidu-redis:2.0.0-dev2 image: openvidu/openvidu-redis:2.0.0
restart: always restart: always
network_mode: host network_mode: host
environment: environment:
@ -84,18 +86,23 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
coturn: coturn:
image: openvidu/openvidu-coturn:3.0.0-dev2 image: openvidu/openvidu-coturn:4.0.0-dev2
restart: on-failure restart: on-failure
network_mode: host network_mode: host
environment: environment:
- REDIS_IP=127.0.0.1 - REDIS_IP=127.0.0.1
- TURN_LISTEN_PORT=3478
- DB_NAME=0 - DB_NAME=0
- DB_PASSWORD=${OPENVIDU_SECRET} - DB_PASSWORD=${OPENVIDU_SECRET}
- MIN_PORT=40000 command:
- MAX_PORT=65535 - --log-file=stdout
- TURN_PUBLIC_IP=${TURN_PUBLIC_IP:-auto-ipv4} - --external-ip=$$(detect-external-ip)
- ENABLE_COTURN_LOGS=true - --listening-port=3478
- --fingerprint
- --lt-cred-mech
- --min-port=40000
- --max-port=65535
- --realm=openvidu
- --verbose
logging: logging:
options: options:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
@ -109,10 +116,15 @@ services:
- .env - .env
environment: environment:
- OPENVIDU_PRO_ELASTICSEARCH_HOST=${OPENVIDU_PRO_ELASTICSEARCH_HOST:-http://127.0.0.1:9200} - OPENVIDU_PRO_ELASTICSEARCH_HOST=${OPENVIDU_PRO_ELASTICSEARCH_HOST:-http://127.0.0.1:9200}
- OPENVIDU_PRO_STATS_MONITORING_INTERVAL=${OPENVIDU_PRO_STATS_MONITORING_INTERVAL:-10}
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./beats/metricbeat.yml:/usr/share/metricbeat/metricbeat.yml:ro - ./beats/metricbeat.yml:/usr/share/metricbeat/metricbeat.yml:ro
- /proc:/hostfs/proc:ro
- /sys/fs/cgroup:/hostfs/sys/fs/cgroup:ro
- /:/hostfs:ro
command: > command: >
/bin/bash -c "metricbeat -e -strict.perms=false /bin/bash -c "metricbeat -e -strict.perms=false -e -system.hostfs=/hostfs
`if [ ! -z $ELASTICSEARCH_USERNAME ]; then echo '-E output.elasticsearch.username=$ELASTICSEARCH_USERNAME'; fi` `if [ ! -z $ELASTICSEARCH_USERNAME ]; then echo '-E output.elasticsearch.username=$ELASTICSEARCH_USERNAME'; fi`
`if [ ! -z $ELASTICSEARCH_PASSWORD ]; then echo '-E output.elasticsearch.password=$ELASTICSEARCH_PASSWORD'; fi`" `if [ ! -z $ELASTICSEARCH_PASSWORD ]; then echo '-E output.elasticsearch.password=$ELASTICSEARCH_PASSWORD'; fi`"
logging: logging:

View File

@ -19,7 +19,7 @@ fatal_error() {
new_ov_installation() { new_ov_installation() {
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Install Openvidu PRO %s' "${OPENVIDU_VERSION}" printf '\n Install OpenVidu Pro %s' "${OPENVIDU_VERSION}"
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
@ -43,7 +43,7 @@ new_ov_installation() {
chown 1000:1000 "${ELASTICSEARCH_FOLDER}" || fatal_error "Error while changing permission to 'elasticsearch' folder" chown 1000:1000 "${ELASTICSEARCH_FOLDER}" || fatal_error "Error while changing permission to 'elasticsearch' folder"
# Download necessary files # Download necessary files
printf '\n => Downloading Openvidu PRO files:' printf '\n => Downloading OpenVidu Pro files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \ curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \
--output "${AWS_SCRIPTS_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'" --output "${AWS_SCRIPTS_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'"
@ -108,7 +108,7 @@ new_ov_installation() {
printf '\n' printf '\n'
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Openvidu PRO successfully installed.' printf '\n OpenVidu Pro successfully installed.'
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
printf '\n 1. Go to openvidu folder:' printf '\n 1. Go to openvidu folder:'
@ -124,12 +124,23 @@ new_ov_installation() {
printf "\n CAUTION: The folder 'openvidu/elasticsearch' use user and group 1000 permissions. " printf "\n CAUTION: The folder 'openvidu/elasticsearch' use user and group 1000 permissions. "
printf "\n This folder is necessary for store elasticsearch data." printf "\n This folder is necessary for store elasticsearch data."
printf "\n For more information, check:" printf "\n For more information, check:"
printf "\n https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/openvidu-pro/deployment/on-premises/#deployment-instructions" printf '\n https://docs.openvidu.io/en/%s/openvidu-pro/deployment/on-premises/#deployment-instructions' "${OPENVIDU_VERSION//v}"
printf '\n' printf '\n'
printf '\n' printf '\n'
exit 0 exit 0
} }
get_previous_env_variable() {
local ENV_VARIABLE_NAME=$1
echo "$(grep -E "${ENV_VARIABLE_NAME}=.*$" "${OPENVIDU_PREVIOUS_FOLDER}/.env" | cut -d'=' -f2)"
}
replace_variable_in_new_env_file() {
local ENV_VARIABLE_NAME=$1
local ENV_VARIABLE_VALUE=$2
[[ -n "${ENV_VARIABLE_VALUE}" ]] && sed -i "s|#${ENV_VARIABLE_NAME}=|${ENV_VARIABLE_NAME}=${ENV_VARIABLE_VALUE}|" "${OPENVIDU_PREVIOUS_FOLDER}/.env-${OPENVIDU_VERSION}"
}
upgrade_ov() { upgrade_ov() {
# Search local Openvidu installation # Search local Openvidu installation
printf '\n' printf '\n'
@ -156,7 +167,7 @@ upgrade_ov() {
[ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found" [ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found"
# Uppgrade Openvidu # Upgrade Openvidu
OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
[ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version" [ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version"
@ -168,7 +179,7 @@ upgrade_ov() {
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Upgrade Openvidu PRO %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}" printf '\n Upgrade OpenVidu Pro %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}"
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
@ -184,7 +195,7 @@ upgrade_ov() {
mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'" mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'"
# Download necessary files # Download necessary files
printf '\n => Downloading new Openvidu PRO files:' printf '\n => Downloading new OpenVidu Pro files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \ curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/aws-asg-openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \
--output "${TMP_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'" --output "${TMP_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'"
@ -222,8 +233,8 @@ upgrade_ov() {
--output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'" --output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'"
printf '\n - openvidu' printf '\n - openvidu'
# Dowloading new images and stoped actual Openvidu # Downloading new images and stopped actual Openvidu
printf '\n => Dowloading new images...' printf '\n => Downloading new images...'
printf '\n' printf '\n'
sleep 1 sleep 1
@ -231,9 +242,9 @@ upgrade_ov() {
printf '\n' printf '\n'
cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder" cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder"
printf '\n' printf '\n'
docker-compose pull | true docker-compose pull || true
printf '\n => Stoping Openvidu...' printf '\n => Stopping Openvidu...'
printf '\n' printf '\n'
sleep 1 sleep 1
@ -241,7 +252,7 @@ upgrade_ov() {
printf '\n' printf '\n'
cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder" cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder"
printf '\n' printf '\n'
docker-compose down | true docker-compose down || true
printf '\n' printf '\n'
printf '\n => Moving to working dir...' printf '\n => Moving to working dir...'
@ -253,7 +264,7 @@ upgrade_ov() {
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'" mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'"
printf '\n - docker-compose.yml' printf '\n - docker-compose.yml'
if [ ! -z "${USE_OV_CALL}" ]; then if [ -n "${USE_OV_CALL}" ]; then
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.override.yml'" mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml' printf '\n - docker-compose.override.yml'
fi fi
@ -281,7 +292,7 @@ upgrade_ov() {
mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'" mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'"
printf '\n - docker-compose.yml' printf '\n - docker-compose.yml'
if [ ! -z "${USE_OV_CALL}" ]; then if [ -n "${USE_OV_CALL}" ]; then
mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.override.yml'" mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml' printf '\n - docker-compose.override.yml'
else else
@ -334,16 +345,44 @@ upgrade_ov() {
# Define old mode: On Premise or Cloud Formation # Define old mode: On Premise or Cloud Formation
OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }') OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }')
[ ! -z "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" [ -n "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml"
# In Aws, update AMI ID # Update .env variables to new .env-version
AWS_REGION=$(grep -E "AWS_DEFAULT_REGION=.*$" "${OPENVIDU_PREVIOUS_FOLDER}/.env" | cut -d'=' -f2) AWS_REGION=$(get_previous_env_variable AWS_DEFAULT_REGION)
if [[ ! -z ${AWS_REGION} ]]; then if [[ -n ${AWS_REGION} ]]; then
# Get new AMI ID
NEW_AMI_ID=$(curl https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/CF-OpenVidu-Pro-${OPENVIDU_VERSION//v}.yaml --silent | NEW_AMI_ID=$(curl https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/CF-OpenVidu-Pro-${OPENVIDU_VERSION//v}.yaml --silent |
sed -n -e '/KMSAMIMAP:/,/Metadata:/ p' | sed -n -e '/KMSAMIMAP:/,/Metadata:/ p' |
grep -A 1 ${AWS_REGION} | grep AMI | tr -d " " | cut -d":" -f2) grep -A 1 "${AWS_REGION}" | grep AMI | tr -d " " | cut -d":" -f2)
[[ -z ${NEW_AMI_ID} ]] && fatal_error "Error while getting new AWS_IMAGE_ID for Media Nodes" [[ -z ${NEW_AMI_ID} ]] && fatal_error "Error while getting new AWS_IMAGE_ID for Media Nodes"
sed -i "s/.*AWS_IMAGE_ID=.*/AWS_IMAGE_ID=${NEW_AMI_ID}/" "${OPENVIDU_PREVIOUS_FOLDER}/.env" || fatal_error "Error while updating new AWS_IMAGE_ID for Media Nodes"
# Get previous values
PREV_AWS_DEFAULT_REGION=$(get_previous_env_variable AWS_DEFAULT_REGION)
PREV_AWS_INSTANCE_TYPE=$(get_previous_env_variable AWS_INSTANCE_TYPE)
PREV_AWS_INSTANCE_ID=$(get_previous_env_variable AWS_INSTANCE_ID)
PREV_AWS_KEY_NAME=$(get_previous_env_variable AWS_KEY_NAME)
PREV_AWS_SUBNET_ID=$(get_previous_env_variable AWS_SUBNET_ID)
PREV_AWS_SECURITY_GROUP=$(get_previous_env_variable AWS_SECURITY_GROUP)
PREV_AWS_STACK_ID=$(get_previous_env_variable AWS_STACK_ID)
PREV_AWS_STACK_NAME=$(get_previous_env_variable AWS_STACK_NAME)
PREV_AWS_CLI_DOCKER_TAG=$(get_previous_env_variable AWS_CLI_DOCKER_TAG)
PREV_AWS_VOLUME_SIZE=$(get_previous_env_variable AWS_VOLUME_SIZE)
# Replace variables in new .env-version file
replace_variable_in_new_env_file "AWS_DEFAULT_REGION" "${PREV_AWS_DEFAULT_REGION}"
replace_variable_in_new_env_file "AWS_INSTANCE_TYPE" "${PREV_AWS_INSTANCE_TYPE}"
replace_variable_in_new_env_file "AWS_INSTANCE_ID" "${PREV_AWS_INSTANCE_ID}"
replace_variable_in_new_env_file "AWS_KEY_NAME" "${PREV_AWS_KEY_NAME}"
replace_variable_in_new_env_file "AWS_SUBNET_ID" "${PREV_AWS_SUBNET_ID}"
replace_variable_in_new_env_file "AWS_SECURITY_GROUP" "${PREV_AWS_SECURITY_GROUP}"
replace_variable_in_new_env_file "AWS_STACK_ID" "${PREV_AWS_STACK_ID}"
replace_variable_in_new_env_file "AWS_STACK_NAME" "${PREV_AWS_STACK_NAME}"
replace_variable_in_new_env_file "AWS_CLI_DOCKER_TAG" "${PREV_AWS_CLI_DOCKER_TAG}"
replace_variable_in_new_env_file "AWS_VOLUME_SIZE" "${PREV_AWS_VOLUME_SIZE}"
# Replace new AMI
replace_variable_in_new_env_file "AWS_IMAGE_ID" "${NEW_AMI_ID}"
fi fi
@ -391,7 +430,7 @@ else
fi fi
# Check type of installation # Check type of installation
if [[ ! -z "$1" && "$1" == "upgrade" ]]; then if [[ -n "$1" && "$1" == "upgrade" ]]; then
upgrade_ov upgrade_ov
else else
new_ov_installation new_ov_installation

View File

@ -4,7 +4,7 @@ upgrade_ov() {
UPGRADE_SCRIPT_URL="https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_pro_OVVERSION.sh" UPGRADE_SCRIPT_URL="https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_pro_OVVERSION.sh"
HTTP_STATUS=$(curl -s -o /dev/null -I -w "%{http_code}" ${UPGRADE_SCRIPT_URL//OVVERSION/$1}) HTTP_STATUS=$(curl -s -o /dev/null -I -w "%{http_code}" ${UPGRADE_SCRIPT_URL//OVVERSION/$1})
printf " => Upgrading Openvidu PRO to '%s' version" "$1" printf " => Upgrading OpenVidu Pro to '%s' version" "$1"
if [ "$HTTP_STATUS" == "200" ]; then if [ "$HTTP_STATUS" == "200" ]; then
printf "\n => Downloading and upgrading new version" printf "\n => Downloading and upgrading new version"
@ -13,7 +13,7 @@ upgrade_ov() {
curl --silent ${UPGRADE_SCRIPT_URL//OVVERSION/$1} | bash -s upgrade curl --silent ${UPGRADE_SCRIPT_URL//OVVERSION/$1} | bash -s upgrade
else else
printf "\n =======¡ERROR!=======" printf "\n =======¡ERROR!======="
printf "\n Openvidu PRO Version '%s' not exist" "$1" printf "\n OpenVidu Pro Version '%s' not exist" "$1"
printf "\n" printf "\n"
exit 0 exit 0
fi fi
@ -28,7 +28,7 @@ collect_basic_information() {
OV_VERSION=$(grep 'Openvidu Version:' "${OV_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OV_VERSION=$(grep 'Openvidu Version:' "${OV_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}') CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}')
if [ ! -z "$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OV_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')" ]; then if [ -n "$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OV_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')" ]; then
OV_CALL_VERSION=$(grep -E 'Openvidu-Call Version:' "${OV_FOLDER}/docker-compose.override.yml" | awk '{ print $4 }') OV_CALL_VERSION=$(grep -E 'Openvidu-Call Version:' "${OV_FOLDER}/docker-compose.override.yml" | awk '{ print $4 }')
fi fi
[ -z "${OV_CALL_VERSION}" ] && OV_CALL_VERSION="No present" [ -z "${OV_CALL_VERSION}" ] && OV_CALL_VERSION="No present"
@ -71,7 +71,7 @@ generate_report() {
REPORT_CREATION_DATE=$(date +"%d-%m-%Y") REPORT_CREATION_DATE=$(date +"%d-%m-%Y")
REPORT_CREATION_TIME=$(date +"%H:%M:%S") REPORT_CREATION_TIME=$(date +"%H:%M:%S")
REPORT_NAME="openvidu-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt" REPORT_NAME="openvidu-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt"
REPORT_OUPUT="${OV_FOLDER}/${REPORT_NAME}" REPORT_OUTPUT="${OV_FOLDER}/${REPORT_NAME}"
{ {
printf "\n =======================================" printf "\n ======================================="
@ -150,10 +150,10 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker logs $CONTAINER docker logs "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
@ -167,19 +167,19 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n =======================================" printf "\n ======================================="
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker exec $CONTAINER env docker exec "$CONTAINER" env
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
done done
} >> "${REPORT_OUPUT}" 2>&1 } >> "${REPORT_OUTPUT}" 2>&1
printf "\n Generation of the report completed with success" printf "\n Generation of the report completed with success"
printf "\n You can get your report at path '%s'" "${REPORT_OUPUT}" printf "\n You can get your report at path '%s'" "${REPORT_OUTPUT}"
printf "\n" printf "\n"
} }
@ -224,9 +224,9 @@ usage() {
printf "\n\nAvailable Commands:" printf "\n\nAvailable Commands:"
printf "\n\tstart\t\t\tStart all services" printf "\n\tstart\t\t\tStart all services"
printf "\n\tstop\t\t\tStop all services" printf "\n\tstop\t\t\tStop all services"
printf "\n\trestart\t\t\tRestart all stoped and running services" printf "\n\trestart\t\t\tRestart all stopped and running services"
printf "\n\tlogs\t\t\tShow openvidu-server logs" printf "\n\tlogs\t\t\tShow openvidu-server logs"
printf "\n\tupgrade\t\t\tUpgrade to the lastest Openvidu version" printf "\n\tupgrade\t\t\tUpgrade to the latest Openvidu version"
printf "\n\tupgrade [version]\tUpgrade to the specific Openvidu version" printf "\n\tupgrade [version]\tUpgrade to the specific Openvidu version"
printf "\n\tversion\t\t\tShow version of Openvidu Server" printf "\n\tversion\t\t\tShow version of Openvidu Server"
printf "\n\treport\t\t\tGenerate a report with the current status of Openvidu" printf "\n\treport\t\t\tGenerate a report with the current status of Openvidu"
@ -268,7 +268,7 @@ case $1 in
UPGRADE_VERSION="$2" UPGRADE_VERSION="$2"
fi fi
read -r -p " You're about to update Openvidu PRO to '${UPGRADE_VERSION}' version. Are you sure? [y/N]: " response read -r -p " You're about to update OpenVidu Pro to '${UPGRADE_VERSION}' version. Are you sure? [y/N]: " response
case "$response" in case "$response" in
[yY][eE][sS]|[yY]) [yY][eE][sS]|[yY])
upgrade_ov "${UPGRADE_VERSION}" upgrade_ov "${UPGRADE_VERSION}"

View File

@ -2,16 +2,15 @@ filebeat.inputs:
- type: container - type: container
paths: paths:
- '/var/lib/docker/containers/*/*.log' - '/var/lib/docker/containers/*/*.log'
multiline.pattern: '^\d*:\d*:\d*'
multiline.negate: true
multiline.match: after
- type: log - type: log
paths: paths:
- /opt/openvidu/kurento-logs/*.log - /opt/openvidu/kurento-logs/*.log
fields: fields:
kurento-media-server: true kurento-media-server: true
ip: ${MEDIA_NODE_IP} ip: ${MEDIA_NODE_IP}
cluster-id: ${CLUSTER_ID} cluster_id: ${CLUSTER_ID}
node_id: ${NODE_ID}
node_role: medianode
fields_under_root: true fields_under_root: true
processors: processors:
@ -24,10 +23,12 @@ processors:
fields: ["message"] fields: ["message"]
target: "json" target: "json"
overwrite_keys: true overwrite_keys: true
- add_fields: - drop_event:
fields: when.or:
ip: ${MEDIA_NODE_IP} - contains:
cluster-id: ${CLUSTER_ID} container.image.name: docker.elastic.co/beats/filebeat-oss
- contains:
container.image.name: docker.elastic.co/beats/metricbeat-oss
output: output:
elasticsearch: elasticsearch:
@ -36,6 +37,14 @@ output:
when.or: when.or:
- equals: - equals:
kurento-media-server: true kurento-media-server: true
- index: "filebeat-media-node-controller-%{+yyyy.MM.dd}"
when.or:
- contains:
container.image.name: openvidu/media-node-controller
- index: "filebeat-openvidu-recording-%{+yyyy.MM.dd}"
when.or:
- contains:
container.image.name: openvidu/openvidu-recording
pipelines: pipelines:
- pipeline: kurento-pipeline - pipeline: kurento-pipeline
when.or: when.or:

View File

@ -2,7 +2,7 @@ metricbeat.modules:
- module: system - module: system
metricsets: metricsets:
- cpu - cpu
#- diskio - diskio
- memory - memory
- network - network
- filesystem - filesystem
@ -26,9 +26,13 @@ metricbeat.modules:
- regexp: - regexp:
system.filesystem.mount_point: '^/hostfs/(sys|cgroup|proc|dev|etc|host)($|/)' system.filesystem.mount_point: '^/hostfs/(sys|cgroup|proc|dev|etc|host)($|/)'
enabled: true enabled: true
period: ${OPENVIDU_PRO_CLUSTER_LOAD_INTERVAL}0s period: ${OPENVIDU_PRO_STATS_MONITORING_INTERVAL}s
cpu.metrics: [normalized_percentages] cpu.metrics: [normalized_percentages]
fields: {ip: "${MEDIA_NODE_IP}", cluster-id: "${CLUSTER_ID}"} fields:
ip: "${MEDIA_NODE_IP}"
cluster_id: "${CLUSTER_ID}"
node_id: ${NODE_ID}
node_role: medianode
pipeline: pipeline:
queue.mem.events: 0 queue.mem.events: 0
setup.ilm.enabled: false setup.ilm.enabled: false

View File

@ -6,7 +6,7 @@
# #
# This docker-compose file coordinates all services of OpenVidu CE Platform. # This docker-compose file coordinates all services of OpenVidu CE Platform.
# #
# Openvidu Version: 2.16.0 # Openvidu Version: 2.17.0
# #
# Installation Mode: On Premises # Installation Mode: On Premises
# #
@ -16,16 +16,17 @@ version: '3.1'
services: services:
media-node-controller: media-node-controller:
image: openvidu/media-node-controller:3.0.0-dev4 image: openvidu/media-node-controller:4.0.0-dev1
restart: always restart: always
ulimits: ulimits:
core: -1 core: -1
entrypoint: ['/bin/sh', '-c', '/beats/copy_config_files.sh && /usr/local/bin/entrypoint.sh'] entrypoint: ['/bin/sh', '-c', '/beats/copy_config_files.sh && /usr/local/bin/entrypoint.sh']
environment: environment:
- KMS_IMAGE=kurento/kurento-media-server:6.15.0 - KMS_IMAGE=kurento/kurento-media-server:6.16.0
- METRICBEAT_IMAGE=docker.elastic.co/beats/metricbeat-oss:7.8.0 - METRICBEAT_IMAGE=docker.elastic.co/beats/metricbeat-oss:7.8.0
- FILEBEAT_IMAGE=docker.elastic.co/beats/filebeat-oss:7.8.0 - FILEBEAT_IMAGE=docker.elastic.co/beats/filebeat-oss:7.8.0
- OPENVIDU_RECORDING_IMAGE=openvidu/openvidu-recording:2.17.0-dev1 - OPENVIDU_RECORDING_IMAGE=openvidu/openvidu-recording:2.17.0
- NO_COLOR=true
ports: ports:
- 3000:3000 - 3000:3000
volumes: volumes:

View File

@ -22,10 +22,10 @@ fatal_error() {
docker_command_by_container_image() { docker_command_by_container_image() {
IMAGE_NAME=$1 IMAGE_NAME=$1
COMMAND=$2 COMMAND=$2
if [[ ! -z "${IMAGE_NAME}" ]]; then if [[ -n "${IMAGE_NAME}" ]]; then
CONTAINERS=$(docker ps -a | grep "${IMAGE_NAME}" | awk '{print $1}') CONTAINERS="$(docker ps -a | grep "${IMAGE_NAME}" | awk '{print $1}')"
for CONTAINER_ID in ${CONTAINERS[@]}; do for CONTAINER_ID in $CONTAINERS; do
if [[ ! -z "${CONTAINER_ID}" ]] && [[ ! -z "${COMMAND}" ]]; then if [[ -n "${CONTAINER_ID}" ]] && [[ -n "${COMMAND}" ]]; then
bash -c "docker ${COMMAND} ${CONTAINER_ID}" bash -c "docker ${COMMAND} ${CONTAINER_ID}"
fi fi
done done
@ -35,7 +35,7 @@ docker_command_by_container_image() {
stop_containers() { stop_containers() {
printf "Stopping containers..." printf "Stopping containers..."
for IMAGE in ${IMAGES[@]}; do for IMAGE in "${IMAGES[@]}"; do
docker_command_by_container_image "${IMAGE}" "rm -f" docker_command_by_container_image "${IMAGE}" "rm -f"
done done
} }
@ -89,15 +89,15 @@ new_media_node_installation() {
# Pull images # Pull images
printf "\n => Pulling images...\n" printf "\n => Pulling images...\n"
cd "${MEDIA_NODE_FOLDER}" || fatal_error "Error when moving to '${MEDIA_NODE_FOLDER}' folder" cd "${MEDIA_NODE_FOLDER}" || fatal_error "Error when moving to '${MEDIA_NODE_FOLDER}' folder"
KMS_IMAGE=$(cat docker-compose.yml | grep KMS_IMAGE | cut -d"=" -f2) KMS_IMAGE=$(grep KMS_IMAGE docker-compose.yml | cut -d"=" -f2)
METRICBEAT_IMAGE=$(cat docker-compose.yml | grep METRICBEAT_IMAGE | cut -d"=" -f2) METRICBEAT_IMAGE=$(grep METRICBEAT_IMAGE docker-compose.yml | cut -d"=" -f2)
FILEBEAT_IMAGE=$(cat docker-compose.yml | grep FILEBEAT_IMAGE | cut -d"=" -f2) FILEBEAT_IMAGE=$(grep FILEBEAT_IMAGE docker-compose.yml | cut -d"=" -f2)
OPENVIDU_RECORDING_IMAGE=$(cat docker-compose.yml | grep OPENVIDU_RECORDING_IMAGE | cut -d"=" -f2) OPENVIDU_RECORDING_IMAGE=$(grep OPENVIDU_RECORDING_IMAGE docker-compose.yml | cut -d"=" -f2)
docker pull $KMS_IMAGE || fatal "Error while pulling docker image: $KMS_IMAGE" docker pull "$KMS_IMAGE" || fatal "Error while pulling docker image: $KMS_IMAGE"
docker pull $METRICBEAT_IMAGE || fatal "Error while pulling docker image: $METRICBEAT_IMAGE" docker pull "$METRICBEAT_IMAGE" || fatal "Error while pulling docker image: $METRICBEAT_IMAGE"
docker pull $FILEBEAT_IMAGE || fatal "Error while pulling docker image: $FILEBEAT_IMAGE" docker pull "$FILEBEAT_IMAGE" || fatal "Error while pulling docker image: $FILEBEAT_IMAGE"
docker pull $OPENVIDU_RECORDING_IMAGE || fatal "Error while pulling docker image: $OPENVIDU_RECORDING_IMAGE" docker pull "$OPENVIDU_RECORDING_IMAGE" || fatal "Error while pulling docker image: $OPENVIDU_RECORDING_IMAGE"
docker-compose pull | true docker-compose pull || true
# Ready to use # Ready to use
printf "\n" printf "\n"
@ -122,7 +122,7 @@ new_media_node_installation() {
printf '\n 4. Start or restart OpenVidu Pro and all containers will be provisioned' printf '\n 4. Start or restart OpenVidu Pro and all containers will be provisioned'
printf '\n automatically to all the media nodes configured in "KMS_URIS"' printf '\n automatically to all the media nodes configured in "KMS_URIS"'
printf '\n More info about Media Nodes deployment here:' printf '\n More info about Media Nodes deployment here:'
printf "\n --> https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/openvidu-pro/deployment/on-premises/#set-the-number-of-media-nodes-on-startup" printf '\n --> https://docs.openvidu.io/en/%s/openvidu-pro/deployment/on-premises/#set-the-number-of-media-nodes-on-startup' "${OPENVIDU_VERSION//v}"
printf '\n' printf '\n'
printf '\n' printf '\n'
printf "\n If you want to rollback, all the files from the previous installation have been copied to folder '.old-%s'" "${OPENVIDU_PREVIOUS_VERSION}" printf "\n If you want to rollback, all the files from the previous installation have been copied to folder '.old-%s'" "${OPENVIDU_PREVIOUS_VERSION}"
@ -156,7 +156,7 @@ upgrade_media_node() {
[ -z "${MEDIA_NODE_PREVIOUS_FOLDER}" ] && fatal_error "No previous Media Node installation found" [ -z "${MEDIA_NODE_PREVIOUS_FOLDER}" ] && fatal_error "No previous Media Node installation found"
# Uppgrade Media Node # Upgrade Media Node
OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${MEDIA_NODE_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${MEDIA_NODE_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
[ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version" [ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version"
@ -205,8 +205,8 @@ upgrade_media_node() {
--output "${TMP_FOLDER}/copy_config_files.sh" || fatal_error "Error when downloading the file 'copy_config_files.sh'" --output "${TMP_FOLDER}/copy_config_files.sh" || fatal_error "Error when downloading the file 'copy_config_files.sh'"
printf '\n - copy_config_files.sh' printf '\n - copy_config_files.sh'
# Dowloading new images and stoped actual Media Node # Downloading new images and stopped actual Media Node
printf '\n => Dowloading new images...' printf '\n => Downloading new images...'
printf '\n' printf '\n'
sleep 1 sleep 1
@ -214,7 +214,7 @@ upgrade_media_node() {
printf '\n' printf '\n'
cd "${TMP_FOLDER}" || fatal_error "Error when moving to '${TMP_FOLDER}' folder" cd "${TMP_FOLDER}" || fatal_error "Error when moving to '${TMP_FOLDER}' folder"
printf '\n => Stoping Media Node containers...' printf '\n => Stopping Media Node containers...'
printf '\n' printf '\n'
sleep 1 sleep 1
@ -222,24 +222,24 @@ upgrade_media_node() {
# Pull images # Pull images
printf "\n => Pulling images...\n" printf "\n => Pulling images...\n"
KMS_IMAGE=$(cat docker-compose.yml | grep KMS_IMAGE | cut -d"=" -f2) KMS_IMAGE="$(grep KMS_IMAGE docker-compose.yml | cut -d"=" -f2)"
METRICBEAT_IMAGE=$(cat docker-compose.yml | grep METRICBEAT_IMAGE | cut -d"=" -f2) METRICBEAT_IMAGE="$(grep METRICBEAT_IMAGE docker-compose.yml | cut -d"=" -f2)"
FILEBEAT_IMAGE=$(cat docker-compose.yml | grep FILEBEAT_IMAGE | cut -d"=" -f2) FILEBEAT_IMAGE="$(grep FILEBEAT_IMAGE docker-compose.yml | cut -d"=" -f2)"
OPENVIDU_RECORDING_IMAGE=$(cat docker-compose.yml | grep OPENVIDU_RECORDING_IMAGE | cut -d"=" -f2) OPENVIDU_RECORDING_IMAGE="$(grep OPENVIDU_RECORDING_IMAGE docker-compose.yml | cut -d"=" -f2)"
docker pull $KMS_IMAGE || fatal "Error while pulling docker image: $KMS_IMAGE" docker pull "$KMS_IMAGE" || fatal "Error while pulling docker image: $KMS_IMAGE"
docker pull $METRICBEAT_IMAGE || fatal "Error while pulling docker image: $METRICBEAT_IMAGE" docker pull "$METRICBEAT_IMAGE" || fatal "Error while pulling docker image: $METRICBEAT_IMAGE"
docker pull $FILEBEAT_IMAGE || fatal "Error while pulling docker image: $FILEBEAT_IMAGE" docker pull "$FILEBEAT_IMAGE" || fatal "Error while pulling docker image: $FILEBEAT_IMAGE"
docker pull $OPENVIDU_RECORDING_IMAGE || fatal "Error while pulling docker image: $OPENVIDU_RECORDING_IMAGE" docker pull "$OPENVIDU_RECORDING_IMAGE" || fatal "Error while pulling docker image: $OPENVIDU_RECORDING_IMAGE"
docker-compose pull | true docker-compose pull || true
printf '\n => Stoping Media Node...' printf '\n => Stopping Media Node...'
printf '\n' printf '\n'
sleep 1 sleep 1
printf "\n => Moving to 'openvidu' folder..." printf "\n => Moving to 'openvidu' folder..."
printf '\n' printf '\n'
cd "${MEDIA_NODE_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder" cd "${MEDIA_NODE_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder"
docker-compose down | true docker-compose down || true
printf '\n' printf '\n'
printf '\n => Moving to working dir...' printf '\n => Moving to working dir...'
@ -290,7 +290,7 @@ upgrade_media_node() {
# Define old mode: On Premise or Cloud Formation # Define old mode: On Premise or Cloud Formation
OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }') OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }')
[ ! -z "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${MEDIA_NODE_PREVIOUS_FOLDER}/docker-compose.yml" [ -n "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${MEDIA_NODE_PREVIOUS_FOLDER}/docker-compose.yml"
# Ready to use # Ready to use
printf '\n' printf '\n'
@ -317,7 +317,7 @@ upgrade_media_node() {
printf '\n 5. Start or restart OpenVidu Pro and all containers will be provisioned' printf '\n 5. Start or restart OpenVidu Pro and all containers will be provisioned'
printf '\n automatically to all the media nodes configured in "KMS_URIS"' printf '\n automatically to all the media nodes configured in "KMS_URIS"'
printf '\n More info about Media Nodes deployment here:' printf '\n More info about Media Nodes deployment here:'
printf "\n --> https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/openvidu-pro/deployment/on-premises/#set-the-number-of-media-nodes-on-startup" printf '\n --> https://docs.openvidu.io/en/%s/openvidu-pro/deployment/on-premises/#set-the-number-of-media-nodes-on-startup' "${OPENVIDU_VERSION//v}"
printf '\n' printf '\n'
printf '\n' printf '\n'
printf "\n If you want to rollback, all the files from the previous installation have been copied to folder '.old-%s'" "${OPENVIDU_PREVIOUS_VERSION}" printf "\n If you want to rollback, all the files from the previous installation have been copied to folder '.old-%s'" "${OPENVIDU_PREVIOUS_VERSION}"
@ -342,7 +342,7 @@ else
fi fi
# Check type of installation # Check type of installation
if [[ ! -z "$1" && "$1" == "upgrade" ]]; then if [[ -n "$1" && "$1" == "upgrade" ]]; then
upgrade_media_node upgrade_media_node
else else
new_media_node_installation new_media_node_installation

View File

@ -11,10 +11,10 @@ IMAGES=(
docker_command_by_container_image() { docker_command_by_container_image() {
IMAGE_NAME=$1 IMAGE_NAME=$1
COMMAND=$2 COMMAND=$2
if [[ ! -z "${IMAGE_NAME}" ]]; then if [[ -n "${IMAGE_NAME}" ]]; then
CONTAINERS=$(docker ps -a | grep "${IMAGE_NAME}" | awk '{print $1}') CONTAINERS="$(docker ps -a | grep "${IMAGE_NAME}" | awk '{print $1}')"
for CONTAINER_ID in ${CONTAINERS[@]}; do for CONTAINER_ID in $CONTAINERS; do
if [[ ! -z "${CONTAINER_ID}" ]] && [[ ! -z "${COMMAND}" ]]; then if [[ -n "${CONTAINER_ID}" ]] && [[ -n "${COMMAND}" ]]; then
bash -c "docker ${COMMAND} ${CONTAINER_ID}" bash -c "docker ${COMMAND} ${CONTAINER_ID}"
fi fi
done done
@ -24,7 +24,7 @@ docker_command_by_container_image() {
stop_containers() { stop_containers() {
printf "Stopping containers..." printf "Stopping containers..."
for IMAGE in ${IMAGES[@]}; do for IMAGE in "${IMAGES[@]}"; do
docker_command_by_container_image "${IMAGE}" "rm -f" docker_command_by_container_image "${IMAGE}" "rm -f"
done done
} }
@ -99,7 +99,7 @@ generate_report() {
REPORT_CREATION_DATE=$(date +"%d-%m-%Y") REPORT_CREATION_DATE=$(date +"%d-%m-%Y")
REPORT_CREATION_TIME=$(date +"%H:%M:%S") REPORT_CREATION_TIME=$(date +"%H:%M:%S")
REPORT_NAME="media-node-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt" REPORT_NAME="media-node-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt"
REPORT_OUPUT="${MEDIA_NODE_FOLDER}/${REPORT_NAME}" REPORT_OUTPUT="${MEDIA_NODE_FOLDER}/${REPORT_NAME}"
CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}') CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}')
{ {
@ -167,10 +167,10 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker logs $CONTAINER docker logs "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
@ -194,19 +194,19 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n =======================================" printf "\n ======================================="
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker exec $CONTAINER env docker exec "$CONTAINER" env
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
done done
} >> "${REPORT_OUPUT}" 2>&1 } >> "${REPORT_OUTPUT}" 2>&1
printf "\n Generation of the report completed with success" printf "\n Generation of the report completed with success"
printf "\n You can get your report at path '%s'" "${REPORT_OUPUT}" printf "\n You can get your report at path '%s'" "${REPORT_OUTPUT}"
printf "\n" printf "\n"
} }
@ -218,7 +218,7 @@ usage() {
printf "\n\trestart\t\t\tRestart media node service" printf "\n\trestart\t\t\tRestart media node service"
printf "\n\tlogs [-f]\t\tShow media-node-controller logs." printf "\n\tlogs [-f]\t\tShow media-node-controller logs."
printf "\n\tkms-logs [-f]\t\tShow kms logs" printf "\n\tkms-logs [-f]\t\tShow kms logs"
printf "\n\tupgrade\t\t\tUpgrade to the lastest Media Node version" printf "\n\tupgrade\t\t\tUpgrade to the latest Media Node version"
printf "\n\tupgrade [version]\tUpgrade to the specific Media Node version" printf "\n\tupgrade [version]\tUpgrade to the specific Media Node version"
printf "\n\tversion\t\t\tShow version of Media Node" printf "\n\tversion\t\t\tShow version of Media Node"
printf "\n\treport\t\t\tGenerate a report with the current status of Media Node" printf "\n\treport\t\t\tGenerate a report with the current status of Media Node"
@ -257,7 +257,7 @@ case $1 in
;; ;;
kms-logs) kms-logs)
kurento_logs $2 kurento_logs "$2"
;; ;;
upgrade) upgrade)

View File

@ -9,7 +9,7 @@
# For example: 198.51.100.1, or openvidu.example.com # For example: 198.51.100.1, or openvidu.example.com
DOMAIN_OR_PUBLIC_IP= DOMAIN_OR_PUBLIC_IP=
# OpenVidu PRO License # OpenVidu Pro License
OPENVIDU_PRO_LICENSE= OPENVIDU_PRO_LICENSE=
# OpenVidu SECRET used for apps to connect to OpenVidu server and users to access to OpenVidu Dashboard # OpenVidu SECRET used for apps to connect to OpenVidu server and users to access to OpenVidu Dashboard
@ -188,7 +188,7 @@ OPENVIDU_PRO_CLUSTER_LOAD_STRATEGY=streams
# AWS region in which the S3 bucket is located (e.g. eu-west-1). If not provided, # AWS region in which the S3 bucket is located (e.g. eu-west-1). If not provided,
# the region will try to be discovered automatically, although this is not always possible. # the region will try to be discovered automatically, although this is not always possible.
# This property is only taken into account if OPENVIDU_PRO_RECORDING_STORAGE=s3 # This property is only taken into account if OPENVIDU_PRO_RECORDING_STORAGE=s3
# OPENVIDU_PRO_AWS_REGION= #OPENVIDU_PRO_AWS_REGION=
# Whether to enable recording module or not # Whether to enable recording module or not
OPENVIDU_RECORDING=false OPENVIDU_RECORDING=false
@ -250,6 +250,10 @@ OPENVIDU_STREAMS_VIDEO_MIN_SEND_BANDWIDTH=300
# Default value is false # Default value is false
# OPENVIDU_STREAMS_ALLOW_TRANSCODING=false # OPENVIDU_STREAMS_ALLOW_TRANSCODING=false
# Send openvidu-browser logs of clients to Elasticsearch
# Default value is 'disabled'
# OPENVIDU_BROWSER_LOGS=disabled
# true to enable OpenVidu Webhook service. false' otherwise # true to enable OpenVidu Webhook service. false' otherwise
# Values: true | false # Values: true | false
OPENVIDU_WEBHOOK=false OPENVIDU_WEBHOOK=false
@ -263,7 +267,7 @@ OPENVIDU_WEBHOOK=false
# List of events that will be sent by OpenVidu Webhook service # List of events that will be sent by OpenVidu Webhook service
# Default value is all available events # Default value is all available events
OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged] OPENVIDU_WEBHOOK_EVENTS=[sessionCreated,sessionDestroyed,participantJoined,participantLeft,webrtcConnectionCreated,webrtcConnectionDestroyed,recordingStatusChanged,filterEventDispatched,mediaNodeStatusChanged,nodeCrashed]
# How often the garbage collector of non active sessions runs. # How often the garbage collector of non active sessions runs.
# This helps cleaning up sessions that have been initialized through # This helps cleaning up sessions that have been initialized through
@ -324,7 +328,7 @@ ELASTICSEARCH_PASSWORD=
# Uncomment the next line and define this variable with KMS image that you want use # Uncomment the next line and define this variable with KMS image that you want use
# By default, KMS_IMAGE is defined in media nodes and it does not need to be specified unless # By default, KMS_IMAGE is defined in media nodes and it does not need to be specified unless
# you want to use a specific version of KMS # you want to use a specific version of KMS
# KMS_IMAGE=kurento/kurento-media-server:6.15.0 # KMS_IMAGE=kurento/kurento-media-server:6.16.0
# Uncomment the next line and define this variable to change # Uncomment the next line and define this variable to change
# the verbosity level of the logs of KMS # the verbosity level of the logs of KMS
@ -337,6 +341,7 @@ ELASTICSEARCH_PASSWORD=
#AWS_DEFAULT_REGION= #AWS_DEFAULT_REGION=
#AWS_IMAGE_ID= #AWS_IMAGE_ID=
#AWS_INSTANCE_TYPE= #AWS_INSTANCE_TYPE=
#AWS_INSTANCE_ID=
#AWS_KEY_NAME= #AWS_KEY_NAME=
#AWS_SUBNET_ID= #AWS_SUBNET_ID=
#AWS_SECURITY_GROUP= #AWS_SECURITY_GROUP=

View File

@ -2,6 +2,11 @@ filebeat.inputs:
- type: container - type: container
paths: paths:
- '/var/lib/docker/containers/*/*.log' - '/var/lib/docker/containers/*/*.log'
fields:
cluster_id: ${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}
node_id: master_${AWS_INSTANCE_ID:${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}}
node_role: masternode
fields_under_root: true
processors: processors:
- add_docker_metadata: - add_docker_metadata:
@ -25,9 +30,8 @@ processors:
container.image.name: docker.elastic.co/beats/filebeat-oss container.image.name: docker.elastic.co/beats/filebeat-oss
- contains: - contains:
container.image.name: docker.elastic.co/beats/metricbeat-oss container.image.name: docker.elastic.co/beats/metricbeat-oss
- add_fields: - contains:
fields: container.image.name: openvidu/openvidu-server-pro
cluster-id: ${OPENVIDU_PRO_CLUSTER_ID:undefined}
output: output:
elasticsearch: elasticsearch:
@ -41,7 +45,10 @@ output:
when.or: when.or:
- contains: - contains:
container.image.name: openvidu/openvidu-proxy container.image.name: openvidu/openvidu-proxy
- index: "filebeat-openvidu-recording-%{+yyyy.MM.dd}"
when.or:
- contains:
container.image.name: openvidu/openvidu-recording
logging.json: true logging.json: true
logging.metrics.enabled: false logging.metrics.enabled: false

View File

@ -1,10 +1,40 @@
metricbeat.modules: metricbeat.modules:
- module: nginx - module: nginx
metricsets: ["stubstatus"] metricsets: ["stubstatus"]
enabled: true enabled: true
period: 10s period: ${OPENVIDU_PRO_STATS_MONITORING_INTERVAL}s
hosts: ["http://127.0.0.1"] hosts: ["http://127.0.0.1"]
server_status_path: "nginx_status" server_status_path: "nginx_status"
- module: system
metricsets:
- cpu
- diskio
- memory
- network
- filesystem
- fsstat
- process_summary
- uptime
filesystem.ignore_types: [nfs, smbfs, autofs, devtmpfs, devpts, hugetlbfs, tmpfs, sysfs, securityfs, cgroup2, cgroup, pstore, debugfs, configfs, fusectl, proc, fuse.lxcfs, squashfs]
processes: ['.*']
processors:
- drop_event:
when:
or:
- regexp:
system.network.name: '^(veth|lo|docker|br-)($|)'
- regexp:
system.filesystem.mount_point: '^/(sys|cgroup|proc|dev|etc|host)($|/)'
- regexp:
system.filesystem.mount_point: '^/hostfs/(sys|cgroup|proc|dev|etc|host)($|/)'
enabled: true
period: ${OPENVIDU_PRO_STATS_MONITORING_INTERVAL}s
cpu.metrics: [normalized_percentages]
fields:
cluster_id: ${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}
node_id: master_${AWS_INSTANCE_ID:${OPENVIDU_PRO_CLUSTER_ID:${DOMAIN_OR_PUBLIC_IP:undefined}}}
node_role: masternode
output: output:
elasticsearch: elasticsearch:
hosts: ["${OPENVIDU_PRO_ELASTICSEARCH_HOST}"] hosts: ["${OPENVIDU_PRO_ELASTICSEARCH_HOST}"]
setup.ilm.enabled: false

View File

@ -35,6 +35,14 @@ exit_on_error () {
esac esac
} }
# Check custom parameters
if [[ -n "${CUSTOM_INSTANCE_TYPE}" ]]; then
AWS_INSTANCE_TYPE="${CUSTOM_INSTANCE_TYPE}"
fi
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 \ docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 run-instances \
--image-id ${AWS_IMAGE_ID} --count 1 \ --image-id ${AWS_IMAGE_ID} --count 1 \
--instance-type ${AWS_INSTANCE_TYPE} \ --instance-type ${AWS_INSTANCE_TYPE} \
@ -51,18 +59,6 @@ docker run --rm amazon/aws-cli:${AWS_CLI_DOCKER_TAG} ec2 wait instance-running -
KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress') KMS_IP=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .NetworkInterfaces[0] | .PrivateIpAddress')
KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId') KMS_ID=$(cat ${OUTPUT} | jq --raw-output ' .Instances[] | .InstanceId')
# Wait media-node controller
attempt_counter=0
max_attempts=10
until $(curl --output /dev/null --silent --head --fail -u OPENVIDUAPP:${OPENVIDU_SECRET} http://${KMS_IP}:3000/media-node/status); do
if [ ${attempt_counter} -eq ${max_attempts} ];then
exit 1
fi
attempt_counter=$(($attempt_counter+1))
sleep 5
done
jq -n \ jq -n \
--arg id "${KMS_ID}" \ --arg id "${KMS_ID}" \
--arg ip "${KMS_IP}" \ --arg ip "${KMS_IP}" \

View File

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

View File

@ -11,7 +11,7 @@
# #
# This file will be overridden when update OpenVidu Platform # This file will be overridden when update OpenVidu Platform
# #
# Openvidu Version: 2.16.0 # Openvidu Version: 2.17.0
# #
# Installation Mode: On Premises # Installation Mode: On Premises
# #
@ -22,7 +22,7 @@ version: '3.1'
services: services:
openvidu-server: openvidu-server:
image: openvidu/openvidu-server-pro:2.17.0-dev5 image: openvidu/openvidu-server-pro:2.18.0-beta17
restart: on-failure restart: on-failure
network_mode: host network_mode: host
entrypoint: ['/usr/local/bin/entrypoint.sh'] entrypoint: ['/usr/local/bin/entrypoint.sh']
@ -52,7 +52,7 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
redis: redis:
image: openvidu/openvidu-redis:2.0.0-dev2 image: openvidu/openvidu-redis:2.0.0
restart: always restart: always
network_mode: host network_mode: host
environment: environment:
@ -62,24 +62,29 @@ services:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
coturn: coturn:
image: openvidu/openvidu-coturn:3.0.0-dev2 image: openvidu/openvidu-coturn:4.0.0-dev2
restart: on-failure restart: on-failure
network_mode: host network_mode: host
environment: environment:
- REDIS_IP=127.0.0.1 - REDIS_IP=127.0.0.1
- TURN_LISTEN_PORT=3478
- DB_NAME=0 - DB_NAME=0
- DB_PASSWORD=${OPENVIDU_SECRET} - DB_PASSWORD=${OPENVIDU_SECRET}
- MIN_PORT=40000 command:
- MAX_PORT=65535 - --log-file=stdout
- TURN_PUBLIC_IP=${TURN_PUBLIC_IP:-auto-ipv4} - --external-ip=$$(detect-external-ip)
- ENABLE_COTURN_LOGS=true - --listening-port=3478
- --fingerprint
- --lt-cred-mech
- --min-port=40000
- --max-port=65535
- --realm=openvidu
- --verbose
logging: logging:
options: options:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}" max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"
nginx: nginx:
image: openvidu/openvidu-proxy:5.0.0-dev2 image: openvidu/openvidu-proxy:6.0.0-dev1
restart: on-failure restart: on-failure
network_mode: host network_mode: host
volumes: volumes:
@ -120,6 +125,7 @@ services:
command: > command: >
/bin/bash -c "elasticsearch-users useradd ${ELASTICSEARCH_USERNAME} /bin/bash -c "elasticsearch-users useradd ${ELASTICSEARCH_USERNAME}
-p ${ELASTICSEARCH_PASSWORD} -r superuser; -p ${ELASTICSEARCH_PASSWORD} -r superuser;
elasticsearch-users passwd ${ELASTICSEARCH_USERNAME} -p ${ELASTICSEARCH_PASSWORD};
docker-entrypoint.sh" docker-entrypoint.sh"
logging: logging:
options: options:
@ -148,10 +154,15 @@ services:
- .env - .env
environment: environment:
- OPENVIDU_PRO_ELASTICSEARCH_HOST=${OPENVIDU_PRO_ELASTICSEARCH_HOST:-http://127.0.0.1:9200} - OPENVIDU_PRO_ELASTICSEARCH_HOST=${OPENVIDU_PRO_ELASTICSEARCH_HOST:-http://127.0.0.1:9200}
- OPENVIDU_PRO_STATS_MONITORING_INTERVAL=${OPENVIDU_PRO_STATS_MONITORING_INTERVAL:-10}
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./beats/metricbeat.yml:/usr/share/metricbeat/metricbeat.yml:ro - ./beats/metricbeat.yml:/usr/share/metricbeat/metricbeat.yml:ro
- /proc:/hostfs/proc:ro
- /sys/fs/cgroup:/hostfs/sys/fs/cgroup:ro
- /:/hostfs:ro
command: > command: >
/bin/bash -c "metricbeat -e -strict.perms=false /bin/bash -c "metricbeat -e -strict.perms=false -e -system.hostfs=/hostfs
`if [ ! -z $ELASTICSEARCH_USERNAME ]; then echo '-E output.elasticsearch.username=$ELASTICSEARCH_USERNAME'; fi` `if [ ! -z $ELASTICSEARCH_USERNAME ]; then echo '-E output.elasticsearch.username=$ELASTICSEARCH_USERNAME'; fi`
`if [ ! -z $ELASTICSEARCH_PASSWORD ]; then echo '-E output.elasticsearch.password=$ELASTICSEARCH_PASSWORD'; fi`" `if [ ! -z $ELASTICSEARCH_PASSWORD ]; then echo '-E output.elasticsearch.password=$ELASTICSEARCH_PASSWORD'; fi`"
logging: logging:

View File

@ -19,7 +19,7 @@ fatal_error() {
new_ov_installation() { new_ov_installation() {
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Install Openvidu PRO %s' "${OPENVIDU_VERSION}" printf '\n Install OpenVidu Pro %s' "${OPENVIDU_VERSION}"
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
@ -43,7 +43,7 @@ new_ov_installation() {
chown 1000:1000 "${ELASTICSEARCH_FOLDER}" || fatal_error "Error while changing permission to 'elasticsearch' folder" chown 1000:1000 "${ELASTICSEARCH_FOLDER}" || fatal_error "Error while changing permission to 'elasticsearch' folder"
# Download necessary files # Download necessary files
printf '\n => Downloading Openvidu PRO files:' printf '\n => Downloading OpenVidu Pro files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \ curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \
--output "${AWS_SCRIPTS_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'" --output "${AWS_SCRIPTS_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'"
@ -108,7 +108,7 @@ new_ov_installation() {
printf '\n' printf '\n'
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Openvidu PRO successfully installed.' printf '\n OpenVidu Pro successfully installed.'
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
printf '\n 1. Go to openvidu folder:' printf '\n 1. Go to openvidu folder:'
@ -124,12 +124,23 @@ new_ov_installation() {
printf "\n CAUTION: The folder 'openvidu/elasticsearch' use user and group 1000 permissions. " printf "\n CAUTION: The folder 'openvidu/elasticsearch' use user and group 1000 permissions. "
printf "\n This folder is necessary for store elasticsearch data." printf "\n This folder is necessary for store elasticsearch data."
printf "\n For more information, check:" printf "\n For more information, check:"
printf "\n https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/openvidu-pro/deployment/on-premises/#deployment-instructions" printf '\n https://docs.openvidu.io/en/%s/openvidu-pro/deployment/on-premises/#deployment-instructions' "${OPENVIDU_VERSION//v}"
printf '\n' printf '\n'
printf '\n' printf '\n'
exit 0 exit 0
} }
get_previous_env_variable() {
local ENV_VARIABLE_NAME=$1
echo "$(grep -E "${ENV_VARIABLE_NAME}=.*$" "${OPENVIDU_PREVIOUS_FOLDER}/.env" | cut -d'=' -f2)"
}
replace_variable_in_new_env_file() {
local ENV_VARIABLE_NAME=$1
local ENV_VARIABLE_VALUE=$2
[[ -n "${ENV_VARIABLE_VALUE}" ]] && sed -i "s|#${ENV_VARIABLE_NAME}=|${ENV_VARIABLE_NAME}=${ENV_VARIABLE_VALUE}|" "${OPENVIDU_PREVIOUS_FOLDER}/.env-${OPENVIDU_VERSION}"
}
upgrade_ov() { upgrade_ov() {
# Search local Openvidu installation # Search local Openvidu installation
printf '\n' printf '\n'
@ -156,19 +167,19 @@ upgrade_ov() {
[ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found" [ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found"
# Uppgrade Openvidu # Upgrade Openvidu
OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OPENVIDU_PREVIOUS_VERSION=$(grep 'Openvidu Version:' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
[ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version" [ -z "${OPENVIDU_PREVIOUS_VERSION}" ] && fatal_error "Can't find previous OpenVidu version"
# In this point using the variable 'OPENVIDU_PREVIOUS_VERSION' we can verify if the upgrade is # In this point using the variable 'OPENVIDU_PREVIOUS_VERSION' we can verify if the upgrade is
# posible or not. If it is not posible launch a warning and stop the upgrade. # posible or not. If it is not posible launch a warning and stop the upgrade.
if [[ "${OPENVIDU_PREVIOUS_VERSION}" != "${OPENVIDU_UPGRADABLE_VERSION}."* ]]; then if [[ "${OPENVIDU_PREVIOUS_VERSION}" != "${OPENVIDU_UPGRADABLE_VERSION}."* ]] && [[ "${OPENVIDU_PREVIOUS_VERSION}" != "${OPENVIDU_VERSION//v}"* ]]; then
fatal_error "You can't update from version ${OPENVIDU_PREVIOUS_VERSION} to ${OPENVIDU_VERSION}.\nNever upgrade across multiple major versions." fatal_error "You can't update from version ${OPENVIDU_PREVIOUS_VERSION} to ${OPENVIDU_VERSION}.\nNever upgrade across multiple major versions."
fi fi
printf '\n' printf '\n'
printf '\n =======================================' printf '\n ======================================='
printf '\n Upgrade Openvidu PRO %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}" printf '\n Upgrade OpenVidu Pro %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}"
printf '\n =======================================' printf '\n ======================================='
printf '\n' printf '\n'
@ -184,7 +195,7 @@ upgrade_ov() {
mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'" mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'"
# Download necessary files # Download necessary files
printf '\n => Downloading new Openvidu PRO files:' printf '\n => Downloading new OpenVidu Pro files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \ curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/pro/docker-compose/openvidu-server-pro/cluster/aws/openvidu_autodiscover.sh \
--output "${TMP_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'" --output "${TMP_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'"
@ -222,8 +233,8 @@ upgrade_ov() {
--output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'" --output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'"
printf '\n - openvidu' printf '\n - openvidu'
# Dowloading new images and stoped actual Openvidu # Downloading new images and stopped actual Openvidu
printf '\n => Dowloading new images...' printf '\n => Downloading new images...'
printf '\n' printf '\n'
sleep 1 sleep 1
@ -231,9 +242,9 @@ upgrade_ov() {
printf '\n' printf '\n'
cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder" cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder"
printf '\n' printf '\n'
docker-compose pull | true docker-compose pull || true
printf '\n => Stoping Openvidu...' printf '\n => Stopping Openvidu...'
printf '\n' printf '\n'
sleep 1 sleep 1
@ -241,7 +252,7 @@ upgrade_ov() {
printf '\n' printf '\n'
cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder" cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder"
printf '\n' printf '\n'
docker-compose down | true docker-compose down || true
printf '\n' printf '\n'
printf '\n => Moving to working dir...' printf '\n => Moving to working dir...'
@ -253,7 +264,7 @@ upgrade_ov() {
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'" mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'"
printf '\n - docker-compose.yml' printf '\n - docker-compose.yml'
if [ ! -z "${USE_OV_CALL}" ]; then if [ -n "${USE_OV_CALL}" ]; then
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.override.yml'" mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml' printf '\n - docker-compose.override.yml'
fi fi
@ -281,7 +292,7 @@ upgrade_ov() {
mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'" mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'"
printf '\n - docker-compose.yml' printf '\n - docker-compose.yml'
if [ ! -z "${USE_OV_CALL}" ]; then if [ -n "${USE_OV_CALL}" ]; then
mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.override.yml'" mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml' printf '\n - docker-compose.override.yml'
else else
@ -334,16 +345,44 @@ upgrade_ov() {
# Define old mode: On Premise or Cloud Formation # Define old mode: On Premise or Cloud Formation
OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }') OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }')
[ ! -z "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" [ -n "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml"
# In Aws, update AMI ID # Update .env variables to new .env-version
AWS_REGION=$(grep -E "AWS_DEFAULT_REGION=.*$" "${OPENVIDU_PREVIOUS_FOLDER}/.env" | cut -d'=' -f2) AWS_REGION=$(get_previous_env_variable AWS_DEFAULT_REGION)
if [[ ! -z ${AWS_REGION} ]]; then if [[ -n ${AWS_REGION} ]]; then
# Get new AMI ID
NEW_AMI_ID=$(curl https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/CF-OpenVidu-Pro-${OPENVIDU_VERSION//v}.yaml --silent | NEW_AMI_ID=$(curl https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/CF-OpenVidu-Pro-${OPENVIDU_VERSION//v}.yaml --silent |
sed -n -e '/KMSAMIMAP:/,/Metadata:/ p' | sed -n -e '/KMSAMIMAP:/,/Metadata:/ p' |
grep -A 1 ${AWS_REGION} | grep AMI | tr -d " " | cut -d":" -f2) grep -A 1 "${AWS_REGION}" | grep AMI | tr -d " " | cut -d":" -f2)
[[ -z ${NEW_AMI_ID} ]] && fatal_error "Error while getting new AWS_IMAGE_ID for Media Nodes" [[ -z ${NEW_AMI_ID} ]] && fatal_error "Error while getting new AWS_IMAGE_ID for Media Nodes"
sed -i "s/.*AWS_IMAGE_ID=.*/AWS_IMAGE_ID=${NEW_AMI_ID}/" "${OPENVIDU_PREVIOUS_FOLDER}/.env" || fatal_error "Error while updating new AWS_IMAGE_ID for Media Nodes"
# Get previous values
PREV_AWS_DEFAULT_REGION=$(get_previous_env_variable AWS_DEFAULT_REGION)
PREV_AWS_INSTANCE_TYPE=$(get_previous_env_variable AWS_INSTANCE_TYPE)
PREV_AWS_INSTANCE_ID=$(get_previous_env_variable AWS_INSTANCE_ID)
PREV_AWS_KEY_NAME=$(get_previous_env_variable AWS_KEY_NAME)
PREV_AWS_SUBNET_ID=$(get_previous_env_variable AWS_SUBNET_ID)
PREV_AWS_SECURITY_GROUP=$(get_previous_env_variable AWS_SECURITY_GROUP)
PREV_AWS_STACK_ID=$(get_previous_env_variable AWS_STACK_ID)
PREV_AWS_STACK_NAME=$(get_previous_env_variable AWS_STACK_NAME)
PREV_AWS_CLI_DOCKER_TAG=$(get_previous_env_variable AWS_CLI_DOCKER_TAG)
PREV_AWS_VOLUME_SIZE=$(get_previous_env_variable AWS_VOLUME_SIZE)
# Replace variables in new .env-version file
replace_variable_in_new_env_file "AWS_DEFAULT_REGION" "${PREV_AWS_DEFAULT_REGION}"
replace_variable_in_new_env_file "AWS_INSTANCE_TYPE" "${PREV_AWS_INSTANCE_TYPE}"
replace_variable_in_new_env_file "AWS_INSTANCE_ID" "${PREV_AWS_INSTANCE_ID}"
replace_variable_in_new_env_file "AWS_KEY_NAME" "${PREV_AWS_KEY_NAME}"
replace_variable_in_new_env_file "AWS_SUBNET_ID" "${PREV_AWS_SUBNET_ID}"
replace_variable_in_new_env_file "AWS_SECURITY_GROUP" "${PREV_AWS_SECURITY_GROUP}"
replace_variable_in_new_env_file "AWS_STACK_ID" "${PREV_AWS_STACK_ID}"
replace_variable_in_new_env_file "AWS_STACK_NAME" "${PREV_AWS_STACK_NAME}"
replace_variable_in_new_env_file "AWS_CLI_DOCKER_TAG" "${PREV_AWS_CLI_DOCKER_TAG}"
replace_variable_in_new_env_file "AWS_VOLUME_SIZE" "${PREV_AWS_VOLUME_SIZE}"
# Replace new AMI
replace_variable_in_new_env_file "AWS_IMAGE_ID" "${NEW_AMI_ID}"
fi fi
@ -391,7 +430,7 @@ else
fi fi
# Check type of installation # Check type of installation
if [[ ! -z "$1" && "$1" == "upgrade" ]]; then if [[ -n "$1" && "$1" == "upgrade" ]]; then
upgrade_ov upgrade_ov
else else
new_ov_installation new_ov_installation

View File

@ -4,7 +4,7 @@ upgrade_ov() {
UPGRADE_SCRIPT_URL="https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_pro_OVVERSION.sh" UPGRADE_SCRIPT_URL="https://s3-eu-west-1.amazonaws.com/aws.openvidu.io/install_openvidu_pro_OVVERSION.sh"
HTTP_STATUS=$(curl -s -o /dev/null -I -w "%{http_code}" ${UPGRADE_SCRIPT_URL//OVVERSION/$1}) HTTP_STATUS=$(curl -s -o /dev/null -I -w "%{http_code}" ${UPGRADE_SCRIPT_URL//OVVERSION/$1})
printf " => Upgrading Openvidu PRO to '%s' version" "$1" printf " => Upgrading OpenVidu Pro to '%s' version" "$1"
if [ "$HTTP_STATUS" == "200" ]; then if [ "$HTTP_STATUS" == "200" ]; then
printf "\n => Downloading and upgrading new version" printf "\n => Downloading and upgrading new version"
@ -13,7 +13,7 @@ upgrade_ov() {
curl --silent ${UPGRADE_SCRIPT_URL//OVVERSION/$1} | bash -s upgrade curl --silent ${UPGRADE_SCRIPT_URL//OVVERSION/$1} | bash -s upgrade
else else
printf "\n =======¡ERROR!=======" printf "\n =======¡ERROR!======="
printf "\n Openvidu PRO Version '%s' not exist" "$1" printf "\n OpenVidu Pro Version '%s' not exist" "$1"
printf "\n" printf "\n"
exit 0 exit 0
fi fi
@ -28,7 +28,7 @@ collect_basic_information() {
OV_VERSION=$(grep 'Openvidu Version:' "${OV_FOLDER}/docker-compose.yml" | awk '{ print $4 }') OV_VERSION=$(grep 'Openvidu Version:' "${OV_FOLDER}/docker-compose.yml" | awk '{ print $4 }')
CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}') CONTAINERS=$(docker ps | awk '{if(NR>1) print $NF}')
if [ ! -z "$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OV_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')" ]; then if [ -n "$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OV_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')" ]; then
OV_CALL_VERSION=$(grep -E 'Openvidu-Call Version:' "${OV_FOLDER}/docker-compose.override.yml" | awk '{ print $4 }') OV_CALL_VERSION=$(grep -E 'Openvidu-Call Version:' "${OV_FOLDER}/docker-compose.override.yml" | awk '{ print $4 }')
fi fi
[ -z "${OV_CALL_VERSION}" ] && OV_CALL_VERSION="No present" [ -z "${OV_CALL_VERSION}" ] && OV_CALL_VERSION="No present"
@ -71,7 +71,7 @@ generate_report() {
REPORT_CREATION_DATE=$(date +"%d-%m-%Y") REPORT_CREATION_DATE=$(date +"%d-%m-%Y")
REPORT_CREATION_TIME=$(date +"%H:%M:%S") REPORT_CREATION_TIME=$(date +"%H:%M:%S")
REPORT_NAME="openvidu-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt" REPORT_NAME="openvidu-report-${REPORT_CREATION_DATE}-$(date +"%H-%M").txt"
REPORT_OUPUT="${OV_FOLDER}/${REPORT_NAME}" REPORT_OUTPUT="${OV_FOLDER}/${REPORT_NAME}"
{ {
printf "\n =======================================" printf "\n ======================================="
@ -150,10 +150,10 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker logs $CONTAINER docker logs "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
@ -167,19 +167,19 @@ generate_report() {
do do
printf '\n' printf '\n'
printf "\n =======================================" printf "\n ======================================="
printf "\n %s" $CONTAINER printf "\n %s" "$CONTAINER"
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
docker exec $CONTAINER env docker exec "$CONTAINER" env
printf "\n ---------------------------------------" printf "\n ---------------------------------------"
printf '\n' printf '\n'
printf '\n' printf '\n'
done done
} >> "${REPORT_OUPUT}" 2>&1 } >> "${REPORT_OUTPUT}" 2>&1
printf "\n Generation of the report completed with success" printf "\n Generation of the report completed with success"
printf "\n You can get your report at path '%s'" "${REPORT_OUPUT}" printf "\n You can get your report at path '%s'" "${REPORT_OUTPUT}"
printf "\n" printf "\n"
} }
@ -224,9 +224,9 @@ usage() {
printf "\n\nAvailable Commands:" printf "\n\nAvailable Commands:"
printf "\n\tstart\t\t\tStart all services" printf "\n\tstart\t\t\tStart all services"
printf "\n\tstop\t\t\tStop all services" printf "\n\tstop\t\t\tStop all services"
printf "\n\trestart\t\t\tRestart all stoped and running services" printf "\n\trestart\t\t\tRestart all stopped and running services"
printf "\n\tlogs\t\t\tShow openvidu-server logs" printf "\n\tlogs\t\t\tShow openvidu-server logs"
printf "\n\tupgrade\t\t\tUpgrade to the lastest Openvidu version" printf "\n\tupgrade\t\t\tUpgrade to the latest Openvidu version"
printf "\n\tupgrade [version]\tUpgrade to the specific Openvidu version" printf "\n\tupgrade [version]\tUpgrade to the specific Openvidu version"
printf "\n\tversion\t\t\tShow version of Openvidu Server" printf "\n\tversion\t\t\tShow version of Openvidu Server"
printf "\n\treport\t\t\tGenerate a report with the current status of Openvidu" printf "\n\treport\t\t\tGenerate a report with the current status of Openvidu"
@ -268,7 +268,7 @@ case $1 in
UPGRADE_VERSION="$2" UPGRADE_VERSION="$2"
fi fi
read -r -p " You're about to update Openvidu PRO to '${UPGRADE_VERSION}' version. Are you sure? [y/N]: " response read -r -p " You're about to update OpenVidu Pro to '${UPGRADE_VERSION}' version. Are you sure? [y/N]: " response
case "$response" in case "$response" in
[yY][eE][sS]|[yY]) [yY][eE][sS]|[yY])
upgrade_ov "${UPGRADE_VERSION}" upgrade_ov "${UPGRADE_VERSION}"

View File

@ -1,14 +1,17 @@
FROM ubuntu:16.04 FROM coturn/coturn:4.5.2-alpine
RUN apt-get update \ USER root
&& apt-get install -y coturn curl dnsutils
COPY ./configuration-files.sh /tmp/ RUN apk add --no-cache bind-tools
COPY ./entrypoint.sh /usr/local/bin
COPY ./discover_my_public_ip.sh /usr/local/bin
RUN chmod +x /tmp/configuration-files.sh \ # Override detect-external-ip.sh script
&& chmod +x /usr/local/bin/entrypoint.sh \ COPY ./detect-external-ip.sh /usr/local/bin/detect-external-ip.sh
&& chmod +x /usr/local/bin/discover_my_public_ip.sh COPY ./docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ] RUN chmod +x /usr/local/bin/detect-external-ip.sh /usr/local/bin/docker-entrypoint.sh && \
chown -R nobody:nogroup /var/lib/coturn/ && \
touch /turnserver.conf && chown nobody:nogroup /turnserver.conf
USER nobody:nogroup
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["--log-file=stdout", "--external-ip=$(detect-external-ip)"]

View File

@ -1,42 +1,4 @@
# Coturn OpenVidu # OpenVidu Coturn
This is a docker image to launch a coturn server. Environment variables can be defined to modify the files `/etc/default/coturn` and `cat>/etc/turnserver.conf`. This is a minor modification from the official [coturn/coturn](https://hub.docker.com/r/coturn/coturn) image.
It just replace the `detect-external-ip.sh` with a custom one to use DNS to resolve getting the public IP.
## Environment variables
### Turn configuration
- TURN_PUBLIC_IP: Public ip where coturn will be placed. If this environment variable is not setted, it will get the ip returned by `curl ifconfig.co`.
- TURN_LISTEN_PORT: Port where turn will be listening.
### Turn credentials
- REDIS_IP: Redis where credentials are stored
- DB_NAME: Name of the database in redis
- DB_PASSWORD: Password of the redis database
# Execution example
Actual version of OpenVidu need to be located in the same node because tokens sends the url for turn/stun connections with the host url.
## Execute turn locally next to the redis database
You need to have a redis database running:
```
docker run --rm --name some-redis -d -p 6379:6379 redis
```
Get the ip of the container and after that, run coturn, you can use url as ip too, in this example I am running coturn with nip.io:
```
docker run -it -e REDIS_IP=172.17.0.2 -e DB_NAME=0 -e DB_PASSWORD=turn -e MIN_PORT=40000 -e MAX_PORT=65535 -e TURN_PUBLIC_IP=auto -e TURN_LISTEN_PORT=3478 --network=host openvidu/openvidu-coturn
```
## Execute turn locally with fixed username and password
```
docker run -it -e TURN_PUBLIC_IP=auto -e TURN_USERNAME_PASSWORD=<USER>:<PASSWORD> -e MIN_PORT=40000 -e MAX_PORT=65535 -e TURN_LISTEN_PORT=3478 --network=host openvidu/openvidu-coturn
```
# Kubernetes
TODO

View File

@ -1,31 +0,0 @@
#!/bin/bash
# Enable turn
cat>/etc/default/coturn<<EOF
TURNSERVER_ENABLED=1
EOF
# Turn server configuration
cat>/etc/turnserver.conf<<EOF
listening-port=${TURN_LISTEN_PORT}
fingerprint
lt-cred-mech
max-port=${MAX_PORT:-65535}
min-port=${MIN_PORT:-40000}
simple-log
pidfile="/var/run/turnserver.pid"
realm=openvidu
verbose
EOF
if [[ ! -z "${TURN_PUBLIC_IP}" ]]; then
echo "external-ip=${TURN_PUBLIC_IP}" >> /etc/turnserver.conf
fi
if [[ ! -z "${REDIS_IP}" ]] && [[ ! -z "${DB_NAME}" ]] && [[ ! -z "${DB_PASSWORD}" ]]; then
echo "redis-userdb=\"ip=${REDIS_IP} dbname=${DB_NAME} password=${DB_PASSWORD} connect_timeout=30\"" >> /etc/turnserver.conf
fi
if [[ ! -z "${TURN_USERNAME_PASSWORD}" ]]; then
echo "user=${TURN_USERNAME_PASSWORD}" >> /etc/turnserver.conf
fi

View File

@ -1,10 +1,8 @@
#!/bin/bash
VERSION=$1 VERSION=$1
if [[ ! -z $VERSION ]]; then if [[ ! -z $VERSION ]]; then
cp ../utils/discover_my_public_ip.sh ./discover_my_public_ip.sh docker build --pull --no-cache --rm=true -t openvidu/openvidu-coturn:$VERSION .
docker build --rm -t openvidu/openvidu-coturn:$VERSION .
rm ./discover_my_public_ip.sh
else else
echo "Error: You need to specify a version as first argument" echo "Error: You need to specify a version as first argument"
fi fi

View File

@ -0,0 +1,115 @@
#!/usr/bin/env sh
# shellcheck shell=dash
#/ Use DNS to find out about the external IP of the running system.
#/
#/ This script is useful when running from a machine that sits behind a NAT.
#/ Due to how NAT works, machines behind it belong to an internal or private
#/ subnet, with a different address space than the external or public side.
#/
#/ Typically it is possible to make an HTTP request to a number of providers
#/ that offer the external IP in their response body (eg: ifconfig.me). However,
#/ why do a slow and heavy HTTP request, when DNS exists and is much faster?
#/ Well established providers such as OpenDNS or Google offer special hostnames
#/ that, when resolved, will actually return the IP address of the caller.
#/
#/ https://unix.stackexchange.com/questions/22615/how-can-i-get-my-external-ip-address-in-a-shell-script/81699#81699
#/
#/
#/ Arguments
#/ ---------
#/
#/ --ipv4
#/
#/ Find the external IPv4 address.
#/ Optional. Default: Enabled.
#/
#/ --ipv6
#/
#/ Find the external IPv6 address.
#/ Optional. Default: Disabled.
# Shell setup
# ===========
# Shell options for strict error checking.
for OPTION in errexit errtrace pipefail nounset; do
set -o | grep -wq "$OPTION" && set -o "$OPTION"
done
# Trace all commands (to stderr).
#set -o xtrace
# Shortcut: REAL_EXTERNAL_IP
# ==========================
if [ -n "${REAL_EXTERNAL_IP:-}" ]; then
echo "$REAL_EXTERNAL_IP"
exit 0
fi
# Parse call arguments
# ====================
CFG_IPV4="true"
while [ $# -gt 0 ]; do
case "${1-}" in
--ipv4) CFG_IPV4="true" ;;
--ipv6) CFG_IPV4="false" ;;
*)
echo "Invalid argument: '${1-}'" >&2
exit 1
;;
esac
shift
done
# Discover the external IP address
# ================================
if [ "$CFG_IPV4" = "true" ]; then
COMMANDS='dig @resolver1.opendns.com myip.opendns.com A -4 +short
dig @ns1.google.com o-o.myaddr.l.google.com TXT -4 +short | tr -d \"
dig @1.1.1.1 whoami.cloudflare TXT CH -4 +short | tr -d \"
dig @ns1-1.akamaitech.net whoami.akamai.net A -4 +short'
is_valid_ip() {
# Check if the input looks like an IPv4 address.
# Doesn't check if the actual values are valid; assumes they are.
echo "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$'
}
else
COMMANDS='dig @resolver1.opendns.com myip.opendns.com AAAA -6 +short
dig @ns1.google.com o-o.myaddr.l.google.com TXT -6 +short | tr -d \"
dig @2606:4700:4700::1111 whoami.cloudflare TXT CH -6 +short | tr -d \"'
is_valid_ip() {
# Check if the input looks like an IPv6 address.
# It's almost impossible to check the IPv6 representation because it
# varies wildly, so just check that there are at least 2 colons.
[ "$(echo "$1" | awk -F':' '{print NF-1}')" -ge 2 ]
}
fi
echo "$COMMANDS" | while read -r COMMAND; do
if IP="$(eval "$COMMAND")" && is_valid_ip "$IP"; then
echo "$IP"
exit 100 # Exits the pipe subshell.
fi
done
if [ $? -eq 100 ]; then
exit 0
else
echo "[$0] All providers failed" >&2
exit 1
fi

View File

@ -0,0 +1,11 @@
#!/bin/sh
if [ ! -z "${REDIS_IP}" ] && [ ! -z "${DB_NAME}" ] && [ ! -z "${DB_PASSWORD}" ]; then
echo "redis-userdb=\"ip=${REDIS_IP} dbname=${DB_NAME} password=${DB_PASSWORD} connect_timeout=30\"" > turnserver.conf
fi
# If command starts with an option, prepend with turnserver binary.
if [ "${1:0:1}" == '-' ]; then
set -- turnserver "$@"
fi
exec $(eval "echo $@")

View File

@ -1,31 +0,0 @@
#!/bin/bash
# Set debug mode
DEBUG=${DEBUG:-false}
[ "$DEBUG" == "true" ] && set -x
#Check parameters
[[ "${TURN_PUBLIC_IP}" == "auto-ipv4" ]] && export TURN_PUBLIC_IP=$(/usr/local/bin/discover_my_public_ip.sh)
[[ "${TURN_PUBLIC_IP}" == "auto-ipv6" ]] && export TURN_PUBLIC_IP=$(/usr/local/bin/discover_my_public_ip.sh --ipv6)
[[ -z "${ENABLE_COTURN_LOGS}" ]] && export ENABLE_COTURN_LOGS=true
echo "TURN public IP: ${TURN_PUBLIC_IP:-"empty"}"
[[ ! -z "${TURN_LISTEN_PORT}" ]] && echo "TURN listening port: ${TURN_LISTEN_PORT}" ||
{ echo "TURN_LISTEN_PORT environment variable is not defined"; exit 1; }
[[ ! -z "${MIN_PORT}" ]] && echo "Defined min port coturn: ${MIN_PORT}" || echo "Min port coturn: 40000"
[[ ! -z "${MAX_PORT}" ]] && echo "Defined max port coturn: ${MAX_PORT}" || echo "Max port coturn: 65535"
# Load configuration files of coturn
source /tmp/configuration-files.sh
# Remove temp file with configuration parameters
rm /tmp/configuration-files.sh
if [[ "${ENABLE_COTURN_LOGS}" == "true" ]]; then
/usr/bin/turnserver -c /etc/turnserver.conf -v --log-file /dev/null
else
/usr/bin/turnserver -c /etc/turnserver.conf -v --log-file /dev/null --no-stdout-log
fi

View File

@ -1,4 +1,4 @@
FROM nginx:1.18.0-alpine FROM nginx:1.20.0-alpine
# Install required software # Install required software
RUN apk update && \ RUN apk update && \

View File

@ -2,7 +2,7 @@ VERSION=$1
if [[ ! -z $VERSION ]]; then if [[ ! -z $VERSION ]]; then
cp ../utils/discover_my_public_ip.sh ./discover_my_public_ip.sh cp ../utils/discover_my_public_ip.sh ./discover_my_public_ip.sh
docker build -t openvidu/openvidu-proxy:$VERSION . docker build --pull --no-cache --rm=true -t openvidu/openvidu-proxy:$VERSION .
rm ./discover_my_public_ip.sh rm ./discover_my_public_ip.sh
else else

View File

@ -14,10 +14,6 @@
proxy_pass http://openviduserver; proxy_pass http://openviduserver;
} }
location ~ /openvidu$ {
proxy_pass http://openviduserver;
}
location /kibana { location /kibana {
{rules_access_dashboard} {rules_access_dashboard}
deny all; deny all;
@ -25,3 +21,24 @@
rewrite ^/kibana/(.*)$ /$1 break; rewrite ^/kibana/(.*)$ /$1 break;
proxy_pass http://kibana/; proxy_pass http://kibana/;
} }
location ~ ^/openvidu/elasticsearch$ {
{rules_access_dashboard}
deny all;
rewrite ^/openvidu/elasticsearch$ / break;
proxy_pass http://elasticsearch;
}
location ~ ^/openvidu/elasticsearch/.*$ {
{rules_access_dashboard}
deny all;
rewrite ^/openvidu/elasticsearch(.*)$ $1 break;
proxy_pass http://elasticsearch;
}
location ~ /openvidu$ {
proxy_pass http://openviduserver;
}

View File

@ -8,6 +8,10 @@ upstream kibana {
server localhost:5601; server localhost:5601;
} }
upstream elasticsearch {
server localhost:9200;
}
upstream openviduserver { upstream openviduserver {
server localhost:5443; server localhost:5443;
} }

View File

@ -43,6 +43,7 @@ CERTIFICATES_CONF="${CERTIFICATES_LIVE_FOLDER}/certificates.conf"
[ -z "${REDIRECT_WWW}" ] && export REDIRECT_WWW=false [ -z "${REDIRECT_WWW}" ] && export REDIRECT_WWW=false
[ -z "${PROXY_MODE}" ] && export PROXY_MODE=CE [ -z "${PROXY_MODE}" ] && export PROXY_MODE=CE
[ -z "${WORKER_CONNECTIONS}" ] && export WORKER_CONNECTIONS=10240 [ -z "${WORKER_CONNECTIONS}" ] && export WORKER_CONNECTIONS=10240
[ -z "${CLIENT_MAX_BODY_SIZE}" ] && export CLIENT_MAX_BODY_SIZE=200M
[ -z "${PUBLIC_IP}" ] && export PUBLIC_IP=auto-ipv4 [ -z "${PUBLIC_IP}" ] && export PUBLIC_IP=auto-ipv4
[ -z "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all [ -z "${ALLOWED_ACCESS_TO_DASHBOARD}" ] && export ALLOWED_ACCESS_TO_DASHBOARD=all
[ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all [ -z "${ALLOWED_ACCESS_TO_RESTAPI}" ] && export ALLOWED_ACCESS_TO_RESTAPI=all
@ -77,6 +78,7 @@ printf "\n"
# Override worker connections # Override worker connections
sed -i "s/{worker_connections}/${WORKER_CONNECTIONS}/g" /etc/nginx/nginx.conf sed -i "s/{worker_connections}/${WORKER_CONNECTIONS}/g" /etc/nginx/nginx.conf
sed -i "s/{client_max_body_size}/${CLIENT_MAX_BODY_SIZE}/g" /etc/nginx/nginx.conf
printf "\n Configure %s domain..." "${DOMAIN_OR_PUBLIC_IP}" printf "\n Configure %s domain..." "${DOMAIN_OR_PUBLIC_IP}"
OLD_DOMAIN_OR_PUBLIC_IP=$(head -n 1 "${CERTIFICATES_CONF}" | cut -f1 -d$'\t') OLD_DOMAIN_OR_PUBLIC_IP=$(head -n 1 "${CERTIFICATES_CONF}" | cut -f1 -d$'\t')

View File

@ -29,6 +29,8 @@ http {
server_tokens off; server_tokens off;
client_max_body_size {client_max_body_size};
include /etc/nginx/conf.d/*.conf; include /etc/nginx/conf.d/*.conf;
include /etc/nginx/vhost.d/*.conf; include /etc/nginx/vhost.d/*.conf;
} }

View File

@ -4,6 +4,6 @@
OPENVIDU_RECORDING_UBUNTU_VERSION=$1 OPENVIDU_RECORDING_UBUNTU_VERSION=$1
OPENVIDU_RECORDING_CHROME_VERSION=$2 # https://www.ubuntuupdates.org/package_logs?noppa=&page=1&type=ppas&vals=8# OPENVIDU_RECORDING_CHROME_VERSION=$2 # https://www.ubuntuupdates.org/package_logs?noppa=&page=1&type=ppas&vals=8#
OPENVIDU_RECORDING_DOCKER_TAG=$3 OPENVIDU_RECORDING_DOCKER_TAG=$3
docker build --rm --build-arg CHROME_VERSION="$OPENVIDU_RECORDING_CHROME_VERSION" \ docker build --rm --pull --no-cache --build-arg CHROME_VERSION="$OPENVIDU_RECORDING_CHROME_VERSION" \
-f $OPENVIDU_RECORDING_UBUNTU_VERSION.Dockerfile \ -f $OPENVIDU_RECORDING_UBUNTU_VERSION.Dockerfile \
-t openvidu/openvidu-recording:$OPENVIDU_RECORDING_DOCKER_TAG . -t openvidu/openvidu-recording:$OPENVIDU_RECORDING_DOCKER_TAG .

View File

@ -4,7 +4,7 @@
URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo} URL=${URL:-https://www.youtube.com/watch?v=JMuzlEQz3uo}
ONLY_VIDEO=${ONLY_VIDEO:-false} ONLY_VIDEO=${ONLY_VIDEO:-false}
RESOLUTION=${RESOLUTION:-1920x1080} RESOLUTION=${RESOLUTION:-1280x720}
FRAMERATE=${FRAMERATE:-25} FRAMERATE=${FRAMERATE:-25}
WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)" WIDTH="$(cut -d'x' -f1 <<< $RESOLUTION)"
HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)" HEIGHT="$(cut -d'x' -f2 <<< $RESOLUTION)"

Some files were not shown because too many files have changed in this diff Show More