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 SendActionUpdate = VoiceRecordBar::SendActionUpdate;
using VoiceToSend = VoiceRecordBar::VoiceToSend; using VoiceToSend = VoiceRecordBar::VoiceToSend;
constexpr auto kAudioVoiceUpdateView = crl::time(200);
constexpr auto kLockDelay = crl::time(100); constexpr auto kLockDelay = crl::time(100);
constexpr auto kRecordingUpdateDelta = crl::time(100); constexpr auto kRecordingUpdateDelta = crl::time(100);
constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes constexpr auto kAudioVoiceMaxLength = 100 * 60; // 100 minutes
@ -55,6 +56,10 @@ enum class FilterType {
Cancel, Cancel,
}; };
[[nodiscard]] auto Progress(int low, int high) {
return std::clamp(float64(low) / high, 0., 1.);
}
[[nodiscard]] auto Duration(int samples) { [[nodiscard]] auto Duration(int samples) {
return samples / ::Media::Player::kDefaultFrequency; return samples / ::Media::Player::kDefaultFrequency;
} }
@ -116,6 +121,10 @@ public:
private: private:
void init(); void init();
void initPlayButton(); void initPlayButton();
void initPlayProgress();
bool isInPlayer(const ::Media::Player::TrackState &state) const;
bool isInPlayer() const;
int computeTopMargin(int height) const; int computeTopMargin(int height) const;
@ -136,9 +145,12 @@ private:
QRect _waveformBgRect; QRect _waveformBgRect;
QRect _waveformBgFinalCenterRect; QRect _waveformBgFinalCenterRect;
QRect _waveformFgRect;
::Media::Player::PlayButtonLayout _playPause; ::Media::Player::PlayButtonLayout _playPause;
anim::value _playProgress;
rpl::variable<float64> _showProgress = 0.; rpl::variable<float64> _showProgress = 0.;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;
@ -253,13 +265,19 @@ void ListenWrap::init() {
// Waveform paint. // Waveform paint.
{ {
const auto &play = _playPauseSt.playOuter; const auto computeRect = [&] {
const auto top = computeTopMargin(st::msgWaveformMax); const auto &play = _playPauseSt.playOuter;
const auto left = play.width() / 2 + halfHeight; const auto top = computeTopMargin(st::msgWaveformMax);
const auto right = st::historyRecordWaveformLeftSkip const auto left = play.width() / 2 + halfHeight;
+ _durationWidth; const auto right = st::historyRecordWaveformLeftSkip
const auto rect = bgCenterRect.marginsRemoved( + _durationWidth;
style::margins(left, top, right, top)); _waveformFgRect = bgCenterRect.marginsRemoved(
style::margins(left, top, right, top));
return _waveformFgRect;
};
const auto rect = (progress == 1.)
? _waveformFgRect
: computeRect();
if (rect.width() > 0) { if (rect.width() > 0) {
p.translate(rect.topLeft()); p.translate(rect.topLeft());
HistoryDocumentVoice::PaintWaveform( HistoryDocumentVoice::PaintWaveform(
@ -268,7 +286,7 @@ void ListenWrap::init() {
rect.width(), rect.width(),
false, false,
false, false,
0.); _playProgress.current());
p.resetTransform(); p.resetTransform();
} }
} }
@ -276,6 +294,7 @@ void ListenWrap::init() {
}, _lifetime); }, _lifetime);
initPlayButton(); initPlayButton();
initPlayProgress();
} }
void ListenWrap::initPlayButton() { void ListenWrap::initPlayButton() {
@ -317,7 +336,7 @@ void ListenWrap::initPlayButton() {
instance()->updatedNotifier( instance()->updatedNotifier(
) | rpl::start_with_next([=](const State &state) { ) | rpl::start_with_next([=](const State &state) {
if (state.id.audio() == _document) { if (isInPlayer(state)) {
*showPause = ShowPauseIcon(state.state); *showPause = ShowPauseIcon(state.state);
} else if (showPause->current()) { } else if (showPause->current()) {
*showPause = false; *showPause = false;
@ -332,13 +351,124 @@ void ListenWrap::initPlayButton() {
const auto weak = Ui::MakeWeak(_controller->content().get()); const auto weak = Ui::MakeWeak(_controller->content().get());
_lifetime.add([=] { _lifetime.add([=] {
const auto voiceState = instance()->getState(AudioMsgId::Type::Voice); if (weak && isInPlayer()) {
if (weak && voiceState.id && (voiceState.id.audio() == _document)) {
weak->stopAndClosePlayer(); 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 { int ListenWrap::computeTopMargin(int height) const {
return (_waveformBgRect.height() - height) / 2; return (_waveformBgRect.height() - height) / 2;
} }
@ -1150,8 +1280,7 @@ void VoiceRecordBar::computeAndSetLockProgress(QPoint globalPos) {
const auto localPos = mapFromGlobal(globalPos); const auto localPos = mapFromGlobal(globalPos);
const auto lower = _lock->height(); const auto lower = _lock->height();
const auto higher = 0; const auto higher = 0;
const auto progress = localPos.y() / (float64)(higher - lower); _lock->requestPaintProgress(Progress(localPos.y(), higher - lower));
_lock->requestPaintProgress(std::clamp(progress, 0., 1.));
} }
void VoiceRecordBar::orderControls() { void VoiceRecordBar::orderControls() {