2021-12-26 20:18:42 +00:00
|
|
|
use std::{
|
|
|
|
future::Future,
|
|
|
|
pin::Pin,
|
2022-01-16 20:29:59 +00:00
|
|
|
sync::atomic::{AtomicUsize, Ordering},
|
2023-06-13 14:56:40 +00:00
|
|
|
sync::Arc,
|
2021-12-26 20:18:42 +00:00
|
|
|
time::{SystemTime, UNIX_EPOCH},
|
|
|
|
};
|
|
|
|
|
2022-09-30 19:36:20 +00:00
|
|
|
use futures_util::{stream::FusedStream, FutureExt, StreamExt};
|
2021-12-26 20:18:42 +00:00
|
|
|
|
2024-03-31 16:33:28 +00:00
|
|
|
use protobuf::Message;
|
2023-01-17 20:46:14 +00:00
|
|
|
use rand::prelude::SliceRandom;
|
2021-12-26 20:18:42 +00:00
|
|
|
use thiserror::Error;
|
2021-02-21 18:38:40 +00:00
|
|
|
use tokio::sync::mpsc;
|
|
|
|
use tokio_stream::wrappers::UnboundedReceiverStream;
|
2017-11-03 01:15:27 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
use crate::{
|
2022-01-05 20:15:19 +00:00
|
|
|
config::ConnectConfig,
|
2022-10-01 21:01:17 +00:00
|
|
|
context::PageContext,
|
2021-12-26 20:18:42 +00:00
|
|
|
core::{
|
2022-09-30 19:36:20 +00:00
|
|
|
authentication::Credentials, mercury::MercurySender, session::UserAttributes,
|
|
|
|
util::SeqGenerator, version, Error, Session, SpotifyId,
|
2021-12-26 20:18:42 +00:00
|
|
|
},
|
|
|
|
playback::{
|
|
|
|
mixer::Mixer,
|
|
|
|
player::{Player, PlayerEvent, PlayerEventChannel},
|
|
|
|
},
|
|
|
|
protocol::{
|
|
|
|
self,
|
|
|
|
explicit_content_pubsub::UserAttributesUpdate,
|
|
|
|
spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef},
|
|
|
|
user_attributes::UserAttributesMutation,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
pub enum SpircError {
|
|
|
|
#[error("response payload empty")]
|
|
|
|
NoData,
|
2022-08-23 20:23:37 +00:00
|
|
|
#[error("playback of local files is not supported")]
|
|
|
|
UnsupportedLocalPlayBack,
|
2021-12-26 20:18:42 +00:00
|
|
|
#[error("message addressed at another ident: {0}")]
|
|
|
|
Ident(String),
|
|
|
|
#[error("message pushed for another URI")]
|
|
|
|
InvalidUri(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<SpircError> for Error {
|
|
|
|
fn from(err: SpircError) -> Self {
|
2022-08-23 20:23:37 +00:00
|
|
|
use SpircError::*;
|
2021-12-26 20:18:42 +00:00
|
|
|
match err {
|
2022-08-23 20:23:37 +00:00
|
|
|
NoData | UnsupportedLocalPlayBack => Error::unavailable(err),
|
|
|
|
Ident(_) | InvalidUri(_) => Error::aborted(err),
|
2021-12-26 20:18:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
2020-01-31 21:41:11 +00:00
|
|
|
enum SpircPlayStatus {
|
|
|
|
Stopped,
|
2020-02-02 22:15:15 +00:00
|
|
|
LoadingPlay {
|
|
|
|
position_ms: u32,
|
|
|
|
},
|
|
|
|
LoadingPause {
|
|
|
|
position_ms: u32,
|
|
|
|
},
|
|
|
|
Playing {
|
|
|
|
nominal_start_time: i64,
|
|
|
|
preloading_of_next_track_triggered: bool,
|
|
|
|
},
|
|
|
|
Paused {
|
|
|
|
position_ms: u32,
|
|
|
|
preloading_of_next_track_triggered: bool,
|
|
|
|
},
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
2021-02-19 23:17:18 +00:00
|
|
|
type BoxedStream<T> = Pin<Box<dyn FusedStream<Item = T> + Send>>;
|
|
|
|
|
|
|
|
struct SpircTask {
|
2023-06-13 14:56:40 +00:00
|
|
|
player: Arc<Player>,
|
|
|
|
mixer: Arc<dyn Mixer>,
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
sequence: SeqGenerator<u32>,
|
2015-07-08 19:50:44 +00:00
|
|
|
|
|
|
|
ident: String,
|
2017-01-20 14:44:13 +00:00
|
|
|
device: DeviceState,
|
2017-01-29 14:11:20 +00:00
|
|
|
state: State,
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: Option<u64>,
|
|
|
|
play_status: SpircPlayStatus,
|
2016-02-13 01:09:15 +00:00
|
|
|
|
2022-01-16 00:14:00 +00:00
|
|
|
remote_update: BoxedStream<Result<(String, Frame), Error>>,
|
2021-12-26 20:18:42 +00:00
|
|
|
connection_id_update: BoxedStream<Result<String, Error>>,
|
|
|
|
user_attributes_update: BoxedStream<Result<UserAttributesUpdate, Error>>,
|
|
|
|
user_attributes_mutation: BoxedStream<Result<UserAttributesMutation, Error>>,
|
2021-02-20 19:59:57 +00:00
|
|
|
sender: MercurySender,
|
2021-02-21 18:38:40 +00:00
|
|
|
commands: Option<mpsc::UnboundedReceiver<SpircCommand>>,
|
|
|
|
player_events: Option<PlayerEventChannel>,
|
2017-01-20 12:56:42 +00:00
|
|
|
|
|
|
|
shutdown: bool,
|
2017-02-22 04:17:04 +00:00
|
|
|
session: Session,
|
2022-09-30 19:36:20 +00:00
|
|
|
resolve_context: Option<String>,
|
|
|
|
autoplay_context: bool,
|
2022-10-01 21:01:17 +00:00
|
|
|
context: Option<PageContext>,
|
2022-01-16 20:29:59 +00:00
|
|
|
|
|
|
|
spirc_id: usize,
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
|
|
|
|
2022-01-16 20:29:59 +00:00
|
|
|
static SPIRC_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
|
2022-08-23 20:23:37 +00:00
|
|
|
#[derive(Debug)]
|
2017-01-20 12:56:42 +00:00
|
|
|
pub enum SpircCommand {
|
2017-02-23 11:05:32 +00:00
|
|
|
Play,
|
|
|
|
PlayPause,
|
|
|
|
Pause,
|
|
|
|
Prev,
|
|
|
|
Next,
|
|
|
|
VolumeUp,
|
|
|
|
VolumeDown,
|
2018-02-11 17:52:53 +00:00
|
|
|
Shutdown,
|
2022-08-23 20:23:37 +00:00
|
|
|
Shuffle(bool),
|
|
|
|
Repeat(bool),
|
|
|
|
Disconnect,
|
|
|
|
SetPosition(u32),
|
|
|
|
SetVolume(u16),
|
2022-12-03 11:25:27 +00:00
|
|
|
Activate,
|
|
|
|
Load(SpircLoadCommand),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct SpircLoadCommand {
|
|
|
|
pub context_uri: String,
|
|
|
|
/// Whether the given tracks should immediately start playing, or just be initially loaded.
|
|
|
|
pub start_playing: bool,
|
|
|
|
pub shuffle: bool,
|
|
|
|
pub repeat: bool,
|
|
|
|
pub playing_track_index: u32,
|
|
|
|
pub tracks: Vec<TrackRef>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<SpircLoadCommand> for State {
|
|
|
|
fn from(command: SpircLoadCommand) -> Self {
|
|
|
|
let mut state = State::new();
|
|
|
|
state.set_context_uri(command.context_uri);
|
|
|
|
state.set_status(if command.start_playing {
|
|
|
|
PlayStatus::kPlayStatusPlay
|
|
|
|
} else {
|
|
|
|
PlayStatus::kPlayStatusStop
|
|
|
|
});
|
|
|
|
state.set_shuffle(command.shuffle);
|
|
|
|
state.set_repeat(command.repeat);
|
|
|
|
state.set_playing_track_index(command.playing_track_index);
|
2023-01-17 20:46:14 +00:00
|
|
|
state.track = command.tracks;
|
2022-12-03 11:25:27 +00:00
|
|
|
state
|
|
|
|
}
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
|
|
|
|
2019-03-16 15:18:38 +00:00
|
|
|
const CONTEXT_TRACKS_HISTORY: usize = 10;
|
|
|
|
const CONTEXT_FETCH_THRESHOLD: u32 = 5;
|
|
|
|
|
2021-05-24 13:53:32 +00:00
|
|
|
const VOLUME_STEPS: i64 = 64;
|
2021-05-31 20:32:39 +00:00
|
|
|
const VOLUME_STEP_SIZE: u16 = 1024; // (u16::MAX + 1) / VOLUME_STEPS
|
2021-05-24 13:53:32 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
pub struct Spirc {
|
|
|
|
commands: mpsc::UnboundedSender<SpircCommand>,
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn initial_state() -> State {
|
2018-02-17 09:15:09 +00:00
|
|
|
let mut frame = protocol::spirc::State::new();
|
|
|
|
frame.set_repeat(false);
|
|
|
|
frame.set_shuffle(false);
|
|
|
|
frame.set_status(PlayStatus::kPlayStatusStop);
|
|
|
|
frame.set_position_ms(0);
|
|
|
|
frame.set_position_measured_at(0);
|
|
|
|
frame
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2018-02-17 09:15:09 +00:00
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
fn int_capability(typ: protocol::spirc::CapabilityType, val: i64) -> protocol::spirc::Capability {
|
|
|
|
let mut cap = protocol::spirc::Capability::new();
|
|
|
|
cap.set_typ(typ);
|
|
|
|
cap.intValue.push(val);
|
|
|
|
cap
|
|
|
|
}
|
|
|
|
|
2018-05-17 01:15:17 +00:00
|
|
|
fn initial_device_state(config: ConnectConfig) -> DeviceState {
|
2023-01-17 20:46:14 +00:00
|
|
|
let mut msg = DeviceState::new();
|
|
|
|
msg.set_sw_version(version::SEMVER.to_string());
|
|
|
|
msg.set_is_active(false);
|
|
|
|
msg.set_can_play(true);
|
|
|
|
msg.set_volume(0);
|
|
|
|
msg.set_name(config.name);
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kCanBePlayer,
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kDeviceType,
|
|
|
|
config.device_type as i64,
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kGaiaEqConnectId,
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
// TODO: implement logout
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kSupportsLogout,
|
|
|
|
0,
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kIsObservable,
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kVolumeSteps,
|
|
|
|
if config.has_volume_ctrl {
|
|
|
|
VOLUME_STEPS
|
|
|
|
} else {
|
|
|
|
0
|
|
|
|
},
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kSupportsPlaylistV2,
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kSupportsExternalEpisodes,
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
// TODO: how would such a rename command be triggered? Handle it.
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kSupportsRename,
|
|
|
|
1,
|
|
|
|
));
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kCommandAcks,
|
|
|
|
0,
|
|
|
|
));
|
|
|
|
// TODO: does this mean local files or the local network?
|
|
|
|
// LAN may be an interesting privacy toggle.
|
|
|
|
msg.capabilities.push(int_capability(
|
|
|
|
protocol::spirc::CapabilityType::kRestrictToLocal,
|
|
|
|
0,
|
|
|
|
));
|
|
|
|
// TODO: what does this hide, or who do we hide from?
|
|
|
|
// May be an interesting privacy toggle.
|
|
|
|
msg.capabilities
|
|
|
|
.push(int_capability(protocol::spirc::CapabilityType::kHidden, 0));
|
|
|
|
let mut supported_types = protocol::spirc::Capability::new();
|
|
|
|
supported_types.set_typ(protocol::spirc::CapabilityType::kSupportedTypes);
|
|
|
|
supported_types
|
|
|
|
.stringValue
|
|
|
|
.push("audio/episode".to_string());
|
|
|
|
supported_types
|
|
|
|
.stringValue
|
|
|
|
.push("audio/episode+track".to_string());
|
|
|
|
supported_types.stringValue.push("audio/track".to_string());
|
|
|
|
// other known types:
|
|
|
|
// - "audio/ad"
|
|
|
|
// - "audio/interruption"
|
|
|
|
// - "audio/local"
|
|
|
|
// - "video/ad"
|
|
|
|
// - "video/episode"
|
|
|
|
msg.capabilities.push(supported_types);
|
|
|
|
msg
|
2017-01-20 14:44:13 +00:00
|
|
|
}
|
|
|
|
|
2021-03-18 16:51:50 +00:00
|
|
|
fn url_encode(bytes: impl AsRef<[u8]>) -> String {
|
|
|
|
form_urlencoded::byte_serialize(bytes.as_ref()).collect()
|
|
|
|
}
|
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
impl Spirc {
|
2022-01-16 00:14:00 +00:00
|
|
|
pub async fn new(
|
2018-02-11 17:52:53 +00:00
|
|
|
config: ConnectConfig,
|
|
|
|
session: Session,
|
2022-01-16 00:14:00 +00:00
|
|
|
credentials: Credentials,
|
2023-06-13 14:56:40 +00:00
|
|
|
player: Arc<Player>,
|
|
|
|
mixer: Arc<dyn Mixer>,
|
2021-12-26 20:18:42 +00:00
|
|
|
) -> Result<(Spirc, impl Future<Output = ()>), Error> {
|
2022-01-16 20:29:59 +00:00
|
|
|
let spirc_id = SPIRC_COUNTER.fetch_add(1, Ordering::AcqRel);
|
|
|
|
debug!("new Spirc[{}]", spirc_id);
|
2017-02-22 04:17:04 +00:00
|
|
|
|
2016-03-17 03:06:56 +00:00
|
|
|
let ident = session.device_id().to_owned();
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
let remote_update = Box::pin(
|
2021-02-19 23:17:18 +00:00
|
|
|
session
|
|
|
|
.mercury()
|
2022-01-16 00:14:00 +00:00
|
|
|
.listen_for("hm://remote/user/")
|
2021-02-21 18:38:40 +00:00
|
|
|
.map(UnboundedReceiverStream::new)
|
2021-02-19 23:17:18 +00:00
|
|
|
.flatten_stream()
|
2022-01-16 00:14:00 +00:00
|
|
|
.map(|response| -> Result<(String, Frame), Error> {
|
|
|
|
let uri_split: Vec<&str> = response.uri.split('/').collect();
|
2022-01-22 20:13:11 +00:00
|
|
|
let username = match uri_split.get(4) {
|
2022-01-16 00:14:00 +00:00
|
|
|
Some(s) => s.to_string(),
|
|
|
|
None => String::new(),
|
|
|
|
};
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
let data = response.payload.first().ok_or(SpircError::NoData)?;
|
2022-01-16 00:14:00 +00:00
|
|
|
Ok((username, Frame::parse_from_bytes(data)?))
|
2021-02-19 23:17:18 +00:00
|
|
|
}),
|
|
|
|
);
|
2017-01-20 12:56:42 +00:00
|
|
|
|
2021-12-11 22:06:58 +00:00
|
|
|
let connection_id_update = Box::pin(
|
|
|
|
session
|
|
|
|
.mercury()
|
|
|
|
.listen_for("hm://pusher/v1/connections/")
|
|
|
|
.map(UnboundedReceiverStream::new)
|
|
|
|
.flatten_stream()
|
2021-12-26 20:18:42 +00:00
|
|
|
.map(|response| -> Result<String, Error> {
|
|
|
|
let connection_id = response
|
2021-12-11 22:06:58 +00:00
|
|
|
.uri
|
|
|
|
.strip_prefix("hm://pusher/v1/connections/")
|
2021-12-26 20:18:42 +00:00
|
|
|
.ok_or_else(|| SpircError::InvalidUri(response.uri.clone()))?;
|
|
|
|
Ok(connection_id.to_owned())
|
2021-12-11 22:06:58 +00:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2021-12-10 23:03:35 +00:00
|
|
|
let user_attributes_update = Box::pin(
|
|
|
|
session
|
|
|
|
.mercury()
|
|
|
|
.listen_for("spotify:user:attributes:update")
|
|
|
|
.map(UnboundedReceiverStream::new)
|
|
|
|
.flatten_stream()
|
2021-12-26 20:18:42 +00:00
|
|
|
.map(|response| -> Result<UserAttributesUpdate, Error> {
|
|
|
|
let data = response.payload.first().ok_or(SpircError::NoData)?;
|
|
|
|
Ok(UserAttributesUpdate::parse_from_bytes(data)?)
|
2021-12-10 23:03:35 +00:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
let user_attributes_mutation = Box::pin(
|
|
|
|
session
|
|
|
|
.mercury()
|
|
|
|
.listen_for("spotify:user:attributes:mutated")
|
|
|
|
.map(UnboundedReceiverStream::new)
|
|
|
|
.flatten_stream()
|
2021-12-26 20:18:42 +00:00
|
|
|
.map(|response| -> Result<UserAttributesMutation, Error> {
|
|
|
|
let data = response.payload.first().ok_or(SpircError::NoData)?;
|
|
|
|
Ok(UserAttributesMutation::parse_from_bytes(data)?)
|
2021-12-10 23:03:35 +00:00
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
2024-10-19 18:27:26 +00:00
|
|
|
// pre-acquire client_token, preventing multiple request while running
|
|
|
|
let _ = session.spclient().client_token().await?;
|
|
|
|
|
2022-01-16 00:14:00 +00:00
|
|
|
// Connect *after* all message listeners are registered
|
2022-07-27 21:31:11 +00:00
|
|
|
session.connect(credentials, true).await?;
|
2022-01-16 00:14:00 +00:00
|
|
|
|
|
|
|
let canonical_username = &session.username();
|
|
|
|
debug!("canonical_username: {}", canonical_username);
|
|
|
|
let sender_uri = format!("hm://remote/user/{}/", url_encode(canonical_username));
|
|
|
|
|
|
|
|
let sender = session.mercury().sender(sender_uri);
|
2017-01-20 12:56:42 +00:00
|
|
|
|
2021-02-21 18:38:40 +00:00
|
|
|
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
2017-01-20 12:56:42 +00:00
|
|
|
|
2021-05-24 13:53:32 +00:00
|
|
|
let initial_volume = config.initial_volume;
|
2020-07-25 07:38:08 +00:00
|
|
|
|
2018-05-17 01:15:17 +00:00
|
|
|
let device = initial_device_state(config);
|
2017-01-20 14:44:13 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
let player_events = player.get_player_event_channel();
|
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
let mut task = SpircTask {
|
2021-03-10 21:39:01 +00:00
|
|
|
player,
|
|
|
|
mixer,
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
sequence: SeqGenerator::new(1),
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2021-03-10 21:39:01 +00:00
|
|
|
ident,
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2021-03-10 21:39:01 +00:00
|
|
|
device,
|
2017-01-29 14:11:20 +00:00
|
|
|
state: initial_state(),
|
2020-01-31 21:41:11 +00:00
|
|
|
play_request_id: None,
|
|
|
|
play_status: SpircPlayStatus::Stopped,
|
2016-02-13 01:09:15 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
remote_update,
|
2021-12-11 22:06:58 +00:00
|
|
|
connection_id_update,
|
2021-12-10 23:03:35 +00:00
|
|
|
user_attributes_update,
|
|
|
|
user_attributes_mutation,
|
2021-03-10 21:39:01 +00:00
|
|
|
sender,
|
2021-02-21 18:38:40 +00:00
|
|
|
commands: Some(cmd_rx),
|
|
|
|
player_events: Some(player_events),
|
2016-01-20 15:47:05 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
shutdown: false,
|
2021-03-10 21:39:01 +00:00
|
|
|
session,
|
2018-10-12 17:15:26 +00:00
|
|
|
|
2022-09-30 19:36:20 +00:00
|
|
|
resolve_context: None,
|
|
|
|
autoplay_context: false,
|
2018-10-12 17:15:26 +00:00
|
|
|
context: None,
|
2022-01-16 20:29:59 +00:00
|
|
|
|
|
|
|
spirc_id,
|
2016-01-20 15:47:05 +00:00
|
|
|
};
|
|
|
|
|
2021-05-24 13:53:32 +00:00
|
|
|
if let Some(volume) = initial_volume {
|
|
|
|
task.set_volume(volume);
|
|
|
|
} else {
|
|
|
|
let current_volume = task.mixer.volume();
|
|
|
|
task.set_volume(current_volume);
|
|
|
|
}
|
2018-05-17 01:15:17 +00:00
|
|
|
|
2018-02-11 17:52:53 +00:00
|
|
|
let spirc = Spirc { commands: cmd_tx };
|
2016-01-20 15:47:05 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
task.hello()?;
|
2016-02-16 21:26:51 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
Ok((spirc, task.run()))
|
2016-02-16 21:26:51 +00:00
|
|
|
}
|
2016-02-16 21:52:55 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn play(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Play)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn play_pause(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::PlayPause)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn pause(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Pause)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn prev(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Prev)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn next(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Next)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn volume_up(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::VolumeUp)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn volume_down(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::VolumeDown)?)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
pub fn shutdown(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Shutdown)?)
|
2016-02-16 21:52:55 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
pub fn shuffle(&self, shuffle: bool) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Shuffle(shuffle))?)
|
|
|
|
}
|
|
|
|
pub fn repeat(&self, repeat: bool) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Repeat(repeat))?)
|
|
|
|
}
|
|
|
|
pub fn set_volume(&self, volume: u16) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::SetVolume(volume))?)
|
|
|
|
}
|
|
|
|
pub fn set_position_ms(&self, position_ms: u32) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::SetPosition(position_ms))?)
|
|
|
|
}
|
|
|
|
pub fn disconnect(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Disconnect)?)
|
2021-03-09 21:37:11 +00:00
|
|
|
}
|
2022-12-03 11:25:27 +00:00
|
|
|
pub fn activate(&self) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Activate)?)
|
|
|
|
}
|
|
|
|
pub fn load(&self, command: SpircLoadCommand) -> Result<(), Error> {
|
|
|
|
Ok(self.commands.send(SpircCommand::Load(command))?)
|
|
|
|
}
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
2016-02-16 21:52:55 +00:00
|
|
|
|
2021-02-19 23:17:18 +00:00
|
|
|
impl SpircTask {
|
|
|
|
async fn run(mut self) {
|
|
|
|
while !self.session.is_invalid() && !self.shutdown {
|
2021-02-21 18:38:40 +00:00
|
|
|
let commands = self.commands.as_mut();
|
|
|
|
let player_events = self.player_events.as_mut();
|
2021-02-19 23:17:18 +00:00
|
|
|
tokio::select! {
|
2021-12-26 20:18:42 +00:00
|
|
|
remote_update = self.remote_update.next() => match remote_update {
|
|
|
|
Some(result) => match result {
|
2022-01-16 00:14:00 +00:00
|
|
|
Ok((username, frame)) => {
|
|
|
|
if username != self.session.username() {
|
2022-07-30 20:39:05 +00:00
|
|
|
warn!("could not dispatch remote update: frame was intended for {}", username);
|
2022-01-16 00:14:00 +00:00
|
|
|
} else if let Err(e) = self.handle_remote_update(frame) {
|
|
|
|
error!("could not dispatch remote update: {}", e);
|
|
|
|
}
|
|
|
|
},
|
2021-12-26 20:18:42 +00:00
|
|
|
Err(e) => error!("could not parse remote update: {}", e),
|
|
|
|
}
|
2021-02-19 23:17:18 +00:00
|
|
|
None => {
|
2022-01-16 00:14:00 +00:00
|
|
|
error!("remote update selected, but none received");
|
2021-02-19 23:17:18 +00:00
|
|
|
break;
|
2018-10-12 17:15:26 +00:00
|
|
|
}
|
2021-02-19 23:17:18 +00:00
|
|
|
},
|
2021-12-10 23:03:35 +00:00
|
|
|
user_attributes_update = self.user_attributes_update.next() => match user_attributes_update {
|
2021-12-26 20:18:42 +00:00
|
|
|
Some(result) => match result {
|
|
|
|
Ok(attributes) => self.handle_user_attributes_update(attributes),
|
|
|
|
Err(e) => error!("could not parse user attributes update: {}", e),
|
|
|
|
}
|
2021-12-10 23:03:35 +00:00
|
|
|
None => {
|
|
|
|
error!("user attributes update selected, but none received");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
user_attributes_mutation = self.user_attributes_mutation.next() => match user_attributes_mutation {
|
2021-12-26 20:18:42 +00:00
|
|
|
Some(result) => match result {
|
|
|
|
Ok(attributes) => self.handle_user_attributes_mutation(attributes),
|
|
|
|
Err(e) => error!("could not parse user attributes mutation: {}", e),
|
|
|
|
}
|
2021-12-10 23:03:35 +00:00
|
|
|
None => {
|
|
|
|
error!("user attributes mutation selected, but none received");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
2021-12-11 22:06:58 +00:00
|
|
|
connection_id_update = self.connection_id_update.next() => match connection_id_update {
|
2021-12-26 20:18:42 +00:00
|
|
|
Some(result) => match result {
|
2024-10-19 18:27:26 +00:00
|
|
|
Ok(connection_id) => {
|
|
|
|
self.handle_connection_id_update(connection_id);
|
|
|
|
|
|
|
|
// pre-acquire access_token, preventing multiple request while running
|
|
|
|
// pre-acquiring for the access_token will only last for one hour
|
|
|
|
//
|
|
|
|
// we need to fire the request after connecting, but can't do it right
|
|
|
|
// after, because by that we would miss certain packages, like this one
|
|
|
|
match self.session.login5().auth_token().await {
|
|
|
|
Ok(_) => debug!("successfully pre-acquire access_token and client_token"),
|
|
|
|
Err(why) => {
|
|
|
|
error!("{why}");
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2021-12-26 20:18:42 +00:00
|
|
|
Err(e) => error!("could not parse connection ID update: {}", e),
|
|
|
|
}
|
2021-12-11 22:06:58 +00:00
|
|
|
None => {
|
|
|
|
error!("connection ID update selected, but none received");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
},
|
2021-12-26 20:18:42 +00:00
|
|
|
cmd = async { commands?.recv().await }, if commands.is_some() => if let Some(cmd) = cmd {
|
|
|
|
if let Err(e) = self.handle_command(cmd) {
|
2022-08-25 18:49:40 +00:00
|
|
|
debug!("could not dispatch command: {}", e);
|
2021-12-26 20:18:42 +00:00
|
|
|
}
|
2021-02-19 23:17:18 +00:00
|
|
|
},
|
2021-12-26 20:18:42 +00:00
|
|
|
event = async { player_events?.recv().await }, if player_events.is_some() => if let Some(event) = event {
|
|
|
|
if let Err(e) = self.handle_player_event(event) {
|
|
|
|
error!("could not dispatch player event: {}", e);
|
|
|
|
}
|
2021-02-19 23:17:18 +00:00
|
|
|
},
|
2021-02-20 19:59:57 +00:00
|
|
|
result = self.sender.flush(), if !self.sender.is_flushed() => if result.is_err() {
|
|
|
|
error!("Cannot flush spirc event sender.");
|
|
|
|
break;
|
2021-02-19 23:17:18 +00:00
|
|
|
},
|
2022-09-30 19:36:20 +00:00
|
|
|
context_uri = async { self.resolve_context.take() }, if self.resolve_context.is_some() => {
|
2022-10-01 21:01:17 +00:00
|
|
|
let context_uri = context_uri.unwrap(); // guaranteed above
|
|
|
|
if context_uri.contains("spotify:show:") || context_uri.contains("spotify:episode:") {
|
|
|
|
continue; // not supported by apollo stations
|
|
|
|
}
|
2022-09-30 19:36:20 +00:00
|
|
|
|
2022-10-01 21:01:17 +00:00
|
|
|
let context = if context_uri.starts_with("hm://") {
|
2022-09-30 19:36:20 +00:00
|
|
|
self.session.spclient().get_next_page(&context_uri).await
|
|
|
|
} else {
|
2022-10-01 21:01:17 +00:00
|
|
|
// only send previous tracks that were before the current playback position
|
2023-01-17 20:46:14 +00:00
|
|
|
let current_position = self.state.playing_track_index() as usize;
|
|
|
|
let previous_tracks = self.state.track[..current_position].iter().filter_map(|t| SpotifyId::try_from(t).ok()).collect();
|
2022-10-01 21:01:17 +00:00
|
|
|
|
|
|
|
let scope = if self.autoplay_context {
|
|
|
|
"stations" // this returns a `StationContext` but we deserialize it into a `PageContext`
|
|
|
|
} else {
|
|
|
|
"tracks" // this returns a `PageContext`
|
|
|
|
};
|
|
|
|
|
|
|
|
self.session.spclient().get_apollo_station(scope, &context_uri, None, previous_tracks, self.autoplay_context).await
|
2022-09-30 19:36:20 +00:00
|
|
|
};
|
|
|
|
|
2021-02-19 23:17:18 +00:00
|
|
|
match context {
|
|
|
|
Ok(value) => {
|
2022-10-01 21:01:17 +00:00
|
|
|
self.context = match serde_json::from_slice::<PageContext>(&value) {
|
2021-02-19 23:17:18 +00:00
|
|
|
Ok(context) => {
|
|
|
|
info!(
|
|
|
|
"Resolved {:?} tracks from <{:?}>",
|
|
|
|
context.tracks.len(),
|
2023-01-17 20:46:14 +00:00
|
|
|
self.state.context_uri(),
|
2021-02-19 23:17:18 +00:00
|
|
|
);
|
|
|
|
Some(context)
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to parse JSONContext {:?}", e);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
};
|
|
|
|
},
|
|
|
|
Err(err) => {
|
|
|
|
error!("ContextError: {:?}", err)
|
|
|
|
}
|
2019-03-24 15:42:00 +00:00
|
|
|
}
|
2021-02-19 23:17:18 +00:00
|
|
|
},
|
|
|
|
else => break
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
2021-02-19 23:17:18 +00:00
|
|
|
}
|
2016-02-17 19:35:52 +00:00
|
|
|
|
2021-02-20 19:59:57 +00:00
|
|
|
if self.sender.flush().await.is_err() {
|
2022-01-16 00:14:00 +00:00
|
|
|
warn!("Cannot flush spirc event sender when done.");
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
2016-02-17 19:35:52 +00:00
|
|
|
}
|
2016-01-20 15:47:05 +00:00
|
|
|
|
2019-03-24 14:15:14 +00:00
|
|
|
fn now_ms(&mut self) -> i64 {
|
|
|
|
let dur = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
|
|
|
Ok(dur) => dur,
|
|
|
|
Err(err) => err.duration(),
|
|
|
|
};
|
2021-03-10 21:32:24 +00:00
|
|
|
|
|
|
|
dur.as_millis() as i64 + 1000 * self.session.time_delta()
|
2019-03-24 14:15:14 +00:00
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
fn update_state_position(&mut self, position_ms: u32) {
|
|
|
|
let now = self.now_ms();
|
|
|
|
self.state.set_position_measured_at(now as u64);
|
|
|
|
self.state.set_position_ms(position_ms);
|
|
|
|
}
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn handle_command(&mut self, cmd: SpircCommand) -> Result<(), Error> {
|
2022-08-23 20:23:37 +00:00
|
|
|
if matches!(cmd, SpircCommand::Shutdown) {
|
|
|
|
trace!("Received SpircCommand::Shutdown");
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeGoodbye).send()?;
|
|
|
|
self.handle_disconnect();
|
|
|
|
self.shutdown = true;
|
|
|
|
if let Some(rx) = self.commands.as_mut() {
|
|
|
|
rx.close()
|
|
|
|
}
|
|
|
|
Ok(())
|
2023-01-17 20:46:14 +00:00
|
|
|
} else if self.device.is_active() {
|
2022-08-23 20:23:37 +00:00
|
|
|
trace!("Received SpircCommand::{:?}", cmd);
|
|
|
|
match cmd {
|
|
|
|
SpircCommand::Play => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_play();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::PlayPause => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_play_pause();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::Pause => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_pause();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::Prev => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_prev();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::Next => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_next();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::VolumeUp => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_volume_up();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::VolumeDown => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_volume_down();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::Disconnect => {
|
|
|
|
self.handle_disconnect();
|
|
|
|
self.notify(None)
|
2021-03-10 21:32:24 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircCommand::Shuffle(shuffle) => {
|
|
|
|
self.state.set_shuffle(shuffle);
|
|
|
|
self.notify(None)
|
|
|
|
}
|
|
|
|
SpircCommand::Repeat(repeat) => {
|
|
|
|
self.state.set_repeat(repeat);
|
|
|
|
self.notify(None)
|
|
|
|
}
|
|
|
|
SpircCommand::SetPosition(position) => {
|
|
|
|
self.handle_seek(position);
|
|
|
|
self.notify(None)
|
|
|
|
}
|
|
|
|
SpircCommand::SetVolume(volume) => {
|
|
|
|
self.set_volume(volume);
|
|
|
|
self.notify(None)
|
|
|
|
}
|
2022-12-03 11:25:27 +00:00
|
|
|
SpircCommand::Load(command) => {
|
|
|
|
self.handle_load(&command.into())?;
|
|
|
|
self.notify(None)
|
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
_ => Ok(()),
|
2021-03-09 21:37:11 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
} else {
|
2022-12-03 11:25:27 +00:00
|
|
|
match cmd {
|
|
|
|
SpircCommand::Activate => {
|
|
|
|
trace!("Received SpircCommand::{:?}", cmd);
|
|
|
|
self.handle_activate();
|
|
|
|
self.notify(None)
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
warn!("SpircCommand::{:?} will be ignored while Not Active", cmd);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn handle_player_event(&mut self, event: PlayerEvent) -> Result<(), Error> {
|
2023-06-13 14:30:56 +00:00
|
|
|
// update play_request_id
|
|
|
|
if let PlayerEvent::PlayRequestIdChanged { play_request_id } = event {
|
|
|
|
self.play_request_id = Some(play_request_id);
|
|
|
|
return Ok(());
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
// we only process events if the play_request_id matches. If it doesn't, it is
|
|
|
|
// an event that belongs to a previous track and only arrives now due to a race
|
|
|
|
// condition. In this case we have updated the state already and don't want to
|
|
|
|
// mess with it.
|
|
|
|
if let Some(play_request_id) = event.get_play_request_id() {
|
|
|
|
if Some(play_request_id) == self.play_request_id {
|
|
|
|
match event {
|
|
|
|
PlayerEvent::EndOfTrack { .. } => self.handle_end_of_track(),
|
2021-12-28 22:46:37 +00:00
|
|
|
PlayerEvent::Loading { .. } => {
|
2022-08-23 20:23:37 +00:00
|
|
|
match self.play_status {
|
|
|
|
SpircPlayStatus::LoadingPlay { position_ms } => {
|
|
|
|
self.update_state_position(position_ms);
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
|
|
|
trace!("==> kPlayStatusPlay");
|
|
|
|
}
|
|
|
|
SpircPlayStatus::LoadingPause { position_ms } => {
|
|
|
|
self.update_state_position(position_ms);
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
|
|
|
trace!("==> kPlayStatusPause");
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusLoading);
|
|
|
|
self.update_state_position(0);
|
|
|
|
trace!("==> kPlayStatusLoading");
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2021-12-28 22:46:37 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
PlayerEvent::Playing { position_ms, .. }
|
|
|
|
| PlayerEvent::PositionCorrection { position_ms, .. }
|
|
|
|
| PlayerEvent::Seeked { position_ms, .. } => {
|
2021-12-26 20:18:42 +00:00
|
|
|
trace!("==> kPlayStatusPlay");
|
2020-02-02 00:07:05 +00:00
|
|
|
let new_nominal_start_time = self.now_ms() - position_ms as i64;
|
2020-01-31 21:41:11 +00:00
|
|
|
match self.play_status {
|
2020-02-02 22:15:15 +00:00
|
|
|
SpircPlayStatus::Playing {
|
|
|
|
ref mut nominal_start_time,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
if (*nominal_start_time - new_nominal_start_time).abs() > 100 {
|
|
|
|
*nominal_start_time = new_nominal_start_time;
|
2020-01-31 21:41:11 +00:00
|
|
|
self.update_state_position(position_ms);
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2021-12-26 20:18:42 +00:00
|
|
|
} else {
|
|
|
|
Ok(())
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircPlayStatus::LoadingPlay { .. }
|
|
|
|
| SpircPlayStatus::LoadingPause { .. } => {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
|
|
|
self.update_state_position(position_ms);
|
|
|
|
self.play_status = SpircPlayStatus::Playing {
|
|
|
|
nominal_start_time: new_nominal_start_time,
|
2020-02-02 22:15:15 +00:00
|
|
|
preloading_of_next_track_triggered: false,
|
2020-01-31 21:41:11 +00:00
|
|
|
};
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
_ => Ok(()),
|
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
PlayerEvent::Paused {
|
|
|
|
position_ms: new_position_ms,
|
|
|
|
..
|
|
|
|
} => {
|
2021-12-26 20:18:42 +00:00
|
|
|
trace!("==> kPlayStatusPause");
|
2020-01-31 21:41:11 +00:00
|
|
|
match self.play_status {
|
2022-08-23 20:23:37 +00:00
|
|
|
SpircPlayStatus::Paused { .. } | SpircPlayStatus::Playing { .. } => {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
|
|
|
self.update_state_position(new_position_ms);
|
|
|
|
self.play_status = SpircPlayStatus::Paused {
|
|
|
|
position_ms: new_position_ms,
|
|
|
|
preloading_of_next_track_triggered: false,
|
|
|
|
};
|
|
|
|
self.notify(None)
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
SpircPlayStatus::LoadingPlay { .. }
|
|
|
|
| SpircPlayStatus::LoadingPause { .. } => {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
|
|
|
self.update_state_position(new_position_ms);
|
|
|
|
self.play_status = SpircPlayStatus::Paused {
|
|
|
|
position_ms: new_position_ms,
|
2020-02-02 22:15:15 +00:00
|
|
|
preloading_of_next_track_triggered: false,
|
2020-01-31 21:41:11 +00:00
|
|
|
};
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
_ => Ok(()),
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
PlayerEvent::Stopped { .. } => {
|
|
|
|
trace!("==> kPlayStatusStop");
|
|
|
|
match self.play_status {
|
|
|
|
SpircPlayStatus::Stopped => Ok(()),
|
|
|
|
_ => {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusStop);
|
|
|
|
self.play_status = SpircPlayStatus::Stopped;
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2021-12-28 22:46:37 +00:00
|
|
|
}
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
PlayerEvent::TimeToPreloadNextTrack { .. } => {
|
|
|
|
self.handle_preload_next_track();
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
PlayerEvent::Unavailable { track_id, .. } => {
|
|
|
|
self.handle_unavailable(track_id);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
_ => Ok(()),
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
} else {
|
|
|
|
Ok(())
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
} else {
|
|
|
|
Ok(())
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-11 22:06:58 +00:00
|
|
|
fn handle_connection_id_update(&mut self, connection_id: String) {
|
|
|
|
trace!("Received connection ID update: {:?}", connection_id);
|
2022-01-12 21:09:57 +00:00
|
|
|
self.session.set_connection_id(&connection_id);
|
2021-12-11 22:06:58 +00:00
|
|
|
}
|
|
|
|
|
2021-12-10 23:03:35 +00:00
|
|
|
fn handle_user_attributes_update(&mut self, update: UserAttributesUpdate) {
|
2021-12-11 19:45:08 +00:00
|
|
|
trace!("Received attributes update: {:#?}", update);
|
2021-12-10 23:03:35 +00:00
|
|
|
let attributes: UserAttributes = update
|
2023-01-17 20:46:14 +00:00
|
|
|
.pairs
|
2021-12-10 23:03:35 +00:00
|
|
|
.iter()
|
2023-01-17 20:46:14 +00:00
|
|
|
.map(|pair| (pair.key().to_owned(), pair.value().to_owned()))
|
2021-12-10 23:03:35 +00:00
|
|
|
.collect();
|
2021-12-26 20:18:42 +00:00
|
|
|
self.session.set_user_attributes(attributes)
|
2021-12-10 23:03:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_user_attributes_mutation(&mut self, mutation: UserAttributesMutation) {
|
2023-01-17 20:46:14 +00:00
|
|
|
for attribute in mutation.fields.iter() {
|
|
|
|
let key = &attribute.name;
|
2022-09-28 20:59:03 +00:00
|
|
|
|
|
|
|
if key == "autoplay" && self.session.config().autoplay.is_some() {
|
|
|
|
trace!("Autoplay override active. Ignoring mutation.");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2021-12-11 19:22:44 +00:00
|
|
|
if let Some(old_value) = self.session.user_data().attributes.get(key) {
|
2021-12-10 23:03:35 +00:00
|
|
|
let new_value = match old_value.as_ref() {
|
|
|
|
"0" => "1",
|
|
|
|
"1" => "0",
|
2021-12-11 19:22:44 +00:00
|
|
|
_ => old_value,
|
2021-12-10 23:03:35 +00:00
|
|
|
};
|
|
|
|
self.session.set_user_attribute(key, new_value);
|
2021-12-30 22:50:28 +00:00
|
|
|
|
2021-12-10 23:03:35 +00:00
|
|
|
trace!(
|
|
|
|
"Received attribute mutation, {} was {} is now {}",
|
|
|
|
key,
|
|
|
|
old_value,
|
|
|
|
new_value
|
|
|
|
);
|
2021-12-30 22:50:28 +00:00
|
|
|
|
|
|
|
if key == "filter-explicit-content" && new_value == "1" {
|
2022-08-23 20:23:37 +00:00
|
|
|
self.player
|
|
|
|
.emit_filter_explicit_content_changed_event(matches!(new_value, "1"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if key == "autoplay" && old_value != new_value {
|
|
|
|
self.player
|
|
|
|
.emit_auto_play_changed_event(matches!(new_value, "1"));
|
2021-12-30 22:50:28 +00:00
|
|
|
}
|
2021-12-10 23:03:35 +00:00
|
|
|
} else {
|
|
|
|
trace!(
|
|
|
|
"Received attribute mutation for {} but key was not found!",
|
|
|
|
key
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn handle_remote_update(&mut self, update: Frame) -> Result<(), Error> {
|
2022-01-16 00:14:00 +00:00
|
|
|
trace!("Received update frame: {:#?}", update);
|
2018-02-11 17:52:53 +00:00
|
|
|
|
2022-01-12 21:09:57 +00:00
|
|
|
// First see if this update was intended for us.
|
2021-12-26 20:18:42 +00:00
|
|
|
let device_id = &self.ident;
|
2023-01-17 20:46:14 +00:00
|
|
|
let ident = update.ident();
|
2021-12-26 20:18:42 +00:00
|
|
|
if ident == device_id
|
2023-01-17 20:46:14 +00:00
|
|
|
|| (!update.recipient.is_empty() && !update.recipient.contains(device_id))
|
2018-02-11 17:52:53 +00:00
|
|
|
{
|
2021-12-26 20:18:42 +00:00
|
|
|
return Err(SpircError::Ident(ident.to_string()).into());
|
2016-01-20 15:47:05 +00:00
|
|
|
}
|
|
|
|
|
2022-08-23 20:23:37 +00:00
|
|
|
let old_client_id = self.session.client_id();
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
for entry in update.device_state.metadata.iter() {
|
|
|
|
match entry.type_() {
|
|
|
|
"client_id" => self.session.set_client_id(entry.metadata()),
|
|
|
|
"brand_display_name" => self.session.set_client_brand_name(entry.metadata()),
|
|
|
|
"model_display_name" => self.session.set_client_model_name(entry.metadata()),
|
2022-08-23 20:23:37 +00:00
|
|
|
_ => (),
|
2022-01-12 21:09:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
self.session.set_client_name(update.device_state.name());
|
2022-08-23 20:23:37 +00:00
|
|
|
|
|
|
|
let new_client_id = self.session.client_id();
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
if self.device.is_active() && new_client_id != old_client_id {
|
2022-08-23 20:23:37 +00:00
|
|
|
self.player.emit_session_client_changed_event(
|
|
|
|
new_client_id,
|
|
|
|
self.session.client_name(),
|
|
|
|
self.session.client_brand_name(),
|
|
|
|
self.session.client_model_name(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
match update.typ() {
|
2021-12-29 21:18:38 +00:00
|
|
|
MessageType::kMessageTypeHello => self.notify(Some(ident)),
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeLoad => {
|
2023-01-17 20:46:14 +00:00
|
|
|
self.handle_load(update.state.get_or_default())?;
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypePlay => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_play();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
MessageType::kMessageTypePlayPause => {
|
|
|
|
self.handle_play_pause();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypePause => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_pause();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeNext => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_next();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2016-01-14 03:27:34 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypePrev => {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.handle_prev();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
MessageType::kMessageTypeVolumeUp => {
|
|
|
|
self.handle_volume_up();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
MessageType::kMessageTypeVolumeDown => {
|
|
|
|
self.handle_volume_down();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2016-01-14 03:27:34 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-10-27 17:45:02 +00:00
|
|
|
MessageType::kMessageTypeRepeat => {
|
2023-01-17 20:46:14 +00:00
|
|
|
let repeat = update.state.repeat();
|
2022-08-23 20:23:37 +00:00
|
|
|
self.state.set_repeat(repeat);
|
|
|
|
|
|
|
|
self.player.emit_repeat_changed_event(repeat);
|
|
|
|
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-10-27 17:45:02 +00:00
|
|
|
}
|
|
|
|
|
2017-11-03 01:15:27 +00:00
|
|
|
MessageType::kMessageTypeShuffle => {
|
2023-01-17 20:46:14 +00:00
|
|
|
let shuffle = update.state.shuffle();
|
2022-08-23 20:23:37 +00:00
|
|
|
self.state.set_shuffle(shuffle);
|
2022-09-30 19:36:20 +00:00
|
|
|
if shuffle {
|
2023-01-17 20:46:14 +00:00
|
|
|
let current_index = self.state.playing_track_index();
|
|
|
|
let tracks = &mut self.state.track;
|
2022-02-14 11:15:19 +00:00
|
|
|
if !tracks.is_empty() {
|
2017-11-03 01:15:27 +00:00
|
|
|
tracks.swap(0, current_index as usize);
|
|
|
|
if let Some((_, rest)) = tracks.split_first_mut() {
|
2019-03-10 16:48:19 +00:00
|
|
|
let mut rng = rand::thread_rng();
|
|
|
|
rest.shuffle(&mut rng);
|
2017-11-03 01:15:27 +00:00
|
|
|
}
|
2022-02-14 11:15:19 +00:00
|
|
|
self.state.set_playing_track_index(0);
|
2017-11-03 01:15:27 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
self.player.emit_shuffle_changed_event(shuffle);
|
|
|
|
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-11-03 01:15:27 +00:00
|
|
|
}
|
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeSeek => {
|
2023-01-17 20:46:14 +00:00
|
|
|
self.handle_seek(update.position());
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeReplace => {
|
2023-01-17 20:46:14 +00:00
|
|
|
let context_uri = update.state.context_uri().to_owned();
|
2022-08-23 20:23:37 +00:00
|
|
|
|
|
|
|
// completely ignore local playback.
|
|
|
|
if context_uri.starts_with("spotify:local-files") {
|
|
|
|
self.notify(None)?;
|
|
|
|
return Err(SpircError::UnsupportedLocalPlayBack.into());
|
|
|
|
}
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
self.update_tracks(update.state.get_or_default());
|
2020-02-02 22:15:15 +00:00
|
|
|
|
|
|
|
if let SpircPlayStatus::Playing {
|
|
|
|
preloading_of_next_track_triggered,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| SpircPlayStatus::Paused {
|
|
|
|
preloading_of_next_track_triggered,
|
|
|
|
..
|
|
|
|
} = self.play_status
|
|
|
|
{
|
|
|
|
if preloading_of_next_track_triggered {
|
2020-05-10 12:31:43 +00:00
|
|
|
// Get the next track_id in the playlist
|
2020-02-02 22:15:15 +00:00
|
|
|
if let Some(track_id) = self.preview_next_track() {
|
|
|
|
self.player.preload(track_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-12-29 21:18:38 +00:00
|
|
|
|
|
|
|
self.notify(None)
|
2016-01-16 01:15:24 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
|
|
|
MessageType::kMessageTypeVolume => {
|
2023-01-17 20:46:14 +00:00
|
|
|
self.set_volume(update.volume() as u16);
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeNotify => {
|
2023-01-17 20:46:14 +00:00
|
|
|
if self.device.is_active()
|
|
|
|
&& update.device_state.is_active()
|
|
|
|
&& self.device.became_active_at() <= update.device_state.became_active_at()
|
2020-07-22 14:28:39 +00:00
|
|
|
{
|
2022-08-23 20:23:37 +00:00
|
|
|
self.handle_disconnect();
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2022-08-23 20:23:37 +00:00
|
|
|
self.notify(None)
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
_ => Ok(()),
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-23 20:23:37 +00:00
|
|
|
fn handle_disconnect(&mut self) {
|
|
|
|
self.device.set_is_active(false);
|
|
|
|
self.handle_stop();
|
|
|
|
|
|
|
|
self.player
|
|
|
|
.emit_session_disconnected_event(self.session.connection_id(), self.session.username());
|
|
|
|
}
|
|
|
|
|
2022-01-02 23:13:28 +00:00
|
|
|
fn handle_stop(&mut self) {
|
|
|
|
self.player.stop();
|
|
|
|
}
|
|
|
|
|
2022-12-03 11:25:27 +00:00
|
|
|
fn handle_activate(&mut self) {
|
|
|
|
let now = self.now_ms();
|
|
|
|
self.device.set_is_active(true);
|
|
|
|
self.device.set_became_active_at(now);
|
|
|
|
self.player
|
|
|
|
.emit_session_connected_event(self.session.connection_id(), self.session.username());
|
|
|
|
self.player.emit_session_client_changed_event(
|
|
|
|
self.session.client_id(),
|
|
|
|
self.session.client_name(),
|
|
|
|
self.session.client_brand_name(),
|
|
|
|
self.session.client_model_name(),
|
|
|
|
);
|
|
|
|
|
|
|
|
self.player
|
2023-01-17 20:46:14 +00:00
|
|
|
.emit_volume_changed_event(self.device.volume() as u16);
|
2022-12-03 11:25:27 +00:00
|
|
|
|
|
|
|
self.player
|
|
|
|
.emit_auto_play_changed_event(self.session.autoplay());
|
|
|
|
|
|
|
|
self.player
|
|
|
|
.emit_filter_explicit_content_changed_event(self.session.filter_explicit_content());
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
self.player.emit_shuffle_changed_event(self.state.shuffle());
|
2022-12-03 11:25:27 +00:00
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
self.player.emit_repeat_changed_event(self.state.repeat());
|
2022-12-03 11:25:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_load(&mut self, state: &State) -> Result<(), Error> {
|
2023-01-17 20:46:14 +00:00
|
|
|
if !self.device.is_active() {
|
2022-12-03 11:25:27 +00:00
|
|
|
self.handle_activate();
|
|
|
|
}
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
let context_uri = state.context_uri().to_owned();
|
2022-12-03 11:25:27 +00:00
|
|
|
|
|
|
|
// completely ignore local playback.
|
|
|
|
if context_uri.starts_with("spotify:local-files") {
|
|
|
|
self.notify(None)?;
|
|
|
|
return Err(SpircError::UnsupportedLocalPlayBack.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
self.update_tracks(state);
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
if !self.state.track.is_empty() {
|
|
|
|
let start_playing = state.status() == PlayStatus::kPlayStatusPlay;
|
|
|
|
self.load_track(start_playing, state.position_ms());
|
2022-12-03 11:25:27 +00:00
|
|
|
} else {
|
|
|
|
info!("No more tracks left in queue");
|
|
|
|
self.handle_stop();
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
fn handle_play(&mut self) {
|
2020-01-31 21:41:11 +00:00
|
|
|
match self.play_status {
|
2020-02-02 22:15:15 +00:00
|
|
|
SpircPlayStatus::Paused {
|
|
|
|
position_ms,
|
|
|
|
preloading_of_next_track_triggered,
|
|
|
|
} => {
|
2020-01-31 21:41:11 +00:00
|
|
|
self.player.play();
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
|
|
|
self.update_state_position(position_ms);
|
|
|
|
self.play_status = SpircPlayStatus::Playing {
|
2023-01-02 18:01:35 +00:00
|
|
|
nominal_start_time: self.now_ms() - position_ms as i64,
|
2020-02-02 22:15:15 +00:00
|
|
|
preloading_of_next_track_triggered,
|
2020-01-31 21:41:11 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
SpircPlayStatus::LoadingPause { position_ms } => {
|
|
|
|
self.player.play();
|
|
|
|
self.play_status = SpircPlayStatus::LoadingPlay { position_ms };
|
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
_ => return,
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2021-12-28 22:46:37 +00:00
|
|
|
|
|
|
|
// Synchronize the volume from the mixer. This is useful on
|
|
|
|
// systems that can switch sources from and back to librespot.
|
|
|
|
let current_volume = self.mixer.volume();
|
|
|
|
self.set_volume(current_volume);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_play_pause(&mut self) {
|
2020-01-31 21:41:11 +00:00
|
|
|
match self.play_status {
|
|
|
|
SpircPlayStatus::Paused { .. } | SpircPlayStatus::LoadingPause { .. } => {
|
|
|
|
self.handle_play()
|
|
|
|
}
|
|
|
|
SpircPlayStatus::Playing { .. } | SpircPlayStatus::LoadingPlay { .. } => {
|
2021-02-20 13:53:24 +00:00
|
|
|
self.handle_pause()
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_pause(&mut self) {
|
2020-01-31 21:41:11 +00:00
|
|
|
match self.play_status {
|
2020-02-02 22:15:15 +00:00
|
|
|
SpircPlayStatus::Playing {
|
|
|
|
nominal_start_time,
|
|
|
|
preloading_of_next_track_triggered,
|
|
|
|
} => {
|
2020-01-31 21:41:11 +00:00
|
|
|
self.player.pause();
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
|
|
|
let position_ms = (self.now_ms() - nominal_start_time) as u32;
|
|
|
|
self.update_state_position(position_ms);
|
2020-02-02 22:15:15 +00:00
|
|
|
self.play_status = SpircPlayStatus::Paused {
|
|
|
|
position_ms,
|
|
|
|
preloading_of_next_track_triggered,
|
|
|
|
};
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
SpircPlayStatus::LoadingPlay { position_ms } => {
|
|
|
|
self.player.pause();
|
|
|
|
self.play_status = SpircPlayStatus::LoadingPause { position_ms };
|
|
|
|
}
|
|
|
|
_ => (),
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
fn handle_seek(&mut self, position_ms: u32) {
|
|
|
|
self.update_state_position(position_ms);
|
|
|
|
self.player.seek(position_ms);
|
|
|
|
let now = self.now_ms();
|
|
|
|
match self.play_status {
|
|
|
|
SpircPlayStatus::Stopped => (),
|
|
|
|
SpircPlayStatus::LoadingPause {
|
|
|
|
position_ms: ref mut position,
|
|
|
|
}
|
|
|
|
| SpircPlayStatus::LoadingPlay {
|
|
|
|
position_ms: ref mut position,
|
|
|
|
}
|
|
|
|
| SpircPlayStatus::Paused {
|
|
|
|
position_ms: ref mut position,
|
2020-02-02 22:15:15 +00:00
|
|
|
..
|
2020-01-31 21:41:11 +00:00
|
|
|
} => *position = position_ms,
|
|
|
|
SpircPlayStatus::Playing {
|
|
|
|
ref mut nominal_start_time,
|
2020-02-02 22:15:15 +00:00
|
|
|
..
|
2020-01-31 21:41:11 +00:00
|
|
|
} => *nominal_start_time = now - position_ms as i64,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
fn consume_queued_track(&mut self) -> usize {
|
|
|
|
// Removes current track if it is queued
|
|
|
|
// Returns the index of the next track
|
2023-01-17 20:46:14 +00:00
|
|
|
let current_index = self.state.playing_track_index() as usize;
|
|
|
|
if (current_index < self.state.track.len()) && self.state.track[current_index].queued() {
|
|
|
|
self.state.track.remove(current_index);
|
2020-01-23 08:05:10 +00:00
|
|
|
current_index
|
|
|
|
} else {
|
|
|
|
current_index + 1
|
2017-10-27 17:45:02 +00:00
|
|
|
}
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
|
2020-01-31 21:41:11 +00:00
|
|
|
fn preview_next_track(&mut self) -> Option<SpotifyId> {
|
2023-01-17 20:46:14 +00:00
|
|
|
self.get_track_id_to_play_from_playlist(self.state.playing_track_index() + 1)
|
2021-03-10 21:32:24 +00:00
|
|
|
.map(|(track_id, _)| track_id)
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 09:49:26 +00:00
|
|
|
fn handle_preload_next_track(&mut self) {
|
|
|
|
// Requests the player thread to preload the next track
|
|
|
|
match self.play_status {
|
|
|
|
SpircPlayStatus::Paused {
|
|
|
|
ref mut preloading_of_next_track_triggered,
|
|
|
|
..
|
|
|
|
}
|
|
|
|
| SpircPlayStatus::Playing {
|
|
|
|
ref mut preloading_of_next_track_triggered,
|
|
|
|
..
|
|
|
|
} => {
|
|
|
|
*preloading_of_next_track_triggered = true;
|
|
|
|
}
|
2022-01-02 23:13:28 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(track_id) = self.preview_next_track() {
|
|
|
|
self.player.preload(track_id);
|
|
|
|
} else {
|
|
|
|
self.handle_stop();
|
2020-05-13 09:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-27 14:24:21 +00:00
|
|
|
|
2020-05-13 11:30:30 +00:00
|
|
|
// Mark unavailable tracks so we can skip them later
|
|
|
|
fn handle_unavailable(&mut self, track_id: SpotifyId) {
|
2020-05-28 14:18:41 +00:00
|
|
|
let unavailables = self.get_track_index_for_spotify_id(&track_id, 0);
|
2020-05-13 11:30:30 +00:00
|
|
|
for &index in unavailables.iter() {
|
|
|
|
let mut unplayable_track_ref = TrackRef::new();
|
2023-01-17 20:46:14 +00:00
|
|
|
unplayable_track_ref.set_gid(self.state.track[index].gid().to_vec());
|
2020-05-13 11:30:30 +00:00
|
|
|
// Misuse context field to flag the track
|
|
|
|
unplayable_track_ref.set_context(String::from("NonPlayable"));
|
2023-01-17 20:46:14 +00:00
|
|
|
std::mem::swap(&mut self.state.track[index], &mut unplayable_track_ref);
|
2020-05-13 11:30:30 +00:00
|
|
|
debug!(
|
|
|
|
"Marked <{:?}> at {:?} as NonPlayable",
|
2023-01-17 20:46:14 +00:00
|
|
|
self.state.track[index], index,
|
2020-05-13 11:30:30 +00:00
|
|
|
);
|
2020-05-13 09:49:26 +00:00
|
|
|
}
|
|
|
|
self.handle_preload_next_track();
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
|
|
|
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
fn handle_next(&mut self) {
|
2023-01-17 20:46:14 +00:00
|
|
|
let context_uri = self.state.context_uri().to_owned();
|
|
|
|
let mut tracks_len = self.state.track.len() as u32;
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
let mut new_index = self.consume_queued_track() as u32;
|
2023-01-17 20:46:14 +00:00
|
|
|
let mut continue_playing = self.state.status() == PlayStatus::kPlayStatusPlay;
|
2022-09-30 19:36:20 +00:00
|
|
|
|
|
|
|
let update_tracks =
|
|
|
|
self.autoplay_context && tracks_len - new_index < CONTEXT_FETCH_THRESHOLD;
|
|
|
|
|
2018-10-12 17:15:26 +00:00
|
|
|
debug!(
|
|
|
|
"At track {:?} of {:?} <{:?}> update [{}]",
|
2021-10-05 20:08:26 +00:00
|
|
|
new_index + 1,
|
|
|
|
tracks_len,
|
2022-09-30 19:36:20 +00:00
|
|
|
context_uri,
|
|
|
|
update_tracks,
|
2018-10-12 17:15:26 +00:00
|
|
|
);
|
2022-09-30 19:36:20 +00:00
|
|
|
|
|
|
|
// When in autoplay, keep topping up the playlist when it nears the end
|
|
|
|
if update_tracks {
|
2022-10-01 21:01:17 +00:00
|
|
|
if let Some(ref context) = self.context {
|
|
|
|
self.resolve_context = Some(context.next_page_url.to_owned());
|
|
|
|
self.update_tracks_from_context();
|
2023-01-17 20:46:14 +00:00
|
|
|
tracks_len = self.state.track.len() as u32;
|
2022-10-01 21:01:17 +00:00
|
|
|
}
|
2018-10-12 17:15:26 +00:00
|
|
|
}
|
2022-01-01 19:23:21 +00:00
|
|
|
|
2022-09-30 19:36:20 +00:00
|
|
|
// When not in autoplay, either start autoplay or loop back to the start
|
2019-03-24 15:42:00 +00:00
|
|
|
if new_index >= tracks_len {
|
2022-10-01 21:01:17 +00:00
|
|
|
// for some contexts there is no autoplay, such as shows and episodes
|
|
|
|
// in such cases there is no context in librespot.
|
|
|
|
if self.context.is_some() && self.session.autoplay() {
|
2021-10-05 20:08:26 +00:00
|
|
|
// Extend the playlist
|
2022-09-30 19:36:20 +00:00
|
|
|
debug!("Starting autoplay for <{}>", context_uri);
|
2022-10-01 21:01:17 +00:00
|
|
|
// force reloading the current context with an autoplay context
|
2022-09-30 19:36:20 +00:00
|
|
|
self.autoplay_context = true;
|
2023-01-17 20:46:14 +00:00
|
|
|
self.resolve_context = Some(self.state.context_uri().to_owned());
|
2021-10-05 20:08:26 +00:00
|
|
|
self.update_tracks_from_context();
|
|
|
|
self.player.set_auto_normalise_as_album(false);
|
|
|
|
} else {
|
|
|
|
new_index = 0;
|
2023-01-17 20:46:14 +00:00
|
|
|
continue_playing &= self.state.repeat();
|
2022-09-30 19:36:20 +00:00
|
|
|
debug!("Looping back to start, repeat is {}", continue_playing);
|
2021-10-05 20:08:26 +00:00
|
|
|
}
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
|
2020-01-23 08:05:10 +00:00
|
|
|
if tracks_len > 0 {
|
|
|
|
self.state.set_playing_track_index(new_index);
|
2020-01-31 21:41:11 +00:00
|
|
|
self.load_track(continue_playing, 0);
|
2020-01-28 18:19:18 +00:00
|
|
|
} else {
|
2020-01-23 08:05:10 +00:00
|
|
|
info!("Not playing next track because there are no more tracks left in queue.");
|
|
|
|
self.state.set_playing_track_index(0);
|
2022-01-02 23:13:28 +00:00
|
|
|
self.handle_stop();
|
2020-01-23 08:05:10 +00:00
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_prev(&mut self) {
|
|
|
|
// Previous behaves differently based on the position
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
// Under 3s it goes to the previous song (starts playing)
|
|
|
|
// Over 3s it seeks to zero (retains previous play status)
|
2017-02-23 11:05:32 +00:00
|
|
|
if self.position() < 3000 {
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
// Queued tracks always follow the currently playing track.
|
|
|
|
// They should not be considered when calculating the previous
|
|
|
|
// track so extract them beforehand and reinsert them after it.
|
|
|
|
let mut queue_tracks = Vec::new();
|
|
|
|
{
|
|
|
|
let queue_index = self.consume_queued_track();
|
2023-01-17 20:46:14 +00:00
|
|
|
let tracks = &mut self.state.track;
|
|
|
|
while queue_index < tracks.len() && tracks[queue_index].queued() {
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
queue_tracks.push(tracks.remove(queue_index));
|
|
|
|
}
|
|
|
|
}
|
2023-01-17 20:46:14 +00:00
|
|
|
let current_index = self.state.playing_track_index();
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
let new_index = if current_index > 0 {
|
|
|
|
current_index - 1
|
2023-01-17 20:46:14 +00:00
|
|
|
} else if self.state.repeat() {
|
|
|
|
self.state.track.len() as u32 - 1
|
2017-02-23 11:05:32 +00:00
|
|
|
} else {
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
0
|
2017-02-23 11:05:32 +00:00
|
|
|
};
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
// Reinsert queued tracks after the new playing track.
|
|
|
|
let mut pos = (new_index + 1) as usize;
|
2021-03-10 21:32:24 +00:00
|
|
|
for track in queue_tracks {
|
2023-01-17 20:46:14 +00:00
|
|
|
self.state.track.insert(pos, track);
|
Improved next/prev handling for queued tracks.
1) A queued track is removed once it has become the current track.
Note that the track doesn't need to actually play i.e. it could
have been immediately skipped over with 'next()'. This is
implemented in 'consume_queued_track()'.
2) Queued tracks are always positioned immediately after the current
track. 1) ensures this is true for 'next()' but 'prev()' requires
all the queued tracks are actually moved for this to remain the
case.
Also fixed the case where 'prev()' on the first track would incorrectly
wrap back around to the last track even when repeat was disabled. The
correct behaviour is to remain on the first track and just seek to the
start.
For example, with the following tracks and repeat enabled:
TrackA, TrackB, TrackC-Q, TrackD-Q, TrackE
^^^^^^
Here, the result of 'prev' changes the current track from TrackB to
TrackA and the queued tracks (TrackC, TrackD) move to the position
immediately after TrackA:
TrackA, TrackC-Q, TrackD-Q, TrackB, TrackE
^^^^^^
Calling 'prev' again results in the current track wrapping back around
to TrackE and the queued tracks moving after that same track:
TrackA, TrackB, TrackE, TrackC-Q, TrackD-Q
^^^^^^
2018-02-01 00:02:12 +00:00
|
|
|
pos += 1;
|
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
|
|
|
|
self.state.set_playing_track_index(new_index);
|
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
let start_playing = self.state.status() == PlayStatus::kPlayStatusPlay;
|
2022-09-30 19:36:20 +00:00
|
|
|
self.load_track(start_playing, 0);
|
2017-02-23 11:05:32 +00:00
|
|
|
} else {
|
2020-01-31 21:41:11 +00:00
|
|
|
self.handle_seek(0);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_volume_up(&mut self) {
|
2023-01-17 20:46:14 +00:00
|
|
|
let volume = (self.device.volume() as u16).saturating_add(VOLUME_STEP_SIZE);
|
2021-05-24 13:53:32 +00:00
|
|
|
self.set_volume(volume);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_volume_down(&mut self) {
|
2023-01-17 20:46:14 +00:00
|
|
|
let volume = (self.device.volume() as u16).saturating_sub(VOLUME_STEP_SIZE);
|
2021-05-24 13:53:32 +00:00
|
|
|
self.set_volume(volume);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn handle_end_of_track(&mut self) -> Result<(), Error> {
|
2018-02-09 01:05:50 +00:00
|
|
|
self.handle_next();
|
2021-12-29 21:18:38 +00:00
|
|
|
self.notify(None)
|
2016-01-20 13:51:35 +00:00
|
|
|
}
|
2016-01-20 15:47:05 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn position(&mut self) -> u32 {
|
2020-01-31 21:41:11 +00:00
|
|
|
match self.play_status {
|
|
|
|
SpircPlayStatus::Stopped => 0,
|
|
|
|
SpircPlayStatus::LoadingPlay { position_ms }
|
|
|
|
| SpircPlayStatus::LoadingPause { position_ms }
|
2020-02-02 22:15:15 +00:00
|
|
|
| SpircPlayStatus::Paused { position_ms, .. } => position_ms,
|
|
|
|
SpircPlayStatus::Playing {
|
|
|
|
nominal_start_time, ..
|
|
|
|
} => (self.now_ms() - nominal_start_time) as u32,
|
2020-01-31 21:41:11 +00:00
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
|
2018-10-12 17:15:26 +00:00
|
|
|
fn update_tracks_from_context(&mut self) {
|
|
|
|
if let Some(ref context) = self.context {
|
|
|
|
let new_tracks = &context.tracks;
|
2022-10-01 21:01:17 +00:00
|
|
|
|
2019-03-28 14:27:50 +00:00
|
|
|
debug!("Adding {:?} tracks from context to frame", new_tracks.len());
|
2022-10-01 21:01:17 +00:00
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
let mut track_vec = self.state.track.clone();
|
2019-03-28 14:27:50 +00:00
|
|
|
if let Some(head) = track_vec.len().checked_sub(CONTEXT_TRACKS_HISTORY) {
|
|
|
|
track_vec.drain(0..head);
|
|
|
|
}
|
2021-09-01 19:25:32 +00:00
|
|
|
track_vec.extend_from_slice(new_tracks);
|
2023-01-17 20:46:14 +00:00
|
|
|
self.state.track = track_vec;
|
2019-03-28 14:27:50 +00:00
|
|
|
|
|
|
|
// Update playing index
|
|
|
|
if let Some(new_index) = self
|
|
|
|
.state
|
2023-01-17 20:46:14 +00:00
|
|
|
.playing_track_index()
|
2019-03-28 14:27:50 +00:00
|
|
|
.checked_sub(CONTEXT_TRACKS_HISTORY as u32)
|
2019-03-16 15:18:38 +00:00
|
|
|
{
|
2019-03-28 14:27:50 +00:00
|
|
|
self.state.set_playing_track_index(new_index);
|
2018-10-12 17:15:26 +00:00
|
|
|
}
|
2019-03-24 15:42:00 +00:00
|
|
|
} else {
|
|
|
|
warn!("No context to update from!");
|
2018-10-12 17:15:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-03 11:25:27 +00:00
|
|
|
fn update_tracks(&mut self, state: &State) {
|
|
|
|
trace!("State: {:#?}", state);
|
2022-01-01 19:23:21 +00:00
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
let index = state.playing_track_index();
|
|
|
|
let context_uri = state.context_uri();
|
|
|
|
let tracks = &state.track;
|
2022-01-01 19:23:21 +00:00
|
|
|
|
2021-12-28 22:46:37 +00:00
|
|
|
trace!("Frame has {:?} tracks", tracks.len());
|
2022-01-01 19:23:21 +00:00
|
|
|
|
2022-09-30 19:36:20 +00:00
|
|
|
// First the tracks from the requested context, without autoplay.
|
|
|
|
// We will transition into autoplay after the latest track of this context.
|
|
|
|
self.autoplay_context = false;
|
2022-10-01 21:01:17 +00:00
|
|
|
self.resolve_context = Some(context_uri.to_owned());
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2021-09-20 17:22:02 +00:00
|
|
|
self.player
|
|
|
|
.set_auto_normalise_as_album(context_uri.starts_with("spotify:album:"));
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
self.state.set_playing_track_index(index);
|
2023-01-17 20:46:14 +00:00
|
|
|
self.state.track = tracks.to_vec();
|
2022-10-01 21:01:17 +00:00
|
|
|
self.state.set_context_uri(context_uri.to_owned());
|
2019-10-19 11:42:23 +00:00
|
|
|
// has_shuffle/repeat seem to always be true in these replace msgs,
|
|
|
|
// but to replicate the behaviour of the Android client we have to
|
|
|
|
// ignore false values.
|
2023-01-17 20:46:14 +00:00
|
|
|
if state.repeat() {
|
2019-10-19 11:42:23 +00:00
|
|
|
self.state.set_repeat(true);
|
|
|
|
}
|
2023-01-17 20:46:14 +00:00
|
|
|
if state.shuffle() {
|
2019-10-19 11:42:23 +00:00
|
|
|
self.state.set_shuffle(true);
|
|
|
|
}
|
2016-01-20 15:47:05 +00:00
|
|
|
}
|
|
|
|
|
2020-05-13 11:30:30 +00:00
|
|
|
// Helper to find corresponding index(s) for track_id
|
2020-05-13 09:49:26 +00:00
|
|
|
fn get_track_index_for_spotify_id(
|
|
|
|
&self,
|
|
|
|
track_id: &SpotifyId,
|
|
|
|
start_index: usize,
|
2020-05-13 11:30:30 +00:00
|
|
|
) -> Vec<usize> {
|
2023-01-17 20:46:14 +00:00
|
|
|
let index: Vec<usize> = self.state.track[start_index..]
|
2020-05-13 09:49:26 +00:00
|
|
|
.iter()
|
2020-05-13 11:30:30 +00:00
|
|
|
.enumerate()
|
2023-01-17 20:46:14 +00:00
|
|
|
.filter(|&(_, track_ref)| track_ref.gid() == track_id.to_raw())
|
2020-05-13 11:30:30 +00:00
|
|
|
.map(|(idx, _)| start_index + idx)
|
|
|
|
.collect();
|
2020-05-13 09:49:26 +00:00
|
|
|
index
|
|
|
|
}
|
|
|
|
|
2020-05-13 11:30:30 +00:00
|
|
|
// Broken out here so we can refactor this later when we move to SpotifyObjectID or similar
|
|
|
|
fn track_ref_is_unavailable(&self, track_ref: &TrackRef) -> bool {
|
2023-01-17 20:46:14 +00:00
|
|
|
track_ref.context() == "NonPlayable"
|
2020-05-13 11:30:30 +00:00
|
|
|
}
|
|
|
|
|
2020-02-02 22:15:15 +00:00
|
|
|
fn get_track_id_to_play_from_playlist(&self, index: u32) -> Option<(SpotifyId, u32)> {
|
2023-01-17 20:46:14 +00:00
|
|
|
let tracks_len = self.state.track.len();
|
2020-02-02 22:15:15 +00:00
|
|
|
|
2021-11-18 03:15:35 +00:00
|
|
|
// Guard against tracks_len being zero to prevent
|
|
|
|
// 'index out of bounds: the len is 0 but the index is 0'
|
|
|
|
// https://github.com/librespot-org/librespot/issues/226#issuecomment-971642037
|
|
|
|
if tracks_len == 0 {
|
|
|
|
warn!("No playable track found in state: {:?}", self.state);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2020-05-13 11:24:30 +00:00
|
|
|
let mut new_playlist_index = index as usize;
|
2020-02-02 22:15:15 +00:00
|
|
|
|
|
|
|
if new_playlist_index >= tracks_len {
|
|
|
|
new_playlist_index = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
let start_index = new_playlist_index;
|
|
|
|
|
2019-10-22 10:05:35 +00:00
|
|
|
// Cycle through all tracks, break if we don't find any playable tracks
|
|
|
|
// tracks in each frame either have a gid or uri (that may or may not be a valid track)
|
|
|
|
// E.g - context based frames sometimes contain tracks with <spotify:meta:page:>
|
2020-02-02 22:15:15 +00:00
|
|
|
|
2023-01-17 20:46:14 +00:00
|
|
|
let mut track_ref = self.state.track[new_playlist_index].clone();
|
2021-12-07 22:22:24 +00:00
|
|
|
let mut track_id = SpotifyId::try_from(&track_ref);
|
|
|
|
while self.track_ref_is_unavailable(&track_ref) || track_id.is_err() {
|
2020-02-02 22:15:15 +00:00
|
|
|
warn!(
|
|
|
|
"Skipping track <{:?}> at position [{}] of {}",
|
2020-05-28 14:18:41 +00:00
|
|
|
track_ref, new_playlist_index, tracks_len
|
2020-02-02 22:15:15 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
new_playlist_index += 1;
|
|
|
|
if new_playlist_index >= tracks_len {
|
|
|
|
new_playlist_index = 0;
|
2019-03-13 19:35:46 +00:00
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2020-02-02 22:15:15 +00:00
|
|
|
if new_playlist_index == start_index {
|
|
|
|
warn!("No playable track found in state: {:?}", self.state);
|
|
|
|
return None;
|
2019-03-13 19:35:46 +00:00
|
|
|
}
|
2023-01-17 20:46:14 +00:00
|
|
|
track_ref = self.state.track[new_playlist_index].clone();
|
2021-12-07 22:22:24 +00:00
|
|
|
track_id = SpotifyId::try_from(&track_ref);
|
2019-10-09 17:49:13 +00:00
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2020-02-02 22:15:15 +00:00
|
|
|
match track_id {
|
2020-05-13 11:24:30 +00:00
|
|
|
Ok(track_id) => Some((track_id, new_playlist_index as u32)),
|
2020-02-02 22:15:15 +00:00
|
|
|
Err(_) => None,
|
2017-01-29 16:25:09 +00:00
|
|
|
}
|
2020-02-02 22:15:15 +00:00
|
|
|
}
|
2017-01-29 16:25:09 +00:00
|
|
|
|
2020-02-02 22:15:15 +00:00
|
|
|
fn load_track(&mut self, start_playing: bool, position_ms: u32) {
|
2023-01-17 20:46:14 +00:00
|
|
|
let index = self.state.playing_track_index();
|
2020-02-02 22:15:15 +00:00
|
|
|
|
|
|
|
match self.get_track_id_to_play_from_playlist(index) {
|
|
|
|
Some((track, index)) => {
|
|
|
|
self.state.set_playing_track_index(index);
|
|
|
|
|
2023-06-13 14:30:56 +00:00
|
|
|
self.player.load(track, start_playing, position_ms);
|
2020-02-02 22:15:15 +00:00
|
|
|
|
|
|
|
self.update_state_position(position_ms);
|
|
|
|
if start_playing {
|
2020-05-27 17:51:56 +00:00
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
2020-02-02 22:15:15 +00:00
|
|
|
self.play_status = SpircPlayStatus::LoadingPlay { position_ms };
|
|
|
|
} else {
|
2020-05-27 17:51:56 +00:00
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
2020-02-02 22:15:15 +00:00
|
|
|
self.play_status = SpircPlayStatus::LoadingPause { position_ms };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
None => {
|
2022-01-02 23:13:28 +00:00
|
|
|
self.handle_stop();
|
2020-02-02 22:15:15 +00:00
|
|
|
}
|
2017-01-29 16:25:09 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn hello(&mut self) -> Result<(), Error> {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeHello).send()
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2021-12-29 21:18:38 +00:00
|
|
|
fn notify(&mut self, recipient: Option<&str>) -> Result<(), Error> {
|
2023-01-17 20:46:14 +00:00
|
|
|
let status = self.state.status();
|
2022-09-28 19:25:56 +00:00
|
|
|
|
|
|
|
// When in loading state, the Spotify UI is disabled for interaction.
|
|
|
|
// On desktop this isn't so bad but on mobile it means that the bottom
|
|
|
|
// control disappears entirely. This is very confusing, so don't notify
|
|
|
|
// in this case.
|
|
|
|
if status == PlayStatus::kPlayStatusLoading {
|
|
|
|
return Ok(());
|
|
|
|
}
|
|
|
|
|
|
|
|
trace!("Sending status to server: [{:?}]", status);
|
2017-01-29 14:11:20 +00:00
|
|
|
let mut cs = CommandSender::new(self, MessageType::kMessageTypeNotify);
|
|
|
|
if let Some(s) = recipient {
|
2021-09-01 19:25:32 +00:00
|
|
|
cs = cs.recipient(s);
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2021-12-26 20:18:42 +00:00
|
|
|
cs.send()
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2018-05-17 01:15:17 +00:00
|
|
|
|
|
|
|
fn set_volume(&mut self, volume: u16) {
|
2023-01-17 20:46:14 +00:00
|
|
|
let old_volume = self.device.volume();
|
2022-08-23 20:23:37 +00:00
|
|
|
let new_volume = volume as u32;
|
2024-10-17 15:02:56 +00:00
|
|
|
if old_volume != new_volume || self.mixer.volume() != volume {
|
2022-08-23 20:23:37 +00:00
|
|
|
self.device.set_volume(new_volume);
|
|
|
|
self.mixer.set_volume(volume);
|
|
|
|
if let Some(cache) = self.session.cache() {
|
|
|
|
cache.save_volume(volume)
|
|
|
|
}
|
2023-01-17 20:46:14 +00:00
|
|
|
if self.device.is_active() {
|
2022-08-23 20:23:37 +00:00
|
|
|
self.player.emit_volume_changed_event(volume);
|
|
|
|
}
|
2018-05-17 01:15:17 +00:00
|
|
|
}
|
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
2016-02-16 21:25:55 +00:00
|
|
|
|
2017-02-22 04:17:04 +00:00
|
|
|
impl Drop for SpircTask {
|
|
|
|
fn drop(&mut self) {
|
2022-01-16 20:29:59 +00:00
|
|
|
debug!("drop Spirc[{}]", self.spirc_id);
|
2017-02-22 04:17:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-17 14:24:40 +00:00
|
|
|
struct CommandSender<'a> {
|
2017-01-20 12:56:42 +00:00
|
|
|
spirc: &'a mut SpircTask,
|
2017-02-21 21:49:45 +00:00
|
|
|
frame: protocol::spirc::Frame,
|
2016-02-17 14:24:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a> CommandSender<'a> {
|
2024-10-06 20:10:02 +00:00
|
|
|
fn new(spirc: &'a mut SpircTask, cmd: MessageType) -> Self {
|
2018-02-17 09:15:09 +00:00
|
|
|
let mut frame = protocol::spirc::Frame::new();
|
2022-10-01 21:01:17 +00:00
|
|
|
// frame version
|
2018-02-17 09:15:09 +00:00
|
|
|
frame.set_version(1);
|
2022-10-01 21:01:17 +00:00
|
|
|
// Latest known Spirc version is 3.2.6, but we need another interface to announce support for Spirc V3.
|
|
|
|
// Setting anything higher than 2.0.0 here just seems to limit it to 2.0.0.
|
|
|
|
frame.set_protocol_version("2.0.0".to_string());
|
2018-02-17 09:15:09 +00:00
|
|
|
frame.set_ident(spirc.ident.clone());
|
|
|
|
frame.set_seq_nr(spirc.sequence.get());
|
|
|
|
frame.set_typ(cmd);
|
2023-01-17 20:46:14 +00:00
|
|
|
*frame.device_state.mut_or_insert_default() = spirc.device.clone();
|
2019-03-24 14:15:14 +00:00
|
|
|
frame.set_state_update_id(spirc.now_ms());
|
2021-03-10 21:39:01 +00:00
|
|
|
CommandSender { spirc, frame }
|
2016-02-16 21:25:55 +00:00
|
|
|
}
|
|
|
|
|
2024-10-06 20:10:02 +00:00
|
|
|
fn recipient(mut self, recipient: &'a str) -> Self {
|
2023-01-17 20:46:14 +00:00
|
|
|
self.frame.recipient.push(recipient.to_owned());
|
2016-02-17 14:24:40 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2017-02-21 22:46:19 +00:00
|
|
|
#[allow(dead_code)]
|
2024-10-06 20:10:02 +00:00
|
|
|
fn state(mut self, state: protocol::spirc::State) -> Self {
|
2023-01-17 20:46:14 +00:00
|
|
|
*self.frame.state.mut_or_insert_default() = state;
|
2016-02-17 14:24:40 +00:00
|
|
|
self
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
fn send(mut self) -> Result<(), Error> {
|
2023-01-17 20:46:14 +00:00
|
|
|
if self.frame.state.is_none() && self.spirc.device.is_active() {
|
|
|
|
*self.frame.state.mut_or_insert_default() = self.spirc.state.clone();
|
2016-02-17 14:24:40 +00:00
|
|
|
}
|
|
|
|
|
2021-12-26 20:18:42 +00:00
|
|
|
self.spirc.sender.send(self.frame.write_to_bytes()?)
|
2016-02-17 14:24:40 +00:00
|
|
|
}
|
2016-02-16 21:25:55 +00:00
|
|
|
}
|