/* * 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>; private displayPinned: boolean; private containers: Map; 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(); }); } }