Prepare for round record pause/resume.

This commit is contained in:
John Preston 2024-10-21 12:21:59 +04:00
parent e8d87d37bb
commit d7ffdbd78d
10 changed files with 163 additions and 71 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 498 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View file

@ -1210,6 +1210,7 @@ historyRecordLockBody: icon {{ "voice_lock/record_lock_body", historyToDownBg }}
historyRecordLockMargin: margins(4px, 4px, 4px, 4px); historyRecordLockMargin: margins(4px, 4px, 4px, 4px);
historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }}; historyRecordLockArrow: icon {{ "voice_lock/voice_arrow", historyToDownFg }};
historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }}; historyRecordLockInput: icon {{ "voice_lock/input_mic_s", historyToDownFg }};
historyRecordLockRound: icon {{ "voice_lock/input_round_s", historyToDownFg }};
historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px); historyRecordLockRippleMargin: margins(6px, 6px, 6px, 6px);
historyRecordDelete: IconButton(historyAttach) { historyRecordDelete: IconButton(historyAttach) {

View file

@ -497,8 +497,7 @@ ListenWrap::ListenWrap(
, _data(data) , _data(data)
, _delete(base::make_unique_q<Ui::IconButton>(parent, _st.remove)) , _delete(base::make_unique_q<Ui::IconButton>(parent, _st.remove))
, _durationFont(font) , _durationFont(font)
, _duration(Ui::FormatDurationText( , _duration(Ui::FormatDurationText(_data->duration / 1000))
float64(_data->samples) / ::Media::Player::kDefaultFrequency))
, _durationWidth(_durationFont->width(_duration)) , _durationWidth(_durationFont->width(_duration))
, _playPauseSt(st::mediaPlayerButton) , _playPauseSt(st::mediaPlayerButton)
, _playPauseButton(base::make_unique_q<Ui::AbstractButton>(parent)) , _playPauseButton(base::make_unique_q<Ui::AbstractButton>(parent))
@ -634,7 +633,7 @@ void ListenWrap::initPlayButton() {
_mediaView->setBytes(_data->bytes); _mediaView->setBytes(_data->bytes);
_document->size = _data->bytes.size(); _document->size = _data->bytes.size();
_document->type = VoiceDocument; _document->type = _data->video ? RoundVideoDocument : VoiceDocument;
const auto &play = _playPauseSt.playOuter; const auto &play = _playPauseSt.playOuter;
const auto &width = _waveformBgFinalCenterRect.height(); const auto &width = _waveformBgFinalCenterRect.height();
@ -833,6 +832,7 @@ public:
void requestPaintLockToStopProgress(float64 progress); void requestPaintLockToStopProgress(float64 progress);
void requestPaintPauseToInputProgress(float64 progress); void requestPaintPauseToInputProgress(float64 progress);
void setVisibleTopPart(int part); void setVisibleTopPart(int part);
void setRecordingVideo(bool value);
[[nodiscard]] rpl::producer<> locks() const; [[nodiscard]] rpl::producer<> locks() const;
[[nodiscard]] bool isLocked() const; [[nodiscard]] bool isLocked() const;
@ -861,6 +861,7 @@ private:
float64 _pauseToInputProgress = 0.; float64 _pauseToInputProgress = 0.;
rpl::variable<float64> _progress = 0.; rpl::variable<float64> _progress = 0.;
int _visibleTopPart = -1; int _visibleTopPart = -1;
bool _recordingVideo = false;
}; };
@ -884,6 +885,10 @@ void RecordLock::setVisibleTopPart(int part) {
_visibleTopPart = part; _visibleTopPart = part;
} }
void RecordLock::setRecordingVideo(bool value) {
_recordingVideo = value;
}
void RecordLock::init() { void RecordLock::init() {
shownValue( shownValue(
) | rpl::start_with_next([=](bool shown) { ) | rpl::start_with_next([=](bool shown) {
@ -974,9 +979,10 @@ void RecordLock::drawProgress(QPainter &p) {
p.setBrush(_st.fg); p.setBrush(_st.fg);
if (_pauseToInputProgress > 0.) { if (_pauseToInputProgress > 0.) {
p.setOpacity(_pauseToInputProgress); p.setOpacity(_pauseToInputProgress);
st::historyRecordLockInput.paintInCenter( const auto &icon = _recordingVideo
p, ? st::historyRecordLockRound
blockRect.toRect()); : st::historyRecordLockInput;
icon.paintInCenter(p, blockRect.toRect());
p.setOpacity(1. - _pauseToInputProgress); p.setOpacity(1. - _pauseToInputProgress);
} }
p.drawRoundedRect( p.drawRoundedRect(
@ -1533,6 +1539,7 @@ void VoiceRecordBar::init() {
} }
_recordingTipRequire = crl::now(); _recordingTipRequire = crl::now();
_recordingVideo = (_send->type() == Ui::SendButton::Type::Round); _recordingVideo = (_send->type() == Ui::SendButton::Type::Round);
_lock->setRecordingVideo(_recordingVideo);
_startTimer.callOnce(st::universalDuration); _startTimer.callOnce(st::universalDuration);
} else if (e->type() == QEvent::MouseButtonRelease) { } else if (e->type() == QEvent::MouseButtonRelease) {
checkTipRequired(); checkTipRequired();
@ -1680,7 +1687,9 @@ void VoiceRecordBar::startRecording() {
_paused = false; _paused = false;
instance()->pause(false, nullptr); instance()->pause(false, nullptr);
if (_videoRecorder) { if (_videoRecorder) {
_videoRecorder->setPaused(false); _videoRecorder->resume({
.content = _data.bytes,
});
} }
} else { } else {
instance()->start(_videoRecorder instance()->start(_videoRecorder
@ -1809,7 +1818,6 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
using namespace ::Media::Capture; using namespace ::Media::Capture;
if (type == StopType::Cancel) { if (type == StopType::Cancel) {
if (_videoRecorder) { if (_videoRecorder) {
_videoRecorder->setPaused(true);
_videoRecorder->hide(); _videoRecorder->hide();
} }
instance()->stop(crl::guard(this, [=](Result &&data) { instance()->stop(crl::guard(this, [=](Result &&data) {
@ -1817,29 +1825,54 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
})); }));
} else if (type == StopType::Listen) { } else if (type == StopType::Listen) {
if (_videoRecorder) { if (_videoRecorder) {
_videoRecorder->setPaused(true); 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();
_paused = true;
_data = ::Media::Capture::Result{
.bytes = std::move(data.content),
//.waveform = std::move(data.waveform),
.duration = data.duration,
.video = true,
};
_listen = std::make_unique<ListenWrap>(
this,
_st,
&_show->session(),
&_data,
_cancelFont);
_listenChanges.fire({});
}
});
});
instance()->pause(true);
} else {
instance()->pause(true, crl::guard(this, [=](Result &&data) {
if (data.bytes.isEmpty()) {
// Close everything.
stop(false);
return;
}
_paused = true;
_data = std::move(data);
window()->raise();
window()->activateWindow();
_listen = std::make_unique<ListenWrap>(
this,
_st,
&_show->session(),
&_data,
_cancelFont);
_listenChanges.fire({});
// _lockShowing = false;
}));
} }
instance()->pause(true, crl::guard(this, [=](Result &&data) {
if (data.bytes.isEmpty()) {
// Close everything.
stop(false);
return;
}
_paused = true;
_data = std::move(data);
window()->raise();
window()->activateWindow();
_listen = std::make_unique<ListenWrap>(
this,
_st,
&_show->session(),
&_data,
_cancelFont);
_listenChanges.fire({});
// _lockShowing = false;
}));
} else if (type == StopType::Send) { } else if (type == StopType::Send) {
if (_videoRecorder) { if (_videoRecorder) {
const auto weak = Ui::MakeWeak(this); const auto weak = Ui::MakeWeak(this);
@ -1882,7 +1915,7 @@ void VoiceRecordBar::stopRecording(StopType type, bool ttlBeforeHide) {
_sendVoiceRequests.fire({ _sendVoiceRequests.fire({
_data.bytes, _data.bytes,
_data.waveform, _data.waveform,
Duration(_data.samples), _data.duration,
options, options,
}); });
})); }));
@ -1949,7 +1982,7 @@ void VoiceRecordBar::requestToSendWithOptions(Api::SendOptions options) {
_sendVoiceRequests.fire({ _sendVoiceRequests.fire({
_data.bytes, _data.bytes,
_data.waveform, _data.waveform,
Duration(_data.samples), _data.duration,
options, options,
}); });
} }

View file

@ -170,13 +170,15 @@ void Instance::stop(Fn<void(Result&&)> callback) {
} }
void Instance::pause(bool value, Fn<void(Result&&)> callback) { void Instance::pause(bool value, Fn<void(Result&&)> callback) {
Expects(callback != nullptr || !value);
InvokeQueued(_inner.get(), [=] { InvokeQueued(_inner.get(), [=] {
_inner->pause(value, [=](Result &&result) { auto done = callback
crl::on_main([=, result = std::move(result)]() mutable { ? [=](Result &&result) {
callback(std::move(result)); crl::on_main([=, result = std::move(result)]() mutable {
}); callback(std::move(result));
}); });
}
: std::move(callback);
_inner->pause(value, std::move(done));
}); });
} }
@ -481,11 +483,16 @@ void Instance::Inner::pause(bool value, Fn<void(Result&&)> callback) {
if (!_paused) { if (!_paused) {
return; return;
} }
callback({ if (callback) {
d->fullSamples ? d->data : QByteArray(), callback({
d->fullSamples ? CollectWaveform(d->waveform) : VoiceWaveform(), .bytes = d->fullSamples ? d->data : QByteArray(),
qint32(d->fullSamples), .waveform = (d->fullSamples
}); ? CollectWaveform(d->waveform)
: VoiceWaveform()),
.duration = ((d->fullSamples * crl::time(1000))
/ int64(kCaptureFrequency)),
});
}
} }
void Instance::Inner::stop(Fn<void(Result&&)> callback) { void Instance::Inner::stop(Fn<void(Result&&)> callback) {
@ -620,7 +627,11 @@ void Instance::Inner::stop(Fn<void(Result&&)> callback) {
} }
if (needResult) { if (needResult) {
callback({ result, waveform, samples }); callback({
.bytes = result,
.waveform = waveform,
.duration = (samples * crl::time(1000)) / kCaptureFrequency,
});
} }
} }

View file

@ -63,7 +63,7 @@ public:
void start(Fn<void(Chunk)> externalProcessing = nullptr); void start(Fn<void(Chunk)> externalProcessing = nullptr);
void stop(Fn<void(Result&&)> callback = nullptr); void stop(Fn<void(Result&&)> callback = nullptr);
void pause(bool value, Fn<void(Result&&)> callback); void pause(bool value, Fn<void(Result&&)> callback = nullptr);
private: private:
class Inner; class Inner;

View file

@ -12,7 +12,8 @@ namespace Media::Capture {
struct Result { struct Result {
QByteArray bytes; QByteArray bytes;
VoiceWaveform waveform; VoiceWaveform waveform;
int samples = 0; crl::time duration;
bool video = false;
}; };
} // namespace Media::Capture } // namespace Media::Capture

View file

@ -46,6 +46,7 @@ public:
[[nodiscard]] rpl::producer<Update, Error> updated() const; [[nodiscard]] rpl::producer<Update, Error> updated() const;
[[nodiscard]] RoundVideoResult finish(); [[nodiscard]] RoundVideoResult finish();
void restart(RoundVideoPartial partial);
private: private:
static int Write(void *opaque, uint8_t *buf, int buf_size); static int Write(void *opaque, uint8_t *buf, int buf_size);
@ -111,6 +112,10 @@ private:
crl::time _lastUpdateDuration = 0; crl::time _lastUpdateDuration = 0;
rpl::event_stream<Update, Error> _updates; rpl::event_stream<Update, Error> _updates;
crl::time _maxDuration = 0;
crl::time _previousPartsDuration = 0;
QByteArray _previousContent;
std::vector<bool> _circleMask; // Always nice to use vector<bool>! :D std::vector<bool> _circleMask; // Always nice to use vector<bool>! :D
base::ConcurrentTimer _timeoutTimer; base::ConcurrentTimer _timeoutTimer;
@ -119,6 +124,7 @@ private:
RoundVideoRecorder::Private::Private(crl::weak_on_queue<Private> weak) RoundVideoRecorder::Private::Private(crl::weak_on_queue<Private> weak)
: _weak(std::move(weak)) : _weak(std::move(weak))
, _maxDuration(kMaxDuration)
, _timeoutTimer(_weak, [=] { timeout(); }) { , _timeoutTimer(_weak, [=] { timeout(); }) {
initEncoding(); initEncoding();
initCircleMask(); initCircleMask();
@ -385,15 +391,31 @@ RoundVideoResult RoundVideoRecorder::Private::finish() {
return {}; return {};
} }
finishEncoding(); finishEncoding();
if (_resultDuration < kMinDuration) { auto result = RoundVideoResult{
.content = base::take(_result),
.waveform = QByteArray(),
.duration = base::take(_resultDuration),
};
if (result.duration < kMinDuration) {
return {}; return {};
} }
return { _previousPartsDuration += result.duration;
.content = _result, _maxDuration -= result.duration;
.waveform = QByteArray(), return result;
.duration = _resultDuration, }
};
}; void RoundVideoRecorder::Private::restart(RoundVideoPartial partial) {
if (_format) {
return;
} else if (_maxDuration <= 0) {
notifyFinished();
return;
}
_previousContent = std::move(partial.content);
_finished = false;
initEncoding();
_timeoutTimer.callOnce(kInitTimeout);
}
void RoundVideoRecorder::Private::fail(Error error) { void RoundVideoRecorder::Private::fail(Error error) {
deinitEncoding(); deinitEncoding();
@ -421,7 +443,17 @@ void RoundVideoRecorder::Private::deinitEncoding() {
_videoFirstTimestamp = -1; _videoFirstTimestamp = -1;
_videoPts = 0; _videoPts = 0;
_audioTail = QByteArray();
_audioPts = 0; _audioPts = 0;
_audioChannels = 0;
_firstAudioChunkFinished = 0;
_firstVideoFrameTime = 0;
_resultOffset = 0;
_maxLevelSinceLastUpdate = 0;
_lastUpdateDuration = 0;
} }
void RoundVideoRecorder::Private::push( void RoundVideoRecorder::Private::push(
@ -494,7 +526,7 @@ void RoundVideoRecorder::Private::encodeVideoFrame(
cutCircleFromYUV420P(_videoFrame.get()); cutCircleFromYUV420P(_videoFrame.get());
_videoFrame->pts = mcstimestamp - _videoFirstTimestamp; _videoFrame->pts = mcstimestamp - _videoFirstTimestamp;
if (_videoFrame->pts >= kMaxDuration * int64(1000)) { if (_videoFrame->pts >= _maxDuration * int64(1000)) {
notifyFinished(); notifyFinished();
return; return;
} else if (!writeFrame(_videoFrame, _videoCodec, _videoStream)) { } else if (!writeFrame(_videoFrame, _videoCodec, _videoStream)) {
@ -611,7 +643,7 @@ void RoundVideoRecorder::Private::encodeAudioFrame(
_audioFrame->pts = _audioPts; _audioFrame->pts = _audioPts;
_audioPts += _audioFrame->nb_samples; _audioPts += _audioFrame->nb_samples;
if (_audioPts >= kMaxDuration * int64(kAudioFrequency) / 1000) { if (_audioPts >= _maxDuration * int64(kAudioFrequency) / 1000) {
notifyFinished(); notifyFinished();
return; return;
} else if (!writeFrame(_audioFrame, _audioCodec, _audioStream)) { } else if (!writeFrame(_audioFrame, _audioCodec, _audioStream)) {
@ -632,7 +664,7 @@ void RoundVideoRecorder::Private::encodeAudioFrame(
void RoundVideoRecorder::Private::notifyFinished() { void RoundVideoRecorder::Private::notifyFinished() {
_finished = true; _finished = true;
_updates.fire({ _updates.fire({
.samples = int(_resultDuration * 48), .samples = int((_previousPartsDuration + _resultDuration) * 48),
.level = base::take(_maxLevelSinceLastUpdate), .level = base::take(_maxLevelSinceLastUpdate),
.finished = true, .finished = true,
}); });
@ -709,7 +741,7 @@ void RoundVideoRecorder::Private::updateResultDuration(
if (initial || (_lastUpdateDuration + kUpdateEach < _resultDuration)) { if (initial || (_lastUpdateDuration + kUpdateEach < _resultDuration)) {
_lastUpdateDuration = _resultDuration; _lastUpdateDuration = _resultDuration;
_updates.fire({ _updates.fire({
.samples = int(_resultDuration * 48), .samples = int((_previousPartsDuration + _resultDuration) * 48),
.level = base::take(_maxLevelSinceLastUpdate), .level = base::take(_maxLevelSinceLastUpdate),
}); });
} }
@ -744,13 +776,7 @@ auto RoundVideoRecorder::updated() -> rpl::producer<Update, Error> {
} }
void RoundVideoRecorder::hide(Fn<void(RoundVideoResult)> done) { void RoundVideoRecorder::hide(Fn<void(RoundVideoResult)> done) {
if (done) { pause(std::move(done));
_private.with([done = std::move(done)](Private &that) {
done(that.finish());
});
}
setPaused(true);
_preview->hide(); _preview->hide();
if (const auto onstack = _descriptor.hidden) { if (const auto onstack = _descriptor.hidden) {
@ -934,16 +960,29 @@ void RoundVideoRecorder::fade(bool visible) {
crl::time(200)); crl::time(200));
} }
void RoundVideoRecorder::setPaused(bool paused) { void RoundVideoRecorder::pause(Fn<void(RoundVideoResult)> done) {
if (_paused == paused) { if (_paused) {
return; return;
} else if (done) {
_private.with([done = std::move(done)](Private &that) {
done(that.finish());
});
} }
_paused = paused; _paused = true;
_descriptor.track->setState(paused _descriptor.track->setState(Webrtc::VideoState::Inactive);
? Webrtc::VideoState::Inactive
: Webrtc::VideoState::Active);
_preview->update(); _preview->update();
} }
void RoundVideoRecorder::resume(RoundVideoPartial partial) {
if (!_paused) {
return;
}
_private.with([partial = std::move(partial)](Private &that) mutable {
that.restart(std::move(partial));
});
_paused = false;
_descriptor.track->setState(Webrtc::VideoState::Active);
_preview->update();
}
} // namespace Ui } // namespace Ui

View file

@ -44,6 +44,12 @@ struct RoundVideoResult {
crl::time duration = 0; crl::time duration = 0;
}; };
struct RoundVideoPartial {
QByteArray content;
crl::time from = 0;
crl::time till = 0;
};
class RoundVideoRecorder final : public base::has_weak_ptr { class RoundVideoRecorder final : public base::has_weak_ptr {
public: public:
explicit RoundVideoRecorder(RoundVideoRecorderDescriptor &&descriptor); explicit RoundVideoRecorder(RoundVideoRecorderDescriptor &&descriptor);
@ -51,7 +57,8 @@ public:
[[nodiscard]] Fn<void(Media::Capture::Chunk)> audioChunkProcessor(); [[nodiscard]] Fn<void(Media::Capture::Chunk)> audioChunkProcessor();
void setPaused(bool paused); void pause(Fn<void(RoundVideoResult)> done = nullptr);
void resume(RoundVideoPartial partial);
void hide(Fn<void(RoundVideoResult)> done = nullptr); void hide(Fn<void(RoundVideoResult)> done = nullptr);
using Update = Media::Capture::Update; using Update = Media::Capture::Update;