diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 084df253e..df1ae2845 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1141,6 +1141,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org "lng_call_error_not_available" = "Sorry, {user} doesn't accept calls."; "lng_call_error_incompatible" = "{user}'s app is using an incompatible protocol. They need to update their app before you can call them."; "lng_call_error_outdated" = "{user}'s app does not support calls. They need to update their app before you can call them."; +"lng_call_error_audio_io" = "There seems to be a problem with audio playback on your computer. Please make sure that your computer's speakers and microphone are working and try again."; "lng_call_bar_info" = "Show call info"; "lng_call_bar_hangup" = "End call"; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index bce0bfbb8..be264ba26 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -93,7 +93,7 @@ void Call::generateModExpFirst(base::const_byte_span randomSeed) { auto first = MTP::CreateModExp(_dhConfig.g, _dhConfig.p, randomSeed); if (first.modexp.empty()) { LOG(("Call Error: Could not compute mod-exp first.")); - setState(State::Failed); + finish(FinishType::Failed); return; } @@ -121,7 +121,7 @@ void Call::start(base::const_byte_span random) { t_assert(!_dhConfig.p.empty()); generateModExpFirst(random); - if (_state != State::Failed) { + if (_state != State::Failed && _state != State::FailedHangingUp) { if (_type == Type::Outgoing) { startOutgoing(); } else { @@ -136,11 +136,14 @@ void Call::startOutgoing() { setState(State::Requesting); request(MTPphone_RequestCall(_user->inputUser, MTP_int(rand_value()), MTP_bytes(_gaHash), MTP_phoneCallProtocol(MTP_flags(MTPDphoneCallProtocol::Flag::f_udp_p2p | MTPDphoneCallProtocol::Flag::f_udp_reflector), MTP_int(kMinLayer), MTP_int(kMaxLayer)))).done([this](const MTPphone_PhoneCall &result) { Expects(result.type() == mtpc_phone_phoneCall); + + setState(State::Waiting); + auto &call = result.c_phone_phoneCall(); App::feedUsers(call.vusers); if (call.vphone_call.type() != mtpc_phoneCallWaiting) { LOG(("Call Error: Expected phoneCallWaiting in response to phone.requestCall()")); - setState(State::Failed); + finish(FinishType::Failed); return; } @@ -148,10 +151,12 @@ void Call::startOutgoing() { auto &waitingCall = phoneCall.c_phoneCallWaiting(); _id = waitingCall.vid.v; _accessHash = waitingCall.vaccess_hash.v; - setState(State::Waiting); - - if (_finishAfterRequestingCall) { - hangup(); + if (_finishAfterRequestingCall != FinishType::None) { + if (_finishAfterRequestingCall == FinishType::Failed) { + finish(_finishAfterRequestingCall); + } else { + hangup(); + } return; } @@ -188,7 +193,7 @@ void Call::answer() { App::feedUsers(call.vusers); if (call.vphone_call.type() != mtpc_phoneCallWaiting) { LOG(("Call Error: Expected phoneCallWaiting in response to phone.acceptCall()")); - setState(State::Failed); + finish(FinishType::Failed); return; } @@ -218,7 +223,7 @@ void Call::hangup() { auto declined = (_state == State::WaitingIncoming); auto reason = missed ? MTP_phoneCallDiscardReasonMissed() : declined ? MTP_phoneCallDiscardReasonBusy() : MTP_phoneCallDiscardReasonHangup(); - finish(reason); + finish(FinishType::Ended, reason); } } @@ -282,7 +287,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { } if (AuthSession::CurrentUserId() != data.vparticipant_id.v) { LOG(("Call Error: Wrong call participant_id %1, expected %2.").arg(data.vparticipant_id.v).arg(AuthSession::CurrentUserId())); - setState(State::Failed); + finish(FinishType::Failed); return true; } _id = data.vid.v; @@ -291,7 +296,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { auto gaHashBytes = bytesFromMTP(data.vg_a_hash); if (gaHashBytes.size() != _gaHash.size()) { LOG(("Call Error: Wrong g_a_hash size %1, expected %2.").arg(gaHashBytes.size()).arg(_gaHash.size())); - setState(State::Failed); + finish(FinishType::Failed); return true; } base::copy_bytes(gsl::make_span(_gaHash), gaHashBytes); @@ -303,7 +308,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { return false; } LOG(("Call Error: phoneCallEmpty received.")); - setState(State::Failed); + finish(FinishType::Failed); } return true; case mtpc_phoneCallWaiting: { @@ -358,7 +363,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { } if (_type != Type::Outgoing) { LOG(("Call Error: Unexpected phoneCallAccepted for an incoming call.")); - setState(State::Failed); + finish(FinishType::Failed); } else if (checkCallFields(data)) { confirmAcceptedCall(data); } @@ -375,7 +380,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p); if (computedAuthKey.empty()) { LOG(("Call Error: Could not compute mod-exp final.")); - setState(State::Failed); + finish(FinishType::Failed); return; } @@ -389,7 +394,7 @@ void Call::confirmAcceptedCall(const MTPDphoneCallAccepted &call) { App::feedUsers(call.vusers); if (call.vphone_call.type() != mtpc_phoneCall) { LOG(("Call Error: Expected phoneCall in response to phone.confirmCall()")); - setState(State::Failed); + finish(FinishType::Failed); return; } @@ -405,7 +410,7 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) { auto firstBytes = bytesFromMTP(call.vg_a_or_b); if (_gaHash != openssl::Sha256(firstBytes)) { LOG(("Call Error: Wrong g_a hash received.")); - setState(State::Failed); + finish(FinishType::Failed); return; } _ga = base::byte_vector(firstBytes.begin(), firstBytes.end()); @@ -413,7 +418,7 @@ void Call::startConfirmedCall(const MTPDphoneCall &call) { auto computedAuthKey = MTP::CreateAuthKey(firstBytes, _randomPower, _dhConfig.p); if (computedAuthKey.empty()) { LOG(("Call Error: Could not compute mod-exp final.")); - setState(State::Failed); + finish(FinishType::Failed); return; } @@ -504,7 +509,7 @@ void Call::handleControllerStateChange(tgvoip::VoIPController *controller, int s template bool Call::checkCallCommonFields(const T &call) { auto checkFailed = [this] { - setState(State::Failed); + finish(FinishType::Failed); return false; }; if (call.vaccess_hash.v != _accessHash) { @@ -530,7 +535,7 @@ bool Call::checkCallFields(const MTPDphoneCall &call) { } if (call.vkey_fingerprint.v != _keyFingerprint) { LOG(("Call Error: Wrong call fingerprint.")); - setState(State::Failed); + finish(FinishType::Failed); return false; } return true; @@ -541,6 +546,12 @@ bool Call::checkCallFields(const MTPDphoneCallAccepted &call) { } void Call::setState(State state) { + if (_state == State::Failed) { + return; + } + if (_state == State::FailedHangingUp && state != State::Failed) { + return; + } if (_state != state) { _state = state; _stateChanged.notify(state, true); @@ -583,31 +594,37 @@ void Call::setState(State state) { } } -void Call::finish(const MTPPhoneCallDiscardReason &reason) { +void Call::finish(FinishType type, const MTPPhoneCallDiscardReason &reason) { + Expects(type != FinishType::None); + auto finalState = (type == FinishType::Ended) ? State::Ended : State::Failed; + auto hangupState = (type == FinishType::Ended) ? State::HangingUp : State::FailedHangingUp; if (_state == State::Requesting) { - _finishByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); }); - _finishAfterRequestingCall = true; + _finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); }); + _finishAfterRequestingCall = type; return; } - if (_state == State::HangingUp || _state == State::Ended) { + if (_state == State::HangingUp + || _state == State::FailedHangingUp + || _state == State::Ended + || _state == State::Failed) { return; } if (!_id) { - setState(State::Ended); + setState(finalState); return; } - setState(State::HangingUp); + setState(hangupState); auto duration = getDurationMs() / 1000; auto connectionId = _controller ? _controller->GetPreferredRelayID() : 0; - _finishByTimeoutTimer.call(kHangupTimeoutMs, [this] { setState(State::Ended); }); - request(MTPphone_DiscardCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_int(duration), reason, MTP_long(connectionId))).done([this](const MTPUpdates &result) { + _finishByTimeoutTimer.call(kHangupTimeoutMs, [this, finalState] { setState(finalState); }); + request(MTPphone_DiscardCall(MTP_inputPhoneCall(MTP_long(_id), MTP_long(_accessHash)), MTP_int(duration), reason, MTP_long(connectionId))).done([this, finalState](const MTPUpdates &result) { // This could be destroyed by updates, so we set Ended after // updates being handled, but in a guarded way. - InvokeQueued(this, [this] { setState(State::Ended); }); + InvokeQueued(this, [this, finalState] { setState(finalState); }); App::main()->sentUpdatesReceived(result); - }).fail([this](const RPCError &error) { - setState(State::Ended); + }).fail([this, finalState](const RPCError &error) { + setState(finalState); }).send(); } @@ -627,14 +644,16 @@ void Call::handleRequestError(const RPCError &error) { } else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) { Ui::show(Box(lng_call_error_incompatible(lt_user, App::peerName(_user)))); } - setState(State::Failed); + finish(FinishType::Failed); } void Call::handleControllerError(int error) { if (error == TGVOIP_ERROR_INCOMPATIBLE) { Ui::show(Box(lng_call_error_incompatible(lt_user, App::peerName(_user)))); + } else if (error == TGVOIP_ERROR_AUDIO_IO) { + Ui::show(Box(lang(lng_call_error_audio_io))); } - setState(State::Failed); + finish(FinishType::Failed); } void Call::destroyController() { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index a6928b63d..1045bf626 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -87,6 +87,7 @@ public: WaitingInit, WaitingInitAck, Established, + FailedHangingUp, Failed, HangingUp, Ended, @@ -127,9 +128,14 @@ public: ~Call(); private: + enum class FinishType { + None, + Ended, + Failed, + }; void handleRequestError(const RPCError &error); void handleControllerError(int error); - void finish(const MTPPhoneCallDiscardReason &reason); + void finish(FinishType type, const MTPPhoneCallDiscardReason &reason = MTP_phoneCallDiscardReasonDisconnect()); void startOutgoing(); void startIncoming(); void startWaitingTrack(); @@ -154,7 +160,7 @@ private: gsl::not_null _user; Type _type = Type::Outgoing; State _state = State::Starting; - bool _finishAfterRequestingCall = false; + FinishType _finishAfterRequestingCall = FinishType::None; base::Observable _stateChanged; TimeMs _startTime = 0; base::DelayedCallTimer _finishByTimeoutTimer; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 254464818..943fb2abf 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -694,7 +694,10 @@ void Panel::stateChanged(State state) { updateStatusText(state); if (_call) { - if ((state != State::HangingUp) && (state != State::Ended) && (state != State::Failed)) { + if ((state != State::HangingUp) + && (state != State::Ended) + && (state != State::FailedHangingUp) + && (state != State::Failed)) { auto toggleButton = [this](auto &&button, bool visible) { if (isHidden()) { button->toggleFast(visible); @@ -765,6 +768,7 @@ void Panel::updateStatusText(State state) { } return lang(lng_call_status_ended); } break; + case State::FailedHangingUp: case State::Failed: return lang(lng_call_status_failed); case State::HangingUp: return lang(lng_call_status_hanging); case State::Ended: return lang(lng_call_status_ended);