From 4e2c8bbc26e2b796047a508b8a3ec2593e7a4a4d Mon Sep 17 00:00:00 2001 From: John Preston Date: Sat, 9 Dec 2017 14:02:51 +0400 Subject: [PATCH] Use SharedMediaMergedViewer() for audio player. That way audio files and voice/video messages will play in context (one after another with ability to go to next or previous in player) almost always, no matter at what part of message history we are. --- Telegram/SourceFiles/data/data_sparse_ids.h | 3 + Telegram/SourceFiles/facades.cpp | 21 +- Telegram/SourceFiles/history/history_item.cpp | 5 + .../history/history_media_types.cpp | 5 +- .../media/player/media_player_instance.cpp | 210 ++++++++++++++---- .../media/player/media_player_instance.h | 13 +- Telegram/SourceFiles/mediaview.cpp | 6 +- Telegram/SourceFiles/mediaview.h | 4 +- 8 files changed, 207 insertions(+), 60 deletions(-) diff --git a/Telegram/SourceFiles/data/data_sparse_ids.h b/Telegram/SourceFiles/data/data_sparse_ids.h index 6cd5b9f29..264358032 100644 --- a/Telegram/SourceFiles/data/data_sparse_ids.h +++ b/Telegram/SourceFiles/data/data_sparse_ids.h @@ -79,6 +79,9 @@ public: && (migratedPeerId == other.migratedPeerId) && (universalId == other.universalId); } + bool operator!=(const Key &other) const { + return !(*this == other); + } PeerId peerId = 0; PeerId migratedPeerId = 0; diff --git a/Telegram/SourceFiles/facades.cpp b/Telegram/SourceFiles/facades.cpp index f92e6a6ef..e1ca65e65 100644 --- a/Telegram/SourceFiles/facades.cpp +++ b/Telegram/SourceFiles/facades.cpp @@ -36,6 +36,7 @@ Copyright (c) 2014-2017 John Preston, https://desktop.telegram.org #include "base/observer.h" #include "base/task_queue.h" #include "history/history_media.h" +#include "styles/style_history.h" Q_DECLARE_METATYPE(ClickHandlerPtr); Q_DECLARE_METATYPE(Qt::MouseButton); @@ -359,15 +360,25 @@ void handlePendingHistoryUpdate() { } Auth().data().pendingHistoryResize().notify(true); - for (auto item : base::take(Global::RefPendingRepaintItems())) { + for (const auto item : base::take(Global::RefPendingRepaintItems())) { Auth().data().requestItemRepaint(item); // Start the video if it is waiting for that. if (item->pendingInitDimensions()) { - if (auto media = item->getMedia()) { - if (auto reader = media->getClipReader()) { - if (!reader->started() && reader->mode() == Media::Clip::Reader::Mode::Video) { - item->resizeGetHeight(item->width()); + if (const auto media = item->getMedia()) { + if (const auto reader = media->getClipReader()) { + const auto startRequired = [&] { + if (reader->started()) { + return false; + } + using Mode = Media::Clip::Reader::Mode; + return (reader->mode() == Mode::Video); + }; + if (startRequired()) { + const auto width = std::max( + item->width(), + st::historyMinimalWidth); + item->resizeGetHeight(width); } } } diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 2f8b3ab14..a6fd4f499 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -1148,6 +1148,11 @@ void HistoryItem::clipCallback(Media::Clip::Notification notification) { } if (!stopped) { setPendingInitDimensions(); + if (detached()) { + // We still want to handle our pending initDimensions and + // resize state even if we're detached in history. + _history->setHasPendingResizedItems(); + } Auth().data().markItemLayoutChanged(this); Global::RefPendingRepaintItems().insert(this); } diff --git a/Telegram/SourceFiles/history/history_media_types.cpp b/Telegram/SourceFiles/history/history_media_types.cpp index ebf3d0983..e69043e27 100644 --- a/Telegram/SourceFiles/history/history_media_types.cpp +++ b/Telegram/SourceFiles/history/history_media_types.cpp @@ -1052,7 +1052,10 @@ HistoryDocument::HistoryDocument(not_null parent, DocumentData *do } } -HistoryDocument::HistoryDocument(not_null parent, const HistoryDocument &other) : HistoryFileMedia(parent) +HistoryDocument::HistoryDocument( + not_null parent, + const HistoryDocument &other) +: HistoryFileMedia(parent) , RuntimeComposer() , _data(other._data) { auto captioned = other.Get(); diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index b3ab6eaa4..5e2e2c1d5 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -34,6 +34,12 @@ namespace { Instance *SingleInstance = nullptr; +// Preload X message ids before and after current. +constexpr auto kIdsLimit = 32; + +// Preload next messages if we went further from current than that. +constexpr auto kIdsPreloadAfter = 28; + } // namespace void start() { @@ -113,55 +119,162 @@ void Instance::setCurrent(const AudioMsgId &audioId) { data->migrated = nullptr; } _trackChangedNotifier.notify(data->type, true); - if (data->history != history || data->migrated != migrated) { - rebuildPlaylist(data); - } + refreshPlaylist(data); } } } -void Instance::rebuildPlaylist(not_null data) { - // #TODO playlist +void Instance::refreshPlaylist(not_null data) { + if (!validPlaylist(data)) { + validatePlaylist(data); + } + playlistUpdated(data); +} + +void Instance::playlistUpdated(not_null data) { + if (data->playlistSlice) { + const auto fullId = data->current.contextId(); + data->playlistIndex = data->playlistSlice->indexOf(fullId); + } else { + data->playlistIndex = base::none; + } + data->playlistChanges.fire({}); +} + +bool Instance::validPlaylist(not_null data) { + if (const auto key = playlistKey(data)) { + if (!data->playlistSlice) { + return false; + } + using Key = SliceKey; + const auto inSameDomain = [](const Key &a, const Key &b) { + return (a.peerId == b.peerId) + && (a.migratedPeerId == b.migratedPeerId); + }; + const auto countDistanceInData = [&](const Key &a, const Key &b) { + return [&](const SparseIdsMergedSlice &data) { + return inSameDomain(a, b) + ? data.distance(a, b) + : base::optional(); + }; + }; + + if (key == data->playlistRequestedKey) { + return true; + } else if (!data->playlistSliceKey + || !data->playlistRequestedKey + || *data->playlistRequestedKey != *data->playlistSliceKey) { + return false; + } + auto distance = data->playlistSlice + | countDistanceInData(*key, *data->playlistRequestedKey) + | func::abs; + if (distance) { + return (*distance < kIdsPreloadAfter); + } + } + return !data->playlistSlice; +} + +void Instance::validatePlaylist(not_null data) { + if (const auto key = playlistKey(data)) { + data->playlistRequestedKey = key; + SharedMediaMergedViewer( + SharedMediaMergedKey(*key, data->overview), + kIdsLimit, + kIdsLimit + ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { + data->playlistSlice = std::move(update); + data->playlistSliceKey = key; + playlistUpdated(data); + }, data->playlistLifetime); + } else { + data->playlistSlice = base::none; + data->playlistSliceKey = data->playlistRequestedKey = base::none; + playlistUpdated(data); + } +} + +auto Instance::playlistKey(not_null data) const +-> base::optional { + const auto contextId = data->current.contextId(); + const auto history = data->history; + if (!contextId || !history) { + return {}; + } + + const auto universalId = (contextId.channel == history->channelId()) + ? contextId.msg + : (contextId.msg - ServerMaxMsgId); + return SliceKey( + data->history->peer->id, + data->migrated ? data->migrated->peer->id : 0, + universalId); +} + +HistoryItem *Instance::itemByIndex(not_null data, int index) { + if (!data->playlistSlice + || index < 0 + || index >= data->playlistSlice->size()) { + return nullptr; + } + const auto fullId = (*data->playlistSlice)[index]; + return App::histItemById(fullId); } bool Instance::moveInPlaylist( not_null data, int delta, bool autonext) { - //auto index = data->playlist.indexOf(data->current.contextId()); - //auto newIndex = index + delta; - //if (!data->current || index < 0 || newIndex < 0 || newIndex >= data->playlist.size()) { - // rebuildPlaylist(data); - // return false; - //} - - //auto msgId = data->playlist[newIndex]; - //if (auto item = App::histItemById(msgId)) { - // if (auto media = item->getMedia()) { - // if (auto document = media->getDocument()) { - // if (autonext) { - // _switchToNextNotifier.notify({ data->current, msgId }); - // } - // DocumentOpenClickHandler::doOpen(media->getDocument(), item, ActionOnLoadPlayInline); - // return true; - // } - // } - //} - //return false; + if (!data->playlistIndex) { + return false; + } + const auto newIndex = *data->playlistIndex + delta; + if (const auto item = itemByIndex(data, newIndex)) { + if (const auto media = item->getMedia()) { + if (const auto document = media->getDocument()) { + if (autonext) { + _switchToNextNotifier.notify({ + data->current, + item->fullId() + }); + } + if (document->tryPlaySong()) { + play(AudioMsgId(document, item->fullId())); + } else { + DocumentOpenClickHandler::doOpen( + document, + item, + ActionOnLoadPlayInline); + } + return true; + } + } + } return false; } bool Instance::previousAvailable(AudioMsgId::Type type) const { - return false; + const auto data = getData(type); + Assert(data != nullptr); + return data->playlistIndex + && data->playlistSlice + && (*data->playlistIndex > 0); } bool Instance::nextAvailable(AudioMsgId::Type type) const { - return false; + const auto data = getData(type); + Assert(data != nullptr); + return data->playlistIndex + && data->playlistSlice + && (*data->playlistIndex + 1 < data->playlistSlice->size()); } rpl::producer<> Media::Player::Instance::playlistChanges( AudioMsgId::Type type) const { - return rpl::never<>(); + const auto data = getData(type); + Assert(data != nullptr); + return data->playlistChanges.events(); } Instance *instance() { @@ -188,7 +301,7 @@ void Instance::play(const AudioMsgId &audioId) { if (!audioId) { return; } - if (audioId.audio()->song() || audioId.audio()->voice()) { + if (audioId.audio()->tryPlaySong() || audioId.audio()->voice()) { mixer()->play(audioId); setCurrent(audioId); if (audioId.audio()->loading()) { @@ -282,7 +395,10 @@ void Instance::stopSeeking(AudioMsgId::Type type) { } void Instance::documentLoadProgress(DocumentData *document) { - emitUpdate(document->song() ? AudioMsgId::Type::Song : AudioMsgId::Type::Voice, [document](const AudioMsgId &audioId) { + const auto type = document->tryPlaySong() + ? AudioMsgId::Type::Song + : AudioMsgId::Type::Voice; + emitUpdate(type, [document](const AudioMsgId &audioId) { return (audioId.audio() == document); }); } @@ -316,26 +432,24 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) { } void Instance::preloadNext(not_null data) { - if (!data->current) { + if (!data->current || !data->playlistSlice || !data->playlistIndex) { return; } - //auto index = data->playlist.indexOf(data->current.contextId()); - //if (index < 0) { - // return; - //} - //auto nextIndex = index + 1; - //if (nextIndex >= data->playlist.size()) { - // return; - //} - //if (auto item = App::histItemById(data->playlist[nextIndex])) { - // if (auto media = item->getMedia()) { - // if (auto document = media->getDocument()) { - // if (!document->loaded(DocumentData::FilePathResolveSaveFromDataSilent)) { - // DocumentOpenClickHandler::doOpen(document, nullptr, ActionOnLoadNone); - // } - // } - // } - //} + const auto nextIndex = *data->playlistIndex + 1; + if (const auto item = itemByIndex(data, nextIndex)) { + if (const auto media = item->getMedia()) { + if (const auto document = media->getDocument()) { + const auto isLoaded = document->loaded( + DocumentData::FilePathResolveSaveFromDataSilent); + if (!isLoaded) { + DocumentOpenClickHandler::doOpen( + document, + nullptr, + ActionOnLoadNone); + } + } + } + } } void Instance::handleLogout() { diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index 4f32dd12c..f87fe9ba9 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -141,6 +141,7 @@ private: friend void start(); using SharedMediaType = Storage::SharedMediaType; + using SliceKey = SparseIdsMergedSlice::Key; struct Data { Data(AudioMsgId::Type type, SharedMediaType overview) : type(type) @@ -152,6 +153,11 @@ private: AudioMsgId current; AudioMsgId seeking; base::optional playlistSlice; + base::optional playlistSliceKey; + base::optional playlistRequestedKey; + base::optional playlistIndex; + rpl::lifetime playlistLifetime; + rpl::event_stream<> playlistChanges; History *history = nullptr; History *migrated = nullptr; bool repeatEnabled = false; @@ -162,9 +168,14 @@ private: void handleSongUpdate(const AudioMsgId &audioId); void setCurrent(const AudioMsgId &audioId); - void rebuildPlaylist(not_null data); + void refreshPlaylist(not_null data); + base::optional playlistKey(not_null data) const; + bool validPlaylist(not_null data); + void validatePlaylist(not_null data); + void playlistUpdated(not_null data); bool moveInPlaylist(not_null data, int delta, bool autonext); void preloadNext(not_null data); + HistoryItem *itemByIndex(not_null data, int index); void handleLogout(); template diff --git a/Telegram/SourceFiles/mediaview.cpp b/Telegram/SourceFiles/mediaview.cpp index 1c578c95d..cf7f76884 100644 --- a/Telegram/SourceFiles/mediaview.cpp +++ b/Telegram/SourceFiles/mediaview.cpp @@ -2316,12 +2316,12 @@ void MediaView::setContext(base::optional_variant< _user = _peer ? _peer->asUser() : nullptr; } -bool MediaView::moveToNext(int32 delta) { +bool MediaView::moveToNext(int delta) { if (!_index) { return false; } auto newIndex = *_index + delta; - auto entity = entityByIndex(*_index + delta); + auto entity = entityByIndex(newIndex); if (!entity.data && !entity.item) { return false; } @@ -2345,7 +2345,7 @@ bool MediaView::moveToNext(int32 delta) { return true; } -void MediaView::preloadData(int32 delta) { +void MediaView::preloadData(int delta) { if (!_index) { return; } diff --git a/Telegram/SourceFiles/mediaview.h b/Telegram/SourceFiles/mediaview.h index be131318c..fc78146f2 100644 --- a/Telegram/SourceFiles/mediaview.h +++ b/Telegram/SourceFiles/mediaview.h @@ -147,8 +147,8 @@ private: void updateOver(QPoint mpos); void moveToScreen(); - bool moveToNext(int32 delta); - void preloadData(int32 delta); + bool moveToNext(int delta); + void preloadData(int delta); struct Entity { base::optional_variant< not_null,