From d7ffdbd78de978e9d9f520e302eb34ac8e4e2670 Mon Sep 17 00:00:00 2001 From: John Preston Date: Mon, 21 Oct 2024 12:21:59 +0400 Subject: [PATCH] Prepare for round record pause/resume. --- .../icons/voice_lock/input_round_s.png | Bin 0 -> 498 bytes .../icons/voice_lock/input_round_s@2x.png | Bin 0 -> 1097 bytes .../icons/voice_lock/input_round_s@3x.png | Bin 0 -> 1655 bytes .../chat_helpers/chat_helpers.style | 1 + .../history_view_voice_record_bar.cpp | 97 ++++++++++++------ .../media/audio/media_audio_capture.cpp | 35 ++++--- .../media/audio/media_audio_capture.h | 2 +- .../media/audio/media_audio_capture_common.h | 3 +- .../ui/controls/round_video_recorder.cpp | 87 +++++++++++----- .../ui/controls/round_video_recorder.h | 9 +- 10 files changed, 163 insertions(+), 71 deletions(-) create mode 100644 Telegram/Resources/icons/voice_lock/input_round_s.png create mode 100644 Telegram/Resources/icons/voice_lock/input_round_s@2x.png create mode 100644 Telegram/Resources/icons/voice_lock/input_round_s@3x.png diff --git a/Telegram/Resources/icons/voice_lock/input_round_s.png b/Telegram/Resources/icons/voice_lock/input_round_s.png new file mode 100644 index 0000000000000000000000000000000000000000..549dd1ab1823cc0fd33cc22bfd130ecfc95a1904 GIT binary patch literal 498 zcmVbR6DMtFcdZ8k+TM~fK;TSLLgU8I(Cqnf)!XwM-(Zo zg_IR!6%jN@i5HMAyk(82i zJ~f4NE~Ru5{hC%|jD6qlZ!|(xRle^#y>}4(3=X2M>)N)ZlulEW(zb28u6tEcS(bU8 zPtDHrye!KvRZY!?5Y~0AwdS0sX_}@f=Ui)D*Y$9e#*H5tNB}?(>u~@`k(`+_bmOVg(C0LeiHk)|yVzIc}Mi2zVUM`n~ zLg83a)=?-FmdhpbvgPam07#O=_vS=uuqJ$;w%u`26!A%&P7MM+J5dyENs6K%(h={C z9axq{z7$2VX3#W^7{lT4)M^lh!=Y)KtJR98X*_v>N5Nna4W6E!oG(gI6w9(+uNMF& zlZmRTv)Rm{tt3gIP>3LiU@(Y-_=h00C;+^@z3p6!AP7Ma^7%Z^^DdVQ&l`r(>-Eaz za=l*P8Dn%^|I0N1{J6U%6^!US$y$OOKDwPV_8%dJF;c%1#$>X8M_X0u+e2LKGiSO(}#Boc|s%S!<0^?J@rfIaR|xm-qr zQmN$ec(#*09#5%M`pfXhg6j3UEXx4EFifM-*rLhf@iZC@lx1aEuGi})5oDTXHk(D8 zAB)8lMX^h+ilW3~F#wp&X4!1k(c|kk_A5We<8d;XMCAv=FprOqnx?heZ4{i=%MYX` znM{tyyTwRo6YX42G)^@RA_J7w20pn@UA8SbA@2@z8SYZL53 zRv{YV3CC@`A|X}8m=qBth0R6(2Acz$fZ!S|-NnNym(vu^`W1f6H{ZtSII5O|bUKnG$+G>3LzZ63J}6-wHlAdIgXo)Nmi>>5QJW@_d51Gr`PKVf?%~;^|UG& z40gNSw=xN*+wBH}K`lB@A7iOhDxFR#`8^|k7q4F-ckq2TlROePbRXbqT5CcocbC=>>Rfl7_* z>uYkksZ>fOu3Rp=-ERFf*NWTiE|<$HWm2gWiL78Ss3;1O0wIjWVtOk)8?jgn_r{8% zXdG|?RIDLlY$`Njn4D@+*F+zp67Ah1VPYRlH)jh zzABf?^VE|Be7yB~J*{(hJRZk=;G1}h$++Ea+)u{iaZSal)ha4eC=}+bBq4=D0V!Fn zR;L%R*=+de^!a@ASCRyu&xiXqo6R<*fZy*&1s)$CUtty|!(=i&K0YE<{eHjd4olaE~SuBW$?xC4>!plt zh8-Ln93LO!Px}3SHk-YnK48t&)Ohzx2 z{zopCLmfAl%V{hX2n1A7kW#7Sa5yHHayT5NQVA~wA=KU{C#$ONHUzLNTPzk)4;&7M z$z*bCYpdOEPcGPQx3{*olF1}$PlLfwEEdUpi3%MIOQ+Koiv?v54-Zv+ax%xq$A^c9 zC~L7;(&;q4R%&MLimLT!sZ=7X7w?lASKjmOijhbJx!QiezqhwXCTDMNuix*F(ng~( z5{XbMMr+u{#s*4fv)QTR>LuFkb~c+GE0zo@!#o}jzGAw$xuKDU7hhRf@pwEm(x?nu zTU$eES(aO zI?QA56Dq@We8JC!P<8<-!|<{0bUL3OT+9T+Fixj)tZ)LT47-AQvg|ge*+ceVf;;mMTGAKgpJZ^7qQ92rpPTi`!gu~&8Mx$fJl0j=& zKA%ThbhFufc6LT4=j`kZZ8{-@`Fx&IFRvwi~1>Xe&079YA{r&yP$;tZqx@vx77-oHa{p952{{9~CfdGJH zGFhoqbWusSDAg#sgooeEX7m33{{H^{)bJYsKv9%;8h!%+WHOn0yiVUCWD7Vxt3h`;fAx>gqRg95}~ z^a%*nM>`zHu`D~Mk9K5PmL%zwKH6EbWXY0$`3H##KK|A}p^g9m002ovPDHLkV1mlG B8*~5w literal 0 HcmV?d00001 diff --git a/Telegram/SourceFiles/chat_helpers/chat_helpers.style b/Telegram/SourceFiles/chat_helpers/chat_helpers.style index b1bfa2f78..25e4852a6 100644 --- a/Telegram/SourceFiles/chat_helpers/chat_helpers.style +++ b/Telegram/SourceFiles/chat_helpers/chat_helpers.style @@ -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) { diff --git a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp index 436e38167..e1110c770 100644 --- a/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp +++ b/Telegram/SourceFiles/history/view/controls/history_view_voice_record_bar.cpp @@ -497,8 +497,7 @@ ListenWrap::ListenWrap( , _data(data) , _delete(base::make_unique_q(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(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 _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( + 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( + 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( - 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, }); } diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp index 3d6df88c6..d46f8789c 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.cpp @@ -170,13 +170,15 @@ void Instance::stop(Fn callback) { } void Instance::pause(bool value, Fn 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 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 callback) { @@ -620,7 +627,11 @@ void Instance::Inner::stop(Fn callback) { } if (needResult) { - callback({ result, waveform, samples }); + callback({ + .bytes = result, + .waveform = waveform, + .duration = (samples * crl::time(1000)) / kCaptureFrequency, + }); } } diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture.h b/Telegram/SourceFiles/media/audio/media_audio_capture.h index e588637c1..fb79b39dd 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture.h +++ b/Telegram/SourceFiles/media/audio/media_audio_capture.h @@ -63,7 +63,7 @@ public: void start(Fn externalProcessing = nullptr); void stop(Fn callback = nullptr); - void pause(bool value, Fn callback); + void pause(bool value, Fn callback = nullptr); private: class Inner; diff --git a/Telegram/SourceFiles/media/audio/media_audio_capture_common.h b/Telegram/SourceFiles/media/audio/media_audio_capture_common.h index 370259f58..e276ad4e9 100644 --- a/Telegram/SourceFiles/media/audio/media_audio_capture_common.h +++ b/Telegram/SourceFiles/media/audio/media_audio_capture_common.h @@ -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 diff --git a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp index 12dc6fd93..3025a84ab 100644 --- a/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp +++ b/Telegram/SourceFiles/ui/controls/round_video_recorder.cpp @@ -46,6 +46,7 @@ public: [[nodiscard]] rpl::producer 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 _updates; + crl::time _maxDuration = 0; + crl::time _previousPartsDuration = 0; + QByteArray _previousContent; + std::vector _circleMask; // Always nice to use vector! :D base::ConcurrentTimer _timeoutTimer; @@ -119,6 +124,7 @@ private: RoundVideoRecorder::Private::Private(crl::weak_on_queue 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 { } void RoundVideoRecorder::hide(Fn 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 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 diff --git a/Telegram/SourceFiles/ui/controls/round_video_recorder.h b/Telegram/SourceFiles/ui/controls/round_video_recorder.h index 661314445..6d7505207 100644 --- a/Telegram/SourceFiles/ui/controls/round_video_recorder.h +++ b/Telegram/SourceFiles/ui/controls/round_video_recorder.h @@ -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 audioChunkProcessor(); - void setPaused(bool paused); + void pause(Fn done = nullptr); + void resume(RoundVideoPartial partial); void hide(Fn done = nullptr); using Update = Media::Capture::Update;