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);