Support on-hover autoplay and position save.

This commit is contained in:
John Preston 2025-01-30 13:39:54 +04:00
parent e8034189df
commit 6a415cf232
9 changed files with 223 additions and 97 deletions

View file

@ -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;

View file

@ -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 <QSvgRenderer>
@ -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<Gif*>(this)->stopAnimation();
} else if (canStartPlay) {
const_cast<Gif*>(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<Ui::BubbleRounding> 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<Gif*>(this)->stopAnimation();
} else if (canStartPlay) {
const_cast<Gif*>(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<Streamed> value) {
history()->owner().registerHeavyViewPart(_parent);
togglePollingStory(true);
} else if (removed) {
_videoPosition = 0;
_parent->checkHeavyPart();
}
}

View file

@ -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<Ui::BubbleRounding> _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;

View file

@ -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<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
qint32 appVideoPlaybackSpeed = app.videoPlaybackSpeedSerialized();
QByteArray appVideoPipGeometry = app.videoPipGeometry();
std::vector<int> 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<bool> 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<DocumentId, crl::time>::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<DocumentId, crl::time>::first);
return (i != _mediaLastPlaybackPosition.end()) ? i->second : 0;
}
void SessionSettings::setArchiveCollapsed(bool collapsed) {
_archiveCollapsed = collapsed;
}

View file

@ -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<bool> _archiveCollapsed = false;
rpl::variable<bool> _archiveInMainMenu = false;
rpl::variable<bool> _skipArchiveInSearch = false;
std::vector<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition;
base::flat_map<ThreadId, MsgId> _hiddenPinnedMessages;
bool _dialogsFiltersEnabled = false;
int _photoEditorHintShowsCount = 0;

View file

@ -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<Audio::Instance*> instance) {
void SaveLastPlaybackPosition(
not_null<DocumentData*> 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;
}

View file

@ -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<QString> Account::collectGoodNames() const {
_searchSuggestionsKey,
_roundPlaceholderKey,
_inlineBotsDownloadsKey,
_mediaLastPlaybackPositionsKey,
};
auto result = base::flat_set<QString>{
"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<DocumentId, crl::time>::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<Account*>(this)->readMediaLastPlaybackPositions();
const auto i = ranges::find(
_mediaLastPlaybackPosition,
id,
&std::pair<DocumentId, crl::time>::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());

View file

@ -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<RecentHashtagPack> saveRecentHashtags(
Fn<RecentHashtagPack()> 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<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition;
Webview::StorageId _webviewStorageIdBots;
Webview::StorageId _webviewStorageIdOther;

View file

@ -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);