mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Separate Call/GroupCall delegates and Instance.
This commit is contained in:
parent
6e34360f7e
commit
c93ddf6aac
23 changed files with 422 additions and 262 deletions
Binary file not shown.
Before Width: | Height: | Size: 345 B |
Binary file not shown.
Before Width: | Height: | Size: 591 B |
Binary file not shown.
Before Width: | Height: | Size: 932 B |
BIN
Telegram/Resources/icons/calls/calls_settings.png
Normal file
BIN
Telegram/Resources/icons/calls/calls_settings.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 756 B |
BIN
Telegram/Resources/icons/calls/calls_settings@2x.png
Normal file
BIN
Telegram/Resources/icons/calls/calls_settings@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
Telegram/Resources/icons/calls/calls_settings@3x.png
Normal file
BIN
Telegram/Resources/icons/calls/calls_settings@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -1107,7 +1107,9 @@ void PeerListContent::paintEvent(QPaintEvent *e) {
|
||||||
Painter p(this);
|
Painter p(this);
|
||||||
|
|
||||||
const auto clip = e->rect();
|
const auto clip = e->rect();
|
||||||
p.fillRect(clip, _st.item.button.textBg);
|
if (_mode != Mode::Custom) {
|
||||||
|
p.fillRect(clip, _st.item.button.textBg);
|
||||||
|
}
|
||||||
|
|
||||||
const auto repaintByStatusAfter = _repaintByStatus.remainingTime();
|
const auto repaintByStatusAfter = _repaintByStatus.remainingTime();
|
||||||
auto repaintAfterMin = repaintByStatusAfter;
|
auto repaintAfterMin = repaintByStatusAfter;
|
||||||
|
|
|
@ -736,7 +736,7 @@ groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMem
|
||||||
|
|
||||||
groupCallSettingsInner: IconButton(callButton) {
|
groupCallSettingsInner: IconButton(callButton) {
|
||||||
iconPosition: point(-1px, 22px);
|
iconPosition: point(-1px, 22px);
|
||||||
icon: icon {{ "calls/call_settings", groupCallIconFg }};
|
icon: icon {{ "calls/calls_settings", groupCallIconFg }};
|
||||||
ripple: RippleAnimation(defaultRippleAnimation) {
|
ripple: RippleAnimation(defaultRippleAnimation) {
|
||||||
color: callMuteRipple;
|
color: callMuteRipple;
|
||||||
}
|
}
|
||||||
|
@ -1022,3 +1022,5 @@ desktopCaptureSourceSkip: 12px;
|
||||||
groupCallNarrowSkip: 9px;
|
groupCallNarrowSkip: 9px;
|
||||||
groupCallNarrowRowSkip: 8px;
|
groupCallNarrowRowSkip: 8px;
|
||||||
groupCallNarrowSize: size(90px, 90px);
|
groupCallNarrowSize: size(90px, 90px);
|
||||||
|
groupCallWideModeWidthMin: 800px;
|
||||||
|
groupCallWideModeSize: size(960px, 520px);
|
||||||
|
|
|
@ -161,8 +161,12 @@ Call::Call(
|
||||||
, _user(user)
|
, _user(user)
|
||||||
, _api(&_user->session().mtp())
|
, _api(&_user->session().mtp())
|
||||||
, _type(type)
|
, _type(type)
|
||||||
, _videoIncoming(std::make_unique<Webrtc::VideoTrack>(StartVideoState(video)))
|
, _videoIncoming(
|
||||||
, _videoOutgoing(std::make_unique<Webrtc::VideoTrack>(StartVideoState(video))) {
|
std::make_unique<Webrtc::VideoTrack>(
|
||||||
|
StartVideoState(video)))
|
||||||
|
, _videoOutgoing(
|
||||||
|
std::make_unique<Webrtc::VideoTrack>(
|
||||||
|
StartVideoState(video))) {
|
||||||
_discardByTimeoutTimer.setCallback([=] { hangup(); });
|
_discardByTimeoutTimer.setCallback([=] { hangup(); });
|
||||||
|
|
||||||
if (_type == Type::Outgoing) {
|
if (_type == Type::Outgoing) {
|
||||||
|
|
|
@ -53,6 +53,11 @@ struct Error {
|
||||||
QString details;
|
QString details;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class CallType {
|
||||||
|
Incoming,
|
||||||
|
Outgoing,
|
||||||
|
};
|
||||||
|
|
||||||
class Call : public base::has_weak_ptr {
|
class Call : public base::has_weak_ptr {
|
||||||
public:
|
public:
|
||||||
class Delegate {
|
class Delegate {
|
||||||
|
@ -81,11 +86,12 @@ public:
|
||||||
|
|
||||||
static constexpr auto kSoundSampleMs = 100;
|
static constexpr auto kSoundSampleMs = 100;
|
||||||
|
|
||||||
enum class Type {
|
using Type = CallType;
|
||||||
Incoming,
|
Call(
|
||||||
Outgoing,
|
not_null<Delegate*> delegate,
|
||||||
};
|
not_null<UserData*> user,
|
||||||
Call(not_null<Delegate*> delegate, not_null<UserData*> user, Type type, bool video);
|
Type type,
|
||||||
|
bool video);
|
||||||
|
|
||||||
[[nodiscard]] Type type() const {
|
[[nodiscard]] Type type() const {
|
||||||
return _type;
|
return _type;
|
||||||
|
|
|
@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
|
|
||||||
|
#include "calls/calls_call.h"
|
||||||
#include "calls/group/calls_group_common.h"
|
#include "calls/group/calls_group_common.h"
|
||||||
|
#include "calls/group/calls_choose_join_as.h"
|
||||||
|
#include "calls/group/calls_group_call.h"
|
||||||
#include "mtproto/mtproto_dh_utils.h"
|
#include "mtproto/mtproto_dh_utils.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
@ -41,9 +44,128 @@ namespace {
|
||||||
|
|
||||||
constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000);
|
constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000);
|
||||||
|
|
||||||
|
using CallSound = Call::Delegate::CallSound;
|
||||||
|
using GroupCallSound = GroupCall::Delegate::GroupCallSound;
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Instance::Instance() = default;
|
class Instance::Delegate final
|
||||||
|
: public Call::Delegate
|
||||||
|
, public GroupCall::Delegate {
|
||||||
|
public:
|
||||||
|
explicit Delegate(not_null<Instance*> instance);
|
||||||
|
|
||||||
|
DhConfig getDhConfig() const override;
|
||||||
|
|
||||||
|
void callFinished(not_null<Call*> call) override;
|
||||||
|
void callFailed(not_null<Call*> call) override;
|
||||||
|
void callRedial(not_null<Call*> call) override;
|
||||||
|
void callRequestPermissionsOrFail(
|
||||||
|
Fn<void()> onSuccess,
|
||||||
|
bool video) override;
|
||||||
|
void callPlaySound(CallSound sound) override;
|
||||||
|
auto callGetVideoCapture()
|
||||||
|
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
||||||
|
|
||||||
|
void groupCallFinished(not_null<GroupCall*> call) override;
|
||||||
|
void groupCallFailed(not_null<GroupCall*> call) override;
|
||||||
|
void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override;
|
||||||
|
void groupCallPlaySound(GroupCallSound sound) override;
|
||||||
|
auto groupCallGetVideoCapture(const QString &deviceId)
|
||||||
|
-> std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<Instance*> _instance;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Instance::Delegate::Delegate(not_null<Instance*> instance)
|
||||||
|
: _instance(instance) {
|
||||||
|
}
|
||||||
|
|
||||||
|
DhConfig Instance::Delegate::getDhConfig() const {
|
||||||
|
return *_instance->_cachedDhConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::callFinished(not_null<Call*> call) {
|
||||||
|
crl::on_main(call, [=] {
|
||||||
|
_instance->destroyCall(call);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::callFailed(not_null<Call*> call) {
|
||||||
|
crl::on_main(call, [=] {
|
||||||
|
_instance->destroyCall(call);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::callRedial(not_null<Call*> call) {
|
||||||
|
if (_instance->_currentCall.get() == call) {
|
||||||
|
_instance->refreshDhConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::callRequestPermissionsOrFail(
|
||||||
|
Fn<void()> onSuccess,
|
||||||
|
bool video) {
|
||||||
|
_instance->requestPermissionsOrFail(std::move(onSuccess), video);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::callPlaySound(CallSound sound) {
|
||||||
|
_instance->playSoundOnce([&] {
|
||||||
|
switch (sound) {
|
||||||
|
case CallSound::Busy: return "call_busy";
|
||||||
|
case CallSound::Ended: return "call_end";
|
||||||
|
case CallSound::Connecting: return "call_connect";
|
||||||
|
}
|
||||||
|
Unexpected("CallSound in Instance::callPlaySound.");
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Instance::Delegate::callGetVideoCapture()
|
||||||
|
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
|
||||||
|
return _instance->getVideoCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::groupCallFinished(not_null<GroupCall*> call) {
|
||||||
|
crl::on_main(call, [=] {
|
||||||
|
_instance->destroyGroupCall(call);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::groupCallFailed(not_null<GroupCall*> call) {
|
||||||
|
crl::on_main(call, [=] {
|
||||||
|
_instance->destroyGroupCall(call);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::groupCallRequestPermissionsOrFail(
|
||||||
|
Fn<void()> onSuccess) {
|
||||||
|
_instance->requestPermissionsOrFail(std::move(onSuccess), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Instance::Delegate::groupCallPlaySound(GroupCallSound sound) {
|
||||||
|
_instance->playSoundOnce([&] {
|
||||||
|
switch (sound) {
|
||||||
|
case GroupCallSound::Started: return "group_call_start";
|
||||||
|
case GroupCallSound::Ended: return "group_call_end";
|
||||||
|
case GroupCallSound::AllowedToSpeak: return "group_call_allowed";
|
||||||
|
case GroupCallSound::Connecting: return "group_call_connect";
|
||||||
|
}
|
||||||
|
Unexpected("GroupCallSound in Instance::groupCallPlaySound.");
|
||||||
|
}());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Instance::Delegate::groupCallGetVideoCapture(const QString &deviceId)
|
||||||
|
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
|
||||||
|
return _instance->getVideoCapture(deviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Instance::Instance()
|
||||||
|
: _delegate(std::make_unique<Delegate>(this))
|
||||||
|
, _cachedDhConfig(std::make_unique<DhConfig>())
|
||||||
|
, _chooseJoinAs(std::make_unique<Group::ChooseJoinAsProcess>()) {
|
||||||
|
}
|
||||||
|
|
||||||
Instance::~Instance() = default;
|
Instance::~Instance() = default;
|
||||||
|
|
||||||
|
@ -72,7 +194,7 @@ void Instance::startOrJoinGroupCall(
|
||||||
: peer->groupCall()
|
: peer->groupCall()
|
||||||
? Group::ChooseJoinAsProcess::Context::Join
|
? Group::ChooseJoinAsProcess::Context::Join
|
||||||
: Group::ChooseJoinAsProcess::Context::Create;
|
: Group::ChooseJoinAsProcess::Context::Create;
|
||||||
_chooseJoinAs.start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
|
_chooseJoinAs->start(peer, context, [=](object_ptr<Ui::BoxContent> box) {
|
||||||
Ui::show(std::move(box), Ui::LayerOption::KeepOther);
|
Ui::show(std::move(box), Ui::LayerOption::KeepOther);
|
||||||
}, [=](QString text) {
|
}, [=](QString text) {
|
||||||
Ui::Toast::Show(text);
|
Ui::Toast::Show(text);
|
||||||
|
@ -85,36 +207,6 @@ void Instance::startOrJoinGroupCall(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void Instance::callFinished(not_null<Call*> call) {
|
|
||||||
crl::on_main(call, [=] {
|
|
||||||
destroyCall(call);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Instance::callFailed(not_null<Call*> call) {
|
|
||||||
crl::on_main(call, [=] {
|
|
||||||
destroyCall(call);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Instance::callRedial(not_null<Call*> call) {
|
|
||||||
if (_currentCall.get() == call) {
|
|
||||||
refreshDhConfig();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Instance::groupCallFinished(not_null<GroupCall*> call) {
|
|
||||||
crl::on_main(call, [=] {
|
|
||||||
destroyGroupCall(call);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void Instance::groupCallFailed(not_null<GroupCall*> call) {
|
|
||||||
crl::on_main(call, [=] {
|
|
||||||
destroyGroupCall(call);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
not_null<Media::Audio::Track*> Instance::ensureSoundLoaded(
|
not_null<Media::Audio::Track*> Instance::ensureSoundLoaded(
|
||||||
const QString &key) {
|
const QString &key) {
|
||||||
const auto i = _tracks.find(key);
|
const auto i = _tracks.find(key);
|
||||||
|
@ -132,31 +224,6 @@ void Instance::playSoundOnce(const QString &key) {
|
||||||
ensureSoundLoaded(key)->playOnce();
|
ensureSoundLoaded(key)->playOnce();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Instance::callPlaySound(CallSound sound) {
|
|
||||||
playSoundOnce([&] {
|
|
||||||
switch (sound) {
|
|
||||||
case CallSound::Busy: return "call_busy";
|
|
||||||
case CallSound::Ended: return "call_end";
|
|
||||||
case CallSound::Connecting: return "call_connect";
|
|
||||||
}
|
|
||||||
Unexpected("CallSound in Instance::callPlaySound.");
|
|
||||||
return "";
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Instance::groupCallPlaySound(GroupCallSound sound) {
|
|
||||||
playSoundOnce([&] {
|
|
||||||
switch (sound) {
|
|
||||||
case GroupCallSound::Started: return "group_call_start";
|
|
||||||
case GroupCallSound::Ended: return "group_call_end";
|
|
||||||
case GroupCallSound::AllowedToSpeak: return "group_call_allowed";
|
|
||||||
case GroupCallSound::Connecting: return "group_call_connect";
|
|
||||||
}
|
|
||||||
Unexpected("GroupCallSound in Instance::groupCallPlaySound.");
|
|
||||||
return "";
|
|
||||||
}());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Instance::destroyCall(not_null<Call*> call) {
|
void Instance::destroyCall(not_null<Call*> call) {
|
||||||
if (_currentCall.get() == call) {
|
if (_currentCall.get() == call) {
|
||||||
_currentCallPanel->closeBeforeDestroy();
|
_currentCallPanel->closeBeforeDestroy();
|
||||||
|
@ -174,7 +241,7 @@ void Instance::destroyCall(not_null<Call*> call) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video) {
|
void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video) {
|
||||||
auto call = std::make_unique<Call>(getCallDelegate(), user, type, video);
|
auto call = std::make_unique<Call>(_delegate.get(), user, type, video);
|
||||||
const auto raw = call.get();
|
const auto raw = call.get();
|
||||||
|
|
||||||
user->session().account().sessionChanges(
|
user->session().account().sessionChanges(
|
||||||
|
@ -217,7 +284,7 @@ void Instance::createGroupCall(
|
||||||
destroyCurrentCall();
|
destroyCurrentCall();
|
||||||
|
|
||||||
auto call = std::make_unique<GroupCall>(
|
auto call = std::make_unique<GroupCall>(
|
||||||
getGroupCallDelegate(),
|
_delegate.get(),
|
||||||
std::move(info),
|
std::move(info),
|
||||||
inputCall);
|
inputCall);
|
||||||
const auto raw = call.get();
|
const auto raw = call.get();
|
||||||
|
@ -237,7 +304,7 @@ void Instance::refreshDhConfig() {
|
||||||
|
|
||||||
const auto weak = base::make_weak(_currentCall);
|
const auto weak = base::make_weak(_currentCall);
|
||||||
_currentCall->user()->session().api().request(MTPmessages_GetDhConfig(
|
_currentCall->user()->session().api().request(MTPmessages_GetDhConfig(
|
||||||
MTP_int(_dhConfig.version),
|
MTP_int(_cachedDhConfig->version),
|
||||||
MTP_int(MTP::ModExpFirst::kRandomPowerSize)
|
MTP_int(MTP::ModExpFirst::kRandomPowerSize)
|
||||||
)).done([=](const MTPmessages_DhConfig &result) {
|
)).done([=](const MTPmessages_DhConfig &result) {
|
||||||
const auto call = weak.get();
|
const auto call = weak.get();
|
||||||
|
@ -249,14 +316,14 @@ void Instance::refreshDhConfig() {
|
||||||
Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
|
Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
|
||||||
call->start(random);
|
call->start(random);
|
||||||
} else {
|
} else {
|
||||||
callFailed(call);
|
_delegate->callFailed(call);
|
||||||
}
|
}
|
||||||
}).fail([=](const MTP::Error &error) {
|
}).fail([=](const MTP::Error &error) {
|
||||||
const auto call = weak.get();
|
const auto call = weak.get();
|
||||||
if (!call) {
|
if (!call) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callFailed(call);
|
_delegate->callFailed(call);
|
||||||
}).send();
|
}).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,13 +344,13 @@ bytes::const_span Instance::updateDhConfig(
|
||||||
} else if (!validRandom(data.vrandom().v)) {
|
} else if (!validRandom(data.vrandom().v)) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
_dhConfig.g = data.vg().v;
|
_cachedDhConfig->g = data.vg().v;
|
||||||
_dhConfig.p = std::move(primeBytes);
|
_cachedDhConfig->p = std::move(primeBytes);
|
||||||
_dhConfig.version = data.vversion().v;
|
_cachedDhConfig->version = data.vversion().v;
|
||||||
return bytes::make_span(data.vrandom().v);
|
return bytes::make_span(data.vrandom().v);
|
||||||
}, [&](const MTPDmessages_dhConfigNotModified &data)
|
}, [&](const MTPDmessages_dhConfigNotModified &data)
|
||||||
-> bytes::const_span {
|
-> bytes::const_span {
|
||||||
if (!_dhConfig.g || _dhConfig.p.empty()) {
|
if (!_cachedDhConfig->g || _cachedDhConfig->p.empty()) {
|
||||||
LOG(("API Error: dhConfigNotModified on zero version."));
|
LOG(("API Error: dhConfigNotModified on zero version."));
|
||||||
return {};
|
return {};
|
||||||
} else if (!validRandom(data.vrandom().v)) {
|
} else if (!validRandom(data.vrandom().v)) {
|
||||||
|
@ -592,22 +659,19 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::callGetVideoCapture() {
|
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
|
||||||
return getVideoCapture();
|
QString deviceId) {
|
||||||
}
|
if (deviceId.isEmpty()) {
|
||||||
|
deviceId = Core::App().settings().callVideoInputDeviceId();
|
||||||
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::groupCallGetVideoCapture() {
|
}
|
||||||
return getVideoCapture();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture() {
|
|
||||||
if (auto result = _videoCapture.lock()) {
|
if (auto result = _videoCapture.lock()) {
|
||||||
|
result->switchToDevice(deviceId.toStdString());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
|
auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
|
||||||
tgcalls::VideoCaptureInterface::Create(
|
tgcalls::VideoCaptureInterface::Create(
|
||||||
tgcalls::StaticThreads::getThreads(),
|
tgcalls::StaticThreads::getThreads(),
|
||||||
Core::App().settings().callVideoInputDeviceId().toStdString()));
|
deviceId.toStdString()));
|
||||||
_videoCapture = result;
|
_videoCapture = result;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,9 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
#include "calls/calls_call.h"
|
|
||||||
#include "calls/group/calls_group_call.h"
|
|
||||||
#include "calls/group/calls_choose_join_as.h"
|
|
||||||
|
|
||||||
namespace Platform {
|
namespace Platform {
|
||||||
enum class PermissionType;
|
enum class PermissionType;
|
||||||
|
@ -27,17 +24,22 @@ class Session;
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
struct JoinInfo;
|
struct JoinInfo;
|
||||||
class Panel;
|
class Panel;
|
||||||
|
class ChooseJoinAsProcess;
|
||||||
} // namespace Calls::Group
|
} // namespace Calls::Group
|
||||||
|
|
||||||
|
namespace tgcalls {
|
||||||
|
class VideoCaptureInterface;
|
||||||
|
} // namespace tgcalls
|
||||||
|
|
||||||
namespace Calls {
|
namespace Calls {
|
||||||
|
|
||||||
|
class Call;
|
||||||
|
enum class CallType;
|
||||||
|
class GroupCall;
|
||||||
class Panel;
|
class Panel;
|
||||||
|
struct DhConfig;
|
||||||
|
|
||||||
class Instance
|
class Instance : private base::Subscriber, public base::has_weak_ptr {
|
||||||
: private Call::Delegate
|
|
||||||
, private GroupCall::Delegate
|
|
||||||
, private base::Subscriber
|
|
||||||
, public base::has_weak_ptr {
|
|
||||||
public:
|
public:
|
||||||
Instance();
|
Instance();
|
||||||
~Instance();
|
~Instance();
|
||||||
|
@ -69,7 +71,8 @@ public:
|
||||||
bool activateCurrentCall(const QString &joinHash = QString());
|
bool activateCurrentCall(const QString &joinHash = QString());
|
||||||
bool minimizeCurrentActiveCall();
|
bool minimizeCurrentActiveCall();
|
||||||
bool closeCurrentActiveCall();
|
bool closeCurrentActiveCall();
|
||||||
std::shared_ptr<tgcalls::VideoCaptureInterface> getVideoCapture();
|
[[nodiscard]] auto getVideoCapture(QString deviceId = QString())
|
||||||
|
-> std::shared_ptr<tgcalls::VideoCaptureInterface>;
|
||||||
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);
|
void requestPermissionsOrFail(Fn<void()> onSuccess, bool video = true);
|
||||||
|
|
||||||
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
void setCurrentAudioDevice(bool input, const QString &deviceId);
|
||||||
|
@ -77,44 +80,13 @@ public:
|
||||||
[[nodiscard]] bool isQuitPrevent();
|
[[nodiscard]] bool isQuitPrevent();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using CallSound = Call::Delegate::CallSound;
|
class Delegate;
|
||||||
using GroupCallSound = GroupCall::Delegate::GroupCallSound;
|
friend class Delegate;
|
||||||
|
|
||||||
[[nodiscard]] not_null<Call::Delegate*> getCallDelegate() {
|
|
||||||
return static_cast<Call::Delegate*>(this);
|
|
||||||
}
|
|
||||||
[[nodiscard]] not_null<GroupCall::Delegate*> getGroupCallDelegate() {
|
|
||||||
return static_cast<GroupCall::Delegate*>(this);
|
|
||||||
}
|
|
||||||
[[nodiscard]] DhConfig getDhConfig() const override {
|
|
||||||
return _dhConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
not_null<Media::Audio::Track*> ensureSoundLoaded(const QString &key);
|
not_null<Media::Audio::Track*> ensureSoundLoaded(const QString &key);
|
||||||
void playSoundOnce(const QString &key);
|
void playSoundOnce(const QString &key);
|
||||||
|
|
||||||
void callFinished(not_null<Call*> call) override;
|
void createCall(not_null<UserData*> user, CallType type, bool video);
|
||||||
void callFailed(not_null<Call*> call) override;
|
|
||||||
void callRedial(not_null<Call*> call) override;
|
|
||||||
void callRequestPermissionsOrFail(
|
|
||||||
Fn<void()> onSuccess,
|
|
||||||
bool video) override {
|
|
||||||
requestPermissionsOrFail(std::move(onSuccess), video);
|
|
||||||
}
|
|
||||||
void callPlaySound(CallSound sound) override;
|
|
||||||
auto callGetVideoCapture()
|
|
||||||
->std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
|
||||||
|
|
||||||
void groupCallFinished(not_null<GroupCall*> call) override;
|
|
||||||
void groupCallFailed(not_null<GroupCall*> call) override;
|
|
||||||
void groupCallRequestPermissionsOrFail(Fn<void()> onSuccess) override {
|
|
||||||
requestPermissionsOrFail(std::move(onSuccess), false);
|
|
||||||
}
|
|
||||||
void groupCallPlaySound(GroupCallSound sound) override;
|
|
||||||
auto groupCallGetVideoCapture()
|
|
||||||
->std::shared_ptr<tgcalls::VideoCaptureInterface> override;
|
|
||||||
|
|
||||||
void createCall(not_null<UserData*> user, Call::Type type, bool video);
|
|
||||||
void destroyCall(not_null<Call*> call);
|
void destroyCall(not_null<Call*> call);
|
||||||
|
|
||||||
void createGroupCall(
|
void createGroupCall(
|
||||||
|
@ -141,7 +113,8 @@ private:
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
const MTPUpdate &update);
|
const MTPUpdate &update);
|
||||||
|
|
||||||
DhConfig _dhConfig;
|
const std::unique_ptr<Delegate> _delegate;
|
||||||
|
const std::unique_ptr<DhConfig> _cachedDhConfig;
|
||||||
|
|
||||||
crl::time _lastServerConfigUpdateTime = 0;
|
crl::time _lastServerConfigUpdateTime = 0;
|
||||||
base::weak_ptr<Main::Session> _serverConfigRequestSession;
|
base::weak_ptr<Main::Session> _serverConfigRequestSession;
|
||||||
|
@ -157,7 +130,7 @@ private:
|
||||||
|
|
||||||
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;
|
base::flat_map<QString, std::unique_ptr<Media::Audio::Track>> _tracks;
|
||||||
|
|
||||||
Group::ChooseJoinAsProcess _chooseJoinAs;
|
const std::unique_ptr<Group::ChooseJoinAsProcess> _chooseJoinAs;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "calls/calls_call.h"
|
#include "calls/calls_call.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "calls/calls_signal_bars.h"
|
#include "calls/calls_signal_bars.h"
|
||||||
|
#include "calls/group/calls_group_call.h"
|
||||||
#include "calls/group/calls_group_menu.h" // Group::LeaveBox.
|
#include "calls/group/calls_group_menu.h" // Group::LeaveBox.
|
||||||
#include "history/view/history_view_group_call_tracker.h" // ContentByCall.
|
#include "history/view/history_view_group_call_tracker.h" // ContentByCall.
|
||||||
#include "data/data_user.h"
|
#include "data/data_user.h"
|
||||||
|
|
|
@ -313,8 +313,8 @@ GroupCall::GroupCall(
|
||||||
, _joinHash(info.joinHash)
|
, _joinHash(info.joinHash)
|
||||||
, _id(inputCall.c_inputGroupCall().vid().v)
|
, _id(inputCall.c_inputGroupCall().vid().v)
|
||||||
, _scheduleDate(info.scheduleDate)
|
, _scheduleDate(info.scheduleDate)
|
||||||
, _videoOutgoing(std::make_unique<Webrtc::VideoTrack>(
|
, _videoOutgoing(
|
||||||
Webrtc::VideoState::Inactive))
|
std::make_unique<Webrtc::VideoTrack>(Webrtc::VideoState::Inactive))
|
||||||
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
|
, _lastSpokeCheckTimer([=] { checkLastSpoke(); })
|
||||||
, _checkJoinedTimer([=] { checkJoined(); })
|
, _checkJoinedTimer([=] { checkJoined(); })
|
||||||
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
|
, _pushToTalkCancelTimer([=] { pushToTalkCancel(); })
|
||||||
|
@ -402,6 +402,7 @@ void GroupCall::switchToCamera() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_videoDeviceId = _videoInputId;
|
_videoDeviceId = _videoInputId;
|
||||||
|
if (_videoOutgoing)
|
||||||
_videoCapture->switchToDevice(_videoDeviceId.toStdString());
|
_videoCapture->switchToDevice(_videoDeviceId.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1366,10 +1367,42 @@ void GroupCall::setupMediaDevices() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::setupOutgoingVideo() {
|
void GroupCall::setupOutgoingVideo() {
|
||||||
_videoCapture = _delegate->groupCallGetVideoCapture();
|
|
||||||
_videoOutgoing->setState(Webrtc::VideoState::Active);
|
|
||||||
_videoCapture->setOutput(_videoOutgoing->sink());
|
|
||||||
_videoDeviceId = _videoInputId;
|
_videoDeviceId = _videoInputId;
|
||||||
|
static const auto hasDevices = [] {
|
||||||
|
return !Webrtc::GetVideoInputList().empty();
|
||||||
|
};
|
||||||
|
const auto started = _videoOutgoing->state();
|
||||||
|
if (!hasDevices()) {
|
||||||
|
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||||
|
}
|
||||||
|
_videoOutgoing->stateValue(
|
||||||
|
) | rpl::start_with_next([=](Webrtc::VideoState state) {
|
||||||
|
if (state != Webrtc::VideoState::Inactive && !hasDevices()) {
|
||||||
|
//_errors.fire({ ErrorType::NoCamera }); // #TODO videochats
|
||||||
|
_videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||||
|
//} else if (state != Webrtc::VideoState::Inactive
|
||||||
|
// && _instance
|
||||||
|
// && !_instance->supportsVideo()) {
|
||||||
|
// _errors.fire({ ErrorType::NotVideoCall });
|
||||||
|
// _videoOutgoing->setState(Webrtc::VideoState::Inactive);
|
||||||
|
} else if (state != Webrtc::VideoState::Inactive) {
|
||||||
|
// Paused not supported right now.
|
||||||
|
Assert(state == Webrtc::VideoState::Active);
|
||||||
|
if (!_videoCapture) {
|
||||||
|
_videoCapture = _delegate->groupCallGetVideoCapture(
|
||||||
|
_videoDeviceId);
|
||||||
|
_videoCapture->setOutput(_videoOutgoing->sink());
|
||||||
|
} else {
|
||||||
|
_videoCapture->switchToDevice(_videoDeviceId.toStdString());
|
||||||
|
}
|
||||||
|
if (_instance) {
|
||||||
|
_instance->setVideoCapture(_videoCapture, nullptr);
|
||||||
|
}
|
||||||
|
_videoCapture->setState(tgcalls::VideoState::Active);
|
||||||
|
} else if (_videoCapture) {
|
||||||
|
_videoCapture->setState(tgcalls::VideoState::Inactive);
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::changeTitle(const QString &title) {
|
void GroupCall::changeTitle(const QString &title) {
|
||||||
|
@ -1424,6 +1457,7 @@ void GroupCall::ensureControllerCreated() {
|
||||||
|
|
||||||
const auto weak = base::make_weak(this);
|
const auto weak = base::make_weak(this);
|
||||||
const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
|
const auto myLevel = std::make_shared<tgcalls::GroupLevelValue>();
|
||||||
|
_videoCall = true;
|
||||||
tgcalls::GroupInstanceDescriptor descriptor = {
|
tgcalls::GroupInstanceDescriptor descriptor = {
|
||||||
.threads = tgcalls::StaticThreads::getThreads(),
|
.threads = tgcalls::StaticThreads::getThreads(),
|
||||||
.config = tgcalls::GroupConfig{
|
.config = tgcalls::GroupConfig{
|
||||||
|
@ -1451,13 +1485,10 @@ void GroupCall::ensureControllerCreated() {
|
||||||
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
.createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator(
|
||||||
settings.callAudioBackend()),
|
settings.callAudioBackend()),
|
||||||
.videoCapture = _videoCapture,
|
.videoCapture = _videoCapture,
|
||||||
//.getVideoSource = [=] {
|
|
||||||
// return _videoCapture->
|
|
||||||
//},
|
|
||||||
.incomingVideoSourcesUpdated = [=](
|
.incomingVideoSourcesUpdated = [=](
|
||||||
const std::vector<uint32_t> &ssrcs) {
|
const std::vector<uint32_t> &ssrcs) {
|
||||||
crl::on_main(weak, [=] {
|
crl::on_main(weak, [=] {
|
||||||
showVideoStreams(ssrcs);
|
setVideoStreams(ssrcs);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
.participantDescriptionsRequired = [=](
|
.participantDescriptionsRequired = [=](
|
||||||
|
@ -1609,9 +1640,53 @@ void GroupCall::requestParticipantsInformation(
|
||||||
addPreparedParticipants();
|
addPreparedParticipants();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GroupCall::showVideoStreams(const std::vector<std::uint32_t> &ssrcs) {
|
void GroupCall::setVideoStreams(const std::vector<std::uint32_t> &ssrcs) {
|
||||||
|
const auto large = _videoStreamLarge.current();
|
||||||
|
auto newLarge = large;
|
||||||
|
if (large && !ranges::contains(ssrcs, large)) {
|
||||||
|
newLarge = 0;
|
||||||
|
_videoStreamPinned = 0;
|
||||||
|
}
|
||||||
|
auto lastSpokeVoice = crl::time(0);
|
||||||
|
auto lastSpokeVoiceSsrc = uint32(0);
|
||||||
|
auto lastSpokeAnything = crl::time(0);
|
||||||
|
auto lastSpokeAnythingSsrc = uint32(0);
|
||||||
|
auto removed = _videoStreamSsrcs;
|
||||||
for (const auto ssrc : ssrcs) {
|
for (const auto ssrc : ssrcs) {
|
||||||
_videoStreamUpdated.fire_copy(ssrc);
|
const auto i = removed.find(ssrc);
|
||||||
|
if (i != end(removed)) {
|
||||||
|
removed.erase(i);
|
||||||
|
} else {
|
||||||
|
_videoStreamSsrcs.emplace(ssrc);
|
||||||
|
_streamsVideoUpdated.fire({ ssrc, true });
|
||||||
|
}
|
||||||
|
if (!newLarge) {
|
||||||
|
const auto j = _lastSpoke.find(ssrc);
|
||||||
|
if (j != end(_lastSpoke)) {
|
||||||
|
if (!lastSpokeVoiceSsrc
|
||||||
|
|| lastSpokeVoice < j->second.voice) {
|
||||||
|
lastSpokeVoiceSsrc = ssrc;
|
||||||
|
lastSpokeVoice = j->second.voice;
|
||||||
|
}
|
||||||
|
if (!lastSpokeAnythingSsrc
|
||||||
|
|| lastSpokeAnything < j->second.anything) {
|
||||||
|
lastSpokeAnythingSsrc = ssrc;
|
||||||
|
lastSpokeAnything = j->second.anything;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!newLarge) {
|
||||||
|
_videoStreamLarge = lastSpokeVoiceSsrc
|
||||||
|
? lastSpokeVoiceSsrc
|
||||||
|
: lastSpokeAnythingSsrc
|
||||||
|
? lastSpokeAnythingSsrc
|
||||||
|
: ssrcs.empty()
|
||||||
|
? 0
|
||||||
|
: ssrcs.front();
|
||||||
|
}
|
||||||
|
for (const auto ssrc : removed) {
|
||||||
|
_streamsVideoUpdated.fire({ ssrc, false });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2035,45 +2110,6 @@ auto GroupCall::otherParticipantStateValue() const
|
||||||
return _otherParticipantStateValue.events();
|
return _otherParticipantStateValue.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
//void GroupCall::setAudioVolume(bool input, float level) {
|
|
||||||
// if (_instance) {
|
|
||||||
// if (input) {
|
|
||||||
// _instance->setInputVolume(level);
|
|
||||||
// } else {
|
|
||||||
// _instance->setOutputVolume(level);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
void GroupCall::setAudioDuckingEnabled(bool enabled) {
|
|
||||||
if (_instance) {
|
|
||||||
//_instance->setAudioOutputDuckingEnabled(enabled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCall::handleRequestError(const MTP::Error &error) {
|
|
||||||
//if (error.type() == qstr("USER_PRIVACY_RESTRICTED")) {
|
|
||||||
// Ui::show(Box<InformBox>(tr::lng_call_error_not_available(tr::now, lt_user, _user->name)));
|
|
||||||
//} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) {
|
|
||||||
// Ui::show(Box<InformBox>(tr::lng_call_error_outdated(tr::now, lt_user, _user->name)));
|
|
||||||
//} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) {
|
|
||||||
// Ui::show(Box<InformBox>(Lang::Hard::CallErrorIncompatible().replace("{user}", _user->name)));
|
|
||||||
//}
|
|
||||||
//finish(FinishType::Failed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GroupCall::handleControllerError(const QString &error) {
|
|
||||||
if (error == u"ERROR_INCOMPATIBLE"_q) {
|
|
||||||
//Ui::show(Box<InformBox>(
|
|
||||||
// Lang::Hard::CallErrorIncompatible().replace(
|
|
||||||
// "{user}",
|
|
||||||
// _user->name)));
|
|
||||||
} else if (error == u"ERROR_AUDIO_IO"_q) {
|
|
||||||
//Ui::show(Box<InformBox>(tr::lng_call_error_audio_io(tr::now)));
|
|
||||||
}
|
|
||||||
//finish(FinishType::Failed);
|
|
||||||
}
|
|
||||||
|
|
||||||
MTPInputGroupCall GroupCall::inputCall() const {
|
MTPInputGroupCall GroupCall::inputCall() const {
|
||||||
Expects(_id != 0);
|
Expects(_id != 0);
|
||||||
|
|
||||||
|
@ -2084,9 +2120,6 @@ MTPInputGroupCall GroupCall::inputCall() const {
|
||||||
|
|
||||||
void GroupCall::destroyController() {
|
void GroupCall::destroyController() {
|
||||||
if (_instance) {
|
if (_instance) {
|
||||||
//_instance->stop([](tgcalls::FinalState) {
|
|
||||||
//});
|
|
||||||
|
|
||||||
DEBUG_LOG(("Call Info: Destroying call controller.."));
|
DEBUG_LOG(("Call Info: Destroying call controller.."));
|
||||||
_instance.reset();
|
_instance.reset();
|
||||||
DEBUG_LOG(("Call Info: Call controller destroyed."));
|
DEBUG_LOG(("Call Info: Call controller destroyed."));
|
||||||
|
|
|
@ -74,6 +74,11 @@ struct LevelUpdate {
|
||||||
bool me = false;
|
bool me = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct StreamsVideoUpdate {
|
||||||
|
uint32 ssrc = 0;
|
||||||
|
bool streams = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct VideoParams;
|
struct VideoParams;
|
||||||
|
|
||||||
[[nodiscard]] std::shared_ptr<VideoParams> ParseVideoParams(
|
[[nodiscard]] std::shared_ptr<VideoParams> ParseVideoParams(
|
||||||
|
@ -100,7 +105,7 @@ public:
|
||||||
Ended,
|
Ended,
|
||||||
};
|
};
|
||||||
virtual void groupCallPlaySound(GroupCallSound sound) = 0;
|
virtual void groupCallPlaySound(GroupCallSound sound) = 0;
|
||||||
virtual auto groupCallGetVideoCapture()
|
virtual auto groupCallGetVideoCapture(const QString &deviceId)
|
||||||
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
|
-> std::shared_ptr<tgcalls::VideoCaptureInterface> = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -158,6 +163,13 @@ public:
|
||||||
return _muted.value();
|
return _muted.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool videoCall() const {
|
||||||
|
return _videoCall.current();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<bool> videoCallValue() const {
|
||||||
|
return _videoCall.value();
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] auto otherParticipantStateValue() const
|
[[nodiscard]] auto otherParticipantStateValue() const
|
||||||
-> rpl::producer<Group::ParticipantState>;
|
-> rpl::producer<Group::ParticipantState>;
|
||||||
|
|
||||||
|
@ -194,8 +206,15 @@ public:
|
||||||
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
|
||||||
return _levelUpdates.events();
|
return _levelUpdates.events();
|
||||||
}
|
}
|
||||||
[[nodiscard]] rpl::producer<uint32> videoStreamUpdated() const {
|
[[nodiscard]] auto streamsVideoUpdates() const
|
||||||
return _videoStreamUpdated.events();
|
-> rpl::producer<StreamsVideoUpdate> {
|
||||||
|
return _streamsVideoUpdated.events();
|
||||||
|
}
|
||||||
|
[[nodiscard]] uint32 videoStreamLarge() const {
|
||||||
|
return _videoStreamLarge.current();
|
||||||
|
}
|
||||||
|
[[nodiscard]] rpl::producer<uint32> videoStreamLargeValue() const {
|
||||||
|
return _videoStreamLarge.value();
|
||||||
}
|
}
|
||||||
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
|
[[nodiscard]] rpl::producer<Group::RejoinEvent> rejoinEvents() const {
|
||||||
return _rejoinEvents.events();
|
return _rejoinEvents.events();
|
||||||
|
@ -213,8 +232,6 @@ public:
|
||||||
bool isScreenSharing() const;
|
bool isScreenSharing() const;
|
||||||
void switchToCamera();
|
void switchToCamera();
|
||||||
void switchToScreenSharing(const QString &uniqueId);
|
void switchToScreenSharing(const QString &uniqueId);
|
||||||
//void setAudioVolume(bool input, float level);
|
|
||||||
void setAudioDuckingEnabled(bool enabled);
|
|
||||||
|
|
||||||
void toggleMute(const Group::MuteRequest &data);
|
void toggleMute(const Group::MuteRequest &data);
|
||||||
void changeVolume(const Group::VolumeRequest &data);
|
void changeVolume(const Group::VolumeRequest &data);
|
||||||
|
@ -264,8 +281,6 @@ private:
|
||||||
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
|
void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data);
|
||||||
void handleUpdate(const MTPDupdateGroupCall &data);
|
void handleUpdate(const MTPDupdateGroupCall &data);
|
||||||
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
void handleUpdate(const MTPDupdateGroupCallParticipants &data);
|
||||||
void handleRequestError(const MTP::Error &error);
|
|
||||||
void handleControllerError(const QString &error);
|
|
||||||
void ensureControllerCreated();
|
void ensureControllerCreated();
|
||||||
void destroyController();
|
void destroyController();
|
||||||
|
|
||||||
|
@ -304,7 +319,7 @@ private:
|
||||||
const Data::GroupCallParticipant &participant);
|
const Data::GroupCallParticipant &participant);
|
||||||
void addPreparedParticipants();
|
void addPreparedParticipants();
|
||||||
void addPreparedParticipantsDelayed();
|
void addPreparedParticipantsDelayed();
|
||||||
void showVideoStreams(const std::vector<std::uint32_t> &ssrcs);
|
void setVideoStreams(const std::vector<std::uint32_t> &ssrcs);
|
||||||
|
|
||||||
void editParticipant(
|
void editParticipant(
|
||||||
not_null<PeerData*> participantPeer,
|
not_null<PeerData*> participantPeer,
|
||||||
|
@ -347,6 +362,7 @@ private:
|
||||||
QString _joinHash;
|
QString _joinHash;
|
||||||
|
|
||||||
rpl::variable<MuteState> _muted = MuteState::Muted;
|
rpl::variable<MuteState> _muted = MuteState::Muted;
|
||||||
|
rpl::variable<bool> _videoCall = false;
|
||||||
bool _initialMuteStateSent = false;
|
bool _initialMuteStateSent = false;
|
||||||
bool _acceptFields = false;
|
bool _acceptFields = false;
|
||||||
|
|
||||||
|
@ -365,7 +381,10 @@ private:
|
||||||
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;
|
std::shared_ptr<tgcalls::VideoCaptureInterface> _videoCapture;
|
||||||
const std::unique_ptr<Webrtc::VideoTrack> _videoOutgoing;
|
const std::unique_ptr<Webrtc::VideoTrack> _videoOutgoing;
|
||||||
rpl::event_stream<LevelUpdate> _levelUpdates;
|
rpl::event_stream<LevelUpdate> _levelUpdates;
|
||||||
rpl::event_stream<uint32> _videoStreamUpdated;
|
rpl::event_stream<StreamsVideoUpdate> _streamsVideoUpdated;
|
||||||
|
base::flat_set<uint32> _videoStreamSsrcs;
|
||||||
|
rpl::variable<uint32> _videoStreamLarge = 0;
|
||||||
|
uint32 _videoStreamPinned = 0;
|
||||||
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
|
||||||
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
|
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
|
||||||
rpl::event_stream<> _allowedToSpeakNotifications;
|
rpl::event_stream<> _allowedToSpeakNotifications;
|
||||||
|
|
|
@ -47,4 +47,9 @@ struct JoinInfo {
|
||||||
TimeId scheduleDate = 0;
|
TimeId scheduleDate = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class PanelMode {
|
||||||
|
Default,
|
||||||
|
Wide,
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Calls::Group
|
} // namespace Calls::Group
|
||||||
|
|
|
@ -143,6 +143,7 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] not_null<Webrtc::VideoTrack*> createVideoTrack();
|
[[nodiscard]] not_null<Webrtc::VideoTrack*> createVideoTrack();
|
||||||
|
void clearVideoTrack();
|
||||||
void setVideoTrack(not_null<Webrtc::VideoTrack*> track);
|
void setVideoTrack(not_null<Webrtc::VideoTrack*> track);
|
||||||
|
|
||||||
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
|
void addActionRipple(QPoint point, Fn<void()> updateCallback) override;
|
||||||
|
@ -352,12 +353,14 @@ private:
|
||||||
not_null<Row*> row,
|
not_null<Row*> row,
|
||||||
uint64 raiseHandRating) const;
|
uint64 raiseHandRating) const;
|
||||||
Row *findRow(not_null<PeerData*> participantPeer) const;
|
Row *findRow(not_null<PeerData*> participantPeer) const;
|
||||||
|
Row *findRow(uint32 audioSsrc) const;
|
||||||
|
|
||||||
void appendInvitedUsers();
|
void appendInvitedUsers();
|
||||||
void scheduleRaisedHandStatusRemove();
|
void scheduleRaisedHandStatusRemove();
|
||||||
|
|
||||||
const not_null<GroupCall*> _call;
|
const not_null<GroupCall*> _call;
|
||||||
not_null<PeerData*> _peer;
|
not_null<PeerData*> _peer;
|
||||||
|
uint32 _largeSsrc = 0;
|
||||||
bool _prepared = false;
|
bool _prepared = false;
|
||||||
|
|
||||||
rpl::event_stream<MuteRequest> _toggleMuteRequests;
|
rpl::event_stream<MuteRequest> _toggleMuteRequests;
|
||||||
|
@ -926,6 +929,13 @@ not_null<Webrtc::VideoTrack*> Row::createVideoTrack() {
|
||||||
return _videoTrack.get();
|
return _videoTrack.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Row::clearVideoTrack() {
|
||||||
|
_videoTrackLifetime.destroy();
|
||||||
|
_videoTrackShown = nullptr;
|
||||||
|
_videoTrack = nullptr;
|
||||||
|
_delegate->rowUpdateRow(this);
|
||||||
|
}
|
||||||
|
|
||||||
void Row::setVideoTrack(not_null<Webrtc::VideoTrack*> track) {
|
void Row::setVideoTrack(not_null<Webrtc::VideoTrack*> track) {
|
||||||
_videoTrackLifetime.destroy();
|
_videoTrackLifetime.destroy();
|
||||||
_videoTrackShown = track;
|
_videoTrackShown = track;
|
||||||
|
@ -1043,17 +1053,29 @@ void MembersController::setupListChangeViewers() {
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
_call->videoStreamUpdated(
|
|
||||||
) | rpl::start_with_next([=](uint32 ssrc) {
|
_call->videoStreamLargeValue(
|
||||||
const auto real = _call->lookupReal();
|
) | rpl::filter([=](uint32 largeSsrc) {
|
||||||
const auto participantPeer = real
|
return (_largeSsrc != largeSsrc);
|
||||||
? real->participantPeerByAudioSsrc(ssrc)
|
}) | rpl::start_with_next([=](uint32 largeSsrc) {
|
||||||
: nullptr;
|
if (const auto row = findRow(_largeSsrc)) {
|
||||||
const auto row = participantPeer
|
_call->addVideoOutput(_largeSsrc, row->createVideoTrack());
|
||||||
? findRow(participantPeer)
|
}
|
||||||
: nullptr;
|
_largeSsrc = largeSsrc;
|
||||||
if (row) {
|
if (const auto row = findRow(_largeSsrc)) {
|
||||||
_call->addVideoOutput(ssrc, row->createVideoTrack());
|
row->clearVideoTrack();
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
|
_call->streamsVideoUpdates(
|
||||||
|
) | rpl::start_with_next([=](StreamsVideoUpdate update) {
|
||||||
|
Assert(update.ssrc != _largeSsrc);
|
||||||
|
if (const auto row = findRow(update.ssrc)) {
|
||||||
|
if (update.streams) {
|
||||||
|
_call->addVideoOutput(update.ssrc, row->createVideoTrack());
|
||||||
|
} else {
|
||||||
|
row->clearVideoTrack();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
|
||||||
|
@ -1345,8 +1367,6 @@ void MembersController::updateRow(
|
||||||
}
|
}
|
||||||
if (isMe(row->peer())) {
|
if (isMe(row->peer())) {
|
||||||
row->setVideoTrack(_call->outgoingVideoTrack());
|
row->setVideoTrack(_call->outgoingVideoTrack());
|
||||||
} else if (nowSsrc) {
|
|
||||||
_call->addVideoOutput(nowSsrc, row->createVideoTrack());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto nowNoSounding = _soundingRowBySsrc.empty();
|
const auto nowNoSounding = _soundingRowBySsrc.empty();
|
||||||
|
@ -1378,6 +1398,17 @@ Row *MembersController::findRow(not_null<PeerData*> participantPeer) const {
|
||||||
delegate()->peerListFindRow(participantPeer->id.value));
|
delegate()->peerListFindRow(participantPeer->id.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row *MembersController::findRow(uint32 audioSsrc) const {
|
||||||
|
if (!audioSsrc) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const auto real = _call->lookupReal();
|
||||||
|
const auto participantPeer = real
|
||||||
|
? real->participantPeerByAudioSsrc(audioSsrc)
|
||||||
|
: nullptr;
|
||||||
|
return participantPeer ? findRow(participantPeer) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
Main::Session &MembersController::session() const {
|
Main::Session &MembersController::session() const {
|
||||||
return _call->peer()->session();
|
return _call->peer()->session();
|
||||||
}
|
}
|
||||||
|
@ -1943,8 +1974,11 @@ int Members::desiredHeight() const {
|
||||||
return 0;
|
return 0;
|
||||||
}();
|
}();
|
||||||
const auto use = std::max(count, _list->fullRowsCount());
|
const auto use = std::max(count, _list->fullRowsCount());
|
||||||
|
const auto single = (_mode == PanelMode::Wide)
|
||||||
|
? (st::groupCallNarrowSize.height() + st::groupCallNarrowRowSkip)
|
||||||
|
: st::groupCallMembersList.item.height;
|
||||||
return top
|
return top
|
||||||
+ (use * st::groupCallMembersList.item.height)
|
+ (use * single)
|
||||||
+ (use ? st::lineWidth : 0);
|
+ (use ? st::lineWidth : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2008,6 +2042,16 @@ void Members::setupAddMember(not_null<GroupCall*> call) {
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Members::setMode(PanelMode mode) {
|
||||||
|
if (_mode == mode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_mode = mode;
|
||||||
|
_list->setMode((_mode == PanelMode::Wide)
|
||||||
|
? PeerListContent::Mode::Custom
|
||||||
|
: PeerListContent::Mode::Default);
|
||||||
|
}
|
||||||
|
|
||||||
rpl::producer<int> Members::fullCountValue() const {
|
rpl::producer<int> Members::fullCountValue() const {
|
||||||
return static_cast<MembersController*>(
|
return static_cast<MembersController*>(
|
||||||
_listController.get())->fullCountValue();
|
_listController.get())->fullCountValue();
|
||||||
|
|
|
@ -26,6 +26,7 @@ namespace Calls::Group {
|
||||||
|
|
||||||
struct VolumeRequest;
|
struct VolumeRequest;
|
||||||
struct MuteRequest;
|
struct MuteRequest;
|
||||||
|
enum class PanelMode;
|
||||||
|
|
||||||
class Members final
|
class Members final
|
||||||
: public Ui::RpWidget
|
: public Ui::RpWidget
|
||||||
|
@ -48,6 +49,8 @@ public:
|
||||||
return _addMemberRequests.events();
|
return _addMemberRequests.events();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void setMode(PanelMode mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
using ListWidget = PeerListContent;
|
using ListWidget = PeerListContent;
|
||||||
|
|
||||||
|
@ -76,6 +79,7 @@ private:
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
|
|
||||||
const not_null<GroupCall*> _call;
|
const not_null<GroupCall*> _call;
|
||||||
|
PanelMode _mode = PanelMode();
|
||||||
object_ptr<Ui::ScrollArea> _scroll;
|
object_ptr<Ui::ScrollArea> _scroll;
|
||||||
std::unique_ptr<PeerListController> _listController;
|
std::unique_ptr<PeerListController> _listController;
|
||||||
object_ptr<Ui::SettingsButton> _addMember = { nullptr };
|
object_ptr<Ui::SettingsButton> _addMember = { nullptr };
|
||||||
|
|
|
@ -525,6 +525,11 @@ void Panel::initWindow() {
|
||||||
? (Flag::Move | Flag::Maximize)
|
? (Flag::Move | Flag::Maximize)
|
||||||
: Flag::None;
|
: Flag::None;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_call->videoCallValue(
|
||||||
|
) | rpl::start_with_next([=] {
|
||||||
|
updateMode();
|
||||||
|
}, _window->lifetime());
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::initWidget() {
|
void Panel::initWidget() {
|
||||||
|
@ -536,8 +541,10 @@ void Panel::initWidget() {
|
||||||
}, widget()->lifetime());
|
}, widget()->lifetime());
|
||||||
|
|
||||||
widget()->sizeValue(
|
widget()->sizeValue(
|
||||||
) | rpl::skip(1) | rpl::start_with_next([=] {
|
) | rpl::skip(1) | rpl::start_with_next([=](QSize size) {
|
||||||
updateControlsGeometry();
|
if (!updateMode()) {
|
||||||
|
updateControlsGeometry();
|
||||||
|
}
|
||||||
|
|
||||||
// title geometry depends on _controls->geometry,
|
// title geometry depends on _controls->geometry,
|
||||||
// which is not updated here yet.
|
// which is not updated here yet.
|
||||||
|
@ -1384,6 +1391,21 @@ QRect Panel::computeTitleRect() const {
|
||||||
#endif // !Q_OS_MAC
|
#endif // !Q_OS_MAC
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Panel::updateMode() {
|
||||||
|
const auto wide = _call->videoCall()
|
||||||
|
&& (widget()->width() >= st::groupCallWideModeWidthMin);
|
||||||
|
const auto mode = wide ? PanelMode::Wide : PanelMode::Default;
|
||||||
|
if (_mode == mode) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_mode = mode;
|
||||||
|
if (_members) {
|
||||||
|
_members->setMode(mode);
|
||||||
|
}
|
||||||
|
updateControlsGeometry();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void Panel::updateControlsGeometry() {
|
void Panel::updateControlsGeometry() {
|
||||||
if (widget()->size().isEmpty() || (!_settings && !_share)) {
|
if (widget()->size().isEmpty() || (!_settings && !_share)) {
|
||||||
return;
|
return;
|
||||||
|
@ -1434,27 +1456,35 @@ void Panel::updateMembersGeometry() {
|
||||||
if (!_members) {
|
if (!_members) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip;
|
|
||||||
const auto membersTop = st::groupCallMembersTop;
|
|
||||||
const auto availableHeight = muteTop
|
|
||||||
- membersTop
|
|
||||||
- st::groupCallMembersMargin.bottom();
|
|
||||||
const auto desiredHeight = _members->desiredHeight();
|
const auto desiredHeight = _members->desiredHeight();
|
||||||
const auto membersWidthAvailable = widget()->width()
|
if (_mode == PanelMode::Wide) {
|
||||||
- st::groupCallMembersMargin.left()
|
_members->setGeometry(
|
||||||
- st::groupCallMembersMargin.right();
|
st::groupCallNarrowSkip,
|
||||||
const auto membersWidthMin = st::groupCallWidth
|
0,
|
||||||
- st::groupCallMembersMargin.left()
|
st::groupCallNarrowSize.width(),
|
||||||
- st::groupCallMembersMargin.right();
|
std::min(desiredHeight, widget()->height()));
|
||||||
const auto membersWidth = std::clamp(
|
} else {
|
||||||
membersWidthAvailable,
|
const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip;
|
||||||
membersWidthMin,
|
const auto membersTop = st::groupCallMembersTop;
|
||||||
st::groupCallMembersWidthMax);
|
const auto availableHeight = muteTop
|
||||||
_members->setGeometry(
|
- membersTop
|
||||||
(widget()->width() - membersWidth) / 2,
|
- st::groupCallMembersMargin.bottom();
|
||||||
membersTop,
|
const auto membersWidthAvailable = widget()->width()
|
||||||
membersWidth,
|
- st::groupCallMembersMargin.left()
|
||||||
std::min(desiredHeight, availableHeight));
|
- st::groupCallMembersMargin.right();
|
||||||
|
const auto membersWidthMin = st::groupCallWidth
|
||||||
|
- st::groupCallMembersMargin.left()
|
||||||
|
- st::groupCallMembersMargin.right();
|
||||||
|
const auto membersWidth = std::clamp(
|
||||||
|
membersWidthAvailable,
|
||||||
|
membersWidthMin,
|
||||||
|
st::groupCallMembersWidthMax);
|
||||||
|
_members->setGeometry(
|
||||||
|
(widget()->width() - membersWidth) / 2,
|
||||||
|
membersTop,
|
||||||
|
membersWidth,
|
||||||
|
std::min(desiredHeight, availableHeight));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Panel::refreshTitle() {
|
void Panel::refreshTitle() {
|
||||||
|
|
|
@ -53,6 +53,7 @@ struct CallBodyLayout;
|
||||||
namespace Calls::Group {
|
namespace Calls::Group {
|
||||||
|
|
||||||
class Members;
|
class Members;
|
||||||
|
enum class PanelMode;
|
||||||
|
|
||||||
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
|
class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate {
|
||||||
public:
|
public:
|
||||||
|
@ -88,6 +89,7 @@ private:
|
||||||
bool handleClose();
|
bool handleClose();
|
||||||
void startScheduledNow();
|
void startScheduledNow();
|
||||||
|
|
||||||
|
bool updateMode();
|
||||||
void updateControlsGeometry();
|
void updateControlsGeometry();
|
||||||
void updateMembersGeometry();
|
void updateMembersGeometry();
|
||||||
void showControls();
|
void showControls();
|
||||||
|
@ -118,6 +120,7 @@ private:
|
||||||
|
|
||||||
const std::unique_ptr<Ui::Window> _window;
|
const std::unique_ptr<Ui::Window> _window;
|
||||||
const std::unique_ptr<Ui::LayerManager> _layerBg;
|
const std::unique_ptr<Ui::LayerManager> _layerBg;
|
||||||
|
PanelMode _mode = PanelMode();
|
||||||
|
|
||||||
#ifndef Q_OS_MAC
|
#ifndef Q_OS_MAC
|
||||||
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
std::unique_ptr<Ui::Platform::TitleControls> _controls;
|
||||||
|
|
|
@ -93,8 +93,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/changelogs.h"
|
#include "core/changelogs.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
|
#include "calls/calls_call.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "calls/calls_top_bar.h"
|
#include "calls/calls_top_bar.h"
|
||||||
|
#include "calls/group/calls_group_call.h"
|
||||||
#include "export/export_settings.h"
|
#include "export/export_settings.h"
|
||||||
#include "export/export_manager.h"
|
#include "export/export_manager.h"
|
||||||
#include "export/view/export_view_top_bar.h"
|
#include "export/view/export_view_top_bar.h"
|
||||||
|
|
|
@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "core/core_settings.h"
|
#include "core/core_settings.h"
|
||||||
|
#include "calls/calls_call.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
#include "calls/calls_video_bubble.h"
|
#include "calls/calls_video_bubble.h"
|
||||||
#include "webrtc/webrtc_media_devices.h"
|
#include "webrtc/webrtc_media_devices.h"
|
||||||
|
@ -250,40 +251,6 @@ void Calls::setupContent() {
|
||||||
AddSkip(content);
|
AddSkip(content);
|
||||||
AddSubsectionTitle(content, tr::lng_settings_call_section_other());
|
AddSubsectionTitle(content, tr::lng_settings_call_section_other());
|
||||||
|
|
||||||
//#if defined Q_OS_MAC && !defined OS_MAC_STORE
|
|
||||||
// AddButton(
|
|
||||||
// content,
|
|
||||||
// tr::lng_settings_call_audio_ducking(),
|
|
||||||
// st::settingsButton
|
|
||||||
// )->toggleOn(
|
|
||||||
// rpl::single(settings.callAudioDuckingEnabled())
|
|
||||||
// )->toggledValue() | rpl::filter([](bool enabled) {
|
|
||||||
// return (enabled != Core::App().settings().callAudioDuckingEnabled());
|
|
||||||
// }) | rpl::start_with_next([=](bool enabled) {
|
|
||||||
// Core::App().settings().setCallAudioDuckingEnabled(enabled);
|
|
||||||
// Core::App().saveSettingsDelayed();
|
|
||||||
// if (const auto call = Core::App().calls().currentCall()) {
|
|
||||||
// call->setAudioDuckingEnabled(enabled);
|
|
||||||
// }
|
|
||||||
// }, content->lifetime());
|
|
||||||
//#endif // Q_OS_MAC && !OS_MAC_STORE
|
|
||||||
|
|
||||||
//const auto backend = [&]() -> QString {
|
|
||||||
// using namespace Webrtc;
|
|
||||||
// switch (settings.callAudioBackend()) {
|
|
||||||
// case Backend::OpenAL: return "OpenAL";
|
|
||||||
// case Backend::ADM: return "WebRTC ADM";
|
|
||||||
// case Backend::ADM2: return "WebRTC ADM2";
|
|
||||||
// }
|
|
||||||
// Unexpected("Value in backend.");
|
|
||||||
//}();
|
|
||||||
//AddButton(
|
|
||||||
// content,
|
|
||||||
// rpl::single("Call audio backend: " + backend),
|
|
||||||
// st::settingsButton
|
|
||||||
//)->addClickHandler([] {
|
|
||||||
// Ui::show(ChooseAudioBackendBox());
|
|
||||||
//});
|
|
||||||
AddButton(
|
AddButton(
|
||||||
content,
|
content,
|
||||||
tr::lng_settings_call_accept_calls(),
|
tr::lng_settings_call_accept_calls(),
|
||||||
|
|
|
@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "ui/toast/toast.h"
|
#include "ui/toast/toast.h"
|
||||||
#include "ui/toasts/common_toasts.h"
|
#include "ui/toasts/common_toasts.h"
|
||||||
#include "calls/calls_instance.h" // Core::App().calls().inCall().
|
#include "calls/calls_instance.h" // Core::App().calls().inCall().
|
||||||
|
#include "calls/group/calls_group_call.h"
|
||||||
#include "ui/boxes/calendar_box.h"
|
#include "ui/boxes/calendar_box.h"
|
||||||
#include "boxes/confirm_box.h"
|
#include "boxes/confirm_box.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
|
Loading…
Add table
Reference in a new issue