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 "core/application.h"
#include "core/core_settings.h"
#include "main/session/session_show.h"
#include "main/main_session.h"
#include "main/main_account.h"
#include "apiwrap.h"
@ -237,14 +238,20 @@ void Instance::startOrJoinGroupCall(
}
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()
: ConferencePanelMigration();
if (!args.migrating) {
if (args.call && !args.migrating) {
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);
const auto raw = call.get();
@ -253,20 +260,46 @@ void Instance::startOrJoinConferenceCall(StartConferenceInfo args) {
destroyGroupCall(raw);
}, 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>(
raw,
call,
migrationInfo);
_currentGroupCall = std::move(call);
_currentGroupCallChanges.fire_copy(raw);
_currentGroupCall = std::move(_migratingGroupCall);
_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()) {
_currentGroupCallPanel->migrationInviteUsers(std::move(args.invite));
} else if (args.sharingLink && !args.linkSlug.isEmpty()) {
} else if (args.sharingLink) {
_currentGroupCallPanel->migrationShowShareLink();
}
if (args.migrating) {
destroyCurrentCall(args.call.get(), args.linkSlug);
}
destroyCurrentCall(real, slug);
}
void Instance::confirmLeaveCurrent(
@ -428,6 +461,8 @@ void Instance::destroyGroupCall(not_null<GroupCall*> call) {
LOG(("Calls::Instance doesn't prevent quit any more."));
}
Core::App().quitPreventFinished();
} else if (_migratingGroupCall.get() == call) {
base::take(_migratingGroupCall);
}
}
@ -658,12 +693,14 @@ void Instance::handleCallUpdate(
void Instance::handleGroupCallUpdate(
not_null<Main::Session*> session,
const MTPUpdate &update) {
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) {
const auto groupCall = _currentGroupCall
? _currentGroupCall.get()
: _migratingGroupCall.get();
if (groupCall && (&groupCall->peer()->session() == session)) {
update.match([&](const MTPDupdateGroupCall &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
groupCall->handlePossibleCreateOrJoinResponse(data);
}, [&](const MTPDupdateGroupCallConnection &data) {
_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
groupCall->handlePossibleCreateOrJoinResponse(data);
}, [](const auto &) {
});
}
@ -692,10 +729,8 @@ void Instance::handleGroupCallUpdate(
});
if (update.type() == mtpc_updateGroupCallChainBlocks) {
const auto existing = session->data().groupCall(callId);
if (existing
&& _currentGroupCall
&& _currentGroupCall->lookupReal() == existing) {
_currentGroupCall->handleUpdate(update);
if (existing && groupCall && groupCall->lookupReal() == existing) {
groupCall->handleUpdate(update);
}
} else if (const auto existing = session->data().groupCall(callId)) {
existing->enqueueUpdate(update);
@ -707,9 +742,11 @@ void Instance::handleGroupCallUpdate(
void Instance::applyGroupCallUpdateChecked(
not_null<Main::Session*> session,
const MTPUpdate &update) {
if (_currentGroupCall
&& (&_currentGroupCall->peer()->session() == session)) {
_currentGroupCall->handleUpdate(update);
const auto groupCall = _currentGroupCall
? _currentGroupCall.get()
: _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 {

View file

@ -86,6 +86,9 @@ public:
not_null<PeerData*> peer,
StartGroupCallArgs args);
void startOrJoinConferenceCall(StartConferenceInfo args);
void migratedConferenceReady(
not_null<GroupCall*> call,
StartConferenceInfo args);
void showStartWithRtmp(
std::shared_ptr<Ui::Show> show,
not_null<PeerData*> peer);
@ -200,6 +203,7 @@ private:
std::unique_ptr<Panel> _currentCallPanel;
std::unique_ptr<GroupCall> _currentGroupCall;
std::unique_ptr<GroupCall> _migratingGroupCall;
rpl::event_stream<GroupCall*> _currentGroupCallChanges;
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/ui/calls_device_menu.h"
#include "calls/calls_emoji_fingerprint.h"
#include "calls/calls_instance.h"
#include "calls/calls_signal_bars.h"
#include "calls/calls_userpic.h"
#include "calls/calls_video_bubble.h"
@ -359,20 +360,16 @@ void Panel::initControls() {
}
*creating = true;
const auto sharingLink = users.empty();
Group::MakeConferenceCall({
Core::App().calls().startOrJoinConferenceCall({
.show = sessionShow(),
.finished = [=](bool) { *creating = false; },
.joining = true,
.info = {
.invite = std::move(users),
.sharingLink = sharingLink,
.migrating = true,
.muted = call->muted(),
.videoCapture = (call->isSharingVideo()
? call->peekVideoCapture()
: nullptr),
.videoCaptureScreenId = call->screenSharingDeviceId(),
},
.invite = std::move(users),
.sharingLink = sharingLink,
.migrating = true,
.muted = call->muted(),
.videoCapture = (call->isSharingVideo()
? call->peekVideoCapture()
: nullptr),
.videoCaptureScreenId = call->screenSharingDeviceId(),
});
};
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_common.h"
#include "calls/calls_instance.h"
#include "main/session/session_show.h"
#include "main/main_app_config.h"
#include "main/main_session.h"
#include "api/api_send_progress.h"
@ -578,9 +580,11 @@ GroupCall::GroupCall(
not_null<Delegate*> delegate,
StartConferenceInfo info)
: GroupCall(delegate, Group::JoinInfo{
.peer = info.call->peer(),
.joinAs = info.call->peer(),
}, info, info.call->input()) {
.peer = info.call ? info.call->peer() : info.show->session().user(),
.joinAs = info.call ? info.call->peer() : info.show->session().user(),
}, info, info.call
? info.call->input()
: MTP_inputGroupCall(MTP_long(0), MTP_long(0))) {
}
GroupCall::GroupCall(
@ -590,7 +594,6 @@ GroupCall::GroupCall(
const MTPInputGroupCall &inputCall)
: _delegate(delegate)
, _conferenceCall(std::move(conference.call))
, _e2e(std::move(conference.e2e))
, _peer(join.peer)
, _history(_peer->owner().history(_peer))
, _api(&_peer->session().mtp())
@ -602,7 +605,6 @@ GroupCall::GroupCall(
, _rtmpUrl(join.rtmpInfo.url)
, _rtmpKey(join.rtmpInfo.key)
, _canManage(Data::CanManageGroupCallValue(_peer))
, _id(inputCall.c_inputGroupCall().vid().v)
, _scheduleDate(join.scheduleDate)
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
, _checkJoinedTimer([=] { checkJoined(); })
@ -627,6 +629,8 @@ GroupCall::GroupCall(
, _listenersHidden(join.rtmp)
, _rtmp(join.rtmp)
, _rtmpVolume(Group::kDefaultVolume) {
applyInputCall(inputCall);
_muted.value(
) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState previous, MuteState state) {
@ -658,7 +662,7 @@ GroupCall::GroupCall(
if (!canManage() && real->joinMuted()) {
_muted = MuteState::ForceMuted;
}
} else {
} else if (!conference.migrating) {
_peer->session().changes().peerFlagsValue(
_peer,
Data::PeerUpdate::Flag::GroupCall
@ -678,19 +682,19 @@ GroupCall::GroupCall(
setupMediaDevices();
setupOutgoingVideo();
if (_conferenceCall) {
setupConferenceCall();
if (conference.migrating) {
if (!conference.muted) {
setMuted(MuteState::Active);
}
_migratedConferenceInfo = std::make_shared<StartConferenceInfo>(
std::move(conference));
if (_conferenceCall || conference.migrating) {
setupConference();
}
if (conference.migrating) {
if (!conference.muted) {
setMuted(MuteState::Active);
}
_migratedConferenceInfo = std::make_shared<StartConferenceInfo>(
std::move(conference));
}
if (_id) {
this->join(inputCall);
if (_id || (!_conferenceCall && _migratedConferenceInfo)) {
initialJoin();
} else {
start(join.scheduleDate, join.rtmp);
}
@ -703,6 +707,7 @@ void GroupCall::processMigration(StartConferenceInfo conference) {
if (!conference.videoCapture) {
return;
}
fillActiveVideoEndpoints();
const auto weak = base::make_weak(this);
if (!conference.videoCaptureScreenId.isEmpty()) {
_screenCapture = std::move(conference.videoCapture);
@ -741,7 +746,7 @@ GroupCall::~GroupCall() {
}
}
void GroupCall::setupConferenceCall() {
void GroupCall::setupConference() {
if (!_e2e) {
_e2e = std::make_shared<TdE2E::Call>(
TdE2E::MakeUserId(_peer->session().user()));
@ -755,10 +760,24 @@ void GroupCall::setupConferenceCall() {
sendOutboundBlock(std::move(block));
}, _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(
) | rpl::start_with_next([=](const base::flat_set<UserId> &staleIds) {
removeConferenceParticipants(staleIds, true);
}, _lifetime);
_e2e->participantsSetValue(
) | rpl::start_with_next([=](const TdE2E::ParticipantsSet &set) {
auto users = base::flat_set<UserId>();
@ -768,11 +787,6 @@ void GroupCall::setupConferenceCall() {
}
_conferenceCall->setParticipantsWithAccess(std::move(users));
}, _lifetime);
_e2e->failures() | rpl::start_with_next([=] {
LOG(("TdE2E: Got failure!"));
hangup();
}, _lifetime);
}
void GroupCall::removeConferenceParticipants(
@ -917,7 +931,7 @@ void GroupCall::setScheduledDate(TimeId date) {
const auto was = _scheduleDate;
_scheduleDate = date;
if (was && !date) {
join(inputCall());
initialJoin();
}
}
@ -1171,7 +1185,7 @@ bool GroupCall::rtmp() const {
}
bool GroupCall::conference() const {
return _conferenceCall != nullptr;
return _conferenceCall || _migratedConferenceInfo;
}
bool GroupCall::listenersHidden() const {
@ -1234,31 +1248,40 @@ void GroupCall::start(TimeId scheduleDate, bool rtmp) {
MTPstring(), // title
MTP_int(scheduleDate)
)).done([=](const MTPUpdates &result) {
_createRequestId = 0;
_reloadedStaleCall = true;
_acceptFields = true;
_peer->session().api().applyUpdates(result);
_acceptFields = false;
}).fail([=](const MTP::Error &error) {
_createRequestId = 0;
LOG(("Call Error: Could not create, error: %1"
).arg(error.type()));
hangup();
}).send();
}
void GroupCall::join(const MTPInputGroupCall &inputCall) {
void GroupCall::applyInputCall(const MTPInputGroupCall &inputCall) {
inputCall.match([&](const MTPDinputGroupCall &data) {
_id = data.vid().v;
_accessHash = data.vaccess_hash().v;
}, [&](const auto &) {
Unexpected("slug/msg in GroupCall::join.");
});
setState(_scheduleDate ? State::Waiting : State::Joining);
}
void GroupCall::initialJoin() {
setState(_scheduleDate ? State::Waiting : State::Joining);
if (_scheduleDate) {
return;
}
rejoin();
if (_id) {
initialJoinRequested();
}
}
void GroupCall::initialJoinRequested() {
using Update = Data::GroupCall::ParticipantUpdate;
const auto real = lookupReal();
Assert(real != nullptr);
@ -1283,10 +1306,10 @@ void GroupCall::join(const MTPInputGroupCall &inputCall) {
_peer->session().updates().addActiveChat(
_peerStream.events_starting_with_copy(_peer));
_canManage = Data::CanManageGroupCallValue(_peer);
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
_peer = group;
SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> peer) {
_peer = 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::Connecting) {
return;
} else if (_joinState.action != JoinAction::None) {
} else if (_joinState.action != JoinAction::None || _createRequestId) {
return;
}
@ -1529,7 +1552,11 @@ void GroupCall::rejoin(not_null<PeerData*> as) {
};
LOG(("Call Info: Join payload received, joining with ssrc: %1."
).arg(_joinState.payload.ssrc));
sendJoinRequest();
if (!_conferenceCall && _migratedConferenceInfo) {
startConference();
} else {
sendJoinRequest();
}
});
});
}
@ -1540,7 +1567,7 @@ void GroupCall::sendJoinRequest() {
checkNextJoinAction();
return;
}
auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray();
const auto joinBlock = _e2e ? _e2e->makeJoinBlock().data : QByteArray();
if (_e2e && joinBlock.isEmpty()) {
_joinState.finish();
LOG(("Call Error: Could not generate join block."));
@ -1554,12 +1581,8 @@ void GroupCall::sendJoinRequest() {
const auto flags = (wasMuteState != MuteState::Active
? Flag::f_muted
: Flag(0))
| (_joinHash.isEmpty()
? Flag(0)
: Flag::f_invite_hash)
| (wasVideoStopped
? Flag::f_video_stopped
: Flag(0))
| (_joinHash.isEmpty() ? Flag(0) : Flag::f_invite_hash)
| (wasVideoStopped ? Flag::f_video_stopped : Flag(0))
| (_e2e ? (Flag::f_public_key | Flag::f_block) : Flag());
_api.request(MTPphone_JoinGroupCall(
MTP_flags(flags),
@ -1570,74 +1593,15 @@ void GroupCall::sendJoinRequest() {
MTP_bytes(joinBlock),
MTP_dataJSON(MTP_bytes(_joinState.payload.json))
)).done([=](
const MTPUpdates &updates,
const MTPUpdates &result,
const MTP::Response &response) {
_serverTimeMs = TimestampInMsFromMsgId(response.outerMsgId);
_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(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();
}
joinDone(
TimestampInMsFromMsgId(response.outerMsgId),
result,
wasMuteState,
wasVideoStopped);
}).fail([=](const MTP::Error &error) {
const auto type = 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);
joinFail(error.type());
}).send();
}
@ -1685,6 +1649,144 @@ void GroupCall::refreshLastBlockAndJoin() {
}).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) {
Expects(subchain >= 0 && subchain < kSubChainsCount);
@ -2168,7 +2270,8 @@ void GroupCall::handlePossibleCreateOrJoinResponse(
} else {
Unexpected("Peer type in GroupCall::join.");
}
join(input);
applyInputCall(input);
initialJoin();
}
return;
} else if (_id != data.vid().v || !_instance) {
@ -3768,7 +3871,6 @@ void GroupCall::editParticipant(
}).send();
}
void GroupCall::inviteToConference(
InviteRequest request,
Fn<not_null<InviteResult*>()> resultAddress,

View file

@ -263,12 +263,15 @@ public:
[[nodiscard]] rpl::producer<not_null<Data::GroupCall*>> real() const;
[[nodiscard]] rpl::producer<QByteArray> emojiHashValue() const;
void applyInputCall(const MTPInputGroupCall &inputCall);
void startConference();
void start(TimeId scheduleDate, bool rtmp);
void hangup();
void discard();
void rejoinAs(Group::JoinInfo info);
void rejoinWithHash(const QString &hash);
void join(const MTPInputGroupCall &inputCall);
void initialJoin();
void initialJoinRequested();
void handleUpdate(const MTPUpdate &update);
void handlePossibleCreateOrJoinResponse(const MTPDupdateGroupCall &data);
void handlePossibleCreateOrJoinResponse(
@ -292,6 +295,14 @@ public:
bool emitShareScreenError();
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 {
return _errors.events();
}
@ -610,6 +621,7 @@ private:
void setupMediaDevices();
void setupOutgoingVideo();
void setupConference();
void setupConferenceCall();
void setScreenEndpoint(std::string endpoint);
void setCameraEndpoint(std::string endpoint);
@ -635,7 +647,7 @@ private:
[[nodiscard]] MTPInputGroupCall inputCallSafe() const;
const not_null<Delegate*> _delegate;
const std::shared_ptr<Data::GroupCall> _conferenceCall;
std::shared_ptr<Data::GroupCall> _conferenceCall;
std::shared_ptr<TdE2E::Call> _e2e;
QByteArray _pendingOutboundBlock;
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_session.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/widgets/buttons.h"
#include "ui/widgets/labels.h"
@ -41,24 +43,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QClipboard>
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() {
#ifdef Q_OS_MAC
@ -468,32 +452,13 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) {
MTPbytes(), // block
MTPDataJSON() // params
)).done([=](const MTPUpdates &result) {
session->api().applyUpdates(result);
const auto updates = result.match([&](const MTPDupdates &data) {
return &data.vupdates().v;
}, [&](const MTPDupdatesCombined &data) {
return &data.vupdates().v;
}, [](const auto &) {
return (const QVector<MTPUpdate>*)nullptr;
});
if (!updates) {
auto call = session->data().sharedConferenceCallFind(result);
if (!call) {
fail(u"Call not found!"_q);
return;
}
auto call = std::shared_ptr<Data::GroupCall>();
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;
}
}
session->api().applyUpdates(result);
const auto link = call ? call->conferenceInviteLink() : QString();
if (link.isEmpty()) {
fail(u"Call link not found!"_q);
@ -521,4 +486,19 @@ void MakeConferenceCall(ConferenceFactoryArgs &&args) {
}).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

View file

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

View file

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

View file

@ -374,6 +374,7 @@ auto GroupCall::staleParticipantIds() const
}
void GroupCall::enqueueUpdate(const MTPUpdate &update) {
const auto initial = !_version;
update.match([&](const MTPDupdateGroupCall &updateData) {
updateData.vcall().match([&](const MTPDgroupCall &data) {
const auto version = data.vversion().v;
@ -427,7 +428,7 @@ void GroupCall::enqueueUpdate(const MTPUpdate &update) {
}, [](const auto &) {
Unexpected("Type in GroupCall::enqueueUpdate.");
});
processQueuedUpdates();
processQueuedUpdates(initial);
}
void GroupCall::discard(const MTPDgroupCallDiscarded &data) {
@ -562,7 +563,7 @@ void GroupCall::applyEnqueuedUpdate(const MTPUpdate &update) {
Core::App().calls().applyGroupCallUpdateChecked(&session(), update);
}
void GroupCall::processQueuedUpdates() {
void GroupCall::processQueuedUpdates(bool initial) {
if (!_version || _applyingQueuedUpdates) {
return;
}
@ -574,7 +575,13 @@ void GroupCall::processQueuedUpdates() {
const auto type = entry.first.second;
const auto incremented = (type == QueuedType::VersionedParticipant);
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());
} else if (version == _version
|| (version == _version + 1 && incremented)) {

View file

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

View file

@ -1159,6 +1159,36 @@ std::shared_ptr<GroupCall> Session::sharedConferenceCall(
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) {
if (!now) {
now = base::unixtime::now();

View file

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

View file

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