From 5cd339332cb1e4e62d281625e6fc7447bb624a38 Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 23 Nov 2021 17:51:38 +0400 Subject: [PATCH] Implement shuffled playlist. --- .../media/player/media_player_instance.cpp | 199 ++++++++++++++++++ .../media/player/media_player_instance.h | 7 + 2 files changed, 206 insertions(+) diff --git a/Telegram/SourceFiles/media/player/media_player_instance.cpp b/Telegram/SourceFiles/media/player/media_player_instance.cpp index 6ecda9263..2028f0201 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.cpp +++ b/Telegram/SourceFiles/media/player/media_player_instance.cpp @@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_document.h" #include "data/data_session.h" +#include "data/data_changes.h" #include "data/data_streaming.h" #include "data/data_file_click_handler.h" +#include "base/random.h" #include "media/audio/media_audio.h" #include "media/audio/media_audio_capture.h" #include "media/streaming/media_streaming_instance.h" @@ -42,6 +44,8 @@ constexpr auto kIdsLimit = 32; // Preload next messages if we went further from current than that. constexpr auto kIdsPreloadAfter = 28; +constexpr auto kShufflePlaylistLimit = 10'000; + constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes. auto VoicePlaybackSpeed() { @@ -62,6 +66,21 @@ struct Instance::Streamed { rpl::lifetime lifetime; }; +struct Instance::ShuffleData { + using UniversalMsgId = MsgId; + + std::vector playlist; + std::vector nonPlayedIds; + std::vector playedIds; + History *history = nullptr; + History *migrated = nullptr; + bool scheduled = false; + int indexInPlayedIds = 0; + bool allLoaded = false; + rpl::lifetime nextSliceLifetime; + rpl::lifetime lifetime; +}; + void start(not_null instance) { Audio::Start(instance); Capture::Start(); @@ -277,8 +296,14 @@ void Instance::playlistUpdated(not_null data) { if (data->playlistSlice) { const auto fullId = data->current.contextId(); data->playlistIndex = data->playlistSlice->indexOf(fullId); + if (data->order.current() == OrderMode::Shuffle) { + validateShuffleData(data); + } else { + data->shuffleData = nullptr; + } } else { data->playlistIndex = std::nullopt; + data->shuffleData = nullptr; } data->playlistChanges.fire({}); } @@ -469,6 +494,41 @@ bool Instance::moveInPlaylist( }; if (data->order.current() == OrderMode::Shuffle) { + const auto raw = data->shuffleData.get(); + if (!raw || !raw->history) { + return false; + } + const auto byUniversal = [&](ShuffleData::UniversalMsgId id) { + return (id < 0) + ? jumpById({ ChannelId(), id + ServerMaxMsgId }) + : jumpById({ raw->history->channelId(), id }); + }; + if (delta < 0) { + return (raw->indexInPlayedIds > 0) + && byUniversal(raw->playedIds[--raw->indexInPlayedIds]); + } else if (raw->indexInPlayedIds + 1 < raw->playedIds.size()) { + return byUniversal(raw->playedIds[++raw->indexInPlayedIds]); + } else { + if (raw->nonPlayedIds.empty()) { + return false; + } + if (const auto universal = computeCurrentUniversalId(data)) { + if (raw->nonPlayedIds.size() == 1 + && raw->nonPlayedIds.front() == universal) { + return false; + } + if (raw->indexInPlayedIds == raw->playedIds.size()) { + raw->playedIds.push_back(universal); + } + const auto i = ranges::find(raw->nonPlayedIds, universal); + if (i != end(raw->nonPlayedIds)) { + raw->nonPlayedIds.erase(i); + } + ++raw->indexInPlayedIds; + } + const auto index = base::RandomIndex(raw->nonPlayedIds.size()); + return byUniversal(raw->nonPlayedIds[index]); + } } const auto newIndex = *data->playlistIndex @@ -495,6 +555,22 @@ bool Instance::moveInPlaylist( return false; } +MsgId Instance::computeCurrentUniversalId(not_null data) const { + const auto raw = data->shuffleData.get(); + if (!raw) { + return MsgId(0); + } + const auto current = data->current.contextId(); + const auto item = raw->history->owner().message(current); + return !item + ? MsgId(0) + : (item->history() == raw->history) + ? item->id + : (item->history() == raw->migrated) + ? (item->id - ServerMaxMsgId) + : MsgId(0); +} + bool Instance::previousAvailable(AudioMsgId::Type type) const { const auto data = getData(type); Assert(data != nullptr); @@ -504,6 +580,8 @@ bool Instance::previousAvailable(AudioMsgId::Type type) const { } else if (data->repeat.current() == RepeatMode::All) { return true; } else if (data->order.current() == OrderMode::Shuffle) { + const auto raw = data->shuffleData.get(); + return raw && (raw->indexInPlayedIds > 0); } return (data->order.current() == OrderMode::Reverse) ? (*data->playlistIndex + 1 < data->playlistSlice->size()) @@ -519,6 +597,13 @@ bool Instance::nextAvailable(AudioMsgId::Type type) const { } else if (data->repeat.current() == RepeatMode::All) { return true; } else if (data->order.current() == OrderMode::Shuffle) { + const auto raw = data->shuffleData.get(); + const auto universal = computeCurrentUniversalId(data); + return raw + && ((raw->indexInPlayedIds + 1 < raw->playedIds.size()) + || (raw->nonPlayedIds.size() > 1) + || (!raw->nonPlayedIds.empty() + && raw->nonPlayedIds.front() != universal)); } return (data->order.current() == OrderMode::Reverse) ? (*data->playlistIndex > 0) @@ -689,6 +774,120 @@ void Instance::stopAndClear(not_null data) { _tracksFinished.fire_copy(data->type); } +void Instance::validateShuffleData(not_null data) { + if (!data->history) { + data->shuffleData = nullptr; + return; + } else if (!data->shuffleData) { + setupShuffleData(data); + } + const auto raw = data->shuffleData.get(); + const auto key = playlistKey(data); + const auto scheduled = key && key->scheduled; + if (raw->history != data->history + || raw->migrated != data->migrated + || raw->scheduled != scheduled) { + raw->history = data->history; + raw->migrated = data->migrated; + raw->scheduled = scheduled; + raw->nextSliceLifetime.destroy(); + raw->allLoaded = false; + raw->playlist.clear(); + raw->nonPlayedIds.clear(); + raw->playedIds.clear(); + raw->indexInPlayedIds = 0; + } else if (raw->allLoaded || raw->nextSliceLifetime) { + return; + } + if (raw->scheduled) { + const auto count = data->playlistSlice + ? int(data->playlistSlice->size()) + : 0; + if (raw->playlist.empty() && count > 0) { + raw->playlist.reserve(count); + for (auto i = 0; i != count; ++i) { + raw->playlist.push_back((*data->playlistSlice)[i].msg); + } + raw->nonPlayedIds = raw->playlist; + raw->allLoaded = true; + data->playlistChanges.fire({}); + } + return; + } + const auto last = raw->playlist.empty() + ? MsgId(ServerMaxMsgId - 1) + : raw->playlist.back(); + SharedMediaMergedViewer( + &raw->history->session(), + SharedMediaMergedKey( + SliceKey( + raw->history->peer->id, + raw->migrated ? raw->migrated->peer->id : 0, + last, + false), + data->overview), + kIdsLimit, + kIdsLimit + ) | rpl::start_with_next([=](SparseIdsMergedSlice &&update) { + raw->nextSliceLifetime.destroy(); + + const auto size = update.size(); + const auto channel = raw->history->channelId(); + raw->playlist.reserve(raw->playlist.size() + size); + raw->nonPlayedIds.reserve(raw->nonPlayedIds.size() + size); + for (auto i = size; i != 0;) { + const auto fullId = update[--i]; + const auto universal = (fullId.channel == channel) + ? fullId.msg + : (fullId.msg - ServerMaxMsgId); + if (raw->playlist.empty() || raw->playlist.back() > universal) { + raw->playlist.push_back(universal); + raw->nonPlayedIds.push_back(universal); + } + } + if (update.skippedBefore() == 0 + || raw->playlist.size() >= kShufflePlaylistLimit) { + raw->allLoaded = true; + } + data->playlistChanges.fire({}); + }, raw->nextSliceLifetime); +} + +void Instance::setupShuffleData(not_null data) { + data->shuffleData = std::make_unique(); + const auto raw = data->shuffleData.get(); + data->history->session().changes().messageUpdates( + ::Data::MessageUpdate::Flag::Destroyed + ) | rpl::map([=](const ::Data::MessageUpdate &update) { + const auto item = update.item; + const auto history = item->history().get(); + return (history == raw->history) + ? item->id + : (history == raw->migrated) + ? (item->id - ServerMaxMsgId) + : MsgId(0); + }) | rpl::filter( + rpl::mappers::_1 != MsgId(0) + ) | rpl::start_with_next([=](MsgId id) { + const auto i = ranges::find(raw->playlist, id); + if (i != end(raw->playlist)) { + raw->playlist.erase(i); + } + const auto j = ranges::find(raw->nonPlayedIds, id); + if (j != end(raw->nonPlayedIds)) { + raw->nonPlayedIds.erase(j); + } + const auto k = ranges::find(raw->playedIds, id); + if (k != end(raw->playedIds)) { + const auto index = (k - begin(raw->playedIds)); + raw->playedIds.erase(k); + if (raw->indexInPlayedIds > index) { + --raw->indexInPlayedIds; + } + } + }, data->shuffleData->lifetime); +} + void Instance::playPause(AudioMsgId::Type type) { if (const auto data = getData(type)) { if (!data->streamed) { diff --git a/Telegram/SourceFiles/media/player/media_player_instance.h b/Telegram/SourceFiles/media/player/media_player_instance.h index bb39322a6..8a83ae82a 100644 --- a/Telegram/SourceFiles/media/player/media_player_instance.h +++ b/Telegram/SourceFiles/media/player/media_player_instance.h @@ -167,6 +167,7 @@ private: using SharedMediaType = Storage::SharedMediaType; using SliceKey = SparseIdsMergedSlice::Key; struct Streamed; + struct ShuffleData; struct Data { Data(AudioMsgId::Type type, SharedMediaType overview); Data(Data &&other); @@ -195,6 +196,7 @@ private: bool isPlaying = false; bool resumeOnCallEnd = false; std::unique_ptr streamed; + std::unique_ptr shuffleData; }; struct SeekingChanges { @@ -237,6 +239,11 @@ private: HistoryItem *itemByIndex(not_null data, int index); void stopAndClear(not_null data); + [[nodiscard]] MsgId computeCurrentUniversalId( + not_null data) const; + void validateShuffleData(not_null data); + void setupShuffleData(not_null data); + void handleStreamingUpdate( not_null data, Streaming::Update &&update);