Implement fast confcall migration.

This commit is contained in:
John Preston 2025-04-09 14:18:14 +04:00
parent 2a7aac76d9
commit c72cf46db7
13 changed files with 366 additions and 191 deletions

View file

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_dh_utils.h" #include "mtproto/mtproto_dh_utils.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "main/session/session_show.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "apiwrap.h" #include "apiwrap.h"
@ -237,14 +238,20 @@ void Instance::startOrJoinGroupCall(
} }
void Instance::startOrJoinConferenceCall(StartConferenceInfo args) { void Instance::startOrJoinConferenceCall(StartConferenceInfo args) {
const auto migrationInfo = (args.migrating && _currentCallPanel) Expects(args.call || (args.migrating && args.show));
const auto migrationInfo = (args.migrating
&& args.call
&& _currentCallPanel)
? _currentCallPanel->migrationInfo() ? _currentCallPanel->migrationInfo()
: ConferencePanelMigration(); : ConferencePanelMigration();
if (!args.migrating) { if (args.call && !args.migrating) {
destroyCurrentCall(); destroyCurrentCall();
} }
const auto session = &args.call->peer()->session(); const auto session = args.show
? &args.show->session()
: &args.call->session();
auto call = std::make_unique<GroupCall>(_delegate.get(), args); auto call = std::make_unique<GroupCall>(_delegate.get(), args);
const auto raw = call.get(); const auto raw = call.get();
@ -253,20 +260,46 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) {
destroyGroupCall(raw); destroyGroupCall(raw);
}, raw->lifetime()); }, raw->lifetime());
if (args.call) {
_currentGroupCallPanel = std::make_unique<Group::Panel>(
raw,
migrationInfo);
_currentGroupCall = std::move(call);
_currentGroupCallChanges.fire_copy(raw);
if (args.migrating) {
destroyCurrentCall(args.call.get(), args.linkSlug);
}
} else {
if (const auto was = base::take(_migratingGroupCall)) {
destroyGroupCall(was.get());
}
_migratingGroupCall = std::move(call);
}
}
void Instance::migratedConferenceReady(
not_null<GroupCall*> call,
StartConferenceInfo args) {
if (_migratingGroupCall.get() != call) {
return;
}
const auto migrationInfo = _currentCallPanel
? _currentCallPanel->migrationInfo()
: ConferencePanelMigration();
_currentGroupCallPanel = std::make_unique<Group::Panel>( _currentGroupCallPanel = std::make_unique<Group::Panel>(
raw, call,
migrationInfo); migrationInfo);
_currentGroupCall = std::move(call); _currentGroupCall = std::move(_migratingGroupCall);
_currentGroupCallChanges.fire_copy(raw); _currentGroupCallChanges.fire_copy(call);
const auto real = call->conferenceCall().get();
const auto link = real->conferenceInviteLink();
const auto slug = Group::ExtractConferenceSlug(link);
if (!args.invite.empty()) { if (!args.invite.empty()) {
_currentGroupCallPanel->migrationInviteUsers(std::move(args.invite)); _currentGroupCallPanel->migrationInviteUsers(std::move(args.invite));
} else if (args.sharingLink && !args.linkSlug.isEmpty()) { } else if (args.sharingLink) {
_currentGroupCallPanel->migrationShowShareLink(); _currentGroupCallPanel->migrationShowShareLink();
} }
destroyCurrentCall(real, slug);
if (args.migrating) {
destroyCurrentCall(args.call.get(), args.linkSlug);
}
} }
void Instance::confirmLeaveCurrent( void Instance::confirmLeaveCurrent(
@ -428,6 +461,8 @@ void Instance::destroyGroupCall(not_null<GroupCall*> call) {
LOG(("Calls::Instance doesn't prevent quit any more.")); LOG(("Calls::Instance doesn't prevent quit any more."));
} }
Core::App().quitPreventFinished(); Core::App().quitPreventFinished();
} else if (_migratingGroupCall.get() == call) {
base::take(_migratingGroupCall);
} }
} }
@ -658,12 +693,14 @@ void Instance::handleCallUpdate(
void Instance::handleGroupCallUpdate( void Instance::handleGroupCallUpdate(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const MTPUpdate &update) { const MTPUpdate &update) {
if (_currentGroupCall const auto groupCall = _currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) { ? _currentGroupCall.get()
: _migratingGroupCall.get();
if (groupCall && (&groupCall->peer()->session() == session)) {
update.match([&](const MTPDupdateGroupCall &data) { update.match([&](const MTPDupdateGroupCall &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data); groupCall->handlePossibleCreateOrJoinResponse(data);
}, [&](const MTPDupdateGroupCallConnection &data) { }, [&](const MTPDupdateGroupCallConnection &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data); groupCall->handlePossibleCreateOrJoinResponse(data);
}, [](const auto &) { }, [](const auto &) {
}); });
} }
@ -692,10 +729,8 @@ void Instance::handleGroupCallUpdate(
}); });
if (update.type() == mtpc_updateGroupCallChainBlocks) { if (update.type() == mtpc_updateGroupCallChainBlocks) {
const auto existing = session->data().groupCall(callId); const auto existing = session->data().groupCall(callId);
if (existing if (existing && groupCall && groupCall->lookupReal() == existing) {
&& _currentGroupCall groupCall->handleUpdate(update);
&& _currentGroupCall->lookupReal() == existing) {
_currentGroupCall->handleUpdate(update);
} }
} else if (const auto existing = session->data().groupCall(callId)) { } else if (const auto existing = session->data().groupCall(callId)) {
existing->enqueueUpdate(update); existing->enqueueUpdate(update);
@ -707,9 +742,11 @@ void Instance::handleGroupCallUpdate(
void Instance::applyGroupCallUpdateChecked( void Instance::applyGroupCallUpdateChecked(
not_null<Main::Session*> session, not_null<Main::Session*> session,
const MTPUpdate &update) { const MTPUpdate &update) {
if (_currentGroupCall const auto groupCall = _currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) { ? _currentGroupCall.get()
_currentGroupCall->handleUpdate(update); : _migratingGroupCall.get();
if (groupCall && (&groupCall->peer()->session() == session)) {
groupCall->handleUpdate(update);
} }
} }
@ -761,6 +798,7 @@ void Instance::destroyCurrentCall(
} }
} }
} }
base::take(_migratingGroupCall);
} }
bool Instance::hasVisiblePanel(Main::Session *session) const { bool Instance::hasVisiblePanel(Main::Session *session) const {

View file

@ -86,6 +86,9 @@ public:
not_null<PeerData*> peer, not_null<PeerData*> peer,
StartGroupCallArgs args); StartGroupCallArgs args);
void startOrJoinConferenceCall(StartConferenceInfo args); void startOrJoinConferenceCall(StartConferenceInfo args);
void migratedConferenceReady(
not_null<GroupCall*> call,
StartConferenceInfo args);
void showStartWithRtmp( void showStartWithRtmp(
std::shared_ptr<Ui::Show> show, std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer); not_null<PeerData*> peer);
@ -200,6 +203,7 @@ private:
std::unique_ptr<Panel> _currentCallPanel; std::unique_ptr<Panel> _currentCallPanel;
std::unique_ptr<GroupCall> _currentGroupCall; std::unique_ptr<GroupCall> _currentGroupCall;
std::unique_ptr<GroupCall> _migratingGroupCall;
rpl::event_stream<GroupCall*> _currentGroupCallChanges; rpl::event_stream<GroupCall*> _currentGroupCallChanges;
std::unique_ptr<Group::Panel> _currentGroupCallPanel; std::unique_ptr<Group::Panel> _currentGroupCallPanel;

View file

@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_invite_controller.h" #include "calls/group/calls_group_invite_controller.h"
#include "calls/ui/calls_device_menu.h" #include "calls/ui/calls_device_menu.h"
#include "calls/calls_emoji_fingerprint.h" #include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_instance.h"
#include "calls/calls_signal_bars.h" #include "calls/calls_signal_bars.h"
#include "calls/calls_userpic.h" #include "calls/calls_userpic.h"
#include "calls/calls_video_bubble.h" #include "calls/calls_video_bubble.h"
@ -359,20 +360,16 @@ void Panel::initControls() {
} }
*creating = true; *creating = true;
const auto sharingLink = users.empty(); const auto sharingLink = users.empty();
Group::MakeConferenceCall({ Core::App().calls().startOrJoinConferenceCall({
.show = sessionShow(), .show = sessionShow(),
.finished = [=](bool) { *creating = false; }, .invite = std::move(users),
.joining = true, .sharingLink = sharingLink,
.info = { .migrating = true,
.invite = std::move(users), .muted = call->muted(),
.sharingLink = sharingLink, .videoCapture = (call->isSharingVideo()
.migrating = true, ? call->peekVideoCapture()
.muted = call->muted(), : nullptr),
.videoCapture = (call->isSharingVideo() .videoCaptureScreenId = call->screenSharingDeviceId(),
? call->peekVideoCapture()
: nullptr),
.videoCaptureScreenId = call->screenSharingDeviceId(),
},
}); });
}; };
const auto invite = crl::guard(call, [=]( const auto invite = crl::guard(call, [=](

View file

@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "calls/group/calls_group_call.h" #include "calls/group/calls_group_call.h"
#include "calls/group/calls_group_common.h" #include "calls/group/calls_group_common.h"
#include "calls/calls_instance.h"
#include "main/session/session_show.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "api/api_send_progress.h" #include "api/api_send_progress.h"
@ -578,9 +580,11 @@ GroupCall::GroupCall(
not_null<Delegate*> delegate, not_null<Delegate*> delegate,
StartConferenceInfo info) StartConferenceInfo info)
: GroupCall(delegate, Group::JoinInfo{ : GroupCall(delegate, Group::JoinInfo{
.peer = info.call->peer(), .peer = info.call ? info.call->peer() : info.show->session().user(),
.joinAs = info.call->peer(), .joinAs = info.call ? info.call->peer() : info.show->session().user(),
}, info, info.call->input()) { }, info, info.call
? info.call->input()
: MTP_inputGroupCall(MTP_long(0), MTP_long(0))) {
} }
GroupCall::GroupCall( GroupCall::GroupCall(
@ -590,7 +594,6 @@ GroupCall::GroupCall(
const MTPInputGroupCall &inputCall) const MTPInputGroupCall &inputCall)
: _delegate(delegate) : _delegate(delegate)
, _conferenceCall(std::move(conference.call)) , _conferenceCall(std::move(conference.call))
, _e2e(std::move(conference.e2e))
, _peer(join.peer) , _peer(join.peer)
, _history(_peer->owner().history(_peer)) , _history(_peer->owner().history(_peer))
, _api(&_peer->session().mtp()) , _api(&_peer->session().mtp())
@ -602,7 +605,6 @@ GroupCall::GroupCall(
, _rtmpUrl(join.rtmpInfo.url) , _rtmpUrl(join.rtmpInfo.url)
, _rtmpKey(join.rtmpInfo.key) , _rtmpKey(join.rtmpInfo.key)
, _canManage(Data::CanManageGroupCallValue(_peer)) , _canManage(Data::CanManageGroupCallValue(_peer))
, _id(inputCall.c_inputGroupCall().vid().v)
, _scheduleDate(join.scheduleDate) , _scheduleDate(join.scheduleDate)
, _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _lastSpokeCheckTimer([=] { checkLastSpoke(); })
, _checkJoinedTimer([=] { checkJoined(); }) , _checkJoinedTimer([=] { checkJoined(); })
@ -627,6 +629,8 @@ GroupCall::GroupCall(
, _listenersHidden(join.rtmp) , _listenersHidden(join.rtmp)
, _rtmp(join.rtmp) , _rtmp(join.rtmp)
, _rtmpVolume(Group::kDefaultVolume) { , _rtmpVolume(Group::kDefaultVolume) {
applyInputCall(inputCall);
_muted.value( _muted.value(
) | rpl::combine_previous( ) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState previous, MuteState state) { ) | rpl::start_with_next([=](MuteState previous, MuteState state) {
@ -658,7 +662,7 @@ GroupCall::GroupCall(
if (!canManage() && real->joinMuted()) { if (!canManage() && real->joinMuted()) {
_muted = MuteState::ForceMuted; _muted = MuteState::ForceMuted;
} }
} else { } else if (!conference.migrating) {
_peer->session().changes().peerFlagsValue( _peer->session().changes().peerFlagsValue(
_peer, _peer,
Data::PeerUpdate::Flag::GroupCall Data::PeerUpdate::Flag::GroupCall
@ -678,19 +682,19 @@ GroupCall::GroupCall(
setupMediaDevices(); setupMediaDevices();
setupOutgoingVideo(); setupOutgoingVideo();
if (_conferenceCall) { if (_conferenceCall || conference.migrating) {
setupConferenceCall(); setupConference();
if (conference.migrating) { }
if (!conference.muted) { if (conference.migrating) {
setMuted(MuteState::Active); if (!conference.muted) {
} setMuted(MuteState::Active);
_migratedConferenceInfo = std::make_shared<StartConferenceInfo>(
std::move(conference));
} }
_migratedConferenceInfo = std::make_shared<StartConferenceInfo>(
std::move(conference));
} }
if (_id) { if (_id || (!_conferenceCall && _migratedConferenceInfo)) {
this->join(inputCall); initialJoin();
} else { } else {
start(join.scheduleDate, join.rtmp); start(join.scheduleDate, join.rtmp);
} }
@ -703,6 +707,7 @@ void GroupCall::processMigration(StartConferenceInfo conference) {
if (!conference.videoCapture) { if (!conference.videoCapture) {
return; return;
} }
fillActiveVideoEndpoints();
const auto weak = base::make_weak(this); const auto weak = base::make_weak(this);
if (!conference.videoCaptureScreenId.isEmpty()) { if (!conference.videoCaptureScreenId.isEmpty()) {
_screenCapture = std::move(conference.videoCapture); _screenCapture = std::move(conference.videoCapture);
@ -741,7 +746,7 @@ GroupCall::~GroupCall() {
} }
} }
void GroupCall::setupConferenceCall() { void GroupCall::setupConference() {
if (!_e2e) { if (!_e2e) {
_e2e = std::make_shared<TdE2E::Call>( _e2e = std::make_shared<TdE2E::Call>(
TdE2E::MakeUserId(_peer->session().user())); TdE2E::MakeUserId(_peer->session().user()));
@ -755,10 +760,24 @@ void GroupCall::setupConferenceCall() {
sendOutboundBlock(std::move(block)); sendOutboundBlock(std::move(block));
}, _lifetime); }, _lifetime);
_e2e->failures() | rpl::start_with_next([=] {
LOG(("TdE2E: Got failure!"));
hangup();
}, _lifetime);
if (_conferenceCall) {
setupConferenceCall();
}
}
void GroupCall::setupConferenceCall() {
Expects(_conferenceCall != nullptr && _e2e != nullptr);
_conferenceCall->staleParticipantIds( _conferenceCall->staleParticipantIds(
) | rpl::start_with_next([=](const base::flat_set<UserId> &staleIds) { ) | rpl::start_with_next([=](const base::flat_set<UserId> &staleIds) {
removeConferenceParticipants(staleIds, true); removeConferenceParticipants(staleIds, true);
}, _lifetime); }, _lifetime);
_e2e->participantsSetValue( _e2e->participantsSetValue(
) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) { ) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) {
auto users = base::flat_set<UserId>(); auto users = base::flat_set<UserId>();
@ -768,11 +787,6 @@ void GroupCall::setupConferenceCall() {
} }
_conferenceCall->setParticipantsWithAccess(std::move(users)); _conferenceCall->setParticipantsWithAccess(std::move(users));
}, _lifetime); }, _lifetime);
_e2e->failures() | rpl::start_with_next([=] {
LOG(("TdE2E: Got failure!"));
hangup();
}, _lifetime);
} }
void GroupCall::removeConferenceParticipants( void GroupCall::removeConferenceParticipants(
@ -917,7 +931,7 @@ void GroupCall::setScheduledDate(TimeId date) {
const auto was = _scheduleDate; const auto was = _scheduleDate;
_scheduleDate = date; _scheduleDate = date;
if (was && !date) { if (was && !date) {
join(inputCall()); initialJoin();
} }
} }
@ -1171,7 +1185,7 @@ bool GroupCall::rtmp() const {
} }
bool GroupCall::conference() const { bool GroupCall::conference() const {
return _conferenceCall != nullptr; return _conferenceCall || _migratedConferenceInfo;
} }
bool GroupCall::listenersHidden() const { bool GroupCall::listenersHidden() const {
@ -1234,31 +1248,40 @@ void GroupCall::start(TimeId scheduleDate, bool rtmp) {
MTPstring(), // title MTPstring(), // title
MTP_int(scheduleDate) MTP_int(scheduleDate)
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
_createRequestId = 0;
_reloadedStaleCall = true; _reloadedStaleCall = true;
_acceptFields = true; _acceptFields = true;
_peer->session().api().applyUpdates(result); _peer->session().api().applyUpdates(result);
_acceptFields = false; _acceptFields = false;
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_createRequestId = 0;
LOG(("Call Error: Could not create, error: %1" LOG(("Call Error: Could not create, error: %1"
).arg(error.type())); ).arg(error.type()));
hangup(); hangup();
}).send(); }).send();
} }
void GroupCall::join(const MTPInputGroupCall &inputCall) { void GroupCall::applyInputCall(const MTPInputGroupCall &inputCall) {
inputCall.match([&](const MTPDinputGroupCall &data) { inputCall.match([&](const MTPDinputGroupCall &data) {
_id = data.vid().v; _id = data.vid().v;
_accessHash = data.vaccess_hash().v; _accessHash = data.vaccess_hash().v;
}, [&](const auto &) { }, [&](const auto &) {
Unexpected("slug/msg in GroupCall::join."); Unexpected("slug/msg in GroupCall::join.");
}); });
setState(_scheduleDate ? State::Waiting : State::Joining); }
void GroupCall::initialJoin() {
setState(_scheduleDate ? State::Waiting : State::Joining);
if (_scheduleDate) { if (_scheduleDate) {
return; return;
} }
rejoin(); rejoin();
if (_id) {
initialJoinRequested();
}
}
void GroupCall::initialJoinRequested() {
using Update = Data::GroupCall::ParticipantUpdate; using Update = Data::GroupCall::ParticipantUpdate;
const auto real = lookupReal(); const auto real = lookupReal();
Assert(real != nullptr); Assert(real != nullptr);
@ -1283,10 +1306,10 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
_peer->session().updates().addActiveChat( _peer->session().updates().addActiveChat(
_peerStream.events_starting_with_copy(_peer)); _peerStream.events_starting_with_copy(_peer));
_canManage = Data::CanManageGroupCallValue(_peer); _canManage = Data::CanManageGroupCallValue(_peer);
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) { SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> peer) {
_peer = group; _peer = peer;
_canManage = Data::CanManageGroupCallValue(_peer); _canManage = Data::CanManageGroupCallValue(_peer);
_peerStream.fire_copy(group); _peerStream.fire_copy(peer);
}); });
} }
@ -1499,7 +1522,7 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
&& state() != State::Joined && state() != State::Joined
&& state() != State::Connecting) { && state() != State::Connecting) {
return; return;
} else if (_joinState.action != JoinAction::None) { } else if (_joinState.action != JoinAction::None || _createRequestId) {
return; return;
} }
@ -1529,7 +1552,11 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
}; };
LOG(("Call Info: Join payload received, joining with ssrc: %1." LOG(("Call Info: Join payload received, joining with ssrc: %1."
).arg(_joinState.payload.ssrc)); ).arg(_joinState.payload.ssrc));
sendJoinRequest(); if (!_conferenceCall && _migratedConferenceInfo) {
startConference();
} else {
sendJoinRequest();
}
}); });
}); });
} }
@ -1540,7 +1567,7 @@ void GroupCall::sendJoinRequest() {
checkNextJoinAction(); checkNextJoinAction();
return; return;
} }
auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray(); const auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray();
if (_e2e && joinBlock.isEmpty()) { if (_e2e && joinBlock.isEmpty()) {
_joinState.finish(); _joinState.finish();
LOG(("Call Error: Could not generate join block.")); LOG(("Call Error: Could not generate join block."));
@ -1554,12 +1581,8 @@ void GroupCall::sendJoinRequest() {
const auto flags = (wasMuteState != MuteState::Active const auto flags = (wasMuteState != MuteState::Active
? Flag::f_muted ? Flag::f_muted
: Flag(0)) : Flag(0))
| (_joinHash.isEmpty() | (_joinHash.isEmpty() ? Flag(0) : Flag::f_invite_hash)
? Flag(0) | (wasVideoStopped ? Flag::f_video_stopped : Flag(0))
: Flag::f_invite_hash)
| (wasVideoStopped
? Flag::f_video_stopped
: Flag(0))
| (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag()); | (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag());
_api.request(MTPphone_JoinGroupCall( _api.request(MTPphone_JoinGroupCall(
MTP_flags(flags), MTP_flags(flags),
@ -1570,74 +1593,15 @@ void GroupCall::sendJoinRequest() {
MTP_bytes(joinBlock), MTP_bytes(joinBlock),
MTP_dataJSON(MTP_bytes(_joinState.payload.json)) MTP_dataJSON(MTP_bytes(_joinState.payload.json))
)).done([=]( )).done([=](
const MTPUpdates &updates, const MTPUpdates &result,
const MTP::Response &response) { const MTP::Response &response) {
_serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId); joinDone(
_serverTimeMsGotAt = crl::now(); TimestampInMsFromMsgId(response.outerMsgId),
result,
_joinState.finish(_joinState.payload.ssrc); wasMuteState,
_mySsrcs.emplace(_joinState.ssrc); wasVideoStopped);
setState((_instanceState.current()
== InstanceState::Disconnected)
? State::Connecting
: State::Joined);
applyMeInCallLocally();
maybeSendMutedUpdate(wasMuteState);
_peer->session().api().applyUpdates(updates);
applyQueuedSelfUpdates();
checkFirstTimeJoined();
_screenJoinState.nextActionPending = true;
checkNextJoinAction();
if (wasVideoStopped == isSharingCamera()) {
sendSelfUpdate(SendUpdateType::CameraStopped);
}
if (isCameraPaused()) {
sendSelfUpdate(SendUpdateType::CameraPaused);
}
sendPendingSelfUpdates();
if (!_reloadedStaleCall
&& _state.current() != State::Joining) {
if (const auto real = lookupReal()) {
_reloadedStaleCall = true;
real->reloadIfStale();
}
}
if (_e2e) {
_e2e->joined();
if (!_pendingOutboundBlock.isEmpty()) {
sendOutboundBlock(base::take(_pendingOutboundBlock));
}
}
if (const auto once = base::take(_migratedConferenceInfo)) {
processMigration(*once);
}
for (const auto &callback : base::take(_rejoinedCallbacks)) {
callback();
}
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
const auto type = error.type(); joinFail(error.type());
if (_e2e) {
if (type == u"BLOCK_INVALID"_q
|| type.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) {
refreshLastBlockAndJoin();
return;
}
}
_joinState.finish();
LOG(("Call Error: Could not join, error: %1").arg(type));
if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") {
rejoin();
return;
}
hangup();
Ui::Toast::Show((type == u"GROUPCALL_FORBIDDEN"_q
|| type == u"GROUPCALL_INVALID"_q)
? tr::lng_confcall_not_accessible(tr::now)
: type);
}).send(); }).send();
} }
@ -1685,6 +1649,144 @@ void GroupCall::refreshLastBlockAndJoin() {
}).send(); }).send();
} }
void GroupCall::startConference() {
Expects(_e2e != nullptr && _migratedConferenceInfo != nullptr);
const auto joinBlock = _e2e->makeJoinBlock().data;
Assert(!joinBlock.isEmpty());
const auto wasMuteState = muted();
const auto wasVideoStopped = !isSharingCamera();
using Flag = MTPphone_CreateConferenceCall::Flag;
const auto flags = Flag::f_join
| Flag::f_public_key
| Flag::f_block
| Flag::f_params
| ((wasMuteState != MuteState::Active) ? Flag::f_muted : Flag(0))
| (wasVideoStopped ? Flag::f_video_stopped : Flag(0));
_createRequestId = _api.request(MTPphone_CreateConferenceCall(
MTP_flags(flags),
MTP_int(base::RandomValue<int32>()),
TdE2E::PublicKeyToMTP(_e2e->myKey()),
MTP_bytes(joinBlock),
MTP_dataJSON(MTP_bytes(_joinState.payload.json))
)).done([=](
const MTPUpdates &result,
const MTP::Response &response) {
_createRequestId = 0;
_conferenceCall = _peer->owner().sharedConferenceCallFind(result);
if (!_conferenceCall) {
joinFail(u"Call not found!"_q);
return;
}
applyInputCall(_conferenceCall->input());
initialJoinRequested();
joinDone(
TimestampInMsFromMsgId(response.outerMsgId),
result,
wasMuteState,
wasVideoStopped,
true);
}).fail([=](const MTP::Error &error) {
_createRequestId = 0;
LOG(("Call Error: Could not create, error: %1"
).arg(error.type()));
hangup();
}).send();
}
void GroupCall::joinDone(
int64 serverTimeMs,
const MTPUpdates &result,
MuteState wasMuteState,
bool wasVideoStopped,
bool justCreated) {
Expects(!justCreated || _migratedConferenceInfo != nullptr);
_serverTimeMs = serverTimeMs;
_serverTimeMsGotAt = crl::now();
_joinState.finish(_joinState.payload.ssrc);
_mySsrcs.emplace(_joinState.ssrc);
setState((_instanceState.current()
== InstanceState::Disconnected)
? State::Connecting
: State::Joined);
applyMeInCallLocally();
maybeSendMutedUpdate(wasMuteState);
_peer->session().api().applyUpdates(result);
if (justCreated) {
subscribeToReal(_conferenceCall.get());
setupConferenceCall();
_conferenceLinkSlug = Group::ExtractConferenceSlug(
_conferenceCall->conferenceInviteLink());
Core::App().calls().migratedConferenceReady(
this,
*_migratedConferenceInfo);
}
applyQueuedSelfUpdates();
checkFirstTimeJoined();
_screenJoinState.nextActionPending = true;
checkNextJoinAction();
if (wasVideoStopped == isSharingCamera()) {
sendSelfUpdate(SendUpdateType::CameraStopped);
}
if (isCameraPaused()) {
sendSelfUpdate(SendUpdateType::CameraPaused);
}
sendPendingSelfUpdates();
if (!_reloadedStaleCall
&& _state.current() != State::Joining) {
if (const auto real = lookupReal()) {
_reloadedStaleCall = true;
real->reloadIfStale();
}
}
if (_e2e) {
_e2e->joined();
if (!_pendingOutboundBlock.isEmpty()) {
sendOutboundBlock(base::take(_pendingOutboundBlock));
}
}
if (const auto once = base::take(_migratedConferenceInfo)) {
processMigration(*once);
}
for (const auto &callback : base::take(_rejoinedCallbacks)) {
callback();
}
}
void GroupCall::joinFail(const QString &error) {
if (_e2e) {
if (error == u"BLOCK_INVALID"_q
|| error.startsWith(u"CONF_WRITE_CHAIN_INVALID"_q)) {
if (_id) {
refreshLastBlockAndJoin();
} else {
hangup();
}
return;
}
}
_joinState.finish();
LOG(("Call Error: Could not join, error: %1").arg(error));
if (_id && error == u"GROUPCALL_SSRC_DUPLICATE_MUCH") {
rejoin();
return;
}
hangup();
Ui::Toast::Show((error == u"GROUPCALL_FORBIDDEN"_q
|| error == u"GROUPCALL_INVALID"_q)
? tr::lng_confcall_not_accessible(tr::now)
: error);
}
void GroupCall::requestSubchainBlocks(int subchain, int height) { void GroupCall::requestSubchainBlocks(int subchain, int height) {
Expects(subchain >= 0 && subchain < kSubChainsCount); Expects(subchain >= 0 && subchain < kSubChainsCount);
@ -2168,7 +2270,8 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
} else { } else {
Unexpected("Peer type in GroupCall::join."); Unexpected("Peer type in GroupCall::join.");
} }
join(input); applyInputCall(input);
initialJoin();
} }
return; return;
} else if (_id != data.vid().v || !_instance) { } else if (_id != data.vid().v || !_instance) {
@ -3768,7 +3871,6 @@ void GroupCall::editParticipant(
}).send(); }).send();
} }
void GroupCall::inviteToConference( void GroupCall::inviteToConference(
InviteRequest request, InviteRequest request,
Fn<not_null<InviteResult*>()> resultAddress, Fn<not_null<InviteResult*>()> resultAddress,

View file

@ -263,12 +263,15 @@ public:
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const; [[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
[[nodiscard]] rpl::producer<QByteArray> emojiHashValue() const; [[nodiscard]] rpl::producer<QByteArray> emojiHashValue() const;
void applyInputCall(const MTPInputGroupCall &inputCall);
void startConference();
void start(TimeId scheduleDate, bool rtmp); void start(TimeId scheduleDate, bool rtmp);
void hangup(); void hangup();
void discard(); void discard();
void rejoinAs(Group::JoinInfo info); void rejoinAs(Group::JoinInfo info);
void rejoinWithHash(const QString &hash); void rejoinWithHash(const QString &hash);
void join(const MTPInputGroupCall &inputCall); void initialJoin();
void initialJoinRequested();
void handleUpdate(const MTPUpdate &update); void handleUpdate(const MTPUpdate &update);
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data); void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
void handlePossibleCreateOrJoinResponse( void handlePossibleCreateOrJoinResponse(
@ -292,6 +295,14 @@ public:
bool emitShareScreenError(); bool emitShareScreenError();
bool emitShareCameraError(); bool emitShareCameraError();
void joinDone(
int64 serverTimeMs,
const MTPUpdates &result,
MuteState wasMuteState,
bool wasVideoStopped,
bool justCreated = false);
void joinFail(const QString &error);
[[nodiscard]] rpl::producer<Group::Error> errors() const { [[nodiscard]] rpl::producer<Group::Error> errors() const {
return _errors.events(); return _errors.events();
} }
@ -610,6 +621,7 @@ private:
void setupMediaDevices(); void setupMediaDevices();
void setupOutgoingVideo(); void setupOutgoingVideo();
void setupConference();
void setupConferenceCall(); void setupConferenceCall();
void setScreenEndpoint(std::string endpoint); void setScreenEndpoint(std::string endpoint);
void setCameraEndpoint(std::string endpoint); void setCameraEndpoint(std::string endpoint);
@ -635,7 +647,7 @@ private:
[[nodiscard]] MTPInputGroupCall inputCallSafe() const; [[nodiscard]] MTPInputGroupCall inputCallSafe() const;
const not_null<Delegate*> _delegate; const not_null<Delegate*> _delegate;
const std::shared_ptr<Data::GroupCall> _conferenceCall; std::shared_ptr<Data::GroupCall> _conferenceCall;
std::shared_ptr<TdE2E::Call> _e2e; std::shared_ptr<TdE2E::Call> _e2e;
QByteArray _pendingOutboundBlock; QByteArray _pendingOutboundBlock;
std::shared_ptr<StartConferenceInfo> _migratedConferenceInfo; std::shared_ptr<StartConferenceInfo> _migratedConferenceInfo;

View file

@ -18,6 +18,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_group_call.h" #include "data/data_group_call.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "info/bot/starref/info_bot_starref_common.h" #include "info/bot/starref/info_bot_starref_common.h"
#include "tde2e/tde2e_api.h"
#include "tde2e/tde2e_integration.h"
#include "ui/boxes/boost_box.h" #include "ui/boxes/boost_box.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h" #include "ui/widgets/labels.h"
@ -41,24 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QClipboard> #include <QtGui/QClipboard>
namespace Calls::Group { namespace Calls::Group {
namespace {
[[nodiscard]] QString ExtractConferenceSlug(const QString &link) {
const auto local = Core::TryConvertUrlToLocal(link);
const auto parts1 = QStringView(local).split('#');
if (!parts1.isEmpty()) {
const auto parts2 = parts1.front().split('&');
if (!parts2.isEmpty()) {
const auto parts3 = parts2.front().split(u"slug="_q);
if (parts3.size() > 1) {
return parts3.back().toString();
}
}
}
return QString();
}
} // namespace
object_ptr<Ui::GenericBox> ScreenSharingPrivacyRequestBox() { object_ptr<Ui::GenericBox> ScreenSharingPrivacyRequestBox() {
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
@ -468,32 +452,13 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) {
MTPbytes(), // block MTPbytes(), // block
MTPDataJSON() // params MTPDataJSON() // params
)).done([=](const MTPUpdates &result) { )).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result); auto call = session->data().sharedConferenceCallFind(result);
const auto updates = result.match([&](const MTPDupdates &data) { if (!call) {
return &data.vupdates().v;
}, [&](const MTPDupdatesCombined &data) {
return &data.vupdates().v;
}, [](const auto &) {
return (const QVector<MTPUpdate>*)nullptr;
});
if (!updates) {
fail(u"Call not found!"_q); fail(u"Call not found!"_q);
return; return;
} }
auto call = std::shared_ptr<Data::GroupCall>(); session->api().applyUpdates(result);
for (const auto &update : *updates) {
update.match([&](const MTPDupdateGroupCall &data) {
data.vcall().match([&](const auto &data) {
call = session->data().sharedConferenceCall(
data.vid().v,
data.vaccess_hash().v);
call->enqueueUpdate(update);
});
}, [](const auto &) {});
if (call) {
break;
}
}
const auto link = call ? call->conferenceInviteLink() : QString(); const auto link = call ? call->conferenceInviteLink() : QString();
if (link.isEmpty()) { if (link.isEmpty()) {
fail(u"Call link not found!"_q); fail(u"Call link not found!"_q);
@ -521,4 +486,19 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) {
}).send(); }).send();
} }
QString ExtractConferenceSlug(const QString &link) {
const auto local = Core::TryConvertUrlToLocal(link);
const auto parts1 = QStringView(local).split('#');
if (!parts1.isEmpty()) {
const auto parts2 = parts1.front().split('&');
if (!parts2.isEmpty()) {
const auto parts3 = parts2.front().split(u"slug="_q);
if (parts3.size() > 1) {
return parts3.back().toString();
}
}
}
return QString();
}
} // namespace Calls::Group } // namespace Calls::Group

View file

@ -64,8 +64,8 @@ struct InviteResult {
}; };
struct StartConferenceInfo { struct StartConferenceInfo {
std::shared_ptr<Main::SessionShow> show;
std::shared_ptr<Data::GroupCall> call; std::shared_ptr<Data::GroupCall> call;
std::shared_ptr<TdE2E::Call> e2e;
QString linkSlug; QString linkSlug;
MsgId joinMessageId; MsgId joinMessageId;
std::vector<InviteRequest> invite; std::vector<InviteRequest> invite;
@ -195,4 +195,6 @@ struct ConferenceFactoryArgs {
}; };
void MakeConferenceCall(ConferenceFactoryArgs &&args); void MakeConferenceCall(ConferenceFactoryArgs &&args);
[[nodiscard]] QString ExtractConferenceSlug(const QString &link);
} // namespace Calls::Group } // namespace Calls::Group

View file

@ -376,6 +376,8 @@ void Panel::initWindow() {
_window->setControlsStyle(st::groupCallTitle); _window->setControlsStyle(st::groupCallTitle);
_window->togglePowerSaveBlocker(true); _window->togglePowerSaveBlocker(true);
uiShow()->hideLayer(anim::type::instant);
} }
void Panel::initWidget() { void Panel::initWidget() {
@ -927,7 +929,6 @@ Fn<void()> Panel::shareConferenceLinkCallback() {
} }
void Panel::migrationShowShareLink() { void Panel::migrationShowShareLink() {
uiShow()->hideLayer(anim::type::instant);
ShowConferenceCallLinkBox( ShowConferenceCallLinkBox(
sessionShow(), sessionShow(),
_call->conferenceCall(), _call->conferenceCall(),

View file

@ -374,6 +374,7 @@ auto GroupCall::staleParticipantIds() const
} }
void GroupCall::enqueueUpdate(const MTPUpdate &update) { void GroupCall::enqueueUpdate(const MTPUpdate &update) {
const auto initial = !_version;
update.match([&](const MTPDupdateGroupCall &updateData) { update.match([&](const MTPDupdateGroupCall &updateData) {
updateData.vcall().match([&](const MTPDgroupCall &data) { updateData.vcall().match([&](const MTPDgroupCall &data) {
const auto version = data.vversion().v; const auto version = data.vversion().v;
@ -427,7 +428,7 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
}, [](const auto &) { }, [](const auto &) {
Unexpected("Type in GroupCall::enqueueUpdate."); Unexpected("Type in GroupCall::enqueueUpdate.");
}); });
processQueuedUpdates(); processQueuedUpdates(initial);
} }
void GroupCall::discard(const MTPDgroupCallDiscarded &data) { void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
@ -562,7 +563,7 @@ void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) {
Core::App().calls().applyGroupCallUpdateChecked(&session(), update); Core::App().calls().applyGroupCallUpdateChecked(&session(), update);
} }
void GroupCall::processQueuedUpdates() { void GroupCall::processQueuedUpdates(bool initial) {
if (!_version || _applyingQueuedUpdates) { if (!_version || _applyingQueuedUpdates) {
return; return;
} }
@ -574,7 +575,13 @@ void GroupCall::processQueuedUpdates() {
const auto type = entry.first.second; const auto type = entry.first.second;
const auto incremented = (type == QueuedType::VersionedParticipant); const auto incremented = (type == QueuedType::VersionedParticipant);
if ((version < _version) if ((version < _version)
|| (version == _version && incremented)) { || (version == _version && incremented && !initial)) {
// There is a case for a new conference call we receive:
// - updateGroupCall, version = 2
// - updateGroupCallParticipants, version = 2, versioned
// In case we were joining together with creation,
// in that case we don't want to skip the participants update,
// so we pass the `initial` flag specifically for that case.
_queuedUpdates.erase(_queuedUpdates.begin()); _queuedUpdates.erase(_queuedUpdates.begin());
} else if (version == _version } else if (version == _version
|| (version == _version + 1 && incremented)) { || (version == _version + 1 && incremented)) {

View file

@ -215,7 +215,7 @@ private:
void applyEnqueuedUpdate(const MTPUpdate &update); void applyEnqueuedUpdate(const MTPUpdate &update);
void setServerParticipantsCount(int count); void setServerParticipantsCount(int count);
void computeParticipantsCount(); void computeParticipantsCount();
void processQueuedUpdates(); void processQueuedUpdates(bool initial = false);
void processFullCallUsersChats(const MTPphone_GroupCall &call); void processFullCallUsersChats(const MTPphone_GroupCall &call);
void processFullCallFields(const MTPphone_GroupCall &call); void processFullCallFields(const MTPphone_GroupCall &call);
[[nodiscard]] bool requestParticipantsAfterReload( [[nodiscard]] bool requestParticipantsAfterReload(

View file

@ -1159,6 +1159,36 @@ std::shared_ptr<GroupCall> Session::sharedConferenceCall(
return result; return result;
} }
std::shared_ptr<GroupCall> Session::sharedConferenceCallFind(
const MTPUpdates &response) {
const auto list = response.match([&](const MTPDupdates &data) {
return &data.vupdates().v;
}, [&](const MTPDupdatesCombined &data) {
return &data.vupdates().v;
}, [](const auto &) {
return (const QVector<MTPUpdate>*)nullptr;
});
const auto empty = std::shared_ptr<GroupCall>();
if (!list) {
return empty;
}
for (const auto &update : *list) {
const auto call = update.match([&](const MTPDupdateGroupCall &data) {
return data.vcall().match([&](const MTPDgroupCall &data) {
return data.is_conference()
? sharedConferenceCall(
data.vid().v,
data.vaccess_hash().v)
: nullptr;
}, [&](const auto &) { return empty; });
}, [&](const auto &) { return empty; });
if (call) {
return call;
}
}
return empty;
}
void Session::watchForOffline(not_null<UserData*> user, TimeId now) { void Session::watchForOffline(not_null<UserData*> user, TimeId now) {
if (!now) { if (!now) {
now = base::unixtime::now(); now = base::unixtime::now();

View file

@ -235,6 +235,8 @@ public:
[[nodiscard]] std::shared_ptr<GroupCall> sharedConferenceCall( [[nodiscard]] std::shared_ptr<GroupCall> sharedConferenceCall(
CallId id, CallId id,
uint64 accessHash); uint64 accessHash);
[[nodiscard]] std::shared_ptr<GroupCall> sharedConferenceCallFind(
const MTPUpdates &response);
void watchForOffline(not_null<UserData*> user, TimeId now = 0); void watchForOffline(not_null<UserData*> user, TimeId now = 0);
void maybeStopWatchForOffline(not_null<UserData*> user); void maybeStopWatchForOffline(not_null<UserData*> user);

View file

@ -882,7 +882,7 @@ void SessionNavigation::resolveConferenceCall(
data.vaccess_hash().v); data.vaccess_hash().v);
call->processFullCall(result); call->processFullCall(result);
const auto join = [=](Fn<void()> close) { const auto join = [=](Fn<void()> close) {
const auto &appConfig = call->peer()->session().appConfig(); const auto &appConfig = call->session().appConfig();
const auto conferenceLimit = appConfig.confcallSizeLimit(); const auto conferenceLimit = appConfig.confcallSizeLimit();
if (call->fullCount() >= conferenceLimit) { if (call->fullCount() >= conferenceLimit) {
showToast(tr::lng_confcall_participants_limit(tr::now)); showToast(tr::lng_confcall_participants_limit(tr::now));