804 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			804 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
This file is part of Telegram Desktop,
 | 
						|
the official desktop application for the Telegram messaging service.
 | 
						|
 | 
						|
For license and copyright information please follow this link:
 | 
						|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 | 
						|
*/
 | 
						|
#include "calls/calls_instance.h"
 | 
						|
 | 
						|
#include "calls/calls_call.h"
 | 
						|
#include "calls/group/calls_group_common.h"
 | 
						|
#include "calls/group/calls_choose_join_as.h"
 | 
						|
#include "calls/group/calls_group_call.h"
 | 
						|
#include "calls/group/calls_group_rtmp.h"
 | 
						|
#include "mtproto/mtproto_dh_utils.h"
 | 
						|
#include "core/application.h"
 | 
						|
#include "core/core_settings.h"
 | 
						|
#include "main/main_session.h"
 | 
						|
#include "main/main_account.h"
 | 
						|
#include "apiwrap.h"
 | 
						|
#include "lang/lang_keys.h"
 | 
						|
#include "ui/boxes/confirm_box.h"
 | 
						|
#include "calls/group/calls_group_call.h"
 | 
						|
#include "calls/group/calls_group_panel.h"
 | 
						|
#include "calls/calls_call.h"
 | 
						|
#include "calls/calls_panel.h"
 | 
						|
#include "data/data_user.h"
 | 
						|
#include "data/data_group_call.h"
 | 
						|
#include "data/data_channel.h"
 | 
						|
#include "data/data_chat.h"
 | 
						|
#include "data/data_session.h"
 | 
						|
#include "media/audio/media_audio_track.h"
 | 
						|
#include "platform/platform_specific.h"
 | 
						|
#include "ui/toast/toast.h"
 | 
						|
#include "base/unixtime.h"
 | 
						|
#include "mtproto/mtproto_config.h"
 | 
						|
#include "boxes/abstract_box.h" // Ui::show().
 | 
						|
 | 
						|
#include <tgcalls/VideoCaptureInterface.h>
 | 
						|
#include <tgcalls/StaticThreads.h>
 | 
						|
 | 
						|
namespace Calls {
 | 
						|
namespace {
 | 
						|
 | 
						|
constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000);
 | 
						|
 | 
						|
using CallSound = Call::Delegate::CallSound;
 | 
						|
using GroupCallSound = GroupCall::Delegate::GroupCallSound;
 | 
						|
 | 
						|
} // namespace
 | 
						|
 | 
						|
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(
 | 
						|
		const QString &deviceId,
 | 
						|
		bool isScreenCapture)
 | 
						|
	-> 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;
 | 
						|
	FnMut<void()> groupCallAddAsyncWaiter() 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(
 | 
						|
	const QString &deviceId,
 | 
						|
	bool isScreenCapture)
 | 
						|
-> std::shared_ptr<tgcalls::VideoCaptureInterface> {
 | 
						|
	return _instance->getVideoCapture(deviceId, isScreenCapture);
 | 
						|
}
 | 
						|
 | 
						|
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, false);
 | 
						|
}
 | 
						|
 | 
						|
FnMut<void()> Instance::Delegate::groupCallAddAsyncWaiter() {
 | 
						|
	return _instance->addAsyncWaiter();
 | 
						|
}
 | 
						|
 | 
						|
Instance::Instance()
 | 
						|
: _delegate(std::make_unique<Delegate>(this))
 | 
						|
, _cachedDhConfig(std::make_unique<DhConfig>())
 | 
						|
, _chooseJoinAs(std::make_unique<Group::ChooseJoinAsProcess>())
 | 
						|
, _startWithRtmp(std::make_unique<Group::StartRtmpProcess>()) {
 | 
						|
}
 | 
						|
 | 
						|
Instance::~Instance() {
 | 
						|
	destroyCurrentCall();
 | 
						|
 | 
						|
	while (!_asyncWaiters.empty()) {
 | 
						|
		_asyncWaiters.front()->acquire();
 | 
						|
		_asyncWaiters.erase(_asyncWaiters.begin());
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::startOutgoingCall(not_null<UserData*> user, bool video) {
 | 
						|
	if (activateCurrentCall()) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (user->callsStatus() == UserData::CallsStatus::Private) {
 | 
						|
		// Request full user once more to refresh the setting in case it was changed.
 | 
						|
		user->session().api().requestFullPeer(user);
 | 
						|
		Ui::show(Ui::MakeInformBox(tr::lng_call_error_not_available(
 | 
						|
			tr::now,
 | 
						|
			lt_user,
 | 
						|
			user->name())));
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	requestPermissionsOrFail(crl::guard(this, [=] {
 | 
						|
		createCall(user, Call::Type::Outgoing, video);
 | 
						|
	}), video);
 | 
						|
}
 | 
						|
 | 
						|
void Instance::startOrJoinGroupCall(
 | 
						|
		std::shared_ptr<Ui::Show> show,
 | 
						|
		not_null<PeerData*> peer,
 | 
						|
		StartGroupCallArgs args) {
 | 
						|
	confirmLeaveCurrent(show, peer, args, [=](StartGroupCallArgs args) {
 | 
						|
		using JoinConfirm = Calls::StartGroupCallArgs::JoinConfirm;
 | 
						|
		const auto context = (args.confirm == JoinConfirm::Always)
 | 
						|
			? Group::ChooseJoinAsProcess::Context::JoinWithConfirm
 | 
						|
			: peer->groupCall()
 | 
						|
			? Group::ChooseJoinAsProcess::Context::Join
 | 
						|
			: args.scheduleNeeded
 | 
						|
			? Group::ChooseJoinAsProcess::Context::CreateScheduled
 | 
						|
			: Group::ChooseJoinAsProcess::Context::Create;
 | 
						|
		_chooseJoinAs->start(peer, context, show, [=](Group::JoinInfo info) {
 | 
						|
			const auto call = info.peer->groupCall();
 | 
						|
			info.joinHash = args.joinHash;
 | 
						|
			if (call) {
 | 
						|
				info.rtmp = call->rtmp();
 | 
						|
			}
 | 
						|
			createGroupCall(
 | 
						|
				std::move(info),
 | 
						|
				call ? call->input() : MTP_inputGroupCall({}, {}));
 | 
						|
		});
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Instance::confirmLeaveCurrent(
 | 
						|
		std::shared_ptr<Ui::Show> show,
 | 
						|
		not_null<PeerData*> peer,
 | 
						|
		StartGroupCallArgs args,
 | 
						|
		Fn<void(StartGroupCallArgs)> confirmed) {
 | 
						|
	using JoinConfirm = Calls::StartGroupCallArgs::JoinConfirm;
 | 
						|
 | 
						|
	auto confirmedArgs = args;
 | 
						|
	confirmedArgs.confirm = JoinConfirm::None;
 | 
						|
 | 
						|
	const auto askConfirmation = [&](QString text, QString button) {
 | 
						|
		show->showBox(Ui::MakeConfirmBox({
 | 
						|
			.text = text,
 | 
						|
			.confirmed = [=] {
 | 
						|
				show->hideLayer();
 | 
						|
				confirmed(confirmedArgs);
 | 
						|
			},
 | 
						|
			.confirmText = button,
 | 
						|
		}));
 | 
						|
	};
 | 
						|
	if (args.confirm != JoinConfirm::None && inCall()) {
 | 
						|
		// Do you want to leave your active voice chat
 | 
						|
		// to join a voice chat in this group?
 | 
						|
		askConfirmation(
 | 
						|
			(peer->isBroadcast()
 | 
						|
				? tr::lng_call_leave_to_other_sure_channel
 | 
						|
				: tr::lng_call_leave_to_other_sure)(tr::now),
 | 
						|
			tr::lng_call_bar_hangup(tr::now));
 | 
						|
	} else if (args.confirm != JoinConfirm::None && inGroupCall()) {
 | 
						|
		const auto now = currentGroupCall()->peer();
 | 
						|
		if (now == peer) {
 | 
						|
			activateCurrentCall(args.joinHash);
 | 
						|
		} else if (currentGroupCall()->scheduleDate()) {
 | 
						|
			confirmed(confirmedArgs);
 | 
						|
		} else {
 | 
						|
			askConfirmation(
 | 
						|
				((peer->isBroadcast() && now->isBroadcast())
 | 
						|
					? tr::lng_group_call_leave_channel_to_other_sure_channel
 | 
						|
					: now->isBroadcast()
 | 
						|
					? tr::lng_group_call_leave_channel_to_other_sure
 | 
						|
					: peer->isBroadcast()
 | 
						|
					? tr::lng_group_call_leave_to_other_sure_channel
 | 
						|
					: tr::lng_group_call_leave_to_other_sure)(tr::now),
 | 
						|
				tr::lng_group_call_leave(tr::now));
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		confirmed(args);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::showStartWithRtmp(
 | 
						|
		std::shared_ptr<Ui::Show> show,
 | 
						|
		not_null<PeerData*> peer) {
 | 
						|
	_startWithRtmp->start(peer, show, [=](Group::JoinInfo info) {
 | 
						|
		confirmLeaveCurrent(show, peer, {}, [=](auto) {
 | 
						|
			_startWithRtmp->close();
 | 
						|
			createGroupCall(std::move(info), MTP_inputGroupCall({}, {}));
 | 
						|
		});
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
not_null<Media::Audio::Track*> Instance::ensureSoundLoaded(
 | 
						|
		const QString &key) {
 | 
						|
	const auto i = _tracks.find(key);
 | 
						|
	if (i != end(_tracks)) {
 | 
						|
		return i->second.get();
 | 
						|
	}
 | 
						|
	const auto result = _tracks.emplace(
 | 
						|
		key,
 | 
						|
		Media::Audio::Current().createTrack()).first->second.get();
 | 
						|
	result->fillFromFile(Core::App().settings().getSoundPath(key));
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
void Instance::playSoundOnce(const QString &key) {
 | 
						|
	ensureSoundLoaded(key)->playOnce();
 | 
						|
}
 | 
						|
 | 
						|
void Instance::destroyCall(not_null<Call*> call) {
 | 
						|
	if (_currentCall.get() == call) {
 | 
						|
		_currentCallPanel->closeBeforeDestroy();
 | 
						|
		_currentCallPanel = nullptr;
 | 
						|
 | 
						|
		auto taken = base::take(_currentCall);
 | 
						|
		_currentCallChanges.fire(nullptr);
 | 
						|
		taken.reset();
 | 
						|
 | 
						|
		if (Core::Quitting()) {
 | 
						|
			LOG(("Calls::Instance doesn't prevent quit any more."));
 | 
						|
		}
 | 
						|
		Core::App().quitPreventFinished();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::createCall(not_null<UserData*> user, Call::Type type, bool video) {
 | 
						|
	auto call = std::make_unique<Call>(_delegate.get(), user, type, video);
 | 
						|
	const auto raw = call.get();
 | 
						|
 | 
						|
	user->session().account().sessionChanges(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		destroyCall(raw);
 | 
						|
	}, raw->lifetime());
 | 
						|
 | 
						|
	if (_currentCall) {
 | 
						|
		_currentCallPanel->replaceCall(raw);
 | 
						|
		std::swap(_currentCall, call);
 | 
						|
		call->hangup();
 | 
						|
	} else {
 | 
						|
		_currentCallPanel = std::make_unique<Panel>(raw);
 | 
						|
		_currentCall = std::move(call);
 | 
						|
	}
 | 
						|
	_currentCallChanges.fire_copy(raw);
 | 
						|
	refreshServerConfig(&user->session());
 | 
						|
	refreshDhConfig();
 | 
						|
}
 | 
						|
 | 
						|
void Instance::destroyGroupCall(not_null<GroupCall*> call) {
 | 
						|
	if (_currentGroupCall.get() == call) {
 | 
						|
		_currentGroupCallPanel->closeBeforeDestroy();
 | 
						|
		_currentGroupCallPanel = nullptr;
 | 
						|
 | 
						|
		auto taken = base::take(_currentGroupCall);
 | 
						|
		_currentGroupCallChanges.fire(nullptr);
 | 
						|
		taken.reset();
 | 
						|
 | 
						|
		if (Core::Quitting()) {
 | 
						|
			LOG(("Calls::Instance doesn't prevent quit any more."));
 | 
						|
		}
 | 
						|
		Core::App().quitPreventFinished();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::createGroupCall(
 | 
						|
		Group::JoinInfo info,
 | 
						|
		const MTPInputGroupCall &inputCall) {
 | 
						|
	destroyCurrentCall();
 | 
						|
 | 
						|
	auto call = std::make_unique<GroupCall>(
 | 
						|
		_delegate.get(),
 | 
						|
		std::move(info),
 | 
						|
		inputCall);
 | 
						|
	const auto raw = call.get();
 | 
						|
 | 
						|
	info.peer->session().account().sessionChanges(
 | 
						|
	) | rpl::start_with_next([=] {
 | 
						|
		destroyGroupCall(raw);
 | 
						|
	}, raw->lifetime());
 | 
						|
 | 
						|
	_currentGroupCallPanel = std::make_unique<Group::Panel>(raw);
 | 
						|
	_currentGroupCall = std::move(call);
 | 
						|
	_currentGroupCallChanges.fire_copy(raw);
 | 
						|
}
 | 
						|
 | 
						|
void Instance::refreshDhConfig() {
 | 
						|
	Expects(_currentCall != nullptr);
 | 
						|
 | 
						|
	const auto weak = base::make_weak(_currentCall);
 | 
						|
	_currentCall->user()->session().api().request(MTPmessages_GetDhConfig(
 | 
						|
		MTP_int(_cachedDhConfig->version),
 | 
						|
		MTP_int(MTP::ModExpFirst::kRandomPowerSize)
 | 
						|
	)).done([=](const MTPmessages_DhConfig &result) {
 | 
						|
		const auto call = weak.get();
 | 
						|
		const auto random = updateDhConfig(result);
 | 
						|
		if (!call) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		if (!random.empty()) {
 | 
						|
			Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize);
 | 
						|
			call->start(random);
 | 
						|
		} else {
 | 
						|
			_delegate->callFailed(call);
 | 
						|
		}
 | 
						|
	}).fail([=] {
 | 
						|
		const auto call = weak.get();
 | 
						|
		if (!call) {
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		_delegate->callFailed(call);
 | 
						|
	}).send();
 | 
						|
}
 | 
						|
 | 
						|
bytes::const_span Instance::updateDhConfig(
 | 
						|
		const MTPmessages_DhConfig &data) {
 | 
						|
	const auto validRandom = [](const QByteArray & random) {
 | 
						|
		if (random.size() != MTP::ModExpFirst::kRandomPowerSize) {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
		return true;
 | 
						|
	};
 | 
						|
	return data.match([&](const MTPDmessages_dhConfig &data)
 | 
						|
	-> bytes::const_span {
 | 
						|
		auto primeBytes = bytes::make_vector(data.vp().v);
 | 
						|
		if (!MTP::IsPrimeAndGood(primeBytes, data.vg().v)) {
 | 
						|
			LOG(("API Error: bad p/g received in dhConfig."));
 | 
						|
			return {};
 | 
						|
		} else if (!validRandom(data.vrandom().v)) {
 | 
						|
			return {};
 | 
						|
		}
 | 
						|
		_cachedDhConfig->g = data.vg().v;
 | 
						|
		_cachedDhConfig->p = std::move(primeBytes);
 | 
						|
		_cachedDhConfig->version = data.vversion().v;
 | 
						|
		return bytes::make_span(data.vrandom().v);
 | 
						|
	}, [&](const MTPDmessages_dhConfigNotModified &data)
 | 
						|
	-> bytes::const_span {
 | 
						|
		if (!_cachedDhConfig->g || _cachedDhConfig->p.empty()) {
 | 
						|
			LOG(("API Error: dhConfigNotModified on zero version."));
 | 
						|
			return {};
 | 
						|
		} else if (!validRandom(data.vrandom().v)) {
 | 
						|
			return {};
 | 
						|
		}
 | 
						|
		return bytes::make_span(data.vrandom().v);
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Instance::refreshServerConfig(not_null<Main::Session*> session) {
 | 
						|
	if (_serverConfigRequestSession) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	if (_lastServerConfigUpdateTime
 | 
						|
		&& ((crl::now() - _lastServerConfigUpdateTime)
 | 
						|
			< kServerConfigUpdateTimeoutMs)) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	_serverConfigRequestSession = session;
 | 
						|
	session->api().request(MTPphone_GetCallConfig(
 | 
						|
	)).done([=](const MTPDataJSON &result) {
 | 
						|
		_serverConfigRequestSession = nullptr;
 | 
						|
		_lastServerConfigUpdateTime = crl::now();
 | 
						|
 | 
						|
		const auto &json = result.c_dataJSON().vdata().v;
 | 
						|
		UpdateConfig(std::string(json.data(), json.size()));
 | 
						|
	}).fail([=] {
 | 
						|
		_serverConfigRequestSession = nullptr;
 | 
						|
	}).send();
 | 
						|
}
 | 
						|
 | 
						|
void Instance::handleUpdate(
 | 
						|
		not_null<Main::Session*> session,
 | 
						|
		const MTPUpdate &update) {
 | 
						|
	update.match([&](const MTPDupdatePhoneCall &data) {
 | 
						|
		handleCallUpdate(session, data.vphone_call());
 | 
						|
	}, [&](const MTPDupdatePhoneCallSignalingData &data) {
 | 
						|
		handleSignalingData(session, data);
 | 
						|
	}, [&](const MTPDupdateGroupCall &data) {
 | 
						|
		handleGroupCallUpdate(session, update);
 | 
						|
	}, [&](const MTPDupdateGroupCallConnection &data) {
 | 
						|
		handleGroupCallUpdate(session, update);
 | 
						|
	}, [&](const MTPDupdateGroupCallParticipants &data) {
 | 
						|
		handleGroupCallUpdate(session, update);
 | 
						|
	}, [](const auto &) {
 | 
						|
		Unexpected("Update type in Calls::Instance::handleUpdate.");
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Instance::showInfoPanel(not_null<Call*> call) {
 | 
						|
	if (_currentCall.get() == call) {
 | 
						|
		_currentCallPanel->showAndActivate();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::showInfoPanel(not_null<GroupCall*> call) {
 | 
						|
	if (_currentGroupCall.get() == call) {
 | 
						|
		_currentGroupCallPanel->showAndActivate();
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::setCurrentAudioDevice(bool input, const QString &deviceId) {
 | 
						|
	if (input) {
 | 
						|
		Core::App().settings().setCallInputDeviceId(deviceId);
 | 
						|
	} else {
 | 
						|
		Core::App().settings().setCallOutputDeviceId(deviceId);
 | 
						|
	}
 | 
						|
	Core::App().saveSettingsDelayed();
 | 
						|
	if (const auto call = currentCall()) {
 | 
						|
		call->setCurrentAudioDevice(input, deviceId);
 | 
						|
	} else if (const auto group = currentGroupCall()) {
 | 
						|
		group->setCurrentAudioDevice(input, deviceId);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
FnMut<void()> Instance::addAsyncWaiter() {
 | 
						|
	auto semaphore = std::make_unique<crl::semaphore>();
 | 
						|
	const auto raw = semaphore.get();
 | 
						|
	const auto weak = base::make_weak(this);
 | 
						|
	_asyncWaiters.emplace(std::move(semaphore));
 | 
						|
	return [raw, weak] {
 | 
						|
		raw->release();
 | 
						|
		crl::on_main(weak, [raw, weak] {
 | 
						|
			auto &waiters = weak->_asyncWaiters;
 | 
						|
			auto wrapped = std::unique_ptr<crl::semaphore>(raw);
 | 
						|
			const auto i = waiters.find(wrapped);
 | 
						|
			wrapped.release();
 | 
						|
 | 
						|
			if (i != end(waiters)) {
 | 
						|
				waiters.erase(i);
 | 
						|
			}
 | 
						|
		});
 | 
						|
	};
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::isQuitPrevent() {
 | 
						|
	if (!_currentCall || _currentCall->isIncomingWaiting()) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	_currentCall->hangup();
 | 
						|
	if (!_currentCall) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	LOG(("Calls::Instance prevents quit, hanging up a call..."));
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
void Instance::handleCallUpdate(
 | 
						|
		not_null<Main::Session*> session,
 | 
						|
		const MTPPhoneCall &call) {
 | 
						|
	if (call.type() == mtpc_phoneCallRequested) {
 | 
						|
		auto &phoneCall = call.c_phoneCallRequested();
 | 
						|
		auto user = session->data().userLoaded(phoneCall.vadmin_id());
 | 
						|
		if (!user) {
 | 
						|
			LOG(("API Error: User not loaded for phoneCallRequested."));
 | 
						|
		} else if (user->isSelf()) {
 | 
						|
			LOG(("API Error: Self found in phoneCallRequested."));
 | 
						|
		} else if (_currentCall
 | 
						|
			&& _currentCall->user() == user
 | 
						|
			&& _currentCall->id() == phoneCall.vid().v) {
 | 
						|
			// May be a repeated phoneCallRequested update from getDifference.
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		const auto &config = session->serverConfig();
 | 
						|
		if (inCall() || inGroupCall() || !user || user->isSelf()) {
 | 
						|
			const auto flags = phoneCall.is_video()
 | 
						|
				? MTPphone_DiscardCall::Flag::f_video
 | 
						|
				: MTPphone_DiscardCall::Flag(0);
 | 
						|
			session->api().request(MTPphone_DiscardCall(
 | 
						|
				MTP_flags(flags),
 | 
						|
				MTP_inputPhoneCall(phoneCall.vid(), phoneCall.vaccess_hash()),
 | 
						|
				MTP_int(0),
 | 
						|
				MTP_phoneCallDiscardReasonBusy(),
 | 
						|
				MTP_long(0)
 | 
						|
			)).send();
 | 
						|
		} else if (phoneCall.vdate().v + (config.callRingTimeoutMs / 1000)
 | 
						|
			< base::unixtime::now()) {
 | 
						|
			LOG(("Ignoring too old call."));
 | 
						|
		} else {
 | 
						|
			createCall(user, Call::Type::Incoming, phoneCall.is_video());
 | 
						|
			_currentCall->handleUpdate(call);
 | 
						|
		}
 | 
						|
	} else if (!_currentCall
 | 
						|
		|| (&_currentCall->user()->session() != session)
 | 
						|
		|| !_currentCall->handleUpdate(call)) {
 | 
						|
		DEBUG_LOG(("API Warning: unexpected phone call update %1").arg(call.type()));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::handleGroupCallUpdate(
 | 
						|
		not_null<Main::Session*> session,
 | 
						|
		const MTPUpdate &update) {
 | 
						|
	if (_currentGroupCall
 | 
						|
		&& (&_currentGroupCall->peer()->session() == session)) {
 | 
						|
		update.match([&](const MTPDupdateGroupCall &data) {
 | 
						|
			_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
 | 
						|
		}, [&](const MTPDupdateGroupCallConnection &data) {
 | 
						|
			_currentGroupCall->handlePossibleCreateOrJoinResponse(data);
 | 
						|
		}, [](const auto &) {
 | 
						|
		});
 | 
						|
	}
 | 
						|
 | 
						|
	if (update.type() == mtpc_updateGroupCallConnection) {
 | 
						|
		return;
 | 
						|
	}
 | 
						|
	const auto callId = update.match([](const MTPDupdateGroupCall &data) {
 | 
						|
		return data.vcall().match([](const auto &data) {
 | 
						|
			return data.vid().v;
 | 
						|
		});
 | 
						|
	}, [](const MTPDupdateGroupCallParticipants &data) {
 | 
						|
		return data.vcall().match([&](const MTPDinputGroupCall &data) {
 | 
						|
			return data.vid().v;
 | 
						|
		});
 | 
						|
	}, [](const auto &) -> CallId {
 | 
						|
		Unexpected("Type in Instance::handleGroupCallUpdate.");
 | 
						|
	});
 | 
						|
	if (const auto existing = session->data().groupCall(callId)) {
 | 
						|
		existing->enqueueUpdate(update);
 | 
						|
	} else {
 | 
						|
		applyGroupCallUpdateChecked(session, update);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::applyGroupCallUpdateChecked(
 | 
						|
		not_null<Main::Session*> session,
 | 
						|
		const MTPUpdate &update) {
 | 
						|
	if (_currentGroupCall
 | 
						|
		&& (&_currentGroupCall->peer()->session() == session)) {
 | 
						|
		_currentGroupCall->handleUpdate(update);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
void Instance::handleSignalingData(
 | 
						|
		not_null<Main::Session*> session,
 | 
						|
		const MTPDupdatePhoneCallSignalingData &data) {
 | 
						|
	if (!_currentCall
 | 
						|
		|| (&_currentCall->user()->session() != session)
 | 
						|
		|| !_currentCall->handleSignalingData(data)) {
 | 
						|
		DEBUG_LOG(("API Warning: unexpected call signaling data %1"
 | 
						|
			).arg(data.vphone_call_id().v));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::inCall() const {
 | 
						|
	if (!_currentCall) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	const auto state = _currentCall->state();
 | 
						|
	return (state != Call::State::Busy);
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::inGroupCall() const {
 | 
						|
	if (!_currentGroupCall) {
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
	const auto state = _currentGroupCall->state();
 | 
						|
	return (state != GroupCall::State::HangingUp)
 | 
						|
		&& (state != GroupCall::State::Ended)
 | 
						|
		&& (state != GroupCall::State::FailedHangingUp)
 | 
						|
		&& (state != GroupCall::State::Failed);
 | 
						|
}
 | 
						|
 | 
						|
void Instance::destroyCurrentCall() {
 | 
						|
	if (const auto current = currentCall()) {
 | 
						|
		current->hangup();
 | 
						|
		if (const auto still = currentCall()) {
 | 
						|
			destroyCall(still);
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if (const auto current = currentGroupCall()) {
 | 
						|
		current->hangup();
 | 
						|
		if (const auto still = currentGroupCall()) {
 | 
						|
			destroyGroupCall(still);
 | 
						|
		}
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::hasActivePanel(not_null<Main::Session*> session) const {
 | 
						|
	if (inCall()) {
 | 
						|
		return (&_currentCall->user()->session() == session)
 | 
						|
			&& _currentCallPanel->isActive();
 | 
						|
	} else if (inGroupCall()) {
 | 
						|
		return (&_currentGroupCall->peer()->session() == session)
 | 
						|
			&& _currentGroupCallPanel->isActive();
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::activateCurrentCall(const QString &joinHash) {
 | 
						|
	if (inCall()) {
 | 
						|
		_currentCallPanel->showAndActivate();
 | 
						|
		return true;
 | 
						|
	} else if (inGroupCall()) {
 | 
						|
		if (!joinHash.isEmpty()) {
 | 
						|
			_currentGroupCall->rejoinWithHash(joinHash);
 | 
						|
		}
 | 
						|
		_currentGroupCallPanel->showAndActivate();
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::minimizeCurrentActiveCall() {
 | 
						|
	if (inCall() && _currentCallPanel->isActive()) {
 | 
						|
		_currentCallPanel->minimize();
 | 
						|
		return true;
 | 
						|
	} else if (inGroupCall() && _currentGroupCallPanel->isActive()) {
 | 
						|
		_currentGroupCallPanel->minimize();
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
bool Instance::closeCurrentActiveCall() {
 | 
						|
	if (inGroupCall() && _currentGroupCallPanel->isActive()) {
 | 
						|
		_currentGroupCallPanel->close();
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
Call *Instance::currentCall() const {
 | 
						|
	return _currentCall.get();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<Call*> Instance::currentCallValue() const {
 | 
						|
	return _currentCallChanges.events_starting_with(currentCall());
 | 
						|
}
 | 
						|
 | 
						|
GroupCall *Instance::currentGroupCall() const {
 | 
						|
	return _currentGroupCall.get();
 | 
						|
}
 | 
						|
 | 
						|
rpl::producer<GroupCall*> Instance::currentGroupCallValue() const {
 | 
						|
	return _currentGroupCallChanges.events_starting_with(currentGroupCall());
 | 
						|
}
 | 
						|
 | 
						|
void Instance::requestPermissionsOrFail(Fn<void()> onSuccess, bool video) {
 | 
						|
	using Type = Platform::PermissionType;
 | 
						|
	requestPermissionOrFail(Type::Microphone, [=] {
 | 
						|
		auto callback = [=] { crl::on_main(onSuccess); };
 | 
						|
		if (video) {
 | 
						|
			requestPermissionOrFail(Type::Camera, std::move(callback));
 | 
						|
		} else {
 | 
						|
			callback();
 | 
						|
		}
 | 
						|
	});
 | 
						|
}
 | 
						|
 | 
						|
void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn<void()> onSuccess) {
 | 
						|
	using Status = Platform::PermissionStatus;
 | 
						|
	const auto status = Platform::GetPermissionStatus(type);
 | 
						|
	if (status == Status::Granted) {
 | 
						|
		onSuccess();
 | 
						|
	} else if (status == Status::CanRequest) {
 | 
						|
		Platform::RequestPermission(type, crl::guard(this, [=](Status status) {
 | 
						|
			if (status == Status::Granted) {
 | 
						|
				crl::on_main(onSuccess);
 | 
						|
			} else {
 | 
						|
				if (_currentCall) {
 | 
						|
					_currentCall->hangup();
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}));
 | 
						|
	} else {
 | 
						|
		if (inCall()) {
 | 
						|
			_currentCall->hangup();
 | 
						|
		}
 | 
						|
		if (inGroupCall()) {
 | 
						|
			_currentGroupCall->hangup();
 | 
						|
		}
 | 
						|
		Ui::show(Ui::MakeConfirmBox({
 | 
						|
			.text = tr::lng_no_mic_permission(),
 | 
						|
			.confirmed = crl::guard(this, [=](Fn<void()> &&close) {
 | 
						|
				Platform::OpenSystemSettingsForPermission(type);
 | 
						|
				close();
 | 
						|
			}),
 | 
						|
			.confirmText = tr::lng_menu_settings(),
 | 
						|
		}));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
std::shared_ptr<tgcalls::VideoCaptureInterface> Instance::getVideoCapture(
 | 
						|
		std::optional<QString> deviceId,
 | 
						|
		bool isScreenCapture) {
 | 
						|
	if (auto result = _videoCapture.lock()) {
 | 
						|
		if (deviceId) {
 | 
						|
			result->switchToDevice(
 | 
						|
				(deviceId->isEmpty()
 | 
						|
					? Core::App().settings().callVideoInputDeviceId()
 | 
						|
					: *deviceId).toStdString(),
 | 
						|
				isScreenCapture);
 | 
						|
		}
 | 
						|
		return result;
 | 
						|
	}
 | 
						|
	const auto startDeviceId = (deviceId && !deviceId->isEmpty())
 | 
						|
		? *deviceId
 | 
						|
		: Core::App().settings().callVideoInputDeviceId();
 | 
						|
	auto result = std::shared_ptr<tgcalls::VideoCaptureInterface>(
 | 
						|
		tgcalls::VideoCaptureInterface::Create(
 | 
						|
			tgcalls::StaticThreads::getThreads(),
 | 
						|
			startDeviceId.toStdString()));
 | 
						|
	_videoCapture = result;
 | 
						|
	return result;
 | 
						|
}
 | 
						|
 | 
						|
} // namespace Calls
 |