mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Implement shuffled playlist.
This commit is contained in:
parent
d1b9785d31
commit
5cd339332c
2 changed files with 206 additions and 0 deletions
|
@ -9,8 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "data/data_document.h"
|
#include "data/data_document.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
#include "data/data_streaming.h"
|
#include "data/data_streaming.h"
|
||||||
#include "data/data_file_click_handler.h"
|
#include "data/data_file_click_handler.h"
|
||||||
|
#include "base/random.h"
|
||||||
#include "media/audio/media_audio.h"
|
#include "media/audio/media_audio.h"
|
||||||
#include "media/audio/media_audio_capture.h"
|
#include "media/audio/media_audio_capture.h"
|
||||||
#include "media/streaming/media_streaming_instance.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.
|
// Preload next messages if we went further from current than that.
|
||||||
constexpr auto kIdsPreloadAfter = 28;
|
constexpr auto kIdsPreloadAfter = 28;
|
||||||
|
|
||||||
|
constexpr auto kShufflePlaylistLimit = 10'000;
|
||||||
|
|
||||||
constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
|
constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
|
||||||
|
|
||||||
auto VoicePlaybackSpeed() {
|
auto VoicePlaybackSpeed() {
|
||||||
|
@ -62,6 +66,21 @@ struct Instance::Streamed {
|
||||||
rpl::lifetime lifetime;
|
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) {
|
void start(not_null<Audio::Instance*> instance) {
|
||||||
Audio::Start(instance);
|
Audio::Start(instance);
|
||||||
Capture::Start();
|
Capture::Start();
|
||||||
|
@ -277,8 +296,14 @@ void Instance::playlistUpdated(not_null<Data*> data) {
|
||||||
if (data->playlistSlice) {
|
if (data->playlistSlice) {
|
||||||
const auto fullId = data->current.contextId();
|
const auto fullId = data->current.contextId();
|
||||||
data->playlistIndex = data->playlistSlice->indexOf(fullId);
|
data->playlistIndex = data->playlistSlice->indexOf(fullId);
|
||||||
|
if (data->order.current() == OrderMode::Shuffle) {
|
||||||
|
validateShuffleData(data);
|
||||||
|
} else {
|
||||||
|
data->shuffleData = nullptr;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data->playlistIndex = std::nullopt;
|
data->playlistIndex = std::nullopt;
|
||||||
|
data->shuffleData = nullptr;
|
||||||
}
|
}
|
||||||
data->playlistChanges.fire({});
|
data->playlistChanges.fire({});
|
||||||
}
|
}
|
||||||
|
@ -469,6 +494,41 @@ bool Instance::moveInPlaylist(
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data->order.current() == OrderMode::Shuffle) {
|
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
|
const auto newIndex = *data->playlistIndex
|
||||||
|
@ -495,6 +555,22 @@ bool Instance::moveInPlaylist(
|
||||||
return false;
|
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 {
|
bool Instance::previousAvailable(AudioMsgId::Type type) const {
|
||||||
const auto data = getData(type);
|
const auto data = getData(type);
|
||||||
Assert(data != nullptr);
|
Assert(data != nullptr);
|
||||||
|
@ -504,6 +580,8 @@ bool Instance::previousAvailable(AudioMsgId::Type type) const {
|
||||||
} else if (data->repeat.current() == RepeatMode::All) {
|
} else if (data->repeat.current() == RepeatMode::All) {
|
||||||
return true;
|
return true;
|
||||||
} else if (data->order.current() == OrderMode::Shuffle) {
|
} else if (data->order.current() == OrderMode::Shuffle) {
|
||||||
|
const auto raw = data->shuffleData.get();
|
||||||
|
return raw && (raw->indexInPlayedIds > 0);
|
||||||
}
|
}
|
||||||
return (data->order.current() == OrderMode::Reverse)
|
return (data->order.current() == OrderMode::Reverse)
|
||||||
? (*data->playlistIndex + 1 < data->playlistSlice->size())
|
? (*data->playlistIndex + 1 < data->playlistSlice->size())
|
||||||
|
@ -519,6 +597,13 @@ bool Instance::nextAvailable(AudioMsgId::Type type) const {
|
||||||
} else if (data->repeat.current() == RepeatMode::All) {
|
} else if (data->repeat.current() == RepeatMode::All) {
|
||||||
return true;
|
return true;
|
||||||
} else if (data->order.current() == OrderMode::Shuffle) {
|
} 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)
|
return (data->order.current() == OrderMode::Reverse)
|
||||||
? (*data->playlistIndex > 0)
|
? (*data->playlistIndex > 0)
|
||||||
|
@ -689,6 +774,120 @@ void Instance::stopAndClear(not_null<Data*> data) {
|
||||||
_tracksFinished.fire_copy(data->type);
|
_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) {
|
void Instance::playPause(AudioMsgId::Type type) {
|
||||||
if (const auto data = getData(type)) {
|
if (const auto data = getData(type)) {
|
||||||
if (!data->streamed) {
|
if (!data->streamed) {
|
||||||
|
|
|
@ -167,6 +167,7 @@ private:
|
||||||
using SharedMediaType = Storage::SharedMediaType;
|
using SharedMediaType = Storage::SharedMediaType;
|
||||||
using SliceKey = SparseIdsMergedSlice::Key;
|
using SliceKey = SparseIdsMergedSlice::Key;
|
||||||
struct Streamed;
|
struct Streamed;
|
||||||
|
struct ShuffleData;
|
||||||
struct Data {
|
struct Data {
|
||||||
Data(AudioMsgId::Type type, SharedMediaType overview);
|
Data(AudioMsgId::Type type, SharedMediaType overview);
|
||||||
Data(Data &&other);
|
Data(Data &&other);
|
||||||
|
@ -195,6 +196,7 @@ private:
|
||||||
bool isPlaying = false;
|
bool isPlaying = false;
|
||||||
bool resumeOnCallEnd = false;
|
bool resumeOnCallEnd = false;
|
||||||
std::unique_ptr<Streamed> streamed;
|
std::unique_ptr<Streamed> streamed;
|
||||||
|
std::unique_ptr<ShuffleData> shuffleData;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SeekingChanges {
|
struct SeekingChanges {
|
||||||
|
@ -237,6 +239,11 @@ private:
|
||||||
HistoryItem *itemByIndex(not_null<Data*> data, int index);
|
HistoryItem *itemByIndex(not_null<Data*> data, int index);
|
||||||
void stopAndClear(not_null<Data*> data);
|
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(
|
void handleStreamingUpdate(
|
||||||
not_null<Data*> data,
|
not_null<Data*> data,
|
||||||
Streaming::Update &&update);
|
Streaming::Update &&update);
|
||||||
|
|
Loading…
Add table
Reference in a new issue