ov-components: enhance layout model with extended options and improved dimension calculations

master
Carlos Santos 2025-09-22 20:22:27 +02:00
parent 1cef3c17a4
commit bab8d3eb2a
1 changed files with 224 additions and 450 deletions

View File

@ -29,127 +29,145 @@ export enum LayoutAlignment {
END = 'end' END = 'end'
} }
/**
* Layout position options for big elements
*/
export type BigFirstOption = boolean | 'column' | 'row';
/**
* Element dimensions interface
*/
export interface ElementDimensions {
height: number;
width: number;
big?: boolean;
}
/**
* Layout area definition
*/
export interface LayoutArea {
top: number;
left: number;
width: number;
height: number;
}
/**
* Layout box positioning
*/
export interface LayoutBox extends LayoutArea {}
/**
* Row structure for layout calculations
*/
export interface LayoutRow {
ratios: number[];
width: number;
height: number;
}
/**
* Best dimensions calculation result
*/
export interface BestDimensions {
maxArea: number;
targetCols: number;
targetRows: number;
targetHeight: number;
targetWidth: number;
ratio: number;
}
/**
* Extended layout options with container dimensions
*/
export interface ExtendedLayoutOptions extends OpenViduLayoutOptions {
containerWidth: number;
containerHeight: number;
}
/**
* Layout configuration constants
*/
export const LAYOUT_CONSTANTS = {
DEFAULT_VIDEO_WIDTH: 640,
DEFAULT_VIDEO_HEIGHT: 480,
DEFAULT_MAX_RATIO: 3 / 2,
DEFAULT_MIN_RATIO: 9 / 16,
DEFAULT_BIG_PERCENTAGE: 0.8,
UPDATE_TIMEOUT: 50,
ANIMATION_DURATION: '0.1s',
ANIMATION_EASING: 'linear'
} as const;
/** /**
* @internal * @internal
*/ */
export interface OpenViduLayoutOptions { export interface OpenViduLayoutOptions {
/** /** The narrowest ratio that will be used (2x3 by default) */
* The narrowest ratio that will be used (*2x3* by default)
*/
maxRatio: number; maxRatio: number;
/** The widest ratio that will be used (16x9 by default) */
/**
* The widest ratio that will be used (*16x9* by default)
*/
minRatio: number; minRatio: number;
/** If true, aspect ratio is maintained and minRatio/maxRatio are ignored */
/**
* If this is true then the aspect ratio of the video is maintained and minRatio and maxRatio are ignored (*false* by default)
*/
fixedRatio: boolean; fixedRatio: boolean;
/** /** Whether to animate transitions */
* Whether you want to animate the transitions animate: boolean;
*/ /** Class for elements that should be sized bigger */
animate: any;
/**
* The class to add to elements that should be sized bigger
*/
bigClass: string; bigClass: string;
/** Class for elements that should be sized smaller */
/**
* The class to add to elements that should be sized smaller
*/
smallClass: string; smallClass: string;
/** Class for elements that should be ignored */
/**
* The class to add to elements that should be ignored
*/
ignoredClass: string; ignoredClass: string;
/** Maximum percentage of space big elements should take up */
/** bigPercentage: number;
* The maximum percentage of space the big ones should take up /** Minimum percentage for big space to scale down whitespace */
*/
bigPercentage: any;
/**
* If this is set then it will scale down the big space if there is left over whitespace down to this minimum size
*/
minBigPercentage: number; minBigPercentage: number;
/** Fixed ratio for big elements */
/** bigFixedRatio: boolean;
* FixedRatio for the big ones /** Narrowest ratio for big elements */
*/ bigMaxRatio: number;
bigFixedRatio: any; /** Widest ratio for big elements */
bigMinRatio: number;
/** /** Position preference for big elements */
* The narrowest ratio to use for the big elements (*2x3* by default) bigFirst: BigFirstOption;
*/ /** Alignment for all elements */
bigMaxRatio: any;
/**
* The widest ratio to use for the big elements (*16x9* by default)
*/
bigMinRatio: any;
/**
* Whether to place the big one in the top left `true` or bottom right
*/
bigFirst: boolean | 'column' | 'row';
/**
*
*/
alignItems: LayoutAlignment; alignItems: LayoutAlignment;
/** /** Alignment for big elements */
*
*/
bigAlignItems: LayoutAlignment; bigAlignItems: LayoutAlignment;
/** /** Alignment for small elements */
*
*/
smallAlignItems: LayoutAlignment; smallAlignItems: LayoutAlignment;
/** /** Maximum width of elements */
* The maximum width of the elements
*/
maxWidth: number; maxWidth: number;
/** /** Maximum height of elements */
* The maximum height of the elements
*/
maxHeight: number; maxHeight: number;
/** Maximum width for small elements */
smallMaxWidth: number; smallMaxWidth: number;
/** Maximum height for small elements */
smallMaxHeight: number; smallMaxHeight: number;
/** Maximum width for big elements */
bigMaxWidth: number; bigMaxWidth: number;
/** Maximum height for big elements */
bigMaxHeight: number; bigMaxHeight: number;
/** Scale up elements in last row if fewer elements */
/** scaleLastRow: boolean;
* If there are less elements on the last row then we can scale them up to take up more space /** Scale up big elements in last row */
*/ bigScaleLastRow: boolean;
scaleLastRow?: boolean;
/**
* Scale last row for the big elements
*/
bigScaleLastRow?: boolean;
} }
/** /**
* @internal * @internal
*/ */
export class OpenViduLayout { export class OpenViduLayout {
/** private layoutContainer!: HTMLElement;
* @hidden private opts!: OpenViduLayoutOptions;
*/ private dimensionsCache = new Map<string, BestDimensions>();
private layoutContainer: HTMLElement;
/**
* @hidden
*/
private opts: OpenViduLayoutOptions;
/** /**
* Update the layout container * Update the layout container
* module export layout * module export layout
*/ */
updateLayout(container: HTMLElement, opts: any) { updateLayout(container: HTMLElement, opts: OpenViduLayoutOptions) {
setTimeout(() => { setTimeout(() => {
this.layoutContainer = container; this.layoutContainer = container;
this.opts = opts; this.opts = opts;
@ -163,14 +181,15 @@ export class OpenViduLayout {
this.layoutContainer.id = id; this.layoutContainer.id = id;
} }
opts.containerHeight = const extendedOpts: ExtendedLayoutOptions = {
this.getHeight(this.layoutContainer) - ...opts,
containerHeight: this.getHeight(this.layoutContainer) -
this.getCSSNumber(this.layoutContainer, 'border-top') - this.getCSSNumber(this.layoutContainer, 'border-top') -
this.getCSSNumber(this.layoutContainer, 'border-bottom'); this.getCSSNumber(this.layoutContainer, 'border-bottom'),
opts.containerWidth = containerWidth: this.getWidth(this.layoutContainer) -
this.getWidth(this.layoutContainer) -
this.getCSSNumber(this.layoutContainer, 'border-left') - this.getCSSNumber(this.layoutContainer, 'border-left') -
this.getCSSNumber(this.layoutContainer, 'border-right'); this.getCSSNumber(this.layoutContainer, 'border-right')
};
const selector = `#${id}>*:not(.${LayoutClass.IGNORED_ELEMENT}):not(.${LayoutClass.MINIMIZED_ELEMENT})`; const selector = `#${id}>*:not(.${LayoutClass.IGNORED_ELEMENT}):not(.${LayoutClass.MINIMIZED_ELEMENT})`;
const children = Array.prototype.filter.call( const children = Array.prototype.filter.call(
@ -183,7 +202,7 @@ export class OpenViduLayout {
return res; return res;
}); });
const layout = this.getLayout(opts, elements); const layout = this.getLayout(extendedOpts, elements);
layout.boxes.forEach((box, idx) => { layout.boxes.forEach((box, idx) => {
const elem = children[idx]; const elem = children[idx];
this.getCssProperty(elem, 'position', 'absolute'); this.getCssProperty(elem, 'position', 'absolute');
@ -211,7 +230,7 @@ export class OpenViduLayout {
this.positionElement(elem, box.left, box.top, actualWidth, actualHeight, this.opts.animate); this.positionElement(elem, box.left, box.top, actualWidth, actualHeight, this.opts.animate);
}); });
}, 50); }, LAYOUT_CONSTANTS.UPDATE_TIMEOUT);
} }
/** /**
@ -220,22 +239,6 @@ export class OpenViduLayout {
* @param opts * @param opts
*/ */
initLayoutContainer(container: HTMLElement, opts: OpenViduLayoutOptions) { initLayoutContainer(container: HTMLElement, opts: OpenViduLayoutOptions) {
// this.opts = this.defaults(opts, {
// maxRatio: 3 / 2,
// minRatio: 9 / 16,
// fixedRatio: false,
// animate: false,
// bigClass: LayoutClass.BIG_ELEMENT,
// smallClass: LayoutClass.SMALL_ELEMENT,
// bigPercentage: 0.8,
// bigFixedRatio: false,
// bigMaxRatio: 3 / 2,
// bigMinRatio: 9 / 16,
// bigFirst: true,
// alignItems: 'center',
// bigAlignItems: 'center',
// smallAlignItems: 'center'
// });
this.opts = opts; this.opts = opts;
this.layoutContainer = container; this.layoutContainer = container;
this.updateLayout(container, opts); this.updateLayout(container, opts);
@ -246,12 +249,13 @@ export class OpenViduLayout {
} }
/** /**
* Set the layout configuration * Clear dimensions cache to free memory
* @param options
*/ */
// private setLayoutOptions(options: OpenViduLayoutOptions) { clearCache(): void {
// this.opts = options; this.dimensionsCache.clear();
// } }
private getCssProperty(el: HTMLVideoElement | HTMLElement, propertyName: any, value?: string): void | string { private getCssProperty(el: HTMLVideoElement | HTMLElement, propertyName: any, value?: string): void | string {
if (value !== undefined) { if (value !== undefined) {
@ -292,15 +296,7 @@ export class OpenViduLayout {
return this.getCssProperty(el, 'width'); return this.getCssProperty(el, 'width');
} }
// private defaults(custom: OpenViduLayoutOptions, defaults: OpenViduLayoutOptions): OpenViduLayoutOptions {
// var res = defaults;
// Object.keys(defaults).forEach((key) => {
// if (custom.hasOwnProperty(key)) {
// res[key] = custom[key];
// }
// });
// return res;
// }
/** /**
* @hidden * @hidden
@ -348,12 +344,12 @@ export class OpenViduLayout {
private setElementPosition(elem: HTMLVideoElement, targetPosition: { [key: string]: string }) { private setElementPosition(elem: HTMLVideoElement, targetPosition: { [key: string]: string }) {
Object.keys(targetPosition).forEach((key) => { Object.keys(targetPosition).forEach((key) => {
elem.style[key] = targetPosition[key]; (elem.style as any)[key] = targetPosition[key];
}); });
} }
private animateElement(elem: HTMLVideoElement, targetPosition: { [key: string]: string }) { private animateElement(elem: HTMLVideoElement, targetPosition: { [key: string]: string }) {
elem.style.transition = 'all .1s linear'; elem.style.transition = `all ${LAYOUT_CONSTANTS.ANIMATION_DURATION} ${LAYOUT_CONSTANTS.ANIMATION_EASING}`;
this.setElementPosition(elem, targetPosition); this.setElementPosition(elem, targetPosition);
} }
@ -378,8 +374,8 @@ export class OpenViduLayout {
} }
} }
return { return {
height: 480, height: LAYOUT_CONSTANTS.DEFAULT_VIDEO_HEIGHT,
width: 640 width: LAYOUT_CONSTANTS.DEFAULT_VIDEO_WIDTH
}; };
} }
@ -415,241 +411,7 @@ export class OpenViduLayout {
return widthStr ? parseInt(widthStr, 10) : 0; return widthStr ? parseInt(widthStr, 10) : 0;
} }
/**
* @hidden
*/
// private arrange(
// children: HTMLVideoElement[],
// containerWidth: number,
// containerHeight: number,
// offsetLeft: number,
// offsetTop: number,
// fixedRatio: boolean,
// minRatio: number,
// maxRatio: number,
// animate: any
// ) {
// const boxes = this.getLayout(
// {
// containerWidth,
// containerHeight,
// minRatio,
// maxRatio,
// fixedRatio,
// },
// children.map((child) => this.getVideoRatio(child))
// );
// boxes.forEach((box, idx) => {
// const elem = children[idx];
// this.css(elem, 'position', 'absolute');
// const actualWidth =
// box.width -
// this.getCSSNumber(elem, 'paddingLeft') -
// this.getCSSNumber(elem, 'paddingRight') -
// this.getCSSNumber(elem, 'marginLeft') -
// this.getCSSNumber(elem, 'marginRight') -
// this.getCSSNumber(elem, 'borderLeft') -
// this.getCSSNumber(elem, 'borderRight');
// const actualHeight =
// box.height -
// this.getCSSNumber(elem, 'paddingTop') -
// this.getCSSNumber(elem, 'paddingBottom') -
// this.getCSSNumber(elem, 'marginTop') -
// this.getCSSNumber(elem, 'marginBottom') -
// this.getCSSNumber(elem, 'borderTop') -
// this.getCSSNumber(elem, 'borderBottom');
// this.positionElement(
// elem,
// box.left + offsetLeft,
// box.top + offsetTop,
// actualWidth,
// actualHeight,
// animate
// );
// });
// }
/**
* @hidden
*/
// private attachElements(
// bigOnes: HTMLVideoElement[],
// normalOnes: HTMLVideoElement[],
// smallOnes: HTMLVideoElement[]
// ) {
// const containerHeight =
// this.getHeight(this.layoutContainer) -
// this.getCSSNumber(this.layoutContainer, 'borderTop') -
// this.getCSSNumber(this.layoutContainer, 'borderBottom');
// const containerWidth =
// this.getWidth(this.layoutContainer) -
// this.getCSSNumber(this.layoutContainer, 'borderLeft') -
// this.getCSSNumber(this.layoutContainer, 'borderRight');
// const offsetLeft = 0;
// const offsetTop = 0;
// if (this.existBigAndNormalOnes(bigOnes, normalOnes, smallOnes)) {
// const smallOnesAux = smallOnes.length > 0 ? smallOnes : normalOnes;
// const bigOnesAux = bigOnes.length > 0 ? bigOnes : normalOnes;
// this.arrangeBigAndSmallOnes(bigOnesAux, smallOnesAux, {
// containerHeight,
// containerWidth,
// });
// } else if (this.onlyExistBigOnes(bigOnes, normalOnes, smallOnes)) {
// // We only have one bigOne just center it
// this.arrange(
// bigOnes,
// containerWidth,
// containerHeight,
// 0,
// 0,
// this.opts.bigFixedRatio,
// this.opts.bigMinRatio,
// this.opts.bigMaxRatio,
// this.opts.animate
// );
// } else if (
// this.existBigAndNormalAndSmallOnes(bigOnes, normalOnes, smallOnes)
// ) {
// this.arrangeBigAndSmallOnes(bigOnes, normalOnes.concat(smallOnes), {
// containerHeight,
// containerWidth,
// });
// } else {
// const normalOnesAux = normalOnes.concat(smallOnes);
// this.arrange(
// normalOnesAux,
// containerWidth - offsetLeft,
// containerHeight - offsetTop,
// offsetLeft,
// offsetTop,
// this.opts.fixedRatio,
// this.opts.minRatio,
// this.opts.maxRatio,
// this.opts.animate
// );
// }
// }
/**
* @hidden
*/
// private arrangeBigAndSmallOnes(
// bigOnesAux: HTMLVideoElement[],
// smallOnesAux: HTMLVideoElement[],
// data: { containerHeight: number; containerWidth: number }
// ) {
// const { containerWidth, containerHeight } = data;
// let offsetLeft = 0;
// let offsetTop = 0;
// const availableRatio = containerHeight / containerWidth;
// let bigOffsetTop = 0;
// let bigOffsetLeft = 0;
// let bigWidth, bigHeight;
// if (availableRatio > this.getVideoRatio(bigOnesAux[0])) {
// // We are tall, going to take up the whole width and arrange small
// // guys at the bottom
// bigWidth = containerWidth;
// bigHeight = Math.floor(containerHeight * this.opts.bigPercentage);
// offsetTop = bigHeight;
// bigOffsetTop = containerHeight - offsetTop;
// } else {
// // We are wide, going to take up the whole height and arrange the small
// // guys on the right
// bigHeight = containerHeight;
// bigWidth = Math.floor(containerWidth * this.opts.bigPercentage);
// offsetLeft = bigWidth;
// bigOffsetLeft = containerWidth - offsetLeft;
// }
// if (this.opts.bigFirst) {
// this.arrange(
// bigOnesAux,
// bigWidth,
// bigHeight,
// 0,
// 0,
// this.opts.bigFixedRatio,
// this.opts.bigMinRatio,
// this.opts.bigMaxRatio,
// this.opts.animate
// );
// this.arrange(
// smallOnesAux,
// containerWidth - offsetLeft,
// containerHeight - offsetTop,
// offsetLeft,
// offsetTop,
// this.opts.fixedRatio,
// this.opts.minRatio,
// this.opts.maxRatio,
// this.opts.animate
// );
// } else {
// this.arrange(
// smallOnesAux,
// containerWidth - offsetLeft,
// containerHeight - offsetTop,
// 0,
// 0,
// this.opts.fixedRatio,
// this.opts.minRatio,
// this.opts.maxRatio,
// this.opts.animate
// );
// this.arrange(
// bigOnesAux,
// bigWidth,
// bigHeight,
// bigOffsetLeft,
// bigOffsetTop,
// this.opts.bigFixedRatio,
// this.opts.bigMinRatio,
// this.opts.bigMaxRatio,
// this.opts.animate
// );
// }
// }
/**
* @hidden
*/
// private existBigAndNormalOnes(
// bigOnes: HTMLVideoElement[],
// normalOnes: HTMLVideoElement[],
// smallOnes: HTMLVideoElement[]
// ) {
// return (
// (bigOnes.length > 0 && normalOnes.length > 0 && smallOnes.length === 0) ||
// (bigOnes.length > 0 && normalOnes.length === 0 && smallOnes.length > 0) ||
// (bigOnes.length === 0 && normalOnes.length > 0 && smallOnes.length > 0)
// );
// }
/**
* @hidden
*/
// private onlyExistBigOnes(
// bigOnes: HTMLVideoElement[],
// normalOnes: HTMLVideoElement[],
// smallOnes: HTMLVideoElement[]
// ): boolean {
// return (
// bigOnes.length > 0 && normalOnes.length === 0 && smallOnes.length === 0
// );
// }
/**
* @hidden
*/
// private existBigAndNormalAndSmallOnes(
// bigOnes: HTMLVideoElement[],
// normalOnes: HTMLVideoElement[],
// smallOnes: HTMLVideoElement[]
// ): boolean {
// return bigOnes.length > 0 && normalOnes.length > 0 && smallOnes.length > 0;
// }
/** /**
* @hidden * @hidden
@ -678,83 +440,93 @@ export class OpenViduLayout {
count: number, count: number,
maxWidth: number, maxWidth: number,
maxHeight: number maxHeight: number
) { ): BestDimensions {
let maxArea: number; // Cache key for memoization
let targetCols: number; const cacheKey = `${minRatio}_${maxRatio}_${width}_${height}_${count}_${maxWidth}_${maxHeight}`;
let targetRows: number; const cached = this.dimensionsCache.get(cacheKey);
let targetHeight: number; if (cached) {
let targetWidth: number; return cached;
let tWidth: number; }
let tHeight: number; let bestArea = 0;
let tRatio: number; let bestCols = 1;
let bestRows = 1;
let bestHeight = 0;
let bestWidth = 0;
// Iterate through every possible combination of rows and columns // Optimized: limit search space based on aspect ratio constraints
// and see which one has the least amount of whitespace const maxCols = Math.min(count, Math.ceil(Math.sqrt(count * width / height)));
for (let i = 1; i <= count; i++) {
const cols = i; for (let cols = 1; cols <= maxCols; cols++) {
const rows = Math.ceil(count / cols); const rows = Math.ceil(count / cols);
// Try taking up the whole height and width // Early exit if too many rows for the height
tHeight = Math.floor(height / rows); if (rows > height / 10) continue;
tWidth = Math.floor(width / cols);
tRatio = tHeight / tWidth; let elementWidth = Math.floor(width / cols);
if (tRatio > maxRatio) { let elementHeight = Math.floor(height / rows);
// We went over decrease the height
tRatio = maxRatio; const ratio = elementHeight / elementWidth;
tHeight = tWidth * tRatio;
} else if (tRatio < minRatio) { // Apply ratio constraints
// We went under decrease the width if (ratio > maxRatio) {
tRatio = minRatio; elementHeight = elementWidth * maxRatio;
tWidth = tHeight / tRatio; } else if (ratio < minRatio) {
elementWidth = elementHeight / minRatio;
} }
tWidth = Math.min(maxWidth, tWidth); // Apply size constraints
tHeight = Math.min(maxHeight, tHeight); elementWidth = Math.min(maxWidth, elementWidth);
const area = tWidth * tHeight * count; elementHeight = Math.min(maxHeight, elementHeight);
// If this width and height takes up the most space then we're going with that const area = elementWidth * elementHeight * count;
if (maxArea === undefined || area >= maxArea) {
if (!(area === maxArea && count % (cols * rows) > count % (targetRows * targetCols))) { // Favor layouts with better utilization and fewer empty cells
// Favour even numbers of participants in each row, eg. 2 on each row const efficiency = count / (cols * rows);
// instead of 3 in one row and then 1 on the next const adjustedArea = area * efficiency;
maxArea = area;
targetHeight = tHeight; if (adjustedArea > bestArea) {
targetWidth = tWidth; bestArea = area;
targetCols = cols; bestHeight = elementHeight;
targetRows = rows; bestWidth = elementWidth;
bestCols = cols;
bestRows = rows;
} }
} }
}
return { const result: BestDimensions = {
maxArea, maxArea: bestArea,
targetCols, targetCols: bestCols,
targetRows, targetRows: bestRows,
targetHeight, targetHeight: bestHeight,
targetWidth, targetWidth: bestWidth,
ratio: targetHeight / targetWidth ratio: bestHeight / bestWidth || 0
}; };
// Cache the result for future use
this.dimensionsCache.set(cacheKey, result);
return result;
} }
private getVideoRatio(element: { height: number; width: number; big?: boolean }) { private getVideoRatio(element: ElementDimensions): number {
return element.height / element.width; return element.height / element.width;
} }
private getLayout(opts: any, elements: { height: number; width: number; big?: boolean }[]) { private getLayout(opts: ExtendedLayoutOptions, elements: ElementDimensions[]) {
const { const {
maxRatio = 3 / 2, maxRatio = LAYOUT_CONSTANTS.DEFAULT_MAX_RATIO,
minRatio = 9 / 16, minRatio = LAYOUT_CONSTANTS.DEFAULT_MIN_RATIO,
fixedRatio = false, fixedRatio = false,
bigPercentage = 0.8, bigPercentage = LAYOUT_CONSTANTS.DEFAULT_BIG_PERCENTAGE,
minBigPercentage = 0, minBigPercentage = 0,
bigFixedRatio = false, bigFixedRatio = false,
bigMaxRatio = 3 / 2, bigMaxRatio = LAYOUT_CONSTANTS.DEFAULT_MAX_RATIO,
bigMinRatio = 9 / 16, bigMinRatio = LAYOUT_CONSTANTS.DEFAULT_MIN_RATIO,
bigFirst = true, bigFirst = true,
containerWidth = 640, containerWidth = LAYOUT_CONSTANTS.DEFAULT_VIDEO_WIDTH,
containerHeight = 480, containerHeight = LAYOUT_CONSTANTS.DEFAULT_VIDEO_HEIGHT,
alignItems = 'center', alignItems = LayoutAlignment.CENTER,
bigAlignItems = 'center', bigAlignItems = LayoutAlignment.CENTER,
smallAlignItems = 'center', smallAlignItems = LayoutAlignment.CENTER,
maxWidth = Infinity, maxWidth = Infinity,
maxHeight = Infinity, maxHeight = Infinity,
smallMaxWidth = Infinity, smallMaxWidth = Infinity,
@ -769,10 +541,10 @@ export class OpenViduLayout {
let offsetTop = 0; let offsetTop = 0;
let bigOffsetTop = 0; let bigOffsetTop = 0;
let bigOffsetLeft = 0; let bigOffsetLeft = 0;
const bigIndices = []; const bigIndices: number[] = [];
let bigBoxes = []; let bigBoxes: LayoutBox[] = [];
let smallBoxes = []; let smallBoxes: LayoutBox[] = [];
let areas: { big: any; small: any } = { big: null, small: null }; let areas: { big: LayoutArea | null; small: LayoutArea | null } = { big: null, small: null };
// Move to Get Layout // Move to Get Layout
const smallOnes = elements.filter((element) => !element.big); const smallOnes = elements.filter((element) => !element.big);
@ -993,7 +765,7 @@ export class OpenViduLayout {
); );
} }
const boxes = []; const boxes: LayoutBox[] = [];
let bigBoxesIdx = 0; let bigBoxesIdx = 0;
let smallBoxesIdx = 0; let smallBoxesIdx = 0;
// Rebuild the array in the right order based on where the bigIndices should be // Rebuild the array in the right order based on where the bigIndices should be
@ -1009,16 +781,16 @@ export class OpenViduLayout {
return { boxes, areas }; return { boxes, areas };
} }
private getLayoutAux(opts: any, elements: { height: number; width: number; big?: boolean }[]) { private getLayoutAux(opts: Partial<OpenViduLayoutOptions & { containerWidth: number; containerHeight: number; offsetLeft: number; offsetTop: number }>, elements: ElementDimensions[]): LayoutBox[] {
const { const {
maxRatio = 3 / 2, maxRatio = LAYOUT_CONSTANTS.DEFAULT_MAX_RATIO,
minRatio = 9 / 16, minRatio = LAYOUT_CONSTANTS.DEFAULT_MIN_RATIO,
fixedRatio = false, fixedRatio = false,
containerWidth = 640, containerWidth = LAYOUT_CONSTANTS.DEFAULT_VIDEO_WIDTH,
containerHeight = 480, containerHeight = LAYOUT_CONSTANTS.DEFAULT_VIDEO_HEIGHT,
offsetLeft = 0, offsetLeft = 0,
offsetTop = 0, offsetTop = 0,
alignItems = 'center', alignItems = LayoutAlignment.CENTER,
maxWidth = Infinity, maxWidth = Infinity,
maxHeight = Infinity, maxHeight = Infinity,
scaleLastRow = true scaleLastRow = true
@ -1032,16 +804,16 @@ export class OpenViduLayout {
dimensions = this.getBestDimensions(minRatio, maxRatio, containerWidth, containerHeight, count, maxWidth, maxHeight); dimensions = this.getBestDimensions(minRatio, maxRatio, containerWidth, containerHeight, count, maxWidth, maxHeight);
} else { } else {
// Use the ratio of the first video element we find to approximate // Use the ratio of the first video element we find to approximate
const ratio = ratios.length > 0 ? ratios[0] : null; const ratio = ratios.length > 0 ? ratios[0] : LAYOUT_CONSTANTS.DEFAULT_MIN_RATIO;
dimensions = this.getBestDimensions(ratio, ratio, containerWidth, containerHeight, count, maxWidth, maxHeight); dimensions = this.getBestDimensions(ratio, ratio, containerWidth, containerHeight, count, maxWidth, maxHeight);
} }
// Loop through each stream in the container and place it inside // Loop through each stream in the container and place it inside
let x = 0; let x = 0;
let y = 0; let y = 0;
const rows = []; const rows: LayoutRow[] = [];
let row; let row: LayoutRow | undefined;
const boxes = []; const boxes: LayoutBox[] = [];
// Iterate through the children and create an array with a new item for each row // Iterate through the children and create an array with a new item for each row
// and calculate the width of each row so that we know if we go over the size and need // and calculate the width of each row so that we know if we go over the size and need
@ -1057,6 +829,7 @@ export class OpenViduLayout {
rows.push(row); rows.push(row);
} }
const ratio = ratios[i]; const ratio = ratios[i];
if (row) {
row.ratios.push(ratio); row.ratios.push(ratio);
let targetWidth = dimensions.targetWidth; let targetWidth = dimensions.targetWidth;
const targetHeight = dimensions.targetHeight; const targetHeight = dimensions.targetHeight;
@ -1067,6 +840,7 @@ export class OpenViduLayout {
row.width += targetWidth; row.width += targetWidth;
row.height = targetHeight; row.height = targetHeight;
} }
}
// Calculate total row height adjusting if we go too wide // Calculate total row height adjusting if we go too wide
let totalRowHeight = 0; let totalRowHeight = 0;
let remainingShortRows = 0; let remainingShortRows = 0;
@ -1132,7 +906,7 @@ export class OpenViduLayout {
break; break;
} }
x = rowMarginLeft; x = rowMarginLeft;
let targetHeight; let targetHeight = row.height;
for (let j = 0; j < row.ratios.length; j++) { for (let j = 0; j < row.ratios.length; j++) {
const ratio = row.ratios[j]; const ratio = row.ratios[j];