Merge branch 'master' of https://github.com/OpenVidu/openvidu into feature/custom-ice-servers

pull/698/head
cruizba 2022-02-07 18:13:34 +01:00
commit 3274db8a61
36 changed files with 464 additions and 171 deletions

View File

@ -251,9 +251,7 @@ export class OpenViduLogger {
* @hidden * @hidden
*/ */
warn(...args: any[]) { warn(...args: any[]) {
if (!this.isProdMode) {
this.defaultConsoleLogger.warn.apply(this.defaultConsoleLogger.logger, arguments); this.defaultConsoleLogger.warn.apply(this.defaultConsoleLogger.logger, arguments);
}
if (this.isJSNLogSetup) { if (this.isJSNLogSetup) {
JL().warn(arguments); JL().warn(arguments);
} }

View File

@ -20,6 +20,7 @@
"@angular/router": "13.0.0", "@angular/router": "13.0.0",
"autolinker": "3.14.3", "autolinker": "3.14.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"ng-dynamic-component": "^10.1.0",
"openvidu-browser": "2.21.0-beta1", "openvidu-browser": "2.21.0-beta1",
"rxjs": "7.4.0", "rxjs": "7.4.0",
"tslib": "2.3.1", "tslib": "2.3.1",
@ -9065,6 +9066,19 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"node_modules/ng-dynamic-component": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ng-dynamic-component/-/ng-dynamic-component-10.1.0.tgz",
"integrity": "sha512-P3ejLAuezi/a7DfLk6SaqGUcDIhM8tfeNbJ3fAu2sPXgOOpHLPkEKw9nJc8C365B+fsZVX0B5GYuDX4pXa3GMQ==",
"dependencies": {
"tslib": "^2.0.0"
},
"peerDependencies": {
"@angular/common": "^12.0.0 || ^13.0.0",
"@angular/core": "^12.0.0 || ^13.0.0",
"rxjs": "^6.0.0 || ^7.0.0"
}
},
"node_modules/ng-packagr": { "node_modules/ng-packagr": {
"version": "13.0.3", "version": "13.0.3",
"resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-13.0.3.tgz", "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-13.0.3.tgz",
@ -21901,6 +21915,14 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true "dev": true
}, },
"ng-dynamic-component": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ng-dynamic-component/-/ng-dynamic-component-10.1.0.tgz",
"integrity": "sha512-P3ejLAuezi/a7DfLk6SaqGUcDIhM8tfeNbJ3fAu2sPXgOOpHLPkEKw9nJc8C365B+fsZVX0B5GYuDX4pXa3GMQ==",
"requires": {
"tslib": "^2.0.0"
}
},
"ng-packagr": { "ng-packagr": {
"version": "13.0.3", "version": "13.0.3",
"resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-13.0.3.tgz", "resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-13.0.3.tgz",

View File

@ -27,6 +27,7 @@
"@angular/router": "13.0.0", "@angular/router": "13.0.0",
"autolinker": "3.14.3", "autolinker": "3.14.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"ng-dynamic-component": "10.1.0",
"openvidu-browser": "2.21.0-beta1", "openvidu-browser": "2.21.0-beta1",
"rxjs": "7.4.0", "rxjs": "7.4.0",
"tslib": "2.3.1", "tslib": "2.3.1",

View File

@ -10,7 +10,8 @@
"@angular/flex-layout": "^13.0.0-beta.36", "@angular/flex-layout": "^13.0.0-beta.36",
"autolinker": "^3.14.3", "autolinker": "^3.14.3",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"openvidu-browser": "^2.20.0" "openvidu-browser": "^2.20.0",
"ng-dynamic-component": "^10.1.0"
}, },
"dependencies": { "dependencies": {
"tslib": "^2.3.0" "tslib": "^2.3.0"

View File

@ -1,35 +1,22 @@
<div id="layout" class="bounds"> <div id="layout" class="bounds">
<!-- Custom local participant -->
<ng-container *ngIf="customLocalParticipantTemplate; else defaultLocalParticipant">
<ng-container *ngTemplateOutlet="customLocalParticipantTemplate"></ng-container>
</ng-container>
<!-- Default local participant if custom participant is not injected -->
<ng-template #defaultLocalParticipant>
<div <div
class="OT_root OT_publisher" class="OT_root OT_publisher"
id="localUser" id="localUser"
*ngFor="let connection of localParticipant | connections" *ngFor="let connection of localParticipant | connections"
[ngClass]="{ OV_small: !connection.streamManager?.stream?.videoActive }" [ngClass]="{ OV_small: !connection.streamManager?.stream?.videoActive }"
> >
<ov-stream [participant]="connection" [videoEnlarged]="connection.videoEnlarged"></ov-stream> <ng-template #localStream [ngComponentOutlet]="_localStreamComponent" [ndcDynamicInputs]="{ participant: connection }">
</div>
</ng-template> </ng-template>
</div>
<!-- Custom remote participant -->
<ng-container *ngIf="customRemoteParticipantsTemplate; else defaultRemoteParticipants">
<ng-container *ngTemplateOutlet="customRemoteParticipantsTemplate"></ng-container>
</ng-container>
<!-- Default remote participants if custom participants is not injected -->
<ng-template #defaultRemoteParticipants>
<div <div
*ngFor="let connection of remoteParticipants | connections" *ngFor="let connection of remoteParticipants | connections"
class="OT_root OT_publisher" class="OT_root OT_publisher"
id="remote-participant" id="remote-participant"
[ngClass]="{ OV_small: !connection.streamManager?.stream?.videoActive }" [ngClass]="{ OV_small: !connection.streamManager?.stream?.videoActive }"
> >
<ov-stream [participant]="connection" [videoEnlarged]="connection.videoEnlarged"></ov-stream> <!-- Dynamic ov-stream component injected -->
</div> <ng-template #remoteStream [ngComponentOutlet]="_remoteStreamComponent" [ndcDynamicInputs]="{ participant: connection }">
</ng-template> </ng-template>
</div>
</div> </div>

View File

@ -1,8 +1,10 @@
import { AfterViewInit, Component, ContentChild, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { AfterViewInit, Component, OnDestroy, OnInit, Type, ViewChild, ViewContainerRef } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ParticipantService } from '../../services/participant/participant.service'; import { ParticipantService } from '../../services/participant/participant.service';
import { ParticipantAbstractModel } from '../../models/participant.model'; import { ParticipantAbstractModel } from '../../models/participant.model';
import { LayoutService } from '../../services/layout/layout.service'; import { LayoutService } from '../../services/layout/layout.service';
import { LibraryComponents } from '../../config/lib.config';
import { LibraryConfigService } from '../../services/library-config/library-config.service';
@Component({ @Component({
selector: 'ov-layout', selector: 'ov-layout',
@ -10,8 +12,8 @@ import { LayoutService } from '../../services/layout/layout.service';
styleUrls: ['./layout.component.css'] styleUrls: ['./layout.component.css']
}) })
export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit { export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
@ContentChild('customLocalParticipant', { read: TemplateRef }) customLocalParticipantTemplate: TemplateRef<any>; _localStreamComponent: Type<any>;
@ContentChild('customRemoteParticipants', { read: TemplateRef }) customRemoteParticipantsTemplate: TemplateRef<any>; _remoteStreamComponent: Type<any>;
localParticipant: ParticipantAbstractModel; localParticipant: ParticipantAbstractModel;
remoteParticipants: ParticipantAbstractModel[] = []; remoteParticipants: ParticipantAbstractModel[] = [];
@ -19,10 +21,39 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
protected remoteParticipantsSubs: Subscription; protected remoteParticipantsSubs: Subscription;
protected updateLayoutInterval: NodeJS.Timer; protected updateLayoutInterval: NodeJS.Timer;
constructor(protected layoutService: LayoutService, protected participantService: ParticipantService) {} constructor(
protected libraryConfigSrv: LibraryConfigService,
protected layoutService: LayoutService,
protected participantService: ParticipantService
) {}
@ViewChild('localStream', { static: false, read: ViewContainerRef })
set stream(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.STREAM);
//reference.clear();
this._localStreamComponent = component;
// reference.createComponent(component);
}
}, 0);
}
@ViewChild('remoteStream', { static: false, read: ViewContainerRef })
set remoteStream(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.STREAM);
// reference.clear();
this._remoteStreamComponent = component;
// reference.createComponent(component);
}
}, 0);
}
ngOnInit(): void { ngOnInit(): void {
this.subscribeToUsers(); this.subscribeToParticipants();
} }
ngAfterViewInit() {} ngAfterViewInit() {}
@ -34,7 +65,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe(); if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe();
} }
protected subscribeToUsers() { protected subscribeToParticipants() {
this.localParticipantSubs = this.participantService.localParticipantObs.subscribe((p) => { this.localParticipantSubs = this.participantService.localParticipantObs.subscribe((p) => {
this.localParticipant = p; this.localParticipant = p;
this.layoutService.update(); this.layoutService.update();

View File

@ -1,6 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ChatService } from '../../services/chat/chat.service'; import { ChatService } from '../../../services/chat/chat.service';
import { ChatServiceMock } from '../../services/chat/chat.service.mock'; import { ChatServiceMock } from '../../../services/chat/chat.service.mock';
import { ChatPanelComponent } from './chat-panel.component'; import { ChatPanelComponent } from './chat-panel.component';

View File

@ -1,9 +1,9 @@
import { AfterViewInit, Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core'; import { AfterViewInit, Component, ElementRef, HostListener, OnInit, ViewChild } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ChatMessage } from '../../models/chat.model'; import { ChatMessage } from '../../../models/chat.model';
import { MenuType } from '../../models/menu.model'; import { MenuType } from '../../../models/menu.model';
import { ChatService } from '../../services/chat/chat.service'; import { ChatService } from '../../../services/chat/chat.service';
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service'; import { SidenavMenuService } from '../../../services/sidenav-menu/sidenav-menu.service';
@Component({ @Component({
selector: 'ov-chat-panel', selector: 'ov-chat-panel',

View File

@ -0,0 +1,5 @@
<ng-container *ngIf="isChatPanelOpened" #chat></ng-container>
<ng-container *ngIf="isParticipantsPanelOpened" #participants></ng-container>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PanelComponent } from './panel.component';
describe('PanelComponent', () => {
let component: PanelComponent;
let fixture: ComponentFixture<PanelComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PanelComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(PanelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,57 @@
import { Component, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { skip, Subscription } from 'rxjs';
import { LibraryComponents } from '../../config/lib.config';
import { MenuType } from '../../models/menu.model';
import { LibraryConfigService } from '../../services/library-config/library-config.service';
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service';
@Component({
selector: 'ov-panel',
templateUrl: './panel.component.html',
styleUrls: ['./panel.component.css']
})
export class PanelComponent implements OnInit, OnDestroy {
isParticipantsPanelOpened: boolean;
isChatPanelOpened: boolean;
menuSubscription: Subscription;
@ViewChild('chat', { static: false, read: ViewContainerRef })
set chat(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.CHAT_PANEL);
reference.clear();
reference.createComponent(component);
}
}, 0);
}
@ViewChild('participants', { static: false, read: ViewContainerRef })
set participants(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.PARTICIPANTS_PANEL);
reference.clear();
reference.createComponent(component);
}
}, 0);
}
constructor(protected libraryConfigSrv: LibraryConfigService, protected menuService: SidenavMenuService) {}
ngOnInit(): void {
this.subscribeToPanelToggling();
}
subscribeToPanelToggling() {
this.menuSubscription = this.menuService.menuOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: MenuType }) => {
this.isChatPanelOpened = ev.opened && ev.type === MenuType.CHAT;
this.isParticipantsPanelOpened = ev.opened && ev.type === MenuType.PARTICIPANTS;
});
}
ngOnDestroy() {
this.isChatPanelOpened = false;
this.isParticipantsPanelOpened = false;
if (this.menuSubscription) this.menuSubscription.unsubscribe();
}
}

View File

@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core'; import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { ParticipantAbstractModel, ParticipantModel } from '../../../models/participant.model'; import { ParticipantAbstractModel, ParticipantModel } from '../../../../models/participant.model';
import { ParticipantService } from '../../../services/participant/participant.service'; import { ParticipantService } from '../../../../services/participant/participant.service';
import { SidenavMenuService } from '../../../services/sidenav-menu/sidenav-menu.service'; import { SidenavMenuService } from '../../../../services/sidenav-menu/sidenav-menu.service';
@Component({ @Component({
selector: 'ov-participants-panel', selector: 'ov-participants-panel',

View File

@ -10,36 +10,15 @@
fixedTopGap="0" fixedTopGap="0"
fixedBottomGap="0" fixedBottomGap="0"
> >
<!-- Custom menu content --> <ng-template #panel></ng-template>
<ng-container *ngIf="customPanelContentTemplate; else defaultPanelContent">
<ng-container *ngTemplateOutlet="customPanelContentTemplate"></ng-container>
</ng-container>
<!-- Default menu content if custom menu content is not injected -->
<ng-template #defaultPanelContent>
<ov-chat-panel *ngIf="isChatPanelOpened"></ov-chat-panel>
<ov-participants-panel *ngIf="isParticipantsPanelOpened"></ov-participants-panel>
</ng-template>
</mat-sidenav> </mat-sidenav>
<!-- OPENVIDU LAYOUT --> <!-- OPENVIDU LAYOUT -->
<mat-sidenav-content class="sidenav-main"> <mat-sidenav-content class="sidenav-main">
<div id="layout-container"> <!-- Dynamic layout injection -->
<ng-content select="[layout]"></ng-content> <ng-template #layout></ng-template>
</div>
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
<!-- Custom toolbar --> <ng-template #toolbar></ng-template>
<!-- <ng-container *ngIf="toolbarTemplate">
<div id="toolbar-container">
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
</div>
</ng-container> -->
<ng-container *ngIf="toolbarTemplate">
<div id="footer-container">
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
</div>
</ng-container>
</div> </div>

View File

@ -1,4 +1,15 @@
import { Component, ContentChild, EventEmitter, HostListener, Input, OnInit, Output, TemplateRef, ViewChild } from '@angular/core'; import {
Component,
ContentChild,
EventEmitter,
HostListener,
Input,
OnInit,
Output,
TemplateRef,
ViewChild,
ViewContainerRef
} from '@angular/core';
import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent } from 'openvidu-browser'; import { Subscriber, Session, StreamEvent, StreamPropertyChangedEvent, SessionDisconnectedEvent, ConnectionEvent } from 'openvidu-browser';
import { VideoType } from '../../models/video-type.model'; import { VideoType } from '../../models/video-type.model';
@ -8,7 +19,6 @@ import { ChatService } from '../../services/chat/chat.service';
import { LoggerService } from '../../services/logger/logger.service'; import { LoggerService } from '../../services/logger/logger.service';
import { WebrtcService } from '../../services/webrtc/webrtc.service'; import { WebrtcService } from '../../services/webrtc/webrtc.service';
import { TokenService } from '../../services/token/token.service'; import { TokenService } from '../../services/token/token.service';
import { PlatformService } from '../../services/platform/platform.service';
import { ActionService } from '../../services/action/action.service'; import { ActionService } from '../../services/action/action.service';
import { Signal } from '../../models/signal.model'; import { Signal } from '../../models/signal.model';
import { ParticipantService } from '../../services/participant/participant.service'; import { ParticipantService } from '../../services/participant/participant.service';
@ -18,6 +28,11 @@ import { LayoutService } from '../../services/layout/layout.service';
import { Subscription, skip } from 'rxjs'; import { Subscription, skip } from 'rxjs';
import { MenuType } from '../../models/menu.model'; import { MenuType } from '../../models/menu.model';
import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service'; import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.service';
import { ToolbarComponent } from '../toolbar/toolbar.component';
import { LibraryConfigService } from '../../services/library-config/library-config.service';
import { LayoutComponent } from '../layout/layout.component';
import { PanelComponent } from '../panel/panel.component';
import { LibraryComponents } from '../../config/lib.config';
@Component({ @Component({
selector: 'ov-session', selector: 'ov-session',
@ -25,14 +40,10 @@ import { SidenavMenuService } from '../../services/sidenav-menu/sidenav-menu.ser
styleUrls: ['./session.component.css'] styleUrls: ['./session.component.css']
}) })
export class SessionComponent implements OnInit { export class SessionComponent implements OnInit {
@ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
@ContentChild('customPanelContent', { read: TemplateRef }) customPanelContentTemplate: TemplateRef<any>;
@ContentChild('customLayoutElement', { read: TemplateRef }) customLayoutElementTemplate: TemplateRef<any>;
@Input() tokens: { webcam: string; screen: string }; @Input() tokens: { webcam: string; screen: string };
@Output() _session = new EventEmitter<any>(); // @Output() _session = new EventEmitter<any>();
@Output() _publisher = new EventEmitter<any>(); // @Output() _publisher = new EventEmitter<any>();
@Output() _error = new EventEmitter<any>(); // @Output() _error = new EventEmitter<any>();
session: Session; session: Session;
sessionScreen: Session; sessionScreen: Session;
@ -40,8 +51,7 @@ export class SessionComponent implements OnInit {
sideMenu: MatSidenav; sideMenu: MatSidenav;
sidenavMode: SidenavMode = SidenavMode.SIDE; sidenavMode: SidenavMode = SidenavMode.SIDE;
isParticipantsPanelOpened: boolean;
isChatPanelOpened: boolean;
protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790; protected readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
protected menuSubscription: Subscription; protected menuSubscription: Subscription;
@ -60,12 +70,45 @@ export class SessionComponent implements OnInit {
protected tokenService: TokenService, protected tokenService: TokenService,
protected layoutService: LayoutService, protected layoutService: LayoutService,
protected menuService: SidenavMenuService, protected menuService: SidenavMenuService,
protected libraryConfigSrv: LibraryConfigService
protected platformService: PlatformService
) { ) {
this.log = this.loggerSrv.get('SessionComponent'); this.log = this.loggerSrv.get('SessionComponent');
} }
@ViewChild('toolbar', { static: false, read: ViewContainerRef })
set toolbar(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.TOOLBAR);
reference.clear();
reference.createComponent(component);
}
}, 0);
}
@ViewChild('layout', { static: false, read: ViewContainerRef })
set layout(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.LAYOUT);
reference.clear();
reference.createComponent(component);
this.layoutService.initialize();
}
}, 0);
}
@ViewChild('panel', { static: false, read: ViewContainerRef })
set panel(reference: ViewContainerRef) {
setTimeout(() => {
if (reference) {
const component = this.libraryConfigSrv.getDynamicComponent(LibraryComponents.PANEL);
reference.clear();
reference.createComponent(component);
}
}, 0);
}
@HostListener('window:beforeunload') @HostListener('window:beforeunload')
beforeunloadHandler() { beforeunloadHandler() {
this.leaveSession(); this.leaveSession();
@ -87,8 +130,6 @@ export class SessionComponent implements OnInit {
} }
async ngOnInit() { async ngOnInit() {
this.layoutService.initialize();
if (this.webrtcService.getWebcamSession() === null) { if (this.webrtcService.getWebcamSession() === null) {
this.webrtcService.initialize(); this.webrtcService.initialize();
await this.webrtcService.initDefaultPublisher(undefined); await this.webrtcService.initDefaultPublisher(undefined);
@ -113,7 +154,7 @@ export class SessionComponent implements OnInit {
// this.webrtcService.publishVideo(this.localUserService.getMyCameraPublisher(), false); // this.webrtcService.publishVideo(this.localUserService.getMyCameraPublisher(), false);
// } // }
this._session.emit(this.session); // this._session.emit(this.session);
} }
ngOnDestroy() { ngOnDestroy() {
@ -124,8 +165,6 @@ export class SessionComponent implements OnInit {
this.session = null; this.session = null;
this.sessionScreen = null; this.sessionScreen = null;
this.layoutService.clear(); this.layoutService.clear();
this.isChatPanelOpened = false;
this.isParticipantsPanelOpened = false;
if (this.menuSubscription) this.menuSubscription.unsubscribe(); if (this.menuSubscription) this.menuSubscription.unsubscribe();
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe(); if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
} }
@ -152,11 +191,7 @@ export class SessionComponent implements OnInit {
}); });
this.menuSubscription = this.menuService.menuOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: MenuType }) => { this.menuSubscription = this.menuService.menuOpenedObs.pipe(skip(1)).subscribe((ev: { opened: boolean; type?: MenuType }) => {
if (this.sideMenu) { this.sideMenu && ev.opened ? this.sideMenu.open() : this.sideMenu.close();
this.isChatPanelOpened = ev.opened && ev.type === MenuType.CHAT;
this.isParticipantsPanelOpened = ev.opened && ev.type === MenuType.PARTICIPANTS;
ev.opened ? this.sideMenu.open() : this.sideMenu.close();
}
}); });
} }
@ -181,7 +216,7 @@ export class SessionComponent implements OnInit {
await this.webrtcService.publish(this.participantService.getMyCameraPublisher()); await this.webrtcService.publish(this.participantService.getMyCameraPublisher());
} }
} catch (error) { } catch (error) {
this._error.emit({ error: error.error, messgae: error.message, code: error.code, status: error.status }); // this._error.emit({ error: error.error, messgae: error.message, code: error.code, status: error.status });
this.log.e('There was an error connecting to the session:', error.code, error.message); this.log.e('There was an error connecting to the session:', error.code, error.message);
this.actionService.openDialog('There was an error connecting to the session:', error?.error || error?.message); this.actionService.openDialog('There was an error connecting to the session:', error?.error || error?.message);
} }

View File

@ -52,16 +52,10 @@ export class StreamComponent implements OnInit {
// this.isFullscreenEnabled = !this.isFullscreenEnabled; // this.isFullscreenEnabled = !this.isFullscreenEnabled;
// } // }
// Has been mandatory fullscreen Input because of Input user did not fire changing
// the fullscreen user property in publisherStartSpeaking event in SessionComponent
@Input()
set videoEnlarged(enlarged: boolean) {
this.checkVideoSizeBigIcon(enlarged);
}
@Input() @Input()
set participant(participant: StreamModel) { set participant(participant: StreamModel) {
this._participant = participant; this._participant = participant;
this.checkVideoSizeBigIcon(this._participant.videoEnlarged);
this.nicknameFormControl = new FormControl(this._participant.nickname, [Validators.maxLength(25), Validators.required]); this.nicknameFormControl = new FormControl(this._participant.nickname, [Validators.maxLength(25), Validators.required]);
} }

View File

@ -1,5 +1,5 @@
#toolbar { #toolbar {
height: 100%; /* height: 100%; */
background-color: transparent; background-color: transparent;
color: var(--ov-light-color); color: var(--ov-light-color);
} }

View File

@ -1,4 +1,15 @@
import { Component, ContentChild, EventEmitter, HostListener, OnDestroy, OnInit, Output, TemplateRef } from '@angular/core'; import {
AfterViewInit,
Component,
ContentChild,
EventEmitter,
HostListener,
OnDestroy,
OnInit,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import { skip, Subscription } from 'rxjs'; import { skip, Subscription } from 'rxjs';
import { TokenService } from '../../services/token/token.service'; import { TokenService } from '../../services/token/token.service';
import { ChatService } from '../../services/chat/chat.service'; import { ChatService } from '../../services/chat/chat.service';
@ -103,11 +114,9 @@ export class ToolbarComponent implements OnInit, OnDestroy {
this.subscribeToUserMediaProperties(); this.subscribeToUserMediaProperties();
this.subscribeToReconnection(); this.subscribeToReconnection();
if(!this.libraryConfigSrv.isUsingProLibrary()){
this.subscribeToMenuToggling(); this.subscribeToMenuToggling();
this.subscribeToChatMessages(); this.subscribeToChatMessages();
} }
}
toggleMicrophone() { toggleMicrophone() {
this.onMicClicked.emit(); this.onMicClicked.emit();
@ -268,7 +277,7 @@ export class ToolbarComponent implements OnInit, OnDestroy {
}); });
} }
protected subscribeToMenuToggling() { protected subscribeToMenuToggling() {
this.menuTogglingSubscription = this.menuService.menuOpenedObs.subscribe((ev: {opened: boolean, type?: MenuType}) => { this.menuTogglingSubscription = this.menuService.menuOpenedObs.subscribe((ev: { opened: boolean; type?: MenuType }) => {
this.isChatOpened = ev.opened && ev.type === MenuType.CHAT; this.isChatOpened = ev.opened && ev.type === MenuType.CHAT;
this.isParticipantsOpened = ev.opened && ev.type === MenuType.PARTICIPANTS; this.isParticipantsOpened = ev.opened && ev.type === MenuType.PARTICIPANTS;
if (this.isChatOpened) { if (this.isChatOpened) {

View File

@ -1,6 +1,6 @@
<div id="call-container"> <div id="call-container">
<div id="user-settings-container" *ngIf="!joinSessionClicked && !closeClicked"> <div id="user-settings-container" *ngIf="!joinSessionClicked && !closeClicked">
<ov-user-settings (onJoinClicked)="onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings> <ov-user-settings (onJoinClicked)="_onJoinClicked()" (onCloseClicked)="onLeaveSessionClicked()"></ov-user-settings>
</div> </div>
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && !error"> <div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && !error">
@ -10,21 +10,23 @@
<div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && error"> <div id="spinner" *ngIf="joinSessionClicked && !isSessionAlive && error">
<mat-icon class="error-icon">error</mat-icon> <mat-icon class="error-icon">error</mat-icon>
<span>{{errorMessage}}</span> <span>{{ errorMessage }}</span>
</div> </div>
<div id="session-container" *ngIf="joinSessionClicked && isSessionAlive && !error"> <div id="session-container" *ngIf="joinSessionClicked && isSessionAlive && !error">
<ov-session [tokens]="_tokens"> <ov-session [tokens]="_tokens">
<ng-template #toolbar>
<!-- Default toolbar -->
<!--<ng-template #toolbar toolbar>
<ov-toolbar <ov-toolbar
(onCamClicked)="onCamClicked()" (onCamClicked)="onCamClicked()"
(onMicClicked)="onMicClicked()" (onMicClicked)="onMicClicked()"
(onScreenShareClicked)="onScreenShareClicked()" (onScreenShareClicked)="onScreenShareClicked()"
(onSpeakerLayoutClicked)="onSpeakerLayoutClicked()"
(onLeaveSessionClicked)="onLeaveSessionClicked()" (onLeaveSessionClicked)="onLeaveSessionClicked()"
></ov-toolbar> ></ov-toolbar>
</ng-template> </ng-template>-->
<ov-layout layout></ov-layout>
<!-- <ov-layout layout></ov-layout> -->
</ov-session> </ov-session>
</div> </div>
</div> </div>

View File

@ -1,17 +1,31 @@
import { Component, Input, OnInit } from '@angular/core'; import {
import { RestService } from '../../services/rest/rest.service'; Component,
Input,
OnInit,
Output,
EventEmitter,
ViewChild,
ChangeDetectorRef,
AfterViewInit,
ViewContainerRef
} from '@angular/core';
import { LibraryConfigService } from '../../services/library-config/library-config.service';
import { ToolbarComponent } from '../toolbar/toolbar.component';
@Component({ @Component({
selector: 'ov-videoconference', selector: 'ov-videoconference',
templateUrl: './videoconference.component.html', templateUrl: './videoconference.component.html',
styleUrls: ['./videoconference.component.css'] styleUrls: ['./videoconference.component.css']
}) })
export class VideoconferenceComponent implements OnInit { export class VideoconferenceComponent implements OnInit, AfterViewInit {
@Input() sessionName: string; @Input() sessionName: string;
@Input() userName: string; @Input() userName: string;
@Input() openviduServerUrl: string; @Input() openviduServerUrl: string;
@Input() openviduSecret: string; @Input() openviduSecret: string;
@Input() tokens: { webcam: string; screen: string }; // @Input() tokens: { webcam: string; screen: string };
@Output() onJoinClicked = new EventEmitter<any>();
@Output() onCloseClicked = new EventEmitter<any>();
joinSessionClicked: boolean = false; joinSessionClicked: boolean = false;
closeClicked: boolean = false; closeClicked: boolean = false;
@ -20,42 +34,91 @@ export class VideoconferenceComponent implements OnInit {
error: boolean = false; error: boolean = false;
errorMessage: string = ''; errorMessage: string = '';
constructor(private restService: RestService) {} _toolbar: ViewContainerRef;
ngOnInit() {} constructor(protected libraryConfigSrv: LibraryConfigService, private cd: ChangeDetectorRef) {}
async onJoinClicked() { // @ViewChild('toolbar', { static: false, read: ViewContainerRef })
if (!this.tokens || (!this.tokens?.webcam && !this.tokens?.screen)) { // set toolbar(reference: ViewContainerRef) {
//No tokens received // setTimeout(() => {
// console.log('setting ref', reference);
// this._toolbar = reference;
if (!!this.sessionName && !!this.openviduServerUrl && !!this.openviduSecret) { // if (this._toolbar) {
// Generate tokens // let component = ToolbarComponent;
this._tokens = { // if (this.libraryConfigSrv.isCustomComponentDefined('ov-toolbar')) {
webcam: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret), // component = this.libraryConfigSrv.getToolbarComponent();
screen: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret) // }
}; // this._toolbar?.clear();
} else { // this._toolbar.createComponent(component);
// No tokens received and can't generate them // }
this.error = true; // }, 100);
this.errorMessage = `Cannot access to OpenVidu Server with url '${this.openviduServerUrl}' to genere tokens for session '${this.sessionName}'`; // }
throw this.errorMessage;
ngAfterViewInit() {
// if(this.customToolbar && this.libraryConfigSrv.isCustomComponentDefined('ov-toolbar')){
// const viewContainerRef = this.customToolbar.viewContainerRef;
// viewContainerRef.clear();
// const componentRef = viewContainerRef.createComponent<any>(this.libraryConfigSrv.getToolbarComponent());
// } else {
// this.customToolbar.viewContainerRef.clear();
// const viewContainerRef = this.toolbar.viewContainerRef;
// viewContainerRef.clear();
// viewContainerRef.createComponent<any>(ToolbarComponent);
// }
} }
} else if (!this.tokens?.webcam || !this.tokens?.screen) {
ngOnInit() {
}
@Input('tokens')
set tokens(tokens: { webcam: string; screen: string }) {
if (!!tokens?.webcam || !!this.tokens?.screen) {
// 1 token received // 1 token received
const aditionalToken = await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret); this.cd.detectChanges();
this._tokens = { // this.cd.markForCheck();
webcam: !!this.tokens.webcam ? this.tokens.webcam : aditionalToken, this._tokens = tokens;
screen: !!this.tokens.screen ? this.tokens.screen : aditionalToken
};
} else {
// 2 tokens received.
this._tokens = {
webcam: this.tokens.webcam,
screen: this.tokens.screen
};
}
this.joinSessionClicked = true; this.joinSessionClicked = true;
this.isSessionAlive = true; this.isSessionAlive = true;
} else {
//No tokens received
throw new Error('No tokens received');
}
}
async _onJoinClicked() {
this.onJoinClicked.emit();
// if (!this.tokens || (!this.tokens?.webcam && !this.tokens?.screen)) {
// //No tokens received
// if (!!this.sessionName && !!this.openviduServerUrl && !!this.openviduSecret) {
// // Generate tokens
// this._tokens = {
// webcam: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret),
// screen: await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret)
// };
// } else {
// // No tokens received and can't generate them
// this.error = true;
// this.errorMessage = `Cannot access to OpenVidu Server with url '${this.openviduServerUrl}' to genere tokens for session '${this.sessionName}'`;
// throw this.errorMessage;
// }
// } else if (!this.tokens?.webcam || !this.tokens?.screen) {
// // 1 token received
// const aditionalToken = await this.restService.getToken(this.sessionName, this.openviduServerUrl, this.openviduSecret);
// this._tokens = {
// webcam: !!this.tokens.webcam ? this.tokens.webcam : aditionalToken,
// screen: !!this.tokens.screen ? this.tokens.screen : aditionalToken
// };
// } else {
// // 2 tokens received.
// this._tokens = {
// webcam: this.tokens.webcam,
// screen: this.tokens.screen
// };
// }
// this.joinSessionClicked = true;
// this.isSessionAlive = true;
} }
onLeaveSessionClicked() { onLeaveSessionClicked() {
this.isSessionAlive = false; this.isSessionAlive = false;

View File

@ -1,6 +1,19 @@
import { Type } from '@angular/core';
export interface LibConfig { export interface LibConfig {
environment: { environment: {
production: boolean; production: boolean;
useProdLibrary?: boolean useProdLibrary?: boolean;
customComponents?: { LibraryComponents: Type<any> };
}; };
} }
export enum LibraryComponents {
TOOLBAR = 'ov-toolbar',
LAYOUT = 'ov-layout',
PANEL = 'ov-panel',
CHAT_PANEL = 'ov-chat-panel',
PARTICIPANTS_PANEL = 'ov-participants-panel',
STREAM = "ov-stream"
}

View File

@ -29,7 +29,7 @@ import { HttpClientModule } from '@angular/common/http';
import { UserSettingsComponent } from './components/user-settings/user-settings.component'; import { UserSettingsComponent } from './components/user-settings/user-settings.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component'; import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { VideoComponent } from './components/video/video.component'; import { VideoComponent } from './components/video/video.component';
import { ChatPanelComponent } from './components/chat-panel/chat-panel.component'; import { ChatPanelComponent } from './components/panel/chat-panel/chat-panel.component';
import { SessionComponent } from './components/session/session.component'; import { SessionComponent } from './components/session/session.component';
import { LayoutComponent } from './components/layout/layout.component'; import { LayoutComponent } from './components/layout/layout.component';
import { StreamComponent } from './components/stream/stream.component'; import { StreamComponent } from './components/stream/stream.component';
@ -54,9 +54,14 @@ import { DocumentService } from './services/document/document.service';
import { LayoutService } from './services/layout/layout.service'; import { LayoutService } from './services/layout/layout.service';
import { SidenavMenuService } from './services/sidenav-menu/sidenav-menu.service'; import { SidenavMenuService } from './services/sidenav-menu/sidenav-menu.service';
import { ParticipantService } from './services/participant/participant.service'; import { ParticipantService } from './services/participant/participant.service';
import { ParticipantItemComponent } from './components/participants-panel/participant-item/participant-item.component'; import { ParticipantItemComponent } from './components/panel/participants-panel/participant-item/participant-item.component';
import { ParticipantsPanelComponent } from './components/participants-panel/participants-panel/participants-panel.component'; import { ParticipantsPanelComponent } from './components/panel/participants-panel/participants-panel/participants-panel.component';
import { VideoconferenceComponent } from './components/videoconference/videoconference.component'; import { VideoconferenceComponent } from './components/videoconference/videoconference.component';
import { PanelComponent } from './components/panel/panel.component';
// Used for loading dynamic components because of Inputs and Outputs are not supported in the official Angular way
// https://github.com/angular/angular/issues/15360
import { DynamicIoModule } from 'ng-dynamic-component';
@NgModule({ @NgModule({
declarations: [ declarations: [
@ -75,7 +80,8 @@ import { VideoconferenceComponent } from './components/videoconference/videoconf
NicknamePipe, NicknamePipe,
ParticipantItemComponent, ParticipantItemComponent,
ParticipantsPanelComponent, ParticipantsPanelComponent,
VideoconferenceComponent VideoconferenceComponent,
PanelComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -102,7 +108,8 @@ import { VideoconferenceComponent } from './components/videoconference/videoconf
FlexLayoutModule, FlexLayoutModule,
MatMenuModule, MatMenuModule,
MatDividerModule, MatDividerModule,
MatListModule MatListModule,
DynamicIoModule
], ],
providers: [ providers: [
ActionService, ActionService,

View File

@ -1,16 +1,22 @@
import { Inject, Injectable } from '@angular/core'; import { Inject, Injectable, Type } from '@angular/core';
import { LibConfig } from '../../config/lib.config'; import { LayoutComponent } from '../../components/layout/layout.component';
import { ChatPanelComponent } from '../../components/panel/chat-panel/chat-panel.component';
import { PanelComponent } from '../../components/panel/panel.component';
import { ParticipantsPanelComponent } from '../../components/panel/participants-panel/participants-panel/participants-panel.component';
import { StreamComponent } from '../../components/stream/stream.component';
import { ToolbarComponent } from '../../components/toolbar/toolbar.component';
import { LibConfig, LibraryComponents } from '../../config/lib.config';
// import { version } from '../../../../package.json'; // import { version } from '../../../../package.json';
@Injectable() @Injectable()
export class LibraryConfigService { export class LibraryConfigService {
private configuration: LibConfig; private configuration: LibConfig;
constructor(@Inject('LIB_CONFIG') config: LibConfig) { constructor(@Inject('LIB_CONFIG') config: LibConfig) {
this.configuration = config; this.configuration = config;
console.log(this.configuration); console.log(this.configuration);
this.isUsingProLibrary() ? console.log('Using PRO library') : console.log('Using CE library');
if(this.isProduction()) console.log('Production Mode'); if(this.isProduction()) console.log('Production Mode');
// console.log(version) // console.log(version)
} }
@ -22,7 +28,64 @@ export class LibraryConfigService {
return this.configuration?.environment?.production; return this.configuration?.environment?.production;
} }
isUsingProLibrary(): boolean { getDynamicComponent(name: LibraryComponents) {
return !!this.configuration?.environment?.useProdLibrary; let component: Type<any>;
switch (name) {
case LibraryComponents.LAYOUT:
component = LayoutComponent;
if (this.isCustomComponentDefined(LibraryComponents.LAYOUT)) {
component = this.getCustomComponent(LibraryComponents.LAYOUT);
}
return component;
case LibraryComponents.TOOLBAR:
component = ToolbarComponent;
if (this.isCustomComponentDefined(LibraryComponents.TOOLBAR)) {
component = this.getCustomComponent(LibraryComponents.TOOLBAR);
}
return component;
case LibraryComponents.PANEL:
component = PanelComponent;
// Full custom panel
// if (this.isCustomComponentDefined(LibraryComponents.PANEL)) {
// component = this.getCustomComponent(LibraryComponents.PANEL);
// }
return component;
case LibraryComponents.CHAT_PANEL:
component = ChatPanelComponent;
if (this.isCustomComponentDefined(LibraryComponents.CHAT_PANEL)) {
component = this.getCustomComponent(LibraryComponents.CHAT_PANEL);
}
return component;
case LibraryComponents.PARTICIPANTS_PANEL:
component = ParticipantsPanelComponent;
if (this.isCustomComponentDefined(LibraryComponents.PARTICIPANTS_PANEL)) {
component = this.getCustomComponent(LibraryComponents.PARTICIPANTS_PANEL);
}
return component;
case LibraryComponents.STREAM:
component = StreamComponent;
if (this.isCustomComponentDefined(LibraryComponents.STREAM)) {
component = this.getCustomComponent(LibraryComponents.STREAM);
}
return component;
}
}
isCustomComponentDefined(component: string): boolean {
return !!this.configuration?.environment?.customComponents && !!this.configuration.environment.customComponents[component];
}
getCustomComponent(component: string){
return this.configuration.environment.customComponents[component];
} }
} }

View File

@ -24,8 +24,8 @@ export * from './lib/services/storage/storage.service';
export * from './lib/components/videoconference/videoconference.component'; export * from './lib/components/videoconference/videoconference.component';
export * from './lib/components/user-settings/user-settings.component'; export * from './lib/components/user-settings/user-settings.component';
export * from './lib/components/toolbar/toolbar.component'; export * from './lib/components/toolbar/toolbar.component';
export * from './lib/components/chat-panel/chat-panel.component'; export * from './lib/components/panel/chat-panel/chat-panel.component';
export * from './lib/components/participants-panel/participants-panel/participants-panel.component'; export * from './lib/components/panel/participants-panel/participants-panel/participants-panel.component';
export * from './lib/components/session/session.component'; export * from './lib/components/session/session.component';
export * from './lib/components/layout/layout.component'; export * from './lib/components/layout/layout.component';
export * from './lib/components/stream/stream.component'; export * from './lib/components/stream/stream.component';

View File

@ -1,6 +1,6 @@
# Openvidu health checker # Openvidu health checker
The main purpose of this image is to have a self-contained and indpendent docker image to test and check possible errors in OpenVidu deployments. The main purpose of this image is to have a self-contained and independent docker image to test and check possible errors in OpenVidu deployments.
This image is also usefull to automation tests of infrastructure. This image is also usefull to automation tests of infrastructure.

View File

@ -10,7 +10,8 @@ public interface IdentifierPrefixes {
public static final String SESSION_ID = "ses_"; public static final String SESSION_ID = "ses_";
public static final String TOKEN_ID = "tok_"; public static final String TOKEN_ID = "tok_";
public static final String IPCAM_ID = "ipc_"; public static final String IPCAM_ID = "ipc_";
public static final String MEDIA_ID = "media_"; public static final String MEDIA_NODE_ID = "media_";
public static final String OPENVIDU_NODE_ID = "opv_";
public static final String CLUSTER_ID = "clu_"; public static final String CLUSTER_ID = "clu_";
} }

View File

@ -360,7 +360,7 @@ public abstract class KmsManager {
} }
public static String generateKmsId() { public static String generateKmsId() {
return IdentifierPrefixes.MEDIA_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase() return IdentifierPrefixes.MEDIA_NODE_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase()
+ RandomStringUtils.randomAlphanumeric(7); + RandomStringUtils.randomAlphanumeric(7);
} }