Use Media::Streaming in EditCaptionBox.

This commit is contained in:
John Preston 2020-05-20 15:04:56 +04:00
parent 3d431a27cb
commit 581a21dbd9
15 changed files with 220 additions and 149 deletions

View File

@ -22,13 +22,18 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_user.h"
#include "data/data_session.h"
#include "data/data_streaming.h"
#include "data/data_file_origin.h"
#include "data/data_document_media.h"
#include "history/history.h"
#include "history/history_item.h"
#include "lang/lang_keys.h"
#include "layout.h"
#include "media/clip/media_clip_reader.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_player.h"
#include "media/streaming/media_streaming_document.h"
#include "media/streaming/media_streaming_loader_local.h"
#include "storage/localimageloader.h"
#include "storage/storage_media_prepare.h"
#include "ui/image/image.h"
#include "ui/widgets/input_fields.h"
@ -47,6 +52,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtCore/QMimeData>
namespace {
using namespace ::Media::Streaming;
} // namespace
EditCaptionBox::EditCaptionBox(
QWidget*,
not_null<Window::SessionController*> controller,
@ -70,14 +81,13 @@ EditCaptionBox::EditCaptionBox(
} else if (const auto document = media->document()) {
_documentMedia = document->createMediaView();
_documentMedia->thumbnailWanted(_msgId);
// #TODO optimize + streamed GIF view
image = _documentMedia->thumbnail();
dimensions = image
? image->size()
: document->dimensions;
if (document->isAnimation()) {
_gifw = document->dimensions.width();
_gifh = document->dimensions.height();
_gifw = style::ConvertScale(document->dimensions.width());
_gifh = style::ConvertScale(document->dimensions.height());
_animated = true;
} else if (document->isVideoFile()) {
_animated = true;
@ -161,7 +171,7 @@ EditCaptionBox::EditCaptionBox(
maxW,
maxH);
};
prepareGifPreview();
prepareStreamedPreview();
} else {
maxW = dimensions.width();
maxH = dimensions.height();
@ -243,22 +253,25 @@ EditCaptionBox::EditCaptionBox(
_thumbnailImageLoaded = _thumbnailImage
? _thumbnailImage->loaded()
: _documentMedia
? !_documentMedia->owner()->hasThumbnail()
: true;
subscribe(_controller->session().downloaderTaskFinished(), [=] {
if (!_thumbnailImageLoaded
&& _thumbnailImage
&& _thumbnailImage->loaded()) {
_thumbnailImageLoaded = true;
_refreshThumbnail();
update();
}
if (_documentMedia && _documentMedia->owner()->isAnimation()) {
if (_documentMedia->loaded() && !_gifPreview) {
prepareGifPreview();
if (!_thumbnailImageLoaded) {
subscribe(_controller->session().downloaderTaskFinished(), [=] {
if (_thumbnailImageLoaded) {
return;
} else if (!_thumbnailImage
&& _documentMedia
&& _documentMedia->owner()->hasThumbnail()) {
_thumbnailImage = _documentMedia->thumbnail();
}
}
});
if (_thumbnailImage && _thumbnailImage->loaded()) {
_thumbnailImageLoaded = true;
_refreshThumbnail();
update();
}
});
}
_field.create(
this,
st::confirmCaptionArea,
@ -293,6 +306,8 @@ EditCaptionBox::EditCaptionBox(
}, _wayWrap->lifetime());
}
EditCaptionBox::~EditCaptionBox() = default;
void EditCaptionBox::emojiFilterForGeometry(not_null<QEvent*> event) {
const auto type = event->type();
if (type == QEvent::Move || type == QEvent::Resize) {
@ -311,78 +326,86 @@ void EditCaptionBox::updateEmojiPanelGeometry() {
local.x() + _emojiToggle->width() * 3);
}
void EditCaptionBox::prepareGifPreview() {
void EditCaptionBox::prepareStreamedPreview() {
const auto isListEmpty = _preparedList.files.empty();
if (_gifPreview) {
if (_streamed) {
return;
} else if (!_documentMedia && isListEmpty) {
return;
}
const auto callback = [=](Media::Clip::Notification notification) {
clipCallback(notification);
};
if (_documentMedia && _documentMedia->owner()->isAnimation()) {
if (_documentMedia->loaded()) {
_gifPreview = Media::Clip::MakeReader(
_documentMedia.get(),
_msgId,
callback);
}
const auto document = _documentMedia
? _documentMedia->owner().get()
: nullptr;
if (document && document->isAnimation()) {
setupStreamedPreview(
document->owner().streaming().sharedDocument(
document,
_msgId));
} else if (!isListEmpty) {
const auto file = &_preparedList.files.front();
if (file->path.isEmpty()) {
_gifPreview = Media::Clip::MakeReader(
file->content,
callback);
} else {
_gifPreview = Media::Clip::MakeReader(
file->path,
callback);
}
}
if (_gifPreview) {
_gifPreview->setAutoplay();
auto loader = file->path.isEmpty()
? MakeBytesLoader(file->content)
: MakeFileLoader(file->path);
setupStreamedPreview(std::make_shared<Document>(std::move(loader)));
}
}
void EditCaptionBox::clipCallback(Media::Clip::Notification notification) {
using namespace Media::Clip;
switch (notification) {
case NotificationReinit: {
if (_gifPreview && _gifPreview->state() == State::Error) {
_gifPreview.setBad();
}
void EditCaptionBox::setupStreamedPreview(std::shared_ptr<Document> shared) {
if (!shared) {
return;
}
_streamed = std::make_unique<Instance>(
std::move(shared),
[=] { update(); });
_streamed->lockPlayer();
_streamed->player().updates(
) | rpl::start_with_next_error([=](Update &&update) {
handleStreamingUpdate(std::move(update));
}, [=](Error &&error) {
handleStreamingError(std::move(error));
}, _streamed->lifetime());
if (_gifPreview && _gifPreview->ready() && !_gifPreview->started()) {
const auto calculateGifDimensions = [&]() {
const auto scaled = QSize(
_gifPreview->width(),
_gifPreview->height()).scaled(
st::sendMediaPreviewSize * cIntRetinaFactor(),
st::confirmMaxHeight * cIntRetinaFactor(),
Qt::KeepAspectRatio);
_thumbw = _gifw = scaled.width();
_thumbh = _gifh = scaled.height();
_thumbx = _gifx = (st::boxWideWidth - _gifw) / 2;
updateBoxSize();
};
// If gif file is not mp4,
// Its dimension values will be known only after reading.
if (_gifw <= 0 || _gifh <= 0) {
calculateGifDimensions();
}
const auto s = QSize(_gifw, _gifh);
_gifPreview->start(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None);
}
if (_streamed->ready()) {
streamingReady(base::duplicate(_streamed->info()));
}
checkStreamedIsStarted();
}
update();
} break;
void EditCaptionBox::handleStreamingUpdate(Update &&update) {
update.data.match([&](Information &update) {
streamingReady(std::move(update));
}, [&](const PreloadedVideo &update) {
}, [&](const UpdateVideo &update) {
this->update();
}, [&](const PreloadedAudio &update) {
}, [&](const UpdateAudio &update) {
}, [&](const WaitingForData &update) {
}, [&](MutedByOther) {
}, [&](Finished) {
});
}
case NotificationRepaint: {
if (_gifPreview && !_gifPreview->currentDisplayed()) {
update();
}
} break;
void EditCaptionBox::handleStreamingError(Error &&error) {
}
void EditCaptionBox::streamingReady(Information &&info) {
const auto calculateGifDimensions = [&]() {
const auto scaled = QSize(
info.video.size.width(),
info.video.size.height()
).scaled(
st::sendMediaPreviewSize * cIntRetinaFactor(),
st::confirmMaxHeight * cIntRetinaFactor(),
Qt::KeepAspectRatio);
_thumbw = _gifw = scaled.width();
_thumbh = _gifh = scaled.height();
_thumbx = _gifx = (st::boxWideWidth - _gifw) / 2;
updateBoxSize();
};
// If gif file is not mp4,
// Its dimension values will be known only after reading.
if (_gifw <= 0 || _gifh <= 0) {
calculateGifDimensions();
}
}
@ -400,7 +423,7 @@ void EditCaptionBox::updateEditPreview() {
_animated = false;
_photo = false;
_doc = false;
_gifPreview = nullptr;
_streamed = nullptr;
_thumbw = _thumbh = _thumbx = 0;
_gifw = _gifh = _gifx = 0;
@ -479,7 +502,7 @@ void EditCaptionBox::updateEditPreview() {
_gifw = _thumbw;
_gifh = _thumbh;
_gifx = _thumbx;
prepareGifPreview();
prepareStreamedPreview();
}
}
updateEditMediaButton();
@ -694,6 +717,31 @@ int EditCaptionBox::errorTopSkip() const {
return (st::defaultBox.buttonPadding.top() / 2);
}
void EditCaptionBox::checkStreamedIsStarted() {
if (!_streamed) {
return;
}
if (_streamed->paused()) {
_streamed->resume();
}
if (!_streamed->active() && !_streamed->failed()) {
startStreamedPlayer();
}
}
void EditCaptionBox::startStreamedPlayer() {
auto options = ::Media::Streaming::PlaybackOptions();
options.audioId = _documentMedia
? AudioMsgId(_documentMedia->owner(), _msgId)
: AudioMsgId();
options.waitForMarkAsShown = true;
//if (!_streamed->withSound) {
options.mode = ::Media::Streaming::Mode::Video;
options.loop = true;
//}
_streamed->play(options);
}
void EditCaptionBox::paintEvent(QPaintEvent *e) {
BoxContent::paintEvent(e);
@ -707,16 +755,27 @@ void EditCaptionBox::paintEvent(QPaintEvent *e) {
if (_thumbx + _thumbw < width() - st::boxPhotoPadding.right()) {
p.fillRect(_thumbx + _thumbw, st::boxPhotoPadding.top(), width() - st::boxPhotoPadding.right() - _thumbx - _thumbw, th, st::confirmBg);
}
if (_gifPreview && _gifPreview->started()) {
checkStreamedIsStarted();
if (_streamed
&& _streamed->player().ready()
&& !_streamed->player().videoSize().isEmpty()) {
const auto s = QSize(_gifw, _gifh);
const auto paused = _controller->isGifPausedAtLeastFor(Window::GifPauseReason::Layer);
const auto frame = _gifPreview->current(s.width(), s.height(), s.width(), s.height(), ImageRoundRadius::None, RectPart::None, paused ? 0 : crl::now());
p.drawPixmap(_gifx, st::boxPhotoPadding.top(), frame);
auto request = ::Media::Streaming::FrameRequest();
request.outer = s * cIntRetinaFactor();
request.resize = s * cIntRetinaFactor();
p.drawImage(
QRect(_gifx, st::boxPhotoPadding.top(), _gifw, _gifh),
_streamed->frame(request));
if (!paused) {
_streamed->markFrameShown();
}
} else {
const auto offset = _gifh ? ((_gifh - _thumbh) / 2) : 0;
p.drawPixmap(_thumbx, st::boxPhotoPadding.top() + offset, _thumb);
}
if (_animated && !_gifPreview) {
if (_animated && !_streamed) {
QRect inner(_thumbx + (_thumbw - st::msgFileSize) / 2, st::boxPhotoPadding.top() + (th - st::msgFileSize) / 2, st::msgFileSize, st::msgFileSize);
p.setPen(Qt::NoPen);
p.setBrush(st::msgDateImgBg);

View File

@ -10,7 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/abstract_box.h"
#include "storage/storage_media_prepare.h"
#include "ui/wrap/slide_wrap.h"
#include "media/clip/media_clip_reader.h"
#include "mtproto/mtproto_rpc_sender.h"
namespace ChatHelpers {
@ -37,6 +36,16 @@ namespace Window {
class SessionController;
} // namespace Window
namespace Media {
namespace Streaming {
class Instance;
class Document;
struct Update;
enum class Error;
struct Information;
} // namespace Streaming
} // namespace Media
class EditCaptionBox
: public Ui::BoxContent
, public RPCSender
@ -46,6 +55,7 @@ public:
QWidget*,
not_null<Window::SessionController*> controller,
not_null<HistoryItem*> item);
~EditCaptionBox();
protected:
void prepare() override;
@ -57,8 +67,14 @@ protected:
private:
void updateBoxSize();
void prepareGifPreview();
void clipCallback(Media::Clip::Notification notification);
void prepareStreamedPreview();
void checkStreamedIsStarted();
void setupStreamedPreview(
std::shared_ptr<::Media::Streaming::Document> shared);
void handleStreamingUpdate(::Media::Streaming::Update &&update);
void handleStreamingError(::Media::Streaming::Error &&error);
void streamingReady(::Media::Streaming::Information &&info);
void startStreamedPlayer();
void setupEmojiPanel();
void updateEmojiPanelGeometry();
@ -96,7 +112,7 @@ private:
bool _doc = false;
QPixmap _thumb;
Media::Clip::ReaderPointer _gifPreview;
std::unique_ptr<::Media::Streaming::Instance> _streamed;
object_ptr<Ui::InputField> _field = { nullptr };
object_ptr<Ui::EmojiButton> _emojiToggle = { nullptr };

View File

@ -888,7 +888,6 @@ void SingleMediaPreview::prepareAnimatedPreview(
_gifPreview = Media::Clip::MakeReader(
animatedPreviewPath,
std::move(callback));
if (_gifPreview) _gifPreview->setAutoplay();
}
}

View File

@ -66,8 +66,8 @@ std::shared_ptr<Streaming::Reader> Streaming::sharedReader(
return nullptr;
}
auto result = std::make_shared<Reader>(
&_owner->cacheBigFile(),
std::move(loader));
std::move(loader),
&_owner->cacheBigFile());
if (!PruneDestroyedAndSet(_readers, document, result)) {
_readers.emplace_or_assign(document, result);
}

View File

@ -145,7 +145,6 @@ void Gif::paint(Painter &p, const QRect &clip, const PaintContext *context) cons
that->_gif = Media::Clip::MakeReader(_dataMedia.get(), FullMsgId(), [that](Media::Clip::Notification notification) {
that->clipCallback(notification);
});
if (_gif) _gif->setAutoplay();
}
const auto animating = (_gif && _gif->started());
@ -1360,7 +1359,6 @@ void Game::paint(Painter &p, const QRect &clip, const PaintContext *context) con
that->_gif = Media::Clip::MakeReader(_dataMedia.get(), FullMsgId(), [that](Media::Clip::Notification notification) {
that->clipCallback(notification);
});
if (_gif) _gif->setAutoplay();
}
bool animating = (_gif && _gif->started());

View File

@ -67,13 +67,6 @@ public:
// Reader can be already deleted.
static void callback(Reader *reader, qint32 threadIndex, qint32 notification);
void setAutoplay() {
_autoplay = true;
}
bool autoplay() const {
return _autoplay;
}
AudioMsgId audioMsgId() const {
return _audioMsgId;
}
@ -172,8 +165,6 @@ private:
QAtomicInt _videoPauseRequest = 0;
int32 _threadIndex;
bool _autoplay = false;
friend class Manager;
ReaderPrivate *_private = nullptr;

View File

@ -8,6 +8,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/streaming/media_streaming_document.h"
#include "media/streaming/media_streaming_instance.h"
#include "media/streaming/media_streaming_loader.h"
#include "media/streaming/media_streaming_reader.h"
#include "data/data_session.h"
#include "data/data_document.h"
#include "data/data_document_media.h"
@ -32,22 +34,29 @@ constexpr auto kGoodThumbnailQuality = 87;
Document::Document(
not_null<DocumentData*> document,
std::shared_ptr<Reader> reader)
: _player(&document->owner(), reader)
: Document(std::move(reader), document) {
_player.fullInCache(
) | rpl::start_with_next([=](bool fullInCache) {
_document->setLoadedInMediaCache(fullInCache);
}, _player.lifetime());
}
Document::Document(std::unique_ptr<Loader> loader)
: Document(std::make_shared<Reader>(std::move(loader)), nullptr) {
}
Document::Document(std::shared_ptr<Reader> reader, DocumentData *document)
: _document(document)
, _player(std::move(reader))
, _radial(
[=] { waitingCallback(); },
st::defaultInfiniteRadialAnimation)
, _document(document) {
[=] { waitingCallback(); },
st::defaultInfiniteRadialAnimation) {
_player.updates(
) | rpl::start_with_next_error([=](Update &&update) {
handleUpdate(std::move(update));
}, [=](Streaming::Error &&error) {
handleError(std::move(error));
}, _player.lifetime());
_player.fullInCache(
) | rpl::start_with_next([=](bool fullInCache) {
_document->setLoadedInMediaCache(fullInCache);
}, _player.lifetime());
}
Player &Document::player() {
@ -62,9 +71,9 @@ const Information &Document::info() const {
return _info;
}
not_null<DocumentData*> Document::data() const {
return _document;
}
//not_null<DocumentData*> Document::data() const {
// return _document;
//}
void Document::play(const PlaybackOptions &options) {
_player.play(options);
@ -145,10 +154,12 @@ void Document::handleUpdate(Update &&update) {
}
void Document::handleError(Error &&error) {
if (error == Error::NotStreamable) {
_document->setNotSupportsStreaming();
} else if (error == Error::OpenFailed) {
_document->setInappPlaybackFailed();
if (_document) {
if (error == Error::NotStreamable) {
_document->setNotSupportsStreaming();
} else if (error == Error::OpenFailed) {
_document->setInappPlaybackFailed();
}
}
waitingChange(false);
}
@ -194,7 +205,9 @@ void Document::waitingChange(bool waiting) {
}
void Document::validateGoodThumbnail() {
if (_info.video.cover.isNull() || _document->goodThumbnailChecked()) {
if (_info.video.cover.isNull()
|| !_document
|| _document->goodThumbnailChecked()) {
return;
}
const auto document = _document;

View File

@ -18,12 +18,14 @@ namespace Media {
namespace Streaming {
class Instance;
class Loader;
class Document {
public:
Document(
not_null<DocumentData*> document,
std::shared_ptr<Reader> reader);
explicit Document(std::unique_ptr<Loader> loader);
void play(const PlaybackOptions &options);
void saveFrameToCover();
@ -31,13 +33,15 @@ public:
[[nodiscard]] Player &player();
[[nodiscard]] const Player &player() const;
[[nodiscard]] const Information &info() const;
[[nodiscard]] not_null<DocumentData*> data() const;
// [[nodiscard]] not_null<DocumentData*> data() const;
[[nodiscard]] bool waitingShown() const;
[[nodiscard]] float64 waitingOpacity() const;
[[nodiscard]] Ui::RadialState waitingState() const;
private:
Document(std::shared_ptr<Reader> reader, DocumentData *document);
friend class Instance;
void registerInstance(not_null<Instance*> instance);
@ -54,6 +58,7 @@ private:
void validateGoodThumbnail();
DocumentData *_document = nullptr;
Player _player;
Information _info;
@ -63,8 +68,6 @@ private:
base::Timer _timer;
base::flat_set<not_null<Instance*>> _instances;
not_null<DocumentData*> _document;
};

View File

@ -390,9 +390,7 @@ void File::Context::stopStreamingAsync() {
_reader->stopStreamingAsync();
}
File::File(
not_null<Data::Session*> owner,
std::shared_ptr<Reader> reader)
File::File(std::shared_ptr<Reader> reader)
: _reader(std::move(reader)) {
}

View File

@ -16,10 +16,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <thread>
namespace Data {
class Session;
} // namespace Data
namespace Media {
namespace Streaming {
@ -27,7 +23,7 @@ class FileDelegate;
class File final {
public:
File(not_null<Data::Session*> owner, std::shared_ptr<Reader> reader);
explicit File(std::shared_ptr<Reader> reader);
File(const File &other) = delete;
File &operator=(const File &other) = delete;

View File

@ -79,10 +79,8 @@ void SaveValidStartInformation(Information &to, Information &&from) {
} // namespace
Player::Player(
not_null<Data::Session*> owner,
std::shared_ptr<Reader> reader)
: _file(std::make_unique<File>(owner, std::move(reader)))
Player::Player(std::shared_ptr<Reader> reader)
: _file(std::make_unique<File>(std::move(reader)))
, _remoteLoader(_file->isRemoteLoader())
, _renderFrameTimer([=] { checkNextFrameRender(); }) {
}

View File

@ -12,10 +12,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/weak_ptr.h"
#include "base/timer.h"
namespace Data {
class Session;
} // namespace Data
namespace Media {
namespace Player {
struct TrackState;
@ -34,7 +30,7 @@ class Instance;
class Player final : private FileDelegate {
public:
// Public interfaces is used from the main thread.
Player(not_null<Data::Session*> owner, std::shared_ptr<Reader> reader);
explicit Player(std::shared_ptr<Reader> reader);
// Because we remember 'this' in calls to crl::on_main.
Player(const Player &other) = delete;

View File

@ -845,11 +845,11 @@ Reader::SerializedSlice Reader::Slices::unloadToCache() {
}
Reader::Reader(
not_null<Storage::Cache::Database*> cache,
std::unique_ptr<Loader> loader)
: _cache(cache)
, _loader(std::move(loader))
, _cacheHelper(InitCacheHelper(_loader->baseCacheKey()))
std::unique_ptr<Loader> loader,
Storage::Cache::Database *cache)
: _loader(std::move(loader))
, _cache(cache)
, _cacheHelper(cache ? InitCacheHelper(_loader->baseCacheKey()) : nullptr)
, _slices(_loader->size(), _cacheHelper != nullptr) {
_loader->parts(
) | rpl::start_with_next([=](LoadedPart &&part) {
@ -1122,6 +1122,7 @@ std::shared_ptr<Reader::CacheHelper> Reader::InitCacheHelper(
// 0 is for headerData, slice index = sliceNumber - 1.
void Reader::readFromCache(int sliceNumber) {
Expects(_cache != nullptr);
Expects(_cacheHelper != nullptr);
Expects(!sliceNumber || !_slices.headerModeUnknown());
@ -1182,6 +1183,7 @@ bool Reader::readFromCacheForDownloader(int sliceNumber) {
}
void Reader::putToCache(SerializedSlice &&slice) {
Expects(_cache != nullptr);
Expects(_cacheHelper != nullptr);
Expects(slice.number >= 0);
@ -1375,6 +1377,7 @@ void Reader::finalizeCache() {
if (!_cacheHelper) {
return;
}
Assert(_cache != nullptr);
if (_cacheHelper->waiting != nullptr) {
QMutexLocker lock(&_cacheHelper->mutex);
_cacheHelper->waiting.store(nullptr, std::memory_order_release);

View File

@ -33,9 +33,9 @@ enum class Error;
class Reader final : public base::has_weak_ptr {
public:
// Main thread.
Reader(
not_null<Storage::Cache::Database*> cache,
std::unique_ptr<Loader> loader);
explicit Reader(
std::unique_ptr<Loader> loader,
Storage::Cache::Database *cache = nullptr);
void setLoaderPriority(int priority);
@ -228,8 +228,10 @@ private:
static std::shared_ptr<CacheHelper> InitCacheHelper(
std::optional<Storage::Cache::Key> baseKey);
const not_null<Storage::Cache::Database*> _cache;
const std::unique_ptr<Loader> _loader;
Storage::Cache::Database * const _cache = nullptr;
// shared_ptr is used to be able to have weak_ptr.
const std::shared_ptr<CacheHelper> _cacheHelper;
base::thread_safe_queue<LoadedPart, std::vector> _loadedParts;

View File

@ -270,7 +270,6 @@ QPixmap MediaPreviewWidget::currentImage() const {
that->_gif = Media::Clip::MakeReader(_documentMedia.get(), FullMsgId(), [=](Media::Clip::Notification notification) {
that->clipCallback(notification);
});
if (_gif) _gif->setAutoplay();
}
}
if (_gif && _gif->started()) {