Show and play 'allowed to speak' notification.

This commit is contained in:
John Preston 2021-03-12 21:51:39 +04:00
parent 4d4a349f09
commit ed4dea2b0e
8 changed files with 124 additions and 40 deletions

View File

@ -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";

View File

@ -9,5 +9,6 @@
<file alias="group_call_start.mp3">../../sounds/group_call_start.mp3</file>
<file alias="group_call_connect.mp3">../../sounds/group_call_connect.mp3</file>
<file alias="group_call_end.mp3">../../sounds/group_call_end.mp3</file>
<file alias="group_call_allowed.mp3">../../sounds/group_call_allowed.mp3</file>
</qresource>
</RCC>

Binary file not shown.

View File

@ -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<PeerData*> 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<bool> GroupCall::connectingValue() const {
auto GroupCall::instanceStateValue() const -> rpl::producer<InstanceState> {
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) {

View File

@ -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<State> stateValue() const {
return _state.value();
}
[[nodiscard]] rpl::producer<bool> connectingValue() const;
enum class InstanceState {
Disconnected,
TransitionToRtc,
Connected,
};
[[nodiscard]] rpl::producer<InstanceState> instanceStateValue() const;
[[nodiscard]] rpl::producer<LevelUpdate> levelUpdates() const {
return _levelUpdates.events();
@ -159,6 +166,9 @@ public:
[[nodiscard]] rpl::producer<Group::RejoinEvent> 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*> _history; // Can change in legacy group migration.
MTP::Sender _api;
rpl::variable<State> _state = State::Creating;
bool _instanceConnected = false;
rpl::variable<InstanceState> _instanceState
= InstanceState::Disconnected;
bool _instanceTransitioning = false;
InstanceMode _instanceMode = InstanceMode::None;
base::flat_set<uint32> _unresolvedSsrcs;
std::vector<tgcalls::GroupParticipantDescription> _preparedParticipants;
@ -290,6 +303,7 @@ private:
rpl::event_stream<LevelUpdate> _levelUpdates;
base::flat_map<uint32, Data::LastSpokeTimes> _lastSpoke;
rpl::event_stream<Group::RejoinEvent> _rejoinEvents;
rpl::event_stream<> _allowedToSpeakNotifications;
base::Timer _lastSpokeCheckTimer;
base::Timer _checkJoinedTimer;

View File

@ -281,6 +281,30 @@ GroupPanel::GroupPanel(not_null<GroupCall*> 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

View File

@ -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.");

View File

@ -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<GroupCall*> call) {
return call && real && (real->id() == call->id());
}) | rpl::take(
1
) | rpl::map([=](not_null<Data::GroupCall*> real) {
) | rpl::before_next([=](not_null<Data::GroupCall*> real) {
real->titleValue() | rpl::start_with_next([=] {
updateInfoLabels();
}, lifetime());
}) | rpl::map([=](not_null<Data::GroupCall*> 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);