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

(image error) Size: 498 B

Binary file not shown.

After

(image error) Size: 1.1 KiB

Binary file not shown.

After

(image error) Size: 1.6 KiB

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -44,6 +44,12 @@ struct RoundVideoResult {
crl::time duration = 0;
};
struct RoundVideoPartial {
QByteArray content;
crl::time from = 0;
crl::time till = 0;
};
class RoundVideoRecorder final : public base::has_weak_ptr {
public:
explicit RoundVideoRecorder(RoundVideoRecorderDescriptor &&descriptor);
@ -51,7 +57,8 @@ public:
[[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);
using Update = Media::Capture::Update;