Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
656 lines
22 KiB
656 lines
22 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
* |
|
* Originally from: |
|
* https://github.com/evgeny-nadymov/telegram-react |
|
* Copyright (C) 2018 Evgeny Nadymov |
|
* https://github.com/evgeny-nadymov/telegram-react/blob/master/LICENSE |
|
*/ |
|
|
|
import { MOUNT_CLASS_TO } from "../../config/debug"; |
|
import AudioAssetPlayer from "../../helpers/audioAssetPlayer"; |
|
import safeReplaceObject from "../../helpers/object/safeReplaceObject"; |
|
import { nextRandomUint } from "../../helpers/random"; |
|
import tsNow from "../../helpers/tsNow"; |
|
import { GroupCall, GroupCallParticipant, GroupCallParticipantVideo, GroupCallParticipantVideoSourceGroup, InputGroupCall, Peer, PhoneJoinGroupCall, PhoneJoinGroupCallPresentation, Update, Updates } from "../../layer"; |
|
import GroupCallInstance from "../calls/groupCallInstance"; |
|
import GROUP_CALL_STATE from "../calls/groupCallState"; |
|
import createMainStreamManager from "../calls/helpers/createMainStreamManager"; |
|
import { generateSsrc } from "../calls/localConferenceDescription"; |
|
import { WebRTCLineType } from "../calls/sdpBuilder"; |
|
import StreamManager from "../calls/streamManager"; |
|
import { Ssrc } from "../calls/types"; |
|
import { logger } from "../logger"; |
|
import apiManager from "../mtproto/mtprotoworker"; |
|
import { NULL_PEER_ID } from "../mtproto/mtproto_config"; |
|
import rootScope from "../rootScope"; |
|
import apiUpdatesManager from "./apiUpdatesManager"; |
|
import appChatsManager from "./appChatsManager"; |
|
import appPeersManager from "./appPeersManager"; |
|
import appUsersManager from "./appUsersManager"; |
|
|
|
export type GroupCallId = GroupCall['id']; |
|
export type MyGroupCall = GroupCall | InputGroupCall; |
|
|
|
export type GroupCallConnectionType = 'main' | 'presentation'; |
|
|
|
export type JoinGroupCallJsonPayload = { |
|
fingerprints: { |
|
fingerprint: string; |
|
setup: string; |
|
hash: string; |
|
}[]; |
|
pwd: string; |
|
ssrc: number; |
|
'ssrc-groups': GroupCallParticipantVideoSourceGroup.groupCallParticipantVideoSourceGroup[]; |
|
ufrag: string; |
|
}; |
|
|
|
const GET_PARTICIPANTS_LIMIT = 100; |
|
|
|
let IS_MUTED = true; |
|
|
|
export type GroupCallOutputSource = 'main' | 'presentation' | number; |
|
|
|
export type GroupCallAudioAssetName = "group_call_connect.mp3" | "group_call_end.mp3" | "group_call_start.mp3" | "voip_onallowtalk.mp3"; |
|
|
|
export class AppGroupCallsManager { |
|
private log: ReturnType<typeof logger>; |
|
|
|
private groupCalls: Map<GroupCallId, MyGroupCall>; |
|
private participants: Map<GroupCallId, Map<PeerId, GroupCallParticipant>>; |
|
private nextOffsets: Map<GroupCallId, string>; |
|
|
|
// private audioAsset: AudioAsset; |
|
|
|
private currentGroupCall: GroupCallInstance; |
|
private connectionAudio: HTMLAudioElement; |
|
private doNotDispatchParticipantUpdate: PeerId; |
|
private audioAsset: AudioAssetPlayer<GroupCallAudioAssetName>; |
|
|
|
constructor() { |
|
this.log = logger('GROUP-CALLS'); |
|
|
|
this.groupCalls = new Map(); |
|
this.participants = new Map(); |
|
this.nextOffsets = new Map(); |
|
|
|
rootScope.addMultipleEventsListeners({ |
|
updateGroupCall: (update) => { |
|
this.saveGroupCall(update.call, update.chat_id); |
|
}, |
|
|
|
updateGroupCallParticipants: (update) => { |
|
this.saveGroupCall(update.call); |
|
|
|
// this.getGroupCallFull(update.call.id, true); // ! WARNING TEMP |
|
|
|
const groupCallId = update.call.id; |
|
this.saveApiParticipants(groupCallId, update.participants); |
|
} |
|
}); |
|
|
|
rootScope.addEventListener('group_call_update', (groupCall) => { |
|
if(groupCall._ === 'groupCallDiscarded') { |
|
const {currentGroupCall} = this; |
|
if(currentGroupCall?.id === groupCall.id) { |
|
currentGroupCall.hangUp(false, false, true); |
|
} |
|
|
|
this.participants.delete(groupCall.id); |
|
} |
|
}); |
|
|
|
this.audioAsset = new AudioAssetPlayer<GroupCallAudioAssetName>([ |
|
'group_call_connect.mp3', |
|
'group_call_end.mp3', |
|
'group_call_start.mp3', |
|
'voip_onallowtalk.mp3' |
|
]); |
|
} |
|
|
|
get groupCall() { |
|
return this.currentGroupCall; |
|
} |
|
|
|
public getCachedParticipants(groupCallId: GroupCallId) { |
|
let participants = this.participants.get(groupCallId); |
|
if(!participants) { |
|
this.participants.set(groupCallId, participants = new Map()); |
|
} |
|
|
|
return participants; |
|
} |
|
|
|
private prepareToSavingNextOffset(groupCallId: GroupCallId) { |
|
const nextOffsetsMap = this.nextOffsets; |
|
|
|
const setNextOffset = (newNextOffset: string) => { |
|
if(nextOffsetsMap.get(groupCallId) === nextOffset) { |
|
nextOffsetsMap.set(groupCallId, newNextOffset); |
|
} |
|
}; |
|
|
|
const nextOffset = nextOffsetsMap.get(groupCallId); |
|
return { |
|
nextOffset, |
|
setNextOffset |
|
}; |
|
} |
|
|
|
public saveApiParticipant(groupCallId: GroupCallId, participant: GroupCallParticipant, skipCounterUpdating?: boolean) { |
|
const {currentGroupCall} = this; |
|
const participants = this.getCachedParticipants(groupCallId); |
|
|
|
const peerId = appPeersManager.getPeerId(participant.peer); |
|
|
|
const oldParticipant = participants.get(peerId); |
|
const hasLeft = participant.pFlags.left; |
|
if(!oldParticipant && hasLeft) { |
|
return; |
|
} |
|
|
|
// * fix missing flag |
|
if(!participant.pFlags.muted && !participant.pFlags.can_self_unmute) { |
|
participant.pFlags.can_self_unmute = true; |
|
} |
|
|
|
const isCurrentGroupCall = currentGroupCall?.id === groupCallId; |
|
|
|
if(oldParticipant) { |
|
safeReplaceObject(oldParticipant, participant); |
|
participant = oldParticipant; |
|
} else { |
|
participants.set(peerId, participant); |
|
} |
|
|
|
if(isCurrentGroupCall) { |
|
currentGroupCall.onParticipantUpdate(participant, this.doNotDispatchParticipantUpdate); |
|
} |
|
|
|
// if(!skipCounterUpdating) { |
|
const groupCall = this.getGroupCall(groupCallId); |
|
if(groupCall?._ === 'groupCall') { |
|
let modified = false; |
|
if(hasLeft) { |
|
--groupCall.participants_count; |
|
modified = true; |
|
} else if(participant.pFlags.just_joined && !oldParticipant && !participant.pFlags.self) { |
|
++groupCall.participants_count; |
|
modified = true; |
|
} |
|
|
|
if(modified) { |
|
rootScope.dispatchEvent('group_call_update', groupCall); |
|
} |
|
} |
|
// } |
|
|
|
if(hasLeft) { |
|
participants.delete(peerId); |
|
} |
|
|
|
if(oldParticipant && this.doNotDispatchParticipantUpdate !== peerId) { |
|
rootScope.dispatchEvent('group_call_participant', { |
|
groupCallId, |
|
participant |
|
}); |
|
} |
|
} |
|
|
|
public saveApiParticipants(groupCallId: GroupCallId, apiParticipants: GroupCallParticipant[], skipCounterUpdating?: boolean) { |
|
if((apiParticipants as any).saved) return; |
|
(apiParticipants as any).saved = true; |
|
apiParticipants.forEach(p => this.saveApiParticipant(groupCallId, p, skipCounterUpdating)); |
|
} |
|
|
|
public async editParticipant(groupCallId: GroupCallId, participant: GroupCallParticipant, options: Partial<{ |
|
muted: boolean, |
|
volume: number, |
|
raiseHand: boolean, |
|
videoStopped: boolean, |
|
videoPaused: boolean, |
|
presentationPaused: boolean |
|
}>) { |
|
if(!Object.keys(options).length) { |
|
return; |
|
} |
|
|
|
let processUpdate = true; |
|
if(participant) { |
|
const {currentGroupCall} = this; |
|
const isCurrentCall = currentGroupCall?.id === groupCallId; |
|
const isUpdatingMeInCurrentCall = isCurrentCall && participant.pFlags.self; |
|
|
|
if(isUpdatingMeInCurrentCall) { |
|
if(options.muted !== undefined && !currentGroupCall.isSharingAudio) { |
|
delete options.muted; |
|
|
|
if(!Object.keys(options).length) { |
|
return; |
|
} |
|
} |
|
} |
|
|
|
// if(isCurrentCall) { |
|
const muted = options.muted; |
|
if(muted !== undefined) { |
|
/* const isAdmin = appChatsManager.hasRights(currentGroupCall.chatId, 'manage_call'); |
|
if(isAdmin) { |
|
if(muted) { |
|
participant.pFlags.muted = true; |
|
delete participant.pFlags.can_self_unmute; |
|
} else { |
|
participant.pFlags.can_self_unmute = true; |
|
} |
|
} else */if(participant.pFlags.self) { |
|
if(muted) { |
|
participant.pFlags.muted = true; |
|
} else if(participant.pFlags.can_self_unmute) { |
|
delete participant.pFlags.muted; |
|
} |
|
}/* else { |
|
if(muted) { |
|
participant.pFlags.muted_by_you = true; |
|
} else { |
|
delete participant.pFlags.muted_by_you; |
|
} |
|
} */ |
|
} |
|
// } |
|
|
|
/* const a: [keyof GroupCallParticipant['pFlags'], keyof typeof options][] = [ |
|
['muted', 'muted'] |
|
]; |
|
|
|
a.forEach(([key, optionKey]) => { |
|
const value = options[optionKey]; |
|
if(value === undefined) { |
|
return; |
|
} |
|
|
|
if(value) { |
|
participant.pFlags[key] = true; |
|
} else { |
|
delete participant.pFlags[key]; |
|
} |
|
}); */ |
|
|
|
if(options.raiseHand !== undefined) { |
|
if(options.raiseHand) participant.raise_hand_rating = '1'; |
|
else delete participant.raise_hand_rating; |
|
} |
|
|
|
if(isUpdatingMeInCurrentCall) { |
|
if(options.videoStopped !== undefined) { |
|
if(options.videoStopped) delete participant.video; |
|
else participant.video = this.generateSelfVideo(currentGroupCall.connections.main.sources.video); |
|
} |
|
|
|
if(!participant.pFlags.muted && participant.pFlags.can_self_unmute) { |
|
currentGroupCall.setMuted(false); |
|
} |
|
|
|
currentGroupCall.dispatchEvent('state', currentGroupCall.state); |
|
} |
|
|
|
rootScope.dispatchEvent('group_call_participant', {groupCallId, participant}); |
|
|
|
/* if(participant.pFlags.self) { |
|
processUpdate = false; |
|
} */ |
|
} |
|
|
|
const peerId = participant.pFlags.self ? NULL_PEER_ID : appPeersManager.getPeerId(participant.peer); |
|
const updates = await apiManager.invokeApiSingle('phone.editGroupCallParticipant', { |
|
call: appGroupCallsManager.getGroupCallInput(groupCallId), |
|
participant: peerId === NULL_PEER_ID ? appPeersManager.getInputPeerSelf() : appPeersManager.getInputPeerById(peerId), |
|
muted: options.muted, |
|
volume: options.volume, |
|
raise_hand: options.raiseHand, |
|
video_paused: options.videoPaused, |
|
video_stopped: options.videoStopped, |
|
presentation_paused: options.presentationPaused |
|
}); |
|
|
|
// do not replace with peerId because it can be null |
|
if(!processUpdate) this.doNotDispatchParticipantUpdate = appPeersManager.getPeerId(participant.peer); |
|
apiUpdatesManager.processUpdateMessage(updates); |
|
if(!processUpdate) this.doNotDispatchParticipantUpdate = undefined; |
|
} |
|
|
|
public getGroupCall(id: GroupCallId) { |
|
return this.groupCalls.get(id); |
|
} |
|
|
|
public async getGroupCallFull(id: GroupCallId, override?: boolean): Promise<GroupCall> { |
|
const call = this.getGroupCall(id); |
|
if(call && call._ !== 'inputGroupCall' && !override) { |
|
return call; |
|
} |
|
|
|
const limit = this.getCachedParticipants(id).size ? 0 : GET_PARTICIPANTS_LIMIT; |
|
return apiManager.invokeApiSingleProcess({ |
|
method: 'phone.getGroupCall', |
|
params: { |
|
call: this.getGroupCallInput(id), |
|
limit |
|
}, |
|
processResult: (groupCall) => { |
|
// ? maybe I should save group call after participants so I can avoid passing the 'skipCounterUpdating' flag ? |
|
appUsersManager.saveApiUsers(groupCall.users); |
|
appChatsManager.saveApiChats(groupCall.chats); |
|
this.saveApiParticipants(id, groupCall.participants, true); |
|
const call = this.saveGroupCall(groupCall.call) as GroupCall; |
|
|
|
if(limit && this.nextOffsets.get(id) === undefined) { |
|
this.nextOffsets.set(id, groupCall.participants_next_offset); |
|
} |
|
|
|
return call; |
|
} |
|
}); |
|
} |
|
|
|
public saveGroupCall(call: MyGroupCall, chatId?: ChatId) { |
|
const oldCall = this.groupCalls.get(call.id); |
|
const shouldUpdate = call._ !== 'inputGroupCall' && (!oldCall || oldCall._ !== 'groupCallDiscarded'); |
|
if(oldCall) { |
|
if(shouldUpdate) { |
|
safeReplaceObject(oldCall, call); |
|
} |
|
|
|
call = oldCall; |
|
} else { |
|
this.groupCalls.set(call.id, call); |
|
} |
|
|
|
if(shouldUpdate) { |
|
rootScope.dispatchEvent('group_call_update', call as any); |
|
} |
|
|
|
return call; |
|
} |
|
|
|
public startConnectingSound() { |
|
this.stopConnectingSound(); |
|
this.audioAsset.playSoundWithTimeout('group_call_connect.mp3', true, 2500); |
|
} |
|
|
|
public stopConnectingSound() { |
|
this.audioAsset.stopSound(); |
|
this.audioAsset.cancelDelayedPlay(); |
|
} |
|
|
|
public setCurrentGroupCall(groupCall: GroupCallInstance) { |
|
this.currentGroupCall = groupCall; |
|
|
|
if(groupCall) { |
|
rootScope.dispatchEvent('group_call_instance', groupCall); |
|
} |
|
/* TdLibController.clientUpdate({ |
|
'@type': 'clientUpdateGroupCall', |
|
call |
|
}); */ |
|
} |
|
|
|
public async createGroupCall(chatId: ChatId, scheduleDate?: number, title?: string) { |
|
const updates = await apiManager.invokeApi('phone.createGroupCall', { |
|
peer: appPeersManager.getInputPeerById(chatId.toPeerId(true)), |
|
random_id: nextRandomUint(32), |
|
schedule_date: scheduleDate, |
|
title |
|
}); |
|
|
|
apiUpdatesManager.processUpdateMessage(updates); |
|
|
|
const update = (updates as Updates.updates).updates.find(update => update._ === 'updateGroupCall') as Update.updateGroupCall; |
|
return update.call; |
|
} |
|
|
|
public async joinGroupCall(chatId: ChatId, groupCallId: GroupCallId, muted = IS_MUTED, rejoin?: boolean, joinVideo?: boolean) { |
|
this.audioAsset.createAudio(); |
|
|
|
this.log(`joinGroupCall chatId=${chatId} id=${groupCallId} muted=${muted} rejoin=${rejoin}`); |
|
|
|
let streamManager: StreamManager; |
|
if(rejoin) { |
|
streamManager = this.currentGroupCall.connections.main.streamManager; |
|
} else { |
|
streamManager = await createMainStreamManager(muted, joinVideo); |
|
} |
|
|
|
return this.joinGroupCallInternal(chatId, groupCallId, streamManager, muted, rejoin, joinVideo); |
|
} |
|
|
|
public async joinGroupCallInternal(chatId: ChatId, groupCallId: GroupCallId, streamManager: StreamManager, muted: boolean, rejoin = false, joinVideo?: boolean) { |
|
const log = this.log.bindPrefix('joinGroupCallInternal'); |
|
log('start', groupCallId); |
|
|
|
const type: GroupCallConnectionType = 'main'; |
|
|
|
let {currentGroupCall} = this; |
|
if(currentGroupCall && rejoin) { |
|
// currentGroupCall.connections.main.connection = connection; |
|
currentGroupCall.handleUpdateGroupCallParticipants = false; |
|
currentGroupCall.updatingSdp = false; |
|
log('update currentGroupCall', groupCallId, currentGroupCall); |
|
} else { |
|
currentGroupCall = new GroupCallInstance({ |
|
chatId, |
|
id: groupCallId |
|
}); |
|
|
|
currentGroupCall.fixSafariAudio(); |
|
|
|
currentGroupCall.addEventListener('state', (state) => { |
|
if(this.currentGroupCall === currentGroupCall && state === GROUP_CALL_STATE.CLOSED) { |
|
this.setCurrentGroupCall(null); |
|
this.stopConnectingSound(); |
|
this.audioAsset.playSound('group_call_end.mp3'); |
|
rootScope.dispatchEvent('chat_update', currentGroupCall.chatId); |
|
} |
|
}); |
|
|
|
currentGroupCall.groupCall = await this.getGroupCallFull(groupCallId); |
|
|
|
const connectionInstance = currentGroupCall.createConnectionInstance({ |
|
streamManager, |
|
type, |
|
options: { |
|
type, |
|
isMuted: muted, |
|
joinVideo, |
|
rejoin |
|
} |
|
}); |
|
|
|
const connection = connectionInstance.createPeerConnection(); |
|
connection.addEventListener('negotiationneeded', () => { |
|
connectionInstance.negotiate(); |
|
}); |
|
|
|
connection.addEventListener('track', (event) => { |
|
log('ontrack', event); |
|
currentGroupCall.onTrack(event); |
|
}); |
|
|
|
connection.addEventListener('iceconnectionstatechange', () => { |
|
currentGroupCall.dispatchEvent('state', currentGroupCall.state); |
|
|
|
const {iceConnectionState} = connection; |
|
if(iceConnectionState === 'disconnected' || iceConnectionState === 'checking' || iceConnectionState === 'new') { |
|
this.startConnectingSound(); |
|
} else { |
|
this.stopConnectingSound(); |
|
} |
|
|
|
switch(iceConnectionState) { |
|
case 'checking': { |
|
break; |
|
} |
|
|
|
case 'closed': { |
|
currentGroupCall.hangUp(); |
|
break; |
|
} |
|
|
|
case 'completed': { |
|
break; |
|
} |
|
|
|
case 'connected': { |
|
if(!currentGroupCall.joined) { |
|
currentGroupCall.joined = true; |
|
this.audioAsset.playSound('group_call_start.mp3'); |
|
|
|
this.getGroupCallParticipants(groupCallId).then(({participants}) => { |
|
this.saveApiParticipants(groupCallId, [...participants.values()]); |
|
}); |
|
} |
|
|
|
break; |
|
} |
|
|
|
case 'disconnected': { |
|
break; |
|
} |
|
|
|
case 'failed': { |
|
//TODO: replace with ICE restart |
|
currentGroupCall.hangUp(); |
|
// connection.restartIce(); |
|
break; |
|
} |
|
|
|
case 'new': { |
|
break; |
|
} |
|
} |
|
}); |
|
|
|
connectionInstance.createDescription(); |
|
connectionInstance.createDataChannel(); |
|
|
|
connectionInstance.appendStreamToConference(); |
|
|
|
this.setCurrentGroupCall(currentGroupCall); |
|
log('set currentGroupCall', groupCallId, currentGroupCall); |
|
|
|
this.startConnectingSound(); |
|
|
|
return connectionInstance.negotiate(); |
|
} |
|
} |
|
|
|
public getGroupCallInput(id: GroupCallId): InputGroupCall { |
|
const groupCall = this.getGroupCall(id); |
|
return { |
|
_: 'inputGroupCall', |
|
id: groupCall.id, |
|
access_hash: groupCall.access_hash |
|
}; |
|
} |
|
|
|
public generateSelfVideo(source: Ssrc, audioSource?: number): GroupCallParticipantVideo { |
|
return source && { |
|
_: 'groupCallParticipantVideo', |
|
pFlags: {}, |
|
endpoint: '', |
|
source_groups: source.sourceGroups, |
|
audio_source: audioSource |
|
}; |
|
} |
|
|
|
public generateSelfParticipant(): GroupCallParticipant { |
|
const mainSources = this.currentGroupCall.connections.main.sources; |
|
const presentationSources = this.currentGroupCall.connections.presentation?.sources; |
|
return { |
|
_: 'groupCallParticipant', |
|
pFlags: { |
|
can_self_unmute: true, |
|
self: true |
|
}, |
|
source: mainSources.audio.source, |
|
video: this.generateSelfVideo(mainSources.video), |
|
presentation: presentationSources && this.generateSelfVideo(presentationSources.video, presentationSources.audio?.source), |
|
date: tsNow(true), |
|
peer: appPeersManager.getOutputPeer(rootScope.myId) |
|
}; |
|
} |
|
|
|
public makeSsrcsFromParticipant = (participant: GroupCallParticipant) => { |
|
return [ |
|
this.makeSsrcFromParticipant(participant, 'audio', participant.source), |
|
participant.video?.audio_source && this.makeSsrcFromParticipant(participant, 'audio', participant.video.audio_source), |
|
participant.video && this.makeSsrcFromParticipant(participant, 'video', participant.video.source_groups, participant.video.endpoint), |
|
participant.presentation?.audio_source && this.makeSsrcFromParticipant(participant, 'audio', participant.presentation.audio_source), |
|
participant.presentation && this.makeSsrcFromParticipant(participant, 'video', participant.presentation.source_groups, participant.presentation.endpoint) |
|
].filter(Boolean); |
|
}; |
|
|
|
public makeSsrcFromParticipant(participant: GroupCallParticipant, type: WebRTCLineType, source?: number | GroupCallParticipantVideoSourceGroup[], endpoint?: string): Ssrc { |
|
return generateSsrc(type, source, endpoint); |
|
} |
|
|
|
public async getGroupCallParticipants(id: GroupCallId) { |
|
const {nextOffset, setNextOffset} = this.prepareToSavingNextOffset(id); |
|
|
|
if(nextOffset !== '') { |
|
await apiManager.invokeApiSingleProcess({ |
|
method: 'phone.getGroupParticipants', |
|
params: { |
|
call: this.getGroupCallInput(id), |
|
ids: [], |
|
sources: [], |
|
offset: nextOffset || '', |
|
limit: GET_PARTICIPANTS_LIMIT |
|
}, |
|
processResult: (groupCallParticipants) => { |
|
const newNextOffset = groupCallParticipants.count === groupCallParticipants.participants.length ? '' : groupCallParticipants.next_offset; |
|
|
|
appChatsManager.saveApiChats(groupCallParticipants.chats); |
|
appUsersManager.saveApiUsers(groupCallParticipants.users); |
|
this.saveApiParticipants(id, groupCallParticipants.participants); |
|
|
|
setNextOffset(newNextOffset); |
|
} |
|
}); |
|
} |
|
|
|
return { |
|
participants: this.getCachedParticipants(id), |
|
isEnd: this.nextOffsets.get(id) === '' |
|
}; |
|
} |
|
|
|
public async hangUp(groupCallId: GroupCallId, discard = false, rejoin = false) { |
|
this.log(`hangUp start id=${groupCallId} discard=${discard} rejoin=${rejoin}`); |
|
const {currentGroupCall} = this; |
|
if(currentGroupCall?.id !== groupCallId) return; |
|
|
|
currentGroupCall.hangUp(discard, rejoin); |
|
} |
|
|
|
public toggleMuted(muted?: boolean) { |
|
return this.changeUserMuted(NULL_PEER_ID, muted); |
|
} |
|
|
|
public changeUserMuted(peerId: PeerId, muted?: boolean) { |
|
const {currentGroupCall} = this; |
|
if(!currentGroupCall) return; |
|
|
|
const participant = currentGroupCall.getParticipantByPeerId(peerId); |
|
if(NULL_PEER_ID === peerId && participant.pFlags.can_self_unmute) { |
|
muted = muted === undefined ? !participant.pFlags.muted : muted; |
|
} |
|
|
|
return this.editParticipant(currentGroupCall.id, participant, {muted}); |
|
} |
|
} |
|
|
|
const appGroupCallsManager = new AppGroupCallsManager(); |
|
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appGroupCallsManager = appGroupCallsManager); |
|
export default appGroupCallsManager;
|
|
|