Added initial support for voice messages with TTL.

This commit is contained in:
23rd 2023-12-29 04:07:04 +03:00 committed by John Preston
parent c2712b0104
commit 85286684e3
10 changed files with 144 additions and 37 deletions

View file

@ -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_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_sent" = "You sent a self-destructing video.";
"lng_ttl_video_expired" = "Video has expired"; "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_add_more_after_create" = "You will be able to add more members after you create the group.";
"lng_profile_camera_title" = "Capture yourself"; "lng_profile_camera_title" = "Capture yourself";

View file

@ -555,6 +555,10 @@ bool Media::hasSpoiler() const {
return false; return false;
} }
crl::time Media::ttlSeconds() const {
return 0;
}
bool Media::consumeMessageText(const TextWithEntities &text) { bool Media::consumeMessageText(const TextWithEntities &text) {
return false; return false;
} }
@ -849,12 +853,14 @@ MediaFile::MediaFile(
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, bool skipPremiumEffect,
bool spoiler) bool spoiler,
crl::time ttlSeconds)
: Media(parent) : Media(parent)
, _document(document) , _document(document)
, _emoji(document->sticker() ? document->sticker()->alt : QString()) , _emoji(document->sticker() ? document->sticker()->alt : QString())
, _skipPremiumEffect(skipPremiumEffect) , _skipPremiumEffect(skipPremiumEffect)
, _spoiler(spoiler) { , _spoiler(spoiler)
, _ttlSeconds(ttlSeconds) {
parent->history()->owner().registerDocumentItem(_document, parent); parent->history()->owner().registerDocumentItem(_document, parent);
if (!_emoji.isEmpty()) { if (!_emoji.isEmpty()) {
@ -882,7 +888,8 @@ std::unique_ptr<Media> MediaFile::clone(not_null<HistoryItem*> parent) {
parent, parent,
_document, _document,
!_document->session().premium(), !_document->session().premium(),
_spoiler); _spoiler,
_ttlSeconds);
} }
DocumentData *MediaFile::document() const { DocumentData *MediaFile::document() const {
@ -1129,6 +1136,10 @@ bool MediaFile::hasSpoiler() const {
return _spoiler; return _spoiler;
} }
crl::time MediaFile::ttlSeconds() const {
return _ttlSeconds;
}
bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) { bool MediaFile::updateInlineResultMedia(const MTPMessageMedia &media) {
if (media.type() != mtpc_messageMediaDocument) { if (media.type() != mtpc_messageMediaDocument) {
return false; return false;

View file

@ -174,6 +174,7 @@ public:
virtual bool dropForwardedInfo() const; virtual bool dropForwardedInfo() const;
virtual bool forceForwardedInfo() const; virtual bool forceForwardedInfo() const;
[[nodiscard]] virtual bool hasSpoiler() const; [[nodiscard]] virtual bool hasSpoiler() const;
[[nodiscard]] virtual crl::time ttlSeconds() const;
[[nodiscard]] virtual bool consumeMessageText( [[nodiscard]] virtual bool consumeMessageText(
const TextWithEntities &text); const TextWithEntities &text);
@ -256,7 +257,8 @@ public:
not_null<HistoryItem*> parent, not_null<HistoryItem*> parent,
not_null<DocumentData*> document, not_null<DocumentData*> document,
bool skipPremiumEffect, bool skipPremiumEffect,
bool spoiler); bool spoiler,
crl::time ttlSeconds);
~MediaFile(); ~MediaFile();
std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override; std::unique_ptr<Media> clone(not_null<HistoryItem*> parent) override;
@ -278,6 +280,7 @@ public:
bool forwardedBecomesUnread() const override; bool forwardedBecomesUnread() const override;
bool dropForwardedInfo() const override; bool dropForwardedInfo() const override;
bool hasSpoiler() const override; bool hasSpoiler() const override;
crl::time ttlSeconds() const override;
bool updateInlineResultMedia(const MTPMessageMedia &media) override; bool updateInlineResultMedia(const MTPMessageMedia &media) override;
bool updateSentMedia(const MTPMessageMedia &media) override; bool updateSentMedia(const MTPMessageMedia &media) override;
@ -292,6 +295,9 @@ private:
bool _skipPremiumEffect = false; bool _skipPremiumEffect = false;
bool _spoiler = false; bool _spoiler = false;
// Video (unsupported) / Voice / Round.
crl::time _ttlSeconds = 0;
}; };
class MediaContact final : public Media { class MediaContact final : public Media {

View file

@ -242,7 +242,7 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
}); });
}, [&](const MTPDmessageMediaDocument &media) -> Result { }, [&](const MTPDmessageMediaDocument &media) -> Result {
const auto document = media.vdocument(); const auto document = media.vdocument();
if (media.vttl_seconds()) { if (media.vttl_seconds() && media.is_video()) {
LOG(("App Error: " LOG(("App Error: "
"Unexpected MTPMessageMediaDocument " "Unexpected MTPMessageMediaDocument "
"with ttl_seconds in CreateMedia.")); "with ttl_seconds in CreateMedia."));
@ -258,7 +258,8 @@ std::unique_ptr<Data::Media> HistoryItem::CreateMedia(
item, item,
item->history()->owner().processDocument(document), item->history()->owner().processDocument(document),
media.is_nopremium(), media.is_nopremium(),
media.is_spoiler()); media.is_spoiler(),
media.vttl_seconds().value_or_empty());
}, [](const MTPDdocumentEmpty &) -> Result { }, [](const MTPDdocumentEmpty &) -> Result {
return nullptr; return nullptr;
}); });
@ -354,7 +355,8 @@ HistoryItem::HistoryItem(
setServiceText({ setServiceText({
tr::lng_message_empty(tr::now, Ui::Text::WithEntities) tr::lng_message_empty(tr::now, Ui::Text::WithEntities)
}); });
} else if (checked == MediaCheckResult::HasTimeToLive) { } else if ((checked == MediaCheckResult::HasUnsupportedTimeToLive)
|| (checked == MediaCheckResult::HasExpiredMediaTimeToLive)) {
createServiceFromMtp(data); createServiceFromMtp(data);
applyTTL(data); applyTTL(data);
} else if (checked == MediaCheckResult::HasStoryMention) { } else if (checked == MediaCheckResult::HasStoryMention) {
@ -617,7 +619,8 @@ HistoryItem::HistoryItem(
this, this,
document, document,
skipPremiumEffect, skipPremiumEffect,
spoiler); spoiler,
/*ttlSeconds = */0);
setText(caption); setText(caption);
} }
@ -1659,7 +1662,8 @@ void HistoryItem::setStoryFields(not_null<Data::Story*> story) {
this, this,
document, document,
/*skipPremiumEffect=*/false, /*skipPremiumEffect=*/false,
spoiler); spoiler,
/*ttlSeconds = */0);
} }
setText(story->caption()); setText(story->caption());
} }
@ -3605,25 +3609,43 @@ void HistoryItem::createServiceFromMtp(const MTPDmessage &message) {
const auto ttl = data.vttl_seconds(); const auto ttl = data.vttl_seconds();
Assert(ttl != nullptr); Assert(ttl != nullptr);
setSelfDestruct(HistoryServiceSelfDestruct::Type::Video, *ttl); if (data.is_video()) {
if (out()) { setSelfDestruct(
setServiceText({ HistoryServiceSelfDestruct::Type::Video,
tr::lng_ttl_video_sent(tr::now, Ui::Text::WithEntities) *ttl);
}); if (out()) {
} else { setServiceText({
auto result = PreparedServiceText(); tr::lng_ttl_video_sent(
result.links.push_back(fromLink()); tr::now,
result.text = tr::lng_ttl_video_received( Ui::Text::WithEntities)
tr::now, });
lt_from, } else {
fromLinkText(), // Link 1. auto result = PreparedServiceText();
Ui::Text::WithEntities); result.links.push_back(fromLink());
setServiceText(std::move(result)); 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 { } else {
setServiceText({ auto text = (data.is_video()
tr::lng_ttl_video_expired(tr::now, Ui::Text::WithEntities) ? 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) { }, [&](const MTPDmessageMediaStory &data) {
setServiceText(prepareStoryMentionText()); setServiceText(prepareStoryMentionText());

View file

@ -451,7 +451,7 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaPhoto &data) { }, [](const MTPDmessageMediaPhoto &data) {
const auto photo = data.vphoto(); const auto photo = data.vphoto();
if (data.vttl_seconds()) { if (data.vttl_seconds()) {
return Result::HasTimeToLive; return Result::HasUnsupportedTimeToLive;
} else if (!photo) { } else if (!photo) {
return Result::Empty; return Result::Empty;
} }
@ -463,7 +463,11 @@ MediaCheckResult CheckMessageMedia(const MTPMessageMedia &media) {
}, [](const MTPDmessageMediaDocument &data) { }, [](const MTPDmessageMediaDocument &data) {
const auto document = data.vdocument(); const auto document = data.vdocument();
if (data.vttl_seconds()) { if (data.vttl_seconds()) {
return Result::HasTimeToLive; if (data.is_video()) {
return Result::HasUnsupportedTimeToLive;
} else if (!document) {
return Result::HasExpiredMediaTimeToLive;
}
} else if (!document) { } else if (!document) {
return Result::Empty; return Result::Empty;
} }

View file

@ -43,7 +43,8 @@ enum class MediaCheckResult {
Good, Good,
Unsupported, Unsupported,
Empty, Empty,
HasTimeToLive, HasExpiredMediaTimeToLive,
HasUnsupportedTimeToLive,
HasStoryMention, HasStoryMention,
}; };
[[nodiscard]] MediaCheckResult CheckMessageMedia( [[nodiscard]] MediaCheckResult CheckMessageMedia(

View file

@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "media/audio/media_audio.h" #include "media/audio/media_audio.h"
#include "media/player/media_player_instance.h" #include "media/player/media_player_instance.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "history/history_item_helpers.h" // PreparedServiceText.
#include "history/history.h" #include "history/history.h"
#include "core/click_handler_types.h" // kDocumentFilenameTooltipProperty. #include "core/click_handler_types.h" // kDocumentFilenameTooltipProperty.
#include "history/view/history_view_element.h" #include "history/view/history_view_element.h"
@ -47,6 +48,10 @@ namespace {
constexpr auto kAudioVoiceMsgUpdateView = crl::time(100); constexpr auto kAudioVoiceMsgUpdateView = crl::time(100);
[[nodiscard]] bool OncePlayable(not_null<HistoryItem*> item) {
return !item->out() && item->media()->ttlSeconds();
}
[[nodiscard]] QString CleanTagSymbols(const QString &value) { [[nodiscard]] QString CleanTagSymbols(const QString &value) {
auto result = QString(); auto result = QString();
const auto begin = value.begin(), end = value.end(); const auto begin = value.begin(), end = value.end();
@ -195,7 +200,8 @@ Document::Document(
not_null<DocumentData*> document) not_null<DocumentData*> document)
: File(parent, realParent) : File(parent, realParent)
, _data(document) { , _data(document) {
if (_data->isVideoMessage()) { const auto isRound = _data->isVideoMessage();
if (isRound) {
const auto &entry = _data->session().api().transcribes().entry( const auto &entry = _data->session().api().transcribes().entry(
realParent); realParent);
_transcribedRound = entry.shown; _transcribedRound = entry.shown;
@ -209,7 +215,49 @@ Document::Document(
_tooltipFilename.setTooltipText(named->name); _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::lifetime>();
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); setStatusSize(Ui::FileStatusSizeReady);
@ -273,9 +321,9 @@ void Document::createComponents(bool caption) {
_realParent->fullId()); _realParent->fullId());
} }
if (const auto voice = Get<HistoryDocumentVoice>()) { if (const auto voice = Get<HistoryDocumentVoice>()) {
voice->seekl = std::make_shared<VoiceSeekClickHandler>( voice->seekl = !OncePlayable(_parent->data())
_data, ? std::make_shared<VoiceSeekClickHandler>(_data, [](FullMsgId) {})
[](FullMsgId) {}); : nullptr;
if (_transcribedRound) { if (_transcribedRound) {
voice->round = std::make_unique<::Media::Player::RoundPainter>( voice->round = std::make_unique<::Media::Player::RoundPainter>(
_realParent); _realParent);

View file

@ -65,7 +65,8 @@ constexpr auto kMaxOriginalEntryLines = 8192;
parent, parent,
*document, *document,
skipPremiumEffect, skipPremiumEffect,
spoiler)); spoiler,
/*ttlSeconds = */0));
} else if (const auto photo = std::get_if<PhotoData*>(&item)) { } else if (const auto photo = std::get_if<PhotoData*>(&item)) {
result.push_back(std::make_unique<Data::MediaPhoto>( result.push_back(std::make_unique<Data::MediaPhoto>(
parent, parent,

View file

@ -752,13 +752,20 @@ void MainWidget::searchMessages(const QString &query, Dialogs::Key inChat) {
void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) { void MainWidget::handleAudioUpdate(const Media::Player::TrackState &state) {
using State = Media::Player::State; using State = Media::Player::State;
const auto document = state.id.audio(); const auto document = state.id.audio();
const auto item = session().data().message(state.id.contextId());
if (!Media::Player::IsStoppedOrStopping(state.state)) { 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) { } else if (state.state == State::StoppedAtStart) {
Media::Player::instance()->stopAndClose(); Media::Player::instance()->stopAndClose();
} }
if (const auto item = session().data().message(state.id.contextId())) { if (item) {
session().data().requestItemRepaint(item); session().data().requestItemRepaint(item);
} }
if (document) { if (document) {

View file

@ -503,6 +503,9 @@ bool Instance::moveInPlaylist(
} }
const auto jumpByItem = [&](not_null<HistoryItem*> item) { const auto jumpByItem = [&](not_null<HistoryItem*> item) {
if (const auto media = item->media()) { if (const auto media = item->media()) {
if (media->ttlSeconds()) {
return false;
}
if (const auto document = media->document()) { if (const auto document = media->document()) {
if (autonext) { if (autonext) {
_switchToNext.fire({ _switchToNext.fire({