From e922e5be39b790ee44cf51e2ed1e90e9687ba3cc Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 11 Feb 2017 01:37:37 +0300 Subject: [PATCH] Alpha 1.0.8: seek in voice messages (by waveform). --- Telegram/Resources/uwp/AppX/AppxManifest.xml | 2 +- Telegram/Resources/winrc/Telegram.rc | 8 +- Telegram/Resources/winrc/Updater.rc | 8 +- Telegram/SourceFiles/application.cpp | 4 +- Telegram/SourceFiles/config.h | 2 - Telegram/SourceFiles/core/version.h | 4 +- Telegram/SourceFiles/history/history_item.cpp | 1 - Telegram/SourceFiles/history/history_item.h | 3 + Telegram/SourceFiles/history/history_media.h | 2 + .../history/history_media_types.cpp | 132 +++++++++++++++--- .../SourceFiles/history/history_media_types.h | 46 ++++-- .../SourceFiles/history/history_message.cpp | 54 +++++++ .../SourceFiles/history/history_message.h | 1 + Telegram/SourceFiles/historywidget.cpp | 30 +++- .../inline_bot_layout_internal.cpp | 2 +- Telegram/SourceFiles/media/media_audio.cpp | 10 +- Telegram/SourceFiles/media/media_audio.h | 1 + .../SourceFiles/media/media_audio_capture.cpp | 6 +- .../media/player/media_player_cover.cpp | 6 +- .../media/player/media_player_instance.cpp | 100 +++++++------ .../media/player/media_player_instance.h | 19 ++- .../media/player/media_player_widget.cpp | 6 +- .../SourceFiles/overview/overview_layout.cpp | 2 +- Telegram/SourceFiles/shortcuts.cpp | 2 +- Telegram/SourceFiles/structs.h | 15 +- Telegram/build/version | 6 +- 26 files changed, 347 insertions(+), 125 deletions(-) diff --git a/Telegram/Resources/uwp/AppX/AppxManifest.xml b/Telegram/Resources/uwp/AppX/AppxManifest.xml index 5828aa5c93..c935f3d1db 100644 --- a/Telegram/Resources/uwp/AppX/AppxManifest.xml +++ b/Telegram/Resources/uwp/AppX/AppxManifest.xml @@ -9,7 +9,7 @@ + Version="1.0.8.0" /> Telegram Desktop Telegram Messenger LLP diff --git a/Telegram/Resources/winrc/Telegram.rc b/Telegram/Resources/winrc/Telegram.rc index 4a461d456e..24397d05de 100644 --- a/Telegram/Resources/winrc/Telegram.rc +++ b/Telegram/Resources/winrc/Telegram.rc @@ -34,8 +34,8 @@ IDI_ICON1 ICON "..\\art\\icon256.ico" // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,7,0 - PRODUCTVERSION 1,0,7,0 + FILEVERSION 1,0,8,0 + PRODUCTVERSION 1,0,8,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -52,10 +52,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" 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 "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.0.7.0" + VALUE "ProductVersion", "1.0.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/Resources/winrc/Updater.rc b/Telegram/Resources/winrc/Updater.rc index 0218f2f66d..5fe87b1dee 100644 --- a/Telegram/Resources/winrc/Updater.rc +++ b/Telegram/Resources/winrc/Updater.rc @@ -25,8 +25,8 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,0,7,0 - PRODUCTVERSION 1,0,7,0 + FILEVERSION 1,0,8,0 + PRODUCTVERSION 1,0,8,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -43,10 +43,10 @@ BEGIN BEGIN VALUE "CompanyName", "Telegram Messenger LLP" VALUE "FileDescription", "Telegram Desktop Updater" - VALUE "FileVersion", "1.0.7.0" + VALUE "FileVersion", "1.0.8.0" VALUE "LegalCopyright", "Copyright (C) 2014-2017" VALUE "ProductName", "Telegram Desktop" - VALUE "ProductVersion", "1.0.7.0" + VALUE "ProductVersion", "1.0.8.0" END END BLOCK "VarFileInfo" diff --git a/Telegram/SourceFiles/application.cpp b/Telegram/SourceFiles/application.cpp index a6f2b6786f..6bc4b8ded5 100644 --- a/Telegram/SourceFiles/application.cpp +++ b/Telegram/SourceFiles/application.cpp @@ -1069,8 +1069,8 @@ void AppClass::checkMapVersion() { if (Local::oldMapVersion() < AppVersion) { if (Local::oldMapVersion()) { QString versionFeatures; - if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000007) { - versionFeatures = QString::fromUtf8("\xe2\x80\x94 Added Theme editor to Settings."); + if ((cAlphaVersion() || cBetaVersion()) && Local::oldMapVersion() < 1000008) { + 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) { versionFeatures = langNewVersionText(); } else { diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 2ec3e03072..801f0ea630 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -103,8 +103,6 @@ enum { 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 - WaveformSamplesCount = 100, - 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 diff --git a/Telegram/SourceFiles/core/version.h b/Telegram/SourceFiles/core/version.h index a8f3088d3c..e83cc6187d 100644 --- a/Telegram/SourceFiles/core/version.h +++ b/Telegram/SourceFiles/core/version.h @@ -24,7 +24,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #define BETA_VERSION_MACRO (0ULL) -constexpr int AppVersion = 1000007; -constexpr str_const AppVersionStr = "1.0.7"; +constexpr int AppVersion = 1000008; +constexpr str_const AppVersionStr = "1.0.8"; constexpr bool AppAlphaVersion = true; constexpr uint64 AppBetaVersion = BETA_VERSION_MACRO; diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index c9aea5cfee..25f8f14867 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -270,7 +270,6 @@ void ReplyKeyboard::clickHandlerActiveChanged(const ClickHandlerPtr &p, bool act } } - ReplyKeyboard::ButtonCoords ReplyKeyboard::findButtonCoordsByClickHandler(const ClickHandlerPtr &p) { for (int i = 0, rows = _rows.size(); i != rows; ++i) { auto &row = _rows[i]; diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index fb5d08095c..e872ba92a9 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -409,6 +409,7 @@ struct HistoryMessageUnreadBar : public RuntimeComponent(that); } } void HistoryDocumentVoice::checkPlaybackFinished() const { if (_playback && !_playback->_a_progress.animating()) { - delete _playback; - _playback = nullptr; + _playback.reset(); } } +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) , _data(document) { createComponents(!caption.isEmpty()); @@ -979,8 +989,11 @@ void HistoryDocument::createComponents(bool caption) { } UpdateComponents(mask); if (auto thumbed = Get()) { - thumbed->_linksavel.reset(new DocumentSaveClickHandler(_data)); - thumbed->_linkcancell.reset(new DocumentCancelClickHandler(_data)); + thumbed->_linksavel = MakeShared(_data); + thumbed->_linkcancell = MakeShared(_data); + } + if (auto voice = Get()) { + voice->_seekl = MakeShared(_data); } } @@ -1211,6 +1224,7 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, auto namewidth = _width - nameleft - nameright; auto statuswidth = namewidth; + auto voiceStatusOverride = QString(); if (auto voice = Get()) { const VoiceWaveform *wf = nullptr; uchar norm_value = 0; @@ -1227,28 +1241,40 @@ void HistoryDocument::draw(Painter &p, const QRect &r, TextSelection selection, 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 - 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); - int32 wf_size = wf ? wf->size() : WaveformSamplesCount, availw = int32(namewidth + st::msgWaveformSkip), activew = qRound(availw * prg); + 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 wf_size = wf ? wf->size() : Media::Player::kWaveformSamplesCount; + auto availw = namewidth + st::msgWaveformSkip; + auto activew = qRound(availw * progress); if (!outbg && !voice->_playback && _parent->isMediaUnread()) { activew = availw; } - int32 bar_count = qMin(availw / int32(st::msgWaveformBar + st::msgWaveformSkip), wf_size); - uchar max_value = 0; + auto bar_count = qMin(availw / (st::msgWaveformBar + st::msgWaveformSkip), wf_size); + auto max_value = 0; auto max_delta = st::msgWaveformMax - st::msgWaveformMin; auto bottom = st::msgFilePadding.top() - topMinus + st::msgWaveformMax; p.setPen(Qt::NoPen); - for (int32 i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { - uchar value = wf ? wf->at(i) : 0; + for (auto i = 0, bar_x = 0, sum_i = 0; i < wf_size; ++i) { + auto value = wf ? wf->at(i) : 0; if (sum_i + bar_count >= wf_size) { // draw bar sum_i = sum_i + bar_count - wf_size; if (sum_i < (bar_count + 1) / 2) { 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) { 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.setPen(status); - p.drawTextLeft(nameleft, statustop, _width, _statusText); + p.drawTextLeft(nameleft, statustop, _width, statusText); if (_parent->isMediaUnread()) { - int32 w = st::normalFont->width(_statusText); + auto w = st::normalFont->width(statusText); if (w + st::mediaUnreadSkip + st::mediaUnreadSize <= statuswidth) { p.setPen(Qt::NoPen); 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; if (auto thumbed = Get()) { nameleft = st::msgFileThumbPadding.left() + st::msgFileThumbSize + st::msgFileThumbPadding.right(); + nameright = st::msgFileThumbPadding.left(); + nametop = st::msgFileThumbNameTop - topMinus; linktop = st::msgFileThumbLinkTop - 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 { + 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; 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()) { + 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; if (auto captioned = Get()) { if (y >= bottom) { @@ -1364,6 +1411,23 @@ HistoryTextState HistoryDocument::getState(int x, int y, HistoryStateRequest req return result; } +void HistoryDocument::updatePressed(int x, int y) { + if (auto voice = Get()) { + if (voice->seeking()) { + auto nameleft = 0, nameright = 0; + if (auto thumbed = Get()) { + 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 result; 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); if (state.id == AudioMsgId(_data, _parent->fullId()) && !Media::Player::IsStopped(state.state) && state.state != State::Finishing) { if (auto voice = Get()) { - bool was = voice->_playback; + bool was = (voice->_playback != nullptr); voice->ensurePlayback(this); if (!was || state.position != voice->_playback->_position) { 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->_a_progress.start(); } + voice->_lastDurationMs = static_cast((state.duration * 1000LL) / state.frequency); // Bad :( } statusSize = -1 - (state.position / state.frequency); @@ -1472,6 +1537,9 @@ bool HistoryDocument::updateStatusText() const { voice->checkPlaybackFinished(); } } + if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId()))) { + showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Voice); + } } else if (_data->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) { @@ -1481,7 +1549,7 @@ bool HistoryDocument::updateStatusText() const { } else { } if (!showPause && (state.id == AudioMsgId(_data, _parent->fullId()))) { - showPause = Media::Player::instance()->isSeeking(); + showPause = Media::Player::instance()->isSeeking(AudioMsgId::Type::Song); } } } 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()) { + 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() { App::regDocumentItem(_data, _parent); } @@ -2455,7 +2545,9 @@ void HistoryWebPage::initDimensions() { } 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(_data->url, true); + } // init layout QString title(_data->title.isEmpty() ? _data->author : _data->title); diff --git a/Telegram/SourceFiles/history/history_media_types.h b/Telegram/SourceFiles/history/history_media_types.h index 8b5a2fbaa2..2dbf4aff4e 100644 --- a/Telegram/SourceFiles/history/history_media_types.h +++ b/Telegram/SourceFiles/history/history_media_types.h @@ -305,17 +305,42 @@ struct HistoryDocumentVoicePlayback { anim::value a_progress; BasicAnimation _a_progress; }; -struct HistoryDocumentVoice : public RuntimeComponent { - HistoryDocumentVoice &operator=(HistoryDocumentVoice &&other) { - std::swap(_playback, other._playback); - return *this; - } - ~HistoryDocumentVoice() { - delete base::take(_playback); - } +class HistoryDocumentVoice : public RuntimeComponent { + // We don't use float64 because components should align to pointer even on 32bit systems. + static constexpr float64 kFloatToIntMultiplier = 65536.; + +public: void ensurePlayback(const HistoryDocument *interfaces) const; void checkPlaybackFinished() const; - mutable HistoryDocumentVoicePlayback *_playback = nullptr; + + mutable std_::unique_ptr _playback; + QSharedPointer _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 { @@ -334,6 +359,7 @@ public: void draw(Painter &p, const QRect &r, TextSelection selection, TimeMs ms) 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 { if (auto captioned = Get()) { @@ -387,6 +413,8 @@ public: void step_voiceProgress(float64 ms, bool timer); + void clickHandlerPressedChanged(const ClickHandlerPtr &p, bool pressed) override; + protected: float64 dataProgress() const override { return _data->progress(); diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 1bb585c96d..e55334dce7 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1671,6 +1671,60 @@ HistoryTextState HistoryMessage::getState(int x, int y, HistoryStateRequest requ 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(); + auto fwdheight = ((fwd->_text.maxWidth() > trect.width()) ? 2 : 1) * st::semiboldFont->height; + trect.setTop(trect.top() + fwdheight); + } + if (Get()) { + auto h = st::msgReplyPadding.top() + st::msgReplyBarSize.height() + st::msgReplyPadding.bottom(); + trect.setTop(trect.top() + h); + } + if (!displayFromName() && !Has()) { + if (auto via = Get()) { + 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 { if (displayFromName()) { if (y >= trect.top() && y < trect.top() + st::msgNameFont->height) { diff --git a/Telegram/SourceFiles/history/history_message.h b/Telegram/SourceFiles/history/history_message.h index a4c913643b..65ca6d742f 100644 --- a/Telegram/SourceFiles/history/history_message.h +++ b/Telegram/SourceFiles/history/history_message.h @@ -78,6 +78,7 @@ public: bool pointInTime(int32 right, int32 bottom, int x, int y, InfoDisplayType type) 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; diff --git a/Telegram/SourceFiles/historywidget.cpp b/Telegram/SourceFiles/historywidget.cpp index b26bacbffb..9d9ce8ea62 100644 --- a/Telegram/SourceFiles/historywidget.cpp +++ b/Telegram/SourceFiles/historywidget.cpp @@ -990,6 +990,11 @@ void HistoryInner::onDragExec() { } } auto pressedHandler = ClickHandler::getPressed(); + + if (dynamic_cast(pressedHandler.data())) { + return; + } + TextWithEntities sel; QList urls; 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) { Ui::Tooltip::Hide(); } @@ -2101,7 +2106,7 @@ void HistoryInner::onUpdateSelected() { } } else if (item) { if (_dragAction == Selecting) { - bool canSelectMany = (_history != nullptr); + auto canSelectMany = (_history != nullptr); if (selectingText) { uint16 second = dragState.symbol; if (dragState.afterSymbol && _dragSelType == TextSelectType::Letters) { @@ -2118,8 +2123,8 @@ void HistoryInner::onUpdateSelected() { } updateDragSelection(0, 0, false); } else if (canSelectMany) { - bool selectingDown = (itemTop(_dragItem) < itemTop(item)) || (_dragItem == item && _dragStartPos.y() < m.y()); - HistoryItem *dragSelFrom = _dragItem, *dragSelTo = item; + auto selectingDown = (itemTop(_dragItem) < itemTop(item)) || (_dragItem == item && _dragStartPos.y() < m.y()); + auto dragSelFrom = _dragItem, dragSelTo = item; if (!dragSelFrom->hasPoint(_dragStartPos.x(), _dragStartPos.y())) { // maybe exclude dragSelFrom if (selectingDown) { 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; - HistoryItem *dragFirstAffected = dragSelFrom; + auto dragSelecting = false; + auto dragFirstAffected = dragSelFrom; while (dragFirstAffected && (dragFirstAffected->id < 0 || dragFirstAffected->serviceMsg())) { dragFirstAffected = (dragFirstAffected == dragSelTo) ? 0 : (selectingDown ? nextItem(dragFirstAffected) : prevItem(dragFirstAffected)); } if (dragFirstAffected) { - SelectedItems::const_iterator i = _selected.constFind(dragFirstAffected); + auto i = _selected.constFind(dragFirstAffected); dragSelecting = (i == _selected.cend() || i.value() != FullSelection); } 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) { _widget->checkSelectingScroll(mousePos); } else { diff --git a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp index 08d8e63d52..8989d1930e 100644 --- a/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp +++ b/Telegram/SourceFiles/inline_bots/inline_bot_layout_internal.cpp @@ -847,7 +847,7 @@ bool File::updateStatusText() const { realDuration = (state.duration / state.frequency); 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; } } else { diff --git a/Telegram/SourceFiles/media/media_audio.cpp b/Telegram/SourceFiles/media/media_audio.cpp index e095fc4d95..3023c664ca 100644 --- a/Telegram/SourceFiles/media/media_audio.cpp +++ b/Telegram/SourceFiles/media/media_audio.cpp @@ -1686,12 +1686,12 @@ public: QByteArray buffer; buffer.reserve(AudioVoiceMsgBufferSize); int64 countbytes = sampleSize * duration(), processed = 0, sumbytes = 0; - if (duration() < WaveformSamplesCount) { + if (duration() < Media::Player::kWaveformSamplesCount) { return false; } QVector peaks; - peaks.reserve(WaveformSamplesCount); + peaks.reserve(Media::Player::kWaveformSamplesCount); int32 fmt = format(); uint16 peak = 0; @@ -1716,7 +1716,7 @@ public: } i += sizeof(uchar); - sumbytes += WaveformSamplesCount; + sumbytes += Media::Player::kWaveformSamplesCount; if (sumbytes >= countbytes) { sumbytes -= countbytes; peaks.push_back(peak); @@ -1731,7 +1731,7 @@ public: } i += sizeof(uint16); - sumbytes += sizeof(uint16) * WaveformSamplesCount; + sumbytes += sizeof(uint16) * Media::Player::kWaveformSamplesCount; if (sumbytes >= countbytes) { sumbytes -= countbytes; peaks.push_back(peak); @@ -1741,7 +1741,7 @@ public: } processed += sampleSize * samples; } - if (sumbytes > 0 && peaks.size() < WaveformSamplesCount) { + if (sumbytes > 0 && peaks.size() < Media::Player::kWaveformSamplesCount) { peaks.push_back(peak); } diff --git a/Telegram/SourceFiles/media/media_audio.h b/Telegram/SourceFiles/media/media_audio.h index 040b961142..68b184725a 100644 --- a/Telegram/SourceFiles/media/media_audio.h +++ b/Telegram/SourceFiles/media/media_audio.h @@ -28,6 +28,7 @@ namespace Player { constexpr auto kDefaultFrequency = 48000; // 48 kHz constexpr auto kTogetherLimit = 4; +constexpr auto kWaveformSamplesCount = 100; class Fader; class Loaders; diff --git a/Telegram/SourceFiles/media/media_audio_capture.cpp b/Telegram/SourceFiles/media/media_audio_capture.cpp index cc23e457f6..3f520e60a9 100644 --- a/Telegram/SourceFiles/media/media_audio_capture.cpp +++ b/Telegram/SourceFiles/media/media_audio_capture.cpp @@ -396,9 +396,9 @@ void Instance::Inner::onStop(bool needResult) { qint32 samples = d->fullSamples; if (samples && !d->waveform.isEmpty()) { int64 count = d->waveform.size(), sum = 0; - if (count >= WaveformSamplesCount) { + if (count >= Player::kWaveformSamplesCount) { QVector peaks; - peaks.reserve(WaveformSamplesCount); + peaks.reserve(Player::kWaveformSamplesCount); uint16 peak = 0; for (int32 i = 0; i < count; ++i) { @@ -406,7 +406,7 @@ void Instance::Inner::onStop(bool needResult) { if (peak < sample) { peak = sample; } - sum += WaveformSamplesCount; + sum += Player::kWaveformSamplesCount; if (sum >= count) { sum -= count; peaks.push_back(peak); diff --git a/Telegram/SourceFiles/media/player/media_player_cover.cpp b/Telegram/SourceFiles/media/player/media_player_cover.cpp index 4165bde2da..436d476eed 100644 --- a/Telegram/SourceFiles/media/player/media_player_cover.cpp +++ b/Telegram/SourceFiles/media/player/media_player_cover.cpp @@ -141,7 +141,7 @@ void CoverWidget::handleSeekProgress(float64 progress) { if (_seekPositionMs != positionMs) { _seekPositionMs = positionMs; 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)); } - instance()->stopSeeking(); + instance()->stopSeeking(type); } 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 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; } auto buttonState = [audio = state.id.audio(), showPause] { diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 6b6e0a2a9e..566de53395 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -49,9 +49,7 @@ void finish() { Instance::Instance() { subscribe(Media::Player::Updated(), [this](const AudioMsgId &audioId) { - if (audioId.type() == AudioMsgId::Type::Song) { - handleSongUpdate(audioId); - } + handleSongUpdate(audioId); }); auto observeEvents = Notify::PeerUpdate::Flag::SharedMediaChanged; subscribe(Notify::PeerUpdated(), Notify::PeerUpdatedHandler(observeEvents, [this](const Notify::PeerUpdate &update) { @@ -79,27 +77,33 @@ void Instance::notifyPeerUpdated(const Notify::PeerUpdate &update) { } void Instance::handleSongUpdate(const AudioMsgId &audioId) { - emitUpdate([&audioId](const AudioMsgId &playing) { + emitUpdate(audioId.type(), [&audioId](const AudioMsgId &playing) { return (audioId == playing); }); } void Instance::setCurrent(const AudioMsgId &audioId) { - if (_current != audioId) { - _current = audioId; - _isPlaying = false; + if (audioId.type() == AudioMsgId::Type::Song) { + if (_current != audioId) { + _current = audioId; + _isPlaying = false; - auto history = _history, migrated = _migrated; - auto item = _current ? App::histItemById(_current.contextId()) : nullptr; - if (item) { - _history = item->history()->peer->migrateTo() ? App::history(item->history()->peer->migrateTo()) : item->history(); - _migrated = _history->peer->migrateFrom() ? App::history(_history->peer->migrateFrom()) : nullptr; - } else { - _history = _migrated = nullptr; + auto history = _history, migrated = _migrated; + auto item = _current ? App::histItemById(_current.contextId()) : nullptr; + if (item) { + _history = item->history()->peer->migrateTo() ? App::history(item->history()->peer->migrateTo()) : item->history(); + _migrated = _history->peer->migrateFrom() ? App::history(_history->peer->migrateFrom()) : nullptr; + } else { + _history = _migrated = nullptr; + } + _songChangedNotifier.notify(true); + if (_history != history || _migrated != migrated) { + rebuildPlaylist(); + } } - _songChangedNotifier.notify(true); - if (_history != history || _migrated != migrated) { - rebuildPlaylist(); + } else if (audioId.type() == AudioMsgId::Type::Voice) { + if (_currentVoice != audioId) { + _currentVoice = audioId; } } } @@ -173,12 +177,12 @@ void Instance::play(const AudioMsgId &audioId) { } } -void Instance::pause() { - auto state = mixer()->currentState(AudioMsgId::Type::Song); +void Instance::pause(AudioMsgId::Type type) { + auto state = mixer()->currentState(type); if (state.id) { if (!IsStopped(state.state)) { 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() { - if (isSeeking()) { + if (isSeeking(AudioMsgId::Type::Song)) { return; } @@ -221,32 +225,40 @@ void Instance::playPauseCancelClicked() { if (audio && audio->loading()) { audio->cancel(); } else if (showPause) { - pause(); + pause(AudioMsgId::Type::Song); } else { play(); } } -void Instance::startSeeking() { - _seeking = _current; - pause(); - emitUpdate([](const AudioMsgId &playing) { return true; }); +void Instance::startSeeking(AudioMsgId::Type type) { + if (type == AudioMsgId::Type::Song) { + _seeking = _current; + } else if (type == AudioMsgId::Type::Voice) { + _seekingVoice = _currentVoice; + } + pause(type); + emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } -void Instance::stopSeeking() { - _seeking = AudioMsgId(); - emitUpdate([](const AudioMsgId &playing) { return true; }); +void Instance::stopSeeking(AudioMsgId::Type type) { + if (type == AudioMsgId::Type::Song) { + _seeking = AudioMsgId(); + } else if (type == AudioMsgId::Type::Voice) { + _seekingVoice = AudioMsgId(); + } + emitUpdate(type, [](const AudioMsgId &playing) { return true; }); } 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); }); } template -void Instance::emitUpdate(CheckCallback check) { - auto state = mixer()->currentState(AudioMsgId::Type::Song); +void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) { + auto state = mixer()->currentState(type); if (!state.id || !check(state.id)) { return; } @@ -254,18 +266,20 @@ void Instance::emitUpdate(CheckCallback check) { setCurrent(state.id); _updatedNotifier.notify(state, true); - if (_isPlaying && state.state == State::StoppedAtEnd) { - if (_repeatEnabled) { - mixer()->play(_current); - } else { - next(); + if (type == AudioMsgId::Type::Song) { + if (_isPlaying && state.state == State::StoppedAtEnd) { + if (_repeatEnabled) { + mixer()->play(_current); + } else { + next(); + } } - } - auto isPlaying = !IsStopped(state.state); - if (_isPlaying != isPlaying) { - _isPlaying = isPlaying; - if (_isPlaying) { - preloadNext(); + auto isPlaying = !IsStopped(state.state); + if (_isPlaying != isPlaying) { + _isPlaying = isPlaying; + if (_isPlaying) { + preloadNext(); + } } } } diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 0590695179..1b4642ca74 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -39,7 +39,7 @@ struct TrackState; class Instance : private base::Subscriber { public: void play(); - void pause(); + void pause(AudioMsgId::Type type); void stop(); void playPause(); void next(); @@ -60,11 +60,16 @@ public: _repeatChangedNotifier.notify(); } - bool isSeeking() const { - return (_seeking == _current); + bool isSeeking(AudioMsgId::Type type) const { + if (type == AudioMsgId::Type::Song) { + return (_seeking == _current); + } else if (type == AudioMsgId::Type::Voice) { + return (_seekingVoice == _currentVoice); + } + return false; } - void startSeeking(); - void stopSeeking(); + void startSeeking(AudioMsgId::Type type); + void stopSeeking(AudioMsgId::Type type); const QList &playlist() const { return _playlist; @@ -111,7 +116,7 @@ private: void handleLogout(); template - void emitUpdate(CheckCallback check); + void emitUpdate(AudioMsgId::Type type, CheckCallback check); AudioMsgId _current, _seeking; History *_history = nullptr; @@ -122,6 +127,8 @@ private: QList _playlist; bool _isPlaying = false; + AudioMsgId _currentVoice, _seekingVoice; + base::Observable _usePanelPlayer; base::Observable _titleButtonOver; base::Observable _playerWidgetOver; diff --git a/Telegram/SourceFiles/media/player/media_player_widget.cpp b/Telegram/SourceFiles/media/player/media_player_widget.cpp index 48d84c47af..61420a062b 100644 --- a/Telegram/SourceFiles/media/player/media_player_widget.cpp +++ b/Telegram/SourceFiles/media/player/media_player_widget.cpp @@ -195,7 +195,7 @@ void Widget::handleSeekProgress(float64 progress) { _seekPositionMs = positionMs; updateTimeLabel(); - instance()->startSeeking(); + instance()->startSeeking(AudioMsgId::Type::Song); } } @@ -211,7 +211,7 @@ void Widget::handleSeekFinished(float64 progress) { mixer()->seek(type, qRound(progress * state.duration)); } - instance()->stopSeeking(); + instance()->stopSeeking(AudioMsgId::Type::Song); } 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 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; } auto buttonState = [audio = state.id.audio(), showPause] { diff --git a/Telegram/SourceFiles/overview/overview_layout.cpp b/Telegram/SourceFiles/overview/overview_layout.cpp index 70f5d6349b..534b515ae0 100644 --- a/Telegram/SourceFiles/overview/overview_layout.cpp +++ b/Telegram/SourceFiles/overview/overview_layout.cpp @@ -940,7 +940,7 @@ bool Document::updateStatusText() { realDuration = (state.duration / state.frequency); 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; } } else { diff --git a/Telegram/SourceFiles/shortcuts.cpp b/Telegram/SourceFiles/shortcuts.cpp index 7d47654cf8..fe8f73c530 100644 --- a/Telegram/SourceFiles/shortcuts.cpp +++ b/Telegram/SourceFiles/shortcuts.cpp @@ -84,7 +84,7 @@ bool media_play() { } bool media_pause() { - Media::Player::instance()->pause(); + Media::Player::instance()->pause(AudioMsgId::Type::Song); return true; } diff --git a/Telegram/SourceFiles/structs.h b/Telegram/SourceFiles/structs.h index e4751f13d2..b4215fe77f 100644 --- a/Telegram/SourceFiles/structs.h +++ b/Telegram/SourceFiles/structs.h @@ -1076,12 +1076,11 @@ struct SongData : public DocumentAdditionalData { typedef QVector VoiceWaveform; // [0] == -1 -- counting, [0] == -2 -- could not count struct VoiceData : public DocumentAdditionalData { - VoiceData() : duration(0), wavemax(0) { - } ~VoiceData(); - int32 duration; + + int duration = 0; VoiceWaveform waveform; - char wavemax; + char wavemax = 0; }; bool fileIsImage(const QString &name, const QString &mime); @@ -1362,6 +1361,14 @@ protected: void onClickImpl() const override; }; +class VoiceSeekClickHandler : public DocumentOpenClickHandler { +public: + using DocumentOpenClickHandler::DocumentOpenClickHandler; +protected: + void onClickImpl() const override { + } +}; + class DocumentCancelClickHandler : public DocumentClickHandler { public: using DocumentClickHandler::DocumentClickHandler; diff --git a/Telegram/build/version b/Telegram/build/version index 569c0411e5..1b0a969e30 100644 --- a/Telegram/build/version +++ b/Telegram/build/version @@ -1,6 +1,6 @@ -AppVersion 1000007 +AppVersion 1000008 AppVersionStrMajor 1.0 -AppVersionStrSmall 1.0.7 -AppVersionStr 1.0.7 +AppVersionStrSmall 1.0.8 +AppVersionStr 1.0.8 AlphaChannel 1 BetaVersion 0