ov-components: enhance layout service with responsive viewport handling and layout options adjustment

master
Carlos Santos 2025-09-22 20:06:25 +02:00
parent bd74184799
commit 1cef3c17a4
1 changed files with 163 additions and 19 deletions

View File

@ -1,8 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable, effect } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } from '../../models/layout.model'; import { LayoutAlignment, LayoutClass, OpenViduLayout, OpenViduLayoutOptions } from '../../models/layout.model';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
import { LoggerService } from '../logger/logger.service'; import { LoggerService } from '../logger/logger.service';
import { ViewportService } from '../viewport/viewport.service';
/** /**
* @internal * @internal
@ -16,14 +17,19 @@ export class LayoutService {
captionsTogglingObs: Observable<boolean>; captionsTogglingObs: Observable<boolean>;
protected layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0); protected layoutWidth: BehaviorSubject<number> = new BehaviorSubject(0);
protected openviduLayout: OpenViduLayout | undefined; protected openviduLayout: OpenViduLayout | undefined;
protected openviduLayoutOptions: OpenViduLayoutOptions; protected openviduLayoutOptions!: OpenViduLayoutOptions;
protected captionsToggling: BehaviorSubject<boolean> = new BehaviorSubject(false); protected captionsToggling: BehaviorSubject<boolean> = new BehaviorSubject(false);
protected log: ILogger; protected log: ILogger;
constructor(protected loggerSrv: LoggerService) { constructor(
protected loggerSrv: LoggerService,
protected viewportSrv: ViewportService
) {
this.layoutWidthObs = this.layoutWidth.asObservable(); this.layoutWidthObs = this.layoutWidth.asObservable();
this.captionsTogglingObs = this.captionsToggling.asObservable(); this.captionsTogglingObs = this.captionsToggling.asObservable();
this.log = this.loggerSrv.get('LayoutService'); this.log = this.loggerSrv.get('LayoutService');
this.openviduLayoutOptions = this.getOptions();
this.setupViewportListener();
} }
initialize(container: HTMLElement) { initialize(container: HTMLElement) {
@ -43,6 +49,7 @@ export class LayoutService {
update(timeout: number | undefined = undefined) { update(timeout: number | undefined = undefined) {
const updateAux = () => { const updateAux = () => {
if (this.openviduLayout && this.layoutContainer) { if (this.openviduLayout && this.layoutContainer) {
this.openviduLayoutOptions = this.getOptions();
this.openviduLayout.updateLayout(this.layoutContainer, this.openviduLayoutOptions); this.openviduLayout.updateLayout(this.layoutContainer, this.openviduLayoutOptions);
this.sendLayoutWidthEvent(); this.sendLayoutWidthEvent();
} }
@ -54,6 +61,10 @@ export class LayoutService {
} }
} }
updateResponsive() {
this.updateLayoutOptions();
}
getLayout() { getLayout() {
return this.openviduLayout; return this.openviduLayout;
} }
@ -62,27 +73,33 @@ export class LayoutService {
this.openviduLayout = undefined; this.openviduLayout = undefined;
} }
/**
* Get layout options adjusted to the current viewport
* @returns Layout options adjusted to the current viewport
*/
protected getOptions(): OpenViduLayoutOptions { protected getOptions(): OpenViduLayoutOptions {
const options = { const ratios = this.getResponsiveRatios();
maxRatio: 3 / 2, // The narrowest ratio that will be used (default 2x3) const percentages = this.getResponsivePercentages();
minRatio: 9 / 16, // The widest ratio that will be used (default 16x9)
fixedRatio: false /* If this is true then the aspect ratio of the video is maintained return {
and minRatio and maxRatio are ignored (default false) */, maxRatio: ratios.maxRatio,
bigClass: LayoutClass.BIG_ELEMENT, // The class to add to elements that should be sized bigger minRatio: ratios.minRatio,
fixedRatio: false,
bigClass: LayoutClass.BIG_ELEMENT,
smallClass: LayoutClass.SMALL_ELEMENT, smallClass: LayoutClass.SMALL_ELEMENT,
ignoredClass: LayoutClass.IGNORED_ELEMENT, ignoredClass: LayoutClass.IGNORED_ELEMENT,
bigPercentage: 0.8, // The maximum percentage of space the big ones should take up bigPercentage: percentages.bigPercentage,
minBigPercentage: 0, // If this is set then it will scale down the big space if there is left over whitespace down to this minimum size minBigPercentage: percentages.minBigPercentage,
bigFixedRatio: false, // fixedRatio for the big ones bigFixedRatio: false,
bigMaxRatio: 9 / 16, // The narrowest ratio to use for the big elements (default 2x3) bigMaxRatio: ratios.bigMaxRatio,
bigMinRatio: 9 / 16, // The widest ratio to use for the big elements (default 16x9) bigMinRatio: ratios.bigMinRatio,
bigFirst: true, // Whether to place the big one in the top left (true) or bottom right bigFirst: true,
animate: true, // Whether you want to animate the transitions. Deprecated property, to disable it remove the transaction property on OV_publisher css class animate: true,
alignItems: LayoutAlignment.CENTER, alignItems: LayoutAlignment.CENTER,
bigAlignItems: LayoutAlignment.CENTER, bigAlignItems: LayoutAlignment.CENTER,
smallAlignItems: LayoutAlignment.CENTER, smallAlignItems: LayoutAlignment.CENTER,
maxWidth: Infinity, // The maximum width of the elements maxWidth: Infinity,
maxHeight: Infinity, // The maximum height of the elements maxHeight: Infinity,
smallMaxWidth: Infinity, smallMaxWidth: Infinity,
smallMaxHeight: Infinity, smallMaxHeight: Infinity,
bigMaxWidth: Infinity, bigMaxWidth: Infinity,
@ -90,7 +107,134 @@ export class LayoutService {
scaleLastRow: true, scaleLastRow: true,
bigScaleLastRow: true bigScaleLastRow: true
}; };
return options; }
protected getResponsiveRatios() {
const isMobile = this.viewportSrv.isMobile();
const isTablet = this.viewportSrv.isTablet();
const isPortrait = this.viewportSrv.isPortrait();
if (isMobile && isPortrait) {
return {
maxRatio: 5 / 4,
minRatio: 4 / 5,
bigMaxRatio: 5 / 4,
bigMinRatio: 3 / 4
};
}
if (isMobile) {
return {
maxRatio: 16 / 9,
minRatio: 3 / 4,
bigMaxRatio: 16 / 9,
bigMinRatio: 4 / 3
};
}
if (isTablet && isPortrait) {
return {
maxRatio: 4 / 3,
minRatio: 3 / 5,
bigMaxRatio: 4 / 3,
bigMinRatio: 9 / 16
};
}
if (isTablet) {
return {
maxRatio: 16 / 9,
minRatio: 2 / 3,
bigMaxRatio: 16 / 9,
bigMinRatio: 9 / 16
};
}
return {
maxRatio: 16 / 9,
minRatio: 9 / 16,
bigMaxRatio: 16 / 9,
bigMinRatio: 9 / 16
};
}
protected getResponsivePercentages() {
const isMobile = this.viewportSrv.isMobile();
const isTablet = this.viewportSrv.isTablet();
const isPortrait = this.viewportSrv.isPortrait();
if (isMobile && isPortrait) {
return {
bigPercentage: 0.85,
minBigPercentage: 0.7
};
}
if (isMobile) {
return {
bigPercentage: 0.82,
minBigPercentage: 0.65
};
}
if (isTablet && isPortrait) {
return {
bigPercentage: 0.83,
minBigPercentage: 0.6
};
}
if (isTablet) {
return {
bigPercentage: 0.81,
minBigPercentage: 0.55
};
}
return {
bigPercentage: 0.8,
minBigPercentage: 0.5
};
}
protected setupViewportListener(): void {
effect(() => {
const viewportInfo = this.viewportSrv.viewportInfo();
const isMobile = this.viewportSrv.isMobile();
const orientation = this.viewportSrv.orientation();
this.updateLayoutOptions();
});
}
protected updateLayoutOptions(): void {
const newOptions = this.getOptions();
if (this.hasSignificantChanges(this.openviduLayoutOptions, newOptions)) {
this.openviduLayoutOptions = newOptions;
if (this.openviduLayout && this.layoutContainer) {
this.openviduLayout.updateLayout(this.layoutContainer, this.openviduLayoutOptions);
this.sendLayoutWidthEvent();
}
}
}
protected hasSignificantChanges(oldOptions: OpenViduLayoutOptions, newOptions: OpenViduLayoutOptions): boolean {
if (!oldOptions) return true;
const significantProps: (keyof OpenViduLayoutOptions)[] = [
'maxRatio',
'minRatio',
'bigMaxRatio',
'bigMinRatio',
'bigPercentage',
'alignItems',
'bigAlignItems'
];
return significantProps.some(
(prop) => Math.abs((oldOptions[prop] as number) - (newOptions[prop] as number)) > 0.01 || oldOptions[prop] !== newOptions[prop]
);
} }
protected sendLayoutWidthEvent() { protected sendLayoutWidthEvent() {