Calls improvements
This commit is contained in:
parent
5f78b9e4cb
commit
c4827d50f6
@ -119,10 +119,11 @@ const seen: Set<PeerId> = new Set();
|
||||
|
||||
export default class AvatarElement extends HTMLElement {
|
||||
private peerId: PeerId;
|
||||
private isDialog = false;
|
||||
private isDialog: boolean;
|
||||
private peerTitle: string;
|
||||
public loadPromises: Promise<any>[];
|
||||
public lazyLoadQueue: LazyLoadQueueIntersector;
|
||||
public isBig: boolean;
|
||||
private addedToQueue = false;
|
||||
|
||||
connectedCallback() {
|
||||
@ -196,7 +197,7 @@ export default class AvatarElement extends HTMLElement {
|
||||
}
|
||||
|
||||
private r(onlyThumb = false) {
|
||||
const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb);
|
||||
const res = appAvatarsManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle, onlyThumb, this.isBig);
|
||||
const promise = res ? res.loadPromise : Promise.resolve();
|
||||
if(this.loadPromises) {
|
||||
if(res && res.cached) {
|
||||
|
@ -75,6 +75,7 @@ export default class CallDescriptionElement {
|
||||
}
|
||||
}
|
||||
|
||||
this.container.classList.toggle('has-duration', connectionState === CALL_STATE.CONNECTED);
|
||||
replaceContent(this.container, element);
|
||||
|
||||
if(!this.container.parentElement) {
|
||||
|
@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import IS_SCREEN_SHARING_SUPPORTED from "../../environment/screenSharingSupport";
|
||||
import { IS_MOBILE } from "../../environment/userAgent";
|
||||
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
||||
import ControlsHover from "../../helpers/dom/controlsHover";
|
||||
import findUpClassName from "../../helpers/dom/findUpClassName";
|
||||
@ -14,6 +15,7 @@ import MovablePanel from "../../helpers/movablePanel";
|
||||
import safeAssign from "../../helpers/object/safeAssign";
|
||||
import toggleClassName from "../../helpers/toggleClassName";
|
||||
import type { AppAvatarsManager } from "../../lib/appManagers/appAvatarsManager";
|
||||
import type { AppCallsManager } from "../../lib/appManagers/appCallsManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import CallInstance from "../../lib/calls/callInstance";
|
||||
import CALL_STATE from "../../lib/calls/callState";
|
||||
@ -21,6 +23,7 @@ import I18n, { i18n } from "../../lib/langPack";
|
||||
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import animationIntersector from "../animationIntersector";
|
||||
import AvatarElement from "../avatar";
|
||||
import ButtonIcon from "../buttonIcon";
|
||||
import GroupCallMicrophoneIconMini from "../groupCall/microphoneIconMini";
|
||||
import { MovableState } from "../movableElement";
|
||||
@ -40,6 +43,7 @@ let previousState: MovableState = {
|
||||
|
||||
export default class PopupCall extends PopupElement {
|
||||
private instance: CallInstance;
|
||||
private appCallsManager: AppCallsManager;
|
||||
private appAvatarsManager: AppAvatarsManager;
|
||||
private appPeersManager: AppPeersManager;
|
||||
private peerId: PeerId;
|
||||
@ -76,6 +80,7 @@ export default class PopupCall extends PopupElement {
|
||||
private controlsHover: ControlsHover;
|
||||
|
||||
constructor(options: {
|
||||
appCallsManager: AppCallsManager,
|
||||
appAvatarsManager: AppAvatarsManager,
|
||||
appPeersManager: AppPeersManager,
|
||||
instance: CallInstance
|
||||
@ -96,8 +101,11 @@ export default class PopupCall extends PopupElement {
|
||||
avatarContainer.classList.add(className + '-avatar');
|
||||
|
||||
const peerId = this.peerId = this.instance.interlocutorUserId.toPeerId();
|
||||
const photo = this.appPeersManager.getPeerPhoto(peerId);
|
||||
this.appAvatarsManager.putAvatar(avatarContainer, peerId, photo, 'photo_big');
|
||||
const avatar = new AvatarElement();
|
||||
avatar.isBig = true;
|
||||
avatar.setAttribute('peer', '' + peerId);
|
||||
avatar.classList.add('avatar-full');
|
||||
avatarContainer.append(avatar);
|
||||
|
||||
const title = new PeerTitle({
|
||||
peerId
|
||||
@ -113,22 +121,28 @@ export default class PopupCall extends PopupElement {
|
||||
const emojisSubtitle = this.emojisSubtitle = document.createElement('div');
|
||||
emojisSubtitle.classList.add(className + '-emojis');
|
||||
|
||||
container.append(avatarContainer, title, subtitle, emojisSubtitle);
|
||||
container.append(avatarContainer, title, subtitle);
|
||||
|
||||
this.btnFullScreen = ButtonIcon('fullscreen');
|
||||
this.btnExitFullScreen = ButtonIcon('smallscreen hide');
|
||||
attachClickEvent(this.btnFullScreen, this.onFullScreenClick, {listenerSetter});
|
||||
attachClickEvent(this.btnExitFullScreen, () => cancelFullScreen(), {listenerSetter});
|
||||
addFullScreenListener(this.container, this.onFullScreenChange, listenerSetter);
|
||||
this.header.prepend(this.btnExitFullScreen);
|
||||
this.header.append(this.btnFullScreen);
|
||||
if(!IS_MOBILE) {
|
||||
this.btnFullScreen = ButtonIcon('fullscreen');
|
||||
this.btnExitFullScreen = ButtonIcon('smallscreen hide');
|
||||
attachClickEvent(this.btnFullScreen, this.onFullScreenClick, {listenerSetter});
|
||||
attachClickEvent(this.btnExitFullScreen, () => cancelFullScreen(), {listenerSetter});
|
||||
addFullScreenListener(this.container, this.onFullScreenChange, listenerSetter);
|
||||
this.header.prepend(this.btnExitFullScreen);
|
||||
this.header.append(this.btnFullScreen);
|
||||
|
||||
container.append(emojisSubtitle);
|
||||
} else {
|
||||
this.header.append(emojisSubtitle);
|
||||
}
|
||||
|
||||
this.partyStates = document.createElement('div');
|
||||
this.partyStates.classList.add(className + '-party-states');
|
||||
|
||||
this.partyMutedState = document.createElement('div');
|
||||
this.partyMutedState.classList.add(className + '-party-state');
|
||||
const stateText = i18n('VoipUserMicrophoneIsOff', [new PeerTitle({peerId, onlyFirstName: true}).element]);
|
||||
const stateText = i18n('VoipUserMicrophoneIsOff', [new PeerTitle({peerId, onlyFirstName: true, limitSymbols: 18}).element]);
|
||||
stateText.classList.add(className + '-party-state-text');
|
||||
const mutedIcon = new GroupCallMicrophoneIconMini(false, true);
|
||||
mutedIcon.setState(false, false);
|
||||
@ -193,6 +207,10 @@ export default class PopupCall extends PopupElement {
|
||||
this.updateInstance();
|
||||
}
|
||||
|
||||
public getCallInstance() {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private constructFirstButtons() {
|
||||
const buttons = this.firstButtonsRow = document.createElement('div');
|
||||
buttons.classList.add(className + '-buttons', 'is-first');
|
||||
@ -260,7 +278,7 @@ export default class PopupCall extends PopupElement {
|
||||
|
||||
const btnAccept = this.btnAccept = this.makeButton({
|
||||
text: 'Call.Accept',
|
||||
icon: 'phone',
|
||||
icon: 'phone_filled',
|
||||
callback: () => {
|
||||
this.instance.acceptCall();
|
||||
},
|
||||
|
@ -5,9 +5,9 @@
|
||||
*/
|
||||
|
||||
import { IS_SAFARI } from "../../environment/userAgent";
|
||||
import { indexOfAndSplice } from "../../helpers/array";
|
||||
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
|
||||
import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl";
|
||||
import { deepEqual } from "../../helpers/object";
|
||||
import deepEqual from "../../helpers/object/deepEqual";
|
||||
|
||||
type ChatBackgroundPatternRendererInitOptions = {
|
||||
url: string,
|
||||
|
@ -4,7 +4,7 @@
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import { forEachReverse } from "../../helpers/array";
|
||||
import forEachReverse from "../../helpers/array/forEachReverse";
|
||||
import positionElementByIndex from "../../helpers/dom/positionElementByIndex";
|
||||
import { Message, ReactionCount } from "../../layer";
|
||||
import appReactionsManager from "../../lib/appManagers/appReactionsManager";
|
||||
|
@ -12,13 +12,15 @@ import replaceContent from "../helpers/dom/replaceContent";
|
||||
import appUsersManager from "../lib/appManagers/appUsersManager";
|
||||
import RichTextProcessor from "../lib/richtextprocessor";
|
||||
import { NULL_PEER_ID } from "../lib/mtproto/mtproto_config";
|
||||
import { limitSymbols } from "../helpers/string";
|
||||
|
||||
export type PeerTitleOptions = {
|
||||
peerId?: PeerId,
|
||||
fromName?: string,
|
||||
plainText?: boolean,
|
||||
onlyFirstName?: boolean,
|
||||
dialog?: boolean
|
||||
dialog?: boolean,
|
||||
limitSymbols?: number
|
||||
};
|
||||
|
||||
const weakMap: WeakMap<HTMLElement, PeerTitle> = new WeakMap();
|
||||
@ -44,6 +46,7 @@ export default class PeerTitle {
|
||||
public plainText = false;
|
||||
public onlyFirstName = false;
|
||||
public dialog = false;
|
||||
public limitSymbols: number;
|
||||
|
||||
constructor(options: PeerTitleOptions) {
|
||||
this.element = document.createElement('span');
|
||||
@ -64,8 +67,13 @@ export default class PeerTitle {
|
||||
}
|
||||
}
|
||||
|
||||
if(this.fromName !== undefined) {
|
||||
this.element.innerHTML = RichTextProcessor.wrapEmojiText(this.fromName);
|
||||
let fromName = this.fromName;
|
||||
if(fromName !== undefined) {
|
||||
if(this.limitSymbols !== undefined) {
|
||||
fromName = limitSymbols(fromName, this.limitSymbols, this.limitSymbols);
|
||||
}
|
||||
|
||||
this.element.innerHTML = RichTextProcessor.wrapEmojiText(fromName);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -77,7 +85,7 @@ export default class PeerTitle {
|
||||
if(this.peerId.isUser() && appUsersManager.getUser(this.peerId).pFlags.deleted) {
|
||||
replaceContent(this.element, i18n(this.onlyFirstName ? 'Deleted' : 'HiddenName'));
|
||||
} else {
|
||||
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName);
|
||||
this.element.innerHTML = appPeersManager.getPeerTitle(this.peerId, this.plainText, this.onlyFirstName, this.limitSymbols);
|
||||
}
|
||||
} else {
|
||||
replaceContent(this.element, i18n(this.onlyFirstName ? 'Saved' : 'SavedMessages'));
|
||||
|
@ -15,7 +15,7 @@ import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import { attachClickEvent, simulateClickEvent } from "../../helpers/dom/clickEvent";
|
||||
import isSendShortcutPressed from "../../helpers/dom/isSendShortcutPressed";
|
||||
import { cancelEvent } from "../../helpers/dom/cancelEvent";
|
||||
import EventListenerBase from "../../helpers/eventListenerBase";
|
||||
import EventListenerBase, { EventListenerListeners } from "../../helpers/eventListenerBase";
|
||||
import { addFullScreenListener, getFullScreenElement } from "../../helpers/dom/fullScreen";
|
||||
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
|
||||
|
||||
@ -52,11 +52,13 @@ const onFullScreenChange = () => {
|
||||
|
||||
addFullScreenListener(DEFAULT_APPEND_TO, onFullScreenChange);
|
||||
|
||||
export default class PopupElement extends EventListenerBase<{
|
||||
type PopupListeners = {
|
||||
close: () => void,
|
||||
closeAfterTimeout: () => void
|
||||
}> {
|
||||
private static POPUPS: PopupElement[] = [];
|
||||
};
|
||||
|
||||
export default class PopupElement<T extends EventListenerListeners = {}> extends EventListenerBase<PopupListeners & T> {
|
||||
private static POPUPS: PopupElement<any>[] = [];
|
||||
protected element = document.createElement('div');
|
||||
protected container = document.createElement('div');
|
||||
protected header = document.createElement('div');
|
||||
@ -181,7 +183,7 @@ export default class PopupElement extends EventListenerBase<{
|
||||
public show() {
|
||||
this.navigationItem = {
|
||||
type: 'popup',
|
||||
onPop: this.destroy,
|
||||
onPop: () => this.destroy(),
|
||||
onEscape: this.onEscape
|
||||
};
|
||||
|
||||
@ -214,8 +216,8 @@ export default class PopupElement extends EventListenerBase<{
|
||||
appNavigationController.backByItem(this.navigationItem);
|
||||
};
|
||||
|
||||
private destroy = () => {
|
||||
this.dispatchEvent('close');
|
||||
protected destroy() {
|
||||
this.dispatchEvent<PopupListeners>('close');
|
||||
this.element.classList.add('hiding');
|
||||
this.element.classList.remove('active');
|
||||
this.listenerSetter.removeAll();
|
||||
@ -234,14 +236,14 @@ export default class PopupElement extends EventListenerBase<{
|
||||
|
||||
setTimeout(() => {
|
||||
this.element.remove();
|
||||
this.dispatchEvent('closeAfterTimeout');
|
||||
this.dispatchEvent<PopupListeners>('closeAfterTimeout');
|
||||
this.cleanup();
|
||||
|
||||
if(!this.withoutOverlay) {
|
||||
animationIntersector.checkAnimations(false);
|
||||
}
|
||||
}, 150);
|
||||
};
|
||||
}
|
||||
|
||||
public static reAppend() {
|
||||
this.POPUPS.forEach(popup => {
|
||||
|
@ -9,7 +9,8 @@ import { attachClickEvent } from "../../../helpers/dom/clickEvent";
|
||||
import replaceContent from "../../../helpers/dom/replaceContent";
|
||||
import toggleDisability from "../../../helpers/dom/toggleDisability";
|
||||
import formatBytes from "../../../helpers/formatBytes";
|
||||
import { copy, deepEqual } from "../../../helpers/object";
|
||||
import copy from "../../../helpers/object/copy";
|
||||
import deepEqual from "../../../helpers/object/deepEqual";
|
||||
import appStateManager, { AutoDownloadPeerTypeSettings, STATE_INIT } from "../../../lib/appManagers/appStateManager";
|
||||
import { FormatterArguments, i18n, join, LangPackKey } from "../../../lib/langPack";
|
||||
import rootScope from "../../../lib/rootScope";
|
||||
|
@ -29,6 +29,7 @@ import PopupCall from "./call";
|
||||
import type { AppAvatarsManager } from "../lib/appManagers/appAvatarsManager";
|
||||
import GroupCallMicrophoneIconMini from "./groupCall/microphoneIconMini";
|
||||
import CallInstance from "../lib/calls/callInstance";
|
||||
import type { AppCallsManager } from "../lib/appManagers/appCallsManager";
|
||||
|
||||
function convertCallStateToGroupState(state: CALL_STATE, isMuted: boolean) {
|
||||
switch(state) {
|
||||
@ -64,11 +65,18 @@ export default class TopbarCall {
|
||||
private appPeersManager: AppPeersManager,
|
||||
private appChatsManager: AppChatsManager,
|
||||
private appAvatarsManager: AppAvatarsManager,
|
||||
private appCallsManager: AppCallsManager
|
||||
) {
|
||||
const listenerSetter = this.listenerSetter = new ListenerSetter();
|
||||
|
||||
listenerSetter.add(rootScope)('call_instance', ({instance, hasCurrent}) => {
|
||||
if(!hasCurrent) {
|
||||
listenerSetter.add(rootScope)('call_instance', ({instance}) => {
|
||||
if(!this.instance) {
|
||||
this.updateInstance(instance);
|
||||
}
|
||||
});
|
||||
|
||||
listenerSetter.add(rootScope)('call_accepting', (instance) => {
|
||||
if(this.instance !== instance) {
|
||||
this.updateInstance(instance);
|
||||
}
|
||||
});
|
||||
@ -121,7 +129,8 @@ export default class TopbarCall {
|
||||
this.construct = undefined;
|
||||
}
|
||||
|
||||
if(this.instance !== instance) {
|
||||
const isChangingInstance = this.instance !== instance;
|
||||
if(isChangingInstance) {
|
||||
this.clearCurrentInstance();
|
||||
|
||||
this.instance = instance;
|
||||
@ -135,6 +144,8 @@ export default class TopbarCall {
|
||||
this.currentDescription = this.callDescription;
|
||||
this.instanceListenerSetter.add(instance)('muted', this.onState);
|
||||
}
|
||||
|
||||
this.container.classList.toggle('is-call', !(instance instanceof GroupCallInstance));
|
||||
}
|
||||
|
||||
const isMuted = this.instance.isMuted;
|
||||
@ -145,7 +156,7 @@ export default class TopbarCall {
|
||||
weave.componentDidMount();
|
||||
|
||||
const isClosed = state === GROUP_CALL_STATE.CLOSED;
|
||||
if(!document.body.classList.contains('is-calling') || isClosed) {
|
||||
if((!document.body.classList.contains('is-calling') || isChangingInstance) || isClosed) {
|
||||
if(isClosed) {
|
||||
weave.setAmplitude(0);
|
||||
}
|
||||
@ -257,7 +268,13 @@ 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) {
|
||||
return;
|
||||
}
|
||||
|
||||
new PopupCall({
|
||||
appCallsManager: this.appCallsManager,
|
||||
appAvatarsManager: this.appAvatarsManager,
|
||||
appPeersManager: this.appPeersManager,
|
||||
instance: this.instance
|
||||
|
@ -50,6 +50,8 @@ import type { ArgumentTypes, SuperReturnType } from "../types";
|
||||
// MOUNT_CLASS_TO.e = e;
|
||||
|
||||
export type EventListenerListeners = Record<string, Function>;
|
||||
// export type EventListenerListeners = Record<string, (...args: any[]) => any>;
|
||||
// export type EventListenerListeners = {[name in string]: Function};
|
||||
|
||||
/**
|
||||
* Better not to remove listeners during setting
|
||||
@ -151,7 +153,8 @@ export default class EventListenerBase<Listeners extends EventListenerListeners>
|
||||
}
|
||||
|
||||
// * must be protected, but who cares
|
||||
public dispatchEvent<T extends keyof Listeners>(name: T, ...args: ArgumentTypes<Listeners[T]>) {
|
||||
public dispatchEvent<L extends EventListenerListeners = Listeners, T extends keyof L = keyof L>(name: T, ...args: ArgumentTypes<L[T]>) {
|
||||
// @ts-ignore
|
||||
this._dispatchEvent(name, false, ...args);
|
||||
}
|
||||
|
||||
|
@ -170,6 +170,10 @@ console.timeEnd('get storage1'); */
|
||||
document.documentElement.classList.add('is-firefox');
|
||||
}
|
||||
|
||||
if(userAgent.IS_MOBILE) {
|
||||
document.documentElement.classList.add('is-mobile');
|
||||
}
|
||||
|
||||
if(userAgent.IS_APPLE) {
|
||||
if(userAgent.IS_SAFARI) {
|
||||
document.documentElement.classList.add('is-safari');
|
||||
|
@ -169,7 +169,7 @@ export class AppAvatarsManager {
|
||||
}
|
||||
|
||||
// peerId === peerId || title
|
||||
public putPhoto(div: HTMLElement, peerId: PeerId, isDialog = false, title = '', onlyThumb = false) {
|
||||
public putPhoto(div: HTMLElement, peerId: PeerId, isDialog = false, title = '', onlyThumb = false, isBig?: boolean) {
|
||||
const myId = rootScope.myId;
|
||||
|
||||
//console.log('loadDialogPhoto location:', location, inputPeer);
|
||||
@ -213,7 +213,7 @@ export class AppAvatarsManager {
|
||||
}
|
||||
|
||||
if(avatarAvailable/* && false */) {
|
||||
const size: PeerPhotoSize = 'photo_small';
|
||||
const size: PeerPhotoSize = isBig ? 'photo_big' : 'photo_small';
|
||||
return this.putAvatar(div, peerId, photo, size, undefined, onlyThumb);
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,8 @@
|
||||
|
||||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||
import IS_CALL_SUPPORTED from "../../environment/callSupport";
|
||||
import indexOfAndSplice from "../../helpers/array/indexOfAndSplice";
|
||||
import insertInDescendSortedArray from "../../helpers/array/insertInDescendSortedArray";
|
||||
import AudioAssetPlayer from "../../helpers/audioAssetPlayer";
|
||||
import bytesCmp from "../../helpers/bytes/bytesCmp";
|
||||
import safeReplaceObject from "../../helpers/object/safeReplaceObject";
|
||||
@ -39,6 +41,7 @@ export class AppCallsManager {
|
||||
private log: ReturnType<typeof logger>;
|
||||
private calls: Map<CallId, MyPhoneCall>;
|
||||
private instances: Map<CallId, CallInstance>;
|
||||
private sortedInstances: Array<CallInstance>;
|
||||
private tempId: number;
|
||||
private audioAsset: AudioAssetPlayer<CallAudioAssetName>;
|
||||
|
||||
@ -48,6 +51,7 @@ export class AppCallsManager {
|
||||
this.tempId = 0;
|
||||
this.calls = new Map();
|
||||
this.instances = new Map();
|
||||
this.sortedInstances = [];
|
||||
|
||||
if(!IS_CALL_SUPPORTED) {
|
||||
return;
|
||||
@ -139,15 +143,7 @@ export class AppCallsManager {
|
||||
}
|
||||
|
||||
public get currentCall() {
|
||||
let lastInstance: CallInstance;
|
||||
for(const [callId, instance] of this.instances) {
|
||||
lastInstance = instance;
|
||||
if(instance.connectionState !== CALL_STATE.PENDING) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return lastInstance;
|
||||
return this.sortedInstances[0];
|
||||
}
|
||||
|
||||
public getCallByUserId(userId: UserId) {
|
||||
@ -207,15 +203,17 @@ export class AppCallsManager {
|
||||
...options,
|
||||
});
|
||||
|
||||
let wasTryingToJoin = false;
|
||||
call.addEventListener('state', (state) => {
|
||||
const currentCall = this.currentCall;
|
||||
if(state === CALL_STATE.CLOSED) {
|
||||
this.instances.delete(call.id);
|
||||
indexOfAndSplice(this.sortedInstances, call);
|
||||
} else {
|
||||
insertInDescendSortedArray(this.sortedInstances, call, 'sortIndex');
|
||||
}
|
||||
|
||||
if(state === CALL_STATE.EXCHANGING_KEYS) {
|
||||
wasTryingToJoin = true;
|
||||
call.wasTryingToJoin = true;
|
||||
}
|
||||
|
||||
const hasConnected = call.connectedAt !== undefined;
|
||||
@ -227,9 +225,9 @@ export class AppCallsManager {
|
||||
|
||||
if(currentCall === call || !currentCall) {
|
||||
if(state === CALL_STATE.CLOSED) {
|
||||
if(!call.isOutgoing && !wasTryingToJoin) { // incoming call has been accepted on other device or ended
|
||||
if(!call.isOutgoing && !call.wasTryingToJoin) { // incoming call has been accepted on other device or ended
|
||||
this.audioAsset.stopSound();
|
||||
} else if(wasTryingToJoin && !hasConnected) { // something has happened during the key exchanging
|
||||
} else if(call.wasTryingToJoin && !hasConnected) { // something has happened during the key exchanging
|
||||
this.audioAsset.playSound('voip_failed.mp3');
|
||||
} else {
|
||||
this.audioAsset.playSound(call.discardReason === 'phoneCallDiscardReasonBusy' ? 'call_busy.mp3' : 'call_end.mp3');
|
||||
|
@ -87,6 +87,8 @@ import appReactionsManager from './appReactionsManager';
|
||||
import PopupCall from '../../components/call';
|
||||
import copy from '../../helpers/object/copy';
|
||||
import getObjectKeysAndSort from '../../helpers/object/getObjectKeysAndSort';
|
||||
import type GroupCallInstance from '../calls/groupCallInstance';
|
||||
import type CallInstance from '../calls/callInstance';
|
||||
|
||||
//console.log('appImManager included33!');
|
||||
|
||||
@ -341,20 +343,39 @@ export class AppImManager {
|
||||
});
|
||||
|
||||
if(IS_CALL_SUPPORTED || IS_GROUP_CALL_SUPPORTED) {
|
||||
this.topbarCall = new TopbarCall(appGroupCallsManager, appPeersManager, appChatsManager, appAvatarsManager);
|
||||
this.topbarCall = new TopbarCall(appGroupCallsManager, appPeersManager, appChatsManager, appAvatarsManager, appCallsManager);
|
||||
}
|
||||
|
||||
if(IS_CALL_SUPPORTED) {
|
||||
rootScope.addEventListener('call_instance', ({instance, hasCurrent}) => {
|
||||
if(hasCurrent) {
|
||||
return;
|
||||
}
|
||||
rootScope.addEventListener('call_instance', ({instance/* , hasCurrent */}) => {
|
||||
// if(hasCurrent) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
new PopupCall({
|
||||
const popup = new PopupCall({
|
||||
appCallsManager,
|
||||
appAvatarsManager,
|
||||
appPeersManager,
|
||||
instance
|
||||
}).show();
|
||||
});
|
||||
|
||||
instance.addEventListener('acceptCallOverride', () => {
|
||||
return this.discardCurrentCall(instance.interlocutorUserId.toPeerId(), undefined, instance)
|
||||
.then(() => {
|
||||
rootScope.dispatchEvent('call_accepting', instance);
|
||||
return true;
|
||||
})
|
||||
.catch(() => false);
|
||||
});
|
||||
|
||||
popup.addEventListener('close', () => {
|
||||
const currentCall = appCallsManager.currentCall;
|
||||
if(currentCall && currentCall !== instance && !instance.wasTryingToJoin) {
|
||||
instance.hangUp('phoneCallDiscardReasonBusy');
|
||||
}
|
||||
}, {once: true});
|
||||
|
||||
popup.show();
|
||||
});
|
||||
}
|
||||
|
||||
@ -944,9 +965,9 @@ export class AppImManager {
|
||||
appCallsManager.startCallInternal(userId, type === 'video');
|
||||
}
|
||||
|
||||
private discardCurrentCall(toPeerId: PeerId) {
|
||||
if(appCallsManager.currentCall) return this.discardCallConfirmation(toPeerId);
|
||||
else if(appGroupCallsManager.groupCall) return this.discardGroupCallConfirmation(toPeerId);
|
||||
private discardCurrentCall(toPeerId: PeerId, ignoreGroupCall?: GroupCallInstance, ignoreCall?: CallInstance) {
|
||||
if(appGroupCallsManager.groupCall && appGroupCallsManager.groupCall !== ignoreGroupCall) return this.discardGroupCallConfirmation(toPeerId);
|
||||
else if(appCallsManager.currentCall && appCallsManager.currentCall !== ignoreCall) return this.discardCallConfirmation(toPeerId);
|
||||
else return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -965,8 +986,8 @@ export class AppImManager {
|
||||
}
|
||||
});
|
||||
|
||||
if(appCallsManager.currentCall === currentCall) {
|
||||
await currentCall.hangUp();
|
||||
if(!currentCall.isClosing) {
|
||||
await currentCall.hangUp('phoneCallDiscardReasonDisconnect');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import I18n from '../langPack';
|
||||
import { NULL_PEER_ID } from "../mtproto/mtproto_config";
|
||||
import { getRestrictionReason } from "../../helpers/restrictions";
|
||||
import isObject from "../../helpers/object/isObject";
|
||||
import { limitSymbols } from "../../helpers/string";
|
||||
|
||||
// https://github.com/eelcohn/Telegram-API/wiki/Calculating-color-for-a-Telegram-user-on-IRC
|
||||
/*
|
||||
@ -73,7 +74,7 @@ export class AppPeersManager {
|
||||
return false;
|
||||
}
|
||||
|
||||
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false) {
|
||||
public getPeerTitle(peerId: PeerId, plainText = false, onlyFirstName = false, _limitSymbols?: number) {
|
||||
if(!peerId) {
|
||||
peerId = rootScope.myId;
|
||||
}
|
||||
@ -94,6 +95,10 @@ export class AppPeersManager {
|
||||
title = title.split(' ')[0];
|
||||
}
|
||||
}
|
||||
|
||||
if(_limitSymbols !== undefined) {
|
||||
title = limitSymbols(title, _limitSymbols, _limitSymbols);
|
||||
}
|
||||
|
||||
return plainText ? title : RichTextProcessor.wrapEmojiText(title);
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||
import assumeType from "../../helpers/assumeType";
|
||||
import callbackify from "../../helpers/callbackify";
|
||||
import callbackifyAll from "../../helpers/callbackifyAll";
|
||||
import { copy } from "../../helpers/object";
|
||||
import copy from "../../helpers/object/copy";
|
||||
import { AvailableReaction, Message, MessagePeerReaction, MessagesAvailableReactions, Update, Updates } from "../../layer";
|
||||
import apiManager from "../mtproto/mtprotoworker";
|
||||
import { ReferenceContext } from "../mtproto/referenceDatabase";
|
||||
|
@ -34,7 +34,8 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
state: (state: CALL_STATE) => void,
|
||||
id: (id: CallId, prevId: CallId) => void,
|
||||
muted: (muted: boolean) => void,
|
||||
mediaState: (mediaState: CallMediaState) => void
|
||||
mediaState: (mediaState: CallMediaState) => void,
|
||||
acceptCallOverride: () => Promise<boolean>,
|
||||
}> {
|
||||
public dh: Partial<DiffieHellmanInfo.a & DiffieHellmanInfo.b>;
|
||||
public id: CallId;
|
||||
@ -55,6 +56,7 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
public release: () => Promise<void>;
|
||||
public _connectionState: CALL_STATE;
|
||||
|
||||
public createdAt: number;
|
||||
public connectedAt: number;
|
||||
public discardReason: string;
|
||||
|
||||
@ -78,6 +80,9 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
|
||||
private wasStartingScreen: boolean;
|
||||
private wasStartingVideo: boolean;
|
||||
public wasTryingToJoin: boolean;
|
||||
|
||||
public streamManager: StreamManager;
|
||||
|
||||
constructor(options: {
|
||||
isOutgoing: boolean,
|
||||
@ -97,6 +102,7 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
|
||||
safeAssign(this, options);
|
||||
|
||||
this.createdAt = Date.now();
|
||||
this.offerReceived = false;
|
||||
this.offerSent = false;
|
||||
this.decryptQueue = [];
|
||||
@ -110,7 +116,7 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
}
|
||||
});
|
||||
|
||||
const streamManager = new StreamManager(GROUP_CALL_AMPLITUDE_ANALYSE_INTERVAL_MS);
|
||||
const streamManager = this.streamManager = new StreamManager(GROUP_CALL_AMPLITUDE_ANALYSE_INTERVAL_MS);
|
||||
streamManager.direction = 'sendrecv';
|
||||
streamManager.types.push('screencast');
|
||||
if(!this.isOutgoing) {
|
||||
@ -164,6 +170,14 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
}
|
||||
}
|
||||
|
||||
get sortIndex() {
|
||||
const connectionState = this.connectionState;
|
||||
const state = CALL_STATE.CLOSED - connectionState + 1;
|
||||
let index = state * 10000000000000;
|
||||
index += 2147483647000 - (connectionState === CALL_STATE.PENDING && this.isOutgoing ? 0 : this.createdAt);
|
||||
return index;
|
||||
}
|
||||
|
||||
public getVideoElement(type: CallMediaState['type']) {
|
||||
if(type === 'input') return this.elements.get('main');
|
||||
else {
|
||||
@ -283,10 +297,6 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
return connectionState === CALL_STATE.CLOSING || connectionState === CALL_STATE.CLOSED;
|
||||
}
|
||||
|
||||
public get streamManager(): StreamManager {
|
||||
return this.connectionInstance?.streamManager;
|
||||
}
|
||||
|
||||
public get description(): localConferenceDescription {
|
||||
return this.connectionInstance?.description;
|
||||
}
|
||||
@ -318,6 +328,11 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
}
|
||||
|
||||
public async acceptCall() {
|
||||
const canAccept = (await Promise.all(this.dispatchResultableEvent('acceptCallOverride')))[0] ?? true;
|
||||
if(this.isClosing || !canAccept) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this.clearHangUpTimeout();
|
||||
this.overrideConnectionState(CALL_STATE.EXCHANGING_KEYS);
|
||||
|
||||
@ -619,7 +634,7 @@ export default class CallInstance extends CallInstanceBase<{
|
||||
}
|
||||
|
||||
public async hangUp(discardReason?: PhoneCallDiscardReason['_'], discardedByOtherParty?: boolean) {
|
||||
if(this.connectionState === CALL_STATE.CLOSED) {
|
||||
if(this.isClosing) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import getAudioConstraints from "./helpers/getAudioConstraints";
|
||||
import getScreenConstraints from "./helpers/getScreenConstraints";
|
||||
import getStreamCached from "./helpers/getStreamCached";
|
||||
import getVideoConstraints from "./helpers/getVideoConstraints";
|
||||
import stopTrack from "./helpers/stopTrack";
|
||||
import LocalConferenceDescription from "./localConferenceDescription";
|
||||
import StreamManager, { StreamItem } from "./streamManager";
|
||||
|
||||
@ -190,6 +191,9 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners>
|
||||
|
||||
if(!isVideo) {
|
||||
player.appendChild(element);
|
||||
} else {
|
||||
element.setAttribute('playsinline', 'true');
|
||||
element.muted = true;
|
||||
}
|
||||
// audio.play();
|
||||
|
||||
@ -229,6 +233,10 @@ export default abstract class CallInstanceBase<E extends EventListenerListeners>
|
||||
if(description) {
|
||||
streamManager.appendToConference(description);
|
||||
}
|
||||
} else { // if call is declined earlier than stream appears
|
||||
stream.getTracks().forEach(track => {
|
||||
stopTrack(track);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -183,8 +183,6 @@ export default class GroupCallInstance extends CallInstanceBase<{
|
||||
|
||||
const clone = element.cloneNode() as typeof element;
|
||||
clone.srcObject = element.srcObject;
|
||||
clone.setAttribute('playsinline', 'true');
|
||||
clone.muted = true;
|
||||
return {video: clone, source};
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ import { encodeEntities } from '../helpers/string';
|
||||
import { IS_SAFARI } from '../environment/userAgent';
|
||||
import { MOUNT_CLASS_TO } from '../config/debug';
|
||||
import IS_EMOJI_SUPPORTED from '../environment/emojiSupport';
|
||||
import { copy } from '../helpers/object';
|
||||
import copy from '../helpers/object/copy';
|
||||
|
||||
const EmojiHelper = {
|
||||
emojiMap: (code: string) => { return code; },
|
||||
|
@ -17,7 +17,7 @@ import type { PushNotificationObject } from "./serviceWorker/push";
|
||||
import type { ConnectionStatusChange } from "./mtproto/connectionStatus";
|
||||
import type { GroupCallId } from "./appManagers/appGroupCallsManager";
|
||||
import type GroupCallInstance from "./calls/groupCallInstance";
|
||||
// import type CallInstance from "./calls/callInstance";
|
||||
import type CallInstance from "./calls/callInstance";
|
||||
import type { StreamAmplitude } from "./calls/streamManager";
|
||||
import type Chat from "../components/chat/chat";
|
||||
import { NULL_PEER_ID, UserAuth } from "./mtproto/mtproto_config";
|
||||
@ -158,7 +158,8 @@ export type BroadcastEvents = {
|
||||
'group_call_participant': {groupCallId: GroupCallId, participant: GroupCallParticipant},
|
||||
// 'group_call_video_track_added': {instance: GroupCallInstance}
|
||||
|
||||
'call_instance': {hasCurrent: boolean, instance: any/* CallInstance */},
|
||||
'call_instance': {hasCurrent: boolean, instance: CallInstance},
|
||||
'call_accepting': CallInstance, // это костыль. используется при параллельном вызове, чтобы заменить звонок в topbarCall
|
||||
|
||||
'quick_reaction': string,
|
||||
|
||||
|
@ -232,3 +232,19 @@ avatar-element {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-full {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: inherit;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.avatar-photo {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
@ -1383,12 +1383,13 @@ $bubble-beside-button-width: 38px;
|
||||
}
|
||||
|
||||
&-subtitle {
|
||||
font-size: .875rem;
|
||||
font-size: var(--messages-secondary-text-size);
|
||||
color: var(--secondary-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: .0625rem;
|
||||
margin-left: -.1875rem;
|
||||
line-height: var(--messages-secondary-line-height);
|
||||
|
||||
&.is-reason:before {
|
||||
margin-right: .0625rem;
|
||||
|
@ -239,6 +239,7 @@ body.is-right-column-shown {
|
||||
height: 3.5rem;
|
||||
max-height: 3.5rem;
|
||||
flex: 1 1 auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,15 +361,29 @@ body.is-right-column-shown {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 480px) {
|
||||
.topbar-call-left,
|
||||
.topbar-call-right {
|
||||
width: auto;
|
||||
&:not(.is-call) {
|
||||
@media only screen and (max-width: 480px) {
|
||||
.topbar-call-left,
|
||||
.topbar-call-right {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.group-call-description {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.group-call-description,
|
||||
.call-description {
|
||||
display: none;
|
||||
&.is-call {
|
||||
@media only screen and (max-width: 480px) {
|
||||
.topbar-call-left,
|
||||
.topbar-call-right {
|
||||
width: 6.25rem;
|
||||
}
|
||||
|
||||
.call-description:not(.has-duration) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -399,6 +414,13 @@ body.is-right-column-shown {
|
||||
&-center {
|
||||
@include sidebar-transform(true);
|
||||
@include text-overflow();
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
// ! it flicks over the left side :(
|
||||
// body.is-right-column-shown & {
|
||||
padding: 0 calc(var(--right-column-width) / 2);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
|
@ -37,6 +37,13 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.tgico-phone_filled {
|
||||
&:before {
|
||||
content: $tgico-endcall_filled;
|
||||
transform: rotate(-135deg);
|
||||
}
|
||||
}
|
||||
|
||||
.tgico-check {
|
||||
&:before {
|
||||
content: $tgico-check;
|
||||
|
@ -24,10 +24,15 @@
|
||||
color: #fff;
|
||||
align-items: center;
|
||||
|
||||
&.is-full-screen {
|
||||
&.is-full-screen,
|
||||
html.is-mobile & {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&.is-full-screen:not(.show-controls) {
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
&.show-controls,
|
||||
&.no-video {
|
||||
.call-title,
|
||||
@ -41,10 +46,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.show-controls {
|
||||
.call-video {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.call-video-blur {
|
||||
opacity: .56;
|
||||
}
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
.btn-icon {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.call-emojis {
|
||||
transform: scale(1.3125);
|
||||
margin-right: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-avatar {
|
||||
@ -55,11 +75,10 @@
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
opacity: .7;
|
||||
border-radius: inherit;
|
||||
|
||||
.avatar-photo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
.avatar-full {
|
||||
font-size: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,6 +201,7 @@
|
||||
object-fit: contain;
|
||||
position: absolute;
|
||||
border-radius: inherit;
|
||||
opacity: 1;
|
||||
|
||||
&-container {
|
||||
position: absolute;
|
||||
@ -212,6 +232,13 @@
|
||||
opacity: .7;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
&,
|
||||
&-blur {
|
||||
@include animation-level(2) {
|
||||
transition: opacity var(--transition-standard-in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// html.emoji-supported & {
|
||||
@ -234,6 +261,7 @@
|
||||
z-index: 2;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
&-party-state {
|
||||
@ -252,7 +280,6 @@
|
||||
opacity: 0;
|
||||
transform: scale(0) translateY(0);
|
||||
max-width: 100%;
|
||||
padding: 0 1rem;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: opacity var(--transition-standard-in), transform var(--transition-standard-in);
|
||||
@ -262,7 +289,7 @@
|
||||
width: 1.875rem !important;
|
||||
height: 1.875rem !important;
|
||||
margin-right: .25rem;
|
||||
margin-left: -.625rem;
|
||||
margin-left: -.25rem;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
@ -275,11 +302,12 @@
|
||||
// opacity: 0 !important;
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
max-width: 100%;
|
||||
@include text-overflow();
|
||||
}
|
||||
&-party-state-text,
|
||||
&-title {
|
||||
max-width: 100%;
|
||||
@include text-overflow();
|
||||
}
|
||||
|
||||
&.two-button-rows {
|
||||
|
Loading…
Reference in New Issue
Block a user