Add a hint to turn on the camera.

This commit is contained in:
John Preston 2021-06-18 16:11:32 +04:00
parent 5827d6ffdb
commit 1cb1f1cbc1
9 changed files with 293 additions and 79 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View file

@ -1251,7 +1251,7 @@ groupCallTooltip: Tooltip(defaultTooltip) {
}
groupCallNiceTooltip: ImportantTooltip(defaultImportantTooltip) {
bg: importantTooltipBg;
padding: margins(10px, 3px, 10px, 5px);
padding: margins(10px, 1px, 10px, 3px);
radius: 4px;
arrow: 4px;
}
@ -1262,5 +1262,16 @@ groupCallNiceTooltipLabel: FlatLabel(defaultImportantTooltipLabel) {
linkFontOver: font(11px underline);
}
}
groupCallStickedTooltip: ImportantTooltip(groupCallNiceTooltip) {
padding: margins(10px, 1px, 6px, 3px);
}
groupCallStickedTooltipClose: IconButton(defaultIconButton) {
width: 20px;
height: 20px;
iconPosition: point(4px, 3px);
icon: icon {{ "calls/video_tooltip", importantTooltipFg }};
iconOver: icon {{ "calls/video_tooltip", importantTooltipFg }};
ripple: emptyRippleAnimation;
}
groupCallNiceTooltipTop: 4px;
groupCallPaused: icon {{ "calls/video_large_paused", groupCallVideoTextFg }};

View file

@ -187,6 +187,28 @@ struct GroupCall::SinkPointer {
std::weak_ptr<Webrtc::SinkInterface> data;
};
struct GroupCall::VideoTrack {
VideoTrack(bool paused, bool requireARGB32, not_null<PeerData*> peer);
Webrtc::VideoTrack track;
rpl::variable<QSize> trackSize;
not_null<PeerData*> peer;
rpl::lifetime lifetime;
Group::VideoQuality quality = Group::VideoQuality();
bool shown = false;
};
GroupCall::VideoTrack::VideoTrack(
bool paused,
bool requireARGB32,
not_null<PeerData*> peer)
: track((paused
? Webrtc::VideoState::Paused
: Webrtc::VideoState::Active),
requireARGB32)
, peer(peer) {
}
[[nodiscard]] bool IsGroupCallAdmin(
not_null<PeerData*> peer,
not_null<PeerData*> participantPeer) {
@ -451,6 +473,21 @@ void GroupCall::MediaChannelDescriptionsTask::cancel() {
}
}
not_null<PeerData*> GroupCall::TrackPeer(
const std::unique_ptr<VideoTrack> &track) {
return track->peer;
}
not_null<Webrtc::VideoTrack*> GroupCall::TrackPointer(
const std::unique_ptr<VideoTrack> &track) {
return &track->track;
}
rpl::producer<QSize> GroupCall::TrackSizeValue(
const std::unique_ptr<VideoTrack> &track) {
return track->trackSize.value();
}
GroupCall::GroupCall(
not_null<Delegate*> delegate,
Group::JoinInfo info,
@ -1064,43 +1101,39 @@ void GroupCall::markEndpointActive(
if (active) {
const auto i = _activeVideoTracks.emplace(
endpoint,
VideoTrack{
.track = std::make_unique<Webrtc::VideoTrack>(
(paused
? Webrtc::VideoState::Paused
: Webrtc::VideoState::Active),
_requireARGB32),
.peer = endpoint.peer,
}).first;
const auto track = i->second.track.get();
std::make_unique<VideoTrack>(
paused,
_requireARGB32,
endpoint.peer)).first;
const auto track = &i->second->track;
track->renderNextFrame(
) | rpl::start_with_next([=] {
auto &activeTrack = _activeVideoTracks[endpoint];
const auto activeTrack = _activeVideoTracks[endpoint].get();
const auto size = track->frameSize();
if (size.isEmpty()) {
track->markFrameShown();
} else if (!activeTrack.shown) {
activeTrack.shown = true;
} else if (!activeTrack->shown) {
activeTrack->shown = true;
markTrackShown(endpoint, true);
}
activeTrack.trackSize = size;
}, i->second.lifetime);
activeTrack->trackSize = size;
}, i->second->lifetime);
const auto size = track->frameSize();
i->second.trackSize = size;
i->second->trackSize = size;
if (!size.isEmpty() || paused) {
i->second.shown = true;
i->second->shown = true;
shown = true;
} else {
track->stateValue(
) | rpl::filter([=](Webrtc::VideoState state) {
return (state == Webrtc::VideoState::Paused)
&& !_activeVideoTracks[endpoint].shown;
&& !_activeVideoTracks[endpoint]->shown;
}) | rpl::start_with_next([=] {
_activeVideoTracks[endpoint].shown = true;
_activeVideoTracks[endpoint]->shown = true;
markTrackShown(endpoint, true);
}, i->second.lifetime);
}, i->second->lifetime);
}
addVideoOutput(i->first.id, { track->sink() });
} else {
@ -1144,7 +1177,7 @@ void GroupCall::markTrackPaused(const VideoEndpoint &endpoint, bool paused) {
const auto i = _activeVideoTracks.find(endpoint);
Assert(i != end(_activeVideoTracks));
i->second.track->setState(paused
i->second->track.setState(paused
? Webrtc::VideoState::Paused
: Webrtc::VideoState::Active);
}
@ -2420,13 +2453,13 @@ void GroupCall::updateRequestedVideoChannels() {
.ssrcGroups = (params->camera.endpointId == endpointId
? params->camera.ssrcGroups
: params->screen.ssrcGroups),
.minQuality = ((video.quality == Group::VideoQuality::Full
.minQuality = ((video->quality == Group::VideoQuality::Full
&& endpoint.type == VideoEndpointType::Screen)
? Quality::Full
: Quality::Thumbnail),
.maxQuality = ((video.quality == Group::VideoQuality::Full)
.maxQuality = ((video->quality == Group::VideoQuality::Full)
? Quality::Full
: (video.quality == Group::VideoQuality::Medium
: (video->quality == Group::VideoQuality::Medium
&& endpoint.type != VideoEndpointType::Screen)
? Quality::Medium
: Quality::Thumbnail),
@ -2911,10 +2944,10 @@ void GroupCall::requestVideoQuality(
return;
}
const auto i = _activeVideoTracks.find(endpoint);
if (i == end(_activeVideoTracks) || i->second.quality == quality) {
if (i == end(_activeVideoTracks) || i->second->quality == quality) {
return;
}
i->second.quality = quality;
i->second->quality = quality;
updateRequestedVideoChannelsDelayed();
}

View file

@ -98,6 +98,8 @@ struct VideoEndpoint {
std::string id;
[[nodiscard]] bool empty() const noexcept {
Expects(id.empty() || peer != nullptr);
return id.empty();
}
[[nodiscard]] explicit operator bool() const noexcept {
@ -194,6 +196,15 @@ public:
using GlobalShortcutManager = base::GlobalShortcutManager;
struct VideoTrack;
[[nodiscard]] static not_null<PeerData*> TrackPeer(
const std::unique_ptr<VideoTrack> &track);
[[nodiscard]] static not_null<Webrtc::VideoTrack*> TrackPointer(
const std::unique_ptr<VideoTrack> &track);
[[nodiscard]] static rpl::producer<QSize> TrackSizeValue(
const std::unique_ptr<VideoTrack> &track);
GroupCall(
not_null<Delegate*> delegate,
Group::JoinInfo info,
@ -321,27 +332,8 @@ public:
-> rpl::producer<VideoEndpoint> {
return _videoEndpointLarge.value();
}
struct VideoTrack {
std::unique_ptr<Webrtc::VideoTrack> track;
rpl::variable<QSize> trackSize;
PeerData *peer = nullptr;
rpl::lifetime lifetime;
Group::VideoQuality quality = Group::VideoQuality();
bool shown = false;
[[nodiscard]] explicit operator bool() const {
return (track != nullptr);
}
[[nodiscard]] bool operator==(const VideoTrack &other) const {
return (track == other.track) && (peer == other.peer);
}
[[nodiscard]] bool operator!=(const VideoTrack &other) const {
return !(*this == other);
}
};
[[nodiscard]] auto activeVideoTracks() const
-> const base::flat_map<VideoEndpoint, VideoTrack> & {
-> const base::flat_map<VideoEndpoint, std::unique_ptr<VideoTrack>> & {
return _activeVideoTracks;
}
[[nodiscard]] auto shownVideoTracks() const
@ -625,7 +617,9 @@ private:
rpl::event_stream<VideoStateToggle> _videoStreamActiveUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamPausedUpdates;
rpl::event_stream<VideoStateToggle> _videoStreamShownUpdates;
base::flat_map<VideoEndpoint, VideoTrack> _activeVideoTracks;
base::flat_map<
VideoEndpoint,
std::unique_ptr<VideoTrack>> _activeVideoTracks;
base::flat_set<VideoEndpoint> _shownVideoTracks;
rpl::variable<VideoEndpoint> _videoEndpointLarge;
rpl::variable<bool> _videoEndpointPinned = false;

View file

@ -499,17 +499,26 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
&st::groupCallVideoActiveSmall);
_video->show();
_video->setClickedCallback([=] {
hideStickedTooltip(
StickedTooltip::Camera,
StickedTooltipHide::Activated);
_call->toggleVideo(!_call->isSharingCamera());
});
_video->setColorOverrides(
toggleableOverrides(_call->isSharingCameraValue()));
_call->isSharingCameraValue(
) | rpl::start_with_next([=](bool sharing) {
if (sharing) {
hideStickedTooltip(
StickedTooltip::Camera,
StickedTooltipHide::Activated);
}
_video->setProgress(sharing ? 1. : 0.);
}, _video->lifetime());
_call->mutedValue(
) | rpl::start_with_next([=] {
updateButtonsGeometry();
showStickedTooltip();
}, _video->lifetime());
}
if (!_screenShare) {
@ -536,6 +545,45 @@ void Panel::refreshVideoButtons(std::optional<bool> overrideWideMode) {
updateButtonsGeometry();
}
void Panel::hideStickedTooltip(StickedTooltipHide hide) {
if (!_stickedTooltipClose || !_niceTooltipControl) {
return;
}
if (_niceTooltipControl.data() == _video.data()) {
hideStickedTooltip(StickedTooltip::Camera, hide);
} else if (_niceTooltipControl.data() == _mute->outer().get()) {
hideStickedTooltip(StickedTooltip::Microphone, hide);
}
}
void Panel::hideStickedTooltip(
StickedTooltip type,
StickedTooltipHide hide) {
if (hide != StickedTooltipHide::Unavailable) {
_stickedTooltipsShown |= type;
if (hide == StickedTooltipHide::Discarded) {
// #TODO calls save to settings.
}
}
const auto control = (type == StickedTooltip::Camera)
? _video.data()
: (type == StickedTooltip::Microphone)
? _mute->outer().get()
: nullptr;
if (_niceTooltipControl.data() == control) {
hideNiceTooltip();
}
}
void Panel::hideNiceTooltip() {
if (!_niceTooltip) {
return;
}
_stickedTooltipClose = nullptr;
_niceTooltip.release()->toggleAnimated(false);
_niceTooltipControl = nullptr;
}
void Panel::initShareAction() {
const auto showBoxCallback = [=](object_ptr<Ui::BoxContent> next) {
_layerBg->showBox(std::move(next));
@ -843,9 +891,9 @@ void Panel::raiseControls() {
void Panel::setupVideo(not_null<Viewport*> viewport) {
const auto setupTile = [=](
const VideoEndpoint &endpoint,
const GroupCall::VideoTrack &track) {
const std::unique_ptr<GroupCall::VideoTrack> &track) {
using namespace rpl::mappers;
const auto row = _members->lookupRow(track.peer);
const auto row = _members->lookupRow(GroupCall::TrackPeer(track));
Assert(row != nullptr);
auto pinned = rpl::combine(
_call->videoEndpointLargeValue(),
@ -853,8 +901,8 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
) | rpl::map(_1 == endpoint && _2);
viewport->add(
endpoint,
VideoTileTrack{ track.track.get(), row },
track.trackSize.value(),
VideoTileTrack{ GroupCall::TrackPointer(track), row },
GroupCall::TrackSizeValue(track),
std::move(pinned));
};
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {
@ -908,18 +956,24 @@ void Panel::toggleWideControls(bool shown) {
}
_showWideControls = shown;
crl::on_main(widget(), [=] {
if (_wideControlsShown == _showWideControls) {
return;
}
_wideControlsShown = _showWideControls;
_wideControlsAnimation.start(
[=] { updateButtonsGeometry(); },
_wideControlsShown ? 0. : 1.,
_wideControlsShown ? 1. : 0.,
st::slideWrapDuration);
updateWideControlsVisibility();
});
}
void Panel::updateWideControlsVisibility() {
const auto shown = _showWideControls
|| (_stickedTooltipClose != nullptr);
if (_wideControlsShown == shown) {
return;
}
_wideControlsShown = shown;
_wideControlsAnimation.start(
[=] { updateButtonsGeometry(); },
_wideControlsShown ? 0. : 1.,
_wideControlsShown ? 1. : 0.,
st::slideWrapDuration);
}
void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
const auto validateRecordingMark = [=](bool recording) {
if (!recording && _recordingMark) {
@ -988,6 +1042,7 @@ void Panel::subscribeToChanges(not_null<Data::GroupCall*> real) {
_call->isSharingCameraValue()
) | rpl::start_with_next([=] {
refreshVideoButtons();
showStickedTooltip();
}, widget()->lifetime());
rpl::combine(
@ -1373,6 +1428,7 @@ bool Panel::updateMode() {
updateButtonsStyles();
refreshControlsBackground();
updateControlsGeometry();
showStickedTooltip();
return true;
}
@ -1557,8 +1613,12 @@ void Panel::trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime) {
}
void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
if (_niceTooltip) {
_niceTooltip.release()->toggleAnimated(false);
if (_stickedTooltipClose) {
if (!over) {
return;
}
} else {
hideNiceTooltip();
}
if (over) {
Ui::Integration::Instance().registerLeaveSubscription(control);
@ -1569,7 +1629,37 @@ void Panel::trackControlOver(not_null<Ui::RpWidget*> control, bool over) {
toggleWideControls(over);
}
void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
void Panel::showStickedTooltip() {
static const auto kHasCamera = !Webrtc::GetVideoInputList().empty();
if (!(_stickedTooltipsShown & StickedTooltip::Camera)
&& (_mode.current() == PanelMode::Wide)
&& _video
&& _call->videoIsWorking()
&& !_call->mutedByAdmin()
&& kHasCamera) { // Don't recount this every time for now.
showNiceTooltip(_video, NiceTooltipType::Sticked);
return;
}
hideStickedTooltip(
StickedTooltip::Camera,
StickedTooltipHide::Unavailable);
if (!(_stickedTooltipsShown & StickedTooltip::Microphone)
&& (_mode.current() == PanelMode::Wide)
&& _mute
&& !_call->mutedByAdmin()
&& false) { // Check if there is incoming sound.
showNiceTooltip(_mute->outer(), NiceTooltipType::Sticked);
return;
}
hideStickedTooltip(
StickedTooltip::Microphone,
StickedTooltipHide::Unavailable);
}
void Panel::showNiceTooltip(
not_null<Ui::RpWidget*> control,
NiceTooltipType type) {
auto text = [&]() -> rpl::producer<QString> {
if (control == _screenShare.data()) {
if (_call->mutedByAdmin()) {
@ -1597,38 +1687,95 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
}();
if (!text
|| _wideControlsAnimation.animating()
|| !_wideControlsShown) {
|| !_wideControlsShown
|| _stickedTooltipClose) {
return;
}
const auto inner = [&]() -> Ui::RpWidget* {
const auto normal = (type == NiceTooltipType::Normal);
auto container = normal
? nullptr
: Ui::CreateChild<Ui::RpWidget>(widget().get());
const auto label = Ui::CreateChild<Ui::FlatLabel>(
(normal ? widget().get() : container),
std::move(text),
st::groupCallNiceTooltipLabel);
if (normal) {
return label;
}
const auto button = Ui::CreateChild<Ui::IconButton>(
container,
st::groupCallStickedTooltipClose);
rpl::combine(
label->sizeValue(),
button->sizeValue()
) | rpl::start_with_next([=](QSize text, QSize close) {
const auto height = std::max(text.height(), close.height());
container->resize(text.width() + close.width(), height);
label->move(0, (height - text.height()) / 2);
button->move(text.width(), (height - close.height()) / 2);
}, container->lifetime());
button->setClickedCallback([=] {
hideStickedTooltip(StickedTooltipHide::Discarded);
});
_stickedTooltipClose = button;
updateWideControlsVisibility();
return container;
}();
_niceTooltip.create(
widget().get(),
object_ptr<Ui::FlatLabel>(
widget().get(),
std::move(text),
st::groupCallNiceTooltipLabel),
st::groupCallNiceTooltip);
object_ptr<Ui::RpWidget>::fromRaw(inner),
(type == NiceTooltipType::Sticked
? st::groupCallStickedTooltip
: st::groupCallNiceTooltip));
const auto tooltip = _niceTooltip.data();
const auto weak = QPointer<QWidget>(tooltip);
const auto destroy = [=] {
delete weak.data();
};
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
if (type != NiceTooltipType::Sticked) {
tooltip->setAttribute(Qt::WA_TransparentForMouseEvents);
}
tooltip->setHiddenCallback(destroy);
base::qt_signal_producer(
control.get(),
&QObject::destroyed
) | rpl::start_with_next(destroy, tooltip->lifetime());
const auto geometry = control->geometry();
_niceTooltipControl = control;
updateTooltipGeometry();
tooltip->toggleAnimated(true);
}
void Panel::updateTooltipGeometry() {
if (!_niceTooltip) {
return;
} else if (!_niceTooltipControl) {
hideNiceTooltip();
return;
}
const auto geometry = _niceTooltipControl->geometry();
const auto weak = QPointer<QWidget>(_niceTooltip);
const auto countPosition = [=](QSize size) {
const auto strong = weak.data();
if (!strong) {
return QPoint();
}
const auto wide = (_mode.current() == PanelMode::Wide);
const auto top = geometry.y()
- st::groupCallNiceTooltipTop
- (wide ? st::groupCallNiceTooltipTop : 0)
- size.height();
const auto middle = geometry.center().x();
if (!strong) {
return QPoint();
} else if (!wide) {
return QPoint(
std::max(
std::min(
middle - size.width() / 2,
(widget()->width()
- st::groupCallMembersMargin.right()
- size.width())),
st::groupCallMembersMargin.left()),
top);
}
const auto back = _controlsBackgroundWide.data();
if (size.width() >= _viewport->widget()->width()) {
return QPoint(_viewport->widget()->x(), top);
@ -1645,8 +1792,7 @@ void Panel::showNiceTooltip(not_null<Ui::RpWidget*> control) {
return QPoint(middle - size.width() / 2, top);
}
};
tooltip->pointAt(geometry, RectPart::Top, countPosition);
tooltip->toggleAnimated(true);
_niceTooltip->pointAt(geometry, RectPart::Top, countPosition);
}
void Panel::trackControls(bool track) {
@ -1832,6 +1978,7 @@ void Panel::updateButtonsGeometry() {
width,
st::groupCallMembersBottomSkip);
}
updateTooltipGeometry();
}
bool Panel::videoButtonInNarrowMode() const {

View file

@ -84,6 +84,24 @@ private:
using State = GroupCall::State;
struct ControlsBackgroundNarrow;
enum class NiceTooltipType {
Normal,
Sticked,
};
enum class StickedTooltip {
Camera = 0x01,
Microphone = 0x02,
};
friend constexpr inline bool is_flag_type(StickedTooltip) {
return true;
};
using StickedTooltips = base::flags<StickedTooltip>;
enum class StickedTooltipHide {
Unavailable,
Activated,
Discarded,
};
std::unique_ptr<Ui::Window> createWindow();
[[nodiscard]] not_null<Ui::RpWidget*> widget() const;
@ -111,11 +129,18 @@ private:
void trackControl(Ui::RpWidget *widget, rpl::lifetime &lifetime);
void trackControlOver(not_null<Ui::RpWidget*> control, bool over);
void showNiceTooltip(not_null<Ui::RpWidget*> control);
void showNiceTooltip(
not_null<Ui::RpWidget*> control,
NiceTooltipType type = NiceTooltipType::Normal);
void showStickedTooltip();
void hideStickedTooltip(StickedTooltipHide hide);
void hideStickedTooltip(StickedTooltip type, StickedTooltipHide hide);
void hideNiceTooltip();
bool updateMode();
void updateControlsGeometry();
void updateButtonsGeometry();
void updateTooltipGeometry();
void updateButtonsStyles();
void updateMembersGeometry();
void refreshControlsBackground();
@ -127,6 +152,7 @@ private:
std::optional<bool> overrideWideMode = std::nullopt);
void refreshTopButton();
void toggleWideControls(bool shown);
void updateWideControlsVisibility();
[[nodiscard]] bool videoButtonInNarrowMode() const;
void endCall();
@ -202,6 +228,9 @@ private:
std::unique_ptr<Ui::CallMuteButton> _mute;
object_ptr<Ui::CallButton> _hangup;
object_ptr<Ui::ImportantTooltip> _niceTooltip = { nullptr };
QPointer<Ui::IconButton> _stickedTooltipClose;
QPointer<Ui::RpWidget> _niceTooltipControl;
StickedTooltips _stickedTooltipsShown;
Fn<void()> _callShareLinkCallback;
const std::unique_ptr<Toasts> _toasts;

@ -1 +1 @@
Subproject commit d8abc60245236e388206e2021bfeb58e83e504fc
Subproject commit 825ef11f1a5c6b00cd571c24525c84dca431eaa2