"Fossies" - the Fresh Open Source Software Archive 
Member "tdesktop-2.6.1/Telegram/SourceFiles/calls/calls_group_call.cpp" (24 Feb 2021, 33520 Bytes) of package /linux/misc/tdesktop-2.6.1.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "calls_group_call.cpp" see the
Fossies "Dox" file reference documentation and the last
Fossies "Diffs" side-by-side code changes report:
2.5.9_vs_2.6.0.
1 /*
2 This file is part of Telegram Desktop,
3 the official desktop application for the Telegram messaging service.
4
5 For license and copyright information please follow this link:
6 https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
7 */
8 #include "calls/calls_group_call.h"
9
10 #include "calls/calls_group_common.h"
11 #include "main/main_session.h"
12 #include "api/api_send_progress.h"
13 #include "apiwrap.h"
14 #include "lang/lang_keys.h"
15 #include "lang/lang_hardcoded.h"
16 #include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
17 #include "ui/toasts/common_toasts.h"
18 #include "base/unixtime.h"
19 #include "core/application.h"
20 #include "core/core_settings.h"
21 #include "data/data_changes.h"
22 #include "data/data_user.h"
23 #include "data/data_chat.h"
24 #include "data/data_channel.h"
25 #include "data/data_group_call.h"
26 #include "data/data_session.h"
27 #include "base/global_shortcuts.h"
28 #include "base/openssl_help.h"
29 #include "webrtc/webrtc_media_devices.h"
30 #include "webrtc/webrtc_create_adm.h"
31
32 #include <tgcalls/group/GroupInstanceImpl.h>
33
34 #include <QtCore/QJsonDocument>
35 #include <QtCore/QJsonObject>
36 #include <QtCore/QJsonArray>
37
38 namespace tgcalls {
39 class GroupInstanceImpl;
40 } // namespace tgcalls
41
42 namespace Calls {
43 namespace {
44
45 constexpr auto kMaxInvitePerSlice = 10;
46 constexpr auto kCheckLastSpokeInterval = crl::time(1000);
47 constexpr auto kCheckJoinedTimeout = 4 * crl::time(1000);
48 constexpr auto kUpdateSendActionEach = crl::time(500);
49 constexpr auto kPlayConnectingEach = crl::time(1056) + 2 * crl::time(1000);
50
51 [[nodiscard]] std::unique_ptr<Webrtc::MediaDevices> CreateMediaDevices() {
52 const auto &settings = Core::App().settings();
53 return Webrtc::CreateMediaDevices(
54 settings.callInputDeviceId(),
55 settings.callOutputDeviceId(),
56 settings.callVideoInputDeviceId());
57 }
58
59 [[nodiscard]] const Data::GroupCall::Participant *LookupParticipant(
60 not_null<PeerData*> chat,
61 uint64 id,
62 not_null<UserData*> user) {
63 const auto call = chat->groupCall();
64 if (!id || !call || call->id() != id) {
65 return nullptr;
66 }
67 const auto &participants = call->participants();
68 const auto i = ranges::find(
69 participants,
70 user,
71 &Data::GroupCall::Participant::user);
72 return (i != end(participants)) ? &*i : nullptr;
73 }
74
75 } // namespace
76
77 [[nodiscard]] bool IsGroupCallAdmin(
78 not_null<PeerData*> peer,
79 not_null<UserData*> user) {
80 if (const auto chat = peer->asChat()) {
81 return chat->admins.contains(user)
82 || (chat->creator == user->bareId());
83 } else if (const auto group = peer->asMegagroup()) {
84 if (const auto mgInfo = group->mgInfo.get()) {
85 if (mgInfo->creator == user) {
86 return true;
87 }
88 const auto i = mgInfo->lastAdmins.find(user);
89 if (i == mgInfo->lastAdmins.end()) {
90 return false;
91 }
92 const auto &rights = i->second.rights;
93 return rights.c_chatAdminRights().is_manage_call();
94 }
95 }
96 return false;
97 }
98
99 GroupCall::GroupCall(
100 not_null<Delegate*> delegate,
101 not_null<PeerData*> peer,
102 const MTPInputGroupCall &inputCall)
103 : _delegate(delegate)
104 , _peer(peer)
105 , _history(peer->owner().history(peer))
106 , _api(&peer->session().mtp())
107 , _lastSpokeCheckTimer([=] { checkLastSpoke(); })
108 , _checkJoinedTimer([=] { checkJoined(); })
109 , _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
110 , _connectingSoundTimer([=] { playConnectingSoundOnce(); })
111 , _mediaDevices(CreateMediaDevices()) {
112 _muted.value(
113 ) | rpl::combine_previous(
114 ) | rpl::start_with_next([=](MuteState previous, MuteState state) {
115 if (_instance) {
116 updateInstanceMuteState();
117 }
118 if (_mySsrc) {
119 maybeSendMutedUpdate(previous);
120 }
121 }, _lifetime);
122
123 checkGlobalShortcutAvailability();
124
125 const auto id = inputCall.c_inputGroupCall().vid().v;
126 if (id) {
127 if (const auto call = _peer->groupCall(); call && call->id() == id) {
128 if (!_peer->canManageGroupCall() && call->joinMuted()) {
129 _muted = MuteState::ForceMuted;
130 }
131 }
132 _state = State::Joining;
133 join(inputCall);
134 } else {
135 start();
136 }
137
138 _mediaDevices->audioInputId(
139 ) | rpl::start_with_next([=](QString id) {
140 _audioInputId = id;
141 if (_instance) {
142 _instance->setAudioInputDevice(id.toStdString());
143 }
144 }, _lifetime);
145
146 _mediaDevices->audioOutputId(
147 ) | rpl::start_with_next([=](QString id) {
148 _audioOutputId = id;
149 if (_instance) {
150 _instance->setAudioOutputDevice(id.toStdString());
151 }
152 }, _lifetime);
153 }
154
155 GroupCall::~GroupCall() {
156 destroyController();
157 }
158
159 void GroupCall::checkGlobalShortcutAvailability() {
160 auto &settings = Core::App().settings();
161 if (!settings.groupCallPushToTalk()) {
162 return;
163 } else if (!base::GlobalShortcutsAllowed()) {
164 settings.setGroupCallPushToTalk(false);
165 Core::App().saveSettingsDelayed();
166 }
167 }
168
169 void GroupCall::setState(State state) {
170 if (_state.current() == State::Failed) {
171 return;
172 } else if (_state.current() == State::FailedHangingUp
173 && state != State::Failed) {
174 return;
175 }
176 if (_state.current() == state) {
177 return;
178 }
179 _state = state;
180
181 if (state == State::Joined) {
182 stopConnectingSound();
183 if (!_hadJoinedState) {
184 _hadJoinedState = true;
185 applyGlobalShortcutChanges();
186 _delegate->groupCallPlaySound(Delegate::GroupCallSound::Started);
187 }
188 if (const auto call = _peer->groupCall(); call && call->id() == _id) {
189 call->setInCall();
190 }
191 } else if (state == State::Connecting || state == State::Joining) {
192 if (_hadJoinedState) {
193 playConnectingSound();
194 }
195 } else {
196 stopConnectingSound();
197 }
198
199 if (false
200 || state == State::Ended
201 || state == State::Failed) {
202 // Destroy controller before destroying Call Panel,
203 // so that the panel hide animation is smooth.
204 destroyController();
205 }
206 switch (state) {
207 case State::HangingUp:
208 case State::FailedHangingUp:
209 _delegate->groupCallPlaySound(Delegate::GroupCallSound::Ended);
210 break;
211 case State::Ended:
212 _delegate->groupCallFinished(this);
213 break;
214 case State::Failed:
215 _delegate->groupCallFailed(this);
216 break;
217 case State::Connecting:
218 if (!_checkJoinedTimer.isActive()) {
219 _checkJoinedTimer.callOnce(kCheckJoinedTimeout);
220 }
221 break;
222 }
223 }
224
225 void GroupCall::playConnectingSound() {
226 if (_connectingSoundTimer.isActive()) {
227 return;
228 }
229 playConnectingSoundOnce();
230 _connectingSoundTimer.callEach(kPlayConnectingEach);
231 }
232
233 void GroupCall::stopConnectingSound() {
234 _connectingSoundTimer.cancel();
235 }
236
237 void GroupCall::playConnectingSoundOnce() {
238 _delegate->groupCallPlaySound(Delegate::GroupCallSound::Connecting);
239 }
240
241 void GroupCall::start() {
242 _createRequestId = _api.request(MTPphone_CreateGroupCall(
243 _peer->input,
244 MTP_int(openssl::RandomValue<int32>())
245 )).done([=](const MTPUpdates &result) {
246 _acceptFields = true;
247 _peer->session().api().applyUpdates(result);
248 _acceptFields = false;
249 }).fail([=](const RPCError &error) {
250 LOG(("Call Error: Could not create, error: %1"
251 ).arg(error.type()));
252 hangup();
253 if (error.type() == u"GROUPCALL_ANONYMOUS_FORBIDDEN"_q) {
254 Ui::ShowMultilineToast({
255 .text = { tr::lng_group_call_no_anonymous(tr::now) },
256 });
257 }
258 }).send();
259 }
260
261 void GroupCall::join(const MTPInputGroupCall &inputCall) {
262 setState(State::Joining);
263 if (const auto chat = _peer->asChat()) {
264 chat->setGroupCall(inputCall);
265 } else if (const auto group = _peer->asMegagroup()) {
266 group->setGroupCall(inputCall);
267 } else {
268 Unexpected("Peer type in GroupCall::join.");
269 }
270
271 inputCall.match([&](const MTPDinputGroupCall &data) {
272 _id = data.vid().v;
273 _accessHash = data.vaccess_hash().v;
274 rejoin();
275 });
276
277 using Update = Data::GroupCall::ParticipantUpdate;
278 _peer->groupCall()->participantUpdated(
279 ) | rpl::filter([=](const Update &update) {
280 return (_instance != nullptr);
281 }) | rpl::start_with_next([=](const Update &update) {
282 if (!update.now) {
283 _instance->removeSsrcs({ update.was->ssrc });
284 } else {
285 const auto &now = *update.now;
286 const auto &was = update.was;
287 const auto volumeChanged = was
288 ? (was->volume != now.volume || was->mutedByMe != now.mutedByMe)
289 : (now.volume != Group::kDefaultVolume || now.mutedByMe);
290 if (volumeChanged) {
291 _instance->setVolume(
292 now.ssrc,
293 (now.mutedByMe
294 ? 0.
295 : (now.volume
296 / float64(Group::kDefaultVolume))));
297 }
298 }
299 }, _lifetime);
300
301 SubscribeToMigration(_peer, _lifetime, [=](not_null<ChannelData*> group) {
302 _peer = group;
303 });
304 }
305
306 void GroupCall::rejoin() {
307 if (state() != State::Joining
308 && state() != State::Joined
309 && state() != State::Connecting) {
310 return;
311 }
312
313 _mySsrc = 0;
314 setState(State::Joining);
315 createAndStartController();
316 applySelfInCallLocally();
317 LOG(("Call Info: Requesting join payload."));
318
319 const auto weak = base::make_weak(this);
320 _instance->emitJoinPayload([=](tgcalls::GroupJoinPayload payload) {
321 crl::on_main(weak, [=, payload = std::move(payload)]{
322 auto fingerprints = QJsonArray();
323 for (const auto print : payload.fingerprints) {
324 auto object = QJsonObject();
325 object.insert("hash", QString::fromStdString(print.hash));
326 object.insert("setup", QString::fromStdString(print.setup));
327 object.insert(
328 "fingerprint",
329 QString::fromStdString(print.fingerprint));
330 fingerprints.push_back(object);
331 }
332
333 auto root = QJsonObject();
334 const auto ssrc = payload.ssrc;
335 root.insert("ufrag", QString::fromStdString(payload.ufrag));
336 root.insert("pwd", QString::fromStdString(payload.pwd));
337 root.insert("fingerprints", fingerprints);
338 root.insert("ssrc", double(payload.ssrc));
339
340 LOG(("Call Info: Join payload received, joining with ssrc: %1."
341 ).arg(ssrc));
342
343 const auto json = QJsonDocument(root).toJson(
344 QJsonDocument::Compact);
345 const auto wasMuteState = muted();
346 _api.request(MTPphone_JoinGroupCall(
347 MTP_flags((wasMuteState != MuteState::Active)
348 ? MTPphone_JoinGroupCall::Flag::f_muted
349 : MTPphone_JoinGroupCall::Flag(0)),
350 inputCall(),
351 MTP_dataJSON(MTP_bytes(json))
352 )).done([=](const MTPUpdates &updates) {
353 _mySsrc = ssrc;
354 setState(_instanceConnected
355 ? State::Joined
356 : State::Connecting);
357 applySelfInCallLocally();
358 maybeSendMutedUpdate(wasMuteState);
359 _peer->session().api().applyUpdates(updates);
360 }).fail([=](const RPCError &error) {
361 const auto type = error.type();
362 LOG(("Call Error: Could not join, error: %1").arg(type));
363
364 if (type == u"GROUPCALL_SSRC_DUPLICATE_MUCH") {
365 rejoin();
366 return;
367 }
368
369 hangup();
370 Ui::ShowMultilineToast({
371 .text = { type == u"GROUPCALL_ANONYMOUS_FORBIDDEN"_q
372 ? tr::lng_group_call_no_anonymous(tr::now)
373 : type == u"GROUPCALL_PARTICIPANTS_TOO_MUCH"_q
374 ? tr::lng_group_call_too_many(tr::now)
375 : type == u"GROUPCALL_FORBIDDEN"_q
376 ? tr::lng_group_not_accessible(tr::now)
377 : Lang::Hard::ServerError() },
378 });
379 }).send();
380 });
381 });
382 }
383
384 void GroupCall::applySelfInCallLocally() {
385 const auto call = _peer->groupCall();
386 if (!call || call->id() != _id) {
387 return;
388 }
389 using Flag = MTPDgroupCallParticipant::Flag;
390 const auto &participants = call->participants();
391 const auto self = _peer->session().user();
392 const auto i = ranges::find(
393 participants,
394 self,
395 &Data::GroupCall::Participant::user);
396 const auto date = (i != end(participants))
397 ? i->date
398 : base::unixtime::now();
399 const auto lastActive = (i != end(participants))
400 ? i->lastActive
401 : TimeId(0);
402 const auto volume = (i != end(participants))
403 ? i->volume
404 : Group::kDefaultVolume;
405 const auto canSelfUnmute = (muted() != MuteState::ForceMuted);
406 const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0))
407 | (lastActive ? Flag::f_active_date : Flag(0))
408 | (_mySsrc ? Flag(0) : Flag::f_left)
409 | Flag::f_volume // Without flag the volume is reset to 100%.
410 | Flag::f_volume_by_admin // Self volume can only be set by admin.
411 | ((muted() != MuteState::Active) ? Flag::f_muted : Flag(0));
412 call->applyUpdateChecked(
413 MTP_updateGroupCallParticipants(
414 inputCall(),
415 MTP_vector<MTPGroupCallParticipant>(
416 1,
417 MTP_groupCallParticipant(
418 MTP_flags(flags),
419 MTP_int(self->bareId()),
420 MTP_int(date),
421 MTP_int(lastActive),
422 MTP_int(_mySsrc),
423 MTP_int(volume))),
424 MTP_int(0)).c_updateGroupCallParticipants());
425 }
426
427 void GroupCall::applyParticipantLocally(
428 not_null<UserData*> user,
429 bool mute,
430 std::optional<int> volume) {
431 const auto participant = LookupParticipant(_peer, _id, user);
432 if (!participant || !participant->ssrc) {
433 return;
434 }
435 const auto canManageCall = _peer->canManageGroupCall();
436 const auto isMuted = participant->muted || (mute && canManageCall);
437 const auto canSelfUnmute = !canManageCall
438 ? participant->canSelfUnmute
439 : (!mute || IsGroupCallAdmin(_peer, user));
440 const auto isMutedByYou = mute && !canManageCall;
441 const auto mutedCount = 0/*participant->mutedCount*/;
442 using Flag = MTPDgroupCallParticipant::Flag;
443 const auto flags = (canSelfUnmute ? Flag::f_can_self_unmute : Flag(0))
444 | Flag::f_volume // Without flag the volume is reset to 100%.
445 | ((participant->applyVolumeFromMin && !volume)
446 ? Flag::f_volume_by_admin
447 : Flag(0))
448 | (participant->lastActive ? Flag::f_active_date : Flag(0))
449 | (isMuted ? Flag::f_muted : Flag(0))
450 | (isMutedByYou ? Flag::f_muted_by_you : Flag(0));
451 _peer->groupCall()->applyUpdateChecked(
452 MTP_updateGroupCallParticipants(
453 inputCall(),
454 MTP_vector<MTPGroupCallParticipant>(
455 1,
456 MTP_groupCallParticipant(
457 MTP_flags(flags),
458 MTP_int(user->bareId()),
459 MTP_int(participant->date),
460 MTP_int(participant->lastActive),
461 MTP_int(participant->ssrc),
462 MTP_int(volume.value_or(participant->volume)))),
463 MTP_int(0)).c_updateGroupCallParticipants());
464 }
465
466 void GroupCall::hangup() {
467 finish(FinishType::Ended);
468 }
469
470 void GroupCall::discard() {
471 if (!_id) {
472 _api.request(_createRequestId).cancel();
473 hangup();
474 return;
475 }
476 _api.request(MTPphone_DiscardGroupCall(
477 inputCall()
478 )).done([=](const MTPUpdates &result) {
479 // Here 'this' could be destroyed by updates, so we set Ended after
480 // updates being handled, but in a guarded way.
481 crl::on_main(this, [=] { hangup(); });
482 _peer->session().api().applyUpdates(result);
483 }).fail([=](const RPCError &error) {
484 hangup();
485 }).send();
486 }
487
488 void GroupCall::finish(FinishType type) {
489 Expects(type != FinishType::None);
490
491 const auto finalState = (type == FinishType::Ended)
492 ? State::Ended
493 : State::Failed;
494 const auto hangupState = (type == FinishType::Ended)
495 ? State::HangingUp
496 : State::FailedHangingUp;
497 const auto state = _state.current();
498 if (state == State::HangingUp
499 || state == State::FailedHangingUp
500 || state == State::Ended
501 || state == State::Failed) {
502 return;
503 }
504 if (!_mySsrc) {
505 setState(finalState);
506 return;
507 }
508
509 setState(hangupState);
510
511 // We want to leave request still being sent and processed even if
512 // the call is already destroyed.
513 const auto session = &_peer->session();
514 const auto weak = base::make_weak(this);
515 session->api().request(MTPphone_LeaveGroupCall(
516 inputCall(),
517 MTP_int(_mySsrc)
518 )).done([=](const MTPUpdates &result) {
519 // Here 'this' could be destroyed by updates, so we set Ended after
520 // updates being handled, but in a guarded way.
521 crl::on_main(weak, [=] { setState(finalState); });
522 session->api().applyUpdates(result);
523 }).fail(crl::guard(weak, [=](const RPCError &error) {
524 setState(finalState);
525 })).send();
526 }
527
528 void GroupCall::setMuted(MuteState mute) {
529 const auto set = [=] {
530 const auto wasMuted = (muted() == MuteState::Muted)
531 || (muted() == MuteState::PushToTalk);
532 _muted = mute;
533 const auto nowMuted = (muted() == MuteState::Muted)
534 || (muted() == MuteState::PushToTalk);
535 if (wasMuted != nowMuted) {
536 applySelfInCallLocally();
537 }
538 };
539 if (mute == MuteState::Active || mute == MuteState::PushToTalk) {
540 _delegate->groupCallRequestPermissionsOrFail(crl::guard(this, set));
541 } else {
542 set();
543 }
544 }
545
546 void GroupCall::handleUpdate(const MTPGroupCall &call) {
547 return call.match([&](const MTPDgroupCall &data) {
548 if (_acceptFields) {
549 if (!_instance && !_id) {
550 join(MTP_inputGroupCall(data.vid(), data.vaccess_hash()));
551 }
552 return;
553 } else if (_id != data.vid().v
554 || _accessHash != data.vaccess_hash().v
555 || !_instance) {
556 return;
557 }
558 if (const auto params = data.vparams()) {
559 params->match([&](const MTPDdataJSON &data) {
560 auto error = QJsonParseError{ 0, QJsonParseError::NoError };
561 const auto document = QJsonDocument::fromJson(
562 data.vdata().v,
563 &error);
564 if (error.error != QJsonParseError::NoError) {
565 LOG(("API Error: "
566 "Failed to parse group call params, error: %1."
567 ).arg(error.errorString()));
568 return;
569 } else if (!document.isObject()) {
570 LOG(("API Error: "
571 "Not an object received in group call params."));
572 return;
573 }
574 const auto readString = [](
575 const QJsonObject &object,
576 const char *key) {
577 return object.value(key).toString().toStdString();
578 };
579 const auto root = document.object().value("transport").toObject();
580 auto payload = tgcalls::GroupJoinResponsePayload();
581 payload.ufrag = readString(root, "ufrag");
582 payload.pwd = readString(root, "pwd");
583 const auto prints = root.value("fingerprints").toArray();
584 const auto candidates = root.value("candidates").toArray();
585 for (const auto &print : prints) {
586 const auto object = print.toObject();
587 payload.fingerprints.push_back(tgcalls::GroupJoinPayloadFingerprint{
588 .hash = readString(object, "hash"),
589 .setup = readString(object, "setup"),
590 .fingerprint = readString(object, "fingerprint"),
591 });
592 }
593 for (const auto &candidate : candidates) {
594 const auto object = candidate.toObject();
595 payload.candidates.push_back(tgcalls::GroupJoinResponseCandidate{
596 .port = readString(object, "port"),
597 .protocol = readString(object, "protocol"),
598 .network = readString(object, "network"),
599 .generation = readString(object, "generation"),
600 .id = readString(object, "id"),
601 .component = readString(object, "component"),
602 .foundation = readString(object, "foundation"),
603 .priority = readString(object, "priority"),
604 .ip = readString(object, "ip"),
605 .type = readString(object, "type"),
606 .tcpType = readString(object, "tcpType"),
607 .relAddr = readString(object, "relAddr"),
608 .relPort = readString(object, "relPort"),
609 });
610 }
611 _instance->setJoinResponsePayload(payload);
612 });
613 }
614 }, [&](const MTPDgroupCallDiscarded &data) {
615 if (data.vid().v == _id) {
616 _mySsrc = 0;
617 hangup();
618 }
619 });
620 }
621
622 void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) {
623 const auto state = _state.current();
624 if (state != State::Joined && state != State::Connecting) {
625 return;
626 }
627
628 const auto handleOtherParticipants = [=](
629 const MTPDgroupCallParticipant &data) {
630 if (data.is_min()) {
631 // No real information about mutedByMe or my custom volume.
632 return;
633 }
634 const auto user = _peer->owner().user(data.vuser_id().v);
635 const auto participant = LookupParticipant(_peer, _id, user);
636 if (!participant) {
637 return;
638 }
639 _otherParticipantStateValue.fire(Group::ParticipantState{
640 .user = user,
641 .volume = data.vvolume().value_or_empty(),
642 .mutedByMe = data.is_muted_by_you(),
643 });
644 };
645
646 const auto self = _peer->session().userId();
647 for (const auto &participant : data.vparticipants().v) {
648 participant.match([&](const MTPDgroupCallParticipant &data) {
649 if (data.vuser_id().v != self) {
650 handleOtherParticipants(data);
651 return;
652 }
653 if (data.is_left() && data.vsource().v == _mySsrc) {
654 // I was removed from the call, rejoin.
655 LOG(("Call Info: Rejoin after got 'left' with my ssrc."));
656 setState(State::Joining);
657 rejoin();
658 } else if (!data.is_left() && data.vsource().v != _mySsrc) {
659 // I joined from another device, hangup.
660 LOG(("Call Info: Hangup after '!left' with ssrc %1, my %2."
661 ).arg(data.vsource().v
662 ).arg(_mySsrc));
663 _mySsrc = 0;
664 hangup();
665 }
666 if (data.is_muted() && !data.is_can_self_unmute()) {
667 setMuted(MuteState::ForceMuted);
668 } else if (muted() == MuteState::ForceMuted) {
669 setMuted(MuteState::Muted);
670 } else if (data.is_muted() && muted() != MuteState::Muted) {
671 setMuted(MuteState::Muted);
672 }
673 });
674 }
675 }
676
677 void GroupCall::createAndStartController() {
678 const auto &settings = Core::App().settings();
679
680 const auto weak = base::make_weak(this);
681 const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
682 tgcalls::GroupInstanceDescriptor descriptor = {
683 .config = tgcalls::GroupConfig{
684 },
685 .networkStateUpdated = [=](bool connected) {
686 crl::on_main(weak, [=] { setInstanceConnected(connected); });
687 },
688 .audioLevelsUpdated = [=](const tgcalls::GroupLevelsUpdate &data) {
689 const auto &updates = data.updates;
690 if (updates.empty()) {
691 return;
692 } else if (updates.size() == 1 && !updates.front().ssrc) {
693 const auto &value = updates.front().value;
694 // Don't send many 0 while we're muted.
695 if (myLevel->level == value.level
696 && myLevel->voice == value.voice) {
697 return;
698 }
699 *myLevel = updates.front().value;
700 }
701 crl::on_main(weak, [=] { audioLevelsUpdated(data); });
702 },
703 .initialInputDeviceId = _audioInputId.toStdString(),
704 .initialOutputDeviceId = _audioOutputId.toStdString(),
705 .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
706 settings.callAudioBackend()),
707 };
708 if (Logs::DebugEnabled()) {
709 auto callLogFolder = cWorkingDir() + qsl("DebugLogs");
710 auto callLogPath = callLogFolder + qsl("/last_group_call_log.txt");
711 auto callLogNative = QDir::toNativeSeparators(callLogPath);
712 #ifdef Q_OS_WIN
713 descriptor.config.logPath.data = callLogNative.toStdWString();
714 #else // Q_OS_WIN
715 const auto callLogUtf = QFile::encodeName(callLogNative);
716 descriptor.config.logPath.data.resize(callLogUtf.size());
717 ranges::copy(callLogUtf, descriptor.config.logPath.data.begin());
718 #endif // Q_OS_WIN
719 QFile(callLogPath).remove();
720 QDir().mkpath(callLogFolder);
721 }
722
723 LOG(("Call Info: Creating group instance"));
724 _instance = std::make_unique<tgcalls::GroupInstanceImpl>(
725 std::move(descriptor));
726
727 updateInstanceMuteState();
728 updateInstanceVolumes();
729
730 //raw->setAudioOutputDuckingEnabled(settings.callAudioDuckingEnabled());
731 }
732
733 void GroupCall::updateInstanceMuteState() {
734 Expects(_instance != nullptr);
735
736 const auto state = muted();
737 _instance->setIsMuted(state != MuteState::Active
738 && state != MuteState::PushToTalk);
739 }
740
741 void GroupCall::updateInstanceVolumes() {
742 const auto real = _peer->groupCall();
743 if (!real || real->id() != _id) {
744 return;
745 }
746
747 const auto &participants = real->participants();
748 for (const auto &participant : participants) {
749 const auto setVolume = participant.mutedByMe
750 || (participant.volume != Group::kDefaultVolume);
751 if (setVolume && participant.ssrc) {
752 _instance->setVolume(
753 participant.ssrc,
754 (participant.mutedByMe
755 ? 0.
756 : (participant.volume / float64(Group::kDefaultVolume))));
757 }
758 }
759 }
760
761 void GroupCall::audioLevelsUpdated(const tgcalls::GroupLevelsUpdate &data) {
762 Expects(!data.updates.empty());
763
764 auto check = false;
765 auto checkNow = false;
766 const auto now = crl::now();
767 for (const auto &[ssrcOrZero, value] : data.updates) {
768 const auto ssrc = ssrcOrZero ? ssrcOrZero : _mySsrc;
769 const auto level = value.level;
770 const auto voice = value.voice;
771 const auto self = (ssrc == _mySsrc);
772 _levelUpdates.fire(LevelUpdate{
773 .ssrc = ssrc,
774 .value = level,
775 .voice = voice,
776 .self = self
777 });
778 if (level <= kSpeakLevelThreshold) {
779 continue;
780 }
781 if (self
782 && voice
783 && (!_lastSendProgressUpdate
784 || _lastSendProgressUpdate + kUpdateSendActionEach < now)) {
785 _lastSendProgressUpdate = now;
786 _peer->session().sendProgressManager().update(
787 _history,
788 Api::SendProgressType::Speaking);
789 }
790
791 check = true;
792 const auto i = _lastSpoke.find(ssrc);
793 if (i == _lastSpoke.end()) {
794 _lastSpoke.emplace(ssrc, Data::LastSpokeTimes{
795 .anything = now,
796 .voice = voice ? now : 0,
797 });
798 checkNow = true;
799 } else {
800 if ((i->second.anything + kCheckLastSpokeInterval / 3 <= now)
801 || (voice
802 && i->second.voice + kCheckLastSpokeInterval / 3 <= now)) {
803 checkNow = true;
804 }
805 i->second.anything = now;
806 if (voice) {
807 i->second.voice = now;
808 }
809 }
810 }
811 if (checkNow) {
812 checkLastSpoke();
813 } else if (check && !_lastSpokeCheckTimer.isActive()) {
814 _lastSpokeCheckTimer.callEach(kCheckLastSpokeInterval / 2);
815 }
816 }
817
818 void GroupCall::checkLastSpoke() {
819 const auto real = _peer->groupCall();
820 if (!real || real->id() != _id) {
821 return;
822 }
823
824 auto hasRecent = false;
825 const auto now = crl::now();
826 auto list = base::take(_lastSpoke);
827 for (auto i = list.begin(); i != list.end();) {
828 const auto [ssrc, when] = *i;
829 if (when.anything + kCheckLastSpokeInterval >= now) {
830 hasRecent = true;
831 ++i;
832 } else {
833 i = list.erase(i);
834 }
835 real->applyLastSpoke(ssrc, when, now);
836 }
837 _lastSpoke = std::move(list);
838
839 if (!hasRecent) {
840 _lastSpokeCheckTimer.cancel();
841 } else if (!_lastSpokeCheckTimer.isActive()) {
842 _lastSpokeCheckTimer.callEach(kCheckLastSpokeInterval / 3);
843 }
844 }
845
846 void GroupCall::checkJoined() {
847 if (state() != State::Connecting || !_id || !_mySsrc) {
848 return;
849 }
850 _api.request(MTPphone_CheckGroupCall(
851 inputCall(),
852 MTP_int(_mySsrc)
853 )).done([=](const MTPBool &result) {
854 if (!mtpIsTrue(result)) {
855 LOG(("Call Info: Rejoin after FALSE in checkGroupCall."));
856 rejoin();
857 } else if (state() == State::Connecting) {
858 _checkJoinedTimer.callOnce(kCheckJoinedTimeout);
859 }
860 }).fail([=](const RPCError &error) {
861 LOG(("Call Info: Rejoin after error '%1' in checkGroupCall."
862 ).arg(error.type()));
863 rejoin();
864 }).send();
865 }
866
867 void GroupCall::setInstanceConnected(bool connected) {
868 if (_instanceConnected == connected) {
869 return;
870 }
871 _instanceConnected = connected;
872 if (state() == State::Connecting && connected) {
873 setState(State::Joined);
874 } else if (state() == State::Joined && !connected) {
875 setState(State::Connecting);
876 }
877 }
878
879 void GroupCall::maybeSendMutedUpdate(MuteState previous) {
880 // Send only Active <-> !Active changes.
881 const auto now = muted();
882 const auto wasActive = (previous == MuteState::Active);
883 const auto nowActive = (now == MuteState::Active);
884 if (now == MuteState::ForceMuted
885 || previous == MuteState::ForceMuted
886 || (nowActive == wasActive)) {
887 return;
888 }
889 sendMutedUpdate();
890 }
891
892 void GroupCall::sendMutedUpdate() {
893 _api.request(_updateMuteRequestId).cancel();
894 _updateMuteRequestId = _api.request(MTPphone_EditGroupCallMember(
895 MTP_flags((muted() != MuteState::Active)
896 ? MTPphone_EditGroupCallMember::Flag::f_muted
897 : MTPphone_EditGroupCallMember::Flag(0)),
898 inputCall(),
899 MTP_inputUserSelf(),
900 MTP_int(100000) // volume
901 )).done([=](const MTPUpdates &result) {
902 _updateMuteRequestId = 0;
903 _peer->session().api().applyUpdates(result);
904 }).fail([=](const RPCError &error) {
905 _updateMuteRequestId = 0;
906 if (error.type() == u"GROUPCALL_FORBIDDEN"_q) {
907 LOG(("Call Info: Rejoin after error '%1' in editGroupCallMember."
908 ).arg(error.type()));
909 rejoin();
910 }
911 }).send();
912 }
913
914 rpl::producer<bool> GroupCall::connectingValue() const {
915 using namespace rpl::mappers;
916 return _state.value() | rpl::map(
917 _1 == State::Creating
918 || _1 == State::Joining
919 || _1 == State::Connecting
920 ) | rpl::distinct_until_changed();
921 }
922
923 void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) {
924 if (input) {
925 _mediaDevices->switchToAudioInput(deviceId);
926 } else {
927 _mediaDevices->switchToAudioOutput(deviceId);
928 }
929 }
930
931 void GroupCall::toggleMute(const Group::MuteRequest &data) {
932 if (data.locallyOnly) {
933 applyParticipantLocally(data.user, data.mute, std::nullopt);
934 } else {
935 editParticipant(data.user, data.mute, std::nullopt);
936 }
937 }
938
939 void GroupCall::changeVolume(const Group::VolumeRequest &data) {
940 if (data.locallyOnly) {
941 applyParticipantLocally(data.user, false, data.volume);
942 } else {
943 editParticipant(data.user, false, data.volume);
944 }
945 }
946
947 void GroupCall::editParticipant(
948 not_null<UserData*> user,
949 bool mute,
950 std::optional<int> volume) {
951 const auto participant = LookupParticipant(_peer, _id, user);
952 if (!participant) {
953 return;
954 }
955 applyParticipantLocally(user, mute, volume);
956
957 using Flag = MTPphone_EditGroupCallMember::Flag;
958 const auto flags = (mute ? Flag::f_muted : Flag(0))
959 | (volume.has_value() ? Flag::f_volume : Flag(0));
960 _api.request(MTPphone_EditGroupCallMember(
961 MTP_flags(flags),
962 inputCall(),
963 user->inputUser,
964 MTP_int(std::clamp(volume.value_or(0), 1, Group::kMaxVolume))
965 )).done([=](const MTPUpdates &result) {
966 _peer->session().api().applyUpdates(result);
967 }).fail([=](const RPCError &error) {
968 if (error.type() == u"GROUPCALL_FORBIDDEN"_q) {
969 LOG(("Call Info: Rejoin after error '%1' in editGroupCallMember."
970 ).arg(error.type()));
971 rejoin();
972 }
973 }).send();
974 }
975
976 std::variant<int, not_null<UserData*>> GroupCall::inviteUsers(
977 const std::vector<not_null<UserData*>> &users) {
978 const auto real = _peer->groupCall();
979 if (!real || real->id() != _id) {
980 return 0;
981 }
982 const auto owner = &_peer->owner();
983 const auto &invited = owner->invitedToCallUsers(_id);
984 const auto &participants = real->participants();
985 auto &&toInvite = users | ranges::view::filter([&](
986 not_null<UserData*> user) {
987 return !invited.contains(user) && !ranges::contains(
988 participants,
989 user,
990 &Data::GroupCall::Participant::user);
991 });
992
993 auto count = 0;
994 auto slice = QVector<MTPInputUser>();
995 auto result = std::variant<int, not_null<UserData*>>(0);
996 slice.reserve(kMaxInvitePerSlice);
997 const auto sendSlice = [&] {
998 count += slice.size();
999 _api.request(MTPphone_InviteToGroupCall(
1000 inputCall(),
1001 MTP_vector<MTPInputUser>(slice)
1002 )).done([=](const MTPUpdates &result) {
1003 _peer->session().api().applyUpdates(result);
1004 }).send();
1005 slice.clear();
1006 };
1007 for (const auto user : users) {
1008 if (!count && slice.empty()) {
1009 result = user;
1010 }
1011 owner->registerInvitedToCallUser(_id, _peer, user);
1012 slice.push_back(user->inputUser);
1013 if (slice.size() == kMaxInvitePerSlice) {
1014 sendSlice();
1015 }
1016 }
1017 if (count != 0 || slice.size() != 1) {
1018 result = int(count + slice.size());
1019 }
1020 if (!slice.empty()) {
1021 sendSlice();
1022 }
1023 return result;
1024 }
1025
1026 auto GroupCall::ensureGlobalShortcutManager()
1027 -> std::shared_ptr<GlobalShortcutManager> {
1028 if (!_shortcutManager) {
1029 _shortcutManager = base::CreateGlobalShortcutManager();
1030 }
1031 return _shortcutManager;
1032 }
1033
1034 void GroupCall::applyGlobalShortcutChanges() {
1035 auto &settings = Core::App().settings();
1036 if (!settings.groupCallPushToTalk()
1037 || settings.groupCallPushToTalkShortcut().isEmpty()
1038 || !base::GlobalShortcutsAvailable()
1039 || !base::GlobalShortcutsAllowed()) {
1040 _shortcutManager = nullptr;
1041 _pushToTalk = nullptr;
1042 return;
1043 }
1044 ensureGlobalShortcutManager();
1045 const auto shortcut = _shortcutManager->shortcutFromSerialized(
1046 settings.groupCallPushToTalkShortcut());
1047 if (!shortcut) {
1048 settings.setGroupCallPushToTalkShortcut(QByteArray());
1049 settings.setGroupCallPushToTalk(false);
1050 Core::App().saveSettingsDelayed();
1051 _shortcutManager = nullptr;
1052 _pushToTalk = nullptr;
1053 return;
1054 }
1055 if (_pushToTalk) {
1056 if (shortcut->serialize() == _pushToTalk->serialize()) {
1057 return;
1058 }
1059 _shortcutManager->stopWatching(_pushToTalk);
1060 }
1061 _pushToTalk = shortcut;
1062 _shortcutManager->startWatching(_pushToTalk, [=](bool pressed) {
1063 pushToTalk(
1064 pressed,
1065 Core::App().settings().groupCallPushToTalkDelay());
1066 });
1067 }
1068
1069 void GroupCall::pushToTalk(bool pressed, crl::time delay) {
1070 if (muted() == MuteState::ForceMuted
1071 || muted() == MuteState::Active) {
1072 return;
1073 } else if (pressed) {
1074 _pushToTalkCancelTimer.cancel();
1075 setMuted(MuteState::PushToTalk);
1076 } else if (delay) {
1077 _pushToTalkCancelTimer.callOnce(delay);
1078 } else {
1079 pushToTalkCancel();
1080 }
1081 }
1082
1083 void GroupCall::pushToTalkCancel() {
1084 _pushToTalkCancelTimer.cancel();
1085 if (muted() == MuteState::PushToTalk) {
1086 setMuted(MuteState::Muted);
1087 }
1088 }
1089
1090 auto GroupCall::otherParticipantStateValue() const
1091 -> rpl::producer<Group::ParticipantState> {
1092 return _otherParticipantStateValue.events();
1093 }
1094
1095 //void GroupCall::setAudioVolume(bool input, float level) {
1096 // if (_instance) {
1097 // if (input) {
1098 // _instance->setInputVolume(level);
1099 // } else {
1100 // _instance->setOutputVolume(level);
1101 // }
1102 // }
1103 //}
1104
1105 void GroupCall::setAudioDuckingEnabled(bool enabled) {
1106 if (_instance) {
1107 //_instance->setAudioOutputDuckingEnabled(enabled);
1108 }
1109 }
1110
1111 void GroupCall::handleRequestError(const RPCError &error) {
1112 //if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
1113 // Ui::show(Box<InformBox>(tr::lng_call_error_not_available(tr::now, lt_user, _user->name)));
1114 //} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) {
1115 // Ui::show(Box<InformBox>(tr::lng_call_error_outdated(tr::now, lt_user, _user->name)));
1116 //} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) {
1117 // Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)));
1118 //}
1119 //finish(FinishType::Failed);
1120 }
1121
1122 void GroupCall::handleControllerError(const QString &error) {
1123 if (error == u"ERROR_INCOMPATIBLE"_q) {
1124 //Ui::show(Box<InformBox>(
1125 // Lang::Hard::CallErrorIncompatible().replace(
1126 // "{user}",
1127 // _user->name)));
1128 } else if (error == u"ERROR_AUDIO_IO"_q) {
1129 //Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
1130 }
1131 //finish(FinishType::Failed);
1132 }
1133
1134 MTPInputGroupCall GroupCall::inputCall() const {
1135 Expects(_id != 0);
1136
1137 return MTP_inputGroupCall(
1138 MTP_long(_id),
1139 MTP_long(_accessHash));
1140 }
1141
1142 void GroupCall::destroyController() {
1143 if (_instance) {
1144 //_instance->stop([](tgcalls::FinalState) {
1145 //});
1146
1147 DEBUG_LOG(("Call Info: Destroying call controller.."));
1148 _instance.reset();
1149 DEBUG_LOG(("Call Info: Call controller destroyed."));
1150 }
1151 }
1152
1153 } // namespace Calls