mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-18 07:07:08 +02:00
Prepare for round record pause/resume.
This commit is contained in:
parent
e8d87d37bb
commit
d7ffdbd78d
10 changed files with 163 additions and 71 deletions
Telegram
Resources/icons/voice_lock
SourceFiles
chat_helpers
history/view/controls
media/audio
ui/controls
BIN
Telegram/Resources/icons/voice_lock/input_round_s.png
Normal file
BIN
Telegram/Resources/icons/voice_lock/input_round_s.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 498 B |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@2x.png
Normal file
BIN
Telegram/Resources/icons/voice_lock/input_round_s@2x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.1 KiB |
BIN
Telegram/Resources/icons/voice_lock/input_round_s@3x.png
Normal file
BIN
Telegram/Resources/icons/voice_lock/input_round_s@3x.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 1.6 KiB |
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Reference in a new issue