mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Allow repeat all in shuffle mode.
This commit is contained in:
parent
7ef78ba6c9
commit
38367dc1c7
2 changed files with 129 additions and 44 deletions
|
@ -45,6 +45,7 @@ constexpr auto kIdsLimit = 32;
|
||||||
constexpr auto kIdsPreloadAfter = 28;
|
constexpr auto kIdsPreloadAfter = 28;
|
||||||
|
|
||||||
constexpr auto kShufflePlaylistLimit = 10'000;
|
constexpr auto kShufflePlaylistLimit = 10'000;
|
||||||
|
constexpr auto kRememberShuffledOrderItems = 16;
|
||||||
|
|
||||||
constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
|
constexpr auto kMinLengthForSavePosition = 20 * TimeId(60); // 20 minutes.
|
||||||
|
|
||||||
|
@ -123,10 +124,6 @@ Instance::Streamed::Streamed(
|
||||||
Instance::Data::Data(AudioMsgId::Type type, SharedMediaType overview)
|
Instance::Data::Data(AudioMsgId::Type type, SharedMediaType overview)
|
||||||
: type(type)
|
: type(type)
|
||||||
, overview(overview) {
|
, overview(overview) {
|
||||||
if (type == AudioMsgId::Type::Song) {
|
|
||||||
repeat = Core::App().settings().playerRepeatModeValue();
|
|
||||||
order = Core::App().settings().playerOrderModeValue();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Instance::Data::Data(Data &&other) = default;
|
Instance::Data::Data(Data &&other) = default;
|
||||||
|
@ -140,12 +137,22 @@ Instance::Instance()
|
||||||
handleSongUpdate(audioId);
|
handleSongUpdate(audioId);
|
||||||
});
|
});
|
||||||
|
|
||||||
_songData.repeat.changes(
|
repeatChanges(
|
||||||
|
&_songData
|
||||||
) | rpl::start_with_next([=](RepeatMode mode) {
|
) | rpl::start_with_next([=](RepeatMode mode) {
|
||||||
if (mode == RepeatMode::All) {
|
if (mode == RepeatMode::All) {
|
||||||
refreshPlaylist(&_songData);
|
refreshPlaylist(&_songData);
|
||||||
}
|
}
|
||||||
}, _lifetime);
|
}, _lifetime);
|
||||||
|
orderChanges(
|
||||||
|
&_songData
|
||||||
|
) | rpl::start_with_next([=](OrderMode mode) {
|
||||||
|
if (mode == OrderMode::Shuffle) {
|
||||||
|
validateShuffleData(&_songData);
|
||||||
|
} else {
|
||||||
|
_songData.shuffleData = nullptr;
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
|
||||||
using namespace rpl::mappers;
|
using namespace rpl::mappers;
|
||||||
rpl::combine(
|
rpl::combine(
|
||||||
|
@ -296,10 +303,8 @@ 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) {
|
if (order(data) == OrderMode::Shuffle) {
|
||||||
validateShuffleData(data);
|
validateShuffleData(data);
|
||||||
} else {
|
|
||||||
data->shuffleData = nullptr;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
data->playlistIndex = std::nullopt;
|
data->playlistIndex = std::nullopt;
|
||||||
|
@ -422,8 +427,8 @@ void Instance::validateOtherPlaylist(not_null<Data*> data) {
|
||||||
|
|
||||||
auto Instance::playlistOtherKey(not_null<const Data*> data) const
|
auto Instance::playlistOtherKey(not_null<const Data*> data) const
|
||||||
-> std::optional<SliceKey> {
|
-> std::optional<SliceKey> {
|
||||||
if (data->repeat.current() != RepeatMode::All
|
if (repeat(data) != RepeatMode::All
|
||||||
|| data->order.current() == OrderMode::Shuffle
|
|| order(data) == OrderMode::Shuffle
|
||||||
|| !data->playlistSlice
|
|| !data->playlistSlice
|
||||||
|| (data->playlistSlice->skippedBefore() != 0
|
|| (data->playlistSlice->skippedBefore() != 0
|
||||||
&& data->playlistSlice->skippedAfter() != 0)
|
&& data->playlistSlice->skippedAfter() != 0)
|
||||||
|
@ -493,46 +498,52 @@ bool Instance::moveInPlaylist(
|
||||||
return jumpByItem(data->history->owner().message(id));
|
return jumpByItem(data->history->owner().message(id));
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data->order.current() == OrderMode::Shuffle) {
|
if (order(data) == OrderMode::Shuffle) {
|
||||||
const auto raw = data->shuffleData.get();
|
const auto raw = data->shuffleData.get();
|
||||||
if (!raw || !raw->history) {
|
if (!raw || !raw->history) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const auto universal = computeCurrentUniversalId(data);
|
||||||
const auto byUniversal = [&](ShuffleData::UniversalMsgId id) {
|
const auto byUniversal = [&](ShuffleData::UniversalMsgId id) {
|
||||||
return (id < 0)
|
return (id < 0)
|
||||||
? jumpById({ ChannelId(), id + ServerMaxMsgId })
|
? jumpById({ ChannelId(), id + ServerMaxMsgId })
|
||||||
: jumpById({ raw->history->channelId(), id });
|
: jumpById({ raw->history->channelId(), id });
|
||||||
};
|
};
|
||||||
|
if (universal && 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (repeat(data) == RepeatMode::All) {
|
||||||
|
ensureShuffleMove(data, delta);
|
||||||
|
}
|
||||||
|
if (raw->nonPlayedIds.empty()
|
||||||
|
&& raw->indexInPlayedIds + 1 == raw->playedIds.size()) {
|
||||||
|
raw->nonPlayedIds.push_back(raw->playedIds.back());
|
||||||
|
raw->playedIds.pop_back();
|
||||||
|
}
|
||||||
|
const auto shuffleCompleted = raw->nonPlayedIds.empty()
|
||||||
|
|| (raw->nonPlayedIds.size() == 1
|
||||||
|
&& raw->nonPlayedIds.front() == universal);
|
||||||
if (delta < 0) {
|
if (delta < 0) {
|
||||||
return (raw->indexInPlayedIds > 0)
|
return (raw->indexInPlayedIds > 0)
|
||||||
&& byUniversal(raw->playedIds[--raw->indexInPlayedIds]);
|
&& byUniversal(raw->playedIds[--raw->indexInPlayedIds]);
|
||||||
} else if (raw->indexInPlayedIds + 1 < raw->playedIds.size()) {
|
} else if (raw->indexInPlayedIds + 1 < raw->playedIds.size()) {
|
||||||
return byUniversal(raw->playedIds[++raw->indexInPlayedIds]);
|
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]);
|
|
||||||
}
|
}
|
||||||
|
if (shuffleCompleted) {
|
||||||
|
return false;
|
||||||
|
} else if (raw->indexInPlayedIds < raw->playedIds.size()) {
|
||||||
|
++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
|
||||||
+ (data->order.current() == OrderMode::Reverse ? -delta : delta);
|
+ (order(data) == OrderMode::Reverse ? -delta : delta);
|
||||||
const auto useIndex = (!data->playlistSlice
|
const auto useIndex = (!data->playlistSlice
|
||||||
|| data->playlistSlice->skippedAfter() != 0
|
|| data->playlistSlice->skippedAfter() != 0
|
||||||
|| data->playlistSlice->skippedBefore() != 0
|
|| data->playlistSlice->skippedBefore() != 0
|
||||||
|
@ -542,7 +553,7 @@ bool Instance::moveInPlaylist(
|
||||||
% int(data->playlistSlice->size()));
|
% int(data->playlistSlice->size()));
|
||||||
if (const auto item = itemByIndex(data, useIndex)) {
|
if (const auto item = itemByIndex(data, useIndex)) {
|
||||||
return jumpByItem(item);
|
return jumpByItem(item);
|
||||||
} else if (data->repeat.current() == RepeatMode::All
|
} else if (repeat(data) == RepeatMode::All
|
||||||
&& data->playlistOtherSlice
|
&& data->playlistOtherSlice
|
||||||
&& data->playlistOtherSlice->size() > 0) {
|
&& data->playlistOtherSlice->size() > 0) {
|
||||||
const auto &other = *data->playlistOtherSlice;
|
const auto &other = *data->playlistOtherSlice;
|
||||||
|
@ -555,6 +566,48 @@ bool Instance::moveInPlaylist(
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Instance::ensureShuffleMove(not_null<Data*> data, int delta) {
|
||||||
|
const auto raw = data->shuffleData.get();
|
||||||
|
if (delta < 0) {
|
||||||
|
if (raw->indexInPlayedIds > 0) {
|
||||||
|
return;
|
||||||
|
} else if (raw->nonPlayedIds.size() < 2) {
|
||||||
|
const auto freeUp = std::max(
|
||||||
|
int(raw->playedIds.size() / 2),
|
||||||
|
int(raw->playlist.size()) - kRememberShuffledOrderItems);
|
||||||
|
const auto till = end(raw->playedIds);
|
||||||
|
const auto from = end(raw->playedIds) - freeUp;
|
||||||
|
raw->nonPlayedIds.insert(end(raw->nonPlayedIds), from, till);
|
||||||
|
raw->playedIds.erase(from, till);
|
||||||
|
}
|
||||||
|
if (raw->nonPlayedIds.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto index = base::RandomIndex(raw->nonPlayedIds.size());
|
||||||
|
raw->playedIds.insert(
|
||||||
|
begin(raw->playedIds),
|
||||||
|
raw->nonPlayedIds[index]);
|
||||||
|
raw->nonPlayedIds.erase(begin(raw->nonPlayedIds) + index);
|
||||||
|
++raw->indexInPlayedIds;
|
||||||
|
if (raw->nonPlayedIds.empty() && raw->playedIds.size() > 1) {
|
||||||
|
raw->nonPlayedIds.push_back(raw->playedIds.back());
|
||||||
|
raw->playedIds.pop_back();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if (raw->indexInPlayedIds + 1 < raw->playedIds.size()) {
|
||||||
|
return;
|
||||||
|
} else if (raw->nonPlayedIds.size() < 2) {
|
||||||
|
const auto freeUp = std::max(
|
||||||
|
int(raw->playedIds.size() / 2),
|
||||||
|
int(raw->playlist.size()) - kRememberShuffledOrderItems);
|
||||||
|
const auto from = begin(raw->playedIds);
|
||||||
|
const auto till = begin(raw->playedIds) + freeUp;
|
||||||
|
raw->nonPlayedIds.insert(end(raw->nonPlayedIds), from, till);
|
||||||
|
raw->playedIds.erase(from, till);
|
||||||
|
raw->indexInPlayedIds -= freeUp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MsgId Instance::computeCurrentUniversalId(not_null<const Data*> data) const {
|
MsgId Instance::computeCurrentUniversalId(not_null<const Data*> data) const {
|
||||||
const auto raw = data->shuffleData.get();
|
const auto raw = data->shuffleData.get();
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
|
@ -577,13 +630,13 @@ bool Instance::previousAvailable(AudioMsgId::Type type) const {
|
||||||
|
|
||||||
if (!data->playlistIndex || !data->playlistSlice) {
|
if (!data->playlistIndex || !data->playlistSlice) {
|
||||||
return false;
|
return false;
|
||||||
} else if (data->repeat.current() == RepeatMode::All) {
|
} else if (repeat(data) == RepeatMode::All) {
|
||||||
return true;
|
return true;
|
||||||
} else if (data->order.current() == OrderMode::Shuffle) {
|
} else if (order(data) == OrderMode::Shuffle) {
|
||||||
const auto raw = data->shuffleData.get();
|
const auto raw = data->shuffleData.get();
|
||||||
return raw && (raw->indexInPlayedIds > 0);
|
return raw && (raw->indexInPlayedIds > 0);
|
||||||
}
|
}
|
||||||
return (data->order.current() == OrderMode::Reverse)
|
return (order(data) == OrderMode::Reverse)
|
||||||
? (*data->playlistIndex + 1 < data->playlistSlice->size())
|
? (*data->playlistIndex + 1 < data->playlistSlice->size())
|
||||||
: (*data->playlistIndex > 0);
|
: (*data->playlistIndex > 0);
|
||||||
}
|
}
|
||||||
|
@ -594,9 +647,9 @@ bool Instance::nextAvailable(AudioMsgId::Type type) const {
|
||||||
|
|
||||||
if (!data->playlistIndex || !data->playlistSlice) {
|
if (!data->playlistIndex || !data->playlistSlice) {
|
||||||
return false;
|
return false;
|
||||||
} else if (data->repeat.current() == RepeatMode::All) {
|
} else if (repeat(data) == RepeatMode::All) {
|
||||||
return true;
|
return true;
|
||||||
} else if (data->order.current() == OrderMode::Shuffle) {
|
} else if (order(data) == OrderMode::Shuffle) {
|
||||||
const auto raw = data->shuffleData.get();
|
const auto raw = data->shuffleData.get();
|
||||||
const auto universal = computeCurrentUniversalId(data);
|
const auto universal = computeCurrentUniversalId(data);
|
||||||
return raw
|
return raw
|
||||||
|
@ -605,7 +658,7 @@ bool Instance::nextAvailable(AudioMsgId::Type type) const {
|
||||||
|| (!raw->nonPlayedIds.empty()
|
|| (!raw->nonPlayedIds.empty()
|
||||||
&& raw->nonPlayedIds.front() != universal));
|
&& raw->nonPlayedIds.front() != universal));
|
||||||
}
|
}
|
||||||
return (data->order.current() == OrderMode::Reverse)
|
return (order(data) == OrderMode::Reverse)
|
||||||
? (*data->playlistIndex > 0)
|
? (*data->playlistIndex > 0)
|
||||||
: (*data->playlistIndex + 1 < data->playlistSlice->size());
|
: (*data->playlistIndex + 1 < data->playlistSlice->size());
|
||||||
}
|
}
|
||||||
|
@ -617,8 +670,8 @@ rpl::producer<> Media::Player::Instance::playlistChanges(
|
||||||
|
|
||||||
return rpl::merge(
|
return rpl::merge(
|
||||||
data->playlistChanges.events(),
|
data->playlistChanges.events(),
|
||||||
data->order.changes() | rpl::to_empty,
|
orderChanges(data) | rpl::to_empty,
|
||||||
data->repeat.changes() | rpl::to_empty);
|
repeatChanges(data) | rpl::to_empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
rpl::producer<> Media::Player::Instance::stops(AudioMsgId::Type type) const {
|
rpl::producer<> Media::Player::Instance::stops(AudioMsgId::Type type) const {
|
||||||
|
@ -1044,6 +1097,32 @@ void Instance::emitUpdate(AudioMsgId::Type type) {
|
||||||
emitUpdate(type, [](const AudioMsgId &playing) { return true; });
|
emitUpdate(type, [](const AudioMsgId &playing) { return true; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RepeatMode Instance::repeat(not_null<const Data*> data) const {
|
||||||
|
return (data->type == AudioMsgId::Type::Song)
|
||||||
|
? Core::App().settings().playerRepeatMode()
|
||||||
|
: RepeatMode::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<RepeatMode> Instance::repeatChanges(
|
||||||
|
not_null<const Data*> data) const {
|
||||||
|
return (data->type == AudioMsgId::Type::Song)
|
||||||
|
? Core::App().settings().playerRepeatModeChanges()
|
||||||
|
: rpl::never<RepeatMode>();
|
||||||
|
}
|
||||||
|
|
||||||
|
OrderMode Instance::order(not_null<const Data*> data) const {
|
||||||
|
return (data->type == AudioMsgId::Type::Song)
|
||||||
|
? Core::App().settings().playerOrderMode()
|
||||||
|
: OrderMode::Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<OrderMode> Instance::orderChanges(
|
||||||
|
not_null<const Data*> data) const {
|
||||||
|
return (data->type == AudioMsgId::Type::Song)
|
||||||
|
? Core::App().settings().playerOrderModeChanges()
|
||||||
|
: rpl::never<OrderMode>();
|
||||||
|
}
|
||||||
|
|
||||||
TrackState Instance::getState(AudioMsgId::Type type) const {
|
TrackState Instance::getState(AudioMsgId::Type type) const {
|
||||||
if (const auto data = getData(type)) {
|
if (const auto data = getData(type)) {
|
||||||
if (data->streamed) {
|
if (data->streamed) {
|
||||||
|
@ -1092,7 +1171,7 @@ void Instance::emitUpdate(AudioMsgId::Type type, CheckCallback check) {
|
||||||
auto finished = false;
|
auto finished = false;
|
||||||
_updatedNotifier.fire_copy({state});
|
_updatedNotifier.fire_copy({state});
|
||||||
if (data->isPlaying && state.state == State::StoppedAtEnd) {
|
if (data->isPlaying && state.state == State::StoppedAtEnd) {
|
||||||
if (data->repeat.current() == RepeatMode::One) {
|
if (repeat(data) == RepeatMode::One) {
|
||||||
play(data->current);
|
play(data->current);
|
||||||
} else if (!moveInPlaylist(data, 1, true)) {
|
} else if (!moveInPlaylist(data, 1, true)) {
|
||||||
finished = true;
|
finished = true;
|
||||||
|
|
|
@ -191,8 +191,6 @@ private:
|
||||||
History *history = nullptr;
|
History *history = nullptr;
|
||||||
History *migrated = nullptr;
|
History *migrated = nullptr;
|
||||||
Main::Session *session = nullptr;
|
Main::Session *session = nullptr;
|
||||||
rpl::variable<RepeatMode> repeat = RepeatMode::None;
|
|
||||||
rpl::variable<OrderMode> order = OrderMode::Default;
|
|
||||||
bool isPlaying = false;
|
bool isPlaying = false;
|
||||||
bool resumeOnCallEnd = false;
|
bool resumeOnCallEnd = false;
|
||||||
std::unique_ptr<Streamed> streamed;
|
std::unique_ptr<Streamed> streamed;
|
||||||
|
@ -243,6 +241,7 @@ private:
|
||||||
not_null<const Data*> data) const;
|
not_null<const Data*> data) const;
|
||||||
void validateShuffleData(not_null<Data*> data);
|
void validateShuffleData(not_null<Data*> data);
|
||||||
void setupShuffleData(not_null<Data*> data);
|
void setupShuffleData(not_null<Data*> data);
|
||||||
|
void ensureShuffleMove(not_null<Data*> data, int delta);
|
||||||
|
|
||||||
void handleStreamingUpdate(
|
void handleStreamingUpdate(
|
||||||
not_null<Data*> data,
|
not_null<Data*> data,
|
||||||
|
@ -256,6 +255,13 @@ private:
|
||||||
template <typename CheckCallback>
|
template <typename CheckCallback>
|
||||||
void emitUpdate(AudioMsgId::Type type, CheckCallback check);
|
void emitUpdate(AudioMsgId::Type type, CheckCallback check);
|
||||||
|
|
||||||
|
[[nodiscard]] RepeatMode repeat(not_null<const Data*> data) const;
|
||||||
|
[[nodiscard]] rpl::producer<RepeatMode> repeatChanges(
|
||||||
|
not_null<const Data*> data) const;
|
||||||
|
[[nodiscard]] OrderMode order(not_null<const Data*> data) const;
|
||||||
|
[[nodiscard]] rpl::producer<OrderMode> orderChanges(
|
||||||
|
not_null<const Data*> data) const;
|
||||||
|
|
||||||
Data *getData(AudioMsgId::Type type) {
|
Data *getData(AudioMsgId::Type type) {
|
||||||
if (type == AudioMsgId::Type::Song) {
|
if (type == AudioMsgId::Type::Song) {
|
||||||
return &_songData;
|
return &_songData;
|
||||||
|
|
Loading…
Add table
Reference in a new issue