From 17713f26b2cc380c9bafe7548979079a4ece8d27 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Thu, 24 Mar 2022 19:31:55 +0200 Subject: [PATCH] Calls fixes --- src/components/call/index.ts | 92 ++++++++++++++++++------ src/components/groupCall/participants.ts | 4 +- src/components/popups/index.ts | 4 +- src/components/stackedAvatars.ts | 7 +- src/components/topbarCall.ts | 6 +- src/scss/partials/popups/_call.scss | 5 ++ 6 files changed, 88 insertions(+), 30 deletions(-) diff --git a/src/components/call/index.ts b/src/components/call/index.ts index 5aa94b7b..2c274393 100644 --- a/src/components/call/index.ts +++ b/src/components/call/index.ts @@ -10,6 +10,7 @@ import { attachClickEvent } from "../../helpers/dom/clickEvent"; import ControlsHover from "../../helpers/dom/controlsHover"; import findUpClassName from "../../helpers/dom/findUpClassName"; import { addFullScreenListener, cancelFullScreen, isFullScreen, requestFullScreen } from "../../helpers/dom/fullScreen"; +import { onMediaLoad } from "../../helpers/files"; import { MediaSize } from "../../helpers/mediaSizes"; import MovablePanel from "../../helpers/movablePanel"; import safeAssign from "../../helpers/object/safeAssign"; @@ -36,11 +37,16 @@ import callVideoCanvasBlur from "./videoCanvasBlur"; const className = 'call'; -let previousState: MovableState = { - width: 400, - height: 580 +const MIN_WIDTH = 400; +const MIN_HEIGHT = 580; + +const INIT_STATE: MovableState = { + width: MIN_WIDTH, + height: MIN_HEIGHT }; +let previousState: MovableState = {...INIT_STATE}; + export default class PopupCall extends PopupElement { private instance: CallInstance; private appCallsManager: AppCallsManager; @@ -169,8 +175,8 @@ export default class PopupCall extends PopupElement { this.movablePanel = new MovablePanel({ listenerSetter, movableOptions: { - minWidth: 400, - minHeight: 580, + minWidth: MIN_WIDTH, + minHeight: MIN_HEIGHT, element: this.element, verifyTouchTarget: (e) => { const target = e.target; @@ -184,7 +190,11 @@ export default class PopupCall extends PopupElement { } }, // onResize: () => this.toggleBigLayout(), - previousState + previousState: !this.instance.wasTryingToJoin && !this.instance.isOutgoing ? {...INIT_STATE} : previousState + }); + + this.listenerSetter.add(this.movablePanel.movable)('resize', () => { + this.resizeVideoContainers(); }); const controlsHover = this.controlsHover = new ControlsHover(); @@ -308,6 +318,8 @@ export default class PopupCall extends PopupElement { animationIntersector.checkAnimations(isFull); rootScope.setThemeColor(isFull ? '#000000' : undefined); + + this.resizeVideoContainers(); } }; @@ -331,6 +343,8 @@ export default class PopupCall extends PopupElement { big.style.cssText = container.style.cssText; container.classList.remove('small'); container.style.cssText = ''; + + this.resizeVideoContainers(); }); const canvas = callVideoCanvasBlur(video); @@ -389,10 +403,24 @@ export default class PopupCall extends PopupElement { SetTransition(this.partyMutedState, 'is-visible', !!outputState?.muted, 300); const containers = this.videoContainers; + const oldContainers = {...containers}; ['input' as const, 'output' as const].forEach(type => { const mediaState = instance.getMediaState(type); const video = instance.getVideoElement(type) as HTMLVideoElement; - const isActive = !!video && !!(mediaState && (mediaState.videoState === 'active' || mediaState.screencastState === 'active')); + + const hasFrame = !!(video && video.videoWidth && video.videoHeight); + if(video && !hasFrame && !video.dataset.hasPromise) { + video.dataset.hasPromise = '1'; + // container.classList.add('hide'); + onMediaLoad(video).then(() => { + delete video.dataset.hasPromise; + this.updateInstance(); + // this.resizeVideoContainers(); + // container.classList.remove('hide'); + }); + } + + const isActive = !!video && hasFrame && !!(mediaState && (mediaState.videoState === 'active' || mediaState.screencastState === 'active')); let videoContainer = containers[type]; if(isActive && video && !videoContainer) { @@ -406,25 +434,20 @@ export default class PopupCall extends PopupElement { } }); - const inputVideoContainer = containers.input; - if(inputVideoContainer) { - const isSmall = !!containers.output; - inputVideoContainer.classList.toggle('small', isSmall); + { + const input = containers.input; + const output = containers.output; + if(Object.keys(oldContainers).length !== Object.keys(containers).length && input) { + input.classList.toggle('small', !!output); + } - const video = instance.getVideoElement('input') as HTMLVideoElement; - if(isSmall) { - const mediaSize = new MediaSize(120, 80); - const aspected = mediaSize.aspectFitted(new MediaSize(video.videoWidth, video.videoHeight)); - // inputVideoContainer.style.width = aspected.width + 'px'; - // inputVideoContainer.style.height = aspected.height + 'px'; - // const ratio = 120 / 80; - inputVideoContainer.style.width = '120px'; - inputVideoContainer.style.height = '80px'; - } else { - inputVideoContainer.style.cssText = ''; + if(output && !input) { + output.classList.remove('small'); } } + this.resizeVideoContainers(); + this.container.classList.toggle('no-video', !Object.keys(containers).length); if(!this.emojisSubtitle.textContent && connectionState < CALL_STATE.EXCHANGING_KEYS) { @@ -436,6 +459,31 @@ export default class PopupCall extends PopupElement { this.setDescription(); } + private resizeVideoContainers() { + Object.values(this.videoContainers).forEach(container => { + const isSmall = container.classList.contains('small'); + if(isSmall) { + const video = container.querySelector('video'); + const popupWidth = this.movablePanel.state; + const MAX_WIDTH_PX = 240; + const MAX_HEIGHT_PX = 240; + + const isVertical = video.videoHeight > video.videoWidth; + const MAX_SIZE = isVertical ? MAX_HEIGHT_PX : MAX_WIDTH_PX; + + const biggestSideSize = 1 / 3 * (isFullScreen() ? 0xFFFF : (isVertical ? popupWidth.height : popupWidth.width)); + const widthRatio = isVertical ? video.videoWidth / video.videoHeight : 1; + const heightRatio = isVertical ? 1 : video.videoHeight / video.videoWidth; + container.style.width = biggestSideSize * widthRatio + 'px'; + container.style.height = biggestSideSize * heightRatio + 'px'; + container.style.maxWidth = MAX_SIZE * widthRatio + 'px'; + container.style.maxHeight = MAX_SIZE * heightRatio + 'px'; + } else { + container.style.cssText = ''; + } + }); + } + private setDescription() { this.description.update(this.instance); } diff --git a/src/components/groupCall/participants.ts b/src/components/groupCall/participants.ts index f092ef77..7b94d786 100644 --- a/src/components/groupCall/participants.ts +++ b/src/components/groupCall/participants.ts @@ -141,7 +141,7 @@ export class GroupCallParticipantContextMenu { let appendTo: HTMLElement = document.body; addFullScreenListener(document.body, () => { const isFull = isFullScreen(); - appendTo = isFull ? (PopupElement.getPopup(PopupGroupCall) as PopupGroupCall).getContainer(): document.body; + appendTo = isFull ? (PopupElement.getPopups(PopupGroupCall) as PopupGroupCall[])[0].getContainer(): document.body; if(!isFull) { closeBtnMenu(); @@ -150,7 +150,7 @@ export class GroupCallParticipantContextMenu { } private onOpenProfileClick = () => { - const popup = PopupElement.getPopup(PopupGroupCall); + const popup = PopupElement.getPopups(PopupGroupCall)[0]; if(popup) { popup.hide(); } diff --git a/src/components/popups/index.ts b/src/components/popups/index.ts index 4e2d02b0..2e7f22a4 100644 --- a/src/components/popups/index.ts +++ b/src/components/popups/index.ts @@ -255,8 +255,8 @@ export default class PopupElement extends }); } - public static getPopup(popupConstructor: PopupElementConstructable) { - return this.POPUPS.find(element => element instanceof popupConstructor); + public static getPopups(popupConstructor: PopupElementConstructable) { + return this.POPUPS.filter(element => element instanceof popupConstructor); } } diff --git a/src/components/stackedAvatars.ts b/src/components/stackedAvatars.ts index 1ba816bd..8c0fbf27 100644 --- a/src/components/stackedAvatars.ts +++ b/src/components/stackedAvatars.ts @@ -31,7 +31,12 @@ export default class StackedAvatars { public render(peerIds: PeerId[], loadPromises?: Promise[]) { const children = this.container.children; - peerIds.slice().reverse().forEach((peerId, idx) => { + peerIds = peerIds.slice().reverse(); + if(peerIds.length > 3) { + peerIds = peerIds.slice(-3); + } + + peerIds.forEach((peerId, idx) => { let avatarContainer = children[idx] as HTMLElement; if(!avatarContainer) { avatarContainer = document.createElement('div'); diff --git a/src/components/topbarCall.ts b/src/components/topbarCall.ts index 9a4cd7ff..66f4dfab 100644 --- a/src/components/topbarCall.ts +++ b/src/components/topbarCall.ts @@ -258,7 +258,7 @@ export default class TopbarCall { attachClickEvent(container, () => { if(this.instance instanceof GroupCallInstance) { - if(PopupElement.getPopup(PopupGroupCall)) { + if(PopupElement.getPopups(PopupGroupCall).length) { return; } @@ -268,8 +268,8 @@ export default class TopbarCall { appChatsManager: this.appChatsManager }).show(); } else if(this.instance instanceof CallInstance) { - const hasPopup = PopupElement.getPopup(PopupCall) as PopupCall; - if(hasPopup && hasPopup.getCallInstance() === this.instance) { + const popups = PopupElement.getPopups(PopupCall) as PopupCall[]; + if(popups.find(popup => popup.getCallInstance() === this.instance)) { return; } diff --git a/src/scss/partials/popups/_call.scss b/src/scss/partials/popups/_call.scss index 3b267b38..11a495d9 100644 --- a/src/scss/partials/popups/_call.scss +++ b/src/scss/partials/popups/_call.scss @@ -244,6 +244,10 @@ // html.emoji-supported & { .call-emojis { transform: scale(1.125); + + .emoji { + margin: 0 .125rem; + } } // } @@ -262,6 +266,7 @@ width: 100%; align-items: center; padding: 0 1rem; + pointer-events: none; } &-party-state {