diff --git a/Telegram/Resources/icons/call_answer.png b/Telegram/Resources/icons/call_answer.png index f5a9ceb73..0d79a1750 100644 Binary files a/Telegram/Resources/icons/call_answer.png and b/Telegram/Resources/icons/call_answer.png differ diff --git a/Telegram/Resources/icons/call_answer@2x.png b/Telegram/Resources/icons/call_answer@2x.png index d6eb7c010..1c573d151 100644 Binary files a/Telegram/Resources/icons/call_answer@2x.png and b/Telegram/Resources/icons/call_answer@2x.png differ diff --git a/Telegram/Resources/icons/call_answer@3x.png b/Telegram/Resources/icons/call_answer@3x.png index 0060a291d..b74b73fef 100644 Binary files a/Telegram/Resources/icons/call_answer@3x.png and b/Telegram/Resources/icons/call_answer@3x.png differ diff --git a/Telegram/Resources/icons/call_camera_active.png b/Telegram/Resources/icons/call_camera_active.png new file mode 100644 index 000000000..3e01106db Binary files /dev/null and b/Telegram/Resources/icons/call_camera_active.png differ diff --git a/Telegram/Resources/icons/call_camera_active@2x.png b/Telegram/Resources/icons/call_camera_active@2x.png new file mode 100644 index 000000000..3692b731e Binary files /dev/null and b/Telegram/Resources/icons/call_camera_active@2x.png differ diff --git a/Telegram/Resources/icons/call_camera_active@3x.png b/Telegram/Resources/icons/call_camera_active@3x.png new file mode 100644 index 000000000..01ce4937d Binary files /dev/null and b/Telegram/Resources/icons/call_camera_active@3x.png differ diff --git a/Telegram/Resources/icons/call_camera_muted.png b/Telegram/Resources/icons/call_camera_muted.png new file mode 100644 index 000000000..848cbe12c Binary files /dev/null and b/Telegram/Resources/icons/call_camera_muted.png differ diff --git a/Telegram/Resources/icons/call_camera_muted@2x.png b/Telegram/Resources/icons/call_camera_muted@2x.png new file mode 100644 index 000000000..012a66561 Binary files /dev/null and b/Telegram/Resources/icons/call_camera_muted@2x.png differ diff --git a/Telegram/Resources/icons/call_camera_muted@3x.png b/Telegram/Resources/icons/call_camera_muted@3x.png new file mode 100644 index 000000000..7dd2aa9db Binary files /dev/null and b/Telegram/Resources/icons/call_camera_muted@3x.png differ diff --git a/Telegram/Resources/icons/call_discard.png b/Telegram/Resources/icons/call_discard.png index 312a8be71..b46c1c695 100644 Binary files a/Telegram/Resources/icons/call_discard.png and b/Telegram/Resources/icons/call_discard.png differ diff --git a/Telegram/Resources/icons/call_discard@2x.png b/Telegram/Resources/icons/call_discard@2x.png index 8fffeb412..0e8d7cf7e 100644 Binary files a/Telegram/Resources/icons/call_discard@2x.png and b/Telegram/Resources/icons/call_discard@2x.png differ diff --git a/Telegram/Resources/icons/call_discard@3x.png b/Telegram/Resources/icons/call_discard@3x.png index 96d95d1ca..f7b2ecc7d 100644 Binary files a/Telegram/Resources/icons/call_discard@3x.png and b/Telegram/Resources/icons/call_discard@3x.png differ diff --git a/Telegram/Resources/icons/call_record_active.png b/Telegram/Resources/icons/call_record_active.png index 4659bbb6c..79447233a 100644 Binary files a/Telegram/Resources/icons/call_record_active.png and b/Telegram/Resources/icons/call_record_active.png differ diff --git a/Telegram/Resources/icons/call_record_active@2x.png b/Telegram/Resources/icons/call_record_active@2x.png index 8ce748f9c..f21fba1bc 100644 Binary files a/Telegram/Resources/icons/call_record_active@2x.png and b/Telegram/Resources/icons/call_record_active@2x.png differ diff --git a/Telegram/Resources/icons/call_record_active@3x.png b/Telegram/Resources/icons/call_record_active@3x.png index 5ff5d9ede..7ab9e8d00 100644 Binary files a/Telegram/Resources/icons/call_record_active@3x.png and b/Telegram/Resources/icons/call_record_active@3x.png differ diff --git a/Telegram/Resources/icons/call_record_muted.png b/Telegram/Resources/icons/call_record_muted.png index 0747c8afa..ac6b48bf8 100644 Binary files a/Telegram/Resources/icons/call_record_muted.png and b/Telegram/Resources/icons/call_record_muted.png differ diff --git a/Telegram/Resources/icons/call_record_muted@2x.png b/Telegram/Resources/icons/call_record_muted@2x.png index 4d7d690e9..3a8ed3906 100644 Binary files a/Telegram/Resources/icons/call_record_muted@2x.png and b/Telegram/Resources/icons/call_record_muted@2x.png differ diff --git a/Telegram/Resources/icons/call_record_muted@3x.png b/Telegram/Resources/icons/call_record_muted@3x.png index dbdc210e4..62f9f8a6a 100644 Binary files a/Telegram/Resources/icons/call_record_muted@3x.png and b/Telegram/Resources/icons/call_record_muted@3x.png differ diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index 2aaeb9f4e..6939a32be 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -84,6 +84,13 @@ callMuteToggle: IconButton(callButton) { } } callUnmuteIcon: icon {{ "call_record_muted", callIconFg }}; +callCameraToggle: IconButton(callButton) { + icon: icon {{ "call_camera_active", callIconFg }}; + ripple: RippleAnimation(defaultRippleAnimation) { + color: callMuteRipple; + } +} +callNoCameraIcon: icon {{ "call_camera_muted", callIconFg }}; callControlsTop: 80px; callControlsSkip: 0px; @@ -124,7 +131,7 @@ callBarMuteToggle: IconButton { height: 38px; icon: icon {{ "call_record_active", callBarFg }}; - iconPosition: point(9px, 8px); + iconPosition: point(3px, 2px); ripple: RippleAnimation(defaultRippleAnimation) { color: callBarMuteRipple; @@ -137,7 +144,7 @@ callBarRightSkip: 12px; callBarSkip: 10px; callBarHangup: IconButton(callBarMuteToggle) { icon: icon {{ "call_discard", callBarFg }}; - iconPosition: point(9px, 11px); + iconPosition: point(3px, 1px); } callBarLabel: LabelSimple(defaultLabelSimple) { font: semiboldFont; diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index d8f0d23c6..ae1c6b512 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -29,6 +29,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "facades.h" #include +#include namespace tgcalls { class InstanceImpl; @@ -42,6 +43,7 @@ namespace { constexpr auto kMinLayer = 65; constexpr auto kHangupTimeoutMs = 5000; constexpr auto kSha256Size = 32; +constexpr auto kDropFramesWhileInactive = 5 * crl::time(1000); const auto kDefaultVersion = "2.4.4"_q; const auto RegisterTag = tgcalls::Register(); @@ -319,12 +321,31 @@ void Call::actuallyAnswer() { }).send(); } -void Call::setMute(bool mute) { - _mute = mute; +void Call::setMuted(bool mute) { + _muted = mute; if (_instance) { - _instance->setMuteMicrophone(_mute); + _instance->setMuteMicrophone(mute); + } +} + +void Call::setVideoEnabled(bool enabled) { + if (_state.current() != State::Established) { + return; + } + _videoEnabled = enabled; + if (enabled) { + if (!_videoCapture) { + _videoCapture = tgcalls::VideoCaptureInterface::Create(); + } + if (_instance) { + _instance->requestVideo(_videoCapture); + } else { + _videoState = VideoState::OutgoingRequested; + } + _videoCapture->setIsVideoEnabled(true); + } else if (_videoCapture) { + _videoCapture->setIsVideoEnabled(false); } - _muteChanged.notify(_mute); } crl::time Call::getDurationMs() const { @@ -430,6 +451,7 @@ bool Call::handleUpdate(const MTPPhoneCall &call) { } _id = data.vid().v; _accessHash = data.vaccess_hash().v; + setVideoEnabled(data.is_video()); auto gaHashBytes = bytes::make_span(data.vg_a_hash().v); if (gaHashBytes.size() != kSha256Size) { LOG(("Call Error: Wrong g_a_hash size %1, expected %2." @@ -650,7 +672,7 @@ void Call::createAndStartController(const MTPDphoneCall &call) { .encryptionKey = tgcalls::EncryptionKey( std::move(encryptionKeyValue), (_type == Type::Outgoing)), - .videoCapture = nullptr, + .videoCapture = _videoEnabled.current() ? _videoCapture : nullptr, .stateUpdated = [=](tgcalls::State state, tgcalls::VideoState videoState) { crl::on_main(weak, [=] { handleControllerStateChange(state, videoState); @@ -662,6 +684,14 @@ void Call::createAndStartController(const MTPDphoneCall &call) { }); }, .remoteVideoIsActiveUpdated = [=](bool active) { + crl::on_main(weak, [=] { + if (!active) { + _frames.fire(QImage()); + _remoteVideoInactiveFrom = crl::now(); + } else { + _remoteVideoInactiveFrom = 0; + } + }); }, .signalingDataEmitted = [=](const std::vector &data) { const auto bytes = QByteArray( @@ -706,8 +736,6 @@ void Call::createAndStartController(const MTPDphoneCall &call) { } } - descriptor.videoCapture = tgcalls::CreateVideoCapture(); - const auto version = call.vprotocol().match([&]( const MTPDphoneCallProtocol &data) { return data.vlibrary_versions().v; @@ -727,13 +755,18 @@ void Call::createAndStartController(const MTPDphoneCall &call) { } const auto raw = _instance.get(); - if (_mute) { - raw->setMuteMicrophone(_mute); + if (_muted.current()) { + raw->setMuteMicrophone(_muted.current()); } const auto &settings = Core::App().settings(); raw->setIncomingVideoOutput(webrtc::CreateVideoSink([=](QImage frame) { crl::on_main(weak, [=] { - _frames.fire_copy(frame); + if (_remoteVideoInactiveFrom > 0 + && (_remoteVideoInactiveFrom + kDropFramesWhileInactive + > crl::now())) { + } else { + _frames.fire_copy(frame); + } }); })); raw->setAudioOutputDevice( @@ -748,6 +781,18 @@ void Call::createAndStartController(const MTPDphoneCall &call) { void Call::handleControllerStateChange( tgcalls::State state, tgcalls::VideoState videoState) { + _videoState = [&] { + switch (videoState) { + case tgcalls::VideoState::Possible: return VideoState::Disabled; + case tgcalls::VideoState::OutgoingRequested: + return VideoState::OutgoingRequested; + case tgcalls::VideoState::IncomingRequested: + return VideoState::IncomingRequested; + case tgcalls::VideoState::Active: return VideoState::Enabled; + } + Unexpected("VideoState value in Call::handleControllerStateChange."); + }(); + switch (state) { case tgcalls::State::WaitInit: { DEBUG_LOG(("Call Info: State changed to WaitingInit.")); @@ -780,10 +825,7 @@ void Call::handleControllerBarCountChange(int count) { } void Call::setSignalBarCount(int count) { - if (_signalBarCount != count) { - _signalBarCount = count; - _signalBarCountChanged.notify(count); - } + _signalBarCount = count; } template @@ -986,9 +1028,7 @@ void Call::handleControllerError(const QString &error) { void Call::destroyController() { if (_instance) { - AssertIsDebug(); const auto state = _instance->stop(); - LOG(("CALL_LOG: %1").arg(QString::fromStdString(state.debugLog))); DEBUG_LOG(("Call Info: Destroying call controller..")); _instance.reset(); diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index 343a20a32..c5b4c39e6 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -21,6 +21,7 @@ class Track; namespace tgcalls { class Instance; +class VideoCaptureInterface; enum class State; enum class VideoState; } // namespace tgcalls @@ -91,29 +92,50 @@ public: Ringing, Busy, }; - State state() const { + [[nodiscard]] State state() const { return _state.current(); } - rpl::producer stateValue() const { + [[nodiscard]] rpl::producer stateValue() const { return _state.value(); } + enum class VideoState { + Disabled, + OutgoingRequested, + IncomingRequested, + Enabled + }; + [[nodiscard]] VideoState videoState() const { + return _videoState.current(); + } + [[nodiscard]] rpl::producer videoStateValue() const { + return _videoState.value(); + } + static constexpr auto kSignalBarStarting = -1; static constexpr auto kSignalBarFinished = -2; static constexpr auto kSignalBarCount = 4; - base::Observable &signalBarCountChanged() { - return _signalBarCountChanged; + [[nodiscard]] rpl::producer signalBarCountValue() const { + return _signalBarCount.value(); } - void setMute(bool mute); - bool isMute() const { - return _mute; + void setMuted(bool mute); + [[nodiscard]] bool muted() const { + return _muted.current(); } - base::Observable &muteChanged() { - return _muteChanged; + [[nodiscard]] rpl::producer mutedValue() const { + return _muted.value(); } - rpl::producer frames() const { + void setVideoEnabled(bool enabled); + [[nodiscard]] bool videoEnabled() const { + return _videoEnabled.current(); + } + [[nodiscard]] rpl::producer videoEnabledValue() const { + return _videoEnabled.value(); + } + + [[nodiscard]] rpl::producer frames() const { return _frames.events(); } @@ -178,17 +200,18 @@ private: MTP::Sender _api; Type _type = Type::Outgoing; rpl::variable _state = State::Starting; + rpl::variable _videoState = VideoState::Disabled; FinishType _finishAfterRequestingCall = FinishType::None; bool _answerAfterDhConfigReceived = false; - int _signalBarCount = kSignalBarStarting; - base::Observable _signalBarCountChanged; + rpl::variable _signalBarCount = kSignalBarStarting; crl::time _startTime = 0; base::DelayedCallTimer _finishByTimeoutTimer; base::Timer _discardByTimeoutTimer; - bool _mute = false; - base::Observable _muteChanged; + rpl::variable _muted = false; + rpl::variable _videoEnabled = false; rpl::event_stream _frames; + crl::time _remoteVideoInactiveFrom = 0; DhConfig _dhConfig; bytes::vector _ga; @@ -203,6 +226,7 @@ private: uint64 _keyFingerprint = 0; std::unique_ptr _instance; + std::shared_ptr _videoCapture; std::unique_ptr _waitingTrack; diff --git a/Telegram/SourceFiles/calls/calls_panel.cpp b/Telegram/SourceFiles/calls/calls_panel.cpp index 1bfdc9eba..bc34d8d81 100644 --- a/Telegram/SourceFiles/calls/calls_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_panel.cpp @@ -89,9 +89,10 @@ SignalBars::SignalBars( resize( _st.width + (_st.width + _st.skip) * (Call::kSignalBarCount - 1), _st.width * Call::kSignalBarCount); - subscribe(call->signalBarCountChanged(), [=](int count) { + call->signalBarCountValue( + ) | rpl::start_with_next([=](int count) { changed(count); - }); + }, lifetime()); } bool SignalBars::isDisplayed() const { @@ -307,6 +308,7 @@ Panel::Panel(not_null call) , _answerHangupRedial(this, st::callAnswer, &st::callHangup) , _decline(this, object_ptr