From 85286684e364d722dcdfa583ad4a190b7b822e8a Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Fri, 29 Dec 2023 04:07:04 +0300 Subject: [PATCH] Added initial support for voice messages with TTL. --- Telegram/Resources/langs/lang.strings | 4 ++ .../SourceFiles/data/data_media_types.cpp | 17 ++++- Telegram/SourceFiles/data/data_media_types.h | 8 ++- Telegram/SourceFiles/history/history_item.cpp | 66 ++++++++++++------- .../history/history_item_helpers.cpp | 8 ++- .../history/history_item_helpers.h | 3 +- .../view/media/history_view_document.cpp | 58 ++++++++++++++-- .../view/media/history_view_web_page.cpp | 3 +- Telegram/SourceFiles/mainwidget.cpp | 11 +++- .../media/player/media_player_instance.cpp | 3 + 10 files changed, 144 insertions(+), 37 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 6793380b7..acebc4530 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1715,6 +1715,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_ttl_video_received" = "{from} sent you a self-destructing video. Please view it on your mobile."; "lng_ttl_video_sent" = "You sent a self-destructing video."; "lng_ttl_video_expired" = "Video has expired"; +"lng_ttl_voice_sent" = "You sent a self-destructing voice messsage."; +"lng_ttl_voice_expired" = "Voice message expired"; +"lng_ttl_round_sent" = "You sent a self-destructing video message."; +"lng_ttl_round_expired" = "Round message expired"; "lng_profile_add_more_after_create" = "You will be able to add more members after you create the group."; "lng_profile_camera_title" = "Capture yourself"; diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index f716984ad..71e2929ac 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -555,6 +555,10 @@ bool Media::hasSpoiler() const { return false; } +crl::time Media::ttlSeconds() const { + return 0; +} + bool Media::consumeMessageText(const TextWithEntities &text) { return false; } @@ -849,12 +853,14 @@ MediaFile::MediaFile( not_null parent, not_null document, bool skipPremiumEffect, - bool spoiler) + bool spoiler, + crl::time ttlSeconds) : Media(parent) , _document(document) , _emoji(document->sticker() ? document->sticker()->alt : QString()) , _skipPremiumEffect(skipPremiumEffect) -, _spoiler(spoiler) { +, _spoiler(spoiler) +, _ttlSeconds(ttlSeconds) { parent->history()->owner().registerDocumentItem(_document, parent); if (!_emoji.isEmpty()) { @@ -882,7 +888,8 @@ std::unique_ptr MediaFile::clone(not_null parent) { parent, _document, !_document->session().premium(), - _spoiler); + _spoiler, + _ttlSeconds); } DocumentData *MediaFile::document() const { @@ -1129,6 +1136,10 @@ bool MediaFile::hasSpoiler() const { return _spoiler; } +crl::time MediaFile::ttlSeconds() const { + return _ttlSeconds; +} + bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { if (media.type() != mtpc_messageMediaDocument) { return false; diff --git a/Telegram/SourceFiles/data/data_media_types.h b/Telegram/SourceFiles/data/data_media_types.h index 2a4e18e90..27d1718c1 100644 --- a/Telegram/SourceFiles/data/data_media_types.h +++ b/Telegram/SourceFiles/data/data_media_types.h @@ -174,6 +174,7 @@ public: virtual bool dropForwardedInfo() const; virtual bool forceForwardedInfo() const; [[nodiscard]] virtual bool hasSpoiler() const; + [[nodiscard]] virtual crl::time ttlSeconds() const; [[nodiscard]] virtual bool consumeMessageText( const TextWithEntities &text); @@ -256,7 +257,8 @@ public: not_null parent, not_null document, bool skipPremiumEffect, - bool spoiler); + bool spoiler, + crl::time ttlSeconds); ~MediaFile(); std::unique_ptr clone(not_null parent) override; @@ -278,6 +280,7 @@ public: bool forwardedBecomesUnread() const override; bool dropForwardedInfo() const override; bool hasSpoiler() const override; + crl::time ttlSeconds() const override; bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override; @@ -292,6 +295,9 @@ private: bool _skipPremiumEffect = false; bool _spoiler = false; + // Video (unsupported) / Voice / Round. + crl::time _ttlSeconds = 0; + }; class MediaContact final : public Media { diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index ff4d4fdb4..2d76eb8db 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -242,7 +242,7 @@ std::unique_ptr HistoryItem::CreateMedia( }); }, [&](const MTPDmessageMediaDocument &media) -> Result { const auto document = media.vdocument(); - if (media.vttl_seconds()) { + if (media.vttl_seconds() && media.is_video()) { LOG(("App Error: " "Unexpected MTPMessageMediaDocument " "with ttl_seconds in CreateMedia.")); @@ -258,7 +258,8 @@ std::unique_ptr HistoryItem::CreateMedia( item, item->history()->owner().processDocument(document), media.is_nopremium(), - media.is_spoiler()); + media.is_spoiler(), + media.vttl_seconds().value_or_empty()); }, [](const MTPDdocumentEmpty &) -> Result { return nullptr; }); @@ -354,7 +355,8 @@ HistoryItem::HistoryItem( setServiceText({ tr::lng_message_empty(tr::now, Ui::Text::WithEntities) }); - } else if (checked == MediaCheckResult::HasTimeToLive) { + } else if ((checked == MediaCheckResult::HasUnsupportedTimeToLive) + || (checked == MediaCheckResult::HasExpiredMediaTimeToLive)) { createServiceFromMtp(data); applyTTL(data); } else if (checked == MediaCheckResult::HasStoryMention) { @@ -617,7 +619,8 @@ HistoryItem::HistoryItem( this, document, skipPremiumEffect, - spoiler); + spoiler, + /*ttlSeconds = */0); setText(caption); } @@ -1659,7 +1662,8 @@ void HistoryItem::setStoryFields(not_null story) { this, document, /*skipPremiumEffect=*/false, - spoiler); + spoiler, + /*ttlSeconds = */0); } setText(story->caption()); } @@ -3605,25 +3609,43 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) { const auto ttl = data.vttl_seconds(); Assert(ttl != nullptr); - setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, *ttl); - if (out()) { - setServiceText({ - tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) - }); - } else { - auto result = PreparedServiceText(); - result.links.push_back(fromLink()); - result.text = tr::lng_ttl_video_received( - tr::now, - lt_from, - fromLinkText(), // Link 1. - Ui::Text::WithEntities); - setServiceText(std::move(result)); + if (data.is_video()) { + setSelfDestruct( + HistoryServiceSelfDestruct::Type::Video, + *ttl); + if (out()) { + setServiceText({ + tr::lng_ttl_video_sent( + tr::now, + Ui::Text::WithEntities) + }); + } else { + auto result = PreparedServiceText(); + result.links.push_back(fromLink()); + result.text = tr::lng_ttl_video_received( + tr::now, + lt_from, + fromLinkText(), // Link 1. + Ui::Text::WithEntities); + setServiceText(std::move(result)); + } + } else if (out()) { + auto text = (data.is_voice() + ? tr::lng_ttl_voice_sent + : data.is_round() + ? tr::lng_ttl_round_sent + : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); + setServiceText({ std::move(text) }); } } else { - setServiceText({ - tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) - }); + auto text = (data.is_video() + ? tr::lng_ttl_video_expired + : data.is_voice() + ? tr::lng_ttl_voice_expired + : data.is_round() + ? tr::lng_ttl_round_expired + : tr::lng_message_empty)(tr::now, Ui::Text::WithEntities); + setServiceText({ std::move(text) }); } }, [&](const MTPDmessageMediaStory &data) { setServiceText(prepareStoryMentionText()); diff --git a/Telegram/SourceFiles/history/history_item_helpers.cpp b/Telegram/SourceFiles/history/history_item_helpers.cpp index 1745148d1..78c7a8121 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.cpp +++ b/Telegram/SourceFiles/history/history_item_helpers.cpp @@ -451,7 +451,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { }, [](const MTPDmessageMediaPhoto &data) { const auto photo = data.vphoto(); if (data.vttl_seconds()) { - return Result::HasTimeToLive; + return Result::HasUnsupportedTimeToLive; } else if (!photo) { return Result::Empty; } @@ -463,7 +463,11 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) { }, [](const MTPDmessageMediaDocument &data) { const auto document = data.vdocument(); if (data.vttl_seconds()) { - return Result::HasTimeToLive; + if (data.is_video()) { + return Result::HasUnsupportedTimeToLive; + } else if (!document) { + return Result::HasExpiredMediaTimeToLive; + } } else if (!document) { return Result::Empty; } diff --git a/Telegram/SourceFiles/history/history_item_helpers.h b/Telegram/SourceFiles/history/history_item_helpers.h index 661efe652..ca972eaa7 100644 --- a/Telegram/SourceFiles/history/history_item_helpers.h +++ b/Telegram/SourceFiles/history/history_item_helpers.h @@ -43,7 +43,8 @@ enum class MediaCheckResult { Good, Unsupported, Empty, - HasTimeToLive, + HasExpiredMediaTimeToLive, + HasUnsupportedTimeToLive, HasStoryMention, }; [[nodiscard]] MediaCheckResult CheckMessageMedia( diff --git a/Telegram/SourceFiles/history/view/media/history_view_document.cpp b/Telegram/SourceFiles/history/view/media/history_view_document.cpp index 04df9f2aa..df9f0f751 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_document.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_document.cpp @@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "media/audio/media_audio.h" #include "media/player/media_player_instance.h" #include "history/history_item_components.h" +#include "history/history_item_helpers.h" // PreparedServiceText. #include "history/history.h" #include "core/click_handler_types.h" // kDocumentFilenameTooltipProperty. #include "history/view/history_view_element.h" @@ -47,6 +48,10 @@ namespace { constexpr auto kAudioVoiceMsgUpdateView = crl::time(100); +[[nodiscard]] bool OncePlayable(not_null item) { + return !item->out() && item->media()->ttlSeconds(); +} + [[nodiscard]] QString CleanTagSymbols(const QString &value) { auto result = QString(); const auto begin = value.begin(), end = value.end(); @@ -195,7 +200,8 @@ Document::Document( not_null document) : File(parent, realParent) , _data(document) { - if (_data->isVideoMessage()) { + const auto isRound = _data->isVideoMessage(); + if (isRound) { const auto &entry = _data->session().api().transcribes().entry( realParent); _transcribedRound = entry.shown; @@ -209,7 +215,49 @@ Document::Document( _tooltipFilename.setTooltipText(named->name); } - setDocumentLinks(_data, realParent); + if ((_data->isVoiceMessage() || isRound) + && OncePlayable(_parent->data())) { + _parent->data()->removeFromSharedMediaIndex(); + setDocumentLinks(_data, realParent, [=] { + _openl = nullptr; + + auto lifetime = std::make_shared(); + rpl::merge( + ::Media::Player::instance()->updatedNotifier( + ) | rpl::filter([=](::Media::Player::TrackState state) { + using State = ::Media::Player::State; + const auto badState = state.state == State::Stopped + || state.state == State::StoppedAtEnd + || state.state == State::StoppedAtError + || state.state == State::StoppedAtStart; + return (state.id.contextId() != _realParent->fullId()) + && !badState; + }) | rpl::to_empty, + ::Media::Player::instance()->tracksFinished( + ) | rpl::filter([=](AudioMsgId::Type type) { + return (type == AudioMsgId::Type::Voice); + }) | rpl::to_empty + ) | rpl::start_with_next([=]() mutable { + const auto item = _parent->data(); + // Destroys this. + item->applyEditionToHistoryCleared(); + item->updateServiceText(PreparedServiceText{ + (isRound + ? tr::lng_ttl_round_expired + : tr::lng_ttl_voice_expired)( + tr::now, + Ui::Text::WithEntities) + }); + if (lifetime) { + base::take(lifetime)->destroy(); + } + }, *lifetime); + + return false; + }); + } else { + setDocumentLinks(_data, realParent); + } setStatusSize(Ui::FileStatusSizeReady); @@ -273,9 +321,9 @@ void Document::createComponents(bool caption) { _realParent->fullId()); } if (const auto voice = Get()) { - voice->seekl = std::make_shared( - _data, - [](FullMsgId) {}); + voice->seekl = !OncePlayable(_parent->data()) + ? std::make_shared(_data, [](FullMsgId) {}) + : nullptr; if (_transcribedRound) { voice->round = std::make_unique<::Media::Player::RoundPainter>( _realParent); diff --git a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp index ce8dcded5..7e75ddb91 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_web_page.cpp @@ -65,7 +65,8 @@ constexpr auto kMaxOriginalEntryLines = 8192; parent, *document, skipPremiumEffect, - spoiler)); + spoiler, + /*ttlSeconds = */0)); } else if (const auto photo = std::get_if(&item)) { result.push_back(std::make_unique( parent, diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 77ca1bfe4..202ac2589 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -752,13 +752,20 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) { void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { using State = Media::Player::State; const auto document = state.id.audio(); + const auto item = session().data().message(state.id.contextId()); if (!Media::Player::IsStoppedOrStopping(state.state)) { - createPlayer(); + const auto ttlSeconds = item + && !item->out() + && item->media() + && item->media()->ttlSeconds(); + if (!ttlSeconds) { + createPlayer(); + } } else if (state.state == State::StoppedAtStart) { Media::Player::instance()->stopAndClose(); } - if (const auto item = session().data().message(state.id.contextId())) { + if (item) { session().data().requestItemRepaint(item); } if (document) { diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 5e1655cc0..4dc687a27 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -503,6 +503,9 @@ bool Instance::moveInPlaylist( } const auto jumpByItem = [&](not_null item) { if (const auto media = item->media()) { + if (media->ttlSeconds()) { + return false; + } if (const auto document = media->document()) { if (autonext) { _switchToNext.fire({