ov-components: Refactored and improved config

pull/842/head
Carlos Santos 2024-09-04 12:19:13 +02:00
parent 724fbd9186
commit bd6ba55504
15 changed files with 199 additions and 71 deletions

View File

@ -11,7 +11,7 @@ import {
ViewChild,
ViewContainerRef
} from '@angular/core';
import { Subscription } from 'rxjs';
import { combineLatest, map, Subscription } from 'rxjs';
import { StreamDirective } from '../../directives/template/openvidu-components-angular.directive';
import { ParticipantTrackPublication, ParticipantModel } from '../../models/participant.model';
import { LayoutService } from '../../services/layout/layout.service';
@ -19,6 +19,8 @@ import { ParticipantService } from '../../services/participant/participant.servi
import { CdkDrag } from '@angular/cdk/drag-drop';
import { PanelService } from '../../services/panel/panel.service';
import { GlobalConfigService } from '../../services/config/global-config.service';
import { ServiceConfigService } from '../../services/config/service-config.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
/**
*
@ -78,17 +80,21 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
private resizeTimeout: NodeJS.Timeout;
private videoIsAtRight: boolean = false;
private lastLayoutWidth: number = 0;
private layoutService: LayoutService;
/**
* @ignore
*/
constructor(
private layoutService: LayoutService,
private serviceConfig: ServiceConfigService,
private panelService: PanelService,
private participantService: ParticipantService,
private globalService: GlobalConfigService,
private directiveService: OpenViduComponentsConfigService,
private cd: ChangeDetectorRef
) {}
) {
this.layoutService = this.serviceConfig.getLayoutService();
}
ngOnInit(): void {
this.subscribeToParticipants();
@ -96,6 +102,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
}
ngAfterViewInit() {
console.log('LayoutComponent.ngAfterViewInit');
this.layoutService.initialize(this.layoutContainer.element.nativeElement);
this.lastLayoutWidth = this.layoutContainer.element.nativeElement.getBoundingClientRect().width;
this.listenToResizeLayout();
@ -142,7 +149,16 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
}
});
this.remoteParticipantsSubs = this.participantService.remoteParticipants$.subscribe((participants) => {
this.remoteParticipantsSubs = combineLatest([
this.participantService.remoteParticipants$,
this.directiveService.layoutRemoteParticipants$
])
.pipe(
map(([serviceParticipants, directiveParticipants]) =>
directiveParticipants !== undefined ? directiveParticipants : serviceParticipants
)
)
.subscribe((participants) => {
this.remoteParticipants = participants;
this.layoutService.update();
this.cd.markForCheck();

View File

@ -3,7 +3,6 @@ import { Subscription } from 'rxjs';
import { ILogger } from '../../models/logger.model';
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
import { LayoutService } from '../../services/layout/layout.service';
import { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { TranslateService } from '../../services/translate/translate.service';
@ -55,11 +54,9 @@ export class PreJoinComponent implements OnInit, OnDestroy {
@HostListener('window:resize')
sizeChange() {
this.windowSize = window.innerWidth;
this.layoutService.update();
}
constructor(
private layoutService: LayoutService,
private loggerSrv: LoggerService,
private libService: OpenViduComponentsConfigService,
private cdkSrv: CdkOverlayService,

View File

@ -47,6 +47,7 @@ import {
Track
} from 'livekit-client';
import { ParticipantModel } from '../../models/participant.model';
import { ServiceConfigService } from '../../services/config/service-config.service';
/**
* @internal
@ -88,15 +89,16 @@ export class SessionComponent implements OnInit, OnDestroy {
private updateLayoutInterval: NodeJS.Timeout;
private captionLanguageSubscription: Subscription;
private log: ILogger;
private layoutService: LayoutService;
constructor(
private serviceConfig: ServiceConfigService,
private actionService: ActionService,
private openviduService: OpenViduService,
private participantService: ParticipantService,
private loggerSrv: LoggerService,
private chatService: ChatService,
private libService: OpenViduComponentsConfigService,
private layoutService: LayoutService,
private panelService: PanelService,
private recordingService: RecordingService,
private broadcastingService: BroadcastingService,
@ -106,6 +108,7 @@ export class SessionComponent implements OnInit, OnDestroy {
private cd: ChangeDetectorRef
) {
this.log = this.loggerSrv.get('SessionComponent');
this.layoutService = this.serviceConfig.getLayoutService();
}
@HostListener('window:beforeunload')

View File

@ -4,6 +4,7 @@ import { CaptionsLangOption } from '../../../models/caption.model';
import { CaptionService } from '../../../services/caption/caption.service';
import { LayoutService } from '../../../services/layout/layout.service';
import { OpenViduService } from '../../../services/openvidu/openvidu.service';
import { ServiceConfigService } from '../../../services/config/service-config.service';
/**
* @internal
@ -22,8 +23,11 @@ export class CaptionsSettingComponent implements OnInit, OnDestroy {
private captionsStatusSubs: Subscription;
private sttStatusSubs: Subscription;
private layoutService: LayoutService;
constructor(private layoutService: LayoutService, private captionService: CaptionService, private openviduService: OpenViduService) {}
constructor(private serviceConfig: ServiceConfigService, private captionService: CaptionService, private openviduService: OpenViduService) {
this.layoutService = this.serviceConfig.getLayoutService();
}
ngOnInit(): void {
// this.isOpenViduPro = this.openviduService.isOpenViduPro();

View File

@ -4,11 +4,10 @@ import { Subscription } from 'rxjs';
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
import { LayoutService } from '../../services/layout/layout.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { ParticipantService } from '../../services/participant/participant.service';
import { StorageService } from '../../services/storage/storage.service';
import { Track } from 'livekit-client';
import { ParticipantTrackPublication } from '../../models/participant.model';
import { ServiceConfigService } from '../../services/config/service-config.service';
/**
* The **StreamComponent** is hosted inside of the {@link LayoutComponent}.
@ -99,17 +98,18 @@ export class StreamComponent implements OnInit, OnDestroy {
private videoControlsSub: Subscription;
private readonly HOVER_TIMEOUT = 3000;
private layoutService: LayoutService;
/**
* @ignore
*/
constructor(
private openviduService: OpenViduService,
private layoutService: LayoutService,
private serviceConfig: ServiceConfigService,
private participantService: ParticipantService,
private storageService: StorageService,
private cdkSrv: CdkOverlayService,
private libService: OpenViduComponentsConfigService
) {}
) {
this.layoutService = this.serviceConfig.getLayoutService();
}
ngOnInit() {
this.subscribeToStreamDirectives();

View File

@ -49,6 +49,7 @@ import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.servic
import { ParticipantModel } from '../../models/participant.model';
import { Room, RoomEvent } from 'livekit-client';
import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
import { ServiceConfigService } from '../../services/config/service-config.service';
/**
* The **ToolbarComponent** is hosted inside of the {@link VideoconferenceComponent}.
@ -342,11 +343,13 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private additionalButtonsPositionSub: Subscription;
private fullscreenChangeSubscription: Subscription;
private currentWindowHeight = window.innerHeight;
private layoutService: LayoutService;
/**
* @ignore
*/
constructor(
private serviceConfig: ServiceConfigService,
private documentService: DocumentService,
private chatService: ChatService,
private panelService: PanelService,
@ -355,7 +358,6 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private oVDevicesService: DeviceService,
private actionService: ActionService,
private loggerSrv: LoggerService,
private layoutService: LayoutService,
private cd: ChangeDetectorRef,
private libService: OpenViduComponentsConfigService,
private platformService: PlatformService,
@ -366,6 +368,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private cdkOverlayService: CdkOverlayService
) {
this.log = this.loggerSrv.get('ToolbarComponent');
this.layoutService = this.serviceConfig.getLayoutService();
}
/**
* @ignore

View File

@ -1,8 +1,9 @@
import { ParticipantProperties } from '../models/participant.model';
export interface OpenViduComponentsConfig {
production?: boolean,
participantFactory?: ParticipantFactoryFunction,
production?: boolean;
participantFactory?: ParticipantFactoryFunction;
services?: any;
}
export type ParticipantFactoryFunction = (props: ParticipantProperties) => any;

View File

@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { ActivitiesPanelBroadcastingActivityDirective, ActivitiesPanelRecordingActivityDirective } from './activities-panel.directive';
import { AdminLoginErrorDirective, AdminDashboardRecordingsListDirective } from './admin.directive';
import { LogoDirective } from './internals.directive';
import { LayoutRemoteParticipantsDirective, LogoDirective } from './internals.directive';
import { ParticipantPanelItemMuteButtonDirective } from './participant-panel-item.directive';
import {
StreamDisplayAudioDetectionDirective,
@ -76,7 +76,8 @@ import {
ActivitiesPanelRecordingActivityDirective,
ActivitiesPanelBroadcastingActivityDirective,
AdminDashboardRecordingsListDirective,
AdminLoginErrorDirective
AdminLoginErrorDirective,
LayoutRemoteParticipantsDirective
],
exports: [
LivekitUrlDirective,
@ -113,7 +114,8 @@ import {
ActivitiesPanelRecordingActivityDirective,
ActivitiesPanelBroadcastingActivityDirective,
AdminDashboardRecordingsListDirective,
AdminLoginErrorDirective
AdminLoginErrorDirective,
LayoutRemoteParticipantsDirective
]
})
export class ApiDirectiveModule {}

View File

@ -1,6 +1,8 @@
// * Private directives *
// * Internal directives *
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
import { ParticipantModel } from '../../models/participant.model';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
/**
* Load default OpenVidu logo if custom one is not exist
@ -21,3 +23,35 @@ export class LogoDirective {
element.src = this.ovLogo || this.defaultLogo;
}
}
/**
* @internal
*/
@Directive({
selector: 'ov-layout[ovRemoteParticipants]'
})
export class LayoutRemoteParticipantsDirective {
@Input() set ovRemoteParticipants(value: ParticipantModel[] | undefined) {
this.update(value);
}
constructor(
public elementRef: ElementRef,
private directiveService: OpenViduComponentsConfigService
) {}
ngOnDestroy(): void {
this.clear();
}
ngAfterViewInit() {
this.update(this.ovRemoteParticipants);
}
update(value: ParticipantModel[] | undefined) {
this.directiveService.setLayoutRemoteParticipants(value);
}
clear() {
this.update(undefined);
}
}

View File

@ -1,7 +1,7 @@
import { OverlayContainer } from '@angular/cdk/overlay';
import { CommonModule } from '@angular/common';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { ModuleWithProviders, NgModule } from '@angular/core';
import { ModuleWithProviders, NgModule, Provider } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DeleteDialogComponent } from './components/dialogs/delete-recording.component';
@ -25,7 +25,6 @@ import { CdkOverlayContainer } from './config/custom-cdk-overlay';
import { OpenViduComponentsConfig } from './config/openvidu-components-angular.config';
import { ActionService } from './services/action/action.service';
import { ChatService } from './services/chat/chat.service';
import { OpenViduComponentsConfigService } from './services/config/directive-config.service';
import { DeviceService } from './services/device/device.service';
import { DocumentService } from './services/document/document.service';
import { LayoutService } from './services/layout/layout.service';
@ -65,6 +64,8 @@ import { AppMaterialModule } from './openvidu-components-angular.material.module
import { VirtualBackgroundService } from './services/virtual-background/virtual-background.service';
import { BroadcastingService } from './services/broadcasting/broadcasting.service';
import { TranslateService } from './services/translate/translate.service';
import { GlobalConfigService } from './services/config/global-config.service';
import { OpenViduComponentsConfigService } from './services/config/directive-config.service';
const publicComponents = [
AdminDashboardComponent,
@ -131,6 +132,8 @@ const privateComponents = [
DragDropModule
],
providers: [
GlobalConfigService,
OpenViduComponentsConfigService,
ActionService,
BroadcastingService,
// CaptionService,
@ -139,7 +142,7 @@ const privateComponents = [
ChatService,
DeviceService,
DocumentService,
LayoutService,
// LayoutService,
LoggerService,
OpenViduService,
PanelService,
@ -153,12 +156,12 @@ const privateComponents = [
]
})
export class OpenViduComponentsModule {
static forRoot(config): ModuleWithProviders<OpenViduComponentsModule> {
// console.log(`${library.name} config: ${environment}`);
const libConfig: OpenViduComponentsConfig = config;
static forRoot(config: OpenViduComponentsConfig): ModuleWithProviders<OpenViduComponentsModule> {
const providers: Provider[] = [{ provide: 'OPENVIDU_COMPONENTS_CONFIG', useValue: config }];
return {
ngModule: OpenViduComponentsModule,
providers: [OpenViduComponentsConfigService, { provide: 'OPENVIDU_COMPONENTS_CONFIG', useValue: libConfig }]
providers
};
}
}

View File

@ -2,11 +2,14 @@ import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { RecordingInfo } from '../../models/recording.model';
import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
import { ParticipantModel } from '../../models/participant.model';
/**
* @internal
*/
@Injectable()
@Injectable({
providedIn: 'root'
})
export class OpenViduComponentsConfigService {
private token = <BehaviorSubject<string>>new BehaviorSubject('');
token$: Observable<string>;
@ -87,6 +90,10 @@ export class OpenViduComponentsConfigService {
private adminLoginError = <BehaviorSubject<any>>new BehaviorSubject(null);
adminLoginError$: Observable<any>;
// Internals
private layoutRemoteParticipants: BehaviorSubject<ParticipantModel[] | undefined> = new BehaviorSubject(<any>undefined);
layoutRemoteParticipants$: Observable<ParticipantModel[] | undefined>;
constructor() {
this.token$ = this.token.asObservable();
this.livekitUrl$ = this.livekitUrl.asObservable();
@ -124,6 +131,8 @@ export class OpenViduComponentsConfigService {
// Admin dashboard
this.adminRecordingsList$ = this.adminRecordingsList.asObservable();
this.adminLoginError$ = this.adminLoginError.asObservable();
// Internals
this.layoutRemoteParticipants$ = this.layoutRemoteParticipants.asObservable();
}
setToken(token: string) {
@ -364,4 +373,9 @@ export class OpenViduComponentsConfigService {
isBroadcastingEnabled(): boolean {
return this.broadcastingButton.getValue() && this.broadcastingActivity.getValue();
}
// Internals
setLayoutRemoteParticipants(participants: ParticipantModel[] | undefined) {
this.layoutRemoteParticipants.next(participants);
}
}

View File

@ -2,6 +2,9 @@ import { DOCUMENT } from '@angular/common';
import { Inject, Injectable} from '@angular/core';
import { ParticipantFactoryFunction, OpenViduComponentsConfig } from '../../config/openvidu-components-angular.config';
/**
* @internal
*/
@Injectable({
providedIn: 'root'
})

View File

@ -0,0 +1,40 @@
import { Injectable, Inject, Injector, Type, Optional } from '@angular/core';
import { LayoutService } from '../layout/layout.service';
import { OpenViduComponentsConfig } from '../../config/openvidu-components-angular.config';
/**
* @internal
*/
@Injectable({
providedIn: 'root'
})
export class ServiceConfigService {
private configuration: OpenViduComponentsConfig;
constructor(
@Optional() private injector: Injector,
@Inject('OPENVIDU_COMPONENTS_CONFIG') config: OpenViduComponentsConfig
) {
this.configuration = config;
}
getLayoutService(): LayoutService {
return this.getServiceSafely<LayoutService>('LayoutService', LayoutService);
}
private getService<T>(key: string): T {
const service = this.configuration.services ? this.configuration.services[key] : null;
if (!service) {
throw new Error(`No service registered with key ${key}`);
}
return this.injector.get(service) as T;
}
private getServiceSafely<T>(key: string, fallback: new (...args: any[]) => T): T {
try {
return this.getService<T>(key);
} catch {
return this.injector.get(fallback);
}
}
}

View File

@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } from '../../models/layout.model';
import { ILogger } from '../../models/logger.model';
import { LoggerService } from '../logger/logger.service';
/**
* @internal
@ -9,17 +11,19 @@ import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } f
providedIn: 'root'
})
export class LayoutService {
layoutContainer: HTMLElement | null = null;
layoutContainer: HTMLElement | undefined = undefined;
layoutWidthObs: Observable<number>;
captionsTogglingObs: Observable<boolean>;
private layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0);
private openviduLayout: OpenViduLayout;
private openviduLayoutOptions: OpenViduLayoutOptions;
private captionsToggling: BehaviorSubject<boolean> = new BehaviorSubject(false);
protected layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0);
protected openviduLayout: OpenViduLayout | undefined;
protected openviduLayoutOptions: OpenViduLayoutOptions;
protected captionsToggling: BehaviorSubject<boolean> = new BehaviorSubject(false);
protected log: ILogger;
constructor() {
constructor(protected loggerSrv: LoggerService) {
this.layoutWidthObs = this.layoutWidth.asObservable();
this.captionsTogglingObs = this.captionsToggling.asObservable();
this.log = this.loggerSrv.get('LayoutService');
}
initialize(container: HTMLElement) {
@ -32,7 +36,33 @@ export class LayoutService {
this.sendLayoutWidthEvent();
}
private getOptions(): OpenViduLayoutOptions {
toggleCaptions() {
this.captionsToggling.next(!this.captionsToggling.getValue());
}
update(timeout: number | undefined = undefined) {
const updateAux = () => {
if (this.openviduLayout && this.layoutContainer) {
this.openviduLayout.updateLayout(this.layoutContainer, this.openviduLayoutOptions);
this.sendLayoutWidthEvent();
}
};
if (typeof timeout === 'number' && timeout >= 0) {
setTimeout(() => updateAux(), timeout);
} else {
updateAux();
}
}
getLayout() {
return this.openviduLayout;
}
clear() {
this.openviduLayout = undefined;
}
protected getOptions(): OpenViduLayoutOptions {
const options = {
maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3)
minRatio: 9 / 16, // The widest ratio that will be used (default 16x9)
@ -63,43 +93,19 @@ export class LayoutService {
return options;
}
toggleCaptions() {
this.captionsToggling.next(!this.captionsToggling.getValue());
protected sendLayoutWidthEvent() {
const layoutContainer = this.openviduLayout?.getLayoutContainer();
if (!layoutContainer) {
this.log.e('Layout container not found. Cannot send layout width event');
return;
}
update(timeout: number = null) {
const updateAux = () => {
if (!!this.openviduLayout) {
this.openviduLayout.updateLayout(this.layoutContainer, this.openviduLayoutOptions);
this.sendLayoutWidthEvent();
}
};
if (typeof timeout === 'number' && timeout >= 0) {
setTimeout(() => updateAux(), timeout);
} else {
updateAux();
}
}
getLayout() {
return this.openviduLayout;
}
clear() {
this.openviduLayout = null;
}
private sendLayoutWidthEvent() {
const sidenavLayoutElement = this.getHTMLElementByClassName(
this.openviduLayout?.getLayoutContainer(),
LayoutClass.SIDENAV_CONTAINER
);
const sidenavLayoutElement = this.getHTMLElementByClassName(layoutContainer, LayoutClass.SIDENAV_CONTAINER);
if (sidenavLayoutElement && sidenavLayoutElement.clientWidth) {
this.layoutWidth.next(sidenavLayoutElement.clientWidth);
}
}
private getHTMLElementByClassName(element: HTMLElement | null, className: string): HTMLElement | null {
protected getHTMLElementByClassName(element: HTMLElement | null, className: string): HTMLElement | null {
while (!!element && element !== document.body) {
if (element.className.includes(className)) {
return element;

View File

@ -36,6 +36,7 @@ export * from './lib/models/recording.model';
export * from './lib/models/data-topic.model';
export * from './lib/models/room.model';
export * from './lib/models/toolbar.model';
export * from './lib/models/logger.model'
export * from './lib/openvidu-components-angular.module';
// Pipes
export * from './lib/pipes/participant.pipe';
@ -50,5 +51,6 @@ export * from './lib/services/panel/panel.service';
export * from './lib/services/participant/participant.service';
export * from './lib/services/recording/recording.service';
export * from './lib/services/config/global-config.service';
export * from './lib/services/logger/logger.service';
export * from 'livekit-client';