tweb/src/components/groupCall/participantVideos.ts
Eduard Kuzmenko 25d222ded4 eslint init
2022-08-04 08:49:54 +02:00

177 lines
5.8 KiB
TypeScript

/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {attachClickEvent} from '../../helpers/dom/clickEvent';
import ControlsHover from '../../helpers/dom/controlsHover';
import findUpClassName from '../../helpers/dom/findUpClassName';
import ListenerSetter from '../../helpers/listenerSetter';
import safeAssign from '../../helpers/object/safeAssign';
import {GroupCallParticipant} from '../../layer';
import {GroupCallOutputSource} from '../../lib/appManagers/appGroupCallsManager';
import {AppManagers} from '../../lib/appManagers/managers';
import getPeerId from '../../lib/appManagers/utils/peers/getPeerId';
import GroupCallInstance from '../../lib/calls/groupCallInstance';
import rootScope from '../../lib/rootScope';
import GroupCallParticipantVideoElement, {GroupCallParticipantVideoType} from './participantVideo';
export default class GroupCallParticipantsVideoElement extends ControlsHover {
private container: HTMLDivElement;
private instance: GroupCallInstance;
private participantsElements: Map<PeerId, Map<GroupCallParticipantVideoType, GroupCallParticipantVideoElement>>;
private displayPinned: boolean;
private containers: Map<HTMLElement, GroupCallParticipantVideoElement>;
private onLengthChange: (length: number) => void;
private managers: AppManagers;
constructor(options: {
appendTo: HTMLElement,
instance: GroupCallInstance,
listenerSetter: ListenerSetter,
displayPinned: boolean,
onLengthChange?: GroupCallParticipantsVideoElement['onLengthChange'],
managers: AppManagers
}) {
super();
safeAssign(this, options);
const className = 'group-call-participants-video';
const container = this.container = document.createElement('div');
this.container.classList.add(className + '-container');
options.appendTo.append(container);
this.participantsElements = new Map();
this.containers = new Map();
const {listenerSetter} = this;
listenerSetter.add(rootScope)('group_call_participant', ({groupCallId, participant}) => {
if(this.instance.id === groupCallId) {
this.updateParticipant(participant);
}
});
listenerSetter.add(this.instance)('pinned', (source) => {
this.participantsElements.forEach((map) => {
map.forEach((element) => {
this.setElementDisplay(element, source);
});
});
});
attachClickEvent(this.container, (e) => {
const container = findUpClassName(e.target, 'group-call-participant-video-container');
if(!container) {
return;
}
const element = this.containers.get(container);
if(this.instance.pinnedSource === element.source) {
this.instance.unpinAll();
return;
}
this.instance.pinSource(element.source);
}, {listenerSetter});
this.setInstance(this.instance);
this.setup({
element: container,
listenerSetter: listenerSetter,
showOnLeaveToClassName: 'group-call-buttons'
});
}
private shouldDisplayElement(element: GroupCallParticipantVideoElement, pinnedSource: GroupCallOutputSource) {
return this.displayPinned ? !pinnedSource || element.source === pinnedSource : pinnedSource && element.source !== pinnedSource;
}
private setElementDisplay(element: GroupCallParticipantVideoElement, pinnedSource: GroupCallOutputSource) {
const shouldDisplay = this.shouldDisplayElement(element, pinnedSource);
element.container.classList.toggle('video-hidden', !shouldDisplay);
const isPinned = element.source === pinnedSource;
element.setPinned(isPinned);
}
private updateParticipant(participant: GroupCallParticipant) {
const peerId = getPeerId(participant.peer);
const types: GroupCallParticipantVideoType[] = ['video', 'presentation'];
const hasAnyVideo = types.some((type) => !!participant[type]);
let participantElements = this.participantsElements.get(peerId);
if(!hasAnyVideo && !participantElements) {
return;
}
if(!participantElements) {
this.participantsElements.set(peerId, participantElements = new Map());
}
types.forEach((type) => {
let element = participantElements.get(type);
const participantVideo = participant[type];
if(!!participantVideo === !!element) {
if(element) {
element.updateParticipant(participant);
}
return;
}
if(participantVideo) {
const result = this.instance.getVideoElementFromParticipantByType(participant, type);
if(!result) {
return;
}
const {video, source} = result;
element = new GroupCallParticipantVideoElement(this.managers, this.instance, source);
this.containers.set(element.container, element);
this.setElementDisplay(element, this.instance.pinnedSource);
participantElements.set(type, element);
element.setParticipant(participant, type, video);
this.container.prepend(element.container);
} else {
participantElements.delete(type);
element.container.remove();
if(!participantElements.size) {
this.participantsElements.delete(peerId);
this.containers.delete(element.container);
element.destroy();
}
}
this._onLengthChange();
});
}
private _onLengthChange() {
const length = this.container.childElementCount;
this.container.dataset.length = '' + length;
this.container.dataset.layout = length <= 2 ? '1' : (length === 3 ? '3' : '4');
this.onLengthChange && this.onLengthChange(length);
}
public async setInstance(instance: GroupCallInstance) {
(await instance.participants).forEach((participant) => {
this.updateParticipant(participant);
});
}
public destroy() {
this.containers.forEach((element) => {
element.destroy();
});
}
}