From 6a415cf232dac6b277e94924da6c49af33b44e4d Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 30 Jan 2025 13:39:54 +0400 Subject: [PATCH] Support on-hover autoplay and position save. --- .../SourceFiles/core/local_url_handlers.cpp | 13 +- .../history/view/media/history_view_gif.cpp | 114 ++++++++++++------ .../history/view/media/history_view_gif.h | 5 + .../main/main_session_settings.cpp | 41 +------ .../SourceFiles/main/main_session_settings.h | 4 - .../media/player/media_player_instance.cpp | 20 +-- .../SourceFiles/storage/storage_account.cpp | 105 ++++++++++++++++ .../SourceFiles/storage/storage_account.h | 10 ++ .../window/window_session_controller.cpp | 8 +- 9 files changed, 223 insertions(+), 97 deletions(-) diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index 8aa7ef9ba..c1e678579 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -61,6 +61,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_privacy_security.h" #include "settings/settings_chat.h" #include "settings/settings_premium.h" +#include "storage/storage_account.h" #include "mainwidget.h" #include "main/main_account.h" #include "main/main_app_config.h" @@ -782,8 +783,8 @@ bool OpenMediaTimestamp( if (!controller) { return false; } - const auto time = match->captured(2).toInt(); - if (time < 0) { + const auto position = match->captured(2).toInt(); + if (position < 0) { return false; } const auto base = match->captured(1); @@ -796,7 +797,7 @@ bool OpenMediaTimestamp( const auto session = &controller->session(); const auto document = session->data().document(documentId); const auto context = session->data().message(itemId); - const auto timeMs = time * crl::time(1000); + const auto time = position * crl::time(1000); if (document->isVideoFile()) { controller->window().openInMediaView(Media::View::OpenRequest( controller, @@ -804,11 +805,9 @@ bool OpenMediaTimestamp( context, context ? context->topicRootId() : MsgId(0), false, - timeMs)); + time)); } else if (document->isSong() || document->isVoiceMessage()) { - session->settings().setMediaLastPlaybackPosition( - documentId, - timeMs); + session->local().setMediaLastPlaybackPosition(documentId, time); Media::Player::instance()->play({ document, itemId }); } return true; diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp index d865b6dbe..ad4f7ee8c 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.cpp @@ -58,6 +58,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_file_origin.h" #include "data/data_document_media.h" #include "data/data_web_page.h" +#include "storage/storage_account.h" #include "styles/style_chat.h" #include @@ -406,11 +407,17 @@ bool Gif::downloadInCorner() const { && !_data->inappPlaybackFailed(); } +bool Gif::autoplayUnderCursor() const { + return (_videoTimestamp || _hasVideoCover); +} + +bool Gif::underCursor() const { + return ClickHandler::getActive() == currentVideoLink(); +} + bool Gif::autoplayEnabled() const { if (_realParent->isSponsored()) { return true; - } else if (_videoTimestamp || _hasVideoCover) { - return false; } return Data::AutoDownload::ShouldAutoPlay( _data->session().settings().autoDownload(), @@ -425,6 +432,8 @@ bool Gif::hideMessageText() const { void Gif::draw(Painter &p, const PaintContext &context) const { if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; + _smallGroupPart = false; + ensureDataMediaCreated(); const auto item = _parent->data(); const auto loaded = dataLoaded(); @@ -481,11 +490,14 @@ void Gif::draw(Painter &p, const PaintContext &context) const { validateSpoilerImageCache(rthumb.size(), rounding); } - const auto startPlay = autoplay + const auto canStartPlay = autoplay && !_streamed && !activeRoundPlaying && !fullHiddenBySpoiler; - if (startPlay) { + const auto shouldBePlaying = !autoplayUnderCursor() || underCursor(); + if (!shouldBePlaying && _videoTimestamp != 0) { + const_cast(this)->stopAnimation(); + } else if (canStartPlay) { const_cast(this)->playAnimation(true); } else { checkStreamedIsStarted(); @@ -526,8 +538,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const { const auto skipDrawingContent = context.skipDrawingParts == PaintContext::SkipDrawingParts::Content; - if (streamed && !skipDrawingContent && !fullHiddenBySpoiler) { - auto paused = context.paused; + const auto drawStreamed = streamed && (shouldBePlaying || !_videoCover); + if (drawStreamed && !skipDrawingContent && !fullHiddenBySpoiler) { + auto paused = context.paused || !shouldBePlaying; auto request = ::Media::Streaming::FrameRequest{ .outer = QSize(usew, painth) * style::DevicePixelRatio(), .blurredBackground = true, @@ -594,8 +607,8 @@ void Gif::draw(Painter &p, const PaintContext &context) const { ensureDataMediaCreated(); validateThumbCache({ usew, painth }, isRound, rounding); p.drawImage(rthumb, _thumbCache); - paintTimestampMark(p, rthumb, rounding); } + paintTimestampMark(p, rthumb, rounding); if (revealed < 1.) { p.setOpacity(1. - revealed); @@ -885,7 +898,7 @@ void Gif::paintTimestampMark( Painter &p, QRect rthumb, std::optional rounding) const { - if (_videoTimestamp <= 0) { + if (_videoTimestamp <= 0 && _videoPosition < crl::time(200)) { return; } const auto convert = [](Ui::BubbleCornerRounding rounding) { @@ -902,15 +915,18 @@ void Gif::paintTimestampMark( ? convert(rounding->bottomRight) : st::roundRadiusSmall; const auto line = st::historyVideoTimestampProgressLine; - const auto duration = _data->duration() / 1000; + const auto duration = _data->duration(); + const auto position = (_videoPosition > 0) + ? _videoPosition + : (_videoTimestamp * crl::time(1000)); if (rthumb.height() <= line || rthumb.width() <= radiusl + radiusr - || _videoTimestamp >= duration) { + || position > duration) { return; } auto hq = PainterHighQualityEnabler(p); const auto used = rthumb.width() - radiusl - radiusr; - const auto progress = _videoTimestamp / float64(duration); + const auto progress = position / float64(duration); const auto edge = radiusl + int(base::SafeRound(used * progress)); const auto top = rthumb.y() + rthumb.height() - line; p.save(); @@ -1278,15 +1294,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const { : (isRound && _parent->data()->media()->ttlSeconds()) ? _openl // Overriden. : _spoiler->link) - : _data->uploading() - ? _cancell - : _realParent->isSending() - ? nullptr - : (dataLoaded() || _dataMedia->canBePlayed(_realParent)) - ? _openl - : _data->loading() - ? _cancell - : _savel; + : currentVideoLink(); } const auto checkBottomInfo = !inWebPage && (unwrapped || !bubble || isBubbleBottom()); @@ -1394,8 +1402,8 @@ void Gif::drawGrouped( || _data->displayLoading(); const auto st = context.st; const auto sti = context.imageStyle(); - const auto fullFeatured = fullFeaturedGrouped(sides); - const auto cornerDownload = fullFeatured && downloadInCorner(); + _smallGroupPart = !fullFeaturedGrouped(sides); + const auto cornerDownload = !_smallGroupPart && downloadInCorner(); const auto canBePlayed = _dataMedia->canBePlayed(_realParent); const auto revealed = _spoiler @@ -1406,16 +1414,22 @@ void Gif::drawGrouped( validateSpoilerImageCache(geometry.size(), rounding); } - const auto autoplay = fullFeatured + const auto autoplay = !_smallGroupPart && autoplayEnabled() && canBePlayed && CanPlayInline(_data); - const auto startPlay = autoplay && !_streamed; - if (startPlay) { + const auto canStartPlay = autoplay + && !_streamed + && !fullHiddenBySpoiler; + const auto shouldBePlaying = !autoplayUnderCursor() || underCursor(); + if (!shouldBePlaying && _videoTimestamp != 0) { + const_cast(this)->stopAnimation(); + } else if (canStartPlay) { const_cast(this)->playAnimation(true); } else { checkStreamedIsStarted(); } + const auto streamingMode = _streamed || autoplay; const auto activeOwnPlaying = activeOwnStreamed(); @@ -1468,7 +1482,9 @@ void Gif::drawGrouped( activeOwnPlaying->frozenStatusText = QString(); } p.drawImage(geometry, streamed->frame(request)); - if (!context.paused) { + const auto paused = context.paused + || (autoplayUnderCursor() && !underCursor()); + if (!paused) { streamed->markFrameShown(); } } @@ -1583,7 +1599,7 @@ void Gif::drawGrouped( } p.setOpacity(1.); } - if (fullFeatured) { + if (!_smallGroupPart) { drawCornerStatus(p, context, geometry.topLeft()); } } @@ -1596,8 +1612,7 @@ TextState Gif::getStateGrouped( if (!geometry.contains(point)) { return {}; } - const auto isFullFeaturedGrouped = fullFeaturedGrouped(sides); - if (isFullFeaturedGrouped) { + if (!_smallGroupPart) { const auto state = cornerStatusTextState( point, request, @@ -1607,20 +1622,27 @@ TextState Gif::getStateGrouped( } } ensureDataMediaCreated(); + auto link = (_spoiler && !_spoiler->revealed) ? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link) - : _data->uploading() + : currentVideoLink(); + return TextState(_parent, std::move(link)); +} + +ClickHandlerPtr Gif::currentVideoLink() const { + return _data->uploading() ? _cancell : _realParent->isSending() ? nullptr : dataLoaded() ? _openl - : (_data->loading() && !isFullFeaturedGrouped) + : (_data->loading() && _smallGroupPart) ? _cancell : _dataMedia->canBePlayed(_realParent) ? _openl + : _data->loading() + ? _cancell : _savel; - return TextState(_parent, std::move(link)); } void Gif::ensureDataMediaCreated() const { @@ -1859,7 +1881,8 @@ void Gif::updateStatusText() const { } const auto round = activeRoundStreamed(); const auto own = activeOwnStreamed(); - if (round || (own && own->frozenFrame.isNull() && _data->isVideoFile())) { + if (round || (own && _data->isVideoFile())) { + const auto frozen = !own->frozenFrame.isNull(); const auto streamed = round ? round : &own->instance; const auto state = streamed->player().prepareLegacyState(); if (state.length) { @@ -1869,9 +1892,17 @@ void Gif::updateStatusText() const { } else if (!::Media::Player::IsStoppedOrStopping(state.state)) { position = state.position; } - statusSize = -1 - int((state.length - position) / state.frequency + 1); + if (!frozen) { + statusSize = -1 - int((state.length - position) / state.frequency + 1); + } + _videoPosition = std::max( + position * crl::time(1000) / state.frequency, + crl::time(1)); } else { - statusSize = -1 - (_data->duration() / 1000); + if (!frozen) { + statusSize = -1 - (_data->duration() / 1000); + } + _videoPosition = 0; } } if (statusSize != _statusSize) { @@ -2031,6 +2062,10 @@ void Gif::startStreamedPlayer() const { //if (!_streamed->withSound) { options.mode = ::Media::Streaming::Mode::Video; options.loop = true; + options.position = _videoTimestamp + ? (_videoTimestamp * crl::time(1000)) + : _parent->history()->session().local().mediaLastPlaybackPosition( + _data->id); //} _streamed->instance.play(options); } @@ -2038,10 +2073,12 @@ void Gif::startStreamedPlayer() const { void Gif::checkStreamedIsStarted() const { if (!_streamed || _streamed->instance.playerLocked()) { return; - } else if (_streamed->instance.paused()) { - _streamed->instance.resume(); } - if (!_streamed->instance.active() && !_streamed->instance.failed()) { + if (_streamed->instance.active()) { + if (_streamed->instance.paused()) { + _streamed->instance.resume(); + } + } else if (!_streamed->instance.failed()) { startStreamedPlayer(); } } @@ -2054,6 +2091,7 @@ void Gif::setStreamed(std::unique_ptr value) { history()->owner().registerHeavyViewPart(_parent); togglePollingStory(true); } else if (removed) { + _videoPosition = 0; _parent->checkHeavyPart(); } } diff --git a/Telegram/SourceFiles/history/view/media/history_view_gif.h b/Telegram/SourceFiles/history/view/media/history_view_gif.h index 35a510c06..b7ce6575d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_gif.h +++ b/Telegram/SourceFiles/history/view/media/history_view_gif.h @@ -141,6 +141,8 @@ private: void dataMediaCreated() const; [[nodiscard]] bool autoplayEnabled() const; + [[nodiscard]] bool autoplayUnderCursor() const; + [[nodiscard]] bool underCursor() const; void playAnimation(bool autoplay) override; QSize countOptimalSize() override; @@ -210,6 +212,7 @@ private: QPoint point, StateRequest request, QPoint position) const; + [[nodiscard]] ClickHandlerPtr currentVideoLink() const; void togglePollingStory(bool enabled) const; @@ -228,12 +231,14 @@ private: QString _downloadSize; mutable QImage _thumbCache; mutable QImage _roundingMask; + mutable crl::time _videoPosition = 0; mutable TimeId _videoTimestamp = 0; mutable std::optional _thumbCacheRounding; mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbIsEllipse : 1 = false; mutable bool _pollingStory : 1 = false; mutable bool _purchasedPriceTag : 1 = false; + mutable bool _smallGroupPart : 1 = false; const bool _sensitiveSpoiler : 1 = false; const bool _hasVideoCover : 1 = false; diff --git a/Telegram/SourceFiles/main/main_session_settings.cpp b/Telegram/SourceFiles/main/main_session_settings.cpp index 253116dea..22a97db43 100644 --- a/Telegram/SourceFiles/main/main_session_settings.cpp +++ b/Telegram/SourceFiles/main/main_session_settings.cpp @@ -23,7 +23,6 @@ namespace { constexpr auto kLegacyCallsPeerToPeerNobody = 4; constexpr auto kVersionTag = -1; constexpr auto kVersion = 2; -constexpr auto kMaxSavedPlaybackPositions = 16; } // namespace @@ -38,8 +37,7 @@ QByteArray SessionSettings::serialize() const { + _groupStickersSectionHidden.size() * sizeof(quint64) + sizeof(qint32) * 4 + Serialize::bytearraySize(autoDownload) - + sizeof(qint32) * 5 - + _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64) + + sizeof(qint32) * 4 + sizeof(qint32) * 5 + sizeof(qint32) + (_mutePeriods.size() * sizeof(quint64)) @@ -71,11 +69,6 @@ QByteArray SessionSettings::serialize() const { << qint32(_archiveCollapsed.current() ? 1 : 0) << qint32(_archiveInMainMenu.current() ? 1 : 0) << qint32(_skipArchiveInSearch.current() ? 1 : 0) - << qint32(_mediaLastPlaybackPosition.size()); - for (const auto &[id, time] : _mediaLastPlaybackPosition) { - stream << quint64(id) << qint64(time); - } - stream << qint32(0) // very old _hiddenPinnedMessages.size()); << qint32(_dialogsFiltersEnabled ? 1 : 0) << qint32(_supportAllSilent ? 1 : 0) @@ -156,7 +149,6 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { qint32 appSuggestEmoji = app.suggestEmoji() ? 1 : 0; qint32 appSuggestStickersByEmoji = app.suggestStickersByEmoji() ? 1 : 0; qint32 appSpellcheckerEnabled = app.spellcheckerEnabled() ? 1 : 0; - std::vector> mediaLastPlaybackPosition; qint32 appVideoPlaybackSpeed = app.videoPlaybackSpeedSerialized(); QByteArray appVideoPipGeometry = app.videoPipGeometry(); std::vector appDictionariesEnabled; @@ -313,7 +305,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { "Bad data for SessionSettings::addFromSerialized()")); return; } - mediaLastPlaybackPosition.emplace_back(documentId, time); + // Old mediaLastPlaybackPosition. } } } @@ -486,7 +478,6 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) { _archiveCollapsed = (archiveCollapsed == 1); _archiveInMainMenu = (archiveInMainMenu == 1); _skipArchiveInSearch = (skipArchiveInSearch == 1); - _mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition); _hiddenPinnedMessages = std::move(hiddenPinnedMessages); _dialogsFiltersEnabled = (dialogsFiltersEnabled == 1); _supportAllSilent = (supportAllSilent == 1); @@ -567,34 +558,6 @@ rpl::producer SessionSettings::supportAllSearchResultsValue() const { return _supportAllSearchResults.value(); } -void SessionSettings::setMediaLastPlaybackPosition(DocumentId id, crl::time time) { - auto &map = _mediaLastPlaybackPosition; - const auto i = ranges::find( - map, - id, - &std::pair::first); - if (i != map.end()) { - if (time > 0) { - i->second = time; - } else { - map.erase(i); - } - } else if (time > 0) { - if (map.size() >= kMaxSavedPlaybackPositions) { - map.erase(map.begin()); - } - map.emplace_back(id, time); - } -} - -crl::time SessionSettings::mediaLastPlaybackPosition(DocumentId id) const { - const auto i = ranges::find( - _mediaLastPlaybackPosition, - id, - &std::pair::first); - return (i != _mediaLastPlaybackPosition.end()) ? i->second : 0; -} - void SessionSettings::setArchiveCollapsed(bool collapsed) { _archiveCollapsed = collapsed; } diff --git a/Telegram/SourceFiles/main/main_session_settings.h b/Telegram/SourceFiles/main/main_session_settings.h index c44b6f785..c171968e2 100644 --- a/Telegram/SourceFiles/main/main_session_settings.h +++ b/Telegram/SourceFiles/main/main_session_settings.h @@ -85,9 +85,6 @@ public: _groupEmojiSectionHidden.remove(peerId); } - void setMediaLastPlaybackPosition(DocumentId id, crl::time time); - [[nodiscard]] crl::time mediaLastPlaybackPosition(DocumentId id) const; - [[nodiscard]] Data::AutoDownload::Full &autoDownload() { return _autoDownload; } @@ -166,7 +163,6 @@ private: rpl::variable _archiveCollapsed = false; rpl::variable _archiveInMainMenu = false; rpl::variable _skipArchiveInSearch = false; - std::vector> _mediaLastPlaybackPosition; base::flat_map _hiddenPinnedMessages; bool _dialogsFiltersEnabled = false; int _photoEditorHintShowsCount = 0; diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 12792f370..de1932b7a 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -34,6 +34,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_account.h" // session->account().sessionChanges(). #include "main/main_session_settings.h" +#include "storage/storage_account.h" namespace Media { namespace Player { @@ -50,7 +51,8 @@ constexpr auto kIdsPreloadAfter = 28; constexpr auto kShufflePlaylistLimit = 10'000; constexpr auto kRememberShuffledOrderItems = 16; -constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes. +constexpr auto kMinLengthForSavePositionVideo = TimeId(60); // 1 minute. +constexpr auto kMinLengthForSavePositionMusic = 20 * TimeId(60); // 20. base::options::toggle OptionDisableAutoplayNext({ .id = kOptionDisableAutoplayNext, @@ -108,18 +110,20 @@ void finish(not_null instance) { void SaveLastPlaybackPosition( not_null document, const TrackState &state) { + const auto limit = document->isVideoFile() + ? kMinLengthForSavePositionVideo + : kMinLengthForSavePositionMusic; const auto time = (state.position == kTimeUnknown || state.length == kTimeUnknown || state.state == State::PausedAtEnd || IsStopped(state.state)) ? TimeId(0) - : (state.length >= kMinLengthForSavePosition * state.frequency) + : (state.length >= limit * state.frequency) ? (state.position / state.frequency) * crl::time(1000) : TimeId(0); auto &session = document->session(); - if (session.settings().mediaLastPlaybackPosition(document->id) != time) { - session.settings().setMediaLastPlaybackPosition(document->id, time); - session.saveSettingsDelayed(); + if (session.local().mediaLastPlaybackPosition(document->id) != time) { + session.local().setMediaLastPlaybackPosition(document->id, time); } } @@ -849,9 +853,9 @@ Streaming::PlaybackOptions Instance::streamingOptions( if (position >= 0) { result.position = position; } else if (document) { - auto &settings = document->session().settings(); - result.position = settings.mediaLastPlaybackPosition(document->id); - settings.setMediaLastPlaybackPosition(document->id, 0); + auto &local = document->session().local(); + result.position = local.mediaLastPlaybackPosition(document->id); + local.setMediaLastPlaybackPosition(document->id, 0); } else { result.position = 0; } diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index 468c291b3..7233e0f45 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -47,6 +47,7 @@ using Database = Cache::Database; constexpr auto kDelayedWriteTimeout = crl::time(1000); constexpr auto kWriteSearchSuggestionsDelay = 5 * crl::time(1000); +constexpr auto kMaxSavedPlaybackPositions = 256; constexpr auto kStickersVersionTag = quint32(-1); constexpr auto kStickersSerializeVersion = 4; @@ -96,6 +97,7 @@ enum { // Local Storage Keys lskWebviewTokens = 0x19, // data: QByteArray bots, QByteArray other lskRoundPlaceholder = 0x1a, // no data lskInlineBotsDownloads = 0x1b, // no data + lskMediaLastPlaybackPositions = 0x1c, // no data }; auto EmptyMessageDraftSources() @@ -228,6 +230,7 @@ base::flat_set Account::collectGoodNames() const { _searchSuggestionsKey, _roundPlaceholderKey, _inlineBotsDownloadsKey, + _mediaLastPlaybackPositionsKey, }; auto result = base::flat_set{ "map0", @@ -316,6 +319,7 @@ Account::ReadMapResult Account::readMapWith( quint64 searchSuggestionsKey = 0; quint64 roundPlaceholderKey = 0; quint64 inlineBotsDownloadsKey = 0; + quint64 mediaLastPlaybackPositionsKey = 0; QByteArray webviewStorageTokenBots, webviewStorageTokenOther; while (!map.stream.atEnd()) { quint32 keyType; @@ -431,6 +435,9 @@ Account::ReadMapResult Account::readMapWith( case lskInlineBotsDownloads: { map.stream >> inlineBotsDownloadsKey; } break; + case lskMediaLastPlaybackPositions: { + map.stream >> mediaLastPlaybackPositionsKey; + } break; case lskWebviewTokens: { map.stream >> webviewStorageTokenBots @@ -474,6 +481,7 @@ Account::ReadMapResult Account::readMapWith( _searchSuggestionsKey = searchSuggestionsKey; _roundPlaceholderKey = roundPlaceholderKey; _inlineBotsDownloadsKey = inlineBotsDownloadsKey; + _mediaLastPlaybackPositionsKey = mediaLastPlaybackPositionsKey; _oldMapVersion = mapData.version; _webviewStorageIdBots.token = webviewStorageTokenBots; _webviewStorageIdOther.token = webviewStorageTokenOther; @@ -590,6 +598,7 @@ void Account::writeMap() { } if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64); + if (_mediaLastPlaybackPositionsKey) mapSize += sizeof(quint32) + sizeof(quint64); EncryptedDescriptor mapData(mapSize); if (!self.isEmpty()) { @@ -668,6 +677,10 @@ void Account::writeMap() { mapData.stream << quint32(lskInlineBotsDownloads); mapData.stream << quint64(_inlineBotsDownloadsKey); } + if (_mediaLastPlaybackPositionsKey) { + mapData.stream << quint32(lskMediaLastPlaybackPositions); + mapData.stream << quint64(_mediaLastPlaybackPositionsKey); + } map.writeEncrypted(mapData, _localKey); _mapChanged = false; @@ -699,6 +712,7 @@ void Account::reset() { _searchSuggestionsKey = 0; _roundPlaceholderKey = 0; _inlineBotsDownloadsKey = 0; + _mediaLastPlaybackPositionsKey = 0; _oldMapVersion = 0; _fileLocations.clear(); _fileLocationPairs.clear(); @@ -709,6 +723,7 @@ void Account::reset() { _cacheTotalTimeLimit = Database::Settings().totalTimeLimit; _cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit; _cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit; + _mediaLastPlaybackPosition.clear(); const auto wvbots = _webviewStorageIdBots.path; const auto wvother = _webviewStorageIdOther.path; @@ -2943,6 +2958,96 @@ Export::Settings Account::readExportSettings() { : Export::Settings(); } +void Account::setMediaLastPlaybackPosition(DocumentId id, crl::time time) { + auto &map = _mediaLastPlaybackPosition; + const auto i = ranges::find( + map, + id, + &std::pair::first); + if (i != map.end()) { + if (time > 0) { + if (i->second == time) { + return; + } + i->second = time; + std::rotate(i, i + 1, map.end()); + } else { + map.erase(i); + } + } else if (time > 0) { + if (map.size() >= kMaxSavedPlaybackPositions) { + map.erase(map.begin()); + } + map.emplace_back(id, time); + } + writeMediaLastPlaybackPositions(); +} + +crl::time Account::mediaLastPlaybackPosition(DocumentId id) const { + const_cast(this)->readMediaLastPlaybackPositions(); + const auto i = ranges::find( + _mediaLastPlaybackPosition, + id, + &std::pair::first); + return (i != _mediaLastPlaybackPosition.end()) ? i->second : 0; +} + +void Account::writeMediaLastPlaybackPositions() { + if (_mediaLastPlaybackPosition.empty()) { + if (_mediaLastPlaybackPositionsKey) { + ClearKey(_mediaLastPlaybackPositionsKey, _basePath); + _mediaLastPlaybackPositionsKey = 0; + writeMapDelayed(); + } + return; + } + if (!_mediaLastPlaybackPositionsKey) { + _mediaLastPlaybackPositionsKey = GenerateKey(_basePath); + writeMapQueued(); + } + quint32 size = sizeof(quint32) + + _mediaLastPlaybackPosition.size() * sizeof(quint64) * 2; + EncryptedDescriptor data(size); + data.stream << quint32(_mediaLastPlaybackPosition.size()); + for (const auto &[id, time] : _mediaLastPlaybackPosition) { + data.stream << quint64(id) << qint64(time); + } + + FileWriteDescriptor file(_mediaLastPlaybackPositionsKey, _basePath); + file.writeEncrypted(data, _localKey); +} + +void Account::readMediaLastPlaybackPositions() { + if (_mediaLastPlaybackPositionsRead) { + return; + } + _mediaLastPlaybackPositionsRead = true; + if (!_mediaLastPlaybackPositionsKey) { + return; + } + + FileReadDescriptor file; + if (!ReadEncryptedFile( + file, + _mediaLastPlaybackPositionsKey, + _basePath, + _localKey)) { + ClearKey(_mediaLastPlaybackPositionsKey, _basePath); + _mediaLastPlaybackPositionsKey = 0; + writeMapDelayed(); + return; + } + + quint32 size = 0; + file.stream >> size; + for (auto i = 0; i < size; ++i) { + quint64 id = 0; + qint64 time = 0; + file.stream >> id >> time; + _mediaLastPlaybackPosition.emplace_back(DocumentId(id), time); + } +} + void Account::writeSearchSuggestionsDelayed() { Expects(_owner->sessionExists()); diff --git a/Telegram/SourceFiles/storage/storage_account.h b/Telegram/SourceFiles/storage/storage_account.h index 5ac7194b3..497ec55ec 100644 --- a/Telegram/SourceFiles/storage/storage_account.h +++ b/Telegram/SourceFiles/storage/storage_account.h @@ -150,6 +150,9 @@ public: void writeExportSettings(const Export::Settings &settings); [[nodiscard]] Export::Settings readExportSettings(); + void setMediaLastPlaybackPosition(DocumentId id, crl::time time); + [[nodiscard]] crl::time mediaLastPlaybackPosition(DocumentId id) const; + void writeSearchSuggestionsDelayed(); void writeSearchSuggestionsIfNeeded(); void writeSearchSuggestions(); @@ -261,6 +264,9 @@ private: void readTrustedBots(); void writeTrustedBots(); + void readMediaLastPlaybackPositions(); + void writeMediaLastPlaybackPositions(); + std::optional saveRecentHashtags( Fn getPack, const QString &text); @@ -311,6 +317,7 @@ private: FileKey _searchSuggestionsKey = 0; FileKey _roundPlaceholderKey = 0; FileKey _inlineBotsDownloadsKey = 0; + FileKey _mediaLastPlaybackPositionsKey = 0; qint64 _cacheTotalSizeLimit = 0; qint64 _cacheBigFileTotalSizeLimit = 0; @@ -323,6 +330,9 @@ private: bool _recentHashtagsAndBotsWereRead = false; bool _searchSuggestionsRead = false; bool _inlineBotsDownloadsRead = false; + bool _mediaLastPlaybackPositionsRead = false; + + std::vector> _mediaLastPlaybackPosition; Webview::StorageId _webviewStorageIdBots; Webview::StorageId _webviewStorageIdOther; diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index a4c5fced8..7e3411c60 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -89,6 +89,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "support/support_helper.h" #include "storage/file_upload.h" #include "storage/download_manager_mtproto.h" +#include "storage/storage_account.h" #include "window/themes/window_theme.h" #include "window/window_peer_menu.h" #include "window/window_session_controller_link_info.h" @@ -2789,13 +2790,18 @@ void SessionController::openDocument( } else if (showInMediaView) { using namespace Media::View; const auto timestamp = item ? ExtractVideoTimestamp(item) : 0; + const auto usedTimestamp = videoTimestampOverride + ? ((*videoTimestampOverride) * crl::time(1000)) + : timestamp + ? (timestamp * crl::time(1000)) + : session().local().mediaLastPlaybackPosition(document->id); _window->openInMediaView(OpenRequest( this, document, item, message.topicRootId, false, - videoTimestampOverride.value_or(timestamp) * crl::time(1000))); + usedTimestamp)); return; } Data::ResolveDocument(this, document, item, message.topicRootId);