diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index b74b24256..a53623bee 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2002,7 +2002,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_group_call_recording_stop" = "Stop recording"; "lng_group_call_recording_started" = "Voice chat recording started."; "lng_group_call_recording_stopped" = "Voice chat recording stopped."; -"lng_group_call_recording_saved" = "Audio saved to Saved Messages"; +"lng_group_call_recording_saved" = "Audio saved to Saved Messages."; "lng_group_call_recording_start_sure" = "Do you want to start recording this chat and save the result into an audio file?\n\nOther members will see the chat is being recorded."; "lng_group_call_recording_stop_sure" = "Do you want to stop recording this chat?"; "lng_group_call_recording_start_field" = "Recording Title"; diff --git a/Telegram/Resources/qrc/telegram/sounds.qrc b/Telegram/Resources/qrc/telegram/sounds.qrc index 69d1427e5..d9f7cf8b5 100644 --- a/Telegram/Resources/qrc/telegram/sounds.qrc +++ b/Telegram/Resources/qrc/telegram/sounds.qrc @@ -9,5 +9,6 @@ ../../sounds/group_call_start.mp3 ../../sounds/group_call_connect.mp3 ../../sounds/group_call_end.mp3 + ../../sounds/group_call_allowed.mp3 diff --git a/Telegram/Resources/sounds/group_call_allowed.mp3 b/Telegram/Resources/sounds/group_call_allowed.mp3 new file mode 100644 index 000000000..b5dfaa606 Binary files /dev/null and b/Telegram/Resources/sounds/group_call_allowed.mp3 differ diff --git a/Telegram/SourceFiles/calls/calls_group_call.cpp b/Telegram/SourceFiles/calls/calls_group_call.cpp index af42fbc5b..0e94602f0 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.cpp +++ b/Telegram/SourceFiles/calls/calls_group_call.cpp @@ -201,6 +201,17 @@ GroupCall::GroupCall( } }, _lifetime); + _instanceState.value( + ) | rpl::filter([=] { + return _hadJoinedState; + }) | rpl::start_with_next([=](InstanceState state) { + if (state == InstanceState::Disconnected) { + playConnectingSound(); + } else { + stopConnectingSound(); + } + }, _lifetime); + checkGlobalShortcutAvailability(); const auto id = inputCall.c_inputGroupCall().vid().v; @@ -269,12 +280,6 @@ void GroupCall::setState(State state) { if (const auto call = _peer->groupCall(); call && call->id() == _id) { call->setInCall(); } - } else if (state == State::Connecting || state == State::Joining) { - if (_hadJoinedState) { - playConnectingSound(); - } - } else { - stopConnectingSound(); } if (false @@ -462,9 +467,10 @@ void GroupCall::rejoin(not_null as) { MTP_dataJSON(MTP_bytes(json)) )).done([=](const MTPUpdates &updates) { _mySsrc = ssrc; - setState(_instanceConnected - ? State::Joined - : State::Connecting); + setState((_instanceState.current() + == InstanceState::Disconnected) + ? State::Connecting + : State::Joined); applyMeInCallLocally(); maybeSendMutedUpdate(wasMuteState); _peer->session().api().applyUpdates(updates); @@ -903,6 +909,9 @@ void GroupCall::handleUpdate(const MTPDupdateGroupCallParticipants &data) { } else if (muted() == MuteState::ForceMuted || muted() == MuteState::RaisedHand) { setMuted(MuteState::Muted); + if (!_instanceTransitioning) { + notifyAboutAllowedToSpeak(); + } } else if (data.is_muted() && muted() != MuteState::Muted) { setMuted(MuteState::Muted); } @@ -1269,19 +1278,37 @@ void GroupCall::checkJoined() { void GroupCall::setInstanceConnected( tgcalls::GroupNetworkState networkState) { - const auto connected = networkState.isConnected; - if (_instanceConnected == connected) { + const auto inTransit = networkState.isTransitioningFromBroadcastToRtc; + const auto instanceState = !networkState.isConnected + ? InstanceState::Disconnected + : inTransit + ? InstanceState::TransitionToRtc + : InstanceState::Connected; + const auto connected = (instanceState != InstanceState::Disconnected); + if (_instanceState.current() == instanceState + && _instanceTransitioning == inTransit) { return; } - _instanceConnected = connected; + const auto nowCanSpeak = connected + && _instanceTransitioning + && !inTransit + && (muted() == MuteState::Muted); + _instanceTransitioning = inTransit; + _instanceState = instanceState; if (state() == State::Connecting && connected) { setState(State::Joined); - if (networkState.isTransitioningFromBroadcastToRtc) { - // #TODO calls play sound?.. - } } else if (state() == State::Joined && !connected) { setState(State::Connecting); } + if (nowCanSpeak) { + notifyAboutAllowedToSpeak(); + } +} + +void GroupCall::notifyAboutAllowedToSpeak() { + _delegate->groupCallPlaySound( + Delegate::GroupCallSound::AllowedToSpeak); + _allowedToSpeakNotifications.fire({}); } void GroupCall::setInstanceMode(InstanceMode mode) { @@ -1342,13 +1369,9 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) { }).send(); } -rpl::producer GroupCall::connectingValue() const { +auto GroupCall::instanceStateValue() const -> rpl::producer { using namespace rpl::mappers; - return _state.value() | rpl::map( - _1 == State::Creating - || _1 == State::Joining - || _1 == State::Connecting - ) | rpl::distinct_until_changed(); + return _instanceState.value(); } void GroupCall::setCurrentAudioDevice(bool input, const QString &deviceId) { diff --git a/Telegram/SourceFiles/calls/calls_group_call.h b/Telegram/SourceFiles/calls/calls_group_call.h index ce314216b..fe54a15a9 100644 --- a/Telegram/SourceFiles/calls/calls_group_call.h +++ b/Telegram/SourceFiles/calls/calls_group_call.h @@ -85,6 +85,7 @@ public: enum class GroupCallSound { Started, Connecting, + AllowedToSpeak, Ended, }; virtual void groupCallPlaySound(GroupCallSound sound) = 0; @@ -151,7 +152,13 @@ public: [[nodiscard]] rpl::producer stateValue() const { return _state.value(); } - [[nodiscard]] rpl::producer connectingValue() const; + + enum class InstanceState { + Disconnected, + TransitionToRtc, + Connected, + }; + [[nodiscard]] rpl::producer instanceStateValue() const; [[nodiscard]] rpl::producer levelUpdates() const { return _levelUpdates.events(); @@ -159,6 +166,9 @@ public: [[nodiscard]] rpl::producer rejoinEvents() const { return _rejoinEvents.events(); } + [[nodiscard]] rpl::producer<> allowedToSpeakNotifications() const { + return _allowedToSpeakNotifications.events(); + } static constexpr auto kSpeakLevelThreshold = 0.2; void setCurrentAudioDevice(bool input, const QString &deviceId); @@ -232,6 +242,7 @@ private: void checkGlobalShortcutAvailability(); void checkJoined(); + void notifyAboutAllowedToSpeak(); void playConnectingSound(); void stopConnectingSound(); @@ -260,7 +271,9 @@ private: not_null _history; // Can change in legacy group migration. MTP::Sender _api; rpl::variable _state = State::Creating; - bool _instanceConnected = false; + rpl::variable _instanceState + = InstanceState::Disconnected; + bool _instanceTransitioning = false; InstanceMode _instanceMode = InstanceMode::None; base::flat_set _unresolvedSsrcs; std::vector _preparedParticipants; @@ -290,6 +303,7 @@ private: rpl::event_stream _levelUpdates; base::flat_map _lastSpoke; rpl::event_stream _rejoinEvents; + rpl::event_stream<> _allowedToSpeakNotifications; base::Timer _lastSpokeCheckTimer; base::Timer _checkJoinedTimer; diff --git a/Telegram/SourceFiles/calls/calls_group_panel.cpp b/Telegram/SourceFiles/calls/calls_group_panel.cpp index 68eaf2f27..01fe119ad 100644 --- a/Telegram/SourceFiles/calls/calls_group_panel.cpp +++ b/Telegram/SourceFiles/calls/calls_group_panel.cpp @@ -281,6 +281,30 @@ GroupPanel::GroupPanel(not_null call) initControls(); initLayout(); showAndActivate(); + + call->allowedToSpeakNotifications( + ) | rpl::start_with_next([=] { + if (isActive()) { + Ui::Toast::Show( + widget(), + tr::lng_group_call_can_speak_here(tr::now)); + } else { + const auto real = _peer->groupCall(); + const auto name = (real + && (real->id() == call->id()) + && !real->title().isEmpty()) + ? real->title() + : _peer->name; + Ui::Toast::Show(Ui::Toast::Config{ + .text = tr::lng_group_call_can_speak( + tr::now, + lt_chat, + Ui::Text::WithEntities(name), + Ui::Text::RichLangValue), + .st = &st::defaultToast, + }); + } + }, widget()->lifetime()); } GroupPanel::~GroupPanel() { @@ -507,13 +531,18 @@ void GroupPanel::initWithCall(GroupCall *call) { } }, _callLifetime); + using namespace rpl::mappers; rpl::combine( _call->mutedValue() | MapPushToTalkToActive(), - _call->connectingValue() + _call->instanceStateValue() ) | rpl::distinct_until_changed( - ) | rpl::start_with_next([=](MuteState mute, bool connecting) { + ) | rpl::filter( + _2 != GroupCall::InstanceState::TransitionToRtc + ) | rpl::start_with_next([=]( + MuteState mute, + GroupCall::InstanceState state) { _mute->setState(Ui::CallMuteButtonState{ - .text = (connecting + .text = (state == GroupCall::InstanceState::Disconnected ? tr::lng_group_call_connecting(tr::now) : mute == MuteState::ForceMuted ? tr::lng_group_call_force_muted(tr::now) @@ -522,7 +551,7 @@ void GroupPanel::initWithCall(GroupCall *call) { : mute == MuteState::Muted ? tr::lng_group_call_unmute(tr::now) : tr::lng_group_call_you_are_live(tr::now)), - .subtext = (connecting + .subtext = (state == GroupCall::InstanceState::Disconnected ? QString() : mute == MuteState::ForceMuted ? tr::lng_group_call_raise_hand_tip(tr::now) @@ -531,7 +560,7 @@ void GroupPanel::initWithCall(GroupCall *call) { : mute == MuteState::Muted ? tr::lng_group_call_unmute_sub(tr::now) : QString()), - .type = (connecting + .type = (state == GroupCall::InstanceState::Disconnected ? Ui::CallMuteButtonType::Connecting : mute == MuteState::ForceMuted ? Ui::CallMuteButtonType::ForceMuted diff --git a/Telegram/SourceFiles/calls/calls_instance.cpp b/Telegram/SourceFiles/calls/calls_instance.cpp index 2be9e59c4..1ab8a8c1a 100644 --- a/Telegram/SourceFiles/calls/calls_instance.cpp +++ b/Telegram/SourceFiles/calls/calls_instance.cpp @@ -146,6 +146,7 @@ void Instance::groupCallPlaySound(GroupCallSound sound) { 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."); diff --git a/Telegram/SourceFiles/calls/calls_top_bar.cpp b/Telegram/SourceFiles/calls/calls_top_bar.cpp index 3e8467e94..1aa9cbfcc 100644 --- a/Telegram/SourceFiles/calls/calls_top_bar.cpp +++ b/Telegram/SourceFiles/calls/calls_top_bar.cpp @@ -57,14 +57,14 @@ constexpr auto kHideBlobsDuration = crl::time(500); constexpr auto kBlobLevelDuration = crl::time(250); constexpr auto kBlobUpdateInterval = crl::time(100); -auto BarStateFromMuteState(MuteState state, bool connecting) { - return (connecting +auto BarStateFromMuteState(MuteState state, GroupCall::InstanceState instanceState) { + return (instanceState == GroupCall::InstanceState::Disconnected) ? BarState::Connecting : (state == MuteState::ForceMuted || state == MuteState::RaisedHand) ? BarState::ForceMuted - : state == MuteState::Muted + : (state == MuteState::Muted) ? BarState::Muted - : BarState::Active); + : BarState::Active; }; auto LinearBlobs() { @@ -293,17 +293,20 @@ void TopBar::initControls() { _call ? mapToState(_call->muted()) : _groupCall->muted(), - false)); + GroupCall::InstanceState::Connected)); + using namespace rpl::mappers; auto muted = _call ? rpl::combine( _call->mutedValue() | rpl::map(mapToState), - rpl::single(false)) | rpl::type_erased() + rpl::single(GroupCall::InstanceState::Connected) + ) | rpl::type_erased() : rpl::combine( (_groupCall->mutedValue() | MapPushToTalkToActive() | rpl::distinct_until_changed() | rpl::type_erased()), - _groupCall->connectingValue()); + _groupCall->instanceStateValue() + ) | rpl::filter(_2 != GroupCall::InstanceState::TransitionToRtc); std::move( muted ) | rpl::map( @@ -465,9 +468,14 @@ void TopBar::initBlobsUnder( auto hideBlobs = rpl::combine( rpl::single(anim::Disabled()) | rpl::then(anim::Disables()), Core::App().appDeactivatedValue(), - group->connectingValue() - ) | rpl::map([](bool animDisabled, bool hide, bool connecting) { - return connecting || animDisabled || hide; + group->instanceStateValue() + ) | rpl::map([]( + bool animDisabled, + bool hide, + GroupCall::InstanceState instanceState) { + return (instanceState == GroupCall::InstanceState::Disconnected) + || animDisabled + || hide; }); std::move( @@ -557,7 +565,12 @@ void TopBar::subscribeToMembersChanges(not_null call) { return call && real && (real->id() == call->id()); }) | rpl::take( 1 - ) | rpl::map([=](not_null real) { + ) | rpl::before_next([=](not_null real) { + real->titleValue() | rpl::start_with_next([=] { + updateInfoLabels(); + }, lifetime()); + }) | rpl::map([=](not_null real) { + return HistoryView::GroupCallTracker::ContentByCall( real, st::groupCallTopBarUserpics.size); @@ -616,9 +629,12 @@ void TopBar::setInfoLabels() { _shortInfoLabel->setText(shortName.toUpper()); } else if (const auto group = _groupCall.get()) { const auto peer = group->peer(); + const auto real = peer->groupCall(); const auto name = peer->name; const auto text = _isGroupConnecting.current() ? tr::lng_group_call_connecting(tr::now) + : (real && real->id() == group->id() && !real->title().isEmpty()) + ? real->title().toUpper() : name.toUpper(); _fullInfoLabel->setText(text); _shortInfoLabel->setText(text);