Alpha 1.0.8: seek in voice messages (by waveform).

This commit is contained in:
John Preston 2017-02-11 01:37:37 +03:00
parent 296c800b39
commit e922e5be39
26 changed files with 347 additions and 125 deletions

View File

@ -9,7 +9,7 @@
<Identity Name="TelegramDesktop" <Identity Name="TelegramDesktop"
ProcessorArchitecture="x64" ProcessorArchitecture="x64"
Publisher="CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB" Publisher="CN=Telegram Messenger LLP, O=Telegram Messenger LLP, L=London, C=GB"
Version="1.0.7.0" /> Version="1.0.8.0" />
<Properties> <Properties>
<DisplayName>Telegram Desktop</DisplayName> <DisplayName>Telegram Desktop</DisplayName>
<PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName> <PublisherDisplayName>Telegram Messenger LLP</PublisherDisplayName>

View File

@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico"
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,7,0 FILEVERSION 1,0,8,0
PRODUCTVERSION 1,0,7,0 PRODUCTVERSION 1,0,8,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -52,10 +52,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop official messenger" VALUE "FileDescription", "Telegram Desktop official messenger"
VALUE "FileVersion", "1.0.7.0" VALUE "FileVersion", "1.0.8.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.0.7.0" VALUE "ProductVersion", "1.0.8.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,7,0 FILEVERSION 1,0,8,0
PRODUCTVERSION 1,0,7,0 PRODUCTVERSION 1,0,8,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -43,10 +43,10 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "Telegram Messenger LLP" VALUE "CompanyName", "Telegram Messenger LLP"
VALUE "FileDescription", "Telegram Desktop Updater" VALUE "FileDescription", "Telegram Desktop Updater"
VALUE "FileVersion", "1.0.7.0" VALUE "FileVersion", "1.0.8.0"
VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "LegalCopyright", "Copyright (C) 2014-2017"
VALUE "ProductName", "Telegram Desktop" VALUE "ProductName", "Telegram Desktop"
VALUE "ProductVersion", "1.0.7.0" VALUE "ProductVersion", "1.0.8.0"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"

View File

@ -1069,8 +1069,8 @@ void AppClass::checkMapVersion() {
if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion() < AppVersion) {
if (Local::oldMapVersion()) { if (Local::oldMapVersion()) {
QString versionFeatures; QString versionFeatures;
if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000007) { if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000008) {
versionFeatures = QString::fromUtf8("\xe2\x80\x94 Added Theme editor to Settings."); versionFeatures = QString::fromUtf8("\xe2\x80\x94 Click and drag on waveform to play audio from a chosen moment.");
} else if (!(cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000005) { } else if (!(cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000005) {
versionFeatures = langNewVersionText(); versionFeatures = langNewVersionText();
} else { } else {

View File

@ -103,8 +103,6 @@ enum {
AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs) AudioVoiceMsgBufferSize = 256 * 1024, // 256 Kb buffers (1.3 - 3.0 secs)
AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded
WaveformSamplesCount = 100,
StickerInMemory = 2 * 1024 * 1024, // 2 Mb stickers hold in memory, auto loaded and displayed inline StickerInMemory = 2 * 1024 * 1024, // 2 Mb stickers hold in memory, auto loaded and displayed inline
StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker

View File

@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org
#define BETA_VERSION_MACRO (0ULL) #define BETA_VERSION_MACRO (0ULL)
constexpr int AppVersion = 1000007; constexpr int AppVersion = 1000008;
constexpr str_const AppVersionStr = "1.0.7"; constexpr str_const AppVersionStr = "1.0.8";
constexpr bool AppAlphaVersion = true; constexpr bool AppAlphaVersion = true;
constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO;

View File

@ -270,7 +270,6 @@ void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool act
} }
} }
ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) { ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) {
for (int i = 0, rows = _rows.size(); i != rows; ++i) { for (int i = 0, rows = _rows.size(); i != rows; ++i) {
auto &row = _rows[i]; auto &row = _rows[i];

View File

@ -409,6 +409,7 @@ struct HistoryMessageUnreadBar : public RuntimeComponent<HistoryMessageUnreadBar
// we've seen the bar and new messages are marked as read // we've seen the bar and new messages are marked as read
// as soon as they are added to the chat history // as soon as they are added to the chat history
bool _freezed = false; bool _freezed = false;
}; };
// HistoryMedia has a special owning smart pointer // HistoryMedia has a special owning smart pointer
@ -616,6 +617,8 @@ public:
} }
virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0;
virtual void updatePressed(int x, int y) {
}
virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const { virtual TextSelection adjustSelection(TextSelection selection, TextSelectType type) const {
return selection; return selection;

View File

@ -66,6 +66,8 @@ public:
} }
virtual void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const = 0; virtual void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const = 0;
virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0; virtual HistoryTextState getState(int x, int y, HistoryStateRequest request) const = 0;
virtual void updatePressed(int x, int y) {
}
// if we are in selecting items mode perhaps we want to // if we are in selecting items mode perhaps we want to
// toggle selection instead of activating the pressed link // toggle selection instead of activating the pressed link

View File

@ -910,17 +910,27 @@ HistoryDocumentVoicePlayback::HistoryDocumentVoicePlayback(const HistoryDocument
void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const { void HistoryDocumentVoice::ensurePlayback(const HistoryDocument *that) const {
if (!_playback) { if (!_playback) {
_playback = new HistoryDocumentVoicePlayback(that); _playback = std_::make_unique<HistoryDocumentVoicePlayback>(that);
} }
} }
void HistoryDocumentVoice::checkPlaybackFinished() const { void HistoryDocumentVoice::checkPlaybackFinished() const {
if (_playback && !_playback->_a_progress.animating()) { if (_playback && !_playback->_a_progress.animating()) {
delete _playback; _playback.reset();
_playback = nullptr;
} }
} }
void HistoryDocumentVoice::startSeeking() {
_seeking = true;
_seekingCurrent = _seekingStart;
Media::Player::instance()->startSeeking(AudioMsgId::Type::Voice);
}
void HistoryDocumentVoice::stopSeeking() {
_seeking = false;
Media::Player::instance()->stopSeeking(AudioMsgId::Type::Voice);
}
HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent) HistoryDocument::HistoryDocument(HistoryItem *parent, DocumentData *document, const QString &caption) : HistoryFileMedia(parent)
, _data(document) { , _data(document) {
createComponents(!caption.isEmpty()); createComponents(!caption.isEmpty());
@ -979,8 +989,11 @@ void HistoryDocument::createComponents(bool caption) {
} }
UpdateComponents(mask); UpdateComponents(mask);
if (auto thumbed = Get<HistoryDocumentThumbed>()) { if (auto thumbed = Get<HistoryDocumentThumbed>()) {
thumbed->_linksavel.reset(new DocumentSaveClickHandler(_data)); thumbed->_linksavel = MakeShared<DocumentSaveClickHandler>(_data);
thumbed->_linkcancell.reset(new DocumentCancelClickHandler(_data)); thumbed->_linkcancell = MakeShared<DocumentCancelClickHandler>(_data);
}
if (auto voice = Get<HistoryDocumentVoice>()) {
voice->_seekl = MakeShared<VoiceSeekClickHandler>(_data);
} }
} }
@ -1211,6 +1224,7 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection,
auto namewidth = _width - nameleft - nameright; auto namewidth = _width - nameleft - nameright;
auto statuswidth = namewidth; auto statuswidth = namewidth;
auto voiceStatusOverride = QString();
if (auto voice = Get<HistoryDocumentVoice>()) { if (auto voice = Get<HistoryDocumentVoice>()) {
const VoiceWaveform *wf = nullptr; const VoiceWaveform *wf = nullptr;
uchar norm_value = 0; uchar norm_value = 0;
@ -1227,28 +1241,40 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection,
norm_value = _data->voice()->wavemax; norm_value = _data->voice()->wavemax;
} }
} }
auto prg = voice->_playback ? voice->_playback->a_progress.current() : 0.; auto progress = ([voice] {
if (voice->seeking()) {
return voice->seekingCurrent();
} else if (voice->_playback) {
return voice->_playback->a_progress.current();
}
return 0.;
})();
if (voice->seeking()) {
voiceStatusOverride = formatPlayedText(qRound(progress * voice->_lastDurationMs) / 1000, voice->_lastDurationMs / 1000);
}
// rescale waveform by going in waveform.size * bar_count 1D grid // rescale waveform by going in waveform.size * bar_count 1D grid
auto &active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive); auto active = outbg ? (selected ? st::msgWaveformOutActiveSelected : st::msgWaveformOutActive) : (selected ? st::msgWaveformInActiveSelected : st::msgWaveformInActive);
auto &inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive); auto inactive = outbg ? (selected ? st::msgWaveformOutInactiveSelected : st::msgWaveformOutInactive) : (selected ? st::msgWaveformInInactiveSelected : st::msgWaveformInInactive);
int32 wf_size = wf ? wf->size() : WaveformSamplesCount, availw = int32(namewidth + st::msgWaveformSkip), activew = qRound(availw * prg); auto wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount;
auto availw = namewidth + st::msgWaveformSkip;
auto activew = qRound(availw * progress);
if (!outbg && !voice->_playback && _parent->isMediaUnread()) { if (!outbg && !voice->_playback && _parent->isMediaUnread()) {
activew = availw; activew = availw;
} }
int32 bar_count = qMin(availw / int32(st::msgWaveformBar + st::msgWaveformSkip), wf_size); auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size);
uchar max_value = 0; auto max_value = 0;
auto max_delta = st::msgWaveformMax - st::msgWaveformMin; auto max_delta = st::msgWaveformMax - st::msgWaveformMin;
auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax; auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax;
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
for (int32 i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) {
uchar value = wf ? wf->at(i) : 0; auto value = wf ? wf->at(i) : 0;
if (sum_i + bar_count >= wf_size) { // draw bar if (sum_i + bar_count >= wf_size) { // draw bar
sum_i = sum_i + bar_count - wf_size; sum_i = sum_i + bar_count - wf_size;
if (sum_i < (bar_count + 1) / 2) { if (sum_i < (bar_count + 1) / 2) {
if (max_value < value) max_value = value; if (max_value < value) max_value = value;
} }
int32 bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1); auto bar_value = ((max_value * max_delta) + ((norm_value + 1) / 2)) / (norm_value + 1);
if (bar_x >= activew) { if (bar_x >= activew) {
p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive); p.fillRect(nameleft + bar_x, bottom - bar_value, st::msgWaveformBar, st::msgWaveformMin + bar_value, inactive);
@ -1281,13 +1307,14 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection,
} }
} }
auto &status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg); auto statusText = voiceStatusOverride.isEmpty() ? _statusText : voiceStatusOverride;
auto status = outbg ? (selected ? st::mediaOutFgSelected : st::mediaOutFg) : (selected ? st::mediaInFgSelected : st::mediaInFg);
p.setFont(st::normalFont); p.setFont(st::normalFont);
p.setPen(status); p.setPen(status);
p.drawTextLeft(nameleft, statustop, _width, _statusText); p.drawTextLeft(nameleft, statustop, _width, statusText);
if (_parent->isMediaUnread()) { if (_parent->isMediaUnread()) {
int32 w = st::normalFont->width(_statusText); auto w = st::normalFont->width(statusText);
if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) { if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) {
p.setPen(Qt::NoPen); p.setPen(Qt::NoPen);
p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg)); p.setBrush(outbg ? (selected ? st::msgFileOutBgSelected : st::msgFileOutBg) : (selected ? st::msgFileInBgSelected : st::msgFileInBg));
@ -1319,6 +1346,8 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus; auto topMinus = isBubbleTop() ? 0 : st::msgFileTopMinus;
if (auto thumbed = Get<HistoryDocumentThumbed>()) { if (auto thumbed = Get<HistoryDocumentThumbed>()) {
nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
nameright = st::msgFileThumbPadding.left();
nametop = st::msgFileThumbNameTop - topMinus;
linktop = st::msgFileThumbLinkTop - topMinus; linktop = st::msgFileThumbLinkTop - topMinus;
bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus; bottom = st::msgFileThumbPadding.top() + st::msgFileThumbSize + st::msgFileThumbPadding.bottom() - topMinus;
@ -1336,6 +1365,9 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
} }
} }
} else { } else {
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
nameright = st::msgFilePadding.left();
nametop = st::msgFileNameTop - topMinus;
bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus; bottom = st::msgFilePadding.top() + st::msgFileSize + st::msgFilePadding.bottom() - topMinus;
QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, _width)); QRect inner(rtlrect(st::msgFilePadding.left(), st::msgFilePadding.top() - topMinus, st::msgFileSize, st::msgFileSize, _width));
@ -1345,6 +1377,21 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
} }
} }
if (auto voice = Get<HistoryDocumentVoice>()) {
auto namewidth = _width - nameleft - nameright;
auto waveformbottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax + st::msgWaveformMin;
if (x >= nameleft && x < nameleft + namewidth && y >= nametop && y < waveformbottom) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state)) {
if (!voice->seeking()) {
voice->setSeekingStart((x - nameleft) / float64(namewidth));
}
result.link = voice->_seekl;
return result;
}
}
}
int32 height = _height; int32 height = _height;
if (auto captioned = Get<HistoryDocumentCaptioned>()) { if (auto captioned = Get<HistoryDocumentCaptioned>()) {
if (y >= bottom) { if (y >= bottom) {
@ -1364,6 +1411,23 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req
return result; return result;
} }
void HistoryDocument::updatePressed(int x, int y) {
if (auto voice = Get<HistoryDocumentVoice>()) {
if (voice->seeking()) {
auto nameleft = 0, nameright = 0;
if (auto thumbed = Get<HistoryDocumentThumbed>()) {
nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right();
nameright = st::msgFileThumbPadding.left();
} else {
nameleft = st::msgFilePadding.left() + st::msgFileSize + st::msgFilePadding.right();
nameright = st::msgFilePadding.left();
}
voice->setSeekingCurrent(snap((x - nameleft) / float64(_width - nameleft - nameright), 0., 1.));
Ui::repaintHistoryItem(_parent);
}
}
}
QString HistoryDocument::notificationText() const { QString HistoryDocument::notificationText() const {
QString result; QString result;
buildStringRepresentation([&result](const QString &type, const QString &fileName, const Text &caption) { buildStringRepresentation([&result](const QString &type, const QString &fileName, const Text &caption) {
@ -1450,7 +1514,7 @@ bool HistoryDocument::updateStatusText() const {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Voice);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
if (auto voice = Get<HistoryDocumentVoice>()) { if (auto voice = Get<HistoryDocumentVoice>()) {
bool was = voice->_playback; bool was = (voice->_playback != nullptr);
voice->ensurePlayback(this); voice->ensurePlayback(this);
if (!was || state.position != voice->_playback->_position) { if (!was || state.position != voice->_playback->_position) {
float64 prg = state.duration ? snap(float64(state.position) / state.duration, 0., 1.) : 0.; float64 prg = state.duration ? snap(float64(state.position) / state.duration, 0., 1.) : 0.;
@ -1462,6 +1526,7 @@ bool HistoryDocument::updateStatusText() const {
voice->_playback->_position = state.position; voice->_playback->_position = state.position;
voice->_playback->_a_progress.start(); voice->_playback->_a_progress.start();
} }
voice->_lastDurationMs = static_cast<int>((state.duration * 1000LL) / state.frequency); // Bad :(
} }
statusSize = -1 - (state.position / state.frequency); statusSize = -1 - (state.position / state.frequency);
@ -1472,6 +1537,9 @@ bool HistoryDocument::updateStatusText() const {
voice->checkPlaybackFinished(); voice->checkPlaybackFinished();
} }
} }
if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId()))) {
showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice);
}
} else if (_data->song()) { } else if (_data->song()) {
auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song); auto state = Media::Player::mixer()->currentState(AudioMsgId::Type::Song);
if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) {
@ -1481,7 +1549,7 @@ bool HistoryDocument::updateStatusText() const {
} else { } else {
} }
if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId()))) { if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId()))) {
showPause = Media::Player::instance()->isSeeking(); showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song);
} }
} }
} else { } else {
@ -1512,6 +1580,28 @@ void HistoryDocument::step_voiceProgress(float64 ms, bool timer) {
} }
} }
void HistoryDocument::clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) {
if (auto voice = Get<HistoryDocumentVoice>()) {
if (pressed && p == voice->_seekl && !voice->seeking()) {
voice->startSeeking();
} else if (!pressed && voice->seeking()) {
auto type = AudioMsgId::Type::Voice;
auto state = Media::Player::mixer()->currentState(type);
if (state.id == AudioMsgId(_data, _parent->fullId()) && state.duration) {
auto currentProgress = voice->seekingCurrent();
auto currentPosition = qRound(currentProgress * state.duration);
Media::Player::mixer()->seek(type, currentPosition);
voice->ensurePlayback(this);
voice->_playback->_position = 0;
voice->_playback->a_progress = anim::value(currentProgress, currentProgress);
}
voice->stopSeeking();
}
}
HistoryFileMedia::clickHandlerPressedChanged(p, pressed);
}
void HistoryDocument::attachToParent() { void HistoryDocument::attachToParent() {
App::regDocumentItem(_data, _parent); App::regDocumentItem(_data, _parent);
} }
@ -2455,7 +2545,9 @@ void HistoryWebPage::initDimensions() {
} }
if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height); if (!_lineHeight) _lineHeight = qMax(st::webPageTitleFont->height, st::webPageDescriptionFont->height);
if (!_openl && !_data->url.isEmpty()) _openl.reset(new UrlClickHandler(_data->url, true)); if (!_openl && !_data->url.isEmpty()) {
_openl = MakeShared<UrlClickHandler>(_data->url, true);
}
// init layout // init layout
QString title(_data->title.isEmpty() ? _data->author : _data->title); QString title(_data->title.isEmpty() ? _data->author : _data->title);

View File

@ -305,17 +305,42 @@ struct HistoryDocumentVoicePlayback {
anim::value a_progress; anim::value a_progress;
BasicAnimation _a_progress; BasicAnimation _a_progress;
}; };
struct HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice> { class HistoryDocumentVoice : public RuntimeComponent<HistoryDocumentVoice> {
HistoryDocumentVoice &operator=(HistoryDocumentVoice &&other) { // We don't use float64 because components should align to pointer even on 32bit systems.
std::swap(_playback, other._playback); static constexpr float64 kFloatToIntMultiplier = 65536.;
return *this;
} public:
~HistoryDocumentVoice() {
delete base::take(_playback);
}
void ensurePlayback(const HistoryDocument *interfaces) const; void ensurePlayback(const HistoryDocument *interfaces) const;
void checkPlaybackFinished() const; void checkPlaybackFinished() const;
mutable HistoryDocumentVoicePlayback *_playback = nullptr;
mutable std_::unique_ptr<HistoryDocumentVoicePlayback> _playback;
QSharedPointer<VoiceSeekClickHandler> _seekl;
mutable int _lastDurationMs = 0;
bool seeking() const {
return _seeking;
}
void startSeeking();
void stopSeeking();
float64 seekingStart() const {
return _seekingStart / kFloatToIntMultiplier;
}
void setSeekingStart(float64 seekingStart) const {
_seekingStart = qRound(seekingStart * kFloatToIntMultiplier);
}
float64 seekingCurrent() const {
return _seekingCurrent / kFloatToIntMultiplier;
}
void setSeekingCurrent(float64 seekingCurrent) {
_seekingCurrent = qRound(seekingCurrent * kFloatToIntMultiplier);
}
private:
bool _seeking = false;
mutable int _seekingStart = 0;
mutable int _seekingCurrent = 0;
}; };
class HistoryDocument : public HistoryFileMedia, public RuntimeComposer { class HistoryDocument : public HistoryFileMedia, public RuntimeComposer {
@ -334,6 +359,7 @@ public:
void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override; void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) const override;
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
void updatePressed(int x, int y) override;
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override { TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override {
if (auto captioned = Get<HistoryDocumentCaptioned>()) { if (auto captioned = Get<HistoryDocumentCaptioned>()) {
@ -387,6 +413,8 @@ public:
void step_voiceProgress(float64 ms, bool timer); void step_voiceProgress(float64 ms, bool timer);
void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override;
protected: protected:
float64 dataProgress() const override { float64 dataProgress() const override {
return _data->progress(); return _data->progress();

View File

@ -1671,6 +1671,60 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ
return result; return result;
} }
// Forward to _media.
void HistoryMessage::updatePressed(int x, int y) {
if (!_media) return;
auto left = 0, width = 0, height = _height;
countPositionAndSize(left, width);
auto keyboard = inlineReplyKeyboard();
if (keyboard) {
auto h = st::msgBotKbButton.margin + keyboard->naturalHeight();
height -= h;
}
if (drawBubble()) {
auto mediaDisplayed = _media && _media->isDisplayed();
auto top = marginTop();
QRect r(left, top, width, height - top - marginBottom());
QRect trect(r.marginsAdded(-st::msgPadding));
if (mediaDisplayed && _media->isBubbleTop()) {
trect.setY(trect.y() - st::msgPadding.top());
} else {
if (displayFromName()) trect.setTop(trect.top() + st::msgNameFont->height);
if (displayForwardedFrom()) {
auto fwd = Get<HistoryMessageForwarded>();
auto fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height;
trect.setTop(trect.top() + fwdheight);
}
if (Get<HistoryMessageReply>()) {
auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom();
trect.setTop(trect.top() + h);
}
if (!displayFromName() && !Has<HistoryMessageForwarded>()) {
if (auto via = Get<HistoryMessageVia>()) {
trect.setTop(trect.top() + st::msgNameFont->height);
}
}
}
if (mediaDisplayed && _media->isBubbleBottom()) {
trect.setHeight(trect.height() + st::msgPadding.bottom());
}
auto needDateCheck = true;
if (mediaDisplayed) {
auto mediaAboveText = _media->isAboveMessage();
auto mediaHeight = _media->height();
auto mediaLeft = trect.x() - st::msgPadding.left();
auto mediaTop = mediaAboveText ? trect.y() : (trect.y() + trect.height() - mediaHeight);
_media->updatePressed(x - mediaLeft, y - mediaTop);
}
} else {
_media->updatePressed(x - left, y - marginTop());
}
}
bool HistoryMessage::getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const { bool HistoryMessage::getStateFromName(int x, int y, QRect &trect, HistoryTextState *outResult) const {
if (displayFromName()) { if (displayFromName()) {
if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) {

View File

@ -78,6 +78,7 @@ public:
bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override; bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) const override;
HistoryTextState getState(int x, int y, HistoryStateRequest request) const override; HistoryTextState getState(int x, int y, HistoryStateRequest request) const override;
void updatePressed(int x, int y) override;
TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override; TextSelection adjustSelection(TextSelection selection, TextSelectType type) const override;

View File

@ -990,6 +990,11 @@ void HistoryInner::onDragExec() {
} }
} }
auto pressedHandler = ClickHandler::getPressed(); auto pressedHandler = ClickHandler::getPressed();
if (dynamic_cast<VoiceSeekClickHandler*>(pressedHandler.data())) {
return;
}
TextWithEntities sel; TextWithEntities sel;
QList<QUrl> urls; QList<QUrl> urls;
if (uponSelected) { if (uponSelected) {
@ -2081,7 +2086,7 @@ void HistoryInner::onUpdateSelected() {
} }
} }
} }
bool lnkChanged = ClickHandler::setActive(dragState.link, lnkhost); auto lnkChanged = ClickHandler::setActive(dragState.link, lnkhost);
if (lnkChanged || dragState.cursor != _dragCursorState) { if (lnkChanged || dragState.cursor != _dragCursorState) {
Ui::Tooltip::Hide(); Ui::Tooltip::Hide();
} }
@ -2101,7 +2106,7 @@ void HistoryInner::onUpdateSelected() {
} }
} else if (item) { } else if (item) {
if (_dragAction == Selecting) { if (_dragAction == Selecting) {
bool canSelectMany = (_history != nullptr); auto canSelectMany = (_history != nullptr);
if (selectingText) { if (selectingText) {
uint16 second = dragState.symbol; uint16 second = dragState.symbol;
if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) { if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) {
@ -2118,8 +2123,8 @@ void HistoryInner::onUpdateSelected() {
} }
updateDragSelection(0, 0, false); updateDragSelection(0, 0, false);
} else if (canSelectMany) { } else if (canSelectMany) {
bool selectingDown = (itemTop(_dragItem) < itemTop(item)) || (_dragItem == item && _dragStartPos.y() < m.y()); auto selectingDown = (itemTop(_dragItem) < itemTop(item)) || (_dragItem == item && _dragStartPos.y() < m.y());
HistoryItem *dragSelFrom = _dragItem, *dragSelTo = item; auto dragSelFrom = _dragItem, dragSelTo = item;
if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom
if (selectingDown) { if (selectingDown) {
if (_dragStartPos.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) { if (_dragStartPos.y() >= dragSelFrom->height() - dragSelFrom->marginBottom() || ((item == dragSelFrom) && (m.y() < _dragStartPos.y() + QApplication::startDragDistance() || m.y() < dragSelFrom->marginTop()))) {
@ -2142,13 +2147,13 @@ void HistoryInner::onUpdateSelected() {
} }
} }
} }
bool dragSelecting = false; auto dragSelecting = false;
HistoryItem *dragFirstAffected = dragSelFrom; auto dragFirstAffected = dragSelFrom;
while (dragFirstAffected && (dragFirstAffected->id < 0 || dragFirstAffected->serviceMsg())) { while (dragFirstAffected && (dragFirstAffected->id < 0 || dragFirstAffected->serviceMsg())) {
dragFirstAffected = (dragFirstAffected == dragSelTo) ? 0 : (selectingDown ? nextItem(dragFirstAffected) : prevItem(dragFirstAffected)); dragFirstAffected = (dragFirstAffected == dragSelTo) ? 0 : (selectingDown ? nextItem(dragFirstAffected) : prevItem(dragFirstAffected));
} }
if (dragFirstAffected) { if (dragFirstAffected) {
SelectedItems::const_iterator i = _selected.constFind(dragFirstAffected); auto i = _selected.constFind(dragFirstAffected);
dragSelecting = (i == _selected.cend() || i.value() != FullSelection); dragSelecting = (i == _selected.cend() || i.value() != FullSelection);
} }
updateDragSelection(dragSelFrom, dragSelTo, dragSelecting); updateDragSelection(dragSelFrom, dragSelTo, dragSelecting);
@ -2164,6 +2169,17 @@ void HistoryInner::onUpdateSelected() {
} }
} }
} }
// Voice message seek support.
if (auto pressedItem = App::pressedLinkItem()) {
if (!pressedItem->detached()) {
if (pressedItem->history() == _history || pressedItem->history() == _migrated) {
auto adjustedPoint = mapMouseToItem(point, pressedItem);
pressedItem->updatePressed(adjustedPoint.x(), adjustedPoint.y());
}
}
}
if (_dragAction == Selecting) { if (_dragAction == Selecting) {
_widget->checkSelectingScroll(mousePos); _widget->checkSelectingScroll(mousePos);
} else { } else {

View File

@ -847,7 +847,7 @@ bool File::updateStatusText() const {
realDuration = (state.duration / state.frequency); realDuration = (state.duration / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
} }
if (!showPause && (state.id == AudioMsgId(document, FullMsgId())) && Media::Player::instance()->isSeeking()) { if (!showPause && (state.id == AudioMsgId(document, FullMsgId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true; showPause = true;
} }
} else { } else {

View File

@ -1686,12 +1686,12 @@ public:
QByteArray buffer; QByteArray buffer;
buffer.reserve(AudioVoiceMsgBufferSize); buffer.reserve(AudioVoiceMsgBufferSize);
int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0; int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0;
if (duration() < WaveformSamplesCount) { if (duration() < Media::Player::kWaveformSamplesCount) {
return false; return false;
} }
QVector<uint16> peaks; QVector<uint16> peaks;
peaks.reserve(WaveformSamplesCount); peaks.reserve(Media::Player::kWaveformSamplesCount);
int32 fmt = format(); int32 fmt = format();
uint16 peak = 0; uint16 peak = 0;
@ -1716,7 +1716,7 @@ public:
} }
i += sizeof(uchar); i += sizeof(uchar);
sumbytes += WaveformSamplesCount; sumbytes += Media::Player::kWaveformSamplesCount;
if (sumbytes >= countbytes) { if (sumbytes >= countbytes) {
sumbytes -= countbytes; sumbytes -= countbytes;
peaks.push_back(peak); peaks.push_back(peak);
@ -1731,7 +1731,7 @@ public:
} }
i += sizeof(uint16); i += sizeof(uint16);
sumbytes += sizeof(uint16) * WaveformSamplesCount; sumbytes += sizeof(uint16) * Media::Player::kWaveformSamplesCount;
if (sumbytes >= countbytes) { if (sumbytes >= countbytes) {
sumbytes -= countbytes; sumbytes -= countbytes;
peaks.push_back(peak); peaks.push_back(peak);
@ -1741,7 +1741,7 @@ public:
} }
processed += sampleSize * samples; processed += sampleSize * samples;
} }
if (sumbytes > 0 && peaks.size() < WaveformSamplesCount) { if (sumbytes > 0 && peaks.size() < Media::Player::kWaveformSamplesCount) {
peaks.push_back(peak); peaks.push_back(peak);
} }

View File

@ -28,6 +28,7 @@ namespace Player {
constexpr auto kDefaultFrequency = 48000; // 48 kHz constexpr auto kDefaultFrequency = 48000; // 48 kHz
constexpr auto kTogetherLimit = 4; constexpr auto kTogetherLimit = 4;
constexpr auto kWaveformSamplesCount = 100;
class Fader; class Fader;
class Loaders; class Loaders;

View File

@ -396,9 +396,9 @@ void Instance::Inner::onStop(bool needResult) {
qint32 samples = d->fullSamples; qint32 samples = d->fullSamples;
if (samples && !d->waveform.isEmpty()) { if (samples && !d->waveform.isEmpty()) {
int64 count = d->waveform.size(), sum = 0; int64 count = d->waveform.size(), sum = 0;
if (count >= WaveformSamplesCount) { if (count >= Player::kWaveformSamplesCount) {
QVector<uint16> peaks; QVector<uint16> peaks;
peaks.reserve(WaveformSamplesCount); peaks.reserve(Player::kWaveformSamplesCount);
uint16 peak = 0; uint16 peak = 0;
for (int32 i = 0; i < count; ++i) { for (int32 i = 0; i < count; ++i) {
@ -406,7 +406,7 @@ void Instance::Inner::onStop(bool needResult) {
if (peak < sample) { if (peak < sample) {
peak = sample; peak = sample;
} }
sum += WaveformSamplesCount; sum += Player::kWaveformSamplesCount;
if (sum >= count) { if (sum >= count) {
sum -= count; sum -= count;
peaks.push_back(peak); peaks.push_back(peak);

View File

@ -141,7 +141,7 @@ void CoverWidget::handleSeekProgress(float64 progress) {
if (_seekPositionMs != positionMs) { if (_seekPositionMs != positionMs) {
_seekPositionMs = positionMs; _seekPositionMs = positionMs;
updateTimeLabel(); updateTimeLabel();
instance()->startSeeking(); instance()->startSeeking(AudioMsgId::Type::Song);
} }
} }
@ -157,7 +157,7 @@ void CoverWidget::handleSeekFinished(float64 progress) {
Media::Player::mixer()->seek(type, qRound(progress * state.duration)); Media::Player::mixer()->seek(type, qRound(progress * state.duration));
} }
instance()->stopSeeking(); instance()->stopSeeking(type);
} }
void CoverWidget::resizeEvent(QResizeEvent *e) { void CoverWidget::resizeEvent(QResizeEvent *e) {
@ -239,7 +239,7 @@ void CoverWidget::handleSongUpdate(const TrackState &state) {
auto stopped = (IsStopped(state.state) || state.state == State::Finishing); auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
if (instance()->isSeeking()) { if (instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true; showPause = true;
} }
auto buttonState = [audio = state.id.audio(), showPause] { auto buttonState = [audio = state.id.audio(), showPause] {

View File

@ -49,9 +49,7 @@ void finish() {
Instance::Instance() { Instance::Instance() {
subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) { subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) {
if (audioId.type() == AudioMsgId::Type::Song) {
handleSongUpdate(audioId); handleSongUpdate(audioId);
}
}); });
auto observeEvents = Notify::PeerUpdate::Flag::SharedMediaChanged; auto observeEvents = Notify::PeerUpdate::Flag::SharedMediaChanged;
subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) {
@ -79,12 +77,13 @@ void Instance::notifyPeerUpdated(const Notify::PeerUpdate &update) {
} }
void Instance::handleSongUpdate(const AudioMsgId &audioId) { void Instance::handleSongUpdate(const AudioMsgId &audioId) {
emitUpdate([&audioId](const AudioMsgId &playing) { emitUpdate(audioId.type(), [&audioId](const AudioMsgId &playing) {
return (audioId == playing); return (audioId == playing);
}); });
} }
void Instance::setCurrent(const AudioMsgId &audioId) { void Instance::setCurrent(const AudioMsgId &audioId) {
if (audioId.type() == AudioMsgId::Type::Song) {
if (_current != audioId) { if (_current != audioId) {
_current = audioId; _current = audioId;
_isPlaying = false; _isPlaying = false;
@ -102,6 +101,11 @@ void Instance::setCurrent(const AudioMsgId &audioId) {
rebuildPlaylist(); rebuildPlaylist();
} }
} }
} else if (audioId.type() == AudioMsgId::Type::Voice) {
if (_currentVoice != audioId) {
_currentVoice = audioId;
}
}
} }
void Instance::rebuildPlaylist() { void Instance::rebuildPlaylist() {
@ -173,12 +177,12 @@ void Instance::play(const AudioMsgId &audioId) {
} }
} }
void Instance::pause() { void Instance::pause(AudioMsgId::Type type) {
auto state = mixer()->currentState(AudioMsgId::Type::Song); auto state = mixer()->currentState(type);
if (state.id) { if (state.id) {
if (!IsStopped(state.state)) { if (!IsStopped(state.state)) {
if (state.state == State::Starting || state.state == State::Resuming || state.state == State::Playing || state.state == State::Finishing) { if (state.state == State::Starting || state.state == State::Resuming || state.state == State::Playing || state.state == State::Finishing) {
mixer()->pauseresume(AudioMsgId::Type::Song); mixer()->pauseresume(type);
} }
} }
} }
@ -210,7 +214,7 @@ void Instance::previous() {
} }
void Instance::playPauseCancelClicked() { void Instance::playPauseCancelClicked() {
if (isSeeking()) { if (isSeeking(AudioMsgId::Type::Song)) {
return; return;
} }
@ -221,32 +225,40 @@ void Instance::playPauseCancelClicked() {
if (audio && audio->loading()) { if (audio && audio->loading()) {
audio->cancel(); audio->cancel();
} else if (showPause) { } else if (showPause) {
pause(); pause(AudioMsgId::Type::Song);
} else { } else {
play(); play();
} }
} }
void Instance::startSeeking() { void Instance::startSeeking(AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Song) {
_seeking = _current; _seeking = _current;
pause(); } else if (type == AudioMsgId::Type::Voice) {
emitUpdate([](const AudioMsgId &playing) { return true; }); _seekingVoice = _currentVoice;
}
pause(type);
emitUpdate(type, [](const AudioMsgId &playing) { return true; });
} }
void Instance::stopSeeking() { void Instance::stopSeeking(AudioMsgId::Type type) {
if (type == AudioMsgId::Type::Song) {
_seeking = AudioMsgId(); _seeking = AudioMsgId();
emitUpdate([](const AudioMsgId &playing) { return true; }); } else if (type == AudioMsgId::Type::Voice) {
_seekingVoice = AudioMsgId();
}
emitUpdate(type, [](const AudioMsgId &playing) { return true; });
} }
void Instance::documentLoadProgress(DocumentData *document) { void Instance::documentLoadProgress(DocumentData *document) {
emitUpdate([document](const AudioMsgId &audioId) { emitUpdate(document->song() ? AudioMsgId::Type::Song : AudioMsgId::Type::Voice, [document](const AudioMsgId &audioId) {
return (audioId.audio() == document); return (audioId.audio() == document);
}); });
} }
template <typename CheckCallback> template <typename CheckCallback>
void Instance::emitUpdate(CheckCallback check) { void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
auto state = mixer()->currentState(AudioMsgId::Type::Song); auto state = mixer()->currentState(type);
if (!state.id || !check(state.id)) { if (!state.id || !check(state.id)) {
return; return;
} }
@ -254,6 +266,7 @@ void Instance::emitUpdate(CheckCallback check) {
setCurrent(state.id); setCurrent(state.id);
_updatedNotifier.notify(state, true); _updatedNotifier.notify(state, true);
if (type == AudioMsgId::Type::Song) {
if (_isPlaying && state.state == State::StoppedAtEnd) { if (_isPlaying && state.state == State::StoppedAtEnd) {
if (_repeatEnabled) { if (_repeatEnabled) {
mixer()->play(_current); mixer()->play(_current);
@ -269,6 +282,7 @@ void Instance::emitUpdate(CheckCallback check) {
} }
} }
} }
}
void Instance::preloadNext() { void Instance::preloadNext() {
if (!_current) { if (!_current) {

View File

@ -39,7 +39,7 @@ struct TrackState;
class Instance : private base::Subscriber { class Instance : private base::Subscriber {
public: public:
void play(); void play();
void pause(); void pause(AudioMsgId::Type type);
void stop(); void stop();
void playPause(); void playPause();
void next(); void next();
@ -60,11 +60,16 @@ public:
_repeatChangedNotifier.notify(); _repeatChangedNotifier.notify();
} }
bool isSeeking() const { bool isSeeking(AudioMsgId::Type type) const {
if (type == AudioMsgId::Type::Song) {
return (_seeking == _current); return (_seeking == _current);
} else if (type == AudioMsgId::Type::Voice) {
return (_seekingVoice == _currentVoice);
} }
void startSeeking(); return false;
void stopSeeking(); }
void startSeeking(AudioMsgId::Type type);
void stopSeeking(AudioMsgId::Type type);
const QList<FullMsgId> &playlist() const { const QList<FullMsgId> &playlist() const {
return _playlist; return _playlist;
@ -111,7 +116,7 @@ private:
void handleLogout(); void handleLogout();
template <typename CheckCallback> template <typename CheckCallback>
void emitUpdate(CheckCallback check); void emitUpdate(AudioMsgId::Type type, CheckCallback check);
AudioMsgId _current, _seeking; AudioMsgId _current, _seeking;
History *_history = nullptr; History *_history = nullptr;
@ -122,6 +127,8 @@ private:
QList<FullMsgId> _playlist; QList<FullMsgId> _playlist;
bool _isPlaying = false; bool _isPlaying = false;
AudioMsgId _currentVoice, _seekingVoice;
base::Observable<bool> _usePanelPlayer; base::Observable<bool> _usePanelPlayer;
base::Observable<bool> _titleButtonOver; base::Observable<bool> _titleButtonOver;
base::Observable<bool> _playerWidgetOver; base::Observable<bool> _playerWidgetOver;

View File

@ -195,7 +195,7 @@ void Widget::handleSeekProgress(float64 progress) {
_seekPositionMs = positionMs; _seekPositionMs = positionMs;
updateTimeLabel(); updateTimeLabel();
instance()->startSeeking(); instance()->startSeeking(AudioMsgId::Type::Song);
} }
} }
@ -211,7 +211,7 @@ void Widget::handleSeekFinished(float64 progress) {
mixer()->seek(type, qRound(progress * state.duration)); mixer()->seek(type, qRound(progress * state.duration));
} }
instance()->stopSeeking(); instance()->stopSeeking(AudioMsgId::Type::Song);
} }
void Widget::resizeEvent(QResizeEvent *e) { void Widget::resizeEvent(QResizeEvent *e) {
@ -312,7 +312,7 @@ void Widget::handleSongUpdate(const TrackState &state) {
auto stopped = (IsStopped(state.state) || state.state == State::Finishing); auto stopped = (IsStopped(state.state) || state.state == State::Finishing);
auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); auto showPause = !stopped && (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
if (instance()->isSeeking()) { if (instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true; showPause = true;
} }
auto buttonState = [audio = state.id.audio(), showPause] { auto buttonState = [audio = state.id.audio(), showPause] {

View File

@ -940,7 +940,7 @@ bool Document::updateStatusText() {
realDuration = (state.duration / state.frequency); realDuration = (state.duration / state.frequency);
showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting); showPause = (state.state == State::Playing || state.state == State::Resuming || state.state == State::Starting);
} }
if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId())) && Media::Player::instance()->isSeeking()) { if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId())) && Media::Player::instance()->isSeeking(AudioMsgId::Type::Song)) {
showPause = true; showPause = true;
} }
} else { } else {

View File

@ -84,7 +84,7 @@ bool media_play() {
} }
bool media_pause() { bool media_pause() {
Media::Player::instance()->pause(); Media::Player::instance()->pause(AudioMsgId::Type::Song);
return true; return true;
} }

View File

@ -1076,12 +1076,11 @@ struct SongData : public DocumentAdditionalData {
typedef QVector<char> VoiceWaveform; // [0] == -1 -- counting, [0] == -2 -- could not count typedef QVector<char> VoiceWaveform; // [0] == -1 -- counting, [0] == -2 -- could not count
struct VoiceData : public DocumentAdditionalData { struct VoiceData : public DocumentAdditionalData {
VoiceData() : duration(0), wavemax(0) {
}
~VoiceData(); ~VoiceData();
int32 duration;
int duration = 0;
VoiceWaveform waveform; VoiceWaveform waveform;
char wavemax; char wavemax = 0;
}; };
bool fileIsImage(const QString &name, const QString &mime); bool fileIsImage(const QString &name, const QString &mime);
@ -1362,6 +1361,14 @@ protected:
void onClickImpl() const override; void onClickImpl() const override;
}; };
class VoiceSeekClickHandler : public DocumentOpenClickHandler {
public:
using DocumentOpenClickHandler::DocumentOpenClickHandler;
protected:
void onClickImpl() const override {
}
};
class DocumentCancelClickHandler : public DocumentClickHandler { class DocumentCancelClickHandler : public DocumentClickHandler {
public: public:
using DocumentClickHandler::DocumentClickHandler; using DocumentClickHandler::DocumentClickHandler;

View File

@ -1,6 +1,6 @@
AppVersion 1000007 AppVersion 1000008
AppVersionStrMajor 1.0 AppVersionStrMajor 1.0
AppVersionStrSmall 1.0.7 AppVersionStrSmall 1.0.8
AppVersionStr 1.0.7 AppVersionStr 1.0.8
AlphaChannel 1 AlphaChannel 1
BetaVersion 0 BetaVersion 0