mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-23 00:36:53 +00:00
Implement shuffled playlist.
This commit is contained in:
parent
d1b9785d31
commit
5cd339332c
@ -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<UniversalMsgId> playlist;
|
||||
std::vector<UniversalMsgId> nonPlayedIds;
|
||||
std::vector<UniversalMsgId> 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<Audio::Instance*> instance) {
|
||||
Audio::Start(instance);
|
||||
Capture::Start();
|
||||
@ -277,8 +296,14 @@ void Instance::playlistUpdated(not_null<Data*> 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<const Data*> 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*> data) {
|
||||
_tracksFinished.fire_copy(data->type);
|
||||
}
|
||||
|
||||
void Instance::validateShuffleData(not_null<Data*> 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) {
|
||||
data->shuffleData = std::make_unique<ShuffleData>();
|
||||
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) {
|
||||
|
@ -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> streamed;
|
||||
std::unique_ptr<ShuffleData> shuffleData;
|
||||
};
|
||||
|
||||
struct SeekingChanges {
|
||||
@ -237,6 +239,11 @@ private:
|
||||
HistoryItem *itemByIndex(not_null<Data*> data, int index);
|
||||
void stopAndClear(not_null<Data*> data);
|
||||
|
||||
[[nodiscard]] MsgId computeCurrentUniversalId(
|
||||
not_null<const Data*> data) const;
|
||||
void validateShuffleData(not_null<Data*> data);
|
||||
void setupShuffleData(not_null<Data*> data);
|
||||
|
||||
void handleStreamingUpdate(
|
||||
not_null<Data*> data,
|
||||
Streaming::Update &&update);
|
||||
|
Loading…
Reference in New Issue
Block a user