mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
connect: rework aftermath
This commit is contained in:
parent
d3ae5c0820
commit
11040d994e
5 changed files with 68 additions and 25 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::state::context::ContextType;
|
||||||
use crate::{
|
use crate::{
|
||||||
core::{Error, Session},
|
core::{Error, Session},
|
||||||
protocol::{
|
protocol::{
|
||||||
|
@ -6,6 +7,7 @@ use crate::{
|
||||||
},
|
},
|
||||||
state::{context::UpdateContext, ConnectState},
|
state::{context::UpdateContext, ConnectState},
|
||||||
};
|
};
|
||||||
|
use std::cmp::PartialEq;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
fmt::{Display, Formatter},
|
fmt::{Display, Formatter},
|
||||||
|
@ -75,10 +77,9 @@ impl ResolveContext {
|
||||||
// otherwise we might not even check if we need to fallback and just use the fallback uri
|
// otherwise we might not even check if we need to fallback and just use the fallback uri
|
||||||
match self.resolve {
|
match self.resolve {
|
||||||
Resolve::Uri(ref uri) => ConnectState::valid_resolve_uri(uri),
|
Resolve::Uri(ref uri) => ConnectState::valid_resolve_uri(uri),
|
||||||
Resolve::Context(ref ctx) => {
|
Resolve::Context(ref ctx) => ConnectState::get_context_uri_from_context(ctx),
|
||||||
ConnectState::get_context_uri_from_context(ctx).or(self.fallback.as_deref())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.or(self.fallback.as_deref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// the actual context uri
|
/// the actual context uri
|
||||||
|
@ -147,6 +148,8 @@ pub struct ContextResolver {
|
||||||
// time after which an unavailable context is retried
|
// time after which an unavailable context is retried
|
||||||
const RETRY_UNAVAILABLE: Duration = Duration::from_secs(3600);
|
const RETRY_UNAVAILABLE: Duration = Duration::from_secs(3600);
|
||||||
|
|
||||||
|
const CONCERNING_AMOUNT_OF_SKIPS: usize = 1_000;
|
||||||
|
|
||||||
impl ContextResolver {
|
impl ContextResolver {
|
||||||
pub fn new(session: Session) -> Self {
|
pub fn new(session: Session) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
@ -208,6 +211,8 @@ impl ContextResolver {
|
||||||
loop {
|
loop {
|
||||||
let next = self.queue.front()?;
|
let next = self.queue.front()?;
|
||||||
match next.resolve_uri() {
|
match next.resolve_uri() {
|
||||||
|
// this is here to prevent an endless amount of skips
|
||||||
|
None if idx > CONCERNING_AMOUNT_OF_SKIPS => unreachable!(),
|
||||||
None => {
|
None => {
|
||||||
warn!("skipped {idx} because of no valid resolve_uri: {next}");
|
warn!("skipped {idx} because of no valid resolve_uri: {next}");
|
||||||
idx += 1;
|
idx += 1;
|
||||||
|
@ -318,7 +323,16 @@ impl ContextResolver {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("last item of type <{:?}> finishing state", next.update);
|
debug!("last item of type <{:?}>, finishing state", next.update);
|
||||||
|
|
||||||
|
match (next.update, state.active_context) {
|
||||||
|
(UpdateContext::Default, ContextType::Default) => {}
|
||||||
|
(UpdateContext::Default, _) => {
|
||||||
|
debug!("skipped finishing default, because it isn't the active context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
(UpdateContext::Autoplay, _) => {}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(transfer_state) = transfer_state.take() {
|
if let Some(transfer_state) = transfer_state.take() {
|
||||||
if let Err(why) = state.finish_transfer(transfer_state) {
|
if let Err(why) = state.finish_transfer(transfer_state) {
|
||||||
|
@ -328,8 +342,17 @@ impl ContextResolver {
|
||||||
|
|
||||||
let res = if state.shuffling_context() {
|
let res = if state.shuffling_context() {
|
||||||
state.shuffle()
|
state.shuffle()
|
||||||
|
} else if let Ok(ctx) = state.get_context(state.active_context) {
|
||||||
|
let idx = ConnectState::find_index_in_context(ctx, |t| {
|
||||||
|
state.current_track(|c| t.uri == c.uri)
|
||||||
|
})
|
||||||
|
.ok();
|
||||||
|
|
||||||
|
state
|
||||||
|
.reset_playback_to_position(idx)
|
||||||
|
.and_then(|_| state.fill_up_next_tracks())
|
||||||
} else {
|
} else {
|
||||||
state.reset_playback_to_position(Some(state.player().index.track as usize))
|
state.fill_up_next_tracks()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(why) = res {
|
if let Err(why) = res {
|
||||||
|
|
|
@ -985,6 +985,13 @@ impl SpircTask {
|
||||||
state.set_active(true);
|
state.set_active(true);
|
||||||
state.handle_initial_transfer(&mut transfer);
|
state.handle_initial_transfer(&mut transfer);
|
||||||
|
|
||||||
|
// adjust active context, so resolve knows for which context it should set up the state
|
||||||
|
state.active_context = if autoplay {
|
||||||
|
ContextType::Autoplay
|
||||||
|
} else {
|
||||||
|
ContextType::Default
|
||||||
|
};
|
||||||
|
|
||||||
// update position if the track continued playing
|
// update position if the track continued playing
|
||||||
let position = if transfer.playback.is_paused {
|
let position = if transfer.playback.is_paused {
|
||||||
transfer.playback.position_as_of_timestamp.into()
|
transfer.playback.position_as_of_timestamp.into()
|
||||||
|
@ -1110,6 +1117,8 @@ impl SpircTask {
|
||||||
// for play commands with skip by uid, the context of the command contains
|
// for play commands with skip by uid, the context of the command contains
|
||||||
// tracks with uri and uid, so we merge the new context with the resolved/existing context
|
// tracks with uri and uid, so we merge the new context with the resolved/existing context
|
||||||
self.connect_state.merge_context(context);
|
self.connect_state.merge_context(context);
|
||||||
|
|
||||||
|
// load here, so that we clear the queue only after we definitely retrieved a new context
|
||||||
self.connect_state.clear_next_tracks(false);
|
self.connect_state.clear_next_tracks(false);
|
||||||
self.connect_state.clear_restrictions();
|
self.connect_state.clear_restrictions();
|
||||||
|
|
||||||
|
@ -1143,10 +1152,12 @@ impl SpircTask {
|
||||||
self.connect_state.update_queue_revision()
|
self.connect_state.update_queue_revision()
|
||||||
} else {
|
} else {
|
||||||
self.connect_state.shuffle()?;
|
self.connect_state.shuffle()?;
|
||||||
|
self.add_autoplay_resolving_when_required();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.connect_state.set_current_track(index)?;
|
self.connect_state.set_current_track(index)?;
|
||||||
self.connect_state.reset_playback_to_position(Some(index))?;
|
self.connect_state.reset_playback_to_position(Some(index))?;
|
||||||
|
self.add_autoplay_resolving_when_required();
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.connect_state.current_track(MessageField::is_some) {
|
if self.connect_state.current_track(MessageField::is_some) {
|
||||||
|
@ -1327,9 +1338,8 @@ impl SpircTask {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
self.add_autoplay_resolving_when_required();
|
|
||||||
|
|
||||||
if has_next_track {
|
if has_next_track {
|
||||||
|
self.add_autoplay_resolving_when_required();
|
||||||
self.load_track(continue_playing, 0)
|
self.load_track(continue_playing, 0)
|
||||||
} else {
|
} else {
|
||||||
info!("Not playing next track because there are no more tracks left in queue.");
|
info!("Not playing next track because there are no more tracks left in queue.");
|
||||||
|
|
|
@ -106,7 +106,7 @@ pub struct ConnectState {
|
||||||
// separation is necessary because we could have already loaded
|
// separation is necessary because we could have already loaded
|
||||||
// the autoplay context but are still playing from the default context
|
// the autoplay context but are still playing from the default context
|
||||||
/// to update the active context use [switch_active_context](ConnectState::set_active_context)
|
/// to update the active context use [switch_active_context](ConnectState::set_active_context)
|
||||||
active_context: ContextType,
|
pub active_context: ContextType,
|
||||||
fill_up_context: ContextType,
|
fill_up_context: ContextType,
|
||||||
|
|
||||||
/// the context from which we play, is used to top up prev and next tracks
|
/// the context from which we play, is used to top up prev and next tracks
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::{
|
||||||
};
|
};
|
||||||
use protobuf::MessageField;
|
use protobuf::MessageField;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::ops::Deref;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
const LOCAL_FILES_IDENTIFIER: &str = "spotify:local-files";
|
const LOCAL_FILES_IDENTIFIER: &str = "spotify:local-files";
|
||||||
|
@ -21,7 +22,7 @@ pub struct StateContext {
|
||||||
pub index: ContextIndex,
|
pub index: ContextIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Copy, Clone)]
|
#[derive(Default, Debug, Copy, Clone, PartialEq)]
|
||||||
pub enum ContextType {
|
pub enum ContextType {
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
|
@ -35,6 +36,17 @@ pub enum UpdateContext {
|
||||||
Autoplay,
|
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> {
|
pub enum ResetContext<'s> {
|
||||||
Completely,
|
Completely,
|
||||||
DefaultIndex,
|
DefaultIndex,
|
||||||
|
@ -86,11 +98,16 @@ impl ConnectState {
|
||||||
&self.player().context_uri
|
&self.player().context_uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn different_context_uri(&self, uri: &str) -> bool {
|
||||||
|
// search identifier is always different
|
||||||
|
self.context_uri() != uri || uri.starts_with(SEARCH_IDENTIFIER)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset_context(&mut self, mut reset_as: ResetContext) {
|
pub fn reset_context(&mut self, mut reset_as: ResetContext) {
|
||||||
self.set_active_context(ContextType::Default);
|
self.set_active_context(ContextType::Default);
|
||||||
self.fill_up_context = ContextType::Default;
|
self.fill_up_context = ContextType::Default;
|
||||||
|
|
||||||
if matches!(reset_as, ResetContext::WhenDifferent(ctx) if self.context_uri() != ctx) {
|
if matches!(reset_as, ResetContext::WhenDifferent(ctx) if self.different_context_uri(ctx)) {
|
||||||
reset_as = ResetContext::Completely
|
reset_as = ResetContext::Completely
|
||||||
}
|
}
|
||||||
self.shuffle_context = None;
|
self.shuffle_context = None;
|
||||||
|
@ -134,7 +151,7 @@ impl ConnectState {
|
||||||
|
|
||||||
let ctx = match self.get_context(new_context) {
|
let ctx = match self.get_context(new_context) {
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
debug!("couldn't load context info because: {why}");
|
warn!("couldn't load context info because: {why}");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(ctx) => ctx,
|
Ok(ctx) => ctx,
|
||||||
|
@ -184,17 +201,8 @@ impl ConnectState {
|
||||||
Some(p) => p,
|
Some(p) => p,
|
||||||
};
|
};
|
||||||
|
|
||||||
let prev_context = match ty {
|
|
||||||
UpdateContext::Default => self.context.as_ref(),
|
|
||||||
UpdateContext::Autoplay => self.autoplay_context.as_ref(),
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"updated context {ty:?} from <{}> ({} tracks) to <{}> ({} tracks)",
|
"updated context {ty:?} to <{}> ({} tracks)",
|
||||||
self.context_uri(),
|
|
||||||
prev_context
|
|
||||||
.map(|c| c.tracks.len().to_string())
|
|
||||||
.unwrap_or_else(|| "-".to_string()),
|
|
||||||
context.uri,
|
context.uri,
|
||||||
page.tracks.len()
|
page.tracks.len()
|
||||||
);
|
);
|
||||||
|
@ -210,8 +218,8 @@ impl ConnectState {
|
||||||
);
|
);
|
||||||
|
|
||||||
// when we update the same context, we should try to preserve the previous position
|
// when we update the same context, we should try to preserve the previous position
|
||||||
// otherwise we might load the entire context twice
|
// otherwise we might load the entire context twice, unless it's the search context
|
||||||
if !self.context_uri().contains(SEARCH_IDENTIFIER)
|
if !self.context_uri().starts_with(SEARCH_IDENTIFIER)
|
||||||
&& self.context_uri() == &context.uri
|
&& self.context_uri() == &context.uri
|
||||||
{
|
{
|
||||||
match Self::find_index_in_context(&new_context, |t| {
|
match Self::find_index_in_context(&new_context, |t| {
|
||||||
|
@ -234,7 +242,7 @@ impl ConnectState {
|
||||||
|
|
||||||
self.context = Some(new_context);
|
self.context = Some(new_context);
|
||||||
|
|
||||||
if !context.url.contains(SEARCH_IDENTIFIER) {
|
if !context.url.starts_with(SEARCH_IDENTIFIER) {
|
||||||
self.player_mut().context_url = context.url;
|
self.player_mut().context_url = context.url;
|
||||||
} else {
|
} else {
|
||||||
self.player_mut().context_url.clear()
|
self.player_mut().context_url.clear()
|
||||||
|
|
|
@ -128,6 +128,8 @@ impl<'ct> ConnectState {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
debug!("next track is {new_track:#?}");
|
||||||
|
|
||||||
let new_track = match new_track {
|
let new_track = match new_track {
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
|
@ -280,7 +282,7 @@ impl<'ct> ConnectState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fill_up_next_tracks(&mut self) -> Result<(), StateError> {
|
pub fn fill_up_next_tracks(&mut self) -> Result<(), Error> {
|
||||||
let ctx = self.get_context(self.fill_up_context)?;
|
let ctx = self.get_context(self.fill_up_context)?;
|
||||||
let mut new_index = ctx.index.track as usize;
|
let mut new_index = ctx.index.track as usize;
|
||||||
let mut iteration = ctx.index.page;
|
let mut iteration = ctx.index.page;
|
||||||
|
|
Loading…
Reference in a new issue