mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-13 04:37:11 +02:00
Support on-hover autoplay and position save.
This commit is contained in:
parent
e8034189df
commit
6a415cf232
9 changed files with 223 additions and 97 deletions
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Add table
Reference in a new issue