Add some error tooltips in group calls.

This commit is contained in:
John Preston 2021-05-28 13:23:24 +04:00
parent 5b0278847d
commit 9a812090a2
6 changed files with 164 additions and 62 deletions

View File

@ -2004,8 +2004,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_screen_share_stop" = "Stop Sharing";
"lng_group_call_screen_title" = "Screen {index}";
"lng_group_call_unmute_small" = "Unmute";
"lng_group_call_you_are_live_small" = "Mute";
"lng_group_call_force_muted_small" = "Muted";
"lng_group_call_more" = "More";
"lng_group_call_unmute" = "Unmute";
"lng_group_call_unmute_sub" = "or hold spacebar to talk";
@ -2014,7 +2012,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_force_muted_sub" = "You are in Listen Only mode";
"lng_group_call_raise_hand_tip" = "Click if you want to speak";
"lng_group_call_raised_hand" = "You asked to speak";
"lng_group_call_raised_hand_small" = "Raised hand";
"lng_group_call_raised_hand_sub" = "We let the speakers know";
"lng_group_call_connecting" = "Connecting...";
"lng_group_call_leave" = "Leave";
@ -2027,6 +2024,17 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_create_sure" = "Do you really want to start a voice chat in this group?";
"lng_group_call_create_sure_channel" = "Are you sure you want to start a voice chat in this channel as your personal account?";
"lng_group_call_join_sure_personal" = "Are you sure you want to join this voice chat as your personal account?";
"lng_group_call_muted_no_camera" = "You can't turn on video while you're muted by admin.";
"lng_group_call_muted_no_screen" = "You can't share your screen while you're muted by admin.";
"lng_group_call_chat_no_camera" = "You can't turn on video in this chat.";
"lng_group_call_chat_no_screen" = "You can't share your screen in this chat.";
"lng_group_call_failed_screen" = "An error occured. Screencast has stopped.";
"lng_group_call_tooltip_screen" = "Share screen";
"lng_group_call_tooltip_camera" = "Your camera is off. Click here to enable camera.";
"lng_group_call_tooltip_microphone" = "You are on mute. Click here to speak.";
"lng_group_call_tooltip_camera_off" = "Disable camera";
"lng_group_call_tooltip_force_muted" = "Muted by admin. Click if you want to speak.";
"lng_group_call_tooltip_raised_hand" = "You asked to speak. We let the speakers know.";
"lng_group_call_also_end" = "End voice chat";
"lng_group_call_settings_title" = "Settings";
"lng_group_call_invite" = "Invite Member";

View File

@ -457,8 +457,7 @@ GroupCall::~GroupCall() {
}
bool GroupCall::isSharingScreen() const {
return _screenOutgoing
&& (_screenOutgoing->state() == Webrtc::VideoState::Active);
return _isSharingScreen.current();
}
rpl::producer<bool> GroupCall::isSharingScreenValue() const {
@ -470,8 +469,7 @@ const std::string &GroupCall::screenSharingEndpoint() const {
}
bool GroupCall::isSharingCamera() const {
return _cameraOutgoing
&& (_cameraOutgoing->state() == Webrtc::VideoState::Active);
return _isSharingCamera.current();
}
rpl::producer<bool> GroupCall::isSharingCameraValue() const {
@ -507,12 +505,9 @@ void GroupCall::toggleVideo(bool active) {
return;
}
ensureOutgoingVideo();
const auto state = active
_cameraOutgoing->setState(active
? Webrtc::VideoState::Active
: Webrtc::VideoState::Inactive;
if (_cameraOutgoing->state() != state) {
_cameraOutgoing->setState(state);
}
: Webrtc::VideoState::Inactive);
}
void GroupCall::toggleScreenSharing(std::optional<QString> uniqueId) {
@ -529,9 +524,7 @@ void GroupCall::toggleScreenSharing(std::optional<QString> uniqueId) {
}
const auto changed = (_screenDeviceId != *uniqueId);
_screenDeviceId = *uniqueId;
if (_screenOutgoing->state() != Webrtc::VideoState::Active) {
_screenOutgoing->setState(Webrtc::VideoState::Active);
}
_screenOutgoing->setState(Webrtc::VideoState::Active);
if (changed) {
_screenCapture->switchToDevice(uniqueId->toStdString());
}
@ -1110,6 +1103,9 @@ void GroupCall::rejoinPresentation() {
LOG(("Call Error: "
"Could not screen join, error: %1").arg(type));
_screenOutgoing->setState(Webrtc::VideoState::Inactive);
_errors.fire_copy(mutedByAdmin()
? Error::MutedNoScreen
: Error::ScreenFailed);
}
}).send();
});
@ -1669,6 +1665,48 @@ void GroupCall::setupMediaDevices() {
}, _lifetime);
}
bool GroupCall::emitShareCameraError() {
const auto emitError = [=](Error error) {
emitShareCameraError(error);
return true;
};
if (const auto real = lookupReal(); real && !real->canStartVideo()) {
return emitError(Error::DisabledNoCamera);
} else if (mutedByAdmin()) {
return emitError(Error::MutedNoCamera);
} else if (Webrtc::GetVideoInputList().empty()) {
return emitError(Error::NoCamera);
}
return false;
}
void GroupCall::emitShareCameraError(Error error) {
if (_cameraOutgoing) {
_cameraOutgoing->setState(Webrtc::VideoState::Inactive);
}
_errors.fire_copy(error);
}
bool GroupCall::emitShareScreenError() {
const auto emitError = [=](Error error) {
emitShareScreenError(error);
return true;
};
if (const auto real = lookupReal(); real && !real->canStartVideo()) {
return emitError(Error::DisabledNoScreen);
} else if (mutedByAdmin()) {
return emitError(Error::MutedNoScreen);
}
return false;
}
void GroupCall::emitShareScreenError(Error error) {
if (_screenOutgoing) {
_screenOutgoing->setState(Webrtc::VideoState::Inactive);
}
_errors.fire_copy(error);
}
void GroupCall::ensureOutgoingVideo() {
Expects(_id != 0);
@ -1682,33 +1720,21 @@ void GroupCall::ensureOutgoingVideo() {
Webrtc::VideoState::Inactive,
_requireARGB32);
using namespace rpl::mappers;
_isSharingCamera = _cameraOutgoing->stateValue(
) | rpl::map(_1 == Webrtc::VideoState::Active);
_isSharingScreen = _screenOutgoing->stateValue(
) | rpl::map(_1 == Webrtc::VideoState::Active);
//static const auto hasDevices = [] {
// return !Webrtc::GetVideoInputList().empty();
//};
_cameraOutgoing->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) {
//if (state != Webrtc::VideoState::Inactive && !hasDevices()) {
//_errors.fire({ ErrorType::NoCamera }); // #TODO calls
//_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) {
const auto active = (state != Webrtc::VideoState::Inactive);
if (active) {
// Paused not supported right now.
Assert(state == Webrtc::VideoState::Active);
if (!_cameraCapture) {
if (emitShareCameraError()) {
return;
} else if (!_cameraCapture) {
_cameraCapture = _delegate->groupCallGetVideoCapture(
_cameraInputId);
if (!_cameraCapture) {
return emitShareCameraError(Error::NoCamera);
_cameraOutgoing->setState(Webrtc::VideoState::Inactive);
_errors.fire_copy(Error::NoCamera);
return;
}
} else {
@ -1721,31 +1747,33 @@ void GroupCall::ensureOutgoingVideo() {
} else if (_cameraCapture) {
_cameraCapture->setState(tgcalls::VideoState::Inactive);
}
markEndpointActive({ _joinAs, _cameraEndpoint }, isSharingCamera());
_isSharingCamera = active;
markEndpointActive({ _joinAs, _cameraEndpoint }, active);
sendSelfUpdate(SendUpdateType::VideoMuted);
applyMeInCallLocally();
}, _lifetime);
_screenOutgoing->stateValue(
) | rpl::start_with_next([=](Webrtc::VideoState state) {
if (state != Webrtc::VideoState::Inactive) {
const auto active = (state != Webrtc::VideoState::Inactive);
if (active) {
// Paused not supported right now.
Assert(state == Webrtc::VideoState::Active);
if (!_screenCapture) {
_screenCapture = std::shared_ptr<tgcalls::VideoCaptureInterface>(
tgcalls::VideoCaptureInterface::Create(
tgcalls::StaticThreads::getThreads(),
_screenDeviceId.toStdString()));
if (emitShareScreenError()) {
return;
} else if (!_screenCapture) {
_screenCapture = std::shared_ptr<
tgcalls::VideoCaptureInterface
>(tgcalls::VideoCaptureInterface::Create(
tgcalls::StaticThreads::getThreads(),
_screenDeviceId.toStdString()));
if (!_screenCapture) {
_screenOutgoing->setState(Webrtc::VideoState::Inactive);
return;
return emitShareScreenError(Error::ScreenFailed);
}
const auto weak = base::make_weak(this);
_screenCapture->setOnFatalError([=] {
crl::on_main(weak, [=] {
_screenOutgoing->setState(
Webrtc::VideoState::Inactive);
// #TODO calls show error toast, receive here device.
emitShareScreenError(Error::ScreenFailed);
});
});
} else {
@ -1758,7 +1786,8 @@ void GroupCall::ensureOutgoingVideo() {
} else if (_screenCapture) {
_screenCapture->setState(tgcalls::VideoState::Inactive);
}
markEndpointActive({ _joinAs, _screenEndpoint }, isSharingScreen());
_isSharingScreen = active;
markEndpointActive({ _joinAs, _screenEndpoint }, active);
_screenJoinState.nextActionPending = true;
checkNextJoinAction();
}, _lifetime);
@ -2457,8 +2486,7 @@ void GroupCall::sendSelfUpdate(SendUpdateType type) {
MTP_bool(muted() != MuteState::Active),
MTP_int(100000), // volume
MTP_bool(muted() == MuteState::RaisedHand),
MTP_bool(!_cameraOutgoing
|| _cameraOutgoing->state() != Webrtc::VideoState::Active)
MTP_bool(!isSharingCamera())
)).done([=](const MTPUpdates &result) {
_updateMuteRequestId = 0;
_peer->session().api().applyUpdates(result);

View File

@ -48,6 +48,7 @@ struct ParticipantState;
struct JoinInfo;
struct RejoinEvent;
enum class VideoQuality;
enum class Error;
} // namespace Group
enum class MuteState {
@ -225,6 +226,13 @@ public:
void startScheduledNow();
void toggleScheduleStartSubscribed(bool subscribed);
bool emitShareScreenError();
bool emitShareCameraError();
[[nodiscard]] rpl::producer<Group::Error> errors() const {
return _errors.events();
}
void addVideoOutput(
const std::string &endpoint,
not_null<Webrtc::VideoTrack*> track);
@ -367,6 +375,7 @@ public:
private:
using GlobalShortcutValue = base::GlobalShortcutValue;
using Error = Group::Error;
struct SinkPointer;
static constexpr uint32 kDisabledSsrc = uint32(-1);
@ -421,6 +430,9 @@ private:
bool tryCreateScreencast();
void destroyScreencast();
void emitShareCameraError(Error error);
void emitShareScreenError(Error error);
void setState(State state);
void finish(FinishType type);
void maybeSendMutedUpdate(MuteState previous);
@ -490,6 +502,7 @@ private:
rpl::event_stream<not_null<Data::GroupCall*>> _realChanges;
rpl::variable<State> _state = State::Creating;
base::flat_set<uint32> _unresolvedSsrcs;
rpl::event_stream<Error> _errors;
bool _recordingStoppedByMe = false;
bool _requestedVideoChannelsUpdateScheduled = false;

View File

@ -59,4 +59,13 @@ enum class VideoQuality {
Full,
};
enum class Error {
NoCamera,
ScreenFailed,
MutedNoCamera,
MutedNoScreen,
DisabledNoCamera,
DisabledNoScreen,
};
} // namespace Calls::Group

View File

@ -66,6 +66,7 @@ constexpr auto kRecordingOpacity = 0.6;
constexpr auto kStartNoConfirmation = TimeId(10);
constexpr auto kControlsBackgroundOpacity = 0.8;
constexpr auto kOverrideActiveColorBgAlpha = 172;
constexpr auto kErrorDuration = 2 * crl::time(1000);
class InviteController final : public ParticipantsBoxController {
public:
@ -437,9 +438,7 @@ Panel::Panel(not_null<GroupCall*> call)
initControls();
initLayout();
showAndActivate();
setupJoinAsChangedToasts();
setupTitleChangedToasts();
setupAllowedToSpeakToasts();
setupToasts();
}
Panel::~Panel() {
@ -866,18 +865,12 @@ void Panel::setupRealMuteButtonState(not_null<Data::GroupCall*> real) {
: state == GroupCall::InstanceState::Disconnected
? tr::lng_group_call_connecting(tr::now)
: mute == MuteState::ForceMuted
? (wide
? tr::lng_group_call_force_muted_small(tr::now)
: tr::lng_group_call_force_muted(tr::now))
? tr::lng_group_call_force_muted(tr::now)
: mute == MuteState::RaisedHand
? (wide
? tr::lng_group_call_raised_hand_small(tr::now)
: tr::lng_group_call_raised_hand(tr::now))
? tr::lng_group_call_raised_hand(tr::now)
: mute == MuteState::Muted
? tr::lng_group_call_unmute(tr::now)
: (wide
? tr::lng_group_call_you_are_live_small(tr::now)
: tr::lng_group_call_you_are_live(tr::now))),
: tr::lng_group_call_you_are_live(tr::now)),
.subtext = ((scheduleDate || wide)
? QString()
: state == GroupCall::InstanceState::Disconnected
@ -1206,6 +1199,14 @@ void Panel::toggleWideControls(bool shown) {
});
}
void Panel::setupToasts() {
setupJoinAsChangedToasts();
setupTitleChangedToasts();
setupRequestedToSpeakToasts();
setupAllowedToSpeakToasts();
setupErrorToasts();
}
void Panel::setupJoinAsChangedToasts() {
_call->rejoinEvents(
) | rpl::filter([](RejoinEvent event) {
@ -1270,6 +1271,46 @@ void Panel::setupAllowedToSpeakToasts() {
}, widget()->lifetime());
}
void Panel::setupRequestedToSpeakToasts() {
_call->mutedValue(
) | rpl::combine_previous(
) | rpl::start_with_next([=](MuteState was, MuteState now) {
if (was == MuteState::ForceMuted && now == MuteState::RaisedHand) {
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = tr::lng_group_call_tooltip_raised_hand(tr::now),
});
}
}, widget()->lifetime());
}
void Panel::setupErrorToasts() {
_call->errors(
) | rpl::start_with_next([=](Error error) {
const auto key = [&] {
switch (error) {
case Error::NoCamera: return tr::lng_call_error_no_camera;
case Error::ScreenFailed:
return tr::lng_group_call_failed_screen;
case Error::MutedNoCamera:
return tr::lng_group_call_muted_no_camera;
case Error::MutedNoScreen:
return tr::lng_group_call_muted_no_screen;
case Error::DisabledNoCamera:
return tr::lng_group_call_chat_no_camera;
case Error::DisabledNoScreen:
return tr::lng_group_call_chat_no_screen;
}
Unexpected("Error in Calls::Group::Panel::setupErrorToasts.");
}();
Ui::ShowMultilineToast({
.parentOverride = widget(),
.text = { key(tr::now) },
.duration = kErrorDuration,
});
}, widget()->lifetime());
}
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
@ -1397,7 +1438,7 @@ void Panel::refreshTopButton() {
}
void Panel::chooseShareScreenSource() {
if (!_call->mutedByAdmin()) {
if (!_call->emitShareScreenError()) {
Ui::DesktopCapture::ChooseSource(this);
}
}

View File

@ -87,9 +87,12 @@ private:
void setupScheduledLabels(rpl::producer<TimeId> date);
void setupMembers();
void setupVideo();
void setupToasts();
void setupJoinAsChangedToasts();
void setupTitleChangedToasts();
void setupRequestedToSpeakToasts();
void setupAllowedToSpeakToasts();
void setupErrorToasts();
void setupRealMuteButtonState(not_null<Data::GroupCall*> real);
bool handleClose();