mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Add recorded round video preview.
This commit is contained in:
parent
9514b6eecd
commit
6cfa053328
8 changed files with 320 additions and 49 deletions
|
@ -1162,6 +1162,8 @@ PRIVATE
|
|||
media/streaming/media_streaming_player.h
|
||||
media/streaming/media_streaming_reader.cpp
|
||||
media/streaming/media_streaming_reader.h
|
||||
media/streaming/media_streaming_round_preview.cpp
|
||||
media/streaming/media_streaming_round_preview.h
|
||||
media/streaming/media_streaming_utility.cpp
|
||||
media/streaming/media_streaming_utility.h
|
||||
media/streaming/media_streaming_video_track.cpp
|
||||
|
|
|
@ -28,6 +28,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/audio/media_audio_capture.h"
|
||||
#include "media/player/media_player_button.h"
|
||||
#include "media/player/media_player_instance.h"
|
||||
#include "media/streaming/media_streaming_instance.h"
|
||||
#include "media/streaming/media_streaming_round_preview.h"
|
||||
#include "ui/controls/round_video_recorder.h"
|
||||
#include "ui/controls/send_button.h"
|
||||
#include "ui/effects/animation_value.h"
|
||||
|
@ -35,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/effects/ripple_animation.h"
|
||||
#include "ui/text/format_values.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/widgets/tooltip.h"
|
||||
#include "ui/rect.h"
|
||||
|
@ -71,6 +74,61 @@ enum class FilterType {
|
|||
Cancel,
|
||||
};
|
||||
|
||||
class SoundedPreview final : public Ui::DynamicImage {
|
||||
public:
|
||||
SoundedPreview(
|
||||
not_null<DocumentData*> document,
|
||||
rpl::producer<> repaints);
|
||||
std::shared_ptr<DynamicImage> clone() override;
|
||||
QImage image(int size) override;
|
||||
void subscribeToUpdates(Fn<void()> callback) override;
|
||||
|
||||
private:
|
||||
const not_null<DocumentData*> _document;
|
||||
QImage _roundingMask;
|
||||
Fn<void()> _repaint;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
||||
SoundedPreview::SoundedPreview(
|
||||
not_null<DocumentData*> document,
|
||||
rpl::producer<> repaints)
|
||||
: _document(document) {
|
||||
std::move(repaints) | rpl::start_with_next([=] {
|
||||
if (const auto onstack = _repaint) {
|
||||
onstack();
|
||||
}
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
std::shared_ptr<Ui::DynamicImage> SoundedPreview::clone() {
|
||||
Unexpected("ListenWrap::videoPreview::clone.");
|
||||
}
|
||||
|
||||
QImage SoundedPreview::image(int size) {
|
||||
const auto player = ::Media::Player::instance();
|
||||
const auto streamed = player->roundVideoPreview(_document);
|
||||
if (!streamed) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto full = QSize(size, size) * style::DevicePixelRatio();
|
||||
if (_roundingMask.size() != full) {
|
||||
_roundingMask = Images::EllipseMask(full);
|
||||
}
|
||||
const auto frame = streamed->frameWithInfo({
|
||||
.resize = full,
|
||||
.outer = full,
|
||||
.mask = _roundingMask,
|
||||
});
|
||||
return frame.image;
|
||||
}
|
||||
|
||||
void SoundedPreview::subscribeToUpdates(Fn<void()> callback) {
|
||||
_repaint = std::move(callback);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto InactiveColor(const QColor &c) {
|
||||
return QColor(c.red(), c.green(), c.blue(), kInactiveWaveformBarAlpha);
|
||||
}
|
||||
|
@ -470,24 +528,26 @@ public:
|
|||
const style::font &font);
|
||||
|
||||
void requestPaintProgress(float64 progress);
|
||||
rpl::producer<> stopRequests() const;
|
||||
[[nodiscard]] rpl::producer<> stopRequests() const;
|
||||
|
||||
void playPause();
|
||||
[[nodiscard]] std::shared_ptr<Ui::DynamicImage> videoPreview();
|
||||
|
||||
rpl::lifetime &lifetime();
|
||||
[[nodiscard]] rpl::lifetime &lifetime();
|
||||
|
||||
private:
|
||||
void init();
|
||||
void initPlayButton();
|
||||
void initPlayProgress();
|
||||
|
||||
bool isInPlayer(const ::Media::Player::TrackState &state) const;
|
||||
bool isInPlayer() const;
|
||||
[[nodiscard]] bool isInPlayer(
|
||||
const ::Media::Player::TrackState &state) const;
|
||||
[[nodiscard]] bool isInPlayer() const;
|
||||
|
||||
int computeTopMargin(int height) const;
|
||||
QRect computeWaveformRect(const QRect ¢erRect) const;
|
||||
[[nodiscard]] int computeTopMargin(int height) const;
|
||||
[[nodiscard]] QRect computeWaveformRect(const QRect ¢erRect) const;
|
||||
|
||||
not_null<Ui::RpWidget*> _parent;
|
||||
const not_null<Ui::RpWidget*> _parent;
|
||||
|
||||
const style::RecordBar &_st;
|
||||
const not_null<Main::Session*> _session;
|
||||
|
@ -515,6 +575,7 @@ private:
|
|||
anim::value _playProgress;
|
||||
|
||||
rpl::variable<float64> _showProgress = 0.;
|
||||
rpl::event_stream<> _videoRepaints;
|
||||
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
|
@ -716,6 +777,9 @@ void ListenWrap::initPlayButton() {
|
|||
) | rpl::start_with_next([=](const State &state) {
|
||||
if (isInPlayer(state)) {
|
||||
*showPause = ShowPauseIcon(state.state);
|
||||
if (!_data->minithumbs.isNull()) {
|
||||
_videoRepaints.fire({});
|
||||
}
|
||||
} else if (showPause->current()) {
|
||||
*showPause = false;
|
||||
}
|
||||
|
@ -865,6 +929,12 @@ rpl::producer<> ListenWrap::stopRequests() const {
|
|||
return _delete->clicks() | rpl::to_empty;
|
||||
}
|
||||
|
||||
std::shared_ptr<Ui::DynamicImage> ListenWrap::videoPreview() {
|
||||
return std::make_shared<SoundedPreview>(
|
||||
_document,
|
||||
_videoRepaints.events());
|
||||
}
|
||||
|
||||
rpl::lifetime &ListenWrap::lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
@ -1634,11 +1704,6 @@ void VoiceRecordBar::activeAnimate(bool active) {
|
|||
}
|
||||
|
||||
void VoiceRecordBar::visibilityAnimate(bool show, Fn<void()> &&callback) {
|
||||
//if (_videoRecorder) {
|
||||
// _videoHiding.push_back(base::take(_videoRecorder));
|
||||
// _videoHiding.back()->hide();
|
||||
//}
|
||||
AssertIsDebug();
|
||||
if (_send->type() == Ui::SendButton::Type::Round) {
|
||||
_level->setType(VoiceRecordButton::Type::Round);
|
||||
} else {
|
||||
|
@ -1871,24 +1936,29 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
|
|||
_cancelRequests.fire({});
|
||||
}));
|
||||
} else if (type == StopType::Listen) {
|
||||
if (_videoRecorder) {
|
||||
const auto weak = Ui::MakeWeak(this);
|
||||
_videoRecorder->pause([=](Ui::RoundVideoResult data) {
|
||||
crl::on_main([=, data = std::move(data)]() mutable {
|
||||
if (weak) {
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
if (const auto recorder = _videoRecorder.get()) {
|
||||
const auto weak = base::make_weak(recorder);
|
||||
recorder->pause([=](Ui::RoundVideoResult data) {
|
||||
crl::on_main(weak, [=, data = std::move(data)]() mutable {
|
||||
window()->raise();
|
||||
window()->activateWindow();
|
||||
|
||||
_paused = true;
|
||||
_data = std::move(data);
|
||||
_listen = std::make_unique<ListenWrap>(
|
||||
this,
|
||||
_st,
|
||||
&_show->session(),
|
||||
&_data,
|
||||
_cancelFont);
|
||||
_listenChanges.fire({});
|
||||
}
|
||||
_paused = true;
|
||||
_data = std::move(data);
|
||||
_listen = std::make_unique<ListenWrap>(
|
||||
this,
|
||||
_st,
|
||||
&_show->session(),
|
||||
&_data,
|
||||
_cancelFont);
|
||||
_listenChanges.fire({});
|
||||
|
||||
using SilentPreview = ::Media::Streaming::RoundPreview;
|
||||
recorder->showPreview(
|
||||
std::make_shared<SilentPreview>(
|
||||
_data.content,
|
||||
recorder->previewSize()),
|
||||
_listen->videoPreview());
|
||||
});
|
||||
});
|
||||
instance()->pause(true);
|
||||
|
@ -1933,11 +2003,11 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
|
|||
: 0),
|
||||
};
|
||||
_sendVoiceRequests.fire({
|
||||
data.content,
|
||||
VoiceWaveform{},
|
||||
data.duration,
|
||||
options,
|
||||
true,
|
||||
.bytes = data.content,
|
||||
//.waveform = {},
|
||||
.duration = data.duration,
|
||||
.options = options,
|
||||
.video = true,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@ -1963,10 +2033,10 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
|
|||
: 0),
|
||||
};
|
||||
_sendVoiceRequests.fire({
|
||||
_data.content,
|
||||
_data.waveform,
|
||||
_data.duration,
|
||||
options,
|
||||
.bytes = _data.content,
|
||||
.waveform = _data.waveform,
|
||||
.duration = _data.duration,
|
||||
.options = options,
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
@ -2030,10 +2100,11 @@ void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) {
|
|||
options.ttlSeconds = std::numeric_limits<int>::max();
|
||||
}
|
||||
_sendVoiceRequests.fire({
|
||||
_data.content,
|
||||
_data.waveform,
|
||||
_data.duration,
|
||||
options,
|
||||
.bytes = _data.content,
|
||||
.waveform = _data.waveform,
|
||||
.duration = _data.duration,
|
||||
.options = options,
|
||||
.video = !_data.minithumbs.isNull(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1200,6 +1200,21 @@ Streaming::Instance *Instance::roundVideoStreamed(HistoryItem *item) const {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Streaming::Instance *Instance::roundVideoPreview(
|
||||
not_null<DocumentData*> document) const {
|
||||
if (const auto data = getData(AudioMsgId::Type::Voice)) {
|
||||
if (const auto streamed = data->streamed.get()) {
|
||||
if (streamed->id.audio() == document) {
|
||||
const auto player = &streamed->instance.player();
|
||||
if (player->ready() && !player->videoSize().isEmpty()) {
|
||||
return &streamed->instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
View::PlaybackProgress *Instance::roundVideoPlayback(
|
||||
HistoryItem *item) const {
|
||||
return roundVideoStreamed(item)
|
||||
|
|
|
@ -109,6 +109,9 @@ public:
|
|||
[[nodiscard]] View::PlaybackProgress *roundVideoPlayback(
|
||||
HistoryItem *item) const;
|
||||
|
||||
[[nodiscard]] Streaming::Instance *roundVideoPreview(
|
||||
not_null<DocumentData*> document) const;
|
||||
|
||||
[[nodiscard]] AudioMsgId current(AudioMsgId::Type type) const {
|
||||
if (const auto data = getData(type)) {
|
||||
return data->current;
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "media/streaming/media_streaming_round_preview.h"
|
||||
|
||||
namespace Media::Streaming {
|
||||
|
||||
RoundPreview::RoundPreview(const QByteArray &bytes, int size)
|
||||
: _bytes(bytes)
|
||||
, _reader(
|
||||
Clip::MakeReader(_bytes, [=](Clip::Notification update) {
|
||||
clipCallback(update);
|
||||
}))
|
||||
, _size(size) {
|
||||
}
|
||||
|
||||
std::shared_ptr<Ui::DynamicImage> RoundPreview::clone() {
|
||||
Unexpected("RoundPreview::clone.");
|
||||
}
|
||||
|
||||
QImage RoundPreview::image(int size) {
|
||||
if (!_reader || !_reader->started()) {
|
||||
return QImage();
|
||||
}
|
||||
return _reader->current({
|
||||
.frame = QSize(_size, _size),
|
||||
.factor = style::DevicePixelRatio(),
|
||||
.radius = ImageRoundRadius::Ellipse,
|
||||
}, crl::now());
|
||||
}
|
||||
|
||||
void RoundPreview::subscribeToUpdates(Fn<void()> callback) {
|
||||
_repaint = std::move(callback);
|
||||
}
|
||||
|
||||
void RoundPreview::clipCallback(Clip::Notification notification) {
|
||||
switch (notification) {
|
||||
case Clip::Notification::Reinit: {
|
||||
if (_reader->state() == ::Media::Clip::State::Error) {
|
||||
_reader.setBad();
|
||||
} else if (_reader->ready() && !_reader->started()) {
|
||||
_reader->start({
|
||||
.frame = QSize(_size, _size),
|
||||
.factor = style::DevicePixelRatio(),
|
||||
.radius = ImageRoundRadius::Ellipse,
|
||||
});
|
||||
}
|
||||
} break;
|
||||
|
||||
case Clip::Notification::Repaint: break;
|
||||
}
|
||||
|
||||
if (const auto onstack = _repaint) {
|
||||
onstack();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Media::Streaming
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "ui/dynamic_image.h"
|
||||
|
||||
#include "media/clip/media_clip_reader.h"
|
||||
|
||||
namespace Media::Streaming {
|
||||
|
||||
class RoundPreview final : public Ui::DynamicImage {
|
||||
public:
|
||||
RoundPreview(const QByteArray &bytes, int size);
|
||||
|
||||
std::shared_ptr<DynamicImage> clone() override;
|
||||
|
||||
QImage image(int size) override;
|
||||
void subscribeToUpdates(Fn<void()> callback) override;
|
||||
|
||||
private:
|
||||
void clipCallback(Clip::Notification notification);
|
||||
|
||||
const QByteArray _bytes;
|
||||
Clip::ReaderPointer _reader;
|
||||
Fn<void()> _repaint;
|
||||
int _size = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace Media::Streaming
|
|
@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "media/audio/media_audio_capture.h"
|
||||
#include "ui/image/image_prepare.h"
|
||||
#include "ui/arc_angles.h"
|
||||
#include "ui/dynamic_image.h"
|
||||
#include "ui/painter.h"
|
||||
#include "ui/rp_widget.h"
|
||||
#include "webrtc/webrtc_video_track.h"
|
||||
|
@ -33,6 +34,7 @@ constexpr auto kInitTimeout = 5 * crl::time(1000);
|
|||
constexpr auto kBlurredSize = 64;
|
||||
constexpr auto kMinithumbsPerSecond = 5;
|
||||
constexpr auto kMinithumbsInRow = 16;
|
||||
constexpr auto kFadeDuration = crl::time(150);
|
||||
|
||||
using namespace FFmpeg;
|
||||
|
||||
|
@ -1044,6 +1046,10 @@ Fn<void(Media::Capture::Chunk)> RoundVideoRecorder::audioChunkProcessor() {
|
|||
};
|
||||
}
|
||||
|
||||
int RoundVideoRecorder::previewSize() const {
|
||||
return _side;
|
||||
}
|
||||
|
||||
auto RoundVideoRecorder::updated() -> rpl::producer<Update, Error> {
|
||||
return _private.producer_on_main([](const Private &that) {
|
||||
return that.updated();
|
||||
|
@ -1078,7 +1084,7 @@ void RoundVideoRecorder::progressTo(float64 progress) {
|
|||
[=] { _preview->update(); },
|
||||
0.,
|
||||
1.,
|
||||
crl::time(200));
|
||||
kFadeDuration);
|
||||
}
|
||||
_progress = progress;
|
||||
_preview->update();
|
||||
|
@ -1225,6 +1231,30 @@ void RoundVideoRecorder::setup() {
|
|||
(full / 4) - length,
|
||||
length);
|
||||
}
|
||||
|
||||
const auto preview = _fadePreviewAnimation.value(
|
||||
_silentPreview ? 1. : 0.);
|
||||
const auto frame = _silentPreview
|
||||
? lookupPreviewFrame()
|
||||
: _cachedPreviewFrame;
|
||||
if (preview > 0. && !frame.image.isNull()) {
|
||||
p.setOpacity(preview);
|
||||
p.drawImage(inner, frame.image);
|
||||
if (frame.silent) {
|
||||
const auto iconSize = st::historyVideoMessageMuteSize;
|
||||
const auto iconRect = style::rtlrect(
|
||||
inner.x() + (inner.width() - iconSize) / 2,
|
||||
inner.y() + st::msgDateImgDelta,
|
||||
iconSize,
|
||||
iconSize,
|
||||
raw->width());
|
||||
p.setPen(Qt::NoPen);
|
||||
p.setBrush(st::msgDateImgBg);
|
||||
auto hq = PainterHighQualityEnabler(p);
|
||||
p.drawEllipse(iconRect);
|
||||
st::historyVideoMessageMute.paintInCenter(p, iconRect);
|
||||
}
|
||||
}
|
||||
}, raw->lifetime());
|
||||
|
||||
_descriptor.track->renderNextFrame() | rpl::start_with_next([=] {
|
||||
|
@ -1257,7 +1287,24 @@ void RoundVideoRecorder::fade(bool visible) {
|
|||
[=] { _preview->update(); },
|
||||
visible ? 0. : 1.,
|
||||
visible ? 1. : 0.,
|
||||
crl::time(200));
|
||||
kFadeDuration);
|
||||
}
|
||||
|
||||
auto RoundVideoRecorder::lookupPreviewFrame() const -> PreviewFrame {
|
||||
auto sounded = _soundedPreview
|
||||
? _soundedPreview->image(_side)
|
||||
: QImage();
|
||||
const auto silent = (_silentPreview && sounded.isNull());
|
||||
return {
|
||||
.image = silent ? _silentPreview->image(_side) : std::move(sounded),
|
||||
.silent = silent,
|
||||
};
|
||||
}
|
||||
|
||||
Fn<void()> RoundVideoRecorder::updater() const {
|
||||
return [=] {
|
||||
_preview->update();
|
||||
};
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::pause(Fn<void(RoundVideoResult)> done) {
|
||||
|
@ -1271,15 +1318,22 @@ void RoundVideoRecorder::pause(Fn<void(RoundVideoResult)> done) {
|
|||
_paused = true;
|
||||
prepareFrame(true);
|
||||
_progressReceived = false;
|
||||
_fadeContentAnimation.start(
|
||||
[=] { _preview->update(); },
|
||||
1.,
|
||||
0.,
|
||||
crl::time(200));
|
||||
_fadeContentAnimation.start(updater(), 1., 0., kFadeDuration);
|
||||
_descriptor.track->setState(Webrtc::VideoState::Inactive);
|
||||
_preview->update();
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::showPreview(
|
||||
std::shared_ptr<Ui::DynamicImage> silent,
|
||||
std::shared_ptr<Ui::DynamicImage> sounded) {
|
||||
_silentPreview = std::move(silent);
|
||||
_soundedPreview = std::move(sounded);
|
||||
_silentPreview->subscribeToUpdates(updater());
|
||||
_soundedPreview->subscribeToUpdates(updater());
|
||||
_fadePreviewAnimation.start(updater(), 0., 1., kFadeDuration);
|
||||
_preview->update();
|
||||
}
|
||||
|
||||
void RoundVideoRecorder::resume(RoundVideoPartial partial) {
|
||||
if (!_paused) {
|
||||
return;
|
||||
|
@ -1288,6 +1342,16 @@ void RoundVideoRecorder::resume(RoundVideoPartial partial) {
|
|||
that.restart(std::move(partial));
|
||||
});
|
||||
_paused = false;
|
||||
_cachedPreviewFrame = lookupPreviewFrame();
|
||||
if (const auto preview = base::take(_silentPreview)) {
|
||||
preview->subscribeToUpdates(nullptr);
|
||||
}
|
||||
if (const auto preview = base::take(_soundedPreview)) {
|
||||
preview->subscribeToUpdates(nullptr);
|
||||
}
|
||||
if (!_cachedPreviewFrame.image.isNull()) {
|
||||
_fadePreviewAnimation.start(updater(), 1., 0., kFadeDuration);
|
||||
}
|
||||
_descriptor.track->setState(Webrtc::VideoState::Active);
|
||||
_preview->update();
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ class VideoTrack;
|
|||
namespace Ui {
|
||||
|
||||
class RpWidget;
|
||||
class DynamicImage;
|
||||
class RoundVideoRecorder;
|
||||
|
||||
struct RoundVideoRecorderDescriptor {
|
||||
|
@ -58,18 +59,27 @@ public:
|
|||
explicit RoundVideoRecorder(RoundVideoRecorderDescriptor &&descriptor);
|
||||
~RoundVideoRecorder();
|
||||
|
||||
[[nodiscard]] int previewSize() const;
|
||||
[[nodiscard]] Fn<void(Media::Capture::Chunk)> audioChunkProcessor();
|
||||
|
||||
void pause(Fn<void(RoundVideoResult)> done = nullptr);
|
||||
void resume(RoundVideoPartial partial);
|
||||
void hide(Fn<void(RoundVideoResult)> done = nullptr);
|
||||
|
||||
void showPreview(
|
||||
std::shared_ptr<Ui::DynamicImage> silent,
|
||||
std::shared_ptr<Ui::DynamicImage> sounded);
|
||||
|
||||
using Update = Media::Capture::Update;
|
||||
using Error = Media::Capture::Error;
|
||||
[[nodiscard]] rpl::producer<Update, Error> updated();
|
||||
|
||||
private:
|
||||
class Private;
|
||||
struct PreviewFrame {
|
||||
QImage image;
|
||||
bool silent = false;
|
||||
};
|
||||
|
||||
void setup();
|
||||
void prepareFrame(bool blurred = false);
|
||||
|
@ -77,12 +87,21 @@ private:
|
|||
void progressTo(float64 progress);
|
||||
void fade(bool visible);
|
||||
|
||||
[[nodiscard]] Fn<void()> updater() const;
|
||||
[[nodiscard]] PreviewFrame lookupPreviewFrame() const;
|
||||
|
||||
const RoundVideoRecorderDescriptor _descriptor;
|
||||
std::unique_ptr<RpWidget> _preview;
|
||||
crl::object_on_queue<Private> _private;
|
||||
Ui::Animations::Simple _progressAnimation;
|
||||
Ui::Animations::Simple _fadeAnimation;
|
||||
Ui::Animations::Simple _fadeContentAnimation;
|
||||
|
||||
std::shared_ptr<Ui::DynamicImage> _silentPreview;
|
||||
std::shared_ptr<Ui::DynamicImage> _soundedPreview;
|
||||
Ui::Animations::Simple _fadePreviewAnimation;
|
||||
PreviewFrame _cachedPreviewFrame;
|
||||
|
||||
float64 _progress = 0.;
|
||||
QImage _frameOriginal;
|
||||
QImage _framePlaceholder;
|
||||
|
|
Loading…
Add table
Reference in a new issue