Added ability to seek recorded voice data.

This commit is contained in:
23rd 2020-11-10 21:06:22 +03:00 committed by John Preston
parent 5eba680483
commit 2668619758
1 changed files with 142 additions and 13 deletions

View File

@ -39,6 +39,7 @@ namespace {
using SendActionUpdate = VoiceRecordBar::SendActionUpdate;
using VoiceToSend = VoiceRecordBar::VoiceToSend;
constexpr auto kAudioVoiceUpdateView = crl::time(200);
constexpr auto kLockDelay = crl::time(100);
constexpr auto kRecordingUpdateDelta = crl::time(100);
constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes
@ -55,6 +56,10 @@ enum class FilterType {
Cancel,
};
[[nodiscard]] auto Progress(int low, int high) {
return std::clamp(float64(low) / high, 0., 1.);
}
[[nodiscard]] auto Duration(int samples) {
return samples / ::Media::Player::kDefaultFrequency;
}
@ -116,6 +121,10 @@ public:
private:
void init();
void initPlayButton();
void initPlayProgress();
bool isInPlayer(const ::Media::Player::TrackState &state) const;
bool isInPlayer() const;
int computeTopMargin(int height) const;
@ -136,9 +145,12 @@ private:
QRect _waveformBgRect;
QRect _waveformBgFinalCenterRect;
QRect _waveformFgRect;
::Media::Player::PlayButtonLayout _playPause;
anim::value _playProgress;
rpl::variable<float64> _showProgress = 0.;
rpl::lifetime _lifetime;
@ -253,13 +265,19 @@ void ListenWrap::init() {
// Waveform paint.
{
const auto &play = _playPauseSt.playOuter;
const auto top = computeTopMargin(st::msgWaveformMax);
const auto left = play.width() / 2 + halfHeight;
const auto right = st::historyRecordWaveformLeftSkip
+ _durationWidth;
const auto rect = bgCenterRect.marginsRemoved(
style::margins(left, top, right, top));
const auto computeRect = [&] {
const auto &play = _playPauseSt.playOuter;
const auto top = computeTopMargin(st::msgWaveformMax);
const auto left = play.width() / 2 + halfHeight;
const auto right = st::historyRecordWaveformLeftSkip
+ _durationWidth;
_waveformFgRect = bgCenterRect.marginsRemoved(
style::margins(left, top, right, top));
return _waveformFgRect;
};
const auto rect = (progress == 1.)
? _waveformFgRect
: computeRect();
if (rect.width() > 0) {
p.translate(rect.topLeft());
HistoryDocumentVoice::PaintWaveform(
@ -268,7 +286,7 @@ void ListenWrap::init() {
rect.width(),
false,
false,
0.);
_playProgress.current());
p.resetTransform();
}
}
@ -276,6 +294,7 @@ void ListenWrap::init() {
}, _lifetime);
initPlayButton();
initPlayProgress();
}
void ListenWrap::initPlayButton() {
@ -317,7 +336,7 @@ void ListenWrap::initPlayButton() {
instance()->updatedNotifier(
) | rpl::start_with_next([=](const State &state) {
if (state.id.audio() == _document) {
if (isInPlayer(state)) {
*showPause = ShowPauseIcon(state.state);
} else if (showPause->current()) {
*showPause = false;
@ -332,13 +351,124 @@ void ListenWrap::initPlayButton() {
const auto weak = Ui::MakeWeak(_controller->content().get());
_lifetime.add([=] {
const auto voiceState = instance()->getState(AudioMsgId::Type::Voice);
if (weak && voiceState.id && (voiceState.id.audio() == _document)) {
if (weak && isInPlayer()) {
weak->stopAndClosePlayer();
}
});
}
void ListenWrap::initPlayProgress() {
using namespace ::Media::Player;
using State = TrackState;
const auto animation = _lifetime.make_state<Ui::Animations::Basic>();
const auto isPointer = _lifetime.make_state<rpl::variable<bool>>(false);
const auto &voice = AudioMsgId::Type::Voice;
const auto updateCursor = [=](const QPoint &p) {
*isPointer = isInPlayer() ? _waveformFgRect.contains(p) : false;
};
rpl::merge(
instance()->startsPlay(voice) | rpl::map_to(true),
instance()->stops(voice) | rpl::map_to(false)
) | rpl::start_with_next([=](bool play) {
_parent->setMouseTracking(isInPlayer() && play);
updateCursor(_parent->mapFromGlobal(QCursor::pos()));
}, _lifetime);
instance()->updatedNotifier(
) | rpl::start_with_next([=](const State &state) {
if (!isInPlayer(state)) {
return;
}
const auto progress = state.length
? Progress(state.position, state.length)
: 0.;
if (IsStopped(state.state)) {
_playProgress = anim::value();
} else {
_playProgress.start(progress);
}
animation->start();
}, _lifetime);
auto animationCallback = [=](crl::time now) {
if (anim::Disabled()) {
now += kAudioVoiceUpdateView;
}
const auto dt = (now - animation->started())
/ float64(kAudioVoiceUpdateView);
if (dt >= 1.) {
animation->stop();
_playProgress.finish();
} else {
_playProgress.update(std::min(dt, 1.), anim::linear);
}
_parent->update();
return (dt < 1.);
};
animation->init(std::move(animationCallback));
const auto isPressed = _lifetime.make_state<bool>(false);
isPointer->changes(
) | rpl::start_with_next([=](bool pointer) {
_parent->setCursor(pointer ? style::cur_pointer : style::cur_default);
}, _lifetime);
_parent->events(
) | rpl::filter([=](not_null<QEvent*> e) {
return (e->type() == QEvent::MouseMove
|| e->type() == QEvent::MouseButtonPress
|| e->type() == QEvent::MouseButtonRelease);
}) | rpl::start_with_next([=](not_null<QEvent*> e) {
if (!isInPlayer()) {
return;
}
const auto type = e->type();
const auto isMove = (type == QEvent::MouseMove);
const auto &pos = static_cast<QMouseEvent*>(e.get())->pos();
if (*isPressed) {
*isPointer = true;
} else if (isMove) {
updateCursor(pos);
}
if (type == QEvent::MouseButtonPress) {
if (isPointer->current() && !(*isPressed)) {
instance()->startSeeking(voice);
*isPressed = true;
}
} else if (*isPressed) {
const auto &rect = _waveformFgRect;
const auto left = float64(pos.x() - rect.x());
const auto progress = Progress(left, rect.width());
const auto isRelease = (type == QEvent::MouseButtonRelease);
if (isRelease || isMove) {
_playProgress = anim::value(progress, progress);
_parent->update();
if (isRelease) {
instance()->finishSeeking(voice, progress);
*isPressed = false;
}
}
}
}, _lifetime);
}
bool ListenWrap::isInPlayer(const ::Media::Player::TrackState &state) const {
return (state.id && (state.id.audio() == _document));
}
bool ListenWrap::isInPlayer() const {
using Type = AudioMsgId::Type;
return isInPlayer(::Media::Player::instance()->getState(Type::Voice));
}
int ListenWrap::computeTopMargin(int height) const {
return (_waveformBgRect.height() - height) / 2;
}
@ -1150,8 +1280,7 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
const auto localPos = mapFromGlobal(globalPos);
const auto lower = _lock->height();
const auto higher = 0;
const auto progress = localPos.y() / (float64)(higher - lower);
_lock->requestPaintProgress(std::clamp(progress, 0., 1.));
_lock->requestPaintProgress(Progress(localPos.y(), higher - lower));
}
void VoiceRecordBar::orderControls() {