openvidu-component: Loaded components dynamically

- For loading components with inputs/outputs directives, has been necessary to use the 'ng-dynamic-component' library because of unsupported on the official Angular issue  https://github.com/angular/angular/issues/15360
- Allowed the dynamic components load
- Grouped panels into panel component
pull/690/head
csantosm 2022-01-28 15:16:09 +01:00
parent e56ac82749
commit 5f0ffb4b3a
32 changed files with 462 additions and 144 deletions

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,11 @@
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';
import { StreamComponent } from '../stream/stream.component';
@Component({ @Component({
selector: 'ov-layout', selector: 'ov-layout',
@ -10,8 +13,10 @@ 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>; _localStream: ViewContainerRef;
@ContentChild('customRemoteParticipants', { read: TemplateRef }) customRemoteParticipantsTemplate: TemplateRef<any>; _localStreamComponent: Type<any>;
_remoteStream: ViewContainerRef;
_remoteStreamComponent: Type<any>;
localParticipant: ParticipantAbstractModel; localParticipant: ParticipantAbstractModel;
remoteParticipants: ParticipantAbstractModel[] = []; remoteParticipants: ParticipantAbstractModel[] = [];
@ -19,10 +24,48 @@ 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(() => {
this._localStream = reference;
if (this._localStream) {
let component = StreamComponent;
if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.STREAM)) {
component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.STREAM);
}
// this._stream?.clear();
this._localStreamComponent = component;
// this._stream.createComponent(component);
}
}, 0);
}
@ViewChild('remoteStream', { static: false, read: ViewContainerRef })
set remoteStream(reference: ViewContainerRef) {
setTimeout(() => {
this._remoteStream = reference;
if (this._remoteStream) {
let component = StreamComponent;
if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.STREAM)) {
component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.STREAM);
}
// this.remoteStream?.clear();
this._remoteStreamComponent = component;
// this._stream.createComponent(component);
}
}, 0);
}
ngOnInit(): void { ngOnInit(): void {
this.subscribeToUsers(); this.subscribeToParticipants();
} }
ngAfterViewInit() {} ngAfterViewInit() {}
@ -34,7 +77,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,71 @@
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';
import { ChatPanelComponent } from './chat-panel/chat-panel.component';
import { ParticipantsPanelComponent } from './participants-panel/participants-panel/participants-panel.component';
@Component({
selector: 'ov-panel',
templateUrl: './panel.component.html',
styleUrls: ['./panel.component.css']
})
export class PanelComponent implements OnInit, OnDestroy {
isParticipantsPanelOpened: boolean;
isChatPanelOpened: boolean;
_chat: ViewContainerRef;
_participants: ViewContainerRef;
menuSubscription: Subscription;
@ViewChild('chat', { static: false, read: ViewContainerRef })
set chat(reference: ViewContainerRef) {
setTimeout(() => {
this._chat = reference;
if (this._chat) {
let component = ChatPanelComponent;
if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.CHAT_PANEL)) {
component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.CHAT_PANEL);
}
this._chat?.clear();
this._chat.createComponent(component);
}
}, 0);
}
@ViewChild('participants', { static: false, read: ViewContainerRef })
set participants(reference: ViewContainerRef) {
setTimeout(() => {
this._participants = reference;
if (this._participants) {
let component = ParticipantsPanelComponent;
if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.PARTICIPANTS_PANEL)) {
component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.PARTICIPANTS_PANEL);
}
this._participants?.clear();
this._participants.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

@ -11,22 +11,30 @@
fixedBottomGap="0" fixedBottomGap="0"
> >
<!-- Custom menu content --> <!-- Custom menu content -->
<ng-container *ngIf="customPanelContentTemplate; else defaultPanelContent"> <!-- <ng-container *ngIf="customPanelContentTemplate; else defaultPanelContent"> -->
<ng-container *ngTemplateOutlet="customPanelContentTemplate"></ng-container> <!-- <ng-container *ngTemplateOutlet="customPanelContentTemplate"></ng-container> -->
</ng-container> <!-- </ng-container> -->
<!-- Default menu content if custom menu content is not injected --> <!-- Default menu content if custom menu content is not injected -->
<ng-template #defaultPanelContent> <!-- <ng-template #defaultPanelContent>
<ov-chat-panel *ngIf="isChatPanelOpened"></ov-chat-panel> <ov-chat-panel *ngIf="isChatPanelOpened"></ov-chat-panel>
<ov-participants-panel *ngIf="isParticipantsPanelOpened"></ov-participants-panel> <ov-participants-panel *ngIf="isParticipantsPanelOpened"></ov-participants-panel>
</ng-template> </ng-template> -->
<ng-template #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"> <!-- <div id="layout-container">
<ng-content select="[layout]"></ng-content> <ng-content select="[layout]"></ng-content>
</div> </div> -->
<ng-template #layout></ng-template>
</mat-sidenav-content> </mat-sidenav-content>
</mat-sidenav-container> </mat-sidenav-container>
@ -37,9 +45,11 @@
</div> </div>
</ng-container> --> </ng-container> -->
<ng-container *ngIf="toolbarTemplate"> <!-- <ng-container *ngIf="toolbarTemplate">
<div id="footer-container"> <div id="footer-container">
<ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container> <ng-container *ngTemplateOutlet="toolbarTemplate"></ng-container>
</div> </div>
</ng-container> </ng-container> -->
<ng-template #toolbar></ng-template>
</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,14 @@ 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('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
@ContentChild('customPanelContent', { read: TemplateRef }) customPanelContentTemplate: TemplateRef<any>; @ContentChild('customPanelContent', { read: TemplateRef }) customPanelContentTemplate: TemplateRef<any>;
@ContentChild('customLayoutElement', { read: TemplateRef }) customLayoutElementTemplate: 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 +55,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;
@ -51,6 +65,10 @@ export class SessionComponent implements OnInit {
protected log: ILogger; protected log: ILogger;
_toolbar: ViewContainerRef;
_layout: ViewContainerRef;
_panel: ViewContainerRef;
constructor( constructor(
protected actionService: ActionService, protected actionService: ActionService,
protected webrtcService: WebrtcService, protected webrtcService: WebrtcService,
@ -60,12 +78,63 @@ 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(() => {
this._toolbar = reference;
if (this._toolbar) {
let component = ToolbarComponent;
// Inject the custom component if exists
if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.TOOLBAR)) {
component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.TOOLBAR);
}
this._toolbar?.clear();
this._toolbar.createComponent(component);
}
}, 0);
}
@ViewChild('layout', { static: false, read: ViewContainerRef })
set layout(reference: ViewContainerRef) {
setTimeout(() => {
this._layout = reference;
if (this._layout) {
let component = LayoutComponent;
// Inject the custom component if exists
if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.LAYOUT)) {
component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.LAYOUT);
}
this._layout?.clear();
this._layout.createComponent(component);
this.layoutService.initialize();
}
}, 0);
}
@ViewChild('panel', { static: false, read: ViewContainerRef })
set panel(reference: ViewContainerRef) {
setTimeout(() => {
this._panel = reference;
if (this._panel) {
let component = PanelComponent;
// Inject the custom component if exists
// if (this.libraryConfigSrv.isCustomComponentDefined(LibraryComponents.PANEL)) {
// component = this.libraryConfigSrv.getCustomComponent(LibraryComponents.PANEL);
// }
this._panel?.clear();
this._panel.createComponent(component);
}
}, 0);
}
@HostListener('window:beforeunload') @HostListener('window:beforeunload')
beforeunloadHandler() { beforeunloadHandler() {
this.leaveSession(); this.leaveSession();
@ -87,8 +156,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 +180,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 +191,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 +217,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 +242,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">
@ -15,16 +15,18 @@
<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

@ -5,12 +5,12 @@ import { LibConfig } from '../../config/lib.config';
@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 +22,11 @@ export class LibraryConfigService {
return this.configuration?.environment?.production; return this.configuration?.environment?.production;
} }
isUsingProLibrary(): boolean { isCustomComponentDefined(component: string): boolean {
return !!this.configuration?.environment?.useProdLibrary; 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';