diff --git a/Telegram/Resources/icons/calls/call_settings.png b/Telegram/Resources/icons/calls/call_settings.png deleted file mode 100644 index f16037f3c..000000000 Binary files a/Telegram/Resources/icons/calls/call_settings.png and /dev/null differ diff --git a/Telegram/Resources/icons/calls/call_settings@2x.png b/Telegram/Resources/icons/calls/call_settings@2x.png deleted file mode 100644 index 21d18e24f..000000000 Binary files a/Telegram/Resources/icons/calls/call_settings@2x.png and /dev/null differ diff --git a/Telegram/Resources/icons/calls/call_settings@3x.png b/Telegram/Resources/icons/calls/call_settings@3x.png deleted file mode 100644 index d86c793a0..000000000 Binary files a/Telegram/Resources/icons/calls/call_settings@3x.png and /dev/null differ diff --git a/Telegram/Resources/icons/calls/calls_settings.png b/Telegram/Resources/icons/calls/calls_settings.png new file mode 100644 index 000000000..d965e0a0b Binary files /dev/null and b/Telegram/Resources/icons/calls/calls_settings.png differ diff --git a/Telegram/Resources/icons/calls/calls_settings@2x.png b/Telegram/Resources/icons/calls/calls_settings@2x.png new file mode 100644 index 000000000..57fd5eb97 Binary files /dev/null and b/Telegram/Resources/icons/calls/calls_settings@2x.png differ diff --git a/Telegram/Resources/icons/calls/calls_settings@3x.png b/Telegram/Resources/icons/calls/calls_settings@3x.png new file mode 100644 index 000000000..c9f788eab Binary files /dev/null and b/Telegram/Resources/icons/calls/calls_settings@3x.png differ diff --git a/Telegram/SourceFiles/boxes/peer_list_box.cpp b/Telegram/SourceFiles/boxes/peer_list_box.cpp index c0b8d6d5f..7feb3b1a3 100644 --- a/Telegram/SourceFiles/boxes/peer_list_box.cpp +++ b/Telegram/SourceFiles/boxes/peer_list_box.cpp @@ -1107,7 +1107,9 @@ void PeerListContent::paintEvent(QPaintEvent *e) { Painter p(this); 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(); auto repaintAfterMin = repaintByStatusAfter; diff --git a/Telegram/SourceFiles/calls/calls.style b/Telegram/SourceFiles/calls/calls.style index f4e7390e3..7588e7387 100644 --- a/Telegram/SourceFiles/calls/calls.style +++ b/Telegram/SourceFiles/calls/calls.style @@ -736,7 +736,7 @@ groupCallMemberRaisedHand: icon {{ "calls/group_calls_raised_hand", groupCallMem groupCallSettingsInner: IconButton(callButton) { iconPosition: point(-1px, 22px); - icon: icon {{ "calls/call_settings", groupCallIconFg }}; + icon: icon {{ "calls/calls_settings", groupCallIconFg }}; ripple: RippleAnimation(defaultRippleAnimation) { color: callMuteRipple; } @@ -1022,3 +1022,5 @@ desktopCaptureSourceSkip: 12px; groupCallNarrowSkip: 9px; groupCallNarrowRowSkip: 8px; groupCallNarrowSize: size(90px, 90px); +groupCallWideModeWidthMin: 800px; +groupCallWideModeSize: size(960px, 520px); diff --git a/Telegram/SourceFiles/calls/calls_call.cpp b/Telegram/SourceFiles/calls/calls_call.cpp index 8f80c38b1..9a304d236 100644 --- a/Telegram/SourceFiles/calls/calls_call.cpp +++ b/Telegram/SourceFiles/calls/calls_call.cpp @@ -161,8 +161,12 @@ Call::Call( , _user(user) , _api(&_user->session().mtp()) , _type(type) -, _videoIncoming(std::make_unique(StartVideoState(video))) -, _videoOutgoing(std::make_unique(StartVideoState(video))) { +, _videoIncoming( + std::make_unique( + StartVideoState(video))) +, _videoOutgoing( + std::make_unique( + StartVideoState(video))) { _discardByTimeoutTimer.setCallback([=] { hangup(); }); if (_type == Type::Outgoing) { diff --git a/Telegram/SourceFiles/calls/calls_call.h b/Telegram/SourceFiles/calls/calls_call.h index c61015a8d..fec90fc4a 100644 --- a/Telegram/SourceFiles/calls/calls_call.h +++ b/Telegram/SourceFiles/calls/calls_call.h @@ -53,6 +53,11 @@ struct Error { QString details; }; +enum class CallType { + Incoming, + Outgoing, +}; + class Call : public base::has_weak_ptr { public: class Delegate { @@ -81,11 +86,12 @@ public: static constexpr auto kSoundSampleMs = 100; - enum class Type { - Incoming, - Outgoing, - }; - Call(not_null delegate, not_null user, Type type, bool video); + using Type = CallType; + Call( + not_null delegate, + not_null user, + Type type, + bool video); [[nodiscard]] Type type() const { return _type; diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 9832b77a1..3722dad09 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -7,7 +7,10 @@ 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 "mtproto/mtproto_dh_utils.h" #include "core/application.h" #include "main/main_session.h" @@ -41,9 +44,128 @@ namespace { constexpr auto kServerConfigUpdateTimeoutMs = 24 * 3600 * crl::time(1000); +using CallSound = Call::Delegate::CallSound; +using GroupCallSound = GroupCall::Delegate::GroupCallSound; + } // namespace -Instance::Instance() = default; +class Instance::Delegate final + : public Call::Delegate + , public GroupCall::Delegate { +public: + explicit Delegate(not_null instance); + + DhConfig getDhConfig() const override; + + void callFinished(not_null call) override; + void callFailed(not_null call) override; + void callRedial(not_null call) override; + void callRequestPermissionsOrFail( + Fn onSuccess, + bool video) override; + void callPlaySound(CallSound sound) override; + auto callGetVideoCapture() + -> std::shared_ptr override; + + void groupCallFinished(not_null call) override; + void groupCallFailed(not_null call) override; + void groupCallRequestPermissionsOrFail(Fn onSuccess) override; + void groupCallPlaySound(GroupCallSound sound) override; + auto groupCallGetVideoCapture(const QString &deviceId) + -> std::shared_ptr override; + +private: + const not_null _instance; + +}; + +Instance::Delegate::Delegate(not_null instance) +: _instance(instance) { +} + +DhConfig Instance::Delegate::getDhConfig() const { + return *_instance->_cachedDhConfig; +} + +void Instance::Delegate::callFinished(not_null call) { + crl::on_main(call, [=] { + _instance->destroyCall(call); + }); +} + +void Instance::Delegate::callFailed(not_null call) { + crl::on_main(call, [=] { + _instance->destroyCall(call); + }); +} + +void Instance::Delegate::callRedial(not_null call) { + if (_instance->_currentCall.get() == call) { + _instance->refreshDhConfig(); + } +} + +void Instance::Delegate::callRequestPermissionsOrFail( + Fn 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 { + return _instance->getVideoCapture(); +} + +void Instance::Delegate::groupCallFinished(not_null call) { + crl::on_main(call, [=] { + _instance->destroyGroupCall(call); + }); +} + +void Instance::Delegate::groupCallFailed(not_null call) { + crl::on_main(call, [=] { + _instance->destroyGroupCall(call); + }); +} + +void Instance::Delegate::groupCallRequestPermissionsOrFail( + Fn 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 { + return _instance->getVideoCapture(deviceId); +} + +Instance::Instance() +: _delegate(std::make_unique(this)) +, _cachedDhConfig(std::make_unique()) +, _chooseJoinAs(std::make_unique()) { +} Instance::~Instance() = default; @@ -72,7 +194,7 @@ void Instance::startOrJoinGroupCall( : peer->groupCall() ? Group::ChooseJoinAsProcess::Context::Join : Group::ChooseJoinAsProcess::Context::Create; - _chooseJoinAs.start(peer, context, [=](object_ptr box) { + _chooseJoinAs->start(peer, context, [=](object_ptr box) { Ui::show(std::move(box), Ui::LayerOption::KeepOther); }, [=](QString text) { Ui::Toast::Show(text); @@ -85,36 +207,6 @@ void Instance::startOrJoinGroupCall( }); } -void Instance::callFinished(not_null call) { - crl::on_main(call, [=] { - destroyCall(call); - }); -} - -void Instance::callFailed(not_null call) { - crl::on_main(call, [=] { - destroyCall(call); - }); -} - -void Instance::callRedial(not_null call) { - if (_currentCall.get() == call) { - refreshDhConfig(); - } -} - -void Instance::groupCallFinished(not_null call) { - crl::on_main(call, [=] { - destroyGroupCall(call); - }); -} - -void Instance::groupCallFailed(not_null call) { - crl::on_main(call, [=] { - destroyGroupCall(call); - }); -} - not_null Instance::ensureSoundLoaded( const QString &key) { const auto i = _tracks.find(key); @@ -132,31 +224,6 @@ void Instance::playSoundOnce(const QString &key) { 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) { if (_currentCall.get() == call) { _currentCallPanel->closeBeforeDestroy(); @@ -174,7 +241,7 @@ void Instance::destroyCall(not_null call) { } void Instance::createCall(not_null user, Call::Type type, bool video) { - auto call = std::make_unique(getCallDelegate(), user, type, video); + auto call = std::make_unique(_delegate.get(), user, type, video); const auto raw = call.get(); user->session().account().sessionChanges( @@ -217,7 +284,7 @@ void Instance::createGroupCall( destroyCurrentCall(); auto call = std::make_unique( - getGroupCallDelegate(), + _delegate.get(), std::move(info), inputCall); const auto raw = call.get(); @@ -237,7 +304,7 @@ void Instance::refreshDhConfig() { const auto weak = base::make_weak(_currentCall); _currentCall->user()->session().api().request(MTPmessages_GetDhConfig( - MTP_int(_dhConfig.version), + MTP_int(_cachedDhConfig->version), MTP_int(MTP::ModExpFirst::kRandomPowerSize) )).done([=](const MTPmessages_DhConfig &result) { const auto call = weak.get(); @@ -249,14 +316,14 @@ void Instance::refreshDhConfig() { Assert(random.size() == MTP::ModExpFirst::kRandomPowerSize); call->start(random); } else { - callFailed(call); + _delegate->callFailed(call); } }).fail([=](const MTP::Error &error) { const auto call = weak.get(); if (!call) { return; } - callFailed(call); + _delegate->callFailed(call); }).send(); } @@ -277,13 +344,13 @@ bytes::const_span Instance::updateDhConfig( } else if (!validRandom(data.vrandom().v)) { return {}; } - _dhConfig.g = data.vg().v; - _dhConfig.p = std::move(primeBytes); - _dhConfig.version = data.vversion().v; + _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 (!_dhConfig.g || _dhConfig.p.empty()) { + if (!_cachedDhConfig->g || _cachedDhConfig->p.empty()) { LOG(("API Error: dhConfigNotModified on zero version.")); return {}; } else if (!validRandom(data.vrandom().v)) { @@ -592,22 +659,19 @@ void Instance::requestPermissionOrFail(Platform::PermissionType type, Fn } } -std::shared_ptr Instance::callGetVideoCapture() { - return getVideoCapture(); -} - -std::shared_ptr Instance::groupCallGetVideoCapture() { - return getVideoCapture(); -} - -std::shared_ptr Instance::getVideoCapture() { +std::shared_ptr Instance::getVideoCapture( + QString deviceId) { + if (deviceId.isEmpty()) { + deviceId = Core::App().settings().callVideoInputDeviceId(); + } if (auto result = _videoCapture.lock()) { + result->switchToDevice(deviceId.toStdString()); return result; } auto result = std::shared_ptr( tgcalls::VideoCaptureInterface::Create( tgcalls::StaticThreads::getThreads(), - Core::App().settings().callVideoInputDeviceId().toStdString())); + deviceId.toStdString())); _videoCapture = result; return result; } diff --git a/Telegram/SourceFiles/calls/calls_instance.h b/Telegram/SourceFiles/calls/calls_instance.h index 1c508c1d8..8b9c3536f 100644 --- a/Telegram/SourceFiles/calls/calls_instance.h +++ b/Telegram/SourceFiles/calls/calls_instance.h @@ -8,9 +8,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #pragma once #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 { enum class PermissionType; @@ -27,17 +24,22 @@ class Session; namespace Calls::Group { struct JoinInfo; class Panel; +class ChooseJoinAsProcess; } // namespace Calls::Group +namespace tgcalls { +class VideoCaptureInterface; +} // namespace tgcalls + namespace Calls { +class Call; +enum class CallType; +class GroupCall; class Panel; +struct DhConfig; -class Instance - : private Call::Delegate - , private GroupCall::Delegate - , private base::Subscriber - , public base::has_weak_ptr { +class Instance : private base::Subscriber, public base::has_weak_ptr { public: Instance(); ~Instance(); @@ -69,7 +71,8 @@ public: bool activateCurrentCall(const QString &joinHash = QString()); bool minimizeCurrentActiveCall(); bool closeCurrentActiveCall(); - std::shared_ptr getVideoCapture(); + [[nodiscard]] auto getVideoCapture(QString deviceId = QString()) + -> std::shared_ptr; void requestPermissionsOrFail(Fn onSuccess, bool video = true); void setCurrentAudioDevice(bool input, const QString &deviceId); @@ -77,44 +80,13 @@ public: [[nodiscard]] bool isQuitPrevent(); private: - using CallSound = Call::Delegate::CallSound; - using GroupCallSound = GroupCall::Delegate::GroupCallSound; - - [[nodiscard]] not_null getCallDelegate() { - return static_cast(this); - } - [[nodiscard]] not_null getGroupCallDelegate() { - return static_cast(this); - } - [[nodiscard]] DhConfig getDhConfig() const override { - return _dhConfig; - } + class Delegate; + friend class Delegate; not_null ensureSoundLoaded(const QString &key); void playSoundOnce(const QString &key); - void callFinished(not_null call) override; - void callFailed(not_null call) override; - void callRedial(not_null call) override; - void callRequestPermissionsOrFail( - Fn onSuccess, - bool video) override { - requestPermissionsOrFail(std::move(onSuccess), video); - } - void callPlaySound(CallSound sound) override; - auto callGetVideoCapture() - ->std::shared_ptr override; - - void groupCallFinished(not_null call) override; - void groupCallFailed(not_null call) override; - void groupCallRequestPermissionsOrFail(Fn onSuccess) override { - requestPermissionsOrFail(std::move(onSuccess), false); - } - void groupCallPlaySound(GroupCallSound sound) override; - auto groupCallGetVideoCapture() - ->std::shared_ptr override; - - void createCall(not_null user, Call::Type type, bool video); + void createCall(not_null user, CallType type, bool video); void destroyCall(not_null call); void createGroupCall( @@ -141,7 +113,8 @@ private: not_null session, const MTPUpdate &update); - DhConfig _dhConfig; + const std::unique_ptr _delegate; + const std::unique_ptr _cachedDhConfig; crl::time _lastServerConfigUpdateTime = 0; base::weak_ptr _serverConfigRequestSession; @@ -157,7 +130,7 @@ private: base::flat_map> _tracks; - Group::ChooseJoinAsProcess _chooseJoinAs; + const std::unique_ptr _chooseJoinAs; }; diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index f0725ecdf..8675e3b92 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -22,6 +22,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_signal_bars.h" +#include "calls/group/calls_group_call.h" #include "calls/group/calls_group_menu.h" // Group::LeaveBox. #include "history/view/history_view_group_call_tracker.h" // ContentByCall. #include "data/data_user.h" diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.cpp b/Telegram/SourceFiles/calls/group/calls_group_call.cpp index 3f076471e..cc0b305b3 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_call.cpp @@ -313,8 +313,8 @@ GroupCall::GroupCall( , _joinHash(info.joinHash) , _id(inputCall.c_inputGroupCall().vid().v) , _scheduleDate(info.scheduleDate) -, _videoOutgoing(std::make_unique( - Webrtc::VideoState::Inactive)) +, _videoOutgoing( + std::make_unique(Webrtc::VideoState::Inactive)) , _lastSpokeCheckTimer([=] { checkLastSpoke(); }) , _checkJoinedTimer([=] { checkJoined(); }) , _pushToTalkCancelTimer([=] { pushToTalkCancel(); }) @@ -402,6 +402,7 @@ void GroupCall::switchToCamera() { return; } _videoDeviceId = _videoInputId; + if (_videoOutgoing) _videoCapture->switchToDevice(_videoDeviceId.toStdString()); } @@ -1366,10 +1367,42 @@ void GroupCall::setupMediaDevices() { } void GroupCall::setupOutgoingVideo() { - _videoCapture = _delegate->groupCallGetVideoCapture(); - _videoOutgoing->setState(Webrtc::VideoState::Active); - _videoCapture->setOutput(_videoOutgoing->sink()); _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) { @@ -1424,6 +1457,7 @@ void GroupCall::ensureControllerCreated() { const auto weak = base::make_weak(this); const auto myLevel = std::make_shared(); + _videoCall = true; tgcalls::GroupInstanceDescriptor descriptor = { .threads = tgcalls::StaticThreads::getThreads(), .config = tgcalls::GroupConfig{ @@ -1451,13 +1485,10 @@ void GroupCall::ensureControllerCreated() { .createAudioDeviceModule = Webrtc::AudioDeviceModuleCreator( settings.callAudioBackend()), .videoCapture = _videoCapture, - //.getVideoSource = [=] { - // return _videoCapture-> - //}, .incomingVideoSourcesUpdated = [=]( const std::vector &ssrcs) { crl::on_main(weak, [=] { - showVideoStreams(ssrcs); + setVideoStreams(ssrcs); }); }, .participantDescriptionsRequired = [=]( @@ -1609,9 +1640,53 @@ void GroupCall::requestParticipantsInformation( addPreparedParticipants(); } -void GroupCall::showVideoStreams(const std::vector &ssrcs) { +void GroupCall::setVideoStreams(const std::vector &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) { - _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(); } -//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(tr::lng_call_error_not_available(tr::now, lt_user, _user->name))); - //} else if (error.type() == qstr("PARTICIPANT_VERSION_OUTDATED")) { - // Ui::show(Box(tr::lng_call_error_outdated(tr::now, lt_user, _user->name))); - //} else if (error.type() == qstr("CALL_PROTOCOL_LAYER_INVALID")) { - // Ui::show(Box(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( - // Lang::Hard::CallErrorIncompatible().replace( - // "{user}", - // _user->name))); - } else if (error == u"ERROR_AUDIO_IO"_q) { - //Ui::show(Box(tr::lng_call_error_audio_io(tr::now))); - } - //finish(FinishType::Failed); -} - MTPInputGroupCall GroupCall::inputCall() const { Expects(_id != 0); @@ -2084,9 +2120,6 @@ MTPInputGroupCall GroupCall::inputCall() const { void GroupCall::destroyController() { if (_instance) { - //_instance->stop([](tgcalls::FinalState) { - //}); - DEBUG_LOG(("Call Info: Destroying call controller..")); _instance.reset(); DEBUG_LOG(("Call Info: Call controller destroyed.")); diff --git a/Telegram/SourceFiles/calls/group/calls_group_call.h b/Telegram/SourceFiles/calls/group/calls_group_call.h index 0fa69f142..7c20314f0 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_call.h +++ b/Telegram/SourceFiles/calls/group/calls_group_call.h @@ -74,6 +74,11 @@ struct LevelUpdate { bool me = false; }; +struct StreamsVideoUpdate { + uint32 ssrc = 0; + bool streams = false; +}; + struct VideoParams; [[nodiscard]] std::shared_ptr ParseVideoParams( @@ -100,7 +105,7 @@ public: Ended, }; virtual void groupCallPlaySound(GroupCallSound sound) = 0; - virtual auto groupCallGetVideoCapture() + virtual auto groupCallGetVideoCapture(const QString &deviceId) -> std::shared_ptr = 0; }; @@ -158,6 +163,13 @@ public: return _muted.value(); } + [[nodiscard]] bool videoCall() const { + return _videoCall.current(); + } + [[nodiscard]] rpl::producer videoCallValue() const { + return _videoCall.value(); + } + [[nodiscard]] auto otherParticipantStateValue() const -> rpl::producer; @@ -194,8 +206,15 @@ public: [[nodiscard]] rpl::producer levelUpdates() const { return _levelUpdates.events(); } - [[nodiscard]] rpl::producer videoStreamUpdated() const { - return _videoStreamUpdated.events(); + [[nodiscard]] auto streamsVideoUpdates() const + -> rpl::producer { + return _streamsVideoUpdated.events(); + } + [[nodiscard]] uint32 videoStreamLarge() const { + return _videoStreamLarge.current(); + } + [[nodiscard]] rpl::producer videoStreamLargeValue() const { + return _videoStreamLarge.value(); } [[nodiscard]] rpl::producer rejoinEvents() const { return _rejoinEvents.events(); @@ -213,8 +232,6 @@ public: bool isScreenSharing() const; void switchToCamera(); void switchToScreenSharing(const QString &uniqueId); - //void setAudioVolume(bool input, float level); - void setAudioDuckingEnabled(bool enabled); void toggleMute(const Group::MuteRequest &data); void changeVolume(const Group::VolumeRequest &data); @@ -264,8 +281,6 @@ private: void handlePossibleDiscarded(const MTPDgroupCallDiscarded &data); void handleUpdate(const MTPDupdateGroupCall &data); void handleUpdate(const MTPDupdateGroupCallParticipants &data); - void handleRequestError(const MTP::Error &error); - void handleControllerError(const QString &error); void ensureControllerCreated(); void destroyController(); @@ -304,7 +319,7 @@ private: const Data::GroupCallParticipant &participant); void addPreparedParticipants(); void addPreparedParticipantsDelayed(); - void showVideoStreams(const std::vector &ssrcs); + void setVideoStreams(const std::vector &ssrcs); void editParticipant( not_null participantPeer, @@ -347,6 +362,7 @@ private: QString _joinHash; rpl::variable _muted = MuteState::Muted; + rpl::variable _videoCall = false; bool _initialMuteStateSent = false; bool _acceptFields = false; @@ -365,7 +381,10 @@ private: std::shared_ptr _videoCapture; const std::unique_ptr _videoOutgoing; rpl::event_stream _levelUpdates; - rpl::event_stream _videoStreamUpdated; + rpl::event_stream _streamsVideoUpdated; + base::flat_set _videoStreamSsrcs; + rpl::variable _videoStreamLarge = 0; + uint32 _videoStreamPinned = 0; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; rpl::event_stream<> _allowedToSpeakNotifications; diff --git a/Telegram/SourceFiles/calls/group/calls_group_common.h b/Telegram/SourceFiles/calls/group/calls_group_common.h index 0cfa4d9fe..56861b193 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_common.h +++ b/Telegram/SourceFiles/calls/group/calls_group_common.h @@ -47,4 +47,9 @@ struct JoinInfo { TimeId scheduleDate = 0; }; +enum class PanelMode { + Default, + Wide, +}; + } // namespace Calls::Group diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.cpp b/Telegram/SourceFiles/calls/group/calls_group_members.cpp index e5317d777..38ddf3e6b 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_members.cpp @@ -143,6 +143,7 @@ public: } [[nodiscard]] not_null createVideoTrack(); + void clearVideoTrack(); void setVideoTrack(not_null track); void addActionRipple(QPoint point, Fn updateCallback) override; @@ -352,12 +353,14 @@ private: not_null row, uint64 raiseHandRating) const; Row *findRow(not_null participantPeer) const; + Row *findRow(uint32 audioSsrc) const; void appendInvitedUsers(); void scheduleRaisedHandStatusRemove(); const not_null _call; not_null _peer; + uint32 _largeSsrc = 0; bool _prepared = false; rpl::event_stream _toggleMuteRequests; @@ -926,6 +929,13 @@ not_null Row::createVideoTrack() { return _videoTrack.get(); } +void Row::clearVideoTrack() { + _videoTrackLifetime.destroy(); + _videoTrackShown = nullptr; + _videoTrack = nullptr; + _delegate->rowUpdateRow(this); +} + void Row::setVideoTrack(not_null track) { _videoTrackLifetime.destroy(); _videoTrackShown = track; @@ -1043,17 +1053,29 @@ void MembersController::setupListChangeViewers() { } }, _lifetime); - _call->videoStreamUpdated( - ) | rpl::start_with_next([=](uint32 ssrc) { - const auto real = _call->lookupReal(); - const auto participantPeer = real - ? real->participantPeerByAudioSsrc(ssrc) - : nullptr; - const auto row = participantPeer - ? findRow(participantPeer) - : nullptr; - if (row) { - _call->addVideoOutput(ssrc, row->createVideoTrack()); + + _call->videoStreamLargeValue( + ) | rpl::filter([=](uint32 largeSsrc) { + return (_largeSsrc != largeSsrc); + }) | rpl::start_with_next([=](uint32 largeSsrc) { + if (const auto row = findRow(_largeSsrc)) { + _call->addVideoOutput(_largeSsrc, row->createVideoTrack()); + } + _largeSsrc = largeSsrc; + if (const auto row = findRow(_largeSsrc)) { + 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); @@ -1345,8 +1367,6 @@ void MembersController::updateRow( } if (isMe(row->peer())) { row->setVideoTrack(_call->outgoingVideoTrack()); - } else if (nowSsrc) { - _call->addVideoOutput(nowSsrc, row->createVideoTrack()); } } const auto nowNoSounding = _soundingRowBySsrc.empty(); @@ -1378,6 +1398,17 @@ Row *MembersController::findRow(not_null participantPeer) const { 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 { return _call->peer()->session(); } @@ -1943,8 +1974,11 @@ int Members::desiredHeight() const { return 0; }(); 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 - + (use * st::groupCallMembersList.item.height) + + (use * single) + (use ? st::lineWidth : 0); } @@ -2008,6 +2042,16 @@ void Members::setupAddMember(not_null call) { }, 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 Members::fullCountValue() const { return static_cast( _listController.get())->fullCountValue(); diff --git a/Telegram/SourceFiles/calls/group/calls_group_members.h b/Telegram/SourceFiles/calls/group/calls_group_members.h index 20064879c..6a4d5ad2c 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_members.h +++ b/Telegram/SourceFiles/calls/group/calls_group_members.h @@ -26,6 +26,7 @@ namespace Calls::Group { struct VolumeRequest; struct MuteRequest; +enum class PanelMode; class Members final : public Ui::RpWidget @@ -48,6 +49,8 @@ public: return _addMemberRequests.events(); } + void setMode(PanelMode mode); + private: using ListWidget = PeerListContent; @@ -76,6 +79,7 @@ private: void updateControlsGeometry(); const not_null _call; + PanelMode _mode = PanelMode(); object_ptr _scroll; std::unique_ptr _listController; object_ptr _addMember = { nullptr }; diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp index 7b7485d9f..4a1dfcb0d 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.cpp @@ -525,6 +525,11 @@ void Panel::initWindow() { ? (Flag::Move | Flag::Maximize) : Flag::None; }); + + _call->videoCallValue( + ) | rpl::start_with_next([=] { + updateMode(); + }, _window->lifetime()); } void Panel::initWidget() { @@ -536,8 +541,10 @@ void Panel::initWidget() { }, widget()->lifetime()); widget()->sizeValue( - ) | rpl::skip(1) | rpl::start_with_next([=] { - updateControlsGeometry(); + ) | rpl::skip(1) | rpl::start_with_next([=](QSize size) { + if (!updateMode()) { + updateControlsGeometry(); + } // title geometry depends on _controls->geometry, // which is not updated here yet. @@ -1384,6 +1391,21 @@ QRect Panel::computeTitleRect() const { #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() { if (widget()->size().isEmpty() || (!_settings && !_share)) { return; @@ -1434,27 +1456,35 @@ void Panel::updateMembersGeometry() { if (!_members) { 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 membersWidthAvailable = widget()->width() - - st::groupCallMembersMargin.left() - - 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)); + if (_mode == PanelMode::Wide) { + _members->setGeometry( + st::groupCallNarrowSkip, + 0, + st::groupCallNarrowSize.width(), + std::min(desiredHeight, widget()->height())); + } else { + const auto muteTop = widget()->height() - st::groupCallMuteBottomSkip; + const auto membersTop = st::groupCallMembersTop; + const auto availableHeight = muteTop + - membersTop + - st::groupCallMembersMargin.bottom(); + const auto membersWidthAvailable = widget()->width() + - st::groupCallMembersMargin.left() + - 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() { diff --git a/Telegram/SourceFiles/calls/group/calls_group_panel.h b/Telegram/SourceFiles/calls/group/calls_group_panel.h index 2ae20b525..51e23a5f7 100644 --- a/Telegram/SourceFiles/calls/group/calls_group_panel.h +++ b/Telegram/SourceFiles/calls/group/calls_group_panel.h @@ -53,6 +53,7 @@ struct CallBodyLayout; namespace Calls::Group { class Members; +enum class PanelMode; class Panel final : private Ui::DesktopCapture::ChooseSourceDelegate { public: @@ -88,6 +89,7 @@ private: bool handleClose(); void startScheduledNow(); + bool updateMode(); void updateControlsGeometry(); void updateMembersGeometry(); void showControls(); @@ -118,6 +120,7 @@ private: const std::unique_ptr _window; const std::unique_ptr _layerBg; + PanelMode _mode = PanelMode(); #ifndef Q_OS_MAC std::unique_ptr _controls; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index fd3b0f86a..6e82e6323 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -93,8 +93,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "core/application.h" #include "core/changelogs.h" #include "base/unixtime.h" +#include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_top_bar.h" +#include "calls/group/calls_group_call.h" #include "export/export_settings.h" #include "export/export_manager.h" #include "export/view/export_view_top_bar.h" diff --git a/Telegram/SourceFiles/settings/settings_calls.cpp b/Telegram/SourceFiles/settings/settings_calls.cpp index 189f2bbc5..fe7220728 100644 --- a/Telegram/SourceFiles/settings/settings_calls.cpp +++ b/Telegram/SourceFiles/settings/settings_calls.cpp @@ -25,6 +25,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "core/application.h" #include "core/core_settings.h" +#include "calls/calls_call.h" #include "calls/calls_instance.h" #include "calls/calls_video_bubble.h" #include "webrtc/webrtc_media_devices.h" @@ -250,40 +251,6 @@ void Calls::setupContent() { AddSkip(content); 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( content, tr::lng_settings_call_accept_calls(), diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 84c00409a..cf923bbc3 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -42,6 +42,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/toast/toast.h" #include "ui/toasts/common_toasts.h" #include "calls/calls_instance.h" // Core::App().calls().inCall(). +#include "calls/group/calls_group_call.h" #include "ui/boxes/calendar_box.h" #include "boxes/confirm_box.h" #include "mainwidget.h"