2017-01-29 14:11:20 +00:00
|
|
|
use futures::future;
|
2018-02-11 17:52:53 +00:00
|
|
|
use futures::sync::{mpsc, oneshot};
|
2018-03-23 05:13:01 +00:00
|
|
|
use futures::{Async, Future, Poll, Sink, Stream};
|
2017-01-29 14:11:20 +00:00
|
|
|
use protobuf::{self, Message};
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-08-03 18:58:44 +00:00
|
|
|
use core::config::ConnectConfig;
|
|
|
|
use core::mercury::MercuryError;
|
|
|
|
use core::session::Session;
|
2018-02-12 20:02:27 +00:00
|
|
|
use core::spotify_id::SpotifyId;
|
|
|
|
use core::util::SeqGenerator;
|
2017-08-03 18:58:44 +00:00
|
|
|
use core::version;
|
2018-05-17 01:15:17 +00:00
|
|
|
use core::volume::Volume;
|
2016-03-17 03:31:57 +00:00
|
|
|
|
2016-01-27 10:40:00 +00:00
|
|
|
use protocol;
|
2019-03-13 19:35:46 +00:00
|
|
|
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State};
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2018-02-09 01:05:50 +00:00
|
|
|
use playback::mixer::Mixer;
|
|
|
|
use playback::player::Player;
|
2018-10-12 17:15:26 +00:00
|
|
|
use serde_json;
|
2017-08-03 18:58:44 +00:00
|
|
|
|
2019-03-13 19:35:46 +00:00
|
|
|
use context::StationContext;
|
2017-11-03 01:15:27 +00:00
|
|
|
use rand;
|
2019-03-10 16:48:19 +00:00
|
|
|
use rand::seq::SliceRandom;
|
2018-02-11 17:52:53 +00:00
|
|
|
use std;
|
|
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
2017-11-03 01:15:27 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
pub struct SpircTask {
|
2016-01-20 14:11:49 +00:00
|
|
|
player: Player,
|
2017-02-22 04:17:04 +00:00
|
|
|
mixer: Box<Mixer>,
|
2018-03-11 10:27:28 +00:00
|
|
|
linear_volume: bool,
|
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,
|
2016-02-13 01:09:15 +00:00
|
|
|
|
2018-01-21 19:29:31 +00:00
|
|
|
subscription: Box<Stream<Item = Frame, Error = MercuryError>>,
|
|
|
|
sender: Box<Sink<SinkItem = Frame, SinkError = MercuryError>>,
|
2017-01-20 12:56:42 +00:00
|
|
|
commands: mpsc::UnboundedReceiver<SpircCommand>,
|
2018-01-21 19:29:31 +00:00
|
|
|
end_of_track: Box<Future<Item = (), Error = oneshot::Canceled>>,
|
2017-01-20 12:56:42 +00:00
|
|
|
|
|
|
|
shutdown: bool,
|
2017-02-22 04:17:04 +00:00
|
|
|
session: Session,
|
2018-10-12 17:15:26 +00:00
|
|
|
context_fut: Box<Future<Item = serde_json::Value, Error = MercuryError>>,
|
|
|
|
context: Option<StationContext>,
|
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,
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Spirc {
|
|
|
|
commands: mpsc::UnboundedSender<SpircCommand>,
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
|
2018-02-10 10:01:19 +00:00
|
|
|
fn now_ms() -> i64 {
|
|
|
|
let dur = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
|
|
|
Ok(dur) => dur,
|
|
|
|
Err(err) => err.duration(),
|
|
|
|
};
|
|
|
|
(dur.as_secs() * 1000 + (dur.subsec_nanos() / 1000_000) as u64) as i64
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2018-05-17 01:15:17 +00:00
|
|
|
fn initial_device_state(config: ConnectConfig) -> DeviceState {
|
2018-02-16 21:04:37 +00:00
|
|
|
{
|
|
|
|
let mut msg = DeviceState::new();
|
|
|
|
msg.set_sw_version(version::version_string());
|
|
|
|
msg.set_is_active(false);
|
|
|
|
msg.set_can_play(true);
|
2018-05-17 01:15:17 +00:00
|
|
|
msg.set_volume(0);
|
2018-02-16 21:04:37 +00:00
|
|
|
msg.set_name(config.name);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_capabilities();
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kCanBePlayer);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(1)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kDeviceType);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(config.device_type as i64)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kGaiaEqConnectId);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(1)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kSupportsLogout);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(0)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kIsObservable);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(1)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kVolumeSteps);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(64)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
2018-10-12 17:15:26 +00:00
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kSupportsPlaylistV2);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_intValue();
|
|
|
|
repeated.push(64)
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
2018-02-16 21:04:37 +00:00
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kSupportedContexts);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_stringValue();
|
|
|
|
repeated.push(::std::convert::Into::into("album"));
|
|
|
|
repeated.push(::std::convert::Into::into("playlist"));
|
|
|
|
repeated.push(::std::convert::Into::into("search"));
|
|
|
|
repeated.push(::std::convert::Into::into("inbox"));
|
|
|
|
repeated.push(::std::convert::Into::into("toplist"));
|
|
|
|
repeated.push(::std::convert::Into::into("starred"));
|
|
|
|
repeated.push(::std::convert::Into::into("publishedstarred"));
|
|
|
|
repeated.push(::std::convert::Into::into("track"))
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
{
|
|
|
|
let msg = repeated.push_default();
|
|
|
|
msg.set_typ(protocol::spirc::CapabilityType::kSupportedTypes);
|
|
|
|
{
|
|
|
|
let repeated = msg.mut_stringValue();
|
|
|
|
repeated.push(::std::convert::Into::into("audio/local"));
|
|
|
|
repeated.push(::std::convert::Into::into("audio/track"));
|
|
|
|
repeated.push(::std::convert::Into::into("local"));
|
|
|
|
repeated.push(::std::convert::Into::into("track"))
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
};
|
|
|
|
};
|
|
|
|
msg
|
|
|
|
}
|
2017-01-20 14:44:13 +00:00
|
|
|
}
|
|
|
|
|
2018-03-11 10:27:28 +00:00
|
|
|
fn calc_logarithmic_volume(volume: u16) -> u16 {
|
2018-01-29 22:37:30 +00:00
|
|
|
// Volume conversion taken from https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2
|
|
|
|
// Convert the given volume [0..0xffff] to a dB gain
|
|
|
|
// We assume a dB range of 60dB.
|
2018-10-12 17:15:26 +00:00
|
|
|
// Use the equation: a * exp(b * x)
|
2018-01-29 22:37:30 +00:00
|
|
|
// in which a = IDEAL_FACTOR, b = 1/1000
|
|
|
|
const IDEAL_FACTOR: f64 = 6.908;
|
|
|
|
let normalized_volume = volume as f64 / std::u16::MAX as f64; // To get a value between 0 and 1
|
|
|
|
|
|
|
|
let mut val = std::u16::MAX;
|
|
|
|
// Prevent val > std::u16::MAX due to rounding errors
|
2018-02-09 01:05:50 +00:00
|
|
|
if normalized_volume < 0.999 {
|
2018-01-29 22:37:30 +00:00
|
|
|
let new_volume = (normalized_volume * IDEAL_FACTOR).exp() / 1000.0;
|
|
|
|
val = (new_volume * std::u16::MAX as f64) as u16;
|
2018-01-25 22:37:28 +00:00
|
|
|
}
|
2018-01-29 22:37:30 +00:00
|
|
|
|
2018-02-09 01:05:50 +00:00
|
|
|
debug!("input volume:{} to mixer: {}", volume, val);
|
2018-01-29 22:37:30 +00:00
|
|
|
|
|
|
|
// return the scale factor (0..0xffff) (equivalent to a voltage multiplier).
|
|
|
|
val
|
2018-01-25 22:37:28 +00:00
|
|
|
}
|
|
|
|
|
2018-03-11 10:27:28 +00:00
|
|
|
fn volume_to_mixer(volume: u16, linear_volume: bool) -> u16 {
|
|
|
|
if linear_volume {
|
|
|
|
debug!("linear volume: {}", volume);
|
|
|
|
volume
|
|
|
|
} else {
|
|
|
|
calc_logarithmic_volume(volume)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
impl Spirc {
|
2018-02-11 17:52:53 +00:00
|
|
|
pub fn new(
|
|
|
|
config: ConnectConfig,
|
|
|
|
session: Session,
|
|
|
|
player: Player,
|
|
|
|
mixer: Box<Mixer>,
|
|
|
|
) -> (Spirc, SpircTask) {
|
2017-02-22 04:17:04 +00:00
|
|
|
debug!("new Spirc[{}]", session.session_id());
|
|
|
|
|
2016-03-17 03:06:56 +00:00
|
|
|
let ident = session.device_id().to_owned();
|
2016-01-01 23:16:12 +00:00
|
|
|
|
2019-02-22 13:09:01 +00:00
|
|
|
// Uri updated in response to issue #288
|
|
|
|
let uri = format!("hm://remote/user/{}/", session.username());
|
2017-01-20 12:56:42 +00:00
|
|
|
|
|
|
|
let subscription = session.mercury().subscribe(&uri as &str);
|
2018-02-11 17:52:53 +00:00
|
|
|
let subscription = subscription
|
|
|
|
.map(|stream| stream.map_err(|_| MercuryError))
|
|
|
|
.flatten_stream();
|
2018-01-21 19:29:31 +00:00
|
|
|
let subscription = Box::new(subscription.map(|response| -> Frame {
|
2017-01-20 12:56:42 +00:00
|
|
|
let data = response.payload.first().unwrap();
|
|
|
|
protobuf::parse_from_bytes(data).unwrap()
|
2018-01-21 19:29:31 +00:00
|
|
|
}));
|
2017-01-20 12:56:42 +00:00
|
|
|
|
2018-02-11 17:52:53 +00:00
|
|
|
let sender = Box::new(
|
|
|
|
session
|
|
|
|
.mercury()
|
|
|
|
.sender(uri)
|
|
|
|
.with(|frame: Frame| Ok(frame.write_to_bytes().unwrap())),
|
|
|
|
);
|
2017-01-20 12:56:42 +00:00
|
|
|
|
|
|
|
let (cmd_tx, cmd_rx) = mpsc::unbounded();
|
|
|
|
|
2018-05-17 01:15:17 +00:00
|
|
|
let volume = config.volume;
|
2018-03-11 10:27:28 +00:00
|
|
|
let linear_volume = config.linear_volume;
|
|
|
|
|
2018-05-17 01:15:17 +00:00
|
|
|
let device = initial_device_state(config);
|
2017-01-20 14:44:13 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
let mut task = SpircTask {
|
2016-01-20 14:11:49 +00:00
|
|
|
player: player,
|
2017-02-21 22:46:19 +00:00
|
|
|
mixer: mixer,
|
2018-03-11 10:27:28 +00:00
|
|
|
linear_volume: linear_volume,
|
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
|
|
|
|
2016-01-01 23:16:12 +00:00
|
|
|
ident: ident,
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
device: device,
|
|
|
|
state: initial_state(),
|
2016-02-13 01:09:15 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
subscription: subscription,
|
|
|
|
sender: sender,
|
|
|
|
commands: cmd_rx,
|
2018-01-21 19:29:31 +00:00
|
|
|
end_of_track: Box::new(future::empty()),
|
2016-01-20 15:47:05 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
shutdown: false,
|
2017-02-22 04:17:04 +00:00
|
|
|
session: session.clone(),
|
2018-10-12 17:15:26 +00:00
|
|
|
|
|
|
|
context_fut: Box::new(future::empty()),
|
|
|
|
context: None,
|
2016-01-20 15:47:05 +00:00
|
|
|
};
|
|
|
|
|
2018-05-17 01:15:17 +00:00
|
|
|
task.set_volume(volume);
|
|
|
|
|
2018-02-11 17:52:53 +00:00
|
|
|
let spirc = Spirc { commands: cmd_tx };
|
2016-01-20 15:47:05 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
task.hello();
|
2016-02-16 21:26:51 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
(spirc, task)
|
2016-02-16 21:26:51 +00:00
|
|
|
}
|
2016-02-16 21:52:55 +00:00
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
pub fn play(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::Play);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
pub fn play_pause(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::PlayPause);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
pub fn pause(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::Pause);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
pub fn prev(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::Prev);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
pub fn next(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::Next);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
pub fn volume_up(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::VolumeUp);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
pub fn volume_down(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::VolumeDown);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
2017-01-29 16:25:09 +00:00
|
|
|
pub fn shutdown(&self) {
|
2018-01-21 19:38:30 +00:00
|
|
|
let _ = self.commands.unbounded_send(SpircCommand::Shutdown);
|
2016-02-16 21:52:55 +00:00
|
|
|
}
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
2016-02-16 21:52:55 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
impl Future for SpircTask {
|
|
|
|
type Item = ();
|
|
|
|
type Error = ();
|
|
|
|
|
|
|
|
fn poll(&mut self) -> Poll<(), ()> {
|
|
|
|
loop {
|
|
|
|
let mut progress = false;
|
|
|
|
|
2018-04-21 15:46:29 +00:00
|
|
|
if self.session.is_invalid() {
|
|
|
|
return Ok(Async::Ready(()));
|
|
|
|
}
|
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
if !self.shutdown {
|
|
|
|
match self.subscription.poll().unwrap() {
|
|
|
|
Async::Ready(Some(frame)) => {
|
|
|
|
progress = true;
|
|
|
|
self.handle_frame(frame);
|
|
|
|
}
|
|
|
|
Async::Ready(None) => panic!("subscription terminated"),
|
|
|
|
Async::NotReady => (),
|
|
|
|
}
|
2016-02-16 21:52:55 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
match self.commands.poll().unwrap() {
|
|
|
|
Async::Ready(Some(command)) => {
|
|
|
|
progress = true;
|
|
|
|
self.handle_command(command);
|
|
|
|
}
|
|
|
|
Async::Ready(None) => (),
|
|
|
|
Async::NotReady => (),
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
|
|
|
match self.end_of_track.poll() {
|
|
|
|
Ok(Async::Ready(())) => {
|
|
|
|
progress = true;
|
|
|
|
self.handle_end_of_track();
|
|
|
|
}
|
|
|
|
Ok(Async::NotReady) => (),
|
2018-02-11 17:52:53 +00:00
|
|
|
Err(oneshot::Canceled) => self.end_of_track = Box::new(future::empty()),
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2018-10-12 17:15:26 +00:00
|
|
|
|
|
|
|
match self.context_fut.poll() {
|
|
|
|
Ok(Async::Ready(value)) => {
|
2019-03-13 19:35:46 +00:00
|
|
|
let r_context = serde_json::from_value::<StationContext>(value.clone());
|
|
|
|
self.context = match r_context {
|
|
|
|
Ok(context) => {
|
|
|
|
info!(
|
|
|
|
"Resolved {:?} tracks from <{:?}>",
|
|
|
|
context.tracks.len(),
|
|
|
|
self.state.get_context_uri(),
|
|
|
|
);
|
|
|
|
Some(context)
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
error!("Unable to parse JSONContext {:?}\n{:?}", e, value);
|
|
|
|
None
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// It needn't be so verbose - can be as simple as
|
|
|
|
// if let Some(ref context) = r_context {
|
|
|
|
// info!("Got {:?} tracks from <{}>", context.tracks.len(), context.uri);
|
|
|
|
// }
|
|
|
|
// self.context = r_context;
|
2018-10-12 17:15:26 +00:00
|
|
|
|
|
|
|
progress = true;
|
|
|
|
self.context_fut = Box::new(future::empty());
|
|
|
|
}
|
|
|
|
Ok(Async::NotReady) => (),
|
|
|
|
Err(err) => {
|
|
|
|
self.context_fut = Box::new(future::empty());
|
2019-03-13 19:35:46 +00:00
|
|
|
error!("ContextError: {:?}", err)
|
2018-10-12 17:15:26 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-20 12:56:42 +00:00
|
|
|
}
|
2016-02-17 14:24:40 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
let poll_sender = self.sender.poll_complete().unwrap();
|
2016-02-17 19:35:31 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
// Only shutdown once we've flushed out all our messages
|
|
|
|
if self.shutdown && poll_sender.is_ready() {
|
|
|
|
return Ok(Async::Ready(()));
|
|
|
|
}
|
2016-02-17 19:35:52 +00:00
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
if !progress {
|
|
|
|
return Ok(Async::NotReady);
|
|
|
|
}
|
|
|
|
}
|
2016-02-17 19:35:52 +00:00
|
|
|
}
|
2016-01-20 15:47:05 +00:00
|
|
|
}
|
|
|
|
|
2017-01-20 12:56:42 +00:00
|
|
|
impl SpircTask {
|
|
|
|
fn handle_command(&mut self, cmd: SpircCommand) {
|
2017-02-23 11:05:32 +00:00
|
|
|
let active = self.device.get_is_active();
|
2017-01-20 12:56:42 +00:00
|
|
|
match cmd {
|
2017-02-23 11:05:32 +00:00
|
|
|
SpircCommand::Play => {
|
|
|
|
if active {
|
|
|
|
self.handle_play();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypePlay).send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircCommand::PlayPause => {
|
|
|
|
if active {
|
|
|
|
self.handle_play_pause();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypePlayPause).send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircCommand::Pause => {
|
|
|
|
if active {
|
|
|
|
self.handle_pause();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypePause).send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircCommand::Prev => {
|
|
|
|
if active {
|
|
|
|
self.handle_prev();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypePrev).send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircCommand::Next => {
|
|
|
|
if active {
|
|
|
|
self.handle_next();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeNext).send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircCommand::VolumeUp => {
|
|
|
|
if active {
|
|
|
|
self.handle_volume_up();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeVolumeUp).send();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SpircCommand::VolumeDown => {
|
|
|
|
if active {
|
|
|
|
self.handle_volume_down();
|
|
|
|
self.notify(None);
|
|
|
|
} else {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeVolumeDown).send();
|
|
|
|
}
|
|
|
|
}
|
2017-01-20 12:56:42 +00:00
|
|
|
SpircCommand::Shutdown => {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeGoodbye).send();
|
|
|
|
self.shutdown = true;
|
|
|
|
self.commands.close();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_frame(&mut self, frame: Frame) {
|
2018-02-11 17:52:53 +00:00
|
|
|
debug!(
|
|
|
|
"{:?} {:?} {} {} {}",
|
|
|
|
frame.get_typ(),
|
|
|
|
frame.get_device_state().get_name(),
|
|
|
|
frame.get_ident(),
|
|
|
|
frame.get_seq_nr(),
|
|
|
|
frame.get_state_update_id()
|
|
|
|
);
|
|
|
|
|
|
|
|
if frame.get_ident() == self.ident
|
|
|
|
|| (frame.get_recipient().len() > 0 && !frame.get_recipient().contains(&self.ident))
|
|
|
|
{
|
2016-01-20 15:47:05 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2015-07-08 19:50:44 +00:00
|
|
|
match frame.get_typ() {
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeHello => {
|
2017-01-29 14:11:20 +00:00
|
|
|
self.notify(Some(frame.get_ident()));
|
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::kMessageTypeLoad => {
|
2017-01-20 14:44:13 +00:00
|
|
|
if !self.device.get_is_active() {
|
|
|
|
self.device.set_is_active(true);
|
|
|
|
self.device.set_became_active_at(now_ms());
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
self.update_tracks(&frame);
|
|
|
|
|
|
|
|
if self.state.get_track().len() > 0 {
|
2018-03-23 05:13:01 +00:00
|
|
|
self.state.set_position_ms(frame.get_state().get_position_ms());
|
2017-01-29 14:11:20 +00:00
|
|
|
self.state.set_position_measured_at(now_ms() as u64);
|
|
|
|
|
2016-08-23 21:43:55 +00:00
|
|
|
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
2017-01-29 14:11:20 +00:00
|
|
|
self.load_track(play);
|
2016-08-23 21:43:55 +00:00
|
|
|
} else {
|
2018-10-12 17:15:26 +00:00
|
|
|
info!("No more tracks left in queue");
|
2017-01-29 14:11:20 +00:00
|
|
|
self.state.set_status(PlayStatus::kPlayStatusStop);
|
2016-08-23 21:43:55 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +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();
|
|
|
|
self.notify(None);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
MessageType::kMessageTypePlayPause => {
|
|
|
|
self.handle_play_pause();
|
2017-01-29 14:11:20 +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();
|
2017-01-29 14:11:20 +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();
|
2017-01-29 14:11:20 +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();
|
|
|
|
self.notify(None);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
MessageType::kMessageTypeVolumeUp => {
|
|
|
|
self.handle_volume_up();
|
|
|
|
self.notify(None);
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageType::kMessageTypeVolumeDown => {
|
|
|
|
self.handle_volume_down();
|
2017-01-29 14:11:20 +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 => {
|
|
|
|
self.state.set_repeat(frame.get_state().get_repeat());
|
|
|
|
self.notify(None);
|
|
|
|
}
|
|
|
|
|
2017-11-03 01:15:27 +00:00
|
|
|
MessageType::kMessageTypeShuffle => {
|
|
|
|
self.state.set_shuffle(frame.get_state().get_shuffle());
|
2018-02-11 17:52:53 +00:00
|
|
|
if self.state.get_shuffle() {
|
2017-11-03 01:15:27 +00:00
|
|
|
let current_index = self.state.get_playing_track_index();
|
|
|
|
{
|
|
|
|
let tracks = self.state.mut_track();
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
self.state.set_playing_track_index(0);
|
|
|
|
} else {
|
|
|
|
let context = self.state.get_context_uri();
|
|
|
|
debug!("{:?}", context);
|
|
|
|
}
|
|
|
|
self.notify(None);
|
|
|
|
}
|
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeSeek => {
|
2017-01-29 14:11:20 +00:00
|
|
|
let position = frame.get_position();
|
|
|
|
|
|
|
|
self.state.set_position_ms(position);
|
|
|
|
self.state.set_position_measured_at(now_ms() as u64);
|
|
|
|
self.player.seek(position);
|
|
|
|
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 => {
|
2017-01-29 14:11:20 +00:00
|
|
|
self.update_tracks(&frame);
|
|
|
|
self.notify(None);
|
2016-01-16 01:15:24 +00:00
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
|
|
|
MessageType::kMessageTypeVolume => {
|
2018-05-17 01:15:17 +00:00
|
|
|
self.set_volume(frame.get_volume() as u16);
|
2017-01-29 14:11:20 +00:00
|
|
|
self.notify(None);
|
|
|
|
}
|
|
|
|
|
2016-02-18 23:02:41 +00:00
|
|
|
MessageType::kMessageTypeNotify => {
|
2018-02-11 17:52:53 +00:00
|
|
|
if self.device.get_is_active() && frame.get_device_state().get_is_active() {
|
2017-01-20 14:44:13 +00:00
|
|
|
self.device.set_is_active(false);
|
2017-01-29 14:11:20 +00:00
|
|
|
self.state.set_status(PlayStatus::kPlayStatusStop);
|
2016-01-20 14:11:49 +00:00
|
|
|
self.player.stop();
|
2017-02-21 22:46:19 +00:00
|
|
|
self.mixer.stop();
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2016-01-02 15:19:39 +00:00
|
|
|
_ => (),
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-23 11:05:32 +00:00
|
|
|
fn handle_play(&mut self) {
|
|
|
|
if self.state.get_status() == PlayStatus::kPlayStatusPause {
|
|
|
|
self.mixer.start();
|
|
|
|
self.player.play();
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
|
|
|
self.state.set_position_measured_at(now_ms() as u64);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_play_pause(&mut self) {
|
|
|
|
match self.state.get_status() {
|
|
|
|
PlayStatus::kPlayStatusPlay => self.handle_pause(),
|
|
|
|
PlayStatus::kPlayStatusPause => self.handle_play(),
|
|
|
|
_ => (),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_pause(&mut self) {
|
|
|
|
if self.state.get_status() == PlayStatus::kPlayStatusPlay {
|
|
|
|
self.player.pause();
|
|
|
|
self.mixer.stop();
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
|
|
|
|
|
|
|
let now = now_ms() as u64;
|
|
|
|
let position = self.state.get_position_ms();
|
|
|
|
|
|
|
|
let diff = now - self.state.get_position_measured_at();
|
|
|
|
|
|
|
|
self.state.set_position_ms(position + diff as u32);
|
|
|
|
self.state.set_position_measured_at(now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
let current_index = self.state.get_playing_track_index() as usize;
|
|
|
|
if self.state.get_track()[current_index].get_queued() {
|
|
|
|
self.state.mut_track().remove(current_index);
|
|
|
|
return current_index;
|
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
|
|
|
current_index + 1
|
|
|
|
}
|
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
|
|
|
fn handle_next(&mut self) {
|
|
|
|
let mut new_index = self.consume_queued_track() as u32;
|
|
|
|
let mut continue_playing = true;
|
2018-10-12 17:15:26 +00:00
|
|
|
debug!(
|
|
|
|
"At track {:?} of {:?} <{:?}> update [{}]",
|
|
|
|
new_index,
|
|
|
|
self.state.get_track().len(),
|
|
|
|
self.state.get_context_uri(),
|
|
|
|
self.state.get_track().len() as u32 - new_index < 5
|
|
|
|
);
|
|
|
|
let context_uri = self.state.get_context_uri().to_owned();
|
2019-03-13 19:35:46 +00:00
|
|
|
if (context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:"))
|
|
|
|
&& ((self.state.get_track().len() as u32) - new_index) < 5
|
|
|
|
{
|
2018-10-12 17:15:26 +00:00
|
|
|
self.context_fut = self.resolve_station(&context_uri);
|
|
|
|
self.update_tracks_from_context();
|
|
|
|
}
|
|
|
|
|
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
|
|
|
if new_index >= self.state.get_track().len() as u32 {
|
|
|
|
new_index = 0; // Loop around back to start
|
|
|
|
continue_playing = self.state.get_repeat();
|
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
self.state.set_playing_track_index(new_index);
|
|
|
|
self.state.set_position_ms(0);
|
|
|
|
self.state.set_position_measured_at(now_ms() as u64);
|
|
|
|
|
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
|
|
|
self.load_track(continue_playing);
|
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();
|
|
|
|
let tracks = self.state.mut_track();
|
|
|
|
while queue_index < tracks.len() && tracks[queue_index].get_queued() {
|
|
|
|
queue_tracks.push(tracks.remove(queue_index));
|
|
|
|
}
|
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
let current_index = self.state.get_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
|
|
|
|
} else if self.state.get_repeat() {
|
2017-02-23 11:05:32 +00:00
|
|
|
self.state.get_track().len() as u32 - 1
|
|
|
|
} 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;
|
|
|
|
for track in queue_tracks.into_iter() {
|
|
|
|
self.state.mut_track().insert(pos, track);
|
|
|
|
pos += 1;
|
|
|
|
}
|
2017-02-23 11:05:32 +00:00
|
|
|
|
|
|
|
self.state.set_playing_track_index(new_index);
|
|
|
|
self.state.set_position_ms(0);
|
|
|
|
self.state.set_position_measured_at(now_ms() as u64);
|
|
|
|
|
|
|
|
self.load_track(true);
|
|
|
|
} else {
|
|
|
|
self.state.set_position_ms(0);
|
|
|
|
self.state.set_position_measured_at(now_ms() as u64);
|
|
|
|
self.player.seek(0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_volume_up(&mut self) {
|
2018-01-25 22:37:28 +00:00
|
|
|
let mut volume: u32 = self.device.get_volume() as u32 + 4096;
|
2017-02-23 11:05:32 +00:00
|
|
|
if volume > 0xFFFF {
|
|
|
|
volume = 0xFFFF;
|
|
|
|
}
|
2018-05-17 01:15:17 +00:00
|
|
|
self.set_volume(volume as u16);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn handle_volume_down(&mut self) {
|
2018-01-25 22:37:28 +00:00
|
|
|
let mut volume: i32 = self.device.get_volume() as i32 - 4096;
|
2017-02-23 11:05:32 +00:00
|
|
|
if volume < 0 {
|
|
|
|
volume = 0;
|
|
|
|
}
|
2018-05-17 01:15:17 +00:00
|
|
|
self.set_volume(volume as u16);
|
2017-02-23 11:05:32 +00:00
|
|
|
}
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn handle_end_of_track(&mut self) {
|
2018-02-09 01:05:50 +00:00
|
|
|
self.handle_next();
|
2017-01-29 14:11:20 +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 {
|
|
|
|
let diff = now_ms() as u64 - self.state.get_position_measured_at();
|
|
|
|
self.state.get_position_ms() + diff as u32
|
2015-07-08 19:50:44 +00:00
|
|
|
}
|
|
|
|
|
2018-10-12 17:15:26 +00:00
|
|
|
fn resolve_station(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
|
|
|
let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri);
|
|
|
|
|
|
|
|
self.resolve_uri(&radio_uri)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn resolve_uri(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
|
|
|
let request = self.session.mercury().get(uri);
|
|
|
|
|
|
|
|
Box::new(request.and_then(move |response| {
|
|
|
|
let data = response.payload.first().expect("Empty payload on context uri");
|
|
|
|
let response: serde_json::Value = serde_json::from_slice(&data).unwrap();
|
|
|
|
|
|
|
|
Ok(response)
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn update_tracks_from_context(&mut self) {
|
|
|
|
if let Some(ref context) = self.context {
|
|
|
|
self.context_fut = self.resolve_uri(&context.next_page_url);
|
|
|
|
|
|
|
|
let new_tracks = &context.tracks;
|
|
|
|
debug!("Adding {:?} tracks from context to playlist", new_tracks.len());
|
|
|
|
// Can we just push the new tracks and forget it?
|
|
|
|
let tracks = self.state.mut_track();
|
|
|
|
// tracks.append(new_tracks.to_owned());
|
|
|
|
for t in new_tracks {
|
|
|
|
tracks.push(t.to_owned());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-29 16:25:09 +00:00
|
|
|
fn update_tracks(&mut self, frame: &protocol::spirc::Frame) {
|
2017-01-29 14:11:20 +00:00
|
|
|
let index = frame.get_state().get_playing_track_index();
|
2018-02-17 15:17:05 +00:00
|
|
|
let context_uri = frame.get_state().get_context_uri().to_owned();
|
2018-10-12 17:15:26 +00:00
|
|
|
let tracks = frame.get_state().get_track();
|
|
|
|
debug!("Frame has {:?} tracks", tracks.len());
|
2019-03-13 19:35:46 +00:00
|
|
|
if context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:") {
|
2018-10-12 17:15:26 +00:00
|
|
|
self.context_fut = self.resolve_station(&context_uri);
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
|
|
|
self.state.set_playing_track_index(index);
|
2017-01-29 16:25:09 +00:00
|
|
|
self.state.set_track(tracks.into_iter().cloned().collect());
|
2018-02-17 15:17:05 +00:00
|
|
|
self.state.set_context_uri(context_uri);
|
2018-02-17 15:59:04 +00:00
|
|
|
self.state.set_repeat(frame.get_state().get_repeat());
|
|
|
|
self.state.set_shuffle(frame.get_state().get_shuffle());
|
2016-01-20 15:47:05 +00:00
|
|
|
}
|
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn load_track(&mut self, play: bool) {
|
|
|
|
let track = {
|
2019-03-15 07:26:58 +00:00
|
|
|
let mut index = self.state.get_playing_track_index();
|
|
|
|
// Check for malformed gid
|
|
|
|
let tracks_len = self.state.get_track().len() as u32;
|
|
|
|
let mut track_ref = &self.state.get_track()[index as usize];
|
|
|
|
while track_ref.get_gid().len() != 16 {
|
2019-03-13 19:35:46 +00:00
|
|
|
warn!(
|
|
|
|
"Skipping track {:?} at position [{}] of {}",
|
|
|
|
track_ref.get_uri(),
|
|
|
|
index,
|
2019-03-15 07:26:58 +00:00
|
|
|
tracks_len
|
2019-03-13 19:35:46 +00:00
|
|
|
);
|
2019-03-15 07:26:58 +00:00
|
|
|
index = if index + 1 < tracks_len { index + 1 } else { 0 };
|
|
|
|
track_ref = &self.state.get_track()[index as usize];
|
2019-03-13 19:35:46 +00:00
|
|
|
}
|
2019-03-15 07:26:58 +00:00
|
|
|
SpotifyId::from_raw(track_ref.get_gid()).unwrap()
|
2017-01-29 14:11:20 +00:00
|
|
|
};
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2019-03-15 07:26:58 +00:00
|
|
|
let position = self.state.get_position_ms();
|
2017-01-29 14:11:20 +00:00
|
|
|
let end_of_track = self.player.load(track, play, position);
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-01-29 16:25:09 +00:00
|
|
|
if play {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
|
|
|
} else {
|
|
|
|
self.state.set_status(PlayStatus::kPlayStatusPause);
|
|
|
|
}
|
|
|
|
|
2018-01-21 19:29:31 +00:00
|
|
|
self.end_of_track = Box::new(end_of_track);
|
2017-01-29 14:11:20 +00:00
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn hello(&mut self) {
|
|
|
|
CommandSender::new(self, MessageType::kMessageTypeHello).send();
|
|
|
|
}
|
2015-07-08 19:50:44 +00:00
|
|
|
|
2017-01-29 14:11:20 +00:00
|
|
|
fn notify(&mut self, recipient: Option<&str>) {
|
|
|
|
let mut cs = CommandSender::new(self, MessageType::kMessageTypeNotify);
|
|
|
|
if let Some(s) = recipient {
|
|
|
|
cs = cs.recipient(&s);
|
|
|
|
}
|
|
|
|
cs.send();
|
|
|
|
}
|
2018-05-17 01:15:17 +00:00
|
|
|
|
|
|
|
fn set_volume(&mut self, volume: u16) {
|
|
|
|
self.device.set_volume(volume as u32);
|
|
|
|
self.mixer.set_volume(volume_to_mixer(volume, self.linear_volume));
|
|
|
|
if let Some(cache) = self.session.cache() {
|
|
|
|
cache.save_volume(Volume { volume })
|
|
|
|
}
|
|
|
|
}
|
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) {
|
|
|
|
debug!("drop Spirc[{}]", self.session.session_id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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> {
|
2017-01-20 12:56:42 +00:00
|
|
|
fn new(spirc: &'a mut SpircTask, cmd: MessageType) -> CommandSender {
|
2018-02-17 09:15:09 +00:00
|
|
|
let mut frame = protocol::spirc::Frame::new();
|
|
|
|
frame.set_version(1);
|
|
|
|
frame.set_protocol_version(::std::convert::Into::into("2.0.0"));
|
|
|
|
frame.set_ident(spirc.ident.clone());
|
|
|
|
frame.set_seq_nr(spirc.sequence.get());
|
|
|
|
frame.set_typ(cmd);
|
|
|
|
frame.set_device_state(spirc.device.clone());
|
|
|
|
frame.set_state_update_id(now_ms());
|
2016-02-17 14:24:40 +00:00
|
|
|
CommandSender {
|
2017-01-20 12:56:42 +00:00
|
|
|
spirc: spirc,
|
2017-02-21 21:49:45 +00:00
|
|
|
frame: frame,
|
2016-02-16 21:25:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-21 21:49:45 +00:00
|
|
|
fn recipient(mut self, recipient: &'a str) -> CommandSender {
|
|
|
|
self.frame.mut_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)]
|
2017-02-21 21:49:45 +00:00
|
|
|
fn state(mut self, state: protocol::spirc::State) -> CommandSender<'a> {
|
|
|
|
self.frame.set_state(state);
|
2016-02-17 14:24:40 +00:00
|
|
|
self
|
|
|
|
}
|
2017-01-29 14:11:20 +00:00
|
|
|
|
2017-02-21 21:49:45 +00:00
|
|
|
fn send(mut self) {
|
2017-02-21 22:46:19 +00:00
|
|
|
if !self.frame.has_state() && self.spirc.device.get_is_active() {
|
|
|
|
self.frame.set_state(self.spirc.state.clone());
|
2016-02-17 14:24:40 +00:00
|
|
|
}
|
|
|
|
|
2017-02-21 22:46:19 +00:00
|
|
|
let send = self.spirc.sender.start_send(self.frame).unwrap();
|
|
|
|
assert!(send.is_ready());
|
2016-02-17 14:24:40 +00:00
|
|
|
}
|
2016-02-16 21:25:55 +00:00
|
|
|
}
|