Add video placeholder if can't receive it.

This commit is contained in:
John Preston 2021-06-15 12:33:21 +04:00
parent b2bf8244dd
commit f18e157e46
16 changed files with 249 additions and 79 deletions

View File

@ -2058,6 +2058,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_group_call_ptt_delay" = "Push to Talk release delay: {delay}";
"lng_group_call_share" = "Share Invite Link";
"lng_group_call_noise_suppression" = "Enable Noise Suppression";
"lng_group_call_limit#one" = "Video is only available\nfor the first {count} member";
"lng_group_call_limit#other" = "Video is only available\nfor the first {count} members";
"lng_group_call_share_speaker" = "Users with this link can speak";
"lng_group_call_copy_speaker_link" = "Copy Speaker Link";
"lng_group_call_copy_listener_link" = "Copy Listener Link";

View File

@ -1229,6 +1229,9 @@ groupCallVideoTile: GroupCallVideoTile {
groupCallVideoSmallSkip: 4px;
groupCallVideoLargeSkip: 6px;
groupCallVideoPlaceholderHeight: 212px;
groupCallVideoPlaceholderIconTop: 50px;
groupCallVideoPlaceholderTextTop: 120px;
groupCallTooltip: Tooltip(defaultTooltip) {
textBg: groupCallMembersBg;

View File

@ -821,6 +821,9 @@ void GroupCall::setState(State state) {
if (const auto call = _peer->groupCall(); call && call->id() == _id) {
call->setInCall();
}
if (!videoIsWorking()) {
refreshHasNotShownVideo();
}
}
if (false
@ -1043,6 +1046,9 @@ void GroupCall::markEndpointActive(
bool paused) {
if (!endpoint) {
return;
} else if (active && !videoIsWorking()) {
refreshHasNotShownVideo();
return;
}
const auto i = _activeVideoTracks.find(endpoint);
const auto changed = active
@ -1067,26 +1073,34 @@ void GroupCall::markEndpointActive(
.peer = endpoint.peer,
}).first;
const auto track = i->second.track.get();
if (!track->frameSize().isEmpty()
|| track->state() == Webrtc::VideoState::Paused) {
track->renderNextFrame(
) | rpl::start_with_next([=] {
auto &activeTrack = _activeVideoTracks[endpoint];
const auto size = track->frameSize();
if (size.isEmpty()) {
track->markFrameShown();
} else if (!activeTrack.shown) {
activeTrack.shown = true;
markTrackShown(endpoint, true);
}
activeTrack.trackSize = size;
}, i->second.lifetime);
const auto size = track->frameSize();
i->second.trackSize = size;
if (!size.isEmpty() || paused) {
i->second.shown = true;
shown = true;
} else {
auto hasFrame = track->renderNextFrame() | rpl::map([=] {
return !track->frameSize().isEmpty();
});
auto isPaused = track->stateValue(
) | rpl::map([=](Webrtc::VideoState state) {
return (state == Webrtc::VideoState::Paused);
});
rpl::merge(
std::move(hasFrame),
std::move(isPaused)
) | rpl::filter([=](bool shouldShow) {
return shouldShow;
track->stateValue(
) | rpl::filter([=](Webrtc::VideoState state) {
return (state == Webrtc::VideoState::Paused)
&& !_activeVideoTracks[endpoint].shown;
}) | rpl::start_with_next([=] {
_activeVideoTracks[endpoint].shownTrackingLifetime.destroy();
_activeVideoTracks[endpoint].shown = true;
markTrackShown(endpoint, true);
}, i->second.shownTrackingLifetime);
}, i->second.lifetime);
}
addVideoOutput(i->first.id, { track->sink() });
} else {
@ -1109,10 +1123,11 @@ void GroupCall::markTrackShown(const VideoEndpoint &endpoint, bool shown) {
const auto changed = shown
? _shownVideoTracks.emplace(endpoint).second
: _shownVideoTracks.remove(endpoint);
if (changed) {
_videoStreamShownUpdates.fire_copy({ endpoint, shown });
if (!changed) {
return;
}
if (shown && changed && endpoint.type == VideoEndpointType::Screen) {
_videoStreamShownUpdates.fire_copy({ endpoint, shown });
if (shown && endpoint.type == VideoEndpointType::Screen) {
crl::on_main(this, [=] {
if (_shownVideoTracks.contains(endpoint)) {
pinVideoEndpoint(endpoint);
@ -2431,21 +2446,37 @@ void GroupCall::updateRequestedVideoChannelsDelayed() {
});
}
void GroupCall::refreshHasNotShownVideo() {
if (!_joinState.ssrc || hasNotShownVideo()) {
return;
}
const auto real = lookupReal();
Assert(real != nullptr);
const auto hasVideo = [&](const Data::GroupCallParticipant &data) {
return (data.peer != _joinAs)
&& (!GetCameraEndpoint(data.videoParams).empty()
|| !GetScreenEndpoint(data.videoParams).empty());
};
_hasNotShownVideo = _joinState.ssrc
&& ranges::any_of(real->participants(), hasVideo);
}
void GroupCall::fillActiveVideoEndpoints() {
const auto real = lookupReal();
Assert(real != nullptr);
if (const auto participant = real->participantByPeer(_joinAs)) {
_videoIsWorking = participant->videoJoined;
const auto me = real->participantByPeer(_joinAs);
if (me && me->videoJoined) {
_videoIsWorking = true;
_hasNotShownVideo = false;
} else {
refreshHasNotShownVideo();
_videoIsWorking = false;
}
if (!videoIsWorking()) {
toggleVideo(false);
toggleScreenSharing(std::nullopt);
}
const auto &participants = real->participants();
const auto &large = _videoEndpointLarge.current();
auto largeFound = false;
auto endpoints = _activeVideoTracks | ranges::views::transform([](
@ -2469,7 +2500,7 @@ void GroupCall::fillActiveVideoEndpoints() {
};
using Type = VideoEndpointType;
if (_videoIsWorking.current()) {
for (const auto &participant : participants) {
for (const auto &participant : real->participants()) {
const auto camera = GetCameraEndpoint(participant.videoParams);
if (camera != _cameraEndpoint
&& camera != _screenEndpoint
@ -2485,7 +2516,6 @@ void GroupCall::fillActiveVideoEndpoints() {
feedOne({ Type::Screen, participant.peer, screen }, paused);
}
}
const auto pausedState = Webrtc::VideoState::Paused;
feedOne(
{ Type::Camera, _joinAs, cameraSharingEndpoint() },
isCameraPaused());

View File

@ -324,9 +324,11 @@ public:
struct VideoTrack {
std::unique_ptr<Webrtc::VideoTrack> track;
rpl::variable<QSize> trackSize;
PeerData *peer = nullptr;
rpl::lifetime shownTrackingLifetime;
rpl::lifetime lifetime;
Group::VideoQuality quality = Group::VideoQuality();
bool shown = false;
[[nodiscard]] explicit operator bool() const {
return (track != nullptr);
@ -366,6 +368,12 @@ public:
[[nodiscard]] rpl::producer<bool> videoIsWorkingValue() const {
return _videoIsWorking.value();
}
[[nodiscard]] bool hasNotShownVideo() const {
return _hasNotShownVideo.current();
}
[[nodiscard]] rpl::producer<bool> hasNotShownVideoValue() const {
return _hasNotShownVideo.value();
}
void setCurrentAudioDevice(bool input, const QString &deviceId);
void setCurrentVideoDevice(const QString &deviceId);
@ -514,6 +522,7 @@ private:
void updateRequestedVideoChannels();
void updateRequestedVideoChannelsDelayed();
void fillActiveVideoEndpoints();
void refreshHasNotShownVideo();
void editParticipant(
not_null<PeerData*> participantPeer,
@ -570,6 +579,7 @@ private:
rpl::variable<MuteState> _muted = MuteState::Muted;
rpl::variable<bool> _canManage = false;
rpl::variable<bool> _videoIsWorking = false;
rpl::variable<bool> _hasNotShownVideo = false;
bool _initialMuteStateSent = false;
bool _acceptFields = false;

View File

@ -29,6 +29,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "core/application.h" // Core::App().domain, .activeWindow.
#include "main/main_domain.h" // Core::App().domain().activate.
#include "main/main_session.h"
#include "main/main_account.h" // account().appConfig().
#include "main/main_app_config.h" // appConfig().get<double>().
#include "boxes/peers/edit_participants_box.h" // SubscribeToMigration.
#include "window/window_controller.h" // Controller::sessionController.
#include "window/window_session_controller.h"
@ -41,9 +43,110 @@ namespace {
constexpr auto kKeepRaisedHandStatusDuration = 3 * crl::time(1000);
constexpr auto kShadowMaxAlpha = 74;
constexpr auto kUserpicSizeForBlur = 40;
constexpr auto kUserpicBlurRadius = 8;
using Row = MembersRow;
void SetupVideoPlaceholder(
not_null<Ui::RpWidget*> widget,
not_null<PeerData*> chat) {
struct State {
QImage blurred;
QImage rounded;
InMemoryKey key = {};
std::shared_ptr<Data::CloudImageView> view;
qint64 blurredCacheKey = 0;
};
const auto state = widget->lifetime().make_state<State>();
const auto refreshBlurred = [=] {
const auto key = chat->userpicUniqueKey(state->view);
if (state->key == key && !state->blurred.isNull()) {
return;
}
constexpr auto size = kUserpicSizeForBlur;
state->key = key;
state->blurred = QImage(
QSize(size, size),
QImage::Format_ARGB32_Premultiplied);
{
auto p = Painter(&state->blurred);
auto hq = PainterHighQualityEnabler(p);
chat->paintUserpicSquare(p, state->view, 0, 0, size);
}
state->blurred = Images::BlurLargeImage(
std::move(state->blurred),
kUserpicBlurRadius);
widget->update();
};
const auto refreshRounded = [=](QSize size) {
refreshBlurred();
const auto key = state->blurred.cacheKey();
if (state->rounded.size() == size && state->blurredCacheKey == key) {
return;
}
state->blurredCacheKey = key;
state->rounded = Images::prepare(
state->blurred,
size.width(),
size.width(), // Square
Images::Option::Smooth,
size.width(),
size.height());
{
auto p = QPainter(&state->rounded);
p.fillRect(
0,
0,
size.width(),
size.height(),
QColor(0, 0, 0, Viewport::kShadowMaxAlpha));
}
state->rounded = Images::prepare(
std::move(state->rounded),
size.width(),
size.height(),
(Images::Option::RoundedLarge | Images::Option::RoundedAll),
size.width(),
size.height());
};
chat->loadUserpic();
refreshBlurred();
widget->paintRequest(
) | rpl::start_with_next([=] {
const auto size = QSize(
widget->width(),
widget->height() - st::groupCallVideoSmallSkip);
refreshRounded(size * cIntRetinaFactor());
auto p = QPainter(widget);
const auto inner = QRect(QPoint(), size);
p.drawImage(inner, state->rounded);
st::groupCallPaused.paint(
p,
(size.width() - st::groupCallPaused.width()) / 2,
st::groupCallVideoPlaceholderIconTop,
size.width());
const auto skip = st::groupCallVideoLargeSkip;
const auto limit = chat->session().account().appConfig().get<double>(
"groupcall_video_participants_max",
30.);
p.setPen(st::groupCallVideoTextFg);
const auto text = QRect(
skip,
st::groupCallVideoPlaceholderTextTop,
(size.width() - 2 * skip),
size.height() - st::groupCallVideoPlaceholderTextTop);
p.setFont(st::semiboldFont);
p.drawText(
text,
tr::lng_group_call_limit(tr::now, lt_count, int(limit)),
style::al_top);
}, widget->lifetime());
}
} // namespace
class Members::Controller final
@ -1467,6 +1570,7 @@ Members::Members(
, _layout(_scroll->setOwnedWidget(
object_ptr<Ui::VerticalLayout>(_scroll.data())))
, _videoWrap(_layout->add(object_ptr<Ui::RpWidget>(_layout.get())))
, _videoPlaceholder(std::make_unique<Ui::RpWidget>(_videoWrap.get()))
, _viewport(
std::make_unique<Viewport>(
_videoWrap.get(),
@ -1704,11 +1808,27 @@ void Members::trackViewportGeometry() {
_scroll->scrollTopValue(
) | rpl::skip(1) | rpl::start_with_next(move, _viewport->lifetime());
_viewport->fullHeightValue(
) | rpl::start_with_next([=](int height) {
_videoWrap->resize(_videoWrap->width(), height);
move();
resize();
rpl::combine(
_layout->widthValue(),
_call->hasNotShownVideoValue()
) | rpl::start_with_next([=](int width, bool has) {
const auto height = has ? st::groupCallVideoPlaceholderHeight : 0;
_videoPlaceholder->setGeometry(0, 0, width, height);
}, _videoPlaceholder->lifetime());
SetupVideoPlaceholder(_videoPlaceholder.get(), _call->peer());
rpl::combine(
_videoPlaceholder->heightValue(),
_viewport->fullHeightValue()
) | rpl::start_with_next([=](int placeholder, int viewport) {
_videoWrap->resize(
_videoWrap->width(),
std::max(placeholder, viewport));
if (viewport > 0) {
move();
resize();
}
}, _viewport->lifetime());
}

View File

@ -101,6 +101,7 @@ private:
std::unique_ptr<Controller> _listController;
not_null<Ui::VerticalLayout*> _layout;
const not_null<Ui::RpWidget*> _videoWrap;
const std::unique_ptr<Ui::RpWidget> _videoPlaceholder;
std::unique_ptr<Viewport> _viewport;
rpl::variable<Ui::RpWidget*> _addMemberButton = nullptr;
RpWidget *_topSkip = nullptr;

View File

@ -854,6 +854,7 @@ void Panel::setupVideo(not_null<Viewport*> viewport) {
viewport->add(
endpoint,
VideoTileTrack{ track.track.get(), row },
track.trackSize.value(),
std::move(pinned));
};
for (const auto &[endpoint, track] : _call->activeVideoTracks()) {

View File

@ -225,10 +225,12 @@ void Viewport::setControlsShown(float64 shown) {
void Viewport::add(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer<QSize> trackSize,
rpl::producer<bool> pinned) {
_tiles.push_back(std::make_unique<VideoTile>(
endpoint,
track,
std::move(trackSize),
std::move(pinned),
[=] { widget()->update(); }));
@ -711,11 +713,13 @@ void Viewport::updateTilesGeometryColumn(int outerWidth) {
const auto layoutNext = [&](not_null<VideoTile*> tile) {
const auto size = tile->trackOrUserpicSize();
const auto shown = !size.isEmpty() && _large && tile != _large;
const auto height = shown
? st::groupCallNarrowVideoHeight
: 0;
setTileGeometry(tile, { 0, y + top, outerWidth, height });
top += height ? (height + st::groupCallVideoSmallSkip) : 0;
const auto height = st::groupCallNarrowVideoHeight;
if (!shown) {
tile->hide();
} else {
setTileGeometry(tile, { 0, y + top, outerWidth, height });
top += height + st::groupCallVideoSmallSkip;
}
};
const auto topPeer = _large ? _large->row()->peer().get() : nullptr;
const auto reorderNeeded = [&] {

View File

@ -39,6 +39,7 @@ enum class VideoQuality;
struct VideoTileTrack {
Webrtc::VideoTrack *track = nullptr;
MembersRow *row = nullptr;
rpl::variable<QSize> trackSize;
[[nodiscard]] explicit operator bool() const {
return track != nullptr;
@ -77,6 +78,7 @@ public:
void add(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer<QSize> trackSize,
rpl::producer<bool> pinned);
void remove(const VideoEndpoint &endpoint);
void showLarge(const VideoEndpoint &endpoint);
@ -91,6 +93,8 @@ public:
[[nodiscard]] rpl::lifetime &lifetime();
static constexpr auto kShadowMaxAlpha = 80;
private:
struct Textures;
class VideoTile;
@ -132,8 +136,6 @@ private:
}
};
static constexpr auto kShadowMaxAlpha = 80;
void setup();
[[nodiscard]] bool wide() const;

View File

@ -320,7 +320,6 @@ Viewport::RendererGL::RendererGL(not_null<Viewport*> owner)
void Viewport::RendererGL::init(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
_factor = widget->devicePixelRatio();
_frameBuffer.emplace();
_frameBuffer->setUsagePattern(QOpenGLBuffer::DynamicDraw);
_frameBuffer->create();
@ -427,7 +426,11 @@ void Viewport::RendererGL::setDefaultViewport(QOpenGLFunctions &f) {
void Viewport::RendererGL::paint(
not_null<QOpenGLWidget*> widget,
QOpenGLFunctions &f) {
_factor = widget->devicePixelRatio();
const auto factor = widget->devicePixelRatio();
if (_factor != factor) {
_factor = factor;
_buttons.invalidate();
}
_viewport = widget->size();
const auto defaultFramebufferObject = widget->defaultFramebufferObject();
@ -1054,7 +1057,6 @@ void Viewport::RendererGL::ensureButtonsImage() {
if (_buttons) {
return;
}
const auto factor = cIntRetinaFactor();
const auto pinOnSize = VideoTile::PinInnerSize(true);
const auto pinOffSize = VideoTile::PinInnerSize(false);
const auto backSize = VideoTile::BackInnerSize();
@ -1074,18 +1076,18 @@ void Viewport::RendererGL::ensureButtonsImage() {
+ backSize.height()
+ muteSize.height()
+ pausedSize.height()));
const auto imageSize = fullSize * factor;
const auto imageSize = fullSize * _factor;
auto image = _buttons.takeImage();
if (image.size() != imageSize) {
image = QImage(imageSize, QImage::Format_ARGB32_Premultiplied);
}
image.fill(Qt::transparent);
image.setDevicePixelRatio(cRetinaFactor());
image.setDevicePixelRatio(_factor);
{
auto p = Painter(&image);
auto hq = PainterHighQualityEnabler(p);
_pinOn = QRect(QPoint(), pinOnSize * factor);
_pinOn = QRect(QPoint(), pinOnSize * _factor);
VideoTile::PaintPinButton(
p,
true,
@ -1096,7 +1098,9 @@ void Viewport::RendererGL::ensureButtonsImage() {
&_pinIcon);
const auto pinOffTop = pinOnSize.height();
_pinOff = QRect(QPoint(0, pinOffTop) * factor, pinOffSize * factor);
_pinOff = QRect(
QPoint(0, pinOffTop) * _factor,
pinOffSize * _factor);
VideoTile::PaintPinButton(
p,
false,
@ -1107,7 +1111,7 @@ void Viewport::RendererGL::ensureButtonsImage() {
&_pinIcon);
const auto backTop = pinOffTop + pinOffSize.height();
_back = QRect(QPoint(0, backTop) * factor, backSize * factor);
_back = QRect(QPoint(0, backTop) * _factor, backSize * _factor);
VideoTile::PaintBackButton(
p,
0,
@ -1116,16 +1120,18 @@ void Viewport::RendererGL::ensureButtonsImage() {
&_pinBackground);
const auto muteTop = backTop + backSize.height();
_muteOn = QRect(QPoint(0, muteTop) * factor, muteSize * factor);
_muteOn = QRect(QPoint(0, muteTop) * _factor, muteSize * _factor);
_muteIcon.paint(p, { 0, muteTop }, 1.);
_muteOff = QRect(
QPoint(muteSize.width(), muteTop) * factor,
muteSize * factor);
QPoint(muteSize.width(), muteTop) * _factor,
muteSize * _factor);
_muteIcon.paint(p, { muteSize.width(), muteTop }, 0.);
const auto pausedTop = muteTop + muteSize.height();
_paused = QRect(QPoint(0, pausedTop) * factor, pausedSize * factor);
_paused = QRect(
QPoint(0, pausedTop) * _factor,
pausedSize * _factor);
st::groupCallPaused.paint(p, 0, pausedTop, fullSize.width());
}
_buttons.setImage(std::move(image));

View File

@ -25,11 +25,13 @@ constexpr auto kPausedVideoSize = 90;
Viewport::VideoTile::VideoTile(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer<QSize> trackSize,
rpl::producer<bool> pinned,
Fn<void()> update)
: _endpoint(endpoint)
, _update(std::move(update))
, _track(track) {
, _track(track)
, _trackSize(std::move(trackSize)) {
Expects(track.track != nullptr);
Expects(track.row != nullptr);
@ -254,19 +256,7 @@ void Viewport::VideoTile::setup(rpl::producer<bool> pinned) {
}, _lifetime);
_track.track->renderNextFrame(
) | rpl::start_with_next([=] {
const auto size = _track.track->frameSize();
if (size.isEmpty()) {
_track.track->markFrameShown();
} else {
_trackSize = size;
}
_update();
}, _lifetime);
if (const auto size = _track.track->frameSize(); !size.isEmpty()) {
_trackSize = size;
}
) | rpl::start_with_next(_update, _lifetime);
updateTopControlsSize();
}

View File

@ -26,6 +26,7 @@ public:
VideoTile(
const VideoEndpoint &endpoint,
VideoTileTrack track,
rpl::producer<QSize> trackSize,
rpl::producer<bool> pinned,
Fn<void()> update);

View File

@ -304,10 +304,10 @@ void Application::showOpenGLCrashNotification() {
Local::writeSettings();
};
_window->show(Box<ConfirmBox>(
"Last time OpenGL crashed on initialization. "
"Perhaps it is a problem with your graphics card driver.\n\n"
"Right now OpenGL was disabled. You can try to enable it back "
"or keep it disabled, if it continues crashing.",
"There may be a problem with your graphics drivers and OpenGL. "
"Try updating your drivers.\n\n"
"OpenGL has been disabled. You can try to enable it again "
"or keep it disabled if crashes continue.",
"Enable",
"Keep Disabled",
enable,

View File

@ -3274,7 +3274,6 @@ void OverlayWidget::paint(not_null<Renderer*> renderer) {
}
paintRadialLoading(renderer);
} else {
int a = 0;
if (_themePreviewShown) {
renderer->paintThemePreview(_themePreviewRect);
} else if (documentBubbleShown() && !_docRect.isEmpty()) {

View File

@ -406,21 +406,22 @@ void Pip::RendererGL::paintTransformedContent(
_f->glActiveTexture(rgbaFrame ? GL_TEXTURE1 : GL_TEXTURE3);
_shadowImage.bind(*_f);
const auto globalFactor = cIntRetinaFactor();
const auto fadeAlpha = st::radialBg->c.alphaF() * geometry.fade;
const auto roundRect = transformRect(RoundingRect(geometry));
program->setUniformValue("roundRect", Uniform(roundRect));
program->setUniformValue("h_texture", GLint(rgbaFrame ? 1 : 3));
program->setUniformValue("h_size", QSizeF(_shadowImage.image().size()));
program->setUniformValue("h_extend", QVector4D(
st::callShadow.extend.left(),
st::callShadow.extend.top(),
st::callShadow.extend.right(),
st::callShadow.extend.bottom()));
st::callShadow.extend.left() * globalFactor,
st::callShadow.extend.top() * globalFactor,
st::callShadow.extend.right() * globalFactor,
st::callShadow.extend.bottom() * globalFactor));
program->setUniformValue("h_components", QVector4D(
float(st::callShadow.topLeft.width()),
float(st::callShadow.topLeft.height()),
float(st::callShadow.left.width()),
float(st::callShadow.top.height())));
float(st::callShadow.topLeft.width() * globalFactor),
float(st::callShadow.topLeft.height() * globalFactor),
float(st::callShadow.left.width() * globalFactor),
float(st::callShadow.top.height() * globalFactor)));
program->setUniformValue(
"roundRadius",
GLfloat(st::roundRadiusLarge * _factor));

@ -1 +1 @@
Subproject commit 9adb220f798cadc0848d3e1efbf32fb2a98a3bcb
Subproject commit d147f286cfe2a23c49c438a63be9aa1c0a04344b