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.
254 lines
8.5 KiB
254 lines
8.5 KiB
/* |
|
* https://github.com/morethanwords/tweb |
|
* Copyright (C) 2019-2021 Eduard Kuzmenko |
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE |
|
*/ |
|
|
|
import getGroupCallAudioAsset from '../../components/groupCall/getAudioAsset'; |
|
import {MOUNT_CLASS_TO} from '../../config/debug'; |
|
import EventListenerBase from '../../helpers/eventListenerBase'; |
|
import {GroupCallParticipant, GroupCallParticipantVideo, GroupCallParticipantVideoSourceGroup} from '../../layer'; |
|
import {GroupCallId, GroupCallConnectionType} from '../appManagers/appGroupCallsManager'; |
|
import {AppManagers} from '../appManagers/managers'; |
|
import {logger} from '../logger'; |
|
import rootScope from '../rootScope'; |
|
import GroupCallInstance from './groupCallInstance'; |
|
import GROUP_CALL_STATE from './groupCallState'; |
|
import createMainStreamManager from './helpers/createMainStreamManager'; |
|
import {generateSsrc} from './localConferenceDescription'; |
|
import {WebRTCLineType} from './sdpBuilder'; |
|
import StreamManager from './streamManager'; |
|
import {Ssrc} from './types'; |
|
|
|
const IS_MUTED = true; |
|
|
|
export function makeSsrcsFromParticipant(participant: GroupCallParticipant) { |
|
return [ |
|
makeSsrcFromParticipant(participant, 'audio', participant.source), |
|
participant.video?.audio_source && makeSsrcFromParticipant(participant, 'audio', participant.video.audio_source), |
|
participant.video && makeSsrcFromParticipant(participant, 'video', participant.video.source_groups, participant.video.endpoint), |
|
participant.presentation?.audio_source && makeSsrcFromParticipant(participant, 'audio', participant.presentation.audio_source), |
|
participant.presentation && makeSsrcFromParticipant(participant, 'video', participant.presentation.source_groups, participant.presentation.endpoint) |
|
].filter(Boolean); |
|
}; |
|
|
|
export function makeSsrcFromParticipant(participant: GroupCallParticipant, type: WebRTCLineType, source?: number | GroupCallParticipantVideoSourceGroup[], endpoint?: string): Ssrc { |
|
return generateSsrc(type, source, endpoint); |
|
} |
|
|
|
export function generateSelfVideo(source: Ssrc, audioSource?: number): GroupCallParticipantVideo { |
|
return source && { |
|
_: 'groupCallParticipantVideo', |
|
pFlags: {}, |
|
endpoint: '', |
|
source_groups: source.sourceGroups, |
|
audio_source: audioSource |
|
}; |
|
} |
|
|
|
export class GroupCallsController extends EventListenerBase<{ |
|
instance: (instance: GroupCallInstance) => void |
|
}> { |
|
private audioAsset: ReturnType<typeof getGroupCallAudioAsset>; |
|
private log: ReturnType<typeof logger>; |
|
private currentGroupCall: GroupCallInstance; |
|
private managers: AppManagers; |
|
|
|
public construct(managers: AppManagers) { |
|
this.managers = managers; |
|
this.audioAsset = getGroupCallAudioAsset(); |
|
this.log = logger('GCC'); |
|
|
|
rootScope.addEventListener('group_call_update', (groupCall) => { |
|
const {currentGroupCall} = this; |
|
if(currentGroupCall?.id === groupCall.id) { |
|
currentGroupCall.groupCall = groupCall; |
|
|
|
if(groupCall._ === 'groupCallDiscarded') { |
|
currentGroupCall.hangUp(false, false, true); |
|
} |
|
} |
|
}); |
|
|
|
rootScope.addEventListener('group_call_participant', ({groupCallId, participant}) => { |
|
const {currentGroupCall} = this; |
|
if(currentGroupCall?.id === groupCallId) { |
|
currentGroupCall.onParticipantUpdate(participant/* , this.doNotDispatchParticipantUpdate */); |
|
} |
|
}); |
|
} |
|
|
|
get groupCall() { |
|
return this.currentGroupCall; |
|
} |
|
|
|
public setCurrentGroupCall(groupCall: GroupCallInstance) { |
|
this.currentGroupCall = groupCall; |
|
|
|
if(groupCall) { |
|
this.dispatchEvent('instance', groupCall); |
|
} |
|
} |
|
|
|
public startConnectingSound() { |
|
this.stopConnectingSound(); |
|
this.audioAsset.playSoundWithTimeout('group_call_connect.mp3', true, 2500); |
|
} |
|
|
|
public stopConnectingSound() { |
|
this.audioAsset.stopSound(); |
|
this.audioAsset.cancelDelayedPlay(); |
|
} |
|
|
|
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) |
|
.then(() => { |
|
// have to refresh participants because of the new connection |
|
const {currentGroupCall} = this; |
|
currentGroupCall.participants.then((participants) => { |
|
if(this.currentGroupCall !== currentGroupCall || currentGroupCall.state === GROUP_CALL_STATE.CLOSED) { |
|
return; |
|
} |
|
|
|
participants.forEach((participant) => { |
|
if(!participant.pFlags.self) { |
|
currentGroupCall.onParticipantUpdate(participant); |
|
} |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
private 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, |
|
managers: this.managers |
|
}); |
|
|
|
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.managers.appGroupCallsManager.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.managers.appGroupCallsManager.getGroupCallParticipants(groupCallId); |
|
} |
|
|
|
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(); |
|
} |
|
} |
|
} |
|
|
|
const groupCallsController = new GroupCallsController(); |
|
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.groupCallController = groupCallsController); |
|
export default groupCallsController;
|
|
|