mirror of https://github.com/OpenVidu/openvidu.git
ov-components: enhance layout model with extended options and improved dimension calculations
parent
1cef3c17a4
commit
bab8d3eb2a
|
@ -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,
|
||||||
this.getCSSNumber(this.layoutContainer, 'border-top') -
|
containerHeight: this.getHeight(this.layoutContainer) -
|
||||||
this.getCSSNumber(this.layoutContainer, 'border-bottom');
|
this.getCSSNumber(this.layoutContainer, 'border-top') -
|
||||||
opts.containerWidth =
|
this.getCSSNumber(this.layoutContainer, 'border-bottom'),
|
||||||
this.getWidth(this.layoutContainer) -
|
containerWidth: 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 {
|
|
||||||
maxArea,
|
const result: BestDimensions = {
|
||||||
targetCols,
|
maxArea: bestArea,
|
||||||
targetRows,
|
targetCols: bestCols,
|
||||||
targetHeight,
|
targetRows: bestRows,
|
||||||
targetWidth,
|
targetHeight: bestHeight,
|
||||||
ratio: targetHeight / targetWidth
|
targetWidth: bestWidth,
|
||||||
|
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,15 +829,17 @@ export class OpenViduLayout {
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
}
|
}
|
||||||
const ratio = ratios[i];
|
const ratio = ratios[i];
|
||||||
row.ratios.push(ratio);
|
if (row) {
|
||||||
let targetWidth = dimensions.targetWidth;
|
row.ratios.push(ratio);
|
||||||
const targetHeight = dimensions.targetHeight;
|
let targetWidth = dimensions.targetWidth;
|
||||||
// If we're using a fixedRatio then we need to set the correct ratio for this element
|
const targetHeight = dimensions.targetHeight;
|
||||||
if (fixedRatio) {
|
// If we're using a fixedRatio then we need to set the correct ratio for this element
|
||||||
targetWidth = targetHeight / ratio;
|
if (fixedRatio) {
|
||||||
|
targetWidth = targetHeight / ratio;
|
||||||
|
}
|
||||||
|
row.width += targetWidth;
|
||||||
|
row.height = targetHeight;
|
||||||
}
|
}
|
||||||
row.width += targetWidth;
|
|
||||||
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;
|
||||||
|
@ -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];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue