mirror of
https://github.com/librespot-org/librespot.git
synced 2025-10-05 02:39:53 +02:00
Shuffle tracks in place (#1445)
* connect: add shuffle_vec.rs * connect: shuffle in place * add shuffle with seed option * reduce complexity to add new metadata fields * add context index to metadata * use seed for shuffle When losing the connection and restarting the dealer, the seed is now stored in the context metadata. So on transfer we can pickup the seed again and shuffle the context as it was previously * add log for shuffle seed * connect: use small_rng, derive Default * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
parent
471735aa5a
commit
34762f2274
12 changed files with 314 additions and 170 deletions
|
@ -7,6 +7,7 @@ use crate::{
|
|||
player::{ContextIndex, ProvidedTrack},
|
||||
restrictions::Restrictions,
|
||||
},
|
||||
shuffle_vec::ShuffleVec,
|
||||
state::{
|
||||
metadata::Metadata,
|
||||
provider::{IsProvider, Provider},
|
||||
|
@ -15,46 +16,28 @@ use crate::{
|
|||
};
|
||||
use protobuf::MessageField;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use uuid::Uuid;
|
||||
|
||||
const LOCAL_FILES_IDENTIFIER: &str = "spotify:local-files";
|
||||
const SEARCH_IDENTIFIER: &str = "spotify:search";
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct StateContext {
|
||||
pub tracks: Vec<ProvidedTrack>,
|
||||
pub tracks: ShuffleVec<ProvidedTrack>,
|
||||
pub skip_track: Option<ProvidedTrack>,
|
||||
pub metadata: HashMap<String, String>,
|
||||
pub restrictions: Option<Restrictions>,
|
||||
/// is used to keep track which tracks are already loaded into the next_tracks
|
||||
pub index: ContextIndex,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
||||
#[derive(Default, Debug, Copy, Clone, PartialEq, Hash, Eq)]
|
||||
pub enum ContextType {
|
||||
#[default]
|
||||
Default,
|
||||
Shuffle,
|
||||
Autoplay,
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum UpdateContext {
|
||||
Default,
|
||||
Autoplay,
|
||||
}
|
||||
|
||||
impl Deref for UpdateContext {
|
||||
type Target = ContextType;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
match self {
|
||||
UpdateContext::Default => &ContextType::Default,
|
||||
UpdateContext::Autoplay => &ContextType::Autoplay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResetContext<'s> {
|
||||
Completely,
|
||||
DefaultIndex,
|
||||
|
@ -96,12 +79,19 @@ impl ConnectState {
|
|||
pub fn get_context(&self, ty: ContextType) -> Result<&StateContext, StateError> {
|
||||
match ty {
|
||||
ContextType::Default => self.context.as_ref(),
|
||||
ContextType::Shuffle => self.shuffle_context.as_ref(),
|
||||
ContextType::Autoplay => self.autoplay_context.as_ref(),
|
||||
}
|
||||
.ok_or(StateError::NoContext(ty))
|
||||
}
|
||||
|
||||
pub fn get_context_mut(&mut self, ty: ContextType) -> Result<&mut StateContext, StateError> {
|
||||
match ty {
|
||||
ContextType::Default => self.context.as_mut(),
|
||||
ContextType::Autoplay => self.autoplay_context.as_mut(),
|
||||
}
|
||||
.ok_or(StateError::NoContext(ty))
|
||||
}
|
||||
|
||||
pub fn context_uri(&self) -> &String {
|
||||
&self.player().context_uri
|
||||
}
|
||||
|
@ -115,14 +105,18 @@ impl ConnectState {
|
|||
if matches!(reset_as, ResetContext::WhenDifferent(ctx) if self.different_context_uri(ctx)) {
|
||||
reset_as = ResetContext::Completely
|
||||
}
|
||||
self.shuffle_context = None;
|
||||
|
||||
if let Ok(ctx) = self.get_context_mut(ContextType::Default) {
|
||||
ctx.remove_shuffle_seed();
|
||||
ctx.tracks.unshuffle()
|
||||
}
|
||||
|
||||
match reset_as {
|
||||
ResetContext::WhenDifferent(_) => debug!("context didn't change, no reset"),
|
||||
ResetContext::Completely => {
|
||||
self.context = None;
|
||||
self.autoplay_context = None;
|
||||
}
|
||||
ResetContext::WhenDifferent(_) => debug!("context didn't change, no reset"),
|
||||
ResetContext::DefaultIndex => {
|
||||
for ctx in [self.context.as_mut(), self.autoplay_context.as_mut()]
|
||||
.into_iter()
|
||||
|
@ -190,7 +184,7 @@ impl ConnectState {
|
|||
pub fn update_context(
|
||||
&mut self,
|
||||
mut context: Context,
|
||||
ty: UpdateContext,
|
||||
ty: ContextType,
|
||||
) -> Result<Option<Vec<String>>, Error> {
|
||||
if context.pages.iter().all(|p| p.tracks.is_empty()) {
|
||||
error!("context didn't have any tracks: {context:#?}");
|
||||
|
@ -221,12 +215,13 @@ impl ConnectState {
|
|||
);
|
||||
|
||||
match ty {
|
||||
UpdateContext::Default => {
|
||||
ContextType::Default => {
|
||||
let mut new_context = self.state_context_from_page(
|
||||
page,
|
||||
context.metadata,
|
||||
context.restrictions.take(),
|
||||
context.uri.as_deref(),
|
||||
Some(0),
|
||||
None,
|
||||
);
|
||||
|
||||
|
@ -245,7 +240,7 @@ impl ConnectState {
|
|||
};
|
||||
|
||||
// enforce reloading the context
|
||||
if let Some(autoplay_ctx) = self.autoplay_context.as_mut() {
|
||||
if let Ok(autoplay_ctx) = self.get_context_mut(ContextType::Autoplay) {
|
||||
autoplay_ctx.index.track = 0
|
||||
}
|
||||
self.clear_next_tracks();
|
||||
|
@ -261,12 +256,13 @@ impl ConnectState {
|
|||
}
|
||||
self.player_mut().context_uri = context.uri.take().unwrap_or_default();
|
||||
}
|
||||
UpdateContext::Autoplay => {
|
||||
ContextType::Autoplay => {
|
||||
self.autoplay_context = Some(self.state_context_from_page(
|
||||
page,
|
||||
context.metadata,
|
||||
context.restrictions.take(),
|
||||
context.uri.as_deref(),
|
||||
None,
|
||||
Some(Provider::Autoplay),
|
||||
))
|
||||
}
|
||||
|
@ -349,6 +345,7 @@ impl ConnectState {
|
|||
metadata: HashMap<String, String>,
|
||||
restrictions: Option<Restrictions>,
|
||||
new_context_uri: Option<&str>,
|
||||
context_length: Option<usize>,
|
||||
provider: Option<Provider>,
|
||||
) -> StateContext {
|
||||
let new_context_uri = new_context_uri.unwrap_or(self.context_uri());
|
||||
|
@ -356,10 +353,12 @@ impl ConnectState {
|
|||
let tracks = page
|
||||
.tracks
|
||||
.iter()
|
||||
.flat_map(|track| {
|
||||
.enumerate()
|
||||
.flat_map(|(i, track)| {
|
||||
match self.context_to_provided_track(
|
||||
track,
|
||||
Some(new_context_uri),
|
||||
context_length.map(|l| l + i),
|
||||
Some(&page.metadata),
|
||||
provider.clone(),
|
||||
) {
|
||||
|
@ -373,20 +372,28 @@ impl ConnectState {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
StateContext {
|
||||
tracks,
|
||||
tracks: tracks.into(),
|
||||
skip_track: None,
|
||||
restrictions,
|
||||
metadata,
|
||||
index: ContextIndex::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_skip_track(&self, track: &ProvidedTrack) -> bool {
|
||||
self.get_context(self.active_context)
|
||||
.ok()
|
||||
.and_then(|t| t.skip_track.as_ref().map(|t| t.uri == track.uri))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub fn merge_context(&mut self, context: Option<Context>) -> Option<()> {
|
||||
let mut context = context?;
|
||||
if matches!(context.uri, Some(ref uri) if uri != self.context_uri()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let current_context = self.context.as_mut()?;
|
||||
let current_context = self.get_context_mut(ContextType::Default).ok()?;
|
||||
let new_page = context.pages.pop()?;
|
||||
|
||||
for new_track in new_page.tracks {
|
||||
|
@ -421,12 +428,7 @@ impl ConnectState {
|
|||
ty: ContextType,
|
||||
new_index: usize,
|
||||
) -> Result<(), StateError> {
|
||||
let context = match ty {
|
||||
ContextType::Default => self.context.as_mut(),
|
||||
ContextType::Shuffle => self.shuffle_context.as_mut(),
|
||||
ContextType::Autoplay => self.autoplay_context.as_mut(),
|
||||
}
|
||||
.ok_or(StateError::NoContext(ty))?;
|
||||
let context = self.get_context_mut(ty)?;
|
||||
|
||||
context.index.track = new_index as u32;
|
||||
Ok(())
|
||||
|
@ -436,6 +438,7 @@ impl ConnectState {
|
|||
&self,
|
||||
ctx_track: &ContextTrack,
|
||||
context_uri: Option<&str>,
|
||||
context_index: Option<usize>,
|
||||
page_metadata: Option<&HashMap<String, String>>,
|
||||
provider: Option<Provider>,
|
||||
) -> Result<ProvidedTrack, Error> {
|
||||
|
@ -479,19 +482,25 @@ impl ConnectState {
|
|||
};
|
||||
|
||||
if let Some(context_uri) = context_uri {
|
||||
track.set_context_uri(context_uri.to_string());
|
||||
track.set_entity_uri(context_uri.to_string());
|
||||
track.set_entity_uri(context_uri);
|
||||
track.set_context_uri(context_uri);
|
||||
}
|
||||
|
||||
if let Some(index) = context_index {
|
||||
track.set_context_index(index);
|
||||
}
|
||||
|
||||
if matches!(provider, Provider::Autoplay) {
|
||||
track.set_autoplay(true)
|
||||
track.set_from_autoplay(true)
|
||||
}
|
||||
|
||||
Ok(track)
|
||||
}
|
||||
|
||||
pub fn fill_context_from_page(&mut self, page: ContextPage) -> Result<(), Error> {
|
||||
let context = self.state_context_from_page(page, HashMap::new(), None, None, None);
|
||||
let ctx_len = self.context.as_ref().map(|c| c.tracks.len());
|
||||
let context = self.state_context_from_page(page, HashMap::new(), None, None, ctx_len, None);
|
||||
|
||||
let ctx = self
|
||||
.context
|
||||
.as_mut()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue