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_privacy_security.h"
#include "settings/settings_chat.h" #include "settings/settings_chat.h"
#include "settings/settings_premium.h" #include "settings/settings_premium.h"
#include "storage/storage_account.h"
#include "mainwidget.h" #include "mainwidget.h"
#include "main/main_account.h" #include "main/main_account.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
@ -782,8 +783,8 @@ bool OpenMediaTimestamp(
if (!controller) { if (!controller) {
return false; return false;
} }
const auto time = match->captured(2).toInt(); const auto position = match->captured(2).toInt();
if (time < 0) { if (position < 0) {
return false; return false;
} }
const auto base = match->captured(1); const auto base = match->captured(1);
@ -796,7 +797,7 @@ bool OpenMediaTimestamp(
const auto session = &controller->session(); const auto session = &controller->session();
const auto document = session->data().document(documentId); const auto document = session->data().document(documentId);
const auto context = session->data().message(itemId); const auto context = session->data().message(itemId);
const auto timeMs = time * crl::time(1000); const auto time = position * crl::time(1000);
if (document->isVideoFile()) { if (document->isVideoFile()) {
controller->window().openInMediaView(Media::View::OpenRequest( controller->window().openInMediaView(Media::View::OpenRequest(
controller, controller,
@ -804,11 +805,9 @@ bool OpenMediaTimestamp(
context, context,
context ? context->topicRootId() : MsgId(0), context ? context->topicRootId() : MsgId(0),
false, false,
timeMs)); time));
} else if (document->isSong() || document->isVoiceMessage()) { } else if (document->isSong() || document->isVoiceMessage()) {
session->settings().setMediaLastPlaybackPosition( session->local().setMediaLastPlaybackPosition(documentId, time);
documentId,
timeMs);
Media::Player::instance()->play({ document, itemId }); Media::Player::instance()->play({ document, itemId });
} }
return true; 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_file_origin.h"
#include "data/data_document_media.h" #include "data/data_document_media.h"
#include "data/data_web_page.h" #include "data/data_web_page.h"
#include "storage/storage_account.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include <QSvgRenderer> #include <QSvgRenderer>
@ -406,11 +407,17 @@ bool Gif::downloadInCorner() const {
&& !_data->inappPlaybackFailed(); && !_data->inappPlaybackFailed();
} }
bool Gif::autoplayUnderCursor() const {
return (_videoTimestamp || _hasVideoCover);
}
bool Gif::underCursor() const {
return ClickHandler::getActive() == currentVideoLink();
}
bool Gif::autoplayEnabled() const { bool Gif::autoplayEnabled() const {
if (_realParent->isSponsored()) { if (_realParent->isSponsored()) {
return true; return true;
} else if (_videoTimestamp || _hasVideoCover) {
return false;
} }
return Data::AutoDownload::ShouldAutoPlay( return Data::AutoDownload::ShouldAutoPlay(
_data->session().settings().autoDownload(), _data->session().settings().autoDownload(),
@ -425,6 +432,8 @@ bool Gif::hideMessageText() const {
void Gif::draw(Painter &p, const PaintContext &context) const { void Gif::draw(Painter &p, const PaintContext &context) const {
if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return; if (width() < st::msgPadding.left() + st::msgPadding.right() + 1) return;
_smallGroupPart = false;
ensureDataMediaCreated(); ensureDataMediaCreated();
const auto item = _parent->data(); const auto item = _parent->data();
const auto loaded = dataLoaded(); const auto loaded = dataLoaded();
@ -481,11 +490,14 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
validateSpoilerImageCache(rthumb.size(), rounding); validateSpoilerImageCache(rthumb.size(), rounding);
} }
const auto startPlay = autoplay const auto canStartPlay = autoplay
&& !_streamed && !_streamed
&& !activeRoundPlaying && !activeRoundPlaying
&& !fullHiddenBySpoiler; && !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); const_cast<Gif*>(this)->playAnimation(true);
} else { } else {
checkStreamedIsStarted(); checkStreamedIsStarted();
@ -526,8 +538,9 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
const auto skipDrawingContent = context.skipDrawingParts const auto skipDrawingContent = context.skipDrawingParts
== PaintContext::SkipDrawingParts::Content; == PaintContext::SkipDrawingParts::Content;
if (streamed && !skipDrawingContent && !fullHiddenBySpoiler) { const auto drawStreamed = streamed && (shouldBePlaying || !_videoCover);
auto paused = context.paused; if (drawStreamed && !skipDrawingContent && !fullHiddenBySpoiler) {
auto paused = context.paused || !shouldBePlaying;
auto request = ::Media::Streaming::FrameRequest{ auto request = ::Media::Streaming::FrameRequest{
.outer = QSize(usew, painth) * style::DevicePixelRatio(), .outer = QSize(usew, painth) * style::DevicePixelRatio(),
.blurredBackground = true, .blurredBackground = true,
@ -594,8 +607,8 @@ void Gif::draw(Painter &p, const PaintContext &context) const {
ensureDataMediaCreated(); ensureDataMediaCreated();
validateThumbCache({ usew, painth }, isRound, rounding); validateThumbCache({ usew, painth }, isRound, rounding);
p.drawImage(rthumb, _thumbCache); p.drawImage(rthumb, _thumbCache);
paintTimestampMark(p, rthumb, rounding);
} }
paintTimestampMark(p, rthumb, rounding);
if (revealed < 1.) { if (revealed < 1.) {
p.setOpacity(1. - revealed); p.setOpacity(1. - revealed);
@ -885,7 +898,7 @@ void Gif::paintTimestampMark(
Painter &p, Painter &p,
QRect rthumb, QRect rthumb,
std::optional<Ui::BubbleRounding> rounding) const { std::optional<Ui::BubbleRounding> rounding) const {
if (_videoTimestamp <= 0) { if (_videoTimestamp <= 0 && _videoPosition < crl::time(200)) {
return; return;
} }
const auto convert = [](Ui::BubbleCornerRounding rounding) { const auto convert = [](Ui::BubbleCornerRounding rounding) {
@ -902,15 +915,18 @@ void Gif::paintTimestampMark(
? convert(rounding->bottomRight) ? convert(rounding->bottomRight)
: st::roundRadiusSmall; : st::roundRadiusSmall;
const auto line = st::historyVideoTimestampProgressLine; 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 if (rthumb.height() <= line
|| rthumb.width() <= radiusl + radiusr || rthumb.width() <= radiusl + radiusr
|| _videoTimestamp >= duration) { || position > duration) {
return; return;
} }
auto hq = PainterHighQualityEnabler(p); auto hq = PainterHighQualityEnabler(p);
const auto used = rthumb.width() - radiusl - radiusr; 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 edge = radiusl + int(base::SafeRound(used * progress));
const auto top = rthumb.y() + rthumb.height() - line; const auto top = rthumb.y() + rthumb.height() - line;
p.save(); p.save();
@ -1278,15 +1294,7 @@ TextState Gif::textState(QPoint point, StateRequest request) const {
: (isRound && _parent->data()->media()->ttlSeconds()) : (isRound && _parent->data()->media()->ttlSeconds())
? _openl // Overriden. ? _openl // Overriden.
: _spoiler->link) : _spoiler->link)
: _data->uploading() : currentVideoLink();
? _cancell
: _realParent->isSending()
? nullptr
: (dataLoaded() || _dataMedia->canBePlayed(_realParent))
? _openl
: _data->loading()
? _cancell
: _savel;
} }
const auto checkBottomInfo = !inWebPage const auto checkBottomInfo = !inWebPage
&& (unwrapped || !bubble || isBubbleBottom()); && (unwrapped || !bubble || isBubbleBottom());
@ -1394,8 +1402,8 @@ void Gif::drawGrouped(
|| _data->displayLoading(); || _data->displayLoading();
const auto st = context.st; const auto st = context.st;
const auto sti = context.imageStyle(); const auto sti = context.imageStyle();
const auto fullFeatured = fullFeaturedGrouped(sides); _smallGroupPart = !fullFeaturedGrouped(sides);
const auto cornerDownload = fullFeatured && downloadInCorner(); const auto cornerDownload = !_smallGroupPart && downloadInCorner();
const auto canBePlayed = _dataMedia->canBePlayed(_realParent); const auto canBePlayed = _dataMedia->canBePlayed(_realParent);
const auto revealed = _spoiler const auto revealed = _spoiler
@ -1406,16 +1414,22 @@ void Gif::drawGrouped(
validateSpoilerImageCache(geometry.size(), rounding); validateSpoilerImageCache(geometry.size(), rounding);
} }
const auto autoplay = fullFeatured const auto autoplay = !_smallGroupPart
&& autoplayEnabled() && autoplayEnabled()
&& canBePlayed && canBePlayed
&& CanPlayInline(_data); && CanPlayInline(_data);
const auto startPlay = autoplay && !_streamed; const auto canStartPlay = autoplay
if (startPlay) { && !_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); const_cast<Gif*>(this)->playAnimation(true);
} else { } else {
checkStreamedIsStarted(); checkStreamedIsStarted();
} }
const auto streamingMode = _streamed || autoplay; const auto streamingMode = _streamed || autoplay;
const auto activeOwnPlaying = activeOwnStreamed(); const auto activeOwnPlaying = activeOwnStreamed();
@ -1468,7 +1482,9 @@ void Gif::drawGrouped(
activeOwnPlaying->frozenStatusText = QString(); activeOwnPlaying->frozenStatusText = QString();
} }
p.drawImage(geometry, streamed->frame(request)); p.drawImage(geometry, streamed->frame(request));
if (!context.paused) { const auto paused = context.paused
|| (autoplayUnderCursor() && !underCursor());
if (!paused) {
streamed->markFrameShown(); streamed->markFrameShown();
} }
} }
@ -1583,7 +1599,7 @@ void Gif::drawGrouped(
} }
p.setOpacity(1.); p.setOpacity(1.);
} }
if (fullFeatured) { if (!_smallGroupPart) {
drawCornerStatus(p, context, geometry.topLeft()); drawCornerStatus(p, context, geometry.topLeft());
} }
} }
@ -1596,8 +1612,7 @@ TextState Gif::getStateGrouped(
if (!geometry.contains(point)) { if (!geometry.contains(point)) {
return {}; return {};
} }
const auto isFullFeaturedGrouped = fullFeaturedGrouped(sides); if (!_smallGroupPart) {
if (isFullFeaturedGrouped) {
const auto state = cornerStatusTextState( const auto state = cornerStatusTextState(
point, point,
request, request,
@ -1607,20 +1622,27 @@ TextState Gif::getStateGrouped(
} }
} }
ensureDataMediaCreated(); ensureDataMediaCreated();
auto link = (_spoiler && !_spoiler->revealed) auto link = (_spoiler && !_spoiler->revealed)
? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link) ? (_sensitiveSpoiler ? spoilerTagLink() : _spoiler->link)
: _data->uploading() : currentVideoLink();
return TextState(_parent, std::move(link));
}
ClickHandlerPtr Gif::currentVideoLink() const {
return _data->uploading()
? _cancell ? _cancell
: _realParent->isSending() : _realParent->isSending()
? nullptr ? nullptr
: dataLoaded() : dataLoaded()
? _openl ? _openl
: (_data->loading() && !isFullFeaturedGrouped) : (_data->loading() && _smallGroupPart)
? _cancell ? _cancell
: _dataMedia->canBePlayed(_realParent) : _dataMedia->canBePlayed(_realParent)
? _openl ? _openl
: _data->loading()
? _cancell
: _savel; : _savel;
return TextState(_parent, std::move(link));
} }
void Gif::ensureDataMediaCreated() const { void Gif::ensureDataMediaCreated() const {
@ -1859,7 +1881,8 @@ void Gif::updateStatusText() const {
} }
const auto round = activeRoundStreamed(); const auto round = activeRoundStreamed();
const auto own = activeOwnStreamed(); 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 streamed = round ? round : &own->instance;
const auto state = streamed->player().prepareLegacyState(); const auto state = streamed->player().prepareLegacyState();
if (state.length) { if (state.length) {
@ -1869,9 +1892,17 @@ void Gif::updateStatusText() const {
} else if (!::Media::Player::IsStoppedOrStopping(state.state)) { } else if (!::Media::Player::IsStoppedOrStopping(state.state)) {
position = state.position; 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 { } else {
statusSize = -1 - (_data->duration() / 1000); if (!frozen) {
statusSize = -1 - (_data->duration() / 1000);
}
_videoPosition = 0;
} }
} }
if (statusSize != _statusSize) { if (statusSize != _statusSize) {
@ -2031,6 +2062,10 @@ void Gif::startStreamedPlayer() const {
//if (!_streamed->withSound) { //if (!_streamed->withSound) {
options.mode = ::Media::Streaming::Mode::Video; options.mode = ::Media::Streaming::Mode::Video;
options.loop = true; options.loop = true;
options.position = _videoTimestamp
? (_videoTimestamp * crl::time(1000))
: _parent->history()->session().local().mediaLastPlaybackPosition(
_data->id);
//} //}
_streamed->instance.play(options); _streamed->instance.play(options);
} }
@ -2038,10 +2073,12 @@ void Gif::startStreamedPlayer() const {
void Gif::checkStreamedIsStarted() const { void Gif::checkStreamedIsStarted() const {
if (!_streamed || _streamed->instance.playerLocked()) { if (!_streamed || _streamed->instance.playerLocked()) {
return; 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(); startStreamedPlayer();
} }
} }
@ -2054,6 +2091,7 @@ void Gif::setStreamed(std::unique_ptr<Streamed> value) {
history()->owner().registerHeavyViewPart(_parent); history()->owner().registerHeavyViewPart(_parent);
togglePollingStory(true); togglePollingStory(true);
} else if (removed) { } else if (removed) {
_videoPosition = 0;
_parent->checkHeavyPart(); _parent->checkHeavyPart();
} }
} }

View file

@ -141,6 +141,8 @@ private:
void dataMediaCreated() const; void dataMediaCreated() const;
[[nodiscard]] bool autoplayEnabled() const; [[nodiscard]] bool autoplayEnabled() const;
[[nodiscard]] bool autoplayUnderCursor() const;
[[nodiscard]] bool underCursor() const;
void playAnimation(bool autoplay) override; void playAnimation(bool autoplay) override;
QSize countOptimalSize() override; QSize countOptimalSize() override;
@ -210,6 +212,7 @@ private:
QPoint point, QPoint point,
StateRequest request, StateRequest request,
QPoint position) const; QPoint position) const;
[[nodiscard]] ClickHandlerPtr currentVideoLink() const;
void togglePollingStory(bool enabled) const; void togglePollingStory(bool enabled) const;
@ -228,12 +231,14 @@ private:
QString _downloadSize; QString _downloadSize;
mutable QImage _thumbCache; mutable QImage _thumbCache;
mutable QImage _roundingMask; mutable QImage _roundingMask;
mutable crl::time _videoPosition = 0;
mutable TimeId _videoTimestamp = 0; mutable TimeId _videoTimestamp = 0;
mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding; mutable std::optional<Ui::BubbleRounding> _thumbCacheRounding;
mutable bool _thumbCacheBlurred : 1 = false; mutable bool _thumbCacheBlurred : 1 = false;
mutable bool _thumbIsEllipse : 1 = false; mutable bool _thumbIsEllipse : 1 = false;
mutable bool _pollingStory : 1 = false; mutable bool _pollingStory : 1 = false;
mutable bool _purchasedPriceTag : 1 = false; mutable bool _purchasedPriceTag : 1 = false;
mutable bool _smallGroupPart : 1 = false;
const bool _sensitiveSpoiler : 1 = false; const bool _sensitiveSpoiler : 1 = false;
const bool _hasVideoCover : 1 = false; const bool _hasVideoCover : 1 = false;

View file

@ -23,7 +23,6 @@ namespace {
constexpr auto kLegacyCallsPeerToPeerNobody = 4; constexpr auto kLegacyCallsPeerToPeerNobody = 4;
constexpr auto kVersionTag = -1; constexpr auto kVersionTag = -1;
constexpr auto kVersion = 2; constexpr auto kVersion = 2;
constexpr auto kMaxSavedPlaybackPositions = 16;
} // namespace } // namespace
@ -38,8 +37,7 @@ QByteArray SessionSettings::serialize() const {
+ _groupStickersSectionHidden.size() * sizeof(quint64) + _groupStickersSectionHidden.size() * sizeof(quint64)
+ sizeof(qint32) * 4 + sizeof(qint32) * 4
+ Serialize::bytearraySize(autoDownload) + Serialize::bytearraySize(autoDownload)
+ sizeof(qint32) * 5 + sizeof(qint32) * 4
+ _mediaLastPlaybackPosition.size() * 2 * sizeof(quint64)
+ sizeof(qint32) * 5 + sizeof(qint32) * 5
+ sizeof(qint32) + sizeof(qint32)
+ (_mutePeriods.size() * sizeof(quint64)) + (_mutePeriods.size() * sizeof(quint64))
@ -71,11 +69,6 @@ QByteArray SessionSettings::serialize() const {
<< qint32(_archiveCollapsed.current() ? 1 : 0) << qint32(_archiveCollapsed.current() ? 1 : 0)
<< qint32(_archiveInMainMenu.current() ? 1 : 0) << qint32(_archiveInMainMenu.current() ? 1 : 0)
<< qint32(_skipArchiveInSearch.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(0) // very old _hiddenPinnedMessages.size());
<< qint32(_dialogsFiltersEnabled ? 1 : 0) << qint32(_dialogsFiltersEnabled ? 1 : 0)
<< qint32(_supportAllSilent ? 1 : 0) << qint32(_supportAllSilent ? 1 : 0)
@ -156,7 +149,6 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
qint32 appSuggestEmoji = app.suggestEmoji() ? 1 : 0; qint32 appSuggestEmoji = app.suggestEmoji() ? 1 : 0;
qint32 appSuggestStickersByEmoji = app.suggestStickersByEmoji() ? 1 : 0; qint32 appSuggestStickersByEmoji = app.suggestStickersByEmoji() ? 1 : 0;
qint32 appSpellcheckerEnabled = app.spellcheckerEnabled() ? 1 : 0; qint32 appSpellcheckerEnabled = app.spellcheckerEnabled() ? 1 : 0;
std::vector<std::pair<DocumentId, crl::time>> mediaLastPlaybackPosition;
qint32 appVideoPlaybackSpeed = app.videoPlaybackSpeedSerialized(); qint32 appVideoPlaybackSpeed = app.videoPlaybackSpeedSerialized();
QByteArray appVideoPipGeometry = app.videoPipGeometry(); QByteArray appVideoPipGeometry = app.videoPipGeometry();
std::vector<int> appDictionariesEnabled; std::vector<int> appDictionariesEnabled;
@ -313,7 +305,7 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
"Bad data for SessionSettings::addFromSerialized()")); "Bad data for SessionSettings::addFromSerialized()"));
return; return;
} }
mediaLastPlaybackPosition.emplace_back(documentId, time); // Old mediaLastPlaybackPosition.
} }
} }
} }
@ -486,7 +478,6 @@ void SessionSettings::addFromSerialized(const QByteArray &serialized) {
_archiveCollapsed = (archiveCollapsed == 1); _archiveCollapsed = (archiveCollapsed == 1);
_archiveInMainMenu = (archiveInMainMenu == 1); _archiveInMainMenu = (archiveInMainMenu == 1);
_skipArchiveInSearch = (skipArchiveInSearch == 1); _skipArchiveInSearch = (skipArchiveInSearch == 1);
_mediaLastPlaybackPosition = std::move(mediaLastPlaybackPosition);
_hiddenPinnedMessages = std::move(hiddenPinnedMessages); _hiddenPinnedMessages = std::move(hiddenPinnedMessages);
_dialogsFiltersEnabled = (dialogsFiltersEnabled == 1); _dialogsFiltersEnabled = (dialogsFiltersEnabled == 1);
_supportAllSilent = (supportAllSilent == 1); _supportAllSilent = (supportAllSilent == 1);
@ -567,34 +558,6 @@ rpl::producer<bool> SessionSettings::supportAllSearchResultsValue() const {
return _supportAllSearchResults.value(); 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) { void SessionSettings::setArchiveCollapsed(bool collapsed) {
_archiveCollapsed = collapsed; _archiveCollapsed = collapsed;
} }

View file

@ -85,9 +85,6 @@ public:
_groupEmojiSectionHidden.remove(peerId); _groupEmojiSectionHidden.remove(peerId);
} }
void setMediaLastPlaybackPosition(DocumentId id, crl::time time);
[[nodiscard]] crl::time mediaLastPlaybackPosition(DocumentId id) const;
[[nodiscard]] Data::AutoDownload::Full &autoDownload() { [[nodiscard]] Data::AutoDownload::Full &autoDownload() {
return _autoDownload; return _autoDownload;
} }
@ -166,7 +163,6 @@ private:
rpl::variable<bool> _archiveCollapsed = false; rpl::variable<bool> _archiveCollapsed = false;
rpl::variable<bool> _archiveInMainMenu = false; rpl::variable<bool> _archiveInMainMenu = false;
rpl::variable<bool> _skipArchiveInSearch = false; rpl::variable<bool> _skipArchiveInSearch = false;
std::vector<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition;
base::flat_map<ThreadId, MsgId> _hiddenPinnedMessages; base::flat_map<ThreadId, MsgId> _hiddenPinnedMessages;
bool _dialogsFiltersEnabled = false; bool _dialogsFiltersEnabled = false;
int _photoEditorHintShowsCount = 0; 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_session.h"
#include "main/main_account.h" // session->account().sessionChanges(). #include "main/main_account.h" // session->account().sessionChanges().
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "storage/storage_account.h"
namespace Media { namespace Media {
namespace Player { namespace Player {
@ -50,7 +51,8 @@ constexpr auto kIdsPreloadAfter = 28;
constexpr auto kShufflePlaylistLimit = 10'000; constexpr auto kShufflePlaylistLimit = 10'000;
constexpr auto kRememberShuffledOrderItems = 16; 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({ base::options::toggle OptionDisableAutoplayNext({
.id = kOptionDisableAutoplayNext, .id = kOptionDisableAutoplayNext,
@ -108,18 +110,20 @@ void finish(not_null<Audio::Instance*> instance) {
void SaveLastPlaybackPosition( void SaveLastPlaybackPosition(
not_null<DocumentData*> document, not_null<DocumentData*> document,
const TrackState &state) { const TrackState &state) {
const auto limit = document->isVideoFile()
? kMinLengthForSavePositionVideo
: kMinLengthForSavePositionMusic;
const auto time = (state.position == kTimeUnknown const auto time = (state.position == kTimeUnknown
|| state.length == kTimeUnknown || state.length == kTimeUnknown
|| state.state == State::PausedAtEnd || state.state == State::PausedAtEnd
|| IsStopped(state.state)) || IsStopped(state.state))
? TimeId(0) ? TimeId(0)
: (state.length >= kMinLengthForSavePosition * state.frequency) : (state.length >= limit * state.frequency)
? (state.position / state.frequency) * crl::time(1000) ? (state.position / state.frequency) * crl::time(1000)
: TimeId(0); : TimeId(0);
auto &session = document->session(); auto &session = document->session();
if (session.settings().mediaLastPlaybackPosition(document->id) != time) { if (session.local().mediaLastPlaybackPosition(document->id) != time) {
session.settings().setMediaLastPlaybackPosition(document->id, time); session.local().setMediaLastPlaybackPosition(document->id, time);
session.saveSettingsDelayed();
} }
} }
@ -849,9 +853,9 @@ Streaming::PlaybackOptions Instance::streamingOptions(
if (position >= 0) { if (position >= 0) {
result.position = position; result.position = position;
} else if (document) { } else if (document) {
auto &settings = document->session().settings(); auto &local = document->session().local();
result.position = settings.mediaLastPlaybackPosition(document->id); result.position = local.mediaLastPlaybackPosition(document->id);
settings.setMediaLastPlaybackPosition(document->id, 0); local.setMediaLastPlaybackPosition(document->id, 0);
} else { } else {
result.position = 0; result.position = 0;
} }

View file

@ -47,6 +47,7 @@ using Database = Cache::Database;
constexpr auto kDelayedWriteTimeout = crl::time(1000); constexpr auto kDelayedWriteTimeout = crl::time(1000);
constexpr auto kWriteSearchSuggestionsDelay = 5 * crl::time(1000); constexpr auto kWriteSearchSuggestionsDelay = 5 * crl::time(1000);
constexpr auto kMaxSavedPlaybackPositions = 256;
constexpr auto kStickersVersionTag = quint32(-1); constexpr auto kStickersVersionTag = quint32(-1);
constexpr auto kStickersSerializeVersion = 4; constexpr auto kStickersSerializeVersion = 4;
@ -96,6 +97,7 @@ enum { // Local Storage Keys
lskWebviewTokens = 0x19, // data: QByteArray bots, QByteArray other lskWebviewTokens = 0x19, // data: QByteArray bots, QByteArray other
lskRoundPlaceholder = 0x1a, // no data lskRoundPlaceholder = 0x1a, // no data
lskInlineBotsDownloads = 0x1b, // no data lskInlineBotsDownloads = 0x1b, // no data
lskMediaLastPlaybackPositions = 0x1c, // no data
}; };
auto EmptyMessageDraftSources() auto EmptyMessageDraftSources()
@ -228,6 +230,7 @@ base::flat_set<QString> Account::collectGoodNames() const {
_searchSuggestionsKey, _searchSuggestionsKey,
_roundPlaceholderKey, _roundPlaceholderKey,
_inlineBotsDownloadsKey, _inlineBotsDownloadsKey,
_mediaLastPlaybackPositionsKey,
}; };
auto result = base::flat_set<QString>{ auto result = base::flat_set<QString>{
"map0", "map0",
@ -316,6 +319,7 @@ Account::ReadMapResult Account::readMapWith(
quint64 searchSuggestionsKey = 0; quint64 searchSuggestionsKey = 0;
quint64 roundPlaceholderKey = 0; quint64 roundPlaceholderKey = 0;
quint64 inlineBotsDownloadsKey = 0; quint64 inlineBotsDownloadsKey = 0;
quint64 mediaLastPlaybackPositionsKey = 0;
QByteArray webviewStorageTokenBots, webviewStorageTokenOther; QByteArray webviewStorageTokenBots, webviewStorageTokenOther;
while (!map.stream.atEnd()) { while (!map.stream.atEnd()) {
quint32 keyType; quint32 keyType;
@ -431,6 +435,9 @@ Account::ReadMapResult Account::readMapWith(
case lskInlineBotsDownloads: { case lskInlineBotsDownloads: {
map.stream >> inlineBotsDownloadsKey; map.stream >> inlineBotsDownloadsKey;
} break; } break;
case lskMediaLastPlaybackPositions: {
map.stream >> mediaLastPlaybackPositionsKey;
} break;
case lskWebviewTokens: { case lskWebviewTokens: {
map.stream map.stream
>> webviewStorageTokenBots >> webviewStorageTokenBots
@ -474,6 +481,7 @@ Account::ReadMapResult Account::readMapWith(
_searchSuggestionsKey = searchSuggestionsKey; _searchSuggestionsKey = searchSuggestionsKey;
_roundPlaceholderKey = roundPlaceholderKey; _roundPlaceholderKey = roundPlaceholderKey;
_inlineBotsDownloadsKey = inlineBotsDownloadsKey; _inlineBotsDownloadsKey = inlineBotsDownloadsKey;
_mediaLastPlaybackPositionsKey = mediaLastPlaybackPositionsKey;
_oldMapVersion = mapData.version; _oldMapVersion = mapData.version;
_webviewStorageIdBots.token = webviewStorageTokenBots; _webviewStorageIdBots.token = webviewStorageTokenBots;
_webviewStorageIdOther.token = webviewStorageTokenOther; _webviewStorageIdOther.token = webviewStorageTokenOther;
@ -590,6 +598,7 @@ void Account::writeMap() {
} }
if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_roundPlaceholderKey) mapSize += sizeof(quint32) + sizeof(quint64);
if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64); if (_inlineBotsDownloadsKey) mapSize += sizeof(quint32) + sizeof(quint64);
if (_mediaLastPlaybackPositionsKey) mapSize += sizeof(quint32) + sizeof(quint64);
EncryptedDescriptor mapData(mapSize); EncryptedDescriptor mapData(mapSize);
if (!self.isEmpty()) { if (!self.isEmpty()) {
@ -668,6 +677,10 @@ void Account::writeMap() {
mapData.stream << quint32(lskInlineBotsDownloads); mapData.stream << quint32(lskInlineBotsDownloads);
mapData.stream << quint64(_inlineBotsDownloadsKey); mapData.stream << quint64(_inlineBotsDownloadsKey);
} }
if (_mediaLastPlaybackPositionsKey) {
mapData.stream << quint32(lskMediaLastPlaybackPositions);
mapData.stream << quint64(_mediaLastPlaybackPositionsKey);
}
map.writeEncrypted(mapData, _localKey); map.writeEncrypted(mapData, _localKey);
_mapChanged = false; _mapChanged = false;
@ -699,6 +712,7 @@ void Account::reset() {
_searchSuggestionsKey = 0; _searchSuggestionsKey = 0;
_roundPlaceholderKey = 0; _roundPlaceholderKey = 0;
_inlineBotsDownloadsKey = 0; _inlineBotsDownloadsKey = 0;
_mediaLastPlaybackPositionsKey = 0;
_oldMapVersion = 0; _oldMapVersion = 0;
_fileLocations.clear(); _fileLocations.clear();
_fileLocationPairs.clear(); _fileLocationPairs.clear();
@ -709,6 +723,7 @@ void Account::reset() {
_cacheTotalTimeLimit = Database::Settings().totalTimeLimit; _cacheTotalTimeLimit = Database::Settings().totalTimeLimit;
_cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit; _cacheBigFileTotalSizeLimit = Database::Settings().totalSizeLimit;
_cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit; _cacheBigFileTotalTimeLimit = Database::Settings().totalTimeLimit;
_mediaLastPlaybackPosition.clear();
const auto wvbots = _webviewStorageIdBots.path; const auto wvbots = _webviewStorageIdBots.path;
const auto wvother = _webviewStorageIdOther.path; const auto wvother = _webviewStorageIdOther.path;
@ -2943,6 +2958,96 @@ Export::Settings Account::readExportSettings() {
: Export::Settings(); : 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() { void Account::writeSearchSuggestionsDelayed() {
Expects(_owner->sessionExists()); Expects(_owner->sessionExists());

View file

@ -150,6 +150,9 @@ public:
void writeExportSettings(const Export::Settings &settings); void writeExportSettings(const Export::Settings &settings);
[[nodiscard]] Export::Settings readExportSettings(); [[nodiscard]] Export::Settings readExportSettings();
void setMediaLastPlaybackPosition(DocumentId id, crl::time time);
[[nodiscard]] crl::time mediaLastPlaybackPosition(DocumentId id) const;
void writeSearchSuggestionsDelayed(); void writeSearchSuggestionsDelayed();
void writeSearchSuggestionsIfNeeded(); void writeSearchSuggestionsIfNeeded();
void writeSearchSuggestions(); void writeSearchSuggestions();
@ -261,6 +264,9 @@ private:
void readTrustedBots(); void readTrustedBots();
void writeTrustedBots(); void writeTrustedBots();
void readMediaLastPlaybackPositions();
void writeMediaLastPlaybackPositions();
std::optional<RecentHashtagPack> saveRecentHashtags( std::optional<RecentHashtagPack> saveRecentHashtags(
Fn<RecentHashtagPack()> getPack, Fn<RecentHashtagPack()> getPack,
const QString &text); const QString &text);
@ -311,6 +317,7 @@ private:
FileKey _searchSuggestionsKey = 0; FileKey _searchSuggestionsKey = 0;
FileKey _roundPlaceholderKey = 0; FileKey _roundPlaceholderKey = 0;
FileKey _inlineBotsDownloadsKey = 0; FileKey _inlineBotsDownloadsKey = 0;
FileKey _mediaLastPlaybackPositionsKey = 0;
qint64 _cacheTotalSizeLimit = 0; qint64 _cacheTotalSizeLimit = 0;
qint64 _cacheBigFileTotalSizeLimit = 0; qint64 _cacheBigFileTotalSizeLimit = 0;
@ -323,6 +330,9 @@ private:
bool _recentHashtagsAndBotsWereRead = false; bool _recentHashtagsAndBotsWereRead = false;
bool _searchSuggestionsRead = false; bool _searchSuggestionsRead = false;
bool _inlineBotsDownloadsRead = false; bool _inlineBotsDownloadsRead = false;
bool _mediaLastPlaybackPositionsRead = false;
std::vector<std::pair<DocumentId, crl::time>> _mediaLastPlaybackPosition;
Webview::StorageId _webviewStorageIdBots; Webview::StorageId _webviewStorageIdBots;
Webview::StorageId _webviewStorageIdOther; Webview::StorageId _webviewStorageIdOther;

View file

@ -89,6 +89,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "support/support_helper.h" #include "support/support_helper.h"
#include "storage/file_upload.h" #include "storage/file_upload.h"
#include "storage/download_manager_mtproto.h" #include "storage/download_manager_mtproto.h"
#include "storage/storage_account.h"
#include "window/themes/window_theme.h" #include "window/themes/window_theme.h"
#include "window/window_peer_menu.h" #include "window/window_peer_menu.h"
#include "window/window_session_controller_link_info.h" #include "window/window_session_controller_link_info.h"
@ -2789,13 +2790,18 @@ void SessionController::openDocument(
} else if (showInMediaView) { } else if (showInMediaView) {
using namespace Media::View; using namespace Media::View;
const auto timestamp = item ? ExtractVideoTimestamp(item) : 0; 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( _window->openInMediaView(OpenRequest(
this, this,
document, document,
item, item,
message.topicRootId, message.topicRootId,
false, false,
videoTimestampOverride.value_or(timestamp) * crl::time(1000))); usedTimestamp));
return; return;
} }
Data::ResolveDocument(this, document, item, message.topicRootId); Data::ResolveDocument(this, document, item, message.topicRootId);