Improve video userpics in chat history.

This commit is contained in:
John Preston 2020-07-03 22:57:30 +04:00
parent f99960e1f6
commit 5c5414b680
6 changed files with 157 additions and 48 deletions

View File

@ -309,6 +309,18 @@ bool ShouldAutoPlay(
document->size); document->size);
} }
bool ShouldAutoPlay(
const Full &data,
not_null<PeerData*> peer,
not_null<PhotoData*> photo) {
const auto source = SourceFromPeer(peer);
const auto size = photo->videoByteSize();
return photo->hasVideo()
&& (data.shouldDownload(source, Type::AutoPlayGIF, size)
|| data.shouldDownload(source, Type::AutoPlayVideo, size)
|| data.shouldDownload(source, Type::AutoPlayVideoMessage, size));
}
Full WithDisabledAutoPlay(const Full &data) { Full WithDisabledAutoPlay(const Full &data) {
auto result = data; auto result = data;
for (const auto source : enums_view<Source>(kSourcesCount)) { for (const auto source : enums_view<Source>(kSourcesCount)) {

View File

@ -120,6 +120,10 @@ private:
const Full &data, const Full &data,
not_null<PeerData*> peer, not_null<PeerData*> peer,
not_null<DocumentData*> document); not_null<DocumentData*> document);
[[nodiscard]] bool ShouldAutoPlay(
const Full &data,
not_null<PeerData*> peer,
not_null<PhotoData*> photo);
[[nodiscard]] Full WithDisabledAutoPlay(const Full &data); [[nodiscard]] Full WithDisabledAutoPlay(const Full &data);

View File

@ -3208,6 +3208,10 @@ void Session::checkPlayingAnimations() {
if (document->isAnimation() || document->isVideoFile()) { if (document->isAnimation() || document->isVideoFile()) {
check.emplace(view); check.emplace(view);
} }
} else if (const auto photo = media->getPhoto()) {
if (photo->hasVideo()) {
check.emplace(view);
}
} }
} }
} }

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_player.h" #include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h" #include "media/streaming/media_streaming_document.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h"
#include "ui/image/image.h" #include "ui/image/image.h"
#include "ui/grouped_layout.h" #include "ui/grouped_layout.h"
#include "data/data_session.h" #include "data/data_session.h"
@ -25,6 +26,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h" #include "data/data_photo.h"
#include "data/data_photo_media.h" #include "data/data_photo_media.h"
#include "data/data_file_origin.h" #include "data/data_file_origin.h"
#include "data/data_auto_download.h"
#include "core/application.h"
#include "app.h" #include "app.h"
#include "styles/style_history.h" #include "styles/style_history.h"
@ -35,6 +38,17 @@ using Data::PhotoSize;
} // namespace } // namespace
struct Photo::Streamed {
explicit Streamed(std::shared_ptr<::Media::Streaming::Document> shared);
::Media::Streaming::Instance instance;
QImage frozenFrame;
};
Photo::Streamed::Streamed(
std::shared_ptr<::Media::Streaming::Document> shared)
: instance(std::move(shared), nullptr) {
}
Photo::Photo( Photo::Photo(
not_null<Element*> parent, not_null<Element*> parent,
not_null<HistoryItem*> realParent, not_null<HistoryItem*> realParent,
@ -61,7 +75,7 @@ Photo::~Photo() {
if (_streamed || _dataMedia) { if (_streamed || _dataMedia) {
if (_streamed) { if (_streamed) {
_data->owner().streaming().keepAlive(_data); _data->owner().streaming().keepAlive(_data);
setStreamed(nullptr); stopAnimation();
} }
if (_dataMedia) { if (_dataMedia) {
_data->owner().keepAlive(base::take(_dataMedia)); _data->owner().keepAlive(base::take(_dataMedia));
@ -107,7 +121,7 @@ bool Photo::hasHeavyPart() const {
} }
void Photo::unloadHeavyPart() { void Photo::unloadHeavyPart() {
setStreamed(nullptr); stopAnimation();
_dataMedia = nullptr; _dataMedia = nullptr;
} }
@ -220,7 +234,7 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width()); auto rthumb = style::rtlrect(paintx, painty, paintw, painth, width());
if (_serviceWidth > 0) { if (_serviceWidth > 0) {
paintUserpicFrame(p, rthumb.topLeft()); paintUserpicFrame(p, rthumb.topLeft(), selected);
} else { } else {
if (bubble) { if (bubble) {
if (!_caption.isEmpty()) { if (!_caption.isEmpty()) {
@ -316,21 +330,40 @@ void Photo::draw(Painter &p, const QRect &r, TextSelection selection, crl::time
} }
} }
void Photo::paintUserpicFrame(Painter &p, QPoint photoPosition) const { void Photo::paintUserpicFrame(
const_cast<Photo*>(this)->validateVideo(); Painter &p,
QPoint photoPosition,
bool selected) const {
const auto autoplay = _data->videoCanBePlayed() && videoAutoplayEnabled();
const auto startPlay = autoplay && !_streamed;
if (startPlay) {
const_cast<Photo*>(this)->playAnimation(true);
} else {
checkStreamedIsStarted();
}
const auto size = QSize{ _pixw, _pixh };
const auto rect = QRect(photoPosition, size);
if (_streamed if (_streamed
&& _streamed->player().ready() && _streamed->instance.player().ready()
&& !_streamed->player().videoSize().isEmpty()) { && !_streamed->instance.player().videoSize().isEmpty()) {
const auto paused = _parent->delegate()->elementIsGifPaused(); const auto paused = _parent->delegate()->elementIsGifPaused();
auto request = ::Media::Streaming::FrameRequest(); auto request = ::Media::Streaming::FrameRequest();
auto size = QSize{ _pixw, _pixh };
request.outer = size * cIntRetinaFactor(); request.outer = size * cIntRetinaFactor();
request.resize = size * cIntRetinaFactor(); request.resize = size * cIntRetinaFactor();
request.radius = ImageRoundRadius::Ellipse; request.radius = ImageRoundRadius::Ellipse;
p.drawImage(QRect(photoPosition, size), _streamed->frame(request)); if (_streamed->instance.playerLocked()) {
if (!paused) { if (_streamed->frozenFrame.isNull()) {
_streamed->markFrameShown(); _streamed->frozenFrame = _streamed->instance.frame(request);
}
p.drawImage(rect, _streamed->frozenFrame);
} else {
_streamed->frozenFrame = QImage();
p.drawImage(rect, _streamed->instance.frame(request));
if (!paused) {
_streamed->instance.markFrameShown();
}
} }
return; return;
} }
@ -349,7 +382,28 @@ void Photo::paintUserpicFrame(Painter &p, QPoint photoPosition) const {
return QPixmap(); return QPixmap();
} }
}(); }();
p.drawPixmap(photoPosition, pix); p.drawPixmap(rect, pix);
if (_data->videoCanBePlayed() && !_streamed) {
auto inner = QRect(rect.x() + (rect.width() - st::msgFileSize) / 2, rect.y() + (rect.height() - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
if (selected) {
p.setBrush(st::msgDateImgBgSelected);
} else {
const auto over = ClickHandler::showAsActive(_openl);
p.setBrush(over ? st::msgDateImgBgOver : st::msgDateImgBg);
}
{
PainterHighQualityEnabler hq(p);
p.drawEllipse(inner);
}
const auto icon = [&]() -> const style::icon * {
return &(selected ? st::historyFileThumbPlaySelected : st::historyFileThumbPlay);
}();
if (icon) {
icon->paintInCenter(p, inner);
}
}
} }
TextState Photo::textState(QPoint point, StateRequest request) const { TextState Photo::textState(QPoint point, StateRequest request) const {
@ -615,28 +669,28 @@ void Photo::validateGroupedCache(
bool Photo::createStreamingObjects() { bool Photo::createStreamingObjects() {
using namespace ::Media::Streaming; using namespace ::Media::Streaming;
setStreamed(std::make_unique<Instance>( setStreamed(std::make_unique<Streamed>(
history()->owner().streaming().sharedDocument( history()->owner().streaming().sharedDocument(
_data, _data,
_realParent->fullId()), _realParent->fullId())));
nullptr)); _streamed->instance.player().updates(
_streamed->player().updates(
) | rpl::start_with_next_error([=](Update &&update) { ) | rpl::start_with_next_error([=](Update &&update) {
handleStreamingUpdate(std::move(update)); handleStreamingUpdate(std::move(update));
}, [=](Error &&error) { }, [=](Error &&error) {
handleStreamingError(std::move(error)); handleStreamingError(std::move(error));
}, _streamed->lifetime()); }, _streamed->instance.lifetime());
if (_streamed->ready()) { if (_streamed->instance.ready()) {
streamingReady(base::duplicate(_streamed->info())); streamingReady(base::duplicate(_streamed->instance.info()));
} }
if (!_streamed->valid()) { if (!_streamed->instance.valid()) {
setStreamed(nullptr); stopAnimation();
return false; return false;
} }
checkStreamedIsStarted();
return true; return true;
} }
void Photo::setStreamed(std::unique_ptr<::Media::Streaming::Instance> value) { void Photo::setStreamed(std::unique_ptr<Streamed> value) {
const auto removed = (_streamed && !value); const auto removed = (_streamed && !value);
const auto set = (!_streamed && value); const auto set = (!_streamed && value);
_streamed = std::move(value); _streamed = std::move(value);
@ -665,14 +719,13 @@ void Photo::handleStreamingUpdate(::Media::Streaming::Update &&update) {
void Photo::handleStreamingError(::Media::Streaming::Error &&error) { void Photo::handleStreamingError(::Media::Streaming::Error &&error) {
_data->setVideoPlaybackFailed(); _data->setVideoPlaybackFailed();
setStreamed(nullptr); stopAnimation();
} }
void Photo::repaintStreamedContent() { void Photo::repaintStreamedContent() {
/* const auto own = activeOwnStreamed(); if (_streamed && !_streamed->frozenFrame.isNull()) {
if (own && !own->frozenFrame.isNull()) { return;
return; } else if (_parent->delegate()->elementIsGifPaused()) {
} else */if (_parent->delegate()->elementIsGifPaused()) {
return; return;
} }
history()->owner().requestViewRepaint(_parent); history()->owner().requestViewRepaint(_parent);
@ -682,36 +735,62 @@ void Photo::streamingReady(::Media::Streaming::Information &&info) {
history()->owner().requestViewRepaint(_parent); history()->owner().requestViewRepaint(_parent);
} }
void Photo::validateVideo() { void Photo::checkAnimation() {
if (!_data->videoCanBePlayed()) { if (_streamed && !videoAutoplayEnabled()) {
setStreamed(nullptr); stopAnimation();
return;
} else if (_streamed) {
return;
} }
if (!createStreamingObjects()) {
_data->setVideoPlaybackFailed();
return;
}
checkStreamedIsStarted();
} }
void Photo::checkStreamedIsStarted() { void Photo::stopAnimation() {
setStreamed(nullptr);
}
void Photo::playAnimation(bool autoplay) {
ensureDataMediaCreated();
if (_streamed && autoplay) {
return;
} else if (_streamed && videoAutoplayEnabled()) {
Core::App().showPhoto(_data, _parent->data());
return;
}
if (_streamed) {
stopAnimation();
} else if (_data->videoCanBePlayed()) {
if (!videoAutoplayEnabled()) {
history()->owner().checkPlayingAnimations();
}
if (!createStreamingObjects()) {
_data->setVideoPlaybackFailed();
return;
}
}
}
void Photo::checkStreamedIsStarted() const {
if (!_streamed) { if (!_streamed) {
return; return;
} else if (_streamed->paused()) { } else if (_streamed->instance.paused()) {
_streamed->resume(); _streamed->instance.resume();
} }
if (_streamed && !_streamed->active() && !_streamed->failed()) { if (_streamed
&& !_streamed->instance.active()
&& !_streamed->instance.failed()) {
const auto position = _data->videoStartPosition(); const auto position = _data->videoStartPosition();
auto options = ::Media::Streaming::PlaybackOptions(); auto options = ::Media::Streaming::PlaybackOptions();
options.position = position; options.position = position;
options.mode = ::Media::Streaming::Mode::Video; options.mode = ::Media::Streaming::Mode::Video;
options.loop = true; options.loop = true;
_streamed->play(options); _streamed->instance.play(options);
} }
} }
bool Photo::videoAutoplayEnabled() const {
return Data::AutoDownload::ShouldAutoPlay(
_data->session().settings().autoDownload(),
_realParent->history()->peer,
_data);
}
TextForMimeData Photo::selectedText(TextSelection selection) const { TextForMimeData Photo::selectedText(TextSelection selection) const {
return _caption.toTextForMimeData(selection); return _caption.toTextForMimeData(selection);
} }

View File

@ -98,8 +98,14 @@ protected:
bool dataLoaded() const override; bool dataLoaded() const override;
private: private:
struct Streamed;
void create(FullMsgId contextId, PeerData *chat = nullptr); void create(FullMsgId contextId, PeerData *chat = nullptr);
void playAnimation(bool autoplay) override;
void stopAnimation() override;
void checkAnimation() override;
void ensureDataMediaCreated() const; void ensureDataMediaCreated() const;
void dataMediaCreated() const; void dataMediaCreated() const;
@ -113,15 +119,18 @@ private:
not_null<uint64*> cacheKey, not_null<uint64*> cacheKey,
not_null<QPixmap*> cache) const; not_null<QPixmap*> cache) const;
void validateVideo(); bool videoAutoplayEnabled() const;
void setStreamed(std::unique_ptr<::Media::Streaming::Instance> value); void setStreamed(std::unique_ptr<Streamed> value);
void repaintStreamedContent(); void repaintStreamedContent();
void checkStreamedIsStarted(); void checkStreamedIsStarted() const;
bool createStreamingObjects(); bool createStreamingObjects();
void handleStreamingUpdate(::Media::Streaming::Update &&update); void handleStreamingUpdate(::Media::Streaming::Update &&update);
void handleStreamingError(::Media::Streaming::Error &&error); void handleStreamingError(::Media::Streaming::Error &&error);
void streamingReady(::Media::Streaming::Information &&info); void streamingReady(::Media::Streaming::Information &&info);
void paintUserpicFrame(Painter &p, QPoint photoPosition) const; void paintUserpicFrame(
Painter &p,
QPoint photoPosition,
bool selected) const;
not_null<PhotoData*> _data; not_null<PhotoData*> _data;
int _serviceWidth = 0; int _serviceWidth = 0;
@ -129,7 +138,7 @@ private:
int _pixh = 1; int _pixh = 1;
Ui::Text::String _caption; Ui::Text::String _caption;
mutable std::shared_ptr<Data::PhotoMedia> _dataMedia; mutable std::shared_ptr<Data::PhotoMedia> _dataMedia;
mutable std::unique_ptr<::Media::Streaming::Instance> _streamed; mutable std::unique_ptr<Streamed> _streamed;
}; };

View File

@ -840,6 +840,7 @@ bool UserpicButton::createStreamingObjects(not_null<PhotoData*> photo) {
_streamed = std::make_unique<Instance>( _streamed = std::make_unique<Instance>(
photo->owner().streaming().sharedDocument(photo, origin), photo->owner().streaming().sharedDocument(photo, origin),
nullptr); nullptr);
_streamed->lockPlayer();
_streamed->player().updates( _streamed->player().updates(
) | rpl::start_with_next_error([=](Update &&update) { ) | rpl::start_with_next_error([=](Update &&update) {
handleStreamingUpdate(std::move(update)); handleStreamingUpdate(std::move(update));