Merge branch 'master' into participant-event-role

pull/723/head
Mihail J 2022-05-12 21:49:23 +02:00
commit 23ade52816
63 changed files with 2241 additions and 9114 deletions

View File

@ -1,8 +1,9 @@
name: openvidu-angular E2E
name: openvidu-components-angular E2E
on:
push:
paths:
- 'openvidu-components-angular/**'
- 'openvidu-browser/**'
pull_request:
branches:
- master
@ -23,6 +24,16 @@ jobs:
run: docker run -d --shm-size="2g" --network host selenium/standalone-chrome:latest
- name: Run openvidu-server-kms
run: docker run -p 4443:4443 --rm -d -e OPENVIDU_SECRET=MY_SECRET openvidu/openvidu-server-kms:latest
- name: Build openvidu-browser
run: |
cd openvidu-browser
npm install
npm run build && npm pack
mv openvidu-browser-*.tgz ../openvidu-components-angular
- name: Install openvidu-browser
run: |
cd openvidu-components-angular
npm install openvidu-browser-*.tgz
- name: Install dependencies
run: npm install --prefix openvidu-components-angular
- name: Build openvidu-angular
@ -31,11 +42,11 @@ jobs:
run: npm run webcomponent:build --prefix openvidu-components-angular
- name: Build openvidu-angular-testapp
run: npm run build --prefix openvidu-components-angular
- name: Run Angular Testapp
- name: Serve openvidu-angular-testapp
run: npm run start-prod --prefix openvidu-components-angular &
- name: Run Angular E2E
- name: Run openvidu-angular E2E
run: npm run lib:e2e-ci --prefix openvidu-components-angular
- name: Run Webcomponent Testapp
- name: Serve Webcomponent Testapp
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
- name: Run Webcomponent E2E
run: npm run webcomponent:e2e-ci --prefix openvidu-components-angular

View File

@ -1,12 +1,12 @@
{
"name": "openvidu-browser",
"version": "2.21.0",
"version": "2.22.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "openvidu-browser",
"version": "2.21.0",
"version": "2.22.0",
"license": "Apache-2.0",
"dependencies": {
"events": "3.3.0",

View File

@ -53,5 +53,5 @@
]
}
},
"version": "2.21.0"
"version": "2.22.0"
}

View File

@ -635,7 +635,7 @@ export class OpenVidu {
if (attempts > MAX_ATTEMPTS) {
clearTimeout(repeatUntilChangeOrMaxAttempts);
}
publisher.getVideoDimensions(publisher.stream.getMediaStream()).then(newDimensions => {
publisher.getVideoDimensions().then(newDimensions => {
if (newDimensions.width !== oldWidth || newDimensions.height !== oldHeight) {
clearTimeout(repeatUntilChangeOrMaxAttempts);
this.sendVideoDimensionsChangedEvent(publisher, reason, oldWidth, oldHeight, newDimensions.width, newDimensions.height);

View File

@ -168,68 +168,88 @@ export class Publisher extends StreamManager {
* useful if the Publisher was unpublished freeing the hardware resource, and openvidu-browser is not able to successfully re-create the video track as it was before unpublishing. In this way previous track settings will be ignored and this MediaStreamTrack
* will be used instead.
*/
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): void {
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
if (this.stream.videoActive !== enabled) {
return new Promise(async (resolve, reject) => {
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
let mustRestartMediaStream = false;
affectedMediaStream.getVideoTracks().forEach((track) => {
track.enabled = enabled;
if (!enabled && resource === true) {
track.stop();
} else if (enabled && track.readyState === 'ended') {
// Resource was freed
mustRestartMediaStream = true;
if (this.stream.videoActive !== enabled) {
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
let mustRestartMediaStream = false;
affectedMediaStream.getVideoTracks().forEach((track) => {
track.enabled = enabled;
if (!enabled && resource === true) {
track.stop();
} else if (enabled && track.readyState === 'ended') {
// Resource was freed
mustRestartMediaStream = true;
}
});
// There is a Virtual Background filter applied that must be removed in case the hardware must be freed
if (!enabled && resource === true && !!this.stream.filter && this.stream.filter.type.startsWith('VB:')) {
this.stream.lastVBFilter = this.stream.filter; // Save the filter to be re-applied in case of unmute
await this.stream.removeFilterAux(true);
}
});
if (mustRestartMediaStream) {
const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
affectedMediaStream.removeTrack(oldVideoTrack);
if (mustRestartMediaStream) {
const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
affectedMediaStream.removeTrack(oldVideoTrack);
const replaceVideoTrack = (tr: MediaStreamTrack) => {
affectedMediaStream.addTrack(tr);
if (this.stream.isLocalStreamPublished) {
this.replaceTrackInRtcRtpSender(tr);
const replaceVideoTrack = async (tr: MediaStreamTrack) => {
affectedMediaStream.addTrack(tr);
if (this.stream.isLocalStreamPublished) {
await this.replaceTrackInRtcRtpSender(tr);
}
if (!!this.stream.lastVBFilter) {
setTimeout(async () => {
let options = this.stream.lastVBFilter!.options;
const lastExecMethod = this.stream.lastVBFilter!.lastExecMethod;
if (!!lastExecMethod && lastExecMethod.method === 'update') {
options = Object.assign({}, options, lastExecMethod.params);
}
await this.stream.applyFilter(this.stream.lastVBFilter!.type, options);
delete this.stream.lastVBFilter;
}, 1);
}
}
if (!!resource && resource instanceof MediaStreamTrack) {
await replaceVideoTrack(resource);
} else {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: this.stream.lastVideoTrackConstraints });
await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
} catch (error) {
return reject(error);
}
}
}
if (!!resource && resource instanceof MediaStreamTrack) {
replaceVideoTrack(resource);
} else {
navigator.mediaDevices.getUserMedia({ audio: false, video: this.stream.lastVideoTrackConstraints })
.then(mediaStream => {
replaceVideoTrack(mediaStream.getVideoTracks()[0]);
})
.catch(error => {
console.error(error);
if (!!this.session && !!this.stream.streamId) {
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'videoActive',
newValue: enabled,
reason: 'publishVideo'
},
(error, response) => {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
this.session.sendVideoData(this.stream.streamManager);
}
});
}
this.stream.videoActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
return resolve();
}
if (!!this.session && !!this.stream.streamId) {
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'videoActive',
newValue: enabled,
reason: 'publishVideo'
},
(error, response) => {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')]);
this.session.sendVideoData(this.stream.streamManager);
}
});
}
this.stream.videoActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
}
});
}
@ -329,27 +349,7 @@ export class Publisher extends StreamManager {
* @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
*/
async replaceTrack(track: MediaStreamTrack): Promise<void> {
// 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 this.replaceTrackInMediaStream(track);
return await this.replaceTrackInRtcRtpSender(track);
} else {
// Publisher not published. Simply replace the track on the local MediaStream
return await this.replaceTrackInMediaStream(track);
}
} catch (error) {
track.enabled = trackOriginalEnabledValue;
throw error;
}
return this.replaceTrackAux(track, true);
}
/* Hidden methods */
@ -438,7 +438,7 @@ export class Publisher extends StreamManager {
if (this.stream.isSendVideo()) {
// Has video track
this.getVideoDimensions(mediaStream).then(dimensions => {
this.getVideoDimensions().then(dimensions => {
this.stream.videoDimensions = {
width: dimensions.width,
height: dimensions.height
@ -636,6 +636,33 @@ export class Publisher extends StreamManager {
});
}
/**
* @hidden
*/
async replaceTrackAux(track: MediaStreamTrack, updateLastConstraints: boolean): Promise<void> {
// 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 this.replaceTrackInMediaStream(track, updateLastConstraints);
return await this.replaceTrackInRtcRtpSender(track);
} else {
// Publisher not published. Simply replace the track on the local MediaStream
return await this.replaceTrackInMediaStream(track, updateLastConstraints);
}
} catch (error) {
track.enabled = trackOriginalEnabledValue;
throw error;
}
}
/**
* @hidden
*
@ -643,7 +670,7 @@ export class Publisher extends StreamManager {
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
* use the HTMLVideoElement properties videoWidth and videoHeight
*/
getVideoDimensions(mediaStream: MediaStream): Promise<{ width: number, height: number }> {
getVideoDimensions(): Promise<{ width: number, height: number }> {
return new Promise((resolve, reject) => {
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
@ -729,19 +756,21 @@ export class Publisher extends StreamManager {
/**
* @hidden
*/
async replaceTrackInMediaStream(track: MediaStreamTrack): Promise<void> {
async replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): Promise<void> {
const mediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
let removedTrack: MediaStreamTrack;
if (track.kind === 'video') {
removedTrack = mediaStream.getVideoTracks()[0];
this.stream.lastVideoTrackConstraints = track.getConstraints();
if (updateLastConstraints) {
this.stream.lastVideoTrackConstraints = track.getConstraints();
}
} else {
removedTrack = mediaStream.getAudioTracks()[0];
}
mediaStream.removeTrack(removedTrack);
removedTrack.stop();
mediaStream.addTrack(track);
if (track.kind === 'video' && this.stream.isLocalStreamPublished) {
if (track.kind === 'video' && this.stream.isLocalStreamPublished && updateLastConstraints) {
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
}

View File

@ -232,6 +232,10 @@ export class Stream {
* @hidden
*/
lastVideoTrackConstraints: MediaTrackConstraints | boolean | undefined;
/**
* @hidden
*/
lastVBFilter?: Filter;
/**
@ -421,9 +425,9 @@ export class Stream {
videoClone.style.display = 'none';
if (this.streamManager.remote) {
this.streamManager.replaceTrackInMediaStream((this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0]);
this.streamManager.replaceTrackInMediaStream((this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0], false);
} else {
(this.streamManager as Publisher).replaceTrack((this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0]);
(this.streamManager as Publisher).replaceTrackAux((this.virtualBackgroundSinkElements.video.srcObject as MediaStream).getVideoTracks()[0], false);
}
resolveApplyFilter(undefined, false);
@ -553,9 +557,9 @@ export class Stream {
const mediaStreamClone = this.virtualBackgroundSourceElements!.mediaStreamClone;
if (!isDisposing) {
if (this.streamManager.remote) {
await this.streamManager.replaceTrackInMediaStream(mediaStreamClone.getVideoTracks()[0]);
await this.streamManager.replaceTrackInMediaStream(mediaStreamClone.getVideoTracks()[0], false);
} else {
await (this.streamManager as Publisher).replaceTrack(mediaStreamClone.getVideoTracks()[0]);
await (this.streamManager as Publisher).replaceTrackAux(mediaStreamClone.getVideoTracks()[0], false);
}
} else {
mediaStreamClone.getTracks().forEach((track) => track.stop());

View File

@ -529,7 +529,7 @@ export abstract class StreamManager extends EventDispatcher {
/**
* @hidden
*/
abstract replaceTrackInMediaStream(track: MediaStreamTrack): Promise<void>;
abstract replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): Promise<void>;
/* Private methods */

View File

@ -78,12 +78,14 @@ export class Subscriber extends StreamManager {
/**
* @hidden
*/
async replaceTrackInMediaStream(track: MediaStreamTrack): Promise<void> {
async replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): Promise<void> {
const mediaStream: MediaStream = this.stream.getMediaStream();
let removedTrack: MediaStreamTrack;
if (track.kind === 'video') {
removedTrack = mediaStream.getVideoTracks()[0];
this.stream.lastVideoTrackConstraints = track.getConstraints();
if (updateLastConstraints) {
this.stream.lastVideoTrackConstraints = track.getConstraints();
}
} else {
removedTrack = mediaStream.getAudioTracks()[0];
}

File diff suppressed because it is too large Load Diff

View File

@ -50,9 +50,9 @@
"@types/chai": "4.3.0",
"@types/mocha": "9.1.0",
"@types/node": "16.11.6",
"@types/selenium-webdriver": "4.0.18",
"@types/selenium-webdriver": "4.0.20",
"chai": "4.3.6",
"chromedriver": "99.0.0",
"chromedriver": "101.0.0",
"codelyzer": "6.0.2",
"concat": "^1.0.3",
"cross-env": "^7.0.3",
@ -70,7 +70,7 @@
"karma-notify-reporter": "1.3.0",
"mocha": "9.2.2",
"ng-packagr": "13.0.3",
"selenium-webdriver": "4.1.1",
"selenium-webdriver": "4.1.2",
"ts-node": "10.4.0",
"tslint": "6.1.3",
"typescript": "4.4.4",

View File

@ -1,28 +1,3 @@
#activities-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.header-container {
padding: 10px;
display: flex;
}
.header-container h3 {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
font-weight: bold;
}
.header-container button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
.activities-body-container {
display: block !important;
overflow-y: auto;
@ -40,7 +15,6 @@
margin: auto;
}
.activity-subtitle {
font-style: italic;
font-size: 11px !important;
@ -49,7 +23,7 @@
font-weight: bold !important;
}
.activity-action-buttons{
.activity-action-buttons {
align-self: flex-start;
margin-top: 15px;
font-weight: 600;
@ -63,7 +37,8 @@
padding: 0px 10px !important;
}
::ng-deep .mat-list-base .mat-list-item .mat-list-item-content, .mat-list-base .mat-list-option .mat-list-item-content {
::ng-deep .mat-list-base .mat-list-item .mat-list-item-content,
.mat-list-base .mat-list-option .mat-list-item-content {
padding: 0px !important;
}

View File

@ -1,7 +1,7 @@
<div class="panel" id="activities-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3>Activities</h3>
<button mat-icon-button matTooltip="Close" (click)="close()">
<div class="panel-container" id="activities-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="panel-header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3 class="panel-title">Activities</h3>
<button class="panel-close-button" mat-icon-button matTooltip="Close" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>

View File

@ -5,7 +5,7 @@ import { PanelService } from '../../../services/panel/panel.service';
@Component({
selector: 'ov-activities-panel',
templateUrl: './activities-panel.component.html',
styleUrls: ['./activities-panel.component.css']
styleUrls: ['../panel.component.css','./activities-panel.component.css']
})
export class ActivitiesPanelComponent implements OnInit {
constructor(private panelService: PanelService) {}

View File

@ -1,28 +1,3 @@
#background-effects-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.header-container {
padding: 10px;
display: flex;
}
.header-container h3 {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
font-weight: bold;
}
.header-container button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
.effects-container {
display: block !important;
overflow-y: auto;
@ -70,21 +45,3 @@
::ng-deep .mat-slider-vertical .mat-slider-track-wrapper {
width: 10px !important;
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-thumb {
background: #a7a7a7;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #7c7c7c;
}
::-webkit-scrollbar-track {
background: var(--ov-light-color);
border-radius: 4px;
}

View File

@ -1,7 +1,7 @@
<div class="panel" id="background-effects-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3>Background effects</h3>
<button mat-icon-button matTooltip="Close" (click)="close()">
<div class="panel-container" id="background-effects-container" fxLayout="column" fxLayoutAlign="space-evenly none">
<div class="panel-header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3 class="panel-title">Background effects</h3>
<button class="panel-close-button" mat-icon-button matTooltip="Close" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>

View File

@ -6,22 +6,22 @@ import { PanelService } from '../../../services/panel/panel.service';
import { VirtualBackgroundService } from '../../../services/virtual-background/virtual-background.service';
@Component({
selector: 'ov-background-effects-panel',
templateUrl: './background-effects-panel.component.html',
styleUrls: ['./background-effects-panel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
selector: 'ov-background-effects-panel',
templateUrl: './background-effects-panel.component.html',
styleUrls: ['../panel.component.css','./background-effects-panel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class BackgroundEffectsPanelComponent implements OnInit {
backgroundSelectedId: string;
backgroundImages: BackgroundEffect[] = [];
backgroundImages: BackgroundEffect[] = [];
noEffectAndBlurredBackground: BackgroundEffect[] = [];
private backgrounds: BackgroundEffect[];
private backgroundSubs: Subscription;
constructor(private panelService: PanelService, private backgroundService: VirtualBackgroundService, private cd: ChangeDetectorRef) {}
constructor(private panelService: PanelService, private backgroundService: VirtualBackgroundService, private cd: ChangeDetectorRef) { }
ngOnInit(): void {
ngOnInit(): void {
this.subscribeToBackgroundSelected();
this.backgrounds = this.backgroundService.getBackgrounds();
this.noEffectAndBlurredBackground = this.backgrounds.filter(f => f.type === EffectType.BLUR || f.type === EffectType.NONE);
@ -29,7 +29,7 @@ export class BackgroundEffectsPanelComponent implements OnInit {
}
ngOnDestroy() {
if(this.backgroundSubs) this.backgroundSubs.unsubscribe();
if (this.backgroundSubs) this.backgroundSubs.unsubscribe();
}
subscribeToBackgroundSelected() {
this.backgroundSubs = this.backgroundService.backgroundSelectedObs.subscribe((id) => {
@ -38,18 +38,16 @@ export class BackgroundEffectsPanelComponent implements OnInit {
});
}
close() {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
close() {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
async applyBackground(effect: BackgroundEffect) {
if(effect.type === EffectType.NONE){
if (effect.type === EffectType.NONE) {
await this.removeBackground();
} else {
await this.backgroundService.applyBackground(effect);
}
}
async removeBackground() {

View File

@ -1,28 +1,4 @@
#chat-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.header-container {
padding: 10px;
display: flex;
}
.header-container h3 {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
}
.header-container button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
.text-container{
.text-container {
/* padding: 5px; */
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color);
@ -33,7 +9,6 @@
.text-info {
margin: 3px;
}
.messages-container {
@ -65,35 +40,34 @@
resize: none;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
}
.message {
.message {
position: relative;
padding: 3px 0;
}
}
.msg-detail {
.msg-detail {
width: 95%;
display: inline-block;
}
}
.msg-detail p {
.msg-detail p {
margin: 0;
font-size: 14px;
}
}
.nickname-container p {
.nickname-container p {
font-size: 13px;
font-weight: bold;
color: var(--ov-panel-text-color);
}
}
.msg-content {
.msg-content {
position: relative;
border-radius: var(--ov-panel-radius);
padding: 8px;
@ -102,12 +76,11 @@
max-width: 95%;
font-size: 13px;
word-break: break-all;
}
}
#send-btn {
#send-btn {
border-radius: var(--ov-buttons-radius);
}
}
/* Start message from other user */
@ -119,30 +92,18 @@
float: left;
}
/* End message from other user */
/* End message from other user */
/* Start my messages */
/* Start my messages */
.message.right .msg-detail .nickname-container {
.message.right .msg-detail .nickname-container {
text-align: right;
}
}
.message.right .msg-detail .msg-content {
.message.right .msg-detail .msg-content {
float: right;
}
}
/* End my messages */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-thumb {
background-color: #6b6b6b;
}
::ng-deep a:-webkit-any-link {
::ng-deep a:-webkit-any-link {
color: #1a73e8;
}

View File

@ -1,7 +1,7 @@
<div id="chat-container" fxLayout="column" fxLayoutAlign="space-evenly none" >
<div class="header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3>Chat</h3>
<button mat-icon-button matTooltip="Close" (click)="close()">
<div class="panel-container" id="chat-container" fxLayout="column" fxLayoutAlign="space-evenly none" >
<div class="panel-header-container" fxFlex="55px" fxLayoutAlign="start center">
<h3 class="panel-title">Chat</h3>
<button class="panel-close-button" mat-icon-button matTooltip="Close" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>

View File

@ -41,7 +41,7 @@ import { PanelService } from '../../../services/panel/panel.service';
@Component({
selector: 'ov-chat-panel',
templateUrl: './chat-panel.component.html',
styleUrls: ['./chat-panel.component.css'],
styleUrls: ['../panel.component.css','./chat-panel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChatPanelComponent implements OnInit, AfterViewInit {

View File

@ -0,0 +1,41 @@
.panel-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.panel-header-container {
padding: 10px;
display: flex;
}
.panel-title {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
}
.panel-close-button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-thumb {
background: #a7a7a7;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #7c7c7c;
}
::-webkit-scrollbar-track {
background: var(--ov-light-color);
border-radius: 4px;
}

View File

@ -1,29 +1,6 @@
.participants-container {
margin: 20px;
background-color: var(--ov-panel-background);
border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px);
min-height: calc(100% - 40px);
}
.header-container {
padding: 10px;
display: flex;
}
.header-container h3 {
margin-left: 5px;
margin-top: auto;
margin-bottom: auto;
}
.header-container button {
margin-left: auto;
border-radius: var(--ov-buttons-radius);
}
.local-participant-container, .remote-participants-container {
margin: 5px 10px;
.local-participant-container,
.remote-participants-container {
margin: 5px 10px;
}
.scrollable {
@ -32,7 +9,7 @@
overflow: auto;
}
.message-container{
.message-container {
padding: 5px;
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color);
@ -41,7 +18,6 @@
font-size: 12px;
}
.message-container p{
.message-container p {
margin: 0;
}

View File

@ -1,7 +1,7 @@
<div class="participants-container" id="participants-container">
<div class="header-container">
<h3>Participants</h3>
<button mat-icon-button matTooltip="Close" (click)="close()">
<div class="panel-container" id="participants-container">
<div class="panel-header-container">
<h3 class="panel-title">Participants</h3>
<button class="panel-close-button" mat-icon-button matTooltip="Close" (click)="close()">
<mat-icon>close</mat-icon>
</button>
</div>

View File

@ -41,7 +41,7 @@ import { Subscription } from 'rxjs';
@Component({
selector: 'ov-participants-panel',
templateUrl: './participants-panel.component.html',
styleUrls: ['./participants-panel.component.css'],
styleUrls: ['../../panel.component.css','./participants-panel.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewInit {

View File

@ -14,6 +14,7 @@
<ng-template #stream let-stream>
<button
*ngIf="!isMinimal && showBackgroundEffectsButton"
[disabled]="isVideoMuted"
mat-icon-button
id="background-effects-btn"
(click)="toggleBackgroundEffects()"
@ -72,7 +73,7 @@
<button
mat-icon-button
id="camera-button"
[disabled]="!hasVideoDevices"
[disabled]="!hasVideoDevices || videoMuteChanging"
[class.warn-btn]="isVideoMuted"
(click)="toggleCam()"
>

View File

@ -20,6 +20,7 @@ import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { PanelService } from '../../services/panel/panel.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { StorageService } from '../../services/storage/storage.service';
import { VirtualBackgroundService } from '../../services/virtual-background/virtual-background.service';
/**
* @internal
@ -37,6 +38,7 @@ export class PreJoinComponent implements OnInit, OnDestroy {
microphoneSelected: CustomDevice;
isVideoMuted: boolean;
isAudioMuted: boolean;
videoMuteChanging: boolean;
localParticipant: ParticipantAbstractModel;
windowSize: number;
hasVideoDevices: boolean;
@ -72,7 +74,8 @@ export class PreJoinComponent implements OnInit, OnDestroy {
private participantService: ParticipantService,
protected panelService: PanelService,
private libService: OpenViduAngularConfigService,
private storageSrv: StorageService
private storageSrv: StorageService,
private backgroundService: VirtualBackgroundService
) {
this.log = this.loggerSrv.get('PreJoinComponent');
}
@ -95,17 +98,12 @@ export class PreJoinComponent implements OnInit, OnDestroy {
this.isLoading = false;
}
ngOnDestroy() {
if (this.localParticipantSubscription) {
this.localParticipantSubscription.unsubscribe();
}
if (this.screenShareStateSubscription) {
this.screenShareStateSubscription.unsubscribe();
}
async ngOnDestroy() {
if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe();
if (this.screenShareStateSubscription) this.screenShareStateSubscription.unsubscribe();
if (this.backgroundEffectsButtonSub) this.backgroundEffectsButtonSub.unsubscribe();
if (this.minimalSub) this.minimalSub.unsubscribe();
this.panelService.closePanel();
}
async onCameraSelected(event: any) {
@ -118,7 +116,16 @@ export class PreJoinComponent implements OnInit, OnDestroy {
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const pp: PublisherProperties = { videoSource, audioSource: this.microphoneSelected.device, mirror };
// Reapply Virtual Background to new Publisher if necessary
const backgroundSelected = this.backgroundService.backgroundSelected.getValue();
if (this.backgroundService.isBackgroundApplied()) {
await this.backgroundService.removeBackground();
}
await this.openviduService.republishTrack(pp);
if (this.backgroundService.isBackgroundApplied()) {
await this.backgroundService.applyBackground(this.backgroundService.backgrounds.find(b => b.id === backgroundSelected));
}
this.deviceSrv.setCameraSelected(videoSource);
this.cameraSelected = this.deviceSrv.getCameraSelected();
@ -142,10 +149,15 @@ export class PreJoinComponent implements OnInit, OnDestroy {
}
async toggleCam() {
this.videoMuteChanging = true;
const publish = this.isVideoMuted;
await this.openviduService.publishVideo(publish);
this.isVideoMuted = !this.isVideoMuted;
this.storageSrv.setVideoMuted(this.isVideoMuted);
if (this.isVideoMuted && this.panelService.isExternalPanelOpened()) {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
this.videoMuteChanging = false;
}
toggleMic() {

View File

@ -29,7 +29,7 @@
id="camera-btn"
mat-icon-button
(click)="toggleCamera()"
[disabled]="isConnectionLost || !hasVideoDevices"
[disabled]="isConnectionLost || !hasVideoDevices || videoMuteChanging"
[class.warn-btn]="!isWebcamVideoActive"
>
<mat-icon *ngIf="isWebcamVideoActive" matTooltip="Mute your cam" id="videocam">videocam</mat-icon>
@ -93,7 +93,13 @@
</button> -->
<!-- Virtual background button -->
<button *ngIf="!isMinimal && showBackgroundEffectsButton" mat-menu-item id="virtual-bg-btn" (click)="toggleBackgroundEffects()">
<button
*ngIf="!isMinimal && showBackgroundEffectsButton"
[disabled]="!isWebcamVideoActive"
mat-menu-item
id="virtual-bg-btn"
(click)="toggleBackgroundEffects()"
>
<mat-icon>auto_awesome</mat-icon>
<span>Background effects</span>
</button>

View File

@ -277,6 +277,10 @@ export class ToolbarComponent implements OnInit, OnDestroy {
* @ignore
*/
showSessionName: boolean = true;
/**
* @ignore
*/
videoMuteChanging: boolean = false;
/**
* @ignore
@ -395,15 +399,19 @@ export class ToolbarComponent implements OnInit, OnDestroy {
* @ignore
*/
async toggleCamera() {
this.videoMuteChanging = true;
this.onCameraButtonClicked.emit();
try {
const publishVideo = !this.participantService.isMyVideoActive();
if(this.panelService.isExternalPanelOpened() && !publishVideo) {
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
}
await this.openviduService.publishVideo(publishVideo);
} catch (error) {
this.log.e('There was an error toggling camera:', error.code, error.message);
this.actionService.openDialog('There was an error toggling camera', error?.error || error?.message);
}
this.videoMuteChanging = false;
}
/**

View File

@ -1,162 +0,0 @@
<!-- Modal -->
<div class="modal">
<div class="modal-dialog modal-xl">
<div class="modal-content cardContainer">
<!-- Modal Header -->
<div class="modal-header card-header">
<div class="headerLogo">
<img id="header_img" alt="OpenVidu Logo" src="assets/images/openvidu_logo.png" />
</div>
<h3 class="headerTitle" *ngIf="sessionId">{{ sessionId }}</h3>
<button mat-mini-fab (click)="close()" class="closeButton" id="closeButton">
<mat-icon matTooltip="Close">close</mat-icon>
</button>
</div>
<!-- Modal body -->
<div class="modal-body" #bodyCard>
<div class="row align-items-center">
<div class="col-sm-6 col-md-6 col-lg-6 leftSection">
<div class="spinner-container" *ngIf="isLoading">
<mat-spinner [diameter]="50"></mat-spinner>
</div>
<div class="videoContainer" *ngIf="!isLoading">
<div *ngFor="let stream of localParticipant | streams">
<!-- Only webcam video will be shown if webcamera is available -->
<ov-video
*ngIf="(stream.type === 'CAMERA' && hasVideoDevices) || stream.type === 'SCREEN'"
[streamManager]="stream.streamManager"
[ngClass]="{ ovVideoSmall: localParticipant.streams.size > 1 && stream.type === 'CAMERA' }"
></ov-video>
<div class="cameraMessageContainer" *ngIf="stream.type === 'CAMERA' && !hasVideoDevices">
<span *ngIf="!hasVideoDevices && !hasAudioDevices">Oops! Camera and microphone are not available</span>
<span *ngIf="!hasVideoDevices && hasAudioDevices">Oops! Camera is not available</span>
<span *ngIf="hasVideoDevices && !hasAudioDevices">Oops! Microphone is not available</span>
</div>
<div id="audio-wave-container" *ngIf="stream.type === 'CAMERA'">
<ov-audio-wave [streamManager]="stream.streamManager"></ov-audio-wave>
</div>
</div>
</div>
</div>
<div class="col-sm-6 col-md-6 col-lg-6 rightSection">
<!-- Devices section / Camera-->
<div fxLayout="row" fxFill id="devicesSection" *ngIf="hasVideoDevices">
<div fxLayout fxFlex>
<div class="one" fxFlex="20" fxLayoutAlign="center center">
<button
mat-mini-fab
(click)="toggleCam()"
class="deviceButton"
id="configCardCameraButton"
[class.warn-btn]="isVideoMuted"
>
<mat-icon *ngIf="!isVideoMuted" matTooltip="Camera Enabled">videocam</mat-icon>
<mat-icon *ngIf="isVideoMuted" matTooltip="Camera Disabled">videocam_off</mat-icon>
</button>
</div>
<div class="two" fxFlex="80" fxLayoutAlign="center center">
<mat-form-field class="alternate-theme">
<mat-label>Camera Options</mat-label>
<mat-select [disabled]="isVideoMuted" (selectionChange)="onCameraSelected($event)">
<mat-option *ngFor="let camera of cameras" [value]="camera.device">
{{ camera.label }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
<!-- Devices section / Microphone-->
<div fxLayout="row" fxFill id="devicesSection" *ngIf="hasAudioDevices">
<div fxLayout fxFlex>
<div class="one" fxFlex="20" fxLayoutAlign="center center">
<button
mat-mini-fab
(click)="toggleMic()"
class="deviceButton"
id="configCardMicrophoneButton"
[class.warn-btn]="isAudioMuted"
>
<mat-icon *ngIf="!isAudioMuted" matTooltip="Microphone Enabled">mic</mat-icon>
<mat-icon *ngIf="isAudioMuted" matTooltip="Microphone Disabled">mic_off</mat-icon>
</button>
</div>
<div class="two" fxFlex="80" fxLayoutAlign="center center">
<mat-form-field class="alternate-theme">
<mat-label>Microphone Options</mat-label>
<mat-select [disabled]="isAudioMuted" (selectionChange)="onMicrophoneSelected($event)">
<mat-option *ngFor="let microphone of microphones" [value]="microphone.device">
{{ microphone.label }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</div>
<!-- Devices section / ScreenShare-->
<div fxLayout="row" fxFill id="devicesSection">
<div fxLayout fxFlex>
<div class="one" fxFlex="20" fxLayoutAlign="center center">
<button
mat-mini-fab
(click)="toggleScreenShare()"
class="deviceButton"
id="configCardScreenButton"
[class.active-btn]="screenShareEnabled"
>
<mat-icon *ngIf="!screenShareEnabled" matTooltip="Enable screen share">screen_share</mat-icon>
<mat-icon *ngIf="screenShareEnabled" matTooltip="Disable screen share">screen_share</mat-icon>
</button>
</div>
<div class="two" fxFlex="80" fxLayoutAlign="center center">
<mat-form-field class="alternate-theme">
<input matInput disabled placeholder="Screen" [ngModel]="screenShareEnabled ? 'Screen' : 'None'" />
</mat-form-field>
</div>
</div>
</div>
<!-- Devices section / Nickname-->
<div fxLayout="row" fxFill id="devicesSection">
<div fxLayout fxFlex>
<div class="one" fxFlex="20" fxLayoutAlign="center center">
<button mat-mini-fab class="deviceButton" disabled>
<mat-icon matTooltip="Nickname">person</mat-icon>
</button>
</div>
<div class="two" fxFlex="80" fxLayoutAlign="center center">
<form id="nicknameForm" class="alternate-theme">
<mat-form-field>
<input
matInput
placeholder="Nickname"
[formControl]="nicknameFormControl"
[errorStateMatcher]="matcher"
(keypress)="eventKeyPress($event)"
autocomplete="off"
/>
<mat-error *ngIf="nicknameFormControl.hasError('required')">
Nickname is <strong>required</strong>
</mat-error>
<mat-error *ngIf="nicknameFormControl.hasError('maxlength')">
Nickname is <strong>too long!</strong>
</mat-error>
</mat-form-field>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal footer -->
<div class="modal-footer" style="justify-content: center">
<button mat-flat-button (click)="joinSession()" form="nicknameForm" id="joinButton">JOIN</button>
</div>
</div>
</div>
</div>

View File

@ -1,47 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActionService } from '../../services/action/action.service';
import { ActionServiceMock } from '../../services/action/action.service.mock';
import { DeviceService } from '../../services/device/device.service';
import { DeviceServiceMock } from '../../services/device/device.service.mock';
import { LocalUserService } from '../../services/local-user/local-user.service';
import { LocalUserServiceMock } from '../../services/local-user/local-user.service.mock';
import { LoggerService } from '../../services/logger/logger.service';
import { LoggerServiceMock } from '../../services/logger/logger.service.mock';
import { StorageService } from '../../services/storage/storage.service';
import { WebrtcService } from '../../services/webrtc/webrtc.service';
import { StorageServiceMock } from '../../services/storage/storage.service.mock';
import { WebrtcServiceMock } from '../../services/webrtc/webrtc.service.mock';
import { UserSettingsComponent } from './user-settings.component';
describe('UserSettingsComponent', () => {
let component: UserSettingsComponent;
let fixture: ComponentFixture<UserSettingsComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ UserSettingsComponent ],
providers: [
{ provide: LoggerService, useClass: LoggerServiceMock },
{ provide: ActionService, useClass: ActionServiceMock },
{ provide: LocalUserService, useClass: LocalUserServiceMock },
{ provide: WebrtcService, useClass: WebrtcServiceMock },
{ provide: DeviceService, useClass: DeviceServiceMock },
{ provide: StorageService, useClass: StorageServiceMock }
]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(UserSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,321 +0,0 @@
import { ChangeDetectionStrategy, Component, ElementRef, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { Subscription } from 'rxjs';
import { OpenViduErrorName } from 'openvidu-browser/lib/OpenViduInternal/Enums/OpenViduError';
import { Publisher, PublisherProperties } from 'openvidu-browser';
import { ILogger } from '../../models/logger.model';
import { CustomDevice } from '../../models/device.model';
import { ScreenType, VideoType } from '../../models/video-type.model';
import { NicknameMatcher } from '../../matchers/nickname.matcher';
import { DeviceService } from '../../services/device/device.service';
import { LoggerService } from '../../services/logger/logger.service';
import { StorageService } from '../../services/storage/storage.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { ActionService } from '../../services/action/action.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { ParticipantAbstractModel } from '../../models/participant.model';
/**
* @internal
*/
@Component({
selector: 'ov-user-settings',
templateUrl: './user-settings.component.html',
styleUrls: ['./user-settings.component.css'],
// changeDetection: ChangeDetectionStrategy.OnPush
})
export class UserSettingsComponent implements OnInit, OnDestroy {
@ViewChild('bodyCard') bodyCard: ElementRef;
@Input() sessionId: string;
@Output() onJoinClicked = new EventEmitter<any>();
@Output() onCloseClicked = new EventEmitter<any>();
cameras: CustomDevice[];
microphones: CustomDevice[];
cameraSelected: CustomDevice;
microphoneSelected: CustomDevice;
isVideoMuted: boolean;
isAudioMuted: boolean;
screenShareEnabled: boolean;
localParticipant: ParticipantAbstractModel;
columns: number;
nicknameFormControl = new FormControl('', [Validators.maxLength(20), Validators.required]);
matcher = new NicknameMatcher();
hasVideoDevices: boolean;
hasAudioDevices: boolean;
isLoading = true;
private log: ILogger;
private localParticipantSubscription: Subscription;
private screenShareStateSubscription: Subscription;
constructor(
private actionService: ActionService,
private deviceSrv: DeviceService,
private loggerSrv: LoggerService,
private openviduService: OpenViduService,
private participantService: ParticipantService,
private storageSrv: StorageService
) {
this.log = this.loggerSrv.get('UserSettingsComponent');
}
@HostListener('window:beforeunload')
beforeunloadHandler() {
this.close();
}
async ngOnInit() {
await this.deviceSrv.initializeDevices();
this.subscribeToLocalParticipantEvents();
this.openviduService.initialize();
const nickname = this.storageSrv.getNickname() || this.generateRandomNickname();
this.nicknameFormControl.setValue(nickname);
this.columns = window.innerWidth > 900 ? 2 : 1;
this.setDevicesInfo();
if (this.hasAudioDevices || this.hasVideoDevices) {
await this.initwebcamPublisher();
}
this.isLoading = false;
}
ngOnDestroy() {
if (this.localParticipantSubscription) {
this.localParticipantSubscription.unsubscribe();
}
if (this.screenShareStateSubscription) {
this.screenShareStateSubscription.unsubscribe();
}
this.deviceSrv.clear();
}
async onCameraSelected(event: any) {
const videoSource = event?.value;
// Is New deviceId different from the old one?
if (this.deviceSrv.needUpdateVideoTrack(videoSource)) {
const mirror = this.deviceSrv.cameraNeedsMirror(videoSource);
//TODO: Uncomment this when replaceTrack issue is fixed
// const pp: PublisherProperties = { videoSource, audioSource: false, mirror };
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const pp: PublisherProperties = { videoSource, audioSource: this.microphoneSelected.device, mirror };
await this.openviduService.republishTrack(pp);
this.cameraSelected = videoSource;
this.deviceSrv.setCameraSelected(this.cameraSelected);
}
if (this.isVideoMuted) {
// Publish Webcam video
this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), true);
this.isVideoMuted = false;
}
}
async onMicrophoneSelected(event: any) {
const audioSource = event?.value;
// Is New deviceId different than older?
if (this.deviceSrv.needUpdateAudioTrack(audioSource)) {
//TODO: Uncomment this when replaceTrack issue is fixed
// const pp: PublisherProperties = { audioSource, videoSource: false };
// await this.openviduService.replaceTrack(VideoType.CAMERA, pp);
// TODO: Remove this when replaceTrack issue is fixed
const mirror = this.deviceSrv.cameraNeedsMirror(this.cameraSelected.device);
const pp: PublisherProperties = { videoSource: this.cameraSelected.device, audioSource, mirror };
await this.openviduService.republishTrack(pp);
this.microphoneSelected = audioSource;
this.deviceSrv.setMicSelected(this.microphoneSelected);
}
if (this.isAudioMuted) {
// Enable microphone
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), true);
this.isAudioMuted = true;
}
}
toggleCam() {
const publish = this.isVideoMuted;
this.openviduService.publishVideo(this.participantService.getMyCameraPublisher(), publish);
if (this.participantService.haveICameraAndScreenActive()) {
// Cam will not published, disable webcam with screensharing active
this.participantService.disableWebcamUser();
this.openviduService.publishAudio(this.participantService.getMyScreenPublisher(), publish);
} else if (this.participantService.isOnlyMyScreenActive()) {
// Cam will be published, enable webcam
this.participantService.enableWebcamUser();
}
this.isVideoMuted = !this.isVideoMuted;
this.storageSrv.setVideoMuted(this.isVideoMuted);
}
async toggleScreenShare() {
// Disabling screenShare
if (this.participantService.haveICameraAndScreenActive()) {
this.participantService.disableScreenUser();
return;
}
// Enabling screenShare
if (this.participantService.isOnlyMyCameraActive()) {
const willThereBeWebcam = this.participantService.isMyCameraActive() && this.participantService.hasCameraVideoActive();
const hasAudio = willThereBeWebcam ? false : this.hasAudioDevices && this.isAudioMuted;
const properties: PublisherProperties = {
videoSource: ScreenType.SCREEN,
audioSource: this.hasAudioDevices ? undefined : null,
publishVideo: true,
publishAudio: hasAudio,
mirror: false
};
const screenPublisher = await this.openviduService.initPublisher(undefined, properties);
screenPublisher.on('accessAllowed', (event) => {
screenPublisher.stream
.getMediaStream()
.getVideoTracks()[0]
.addEventListener('ended', () => {
this.log.d('Clicked native stop button. Stopping screen sharing');
this.toggleScreenShare();
});
this.participantService.activeMyScreenShare(screenPublisher);
if (!this.participantService.hasCameraVideoActive()) {
this.participantService.disableWebcamUser();
}
});
screenPublisher.on('accessDenied', (error: any) => {
if (error && error.name === 'SCREEN_SHARING_NOT_SUPPORTED') {
this.actionService.openDialog('Error sharing screen', 'Your browser does not support screen sharing');
}
});
return;
}
// Disabling screnShare and enabling webcam
this.participantService.enableWebcamUser();
this.participantService.disableScreenUser();
}
toggleMic() {
const publish = this.isAudioMuted;
this.openviduService.publishAudio(this.participantService.getMyCameraPublisher(), publish);
this.isAudioMuted = !this.isAudioMuted;
this.storageSrv.setAudioMuted(this.isAudioMuted);
}
eventKeyPress(event) {
if (event && event.keyCode === 13 && this.nicknameFormControl.valid) {
this.joinSession();
}
}
onResize(event) {
this.columns = event.target.innerWidth > 900 ? 2 : 1;
}
joinSession() {
if (this.nicknameFormControl.valid) {
const nickname = this.nicknameFormControl.value;
this.participantService.setMyNickname(nickname);
this.storageSrv.setNickname(nickname);
return this.onJoinClicked.emit();
}
this.scrollToBottom();
}
close() {
this.onCloseClicked.emit();
}
private setDevicesInfo() {
this.hasVideoDevices = this.deviceSrv.hasVideoDeviceAvailable();
this.hasAudioDevices = this.deviceSrv.hasAudioDeviceAvailable();
this.microphones = this.deviceSrv.getMicrophones();
this.cameras = this.deviceSrv.getCameras();
this.cameraSelected = this.deviceSrv.getCameraSelected();
this.microphoneSelected = this.deviceSrv.getMicrophoneSelected();
this.isVideoMuted = this.deviceSrv.isVideoMuted();
this.isAudioMuted = this.deviceSrv.isAudioMuted();
}
private scrollToBottom(): void {
try {
this.bodyCard.nativeElement.scrollTop = this.bodyCard.nativeElement.scrollHeight;
} catch (err) {}
}
private subscribeToLocalParticipantEvents() {
this.localParticipantSubscription = this.participantService.localParticipantObs.subscribe((p) => {
this.localParticipant = p;
this.screenShareEnabled = p.isScreenActive();
});
}
private async initwebcamPublisher() {
const publisher = await this.openviduService.initDefaultPublisher(undefined);
if (publisher) {
// this.handlePublisherSuccess(publisher);
this.handlePublisherError(publisher);
}
}
//? After test in Chrome and Firefox, the devices always have labels.
//? It's not longer needed
// private handlePublisherSuccess(publisher: Publisher) {
// publisher.once('accessAllowed', async () => {
// if (this.deviceSrv.areEmptyLabels()) {
// await this.deviceSrv.forceUpdate();
// if (this.hasAudioDevices) {
// const audioLabel = publisher?.stream?.getMediaStream()?.getAudioTracks()[0]?.label;
// this.deviceSrv.setMicSelected(audioLabel);
// }
// if (this.hasVideoDevices) {
// const videoLabel = publisher?.stream?.getMediaStream()?.getVideoTracks()[0]?.label;
// this.deviceSrv.setCameraSelected(videoLabel);
// }
// this.setDevicesInfo();
// }
// });
// }
private handlePublisherError(publisher: Publisher) {
publisher.once('accessDenied', (e: any) => {
let message: string;
if (e.name === OpenViduErrorName.DEVICE_ALREADY_IN_USE) {
this.log.w('Video device already in use. Disabling video device...');
// Allow access to the room with only mic if camera device is already in use
this.hasVideoDevices = false;
this.deviceSrv.disableVideoDevices();
return this.initwebcamPublisher();
}
if (e.name === OpenViduErrorName.DEVICE_ACCESS_DENIED) {
message = 'Access to media devices was not allowed.';
this.hasVideoDevices = false;
this.hasAudioDevices = false;
this.deviceSrv.disableVideoDevices();
this.deviceSrv.disableAudioDevices();
return this.initwebcamPublisher();
} else if (e.name === OpenViduErrorName.NO_INPUT_SOURCE_SET) {
message = 'No video or audio devices have been found. Please, connect at least one.';
}
this.actionService.openDialog(e.name.replace(/_/g, ' '), message, true);
this.log.e(e.message);
});
}
private generateRandomNickname(): string {
return 'OpenVidu_User' + Math.floor(Math.random() * 100);
}
}

View File

@ -414,11 +414,11 @@ export class VideoconferenceComponent implements OnInit, OnDestroy, AfterViewIni
}
}
ngOnDestroy(): void {
async ngOnDestroy() {
if (this.prejoinSub) this.prejoinSub.unsubscribe();
if (this.participantNameSub) this.participantNameSub.unsubscribe();
this.deviceSrv.clear();
this.openviduService.clear();
await this.openviduService.clear();
}
/**

View File

@ -27,7 +27,6 @@ import { CommonModule } from '@angular/common';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
// import { UserSettingsComponent } from './components/user-settings/user-settings.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { VideoComponent } from './components/video/video.component';
import { ChatPanelComponent } from './components/panel/chat-panel/chat-panel.component';
@ -70,7 +69,6 @@ import { RecordingActivityComponent } from './components/panel/activities-panel/
@NgModule({
declarations: [
// UserSettingsComponent,
VideoComponent,
ToolbarComponent,
ChatPanelComponent,
@ -140,7 +138,6 @@ import { RecordingActivityComponent } from './components/panel/activities-panel/
],
exports: [
VideoconferenceComponent,
// UserSettingsComponent,
ToolbarComponent,
PanelComponent,
ParticipantsPanelComponent,

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Connection, OpenVidu, Publisher, PublisherProperties, Session, SignalOptions } from 'openvidu-browser';
import { Connection, OpenVidu, Publisher, PublisherProperties, Session, SignalOptions, Stream } from 'openvidu-browser';
import { LoggerService } from '../logger/logger.service';
@ -84,11 +84,11 @@ export class OpenViduService {
/**
* @internal
*/
clear() {
async clear() {
this.videoSource = undefined;
this.audioSource = undefined;
this.stopTracks(this.participantService.getMyCameraPublisher()?.stream?.getMediaStream());
this.stopTracks(this.participantService.getMyScreenPublisher()?.stream?.getMediaStream());
await this.participantService.getMyCameraPublisher()?.stream?.disposeMediaStream();
await this.participantService.getMyScreenPublisher()?.stream?.disposeMediaStream();
}
/**
@ -261,7 +261,7 @@ export class OpenViduService {
// Disabling webcam
if (this.participantService.haveICameraAndScreenActive()) {
this.publishVideoAux(this.participantService.getMyCameraPublisher(), publish);
await this.publishVideoAux(this.participantService.getMyCameraPublisher(), publish);
this.participantService.disableWebcamStream();
this.unpublish(this.participantService.getMyCameraPublisher());
this.publishAudioAux(this.participantService.getMyScreenPublisher(), publishAudio);
@ -273,22 +273,22 @@ export class OpenViduService {
await this.connectSession(this.getWebcamSession(), this.tokenService.getWebcamToken());
}
await this.publish(this.participantService.getMyCameraPublisher());
this.publishVideoAux(this.participantService.getMyCameraPublisher(), true);
await this.publishVideoAux(this.participantService.getMyCameraPublisher(), true);
this.publishAudioAux(this.participantService.getMyScreenPublisher(), false);
this.publishAudioAux(this.participantService.getMyCameraPublisher(), hasAudio);
this.participantService.enableWebcamStream();
} else {
// Muting/unmuting webcam
this.publishVideoAux(this.participantService.getMyCameraPublisher(), publish);
await this.publishVideoAux(this.participantService.getMyCameraPublisher(), publish);
}
}
/**
* @internal
*/
private publishVideoAux(publisher: Publisher, publish: boolean): void {
private async publishVideoAux(publisher: Publisher, publish: boolean): Promise<void> {
if (!!publisher) {
publisher.publishVideo(publish, true);
await publisher.publishVideo(publish, true);
this.participantService.updateLocalParticipant();
}
}
@ -592,12 +592,4 @@ export class OpenViduService {
}
}
}
private stopTracks(mediaStream: MediaStream) {
if (mediaStream) {
mediaStream?.getAudioTracks().forEach((track) => track.stop());
mediaStream?.getVideoTracks().forEach((track) => track.stop());
// this.webcamMediaStream?.getAudioTracks().forEach((track) => track.stop());
}
}
}

View File

@ -88,7 +88,7 @@ export class PanelService {
return this.isParticipantsOpened;
}
private isExternalPanelOpened(): boolean {
isExternalPanelOpened(): boolean {
return this.isExternalOpened;
}
}

View File

@ -41,9 +41,16 @@ export class VirtualBackgroundService {
getBackgrounds(): any[] {
return this.backgrounds;
}
isBackgroundApplied(): boolean {
const bgSelected = this.backgroundSelected.getValue();
return !!bgSelected && bgSelected !== 'no_effect';
}
async applyBackground(effect: BackgroundEffect) {
if (effect.id !== this.backgroundSelected.getValue()) {
const isBackgroundSelected = !!this.backgroundSelected.getValue() && this.backgroundSelected.getValue() !== 'no_effect';
const filter = this.participantService.getMyCameraPublisher().stream.filter;
const isBackgroundSelected = !!filter && filter.type.startsWith('VB:');
let options = { token: this.tokenService.getWebcamToken(), url: '' };
if (effect.type === EffectType.IMAGE) {
options.url = effect.src;
@ -60,7 +67,7 @@ export class VirtualBackgroundService {
}
async removeBackground() {
if (!!this.backgroundSelected.getValue() && this.backgroundSelected.getValue() !== 'no_effect') {
if (!!this.isBackgroundApplied()) {
this.backgroundSelected.next('no_effect');
await this.participantService.getMyCameraPublisher().stream.removeFilter();
}

View File

@ -20,9 +20,9 @@ export class ToolbarDirectiveComponent implements OnInit {
OPENVIDU_SECRET = 'MY_SECRET';
publishVideo = true;
publishAudio = true;
constructor(private restService: RestService, private openviduService: OpenViduService) {}
constructor(private restService: RestService, private openviduService: OpenViduService) { }
ngOnInit(): void {}
ngOnInit(): void { }
async onJoinButtonClicked() {
this.tokens = {
@ -31,13 +31,13 @@ export class ToolbarDirectiveComponent implements OnInit {
};
}
toggleVideo() {
async toggleVideo(): Promise<void> {
this.publishVideo = !this.publishVideo;
this.openviduService.publishVideo(this.publishVideo);
await this.openviduService.publishVideo(this.publishVideo);
}
toggleAudio() {
async toggleAudio(): Promise<void> {
this.publishAudio = !this.publishAudio;
this.openviduService.publishAudio(this.publishAudio);
await this.openviduService.publishAudio(this.publishAudio);
}
}

View File

@ -22,7 +22,7 @@ export class ToolbarAdditionalButtonsDirectiveComponent {
private restService: RestService,
private openviduService: OpenViduService,
private participantService: ParticipantService
) {}
) { }
async onJoinButtonClicked() {
this.tokens = {
@ -31,13 +31,13 @@ export class ToolbarAdditionalButtonsDirectiveComponent {
};
}
toggleVideo() {
async toggleVideo() {
const publishVideo = !this.participantService.isMyVideoActive();
this.openviduService.publishVideo(publishVideo);
await this.openviduService.publishVideo(publishVideo);
}
toggleAudio() {
async toggleAudio() {
const publishAudio = !this.participantService.isMyAudioActive();
this.openviduService.publishAudio(publishAudio);
await this.openviduService.publishAudio(publishAudio);
}
}

View File

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

View File

@ -1,12 +1,12 @@
{
"name": "openvidu-node-client",
"version": "2.21.0",
"version": "2.22.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "openvidu-node-client",
"version": "2.21.0",
"version": "2.22.0",
"license": "Apache-2.0",
"dependencies": {
"axios": "0.21.4",

View File

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

View File

@ -9,7 +9,7 @@ services:
#
# Default Application
#
# Openvidu-Call Version: 2.21.0
# Openvidu-Call Version: 2.22.0
#
# --------------------------------------------------------------
app:

View File

@ -11,7 +11,7 @@
#
# This file will be overridden when update OpenVidu Platform
#
# Openvidu Version: 2.21.0
# Openvidu Version: 2.22.0
#
# Installation Mode: On Premises
#

View File

@ -3,7 +3,7 @@
# Global variables
OPENVIDU_FOLDER=openvidu
OPENVIDU_VERSION=master
OPENVIDU_UPGRADABLE_VERSION="2.21"
OPENVIDU_UPGRADABLE_VERSION="2.22"
DOWNLOAD_URL=https://raw.githubusercontent.com/OpenVidu/openvidu/${OPENVIDU_VERSION}
# Support docker compose v1 and v2

View File

@ -356,6 +356,20 @@ ELASTICSEARCH_PASSWORD=
# Documentation: https://doc-kurento.readthedocs.io/en/stable/features/logging.html
# KMS_DOCKER_ENV_GST_DEBUG=
# Coturn deployment in media nodes (Experimental)
# --------------------------
# OpenVidu PRO/Enterprise includes a default TURN/STUN deployment (Coturn).
# By default, Coturn is deployed in master nodes, but if you want to deploy
# Coturn in the media nodes, you just need to set OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=true
# Default value is OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=false
# For Coturn to work, you need at least this ports open:
# For master node you need 3478 TCP/UDP open in the master node.
# For Media nodes you need 443 TCP/UDP open in media nodes.
# More info about Coturn configuration in OpenVidu PRO/ENTERPRISE:
# - https://docs.openvidu.io/en/stable/deployment/pro/on-premises/#coturn-configuration
OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=false
# Cloudformation configuration
# --------------------------
# If you're working outside AWS ignore this section

View File

@ -1,26 +0,0 @@
version: '3.1'
services:
# --------------------------------------------------------------
#
# Change this if your want use your own application.
# It's very important expose your application in port 5442
# and use the http protocol.
#
# Default Application
#
# Openvidu-Call Version: 2.21.0
#
# --------------------------------------------------------------
app:
image: openvidu/openvidu-call:2.21.0
restart: on-failure
network_mode: host
environment:
- SERVER_PORT=5442
- OPENVIDU_URL=http://localhost:5443
- OPENVIDU_SECRET=${OPENVIDU_SECRET}
- CALL_OPENVIDU_CERTTYPE=${CERTIFICATE_TYPE}
logging:
options:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"

View File

@ -11,7 +11,7 @@
#
# This file will be overridden when update OpenVidu Platform
#
# Openvidu Version: 2.21.0
# Openvidu Version: 2.22.0
#
# Installation Mode: On Premises
#
@ -64,6 +64,7 @@ services:
network_mode: host
volumes:
- ./:/opt/openvidu
- /var/run/docker.sock:/var/run/docker.sock
environment:
- SERVER_PORT=4443
- SERVER_SSL_ENABLED=false
@ -81,6 +82,7 @@ services:
- RM_S3_CONFIGURATION_BUCKET_REGION=${RM_S3_CONFIGURATION_BUCKET_REGION}
- RM_MEDIA_NODES_AUTOSCALING_GROUP_NAME=${RM_MEDIA_NODES_AUTOSCALING_GROUP_NAME}
- RM_MASTER_NODES_AUTOSCALING_GROUP_NAME=${RM_MASTER_NODES_AUTOSCALING_GROUP_NAME}
- RM_S3_CONFIG_AUTORESTART=${OPENVIDU_ENTERPRISE_S3_CONFIG_AUTORESTART:-true}
logging:
options:
max-size: "${DOCKER_LOGS_MAX_SIZE:-100M}"

View File

@ -3,7 +3,7 @@
# Global variables
OPENVIDU_FOLDER=openvidu
OPENVIDU_VERSION=master
OPENVIDU_UPGRADABLE_VERSION="2.21"
OPENVIDU_UPGRADABLE_VERSION="2.22"
AWS_SCRIPTS_FOLDER=${OPENVIDU_FOLDER}/cluster/aws
ELASTICSEARCH_FOLDER=${OPENVIDU_FOLDER}/elasticsearch
BEATS_FOLDER=${OPENVIDU_FOLDER}/beats
@ -80,10 +80,6 @@ new_ov_installation() {
--output "${OPENVIDU_FOLDER}/.env" || fatal_error "Error when downloading the file '.env'"
printf '\n - .env'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/docker-compose.override.yml \
--output "${OPENVIDU_FOLDER}/docker-compose.override.yml" || fatal_error "Error when downloading the file 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/docker-compose.yml \
--output "${OPENVIDU_FOLDER}/docker-compose.yml" || fatal_error "Error when downloading the file 'docker-compose.yml'"
printf '\n - docker-compose.yml'
@ -160,286 +156,6 @@ replace_variable_in_new_env_file() {
[[ -n "${ENV_VARIABLE_VALUE}" ]] && sed -i "s|#${ENV_VARIABLE_NAME}=|${ENV_VARIABLE_NAME}=${ENV_VARIABLE_VALUE}|" "${OPENVIDU_PREVIOUS_FOLDER}/.env-${OPENVIDU_VERSION}"
}
upgrade_ov() {
# Search local Openvidu installation
printf '\n'
printf '\n ============================================'
printf '\n Search Previous Installation of Openvidu'
printf '\n ============================================'
printf '\n'
SEARCH_IN_FOLDERS=(
"${PWD}"
"/opt/${OPENVIDU_FOLDER}"
)
for folder in "${SEARCH_IN_FOLDERS[@]}"; do
printf "\n => Searching in '%s' folder..." "${folder}"
if [ -f "${folder}/docker-compose.yml" ]; then
OPENVIDU_PREVIOUS_FOLDER="${folder}"
printf "\n => Found installation in folder '%s'" "${folder}"
break
fi
done
[ -z "${OPENVIDU_PREVIOUS_FOLDER}" ] && fatal_error "No previous Openvidu installation found"
# Upgrade Openvidu
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"
# 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.
if [[ "${OPENVIDU_PREVIOUS_VERSION}" != "${OPENVIDU_UPGRADABLE_VERSION}."* ]]; then
fatal_error "You can't update from version ${OPENVIDU_PREVIOUS_VERSION} to ${OPENVIDU_VERSION}.\nNever upgrade across multiple major versions."
fi
printf '\n'
printf '\n ======================================='
printf '\n Upgrade OpenVidu Pro %s to %s' "${OPENVIDU_PREVIOUS_VERSION}" "${OPENVIDU_VERSION}"
printf '\n ======================================='
printf '\n'
ROLL_BACK_FOLDER="${OPENVIDU_PREVIOUS_FOLDER}/.old-${OPENVIDU_PREVIOUS_VERSION}"
TMP_FOLDER="${OPENVIDU_PREVIOUS_FOLDER}/tmp"
ACTUAL_FOLDER="${PWD}"
USE_OV_CALL=$(grep -E '^ image: openvidu/openvidu-call:.*$' "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml" | tr -d '[:space:]')
printf "\n Creating rollback folder '%s'..." ".old-${OPENVIDU_PREVIOUS_VERSION}"
mkdir "${ROLL_BACK_FOLDER}" || fatal_error "Error while creating the folder '.old-${OPENVIDU_PREVIOUS_VERSION}'"
printf "\n Creating temporal folder 'tmp'..."
mkdir "${TMP_FOLDER}" || fatal_error "Error while creating the folder 'temporal'"
# Download necessary files
printf '\n => Downloading new OpenVidu Pro files:'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/cluster/aws/openvidu_autodiscover.sh \
--output "${TMP_FOLDER}/openvidu_autodiscover.sh" || fatal_error "Error when downloading the file 'openvidu_autodiscover.sh'"
printf '\n - openvidu_autodiscover.sh'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/cluster/aws/openvidu_drop.sh \
--output "${TMP_FOLDER}/openvidu_drop.sh" || fatal_error "Error when downloading the file 'openvidu_drop.sh'"
printf '\n - openvidu_drop.sh'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/cluster/aws/openvidu_launch_kms.sh \
--output "${TMP_FOLDER}/openvidu_launch_kms.sh" || fatal_error "Error when downloading the file 'openvidu_launch_kms.sh'"
printf '\n - openvidu_launch_kms.sh'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/beats/filebeat.yml \
--output "${TMP_FOLDER}/filebeat.yml" || fatal_error "Error when downloading the file 'filebeat.yml'"
printf '\n - filebeat.yml'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/beats/metricbeat.yml \
--output "${TMP_FOLDER}/metricbeat.yml" || fatal_error "Error when downloading the file 'metricbeat.yml'"
printf '\n - metricbeat.yml'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/.env \
--output "${TMP_FOLDER}/.env" || fatal_error "Error when downloading the file '.env'"
printf '\n - .env'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/docker-compose.override.yml \
--output "${TMP_FOLDER}/docker-compose.override.yml" || fatal_error "Error when downloading the file 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/docker-compose.yml \
--output "${TMP_FOLDER}/docker-compose.yml" || fatal_error "Error when downloading the file 'docker-compose.yml'"
printf '\n - docker-compose.yml'
curl --silent ${DOWNLOAD_URL}/openvidu-server/deployments/enterprise/master-node/openvidu \
--output "${TMP_FOLDER}/openvidu" || fatal_error "Error when downloading the file 'openvidu'"
printf '\n - openvidu'
# Downloading new images and stopped actual Openvidu
printf '\n => Downloading new images...'
printf '\n'
sleep 1
printf "\n => Moving to 'tmp' folder..."
printf '\n'
cd "${TMP_FOLDER}" || fatal_error "Error when moving to 'tmp' folder"
printf '\n'
docker-compose pull || true
printf '\n => Stopping Openvidu...'
printf '\n'
sleep 1
printf "\n => Moving to 'openvidu' folder..."
printf '\n'
cd "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error when moving to 'openvidu' folder"
printf '\n'
docker-compose down || true
printf '\n'
printf '\n => Moving to working dir...'
cd "${ACTUAL_FOLDER}" || fatal_error "Error when moving to working dir"
# Move old files to rollback folder
printf '\n => Moving previous installation files to rollback folder:'
mv "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'docker-compose.yml'"
printf '\n - docker-compose.yml'
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'"
printf '\n - docker-compose.override.yml'
fi
mv "${OPENVIDU_PREVIOUS_FOLDER}/openvidu" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'openvidu'"
printf '\n - openvidu'
mv "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'cluster/aws'"
printf '\n - cluster/aws'
mv "${OPENVIDU_PREVIOUS_FOLDER}/beats" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous 'beats'"
printf '\n - beats'
cp "${OPENVIDU_PREVIOUS_FOLDER}/.env" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous '.env'"
printf '\n - .env'
if [ -d "${OPENVIDU_PREVIOUS_FOLDER}/custom-nginx-vhosts" ]; then
mv "${OPENVIDU_PREVIOUS_FOLDER}/custom-nginx-vhosts" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous directory 'custom-nginx-vhosts'"
printf '\n - custom-nginx-vhosts'
fi
if [ -d "${OPENVIDU_PREVIOUS_FOLDER}/custom-nginx-locations" ]; then
mv "${OPENVIDU_PREVIOUS_FOLDER}/custom-nginx-locations" "${ROLL_BACK_FOLDER}" || fatal_error "Error while moving previous directory 'custom-nginx-locations'"
printf '\n - custom-nginx-locations'
fi
# Move tmp files to Openvidu
printf '\n => Updating files:'
mv "${TMP_FOLDER}/docker-compose.yml" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'docker-compose.yml'"
printf '\n - docker-compose.yml'
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'"
printf '\n - docker-compose.override.yml'
else
mv "${TMP_FOLDER}/docker-compose.override.yml" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.override.yml-${OPENVIDU_VERSION}" || fatal_error "Error while updating 'docker-compose.override.yml'"
printf '\n - docker-compose.override.yml-%s' "${OPENVIDU_VERSION}"
fi
mv "${TMP_FOLDER}/.env" "${OPENVIDU_PREVIOUS_FOLDER}/.env-${OPENVIDU_VERSION}" || fatal_error "Error while moving previous '.env'"
printf '\n - .env-%s' "${OPENVIDU_VERSION}"
mv "${TMP_FOLDER}/openvidu" "${OPENVIDU_PREVIOUS_FOLDER}" || fatal_error "Error while updating 'openvidu'"
printf '\n - openvidu'
mkdir "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws" || fatal_error "Error while creating the folder 'cluster/aws'"
mkdir "${OPENVIDU_PREVIOUS_FOLDER}/beats" || fatal_error "Error while creating the folder 'beats'"
mv "${TMP_FOLDER}/openvidu_autodiscover.sh" "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws" || fatal_error "Error while updating 'openvidu_autodiscover.sh'"
printf '\n - openvidu_autodiscover.sh'
mv "${TMP_FOLDER}/openvidu_drop.sh" "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws" || fatal_error "Error while updating 'openvidu_drop.sh'"
printf '\n - openvidu_drop.sh'
mv "${TMP_FOLDER}/openvidu_launch_kms.sh" "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws" || fatal_error "Error while updating 'openvidu_launch_kms.sh'"
printf '\n - openvidu_launch_kms.sh'
mv "${TMP_FOLDER}/filebeat.yml" "${OPENVIDU_PREVIOUS_FOLDER}/beats/filebeat.yml" || fatal_error "Error while updating 'filebeat.yml'"
printf '\n - filebeat.yml'
mv "${TMP_FOLDER}/metricbeat.yml" "${OPENVIDU_PREVIOUS_FOLDER}/beats/metricbeat.yml" || fatal_error "Error while updating 'metricbeat.yml'"
printf '\n - metricbeat.yml'
printf "\n => Deleting 'tmp' folder"
rm -rf "${TMP_FOLDER}" || fatal_error "Error deleting 'tmp' folder"
# Add execution permissions
printf "\n => Adding permission to 'openvidu' program..."
chmod +x "${OPENVIDU_PREVIOUS_FOLDER}/openvidu" || fatal_error "Error while adding permission to 'openvidu' program"
printf '\n - openvidu'
# Change recording folder with all permissions
printf "\n => Adding permission to 'recordings' folder..."
mkdir -p "${OPENVIDU_PREVIOUS_FOLDER}/recordings"
chmod +x "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws/openvidu_autodiscover.sh" || fatal_error "Error while adding permission to 'openvidu_autodiscover.sh' program"
printf '\n - openvidu_autodiscover.sh'
chmod +x "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws/openvidu_drop.sh" || fatal_error "Error while adding permission to 'openvidu' openvidu_drop.sh"
printf '\n - openvidu_drop.sh'
chmod +x "${OPENVIDU_PREVIOUS_FOLDER}/cluster/aws/openvidu_launch_kms.sh" || fatal_error "Error while adding permission to 'openvidu_launch_kms.sh' program"
printf '\n - openvidu_launch_kms.sh'
# Define old mode: On Premise or Cloud Formation
OLD_MODE=$(grep -E "Installation Mode:.*$" "${ROLL_BACK_FOLDER}/docker-compose.yml" | awk '{ print $4,$5 }')
[ -n "${OLD_MODE}" ] && sed -i -r "s/Installation Mode:.+/Installation Mode: ${OLD_MODE}/" "${OPENVIDU_PREVIOUS_FOLDER}/docker-compose.yml"
# Update .env variables to new .env-version
AWS_REGION=$(get_previous_env_variable AWS_DEFAULT_REGION)
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 |
sed -n -e '/KMSAMIMAP:/,/Metadata:/ p' |
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"
# 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
# Ready to use
printf '\n'
printf '\n'
printf '\n ================================================'
printf "\n Openvidu successfully upgraded to version %s" "${OPENVIDU_VERSION}"
printf '\n ================================================'
printf '\n'
printf "\n 1. A new file 'docker-compose.yml' has been created with the new OpenVidu %s services" "${OPENVIDU_VERSION}"
printf '\n'
printf "\n 2. The previous file '.env' remains intact, but a new file '.env-%s' has been created." "${OPENVIDU_VERSION}"
printf "\n Transfer any configuration you wish to keep in the upgraded version from '.env' to '.env-%s'." "${OPENVIDU_VERSION}"
printf "\n When you are OK with it, rename and leave as the only '.env' file of the folder the new '.env-%s'." "${OPENVIDU_VERSION}"
printf '\n'
printf "\n 3. If you were using Openvidu Call application, it has been automatically updated in file 'docker-compose.override.yml'."
printf "\n However, if you were using your own application, a file called 'docker-compose.override.yml-%s'" "${OPENVIDU_VERSION}"
printf "\n has been created with the latest version of Openvidu Call. If you don't plan to use it you can delete it."
printf '\n'
printf '\n 4. Start new version of Openvidu'
printf '\n $ ./openvidu start'
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'
printf '\n'
printf '\n'
}
# Check docker and docker-compose installation
if ! command -v docker > /dev/null; then
echo "You don't have docker installed, please install it and re-run the command"
@ -459,7 +175,7 @@ fi
# Check type of installation
if [[ -n "$1" && "$1" == "upgrade" ]]; then
upgrade_ov
fatal_error "OpenVidu Enterprise HA can't be upgraded manually. Deploy the Cloudformation template of the version '${OPENVIDU_VERSION}' you want to deploy: https://docs.openvidu.io/en/${OPENVIDU_VERSION//v}/deployment/enterprise/aws/"
else
new_ov_installation
fi

View File

@ -207,27 +207,12 @@ is_external_url() {
}
start_openvidu() {
local RUN_LOCAL_ES
local RUN_LOCAL_KIBANA
local CONFIGURED_ELASTICSEARCH_HOST
local CONFIGURED_KIBANA_HOST
CONFIGURED_ELASTICSEARCH_HOST=$(grep -v '^#' .env | grep OPENVIDU_PRO_ELASTICSEARCH_HOST | cut -d '=' -f2)
CONFIGURED_KIBANA_HOST=$(grep -v '^#' .env | grep OPENVIDU_PRO_KIBANA_HOST | cut -d '=' -f2)
RUN_LOCAL_ES=true
RUN_LOCAL_KIBANA=true
if is_external_url "${CONFIGURED_ELASTICSEARCH_HOST}"; then
printf "Configured external elasticsearch: %s" "${CONFIGURED_ELASTICSEARCH_HOST}"
printf "\n"
RUN_LOCAL_ES=false
fi
if is_external_url "${CONFIGURED_KIBANA_HOST}"; then
printf "Configured external kibana: %s" "${CONFIGURED_KIBANA_HOST}"
printf "\n"
RUN_LOCAL_KIBANA=false
COTURN_IN_MEDIA_NODES=$(grep -v '^#' .env | grep COTURN_IN_MEDIA_NODES | cut -d '=' -f2)
if [[ -z "${COTURN_IN_MEDIA_NODES}" ]]; then
COTURN_IN_MEDIA_NODES=false
fi
docker-compose up -d \
$(if [ "${RUN_LOCAL_ES}" == "false" ]; then echo '--scale elasticsearch=0'; fi) \
$(if [ "${RUN_LOCAL_KIBANA}" == "false" ]; then echo '--scale kibana=0'; fi)
$(if [ "${COTURN_IN_MEDIA_NODES}" == "true" ]; then echo '--scale coturn=0'; fi)
}
usage() {

View File

@ -93,7 +93,7 @@ Resources:
# Environment variables from docker-compose.yml
sed -i "s|MEDIASOUP_IMAGE=openvidu/mediasoup-controller.*|MEDIASOUP_IMAGE=openvidu/mediasoup-controller:master|g" docker-compose.yml
sed -i "s|COTURN_IMAGE=openvidu/openvidu-coturn.*|MEDIASOUP_IMAGE=openvidu/openvidu-coturn:master|g" docker-compose.yml
sed -i "s|COTURN_IMAGE=openvidu/openvidu-coturn.*|COTURN_IMAGE=openvidu/openvidu-coturn:master|g" docker-compose.yml
popd
fi

View File

@ -221,6 +221,14 @@ Parameters:
- false
Default: true
CoturnInMediaNodes:
Description: "If true, Coturn will be deployed on media nodes. Otherwise it will be deployed in master nodes."
Type: String
AllowedValues:
- true
- false
Default: false
#start_mappings
Mappings:
OVAMIMAP:
@ -278,6 +286,7 @@ Metadata:
default: Other configuration
Parameters:
- WantToDeployDemos
- CoturnInMediaNodes
ParameterLabels:
# SSL certificate configuration
@ -330,6 +339,8 @@ Metadata:
# Other configuration
WantToDeployDemos:
default: "Deploy OpenVidu Call application"
CoturnInMediaNodes:
default: "Deploy Coturn in Media Nodes. (Experimental)"
Conditions:
WhichCertPresent: !Not [ !Equals [!Ref WhichCert, ''] ]
@ -542,6 +553,11 @@ Resources:
rm $WORKINGDIR/docker-compose.override.yml
fi
# Deploy Coturn in media nodes
if [ "${CoturnInMediaNodes}" == "true" ]; then
sed -i "s/OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=false/OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=true/" $WORKINGDIR/.env
fi
# Recording Configuration
if [ "${Recording}" != "disabled" ]; then
sed -i "s/OPENVIDU_RECORDING=false/OPENVIDU_RECORDING=true/" $WORKINGDIR/.env

View File

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

View File

@ -2,7 +2,7 @@
MEDIA_NODE_FOLDER=kms
MEDIA_NODE_VERSION=master
OPENVIDU_UPGRADABLE_VERSION="2.21"
OPENVIDU_UPGRADABLE_VERSION="2.22"
BEATS_FOLDER=${MEDIA_NODE_FOLDER}/beats
OPENVIDU_RECORDINGS_FOLDER="/opt/openvidu/recordings"
DOWNLOAD_URL=https://raw.githubusercontent.com/OpenVidu/openvidu/${MEDIA_NODE_VERSION}

View File

@ -353,6 +353,19 @@ ELASTICSEARCH_PASSWORD=
# Documentation: https://doc-kurento.readthedocs.io/en/stable/features/logging.html
# KMS_DOCKER_ENV_GST_DEBUG=
# Coturn deployment in media nodes (Experimental)
# --------------------------
# OpenVidu PRO/Enterprise includes a default TURN/STUN deployment (Coturn).
# By default, Coturn is deployed in master nodes, but if you want to deploy
# Coturn in the media nodes, you just need to set OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=true
# Default value is OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=false
# For Coturn to work, you need at least this ports open:
# For master node you need 3478 TCP/UDP open in the master node.
# For Media nodes you need 443 TCP/UDP open in media nodes.
# More info about Coturn configuration in OpenVidu PRO/ENTERPRISE:
# - https://docs.openvidu.io/en/stable/deployment/pro/on-premises/#coturn-configuration
OPENVIDU_PRO_COTURN_IN_MEDIA_NODES=false
# Cloudformation configuration
# --------------------------
# If you're working outside AWS ignore this section

View File

@ -9,7 +9,7 @@ services:
#
# Default Application
#
# Openvidu-Call Version: 2.21.0
# Openvidu-Call Version: 2.22.0
#
# --------------------------------------------------------------
app:

View File

@ -11,7 +11,7 @@
#
# This file will be overridden when update OpenVidu Platform
#
# Openvidu Version: 2.21.0
# Openvidu Version: 2.22.0
#
# Installation Mode: On Premises
#

View File

@ -3,7 +3,7 @@
# Global variables
OPENVIDU_FOLDER=openvidu
OPENVIDU_VERSION=master
OPENVIDU_UPGRADABLE_VERSION="2.21"
OPENVIDU_UPGRADABLE_VERSION="2.22"
AWS_SCRIPTS_FOLDER=${OPENVIDU_FOLDER}/cluster/aws
ELASTICSEARCH_FOLDER=${OPENVIDU_FOLDER}/elasticsearch
BEATS_FOLDER=${OPENVIDU_FOLDER}/beats

View File

@ -213,8 +213,12 @@ start_openvidu() {
local CONFIGURED_KIBANA_HOST
CONFIGURED_ELASTICSEARCH_HOST=$(grep -v '^#' .env | grep OPENVIDU_PRO_ELASTICSEARCH_HOST | cut -d '=' -f2)
CONFIGURED_KIBANA_HOST=$(grep -v '^#' .env | grep OPENVIDU_PRO_KIBANA_HOST | cut -d '=' -f2)
COTURN_IN_MEDIA_NODES=$(grep -v '^#' .env | grep COTURN_IN_MEDIA_NODES | cut -d '=' -f2)
RUN_LOCAL_ES=true
RUN_LOCAL_KIBANA=true
if [[ -z "${COTURN_IN_MEDIA_NODES}" ]]; then
COTURN_IN_MEDIA_NODES=false
fi
if is_external_url "${CONFIGURED_ELASTICSEARCH_HOST}"; then
printf "Configured external elasticsearch: %s" "${CONFIGURED_ELASTICSEARCH_HOST}"
printf "\n"
@ -227,7 +231,8 @@ start_openvidu() {
fi
docker-compose up -d \
$(if [ "${RUN_LOCAL_ES}" == "false" ]; then echo '--scale elasticsearch=0'; fi) \
$(if [ "${RUN_LOCAL_KIBANA}" == "false" ]; then echo '--scale kibana=0'; fi)
$(if [ "${RUN_LOCAL_KIBANA}" == "false" ]; then echo '--scale kibana=0'; fi) \
$(if [ "${COTURN_IN_MEDIA_NODES}" == "true" ]; then echo '--scale coturn=0'; fi)
}
usage() {

View File

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

View File

@ -23,7 +23,7 @@
"@angular/router": "13.3.0",
"core-js": "3.21.1",
"jquery": "3.6.0",
"openvidu-browser": "2.21.0",
"openvidu-browser": "2.22.0",
"rxjs": "7.5.5",
"tslib": "2.3.1",
"zone.js": "0.11.5"
@ -9191,9 +9191,9 @@
}
},
"node_modules/openvidu-browser": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.21.0.tgz",
"integrity": "sha512-dQZ8lsvt7zydklG/naHtaWpnhRo5ErTjEkWslmg6e2fjFVy+GmGVrJBeMJrJDM2FrOWv8aH86fRNR+P7eESB9g==",
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.22.0.tgz",
"integrity": "sha512-fug7V5WZPwEXCF4im6RzhWSbv9nYB4QWMXrEQBEsh7RI1VPQ5Jl2VcindiUeMtN+quLqeTjXSRUh4lfkrTlQSQ==",
"dependencies": {
"events": "3.3.0",
"freeice": "2.2.2",
@ -14935,7 +14935,8 @@
"version": "13.3.0",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.0.tgz",
"integrity": "sha512-QbTQWXK2WzYU+aKKVDG0ya7WYT+6rNAUXVt5ov9Nz1SGgDeozpiOx8ZqPWUvnToTY8EoodwWFGCVtkLHXUR+wA==",
"dev": true
"dev": true,
"requires": {}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
@ -15501,7 +15502,8 @@
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz",
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"dev": true
"dev": true,
"requires": {}
},
"acorn-walk": {
"version": "8.2.0",
@ -16234,7 +16236,8 @@
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz",
"integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==",
"dev": true
"dev": true,
"requires": {}
},
"clean-stack": {
"version": "2.2.0",
@ -16317,13 +16320,15 @@
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.0.0.tgz",
"integrity": "sha512-ctjwuntPfZZT2mNj2NDIVu51t9cvbhl/16epc5xEwyzyDt76pX9UgwvY+MbXrf/C/FWwdtmNtfP698BKI+9leQ==",
"dev": true
"dev": true,
"requires": {}
},
"@angular/core": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-9.0.0.tgz",
"integrity": "sha512-6Pxgsrf0qF9iFFqmIcWmjJGkkCaCm6V5QNnxMy2KloO3SDq6QuMVRbN9RtC8Urmo25LP+eZ6ZgYqFYpdD8Hd9w==",
"dev": true
"dev": true,
"requires": {}
},
"rxjs": {
"version": "6.6.7",
@ -16806,7 +16811,8 @@
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz",
"integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==",
"dev": true
"dev": true,
"requires": {}
},
"css-select": {
"version": "4.2.1",
@ -18291,7 +18297,8 @@
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
"integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==",
"dev": true
"dev": true,
"requires": {}
},
"ieee754": {
"version": "1.2.1",
@ -19137,7 +19144,8 @@
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz",
"integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==",
"dev": true
"dev": true,
"requires": {}
},
"karma-source-map-support": {
"version": "1.4.0",
@ -20071,9 +20079,9 @@
}
},
"openvidu-browser": {
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.21.0.tgz",
"integrity": "sha512-dQZ8lsvt7zydklG/naHtaWpnhRo5ErTjEkWslmg6e2fjFVy+GmGVrJBeMJrJDM2FrOWv8aH86fRNR+P7eESB9g==",
"version": "2.22.0",
"resolved": "https://registry.npmjs.org/openvidu-browser/-/openvidu-browser-2.22.0.tgz",
"integrity": "sha512-fug7V5WZPwEXCF4im6RzhWSbv9nYB4QWMXrEQBEsh7RI1VPQ5Jl2VcindiUeMtN+quLqeTjXSRUh4lfkrTlQSQ==",
"requires": {
"events": "3.3.0",
"freeice": "2.2.2",
@ -20532,7 +20540,8 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz",
"integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-custom-properties": {
"version": "12.1.4",
@ -20602,13 +20611,15 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz",
"integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-gap-properties": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz",
"integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-image-set-function": {
"version": "4.0.6",
@ -20634,7 +20645,8 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz",
"integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-lab-function": {
"version": "4.1.2",
@ -20661,19 +20673,22 @@
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz",
"integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-media-minmax": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz",
"integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-modules-extract-imports": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz",
"integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-modules-local-by-default": {
"version": "4.0.0",
@ -20717,13 +20732,15 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz",
"integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-page-break": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz",
"integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-place": {
"version": "7.0.4",
@ -20788,7 +20805,8 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz",
"integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==",
"dev": true
"dev": true,
"requires": {}
},
"postcss-selector-not": {
"version": "5.0.0",
@ -21534,7 +21552,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
"dev": true,
"requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
@ -22162,7 +22181,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
"dev": true,
"requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
@ -22690,7 +22710,8 @@
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true
"dev": true,
"requires": {}
},
"json-schema-traverse": {
"version": "0.4.1",
@ -22987,7 +23008,8 @@
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"dev": true
"dev": true,
"requires": {}
},
"xml2js": {
"version": "0.4.23",

View File

@ -13,7 +13,7 @@
"@angular/router": "13.3.0",
"core-js": "3.21.1",
"jquery": "3.6.0",
"openvidu-browser": "2.21.0",
"openvidu-browser": "2.22.0",
"rxjs": "7.5.5",
"tslib": "2.3.1",
"zone.js": "0.11.5"

View File

@ -16,7 +16,7 @@
"core-js": "3.4.7",
"hammerjs": "2.0.8",
"json-stringify-safe": "^5.0.1",
"openvidu-browser": "2.21.0",
"openvidu-browser": "2.22.0",
"openvidu-node-client": "2.21.0",
"rxjs": "6.5.3",
"zone.js": "0.10.2"
@ -45,5 +45,5 @@
"start": "ng serve",
"test": "ng test"
},
"version": "2.21.0"
"version": "2.22.0"
}

View File

@ -65,7 +65,7 @@
<version.webdrivermanager>4.2.2</version.webdrivermanager>
<version.openvidu.java.client>2.21.1</version.openvidu.java.client>
<version.openvidu.java.client>2.22.0</version.openvidu.java.client>
<version.openvidu.client>1.1.0</version.openvidu.client>
<version.openvidu.test.browsers>1.1.0</version.openvidu.test.browsers>
<version.openvidu.test.e2e>1.1.1</version.openvidu.test.e2e>