This commit is contained in:
Sean McNamara 2020-04-06 21:06:26 -04:00
commit a55b226716
10 changed files with 1493 additions and 479 deletions

View file

@ -5,18 +5,15 @@ rust:
- beta - beta
- nightly - nightly
cache: cargo # Need to cache the whole `.cargo` directory to keep .crates.toml for
# Reduce cache bloat # cargo-update to work
cache:
directories:
- /home/travis/.cargo
# But don't cache the cargo registry
before_cache: before_cache:
- rm -rfv "$TRAVIS_HOME/.cargo/registry/src" - rm -rf /home/travis/.cargo/registry
- rm -rfv target/debug/incremental/{librespot,build_script_build}-*
- rm -rfv target/debug/.fingerprint/librespot-*
- rm -rfv target/debug/build/librespot-*
- rm -rfv target/debug/deps/liblibrespot-*
- rm -rfv target/debug/deps/librespot-*
- rm -rfv target/debug/{librespot,liblibrespot}.d
- rm -rfv target/debug/incremental/{build_script_build,librespot,librespot_core,librespot_connect,librespot_audio,librespot_metadata,librespot_playback,librespot_player,librespot_protocol}-*
- cargo clean -p librespot -p librespot-core -p librespot-connect -p librespot-audio -p librespot-metadata -p librespot-playback
addons: addons:
apt: apt:

View file

@ -144,6 +144,15 @@ impl StreamLoaderController {
} }
} }
pub fn range_to_end_available(&self) -> bool {
if let Some(ref shared) = self.stream_shared {
let read_position = shared.read_position.load(atomic::Ordering::Relaxed);
self.range_available(Range::new(read_position, self.len() - read_position))
} else {
true
}
}
pub fn ping_time_ms(&self) -> usize { pub fn ping_time_ms(&self) -> usize {
if let Some(ref shared) = self.stream_shared { if let Some(ref shared) = self.stream_shared {
return shared.ping_time_ms.load(atomic::Ordering::Relaxed); return shared.ping_time_ms.load(atomic::Ordering::Relaxed);

View file

@ -2,7 +2,7 @@ use std;
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use futures::future; use futures::future;
use futures::sync::{mpsc, oneshot}; use futures::sync::mpsc;
use futures::{Async, Future, Poll, Sink, Stream}; use futures::{Async, Future, Poll, Sink, Stream};
use protobuf::{self, Message}; use protobuf::{self, Message};
use rand; use rand;
@ -11,7 +11,7 @@ use serde_json;
use crate::context::StationContext; use crate::context::StationContext;
use crate::playback::mixer::Mixer; use crate::playback::mixer::Mixer;
use crate::playback::player::Player; use crate::playback::player::{Player, PlayerEvent, PlayerEventChannel};
use crate::protocol; use crate::protocol;
use crate::protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef}; use crate::protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
use librespot_core::config::ConnectConfig; use librespot_core::config::ConnectConfig;
@ -22,6 +22,24 @@ use librespot_core::util::SeqGenerator;
use librespot_core::version; use librespot_core::version;
use librespot_core::volume::Volume; use librespot_core::volume::Volume;
enum SpircPlayStatus {
Stopped,
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,
},
}
pub struct SpircTask { pub struct SpircTask {
player: Player, player: Player,
mixer: Box<dyn Mixer>, mixer: Box<dyn Mixer>,
@ -32,11 +50,14 @@ pub struct SpircTask {
ident: String, ident: String,
device: DeviceState, device: DeviceState,
state: State, state: State,
play_request_id: Option<u64>,
mixer_started: bool,
play_status: SpircPlayStatus,
subscription: Box<dyn Stream<Item = Frame, Error = MercuryError>>, subscription: Box<dyn Stream<Item = Frame, Error = MercuryError>>,
sender: Box<dyn Sink<SinkItem = Frame, SinkError = MercuryError>>, sender: Box<dyn Sink<SinkItem = Frame, SinkError = MercuryError>>,
commands: mpsc::UnboundedReceiver<SpircCommand>, commands: mpsc::UnboundedReceiver<SpircCommand>,
end_of_track: Box<dyn Future<Item = (), Error = oneshot::Canceled>>, player_events: PlayerEventChannel,
shutdown: bool, shutdown: bool,
session: Session, session: Session,
@ -255,6 +276,8 @@ impl Spirc {
}; };
let device = initial_device_state(config); let device = initial_device_state(config);
let player_events = player.get_player_event_channel();
let mut task = SpircTask { let mut task = SpircTask {
player: player, player: player,
mixer: mixer, mixer: mixer,
@ -266,11 +289,14 @@ impl Spirc {
device: device, device: device,
state: initial_state(), state: initial_state(),
play_request_id: None,
mixer_started: false,
play_status: SpircPlayStatus::Stopped,
subscription: subscription, subscription: subscription,
sender: sender, sender: sender,
commands: cmd_rx, commands: cmd_rx,
end_of_track: Box::new(future::empty()), player_events: player_events,
shutdown: false, shutdown: false,
session: session.clone(), session: session.clone(),
@ -350,13 +376,14 @@ impl Future for SpircTask {
Async::NotReady => (), Async::NotReady => (),
} }
match self.end_of_track.poll() { match self.player_events.poll() {
Ok(Async::Ready(())) => {
progress = true;
self.handle_end_of_track();
}
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
Err(oneshot::Canceled) => self.end_of_track = Box::new(future::empty()), Ok(Async::Ready(None)) => (),
Err(_) => (),
Ok(Async::Ready(Some(event))) => {
progress = true;
self.handle_player_event(event);
}
} }
// TODO: Refactor // TODO: Refactor
match self.context_fut.poll() { match self.context_fut.poll() {
@ -431,13 +458,33 @@ impl SpircTask {
+ (dur.subsec_nanos() / 1000_000) as i64) + (dur.subsec_nanos() / 1000_000) as i64)
} }
fn ensure_mixer_started(&mut self) {
if !self.mixer_started {
self.mixer.start();
self.mixer_started = true;
}
}
fn ensure_mixer_stopped(&mut self) {
if self.mixer_started {
self.mixer.stop();
self.mixer_started = false;
}
}
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);
}
fn handle_command(&mut self, cmd: SpircCommand) { fn handle_command(&mut self, cmd: SpircCommand) {
let active = self.device.get_is_active(); let active = self.device.get_is_active();
match cmd { match cmd {
SpircCommand::Play => { SpircCommand::Play => {
if active { if active {
self.handle_play(); self.handle_play();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypePlay).send(); CommandSender::new(self, MessageType::kMessageTypePlay).send();
} }
@ -445,7 +492,7 @@ impl SpircTask {
SpircCommand::PlayPause => { SpircCommand::PlayPause => {
if active { if active {
self.handle_play_pause(); self.handle_play_pause();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypePlayPause).send(); CommandSender::new(self, MessageType::kMessageTypePlayPause).send();
} }
@ -453,7 +500,7 @@ impl SpircTask {
SpircCommand::Pause => { SpircCommand::Pause => {
if active { if active {
self.handle_pause(); self.handle_pause();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypePause).send(); CommandSender::new(self, MessageType::kMessageTypePause).send();
} }
@ -461,7 +508,7 @@ impl SpircTask {
SpircCommand::Prev => { SpircCommand::Prev => {
if active { if active {
self.handle_prev(); self.handle_prev();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypePrev).send(); CommandSender::new(self, MessageType::kMessageTypePrev).send();
} }
@ -469,7 +516,7 @@ impl SpircTask {
SpircCommand::Next => { SpircCommand::Next => {
if active { if active {
self.handle_next(); self.handle_next();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypeNext).send(); CommandSender::new(self, MessageType::kMessageTypeNext).send();
} }
@ -477,7 +524,7 @@ impl SpircTask {
SpircCommand::VolumeUp => { SpircCommand::VolumeUp => {
if active { if active {
self.handle_volume_up(); self.handle_volume_up();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypeVolumeUp).send(); CommandSender::new(self, MessageType::kMessageTypeVolumeUp).send();
} }
@ -485,7 +532,7 @@ impl SpircTask {
SpircCommand::VolumeDown => { SpircCommand::VolumeDown => {
if active { if active {
self.handle_volume_down(); self.handle_volume_down();
self.notify(None); self.notify(None, true);
} else { } else {
CommandSender::new(self, MessageType::kMessageTypeVolumeDown).send(); CommandSender::new(self, MessageType::kMessageTypeVolumeDown).send();
} }
@ -498,14 +545,122 @@ impl SpircTask {
} }
} }
fn handle_player_event(&mut self, event: PlayerEvent) {
// 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(),
PlayerEvent::Loading { .. } => self.notify(None, false),
PlayerEvent::Playing { position_ms, .. } => {
let new_nominal_start_time = self.now_ms() - position_ms as i64;
match self.play_status {
SpircPlayStatus::Playing {
ref mut nominal_start_time,
..
} => {
if (*nominal_start_time - new_nominal_start_time).abs() > 100 {
*nominal_start_time = new_nominal_start_time;
self.update_state_position(position_ms);
self.notify(None, true);
}
}
SpircPlayStatus::LoadingPlay { .. }
| SpircPlayStatus::LoadingPause { .. } => {
self.state.set_status(PlayStatus::kPlayStatusPlay);
self.update_state_position(position_ms);
self.notify(None, true);
self.play_status = SpircPlayStatus::Playing {
nominal_start_time: new_nominal_start_time,
preloading_of_next_track_triggered: false,
};
}
_ => (),
};
trace!("==> kPlayStatusPlay");
}
PlayerEvent::Paused {
position_ms: new_position_ms,
..
} => {
match self.play_status {
SpircPlayStatus::Paused {
ref mut position_ms,
..
} => {
if *position_ms != new_position_ms {
*position_ms = new_position_ms;
self.update_state_position(new_position_ms);
self.notify(None, true);
}
}
SpircPlayStatus::LoadingPlay { .. }
| SpircPlayStatus::LoadingPause { .. } => {
self.state.set_status(PlayStatus::kPlayStatusPause);
self.update_state_position(new_position_ms);
self.notify(None, true);
self.play_status = SpircPlayStatus::Paused {
position_ms: new_position_ms,
preloading_of_next_track_triggered: false,
};
}
_ => (),
}
trace!("==> kPlayStatusPause");
}
PlayerEvent::Stopped { .. } => match self.play_status {
SpircPlayStatus::Stopped => (),
_ => {
warn!("The player has stopped unexpectedly.");
self.state.set_status(PlayStatus::kPlayStatusStop);
self.ensure_mixer_stopped();
self.notify(None, true);
self.play_status = SpircPlayStatus::Stopped;
}
},
PlayerEvent::TimeToPreloadNextTrack { .. } => 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;
if let Some(track_id) = self.preview_next_track() {
self.player.preload(track_id);
}
}
SpircPlayStatus::LoadingPause { .. }
| SpircPlayStatus::LoadingPlay { .. }
| SpircPlayStatus::Stopped => (),
},
_ => (),
}
}
}
}
fn handle_frame(&mut self, frame: Frame) { fn handle_frame(&mut self, frame: Frame) {
let state_string = match frame.get_state().get_status() {
PlayStatus::kPlayStatusLoading => "kPlayStatusLoading",
PlayStatus::kPlayStatusPause => "kPlayStatusPause",
PlayStatus::kPlayStatusStop => "kPlayStatusStop",
PlayStatus::kPlayStatusPlay => "kPlayStatusPlay",
};
debug!( debug!(
"{:?} {:?} {} {} {}", "{:?} {:?} {} {} {} {}",
frame.get_typ(), frame.get_typ(),
frame.get_device_state().get_name(), frame.get_device_state().get_name(),
frame.get_ident(), frame.get_ident(),
frame.get_seq_nr(), frame.get_seq_nr(),
frame.get_state_update_id() frame.get_state_update_id(),
state_string,
); );
if frame.get_ident() == self.ident if frame.get_ident() == self.ident
@ -516,7 +671,7 @@ impl SpircTask {
match frame.get_typ() { match frame.get_typ() {
MessageType::kMessageTypeHello => { MessageType::kMessageTypeHello => {
self.notify(Some(frame.get_ident())); self.notify(Some(frame.get_ident()), true);
} }
MessageType::kMessageTypeLoad => { MessageType::kMessageTypeLoad => {
@ -529,61 +684,58 @@ impl SpircTask {
self.update_tracks(&frame); self.update_tracks(&frame);
if self.state.get_track().len() > 0 { if self.state.get_track().len() > 0 {
let now = self.now_ms(); let start_playing =
self.state frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
.set_position_ms(frame.get_state().get_position_ms()); self.load_track(start_playing, frame.get_state().get_position_ms());
self.state.set_position_measured_at(now as u64);
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
self.load_track(play);
} else { } else {
info!("No more tracks left in queue"); info!("No more tracks left in queue");
self.state.set_status(PlayStatus::kPlayStatusStop); self.state.set_status(PlayStatus::kPlayStatusStop);
self.player.stop(); self.player.stop();
self.mixer.stop(); self.mixer.stop();
self.play_status = SpircPlayStatus::Stopped;
} }
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypePlay => { MessageType::kMessageTypePlay => {
self.handle_play(); self.handle_play();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypePlayPause => { MessageType::kMessageTypePlayPause => {
self.handle_play_pause(); self.handle_play_pause();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypePause => { MessageType::kMessageTypePause => {
self.handle_pause(); self.handle_pause();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeNext => { MessageType::kMessageTypeNext => {
self.handle_next(); self.handle_next();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypePrev => { MessageType::kMessageTypePrev => {
self.handle_prev(); self.handle_prev();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeVolumeUp => { MessageType::kMessageTypeVolumeUp => {
self.handle_volume_up(); self.handle_volume_up();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeVolumeDown => { MessageType::kMessageTypeVolumeDown => {
self.handle_volume_down(); self.handle_volume_down();
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeRepeat => { MessageType::kMessageTypeRepeat => {
self.state.set_repeat(frame.get_state().get_repeat()); self.state.set_repeat(frame.get_state().get_repeat());
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeShuffle => { MessageType::kMessageTypeShuffle => {
@ -603,27 +755,38 @@ impl SpircTask {
let context = self.state.get_context_uri(); let context = self.state.get_context_uri();
debug!("{:?}", context); debug!("{:?}", context);
} }
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeSeek => { MessageType::kMessageTypeSeek => {
let position = frame.get_position(); self.handle_seek(frame.get_position());
self.notify(None, true);
let now = self.now_ms();
self.state.set_position_ms(position);
self.state.set_position_measured_at(now as u64);
self.player.seek(position);
self.notify(None);
} }
MessageType::kMessageTypeReplace => { MessageType::kMessageTypeReplace => {
self.update_tracks(&frame); self.update_tracks(&frame);
self.notify(None); self.notify(None, true);
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 {
if let Some(track_id) = self.preview_next_track() {
self.player.preload(track_id);
}
}
}
} }
MessageType::kMessageTypeVolume => { MessageType::kMessageTypeVolume => {
self.set_volume(frame.get_volume() as u16); self.set_volume(frame.get_volume() as u16);
self.notify(None); self.notify(None, true);
} }
MessageType::kMessageTypeNotify => { MessageType::kMessageTypeNotify => {
@ -631,7 +794,8 @@ impl SpircTask {
self.device.set_is_active(false); self.device.set_is_active(false);
self.state.set_status(PlayStatus::kPlayStatusStop); self.state.set_status(PlayStatus::kPlayStatusStop);
self.player.stop(); self.player.stop();
self.mixer.stop(); self.ensure_mixer_stopped();
self.play_status = SpircPlayStatus::Stopped;
} }
} }
@ -640,39 +804,87 @@ impl SpircTask {
} }
fn handle_play(&mut self) { fn handle_play(&mut self) {
if self.state.get_status() == PlayStatus::kPlayStatusPause { match self.play_status {
self.mixer.start(); SpircPlayStatus::Paused {
self.player.play(); position_ms,
self.state.set_status(PlayStatus::kPlayStatusPlay); preloading_of_next_track_triggered,
let now = self.now_ms(); } => {
self.state.set_position_measured_at(now as u64); self.ensure_mixer_started();
self.player.play();
self.state.set_status(PlayStatus::kPlayStatusPlay);
self.update_state_position(position_ms);
self.play_status = SpircPlayStatus::Playing {
nominal_start_time: self.now_ms() as i64 - position_ms as i64,
preloading_of_next_track_triggered,
};
}
SpircPlayStatus::LoadingPause { position_ms } => {
self.ensure_mixer_started();
self.player.play();
self.play_status = SpircPlayStatus::LoadingPlay { position_ms };
}
_ => (),
} }
} }
fn handle_play_pause(&mut self) { fn handle_play_pause(&mut self) {
match self.state.get_status() { match self.play_status {
PlayStatus::kPlayStatusPlay => self.handle_pause(), SpircPlayStatus::Paused { .. } | SpircPlayStatus::LoadingPause { .. } => {
PlayStatus::kPlayStatusPause => self.handle_play(), self.handle_play()
}
SpircPlayStatus::Playing { .. } | SpircPlayStatus::LoadingPlay { .. } => {
self.handle_play()
}
_ => (), _ => (),
} }
} }
fn handle_pause(&mut self) { fn handle_pause(&mut self) {
if self.state.get_status() == PlayStatus::kPlayStatusPlay { match self.play_status {
self.player.pause(); SpircPlayStatus::Playing {
self.mixer.stop(); nominal_start_time,
self.state.set_status(PlayStatus::kPlayStatusPause); preloading_of_next_track_triggered,
} => {
let now = self.now_ms() as u64; self.player.pause();
let position = self.state.get_position_ms(); self.state.set_status(PlayStatus::kPlayStatusPause);
let position_ms = (self.now_ms() - nominal_start_time) as u32;
let diff = now - self.state.get_position_measured_at(); self.update_state_position(position_ms);
self.play_status = SpircPlayStatus::Paused {
self.state.set_position_ms(position + diff as u32); position_ms,
self.state.set_position_measured_at(now); preloading_of_next_track_triggered,
};
}
SpircPlayStatus::LoadingPlay { position_ms } => {
self.player.pause();
self.play_status = SpircPlayStatus::LoadingPause { position_ms };
}
_ => (),
} }
} }
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,
..
} => *position = position_ms,
SpircPlayStatus::Playing {
ref mut nominal_start_time,
..
} => *nominal_start_time = now - position_ms as i64,
};
}
fn consume_queued_track(&mut self) -> usize { fn consume_queued_track(&mut self) -> usize {
// Removes current track if it is queued // Removes current track if it is queued
// Returns the index of the next track // Returns the index of the next track
@ -687,6 +899,11 @@ impl SpircTask {
} }
} }
fn preview_next_track(&mut self) -> Option<SpotifyId> {
self.get_track_id_to_play_from_playlist(self.state.get_playing_track_index() + 1)
.and_then(|(track_id, _)| Some(track_id))
}
fn handle_next(&mut self) { fn handle_next(&mut self) {
let mut new_index = self.consume_queued_track() as u32; let mut new_index = self.consume_queued_track() as u32;
let mut continue_playing = true; let mut continue_playing = true;
@ -720,17 +937,14 @@ impl SpircTask {
if tracks_len > 0 { if tracks_len > 0 {
self.state.set_playing_track_index(new_index); self.state.set_playing_track_index(new_index);
self.state.set_position_ms(0); self.load_track(continue_playing, 0);
let now = self.now_ms();
self.state.set_position_measured_at(now as u64);
self.load_track(continue_playing);
} else { } else {
info!("Not playing next track because there are no more tracks left in queue."); info!("Not playing next track because there are no more tracks left in queue.");
self.state.set_playing_track_index(0); self.state.set_playing_track_index(0);
self.state.set_status(PlayStatus::kPlayStatusStop); self.state.set_status(PlayStatus::kPlayStatusStop);
self.player.stop(); self.player.stop();
self.mixer.stop(); self.ensure_mixer_stopped();
self.play_status = SpircPlayStatus::Stopped;
} }
} }
@ -765,17 +979,11 @@ impl SpircTask {
pos += 1; pos += 1;
} }
let now = self.now_ms();
self.state.set_playing_track_index(new_index); self.state.set_playing_track_index(new_index);
self.state.set_position_ms(0);
self.state.set_position_measured_at(now as u64);
self.load_track(true); self.load_track(true, 0);
} else { } else {
let now = self.now_ms(); self.handle_seek(0);
self.state.set_position_ms(0);
self.state.set_position_measured_at(now as u64);
self.player.seek(0);
} }
} }
@ -797,12 +1005,19 @@ impl SpircTask {
fn handle_end_of_track(&mut self) { fn handle_end_of_track(&mut self) {
self.handle_next(); self.handle_next();
self.notify(None); self.notify(None, true);
} }
fn position(&mut self) -> u32 { fn position(&mut self) -> u32 {
let diff = self.now_ms() as u64 - self.state.get_position_measured_at(); match self.play_status {
self.state.get_position_ms() + diff as u32 SpircPlayStatus::Stopped => 0,
SpircPlayStatus::LoadingPlay { position_ms }
| SpircPlayStatus::LoadingPause { position_ms }
| SpircPlayStatus::Paused { position_ms, .. } => position_ms,
SpircPlayStatus::Playing {
nominal_start_time, ..
} => (self.now_ms() - nominal_start_time) as u32,
}
} }
fn resolve_station( fn resolve_station(
@ -920,60 +1135,91 @@ impl SpircTask {
}) })
} }
fn load_track(&mut self, play: bool) { fn get_track_id_to_play_from_playlist(&self, index: u32) -> Option<(SpotifyId, u32)> {
let context_uri = self.state.get_context_uri().to_owned();
let mut index = self.state.get_playing_track_index();
let start_index = index;
let tracks_len = self.state.get_track().len() as u32; let tracks_len = self.state.get_track().len() as u32;
debug!(
"Loading context: <{}> index: [{}] of {}", let mut new_playlist_index = index;
context_uri, index, tracks_len
); if new_playlist_index >= tracks_len {
new_playlist_index = 0;
}
let start_index = new_playlist_index;
// Cycle through all tracks, break if we don't find any playable tracks // Cycle through all tracks, break if we don't find any playable tracks
// TODO: This will panic if no playable tracks are found!
// tracks in each frame either have a gid or uri (that may or may not be a valid track) // 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:> // E.g - context based frames sometimes contain tracks with <spotify:meta:page:>
let track = {
let mut track_ref = self.state.get_track()[index as usize].clone(); let mut track_ref = self.state.get_track()[new_playlist_index as usize].clone();
let mut track_id = self.get_spotify_id_for_track(&track_ref); let mut track_id = self.get_spotify_id_for_track(&track_ref);
while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable {
{ warn!(
warn!( "Skipping track <{:?}> at position [{}] of {}",
"Skipping track <{:?}> at position [{}] of {}", track_ref.get_uri(),
track_ref.get_uri(), new_playlist_index,
index, tracks_len
tracks_len );
);
index = if index + 1 < tracks_len { index + 1 } else { 0 }; new_playlist_index += 1;
self.state.set_playing_track_index(index); if new_playlist_index >= tracks_len {
if index == start_index { new_playlist_index = 0;
warn!("No playable track found in state: {:?}", self.state);
break;
}
track_ref = self.state.get_track()[index as usize].clone();
track_id = self.get_spotify_id_for_track(&track_ref);
} }
track_id
}
.expect("Invalid SpotifyId");
let position = self.state.get_position_ms(); if new_playlist_index == start_index {
let end_of_track = self.player.load(track, play, position); warn!("No playable track found in state: {:?}", self.state);
return None;
if play { }
self.state.set_status(PlayStatus::kPlayStatusPlay); track_ref = self.state.get_track()[index as usize].clone();
} else { track_id = self.get_spotify_id_for_track(&track_ref);
self.state.set_status(PlayStatus::kPlayStatusPause);
} }
self.end_of_track = Box::new(end_of_track); match track_id {
Ok(track_id) => Some((track_id, new_playlist_index)),
Err(_) => None,
}
}
fn load_track(&mut self, start_playing: bool, position_ms: u32) {
let index = self.state.get_playing_track_index();
match self.get_track_id_to_play_from_playlist(index) {
Some((track, index)) => {
self.state.set_playing_track_index(index);
self.play_request_id = Some(self.player.load(track, start_playing, position_ms));
self.update_state_position(position_ms);
self.state.set_status(PlayStatus::kPlayStatusLoading);
if start_playing {
self.play_status = SpircPlayStatus::LoadingPlay { position_ms };
} else {
self.play_status = SpircPlayStatus::LoadingPause { position_ms };
}
}
None => {
self.state.set_status(PlayStatus::kPlayStatusStop);
self.player.stop();
self.ensure_mixer_stopped();
self.play_status = SpircPlayStatus::Stopped;
}
}
} }
fn hello(&mut self) { fn hello(&mut self) {
CommandSender::new(self, MessageType::kMessageTypeHello).send(); CommandSender::new(self, MessageType::kMessageTypeHello).send();
} }
fn notify(&mut self, recipient: Option<&str>) { fn notify(&mut self, recipient: Option<&str>, suppress_loading_status: bool) {
if suppress_loading_status && (self.state.get_status() == PlayStatus::kPlayStatusLoading) {
return;
};
let status_string = match self.state.get_status() {
PlayStatus::kPlayStatusLoading => "kPlayStatusLoading",
PlayStatus::kPlayStatusPause => "kPlayStatusPause",
PlayStatus::kPlayStatusStop => "kPlayStatusStop",
PlayStatus::kPlayStatusPlay => "kPlayStatusPlay",
};
trace!("Sending status to server: [{}]", status_string);
let mut cs = CommandSender::new(self, MessageType::kMessageTypeNotify); let mut cs = CommandSender::new(self, MessageType::kMessageTypeNotify);
if let Some(s) = recipient { if let Some(s) = recipient {
cs = cs.recipient(&s); cs = cs.recipient(&s);
@ -988,6 +1234,7 @@ impl SpircTask {
if let Some(cache) = self.session.cache() { if let Some(cache) = self.session.cache() {
cache.save_volume(Volume { volume }) cache.save_volume(Volume { volume })
} }
self.player.emit_volume_set_event(volume);
} }
} }

View file

@ -38,7 +38,7 @@ pub trait Seq {
macro_rules! impl_seq { macro_rules! impl_seq {
($($ty:ty)*) => { $( ($($ty:ty)*) => { $(
impl Seq for $ty { impl Seq for $ty {
fn next(&self) -> Self { *self + 1 } fn next(&self) -> Self { (*self).wrapping_add(1) }
} }
)* } )* }
} }

View file

@ -34,12 +34,14 @@ fn main() {
.run(Session::connect(session_config, credentials, None, handle)) .run(Session::connect(session_config, credentials, None, handle))
.unwrap(); .unwrap();
let (player, _) = Player::new(player_config, session.clone(), None, move || { let (mut player, _) = Player::new(player_config, session.clone(), None, move || {
(backend)(None) (backend)(None)
}); });
player.load(track, true, 0);
println!("Playing..."); println!("Playing...");
core.run(player.load(track, true, 0)).unwrap(); core.run(player.get_end_of_track_future()).unwrap();
println!("Done"); println!("Done");
} }

View file

@ -63,6 +63,7 @@ pub struct AudioItem {
pub uri: String, pub uri: String,
pub files: LinearMap<FileFormat, FileId>, pub files: LinearMap<FileFormat, FileId>,
pub name: String, pub name: String,
pub duration: i32,
pub available: bool, pub available: bool,
pub alternatives: Option<Vec<SpotifyId>>, pub alternatives: Option<Vec<SpotifyId>>,
} }
@ -100,6 +101,7 @@ impl AudioFiles for Track {
uri: format!("spotify:track:{}", id.to_base62()), uri: format!("spotify:track:{}", id.to_base62()),
files: item.files, files: item.files,
name: item.name, name: item.name,
duration: item.duration,
available: item.available, available: item.available,
alternatives: Some(item.alternatives), alternatives: Some(item.alternatives),
}) })
@ -118,6 +120,7 @@ impl AudioFiles for Episode {
uri: format!("spotify:episode:{}", id.to_base62()), uri: format!("spotify:episode:{}", id.to_base62()),
files: item.files, files: item.files,
name: item.name, name: item.name,
duration: item.duration,
available: item.available, available: item.available,
alternatives: None, alternatives: None,
}) })

View file

@ -30,6 +30,7 @@ pub struct PlayerConfig {
pub bitrate: Bitrate, pub bitrate: Bitrate,
pub normalisation: bool, pub normalisation: bool,
pub normalisation_pregain: f32, pub normalisation_pregain: f32,
pub gapless: bool,
} }
impl Default for PlayerConfig { impl Default for PlayerConfig {
@ -38,6 +39,7 @@ impl Default for PlayerConfig {
bitrate: Bitrate::default(), bitrate: Bitrate::default(),
normalisation: false, normalisation: false,
normalisation_pregain: 0.0, normalisation_pregain: 0.0,
gapless: true,
} }
} }
} }

File diff suppressed because it is too large Load diff

View file

@ -180,6 +180,11 @@ fn setup(args: &[String]) -> Setup {
"", "",
"autoplay", "autoplay",
"autoplay similar songs when your music ends.", "autoplay similar songs when your music ends.",
)
.optflag(
"",
"disable-gapless",
"disable gapless playback.",
); );
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
@ -287,7 +292,7 @@ fn setup(args: &[String]) -> Setup {
|s| { |s| {
match Url::parse(&s) { match Url::parse(&s) {
Ok(url) => { Ok(url) => {
if url.host().is_none() || url.port().is_none() { if url.host().is_none() || url.port_or_known_default().is_none() {
panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed"); panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed");
} }
@ -312,9 +317,9 @@ fn setup(args: &[String]) -> Setup {
.as_ref() .as_ref()
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate")) .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
.unwrap_or(Bitrate::default()); .unwrap_or(Bitrate::default());
PlayerConfig { PlayerConfig {
bitrate: bitrate, bitrate: bitrate,
gapless: !matches.opt_present("disable-gapless"),
normalisation: matches.opt_present("enable-volume-normalisation"), normalisation: matches.opt_present("enable-volume-normalisation"),
normalisation_pregain: matches normalisation_pregain: matches
.opt_str("normalisation-pregain") .opt_str("normalisation-pregain")
@ -539,16 +544,18 @@ impl Future for Main {
if let Some(ref mut player_event_channel) = self.player_event_channel { if let Some(ref mut player_event_channel) = self.player_event_channel {
if let Async::Ready(Some(event)) = player_event_channel.poll().unwrap() { if let Async::Ready(Some(event)) = player_event_channel.poll().unwrap() {
if let Some(ref program) = self.player_event_program { if let Some(ref program) = self.player_event_program {
let child = run_program_on_events(event, program) if let Some(child) = run_program_on_events(event, program) {
.expect("program failed to start") let child = child
.map(|status| { .expect("program failed to start")
if !status.success() { .map(|status| {
error!("child exited with status {:?}", status.code()); if !status.success() {
} error!("child exited with status {:?}", status.code());
}) }
.map_err(|e| error!("failed to wait on child process: {}", e)); })
.map_err(|e| error!("failed to wait on child process: {}", e));
self.handle.spawn(child); self.handle.spawn(child);
}
} }
} }
} }

View file

@ -14,7 +14,7 @@ fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result<Chi
.spawn_async() .spawn_async()
} }
pub fn run_program_on_events(event: PlayerEvent, onevent: &str) -> io::Result<Child> { pub fn run_program_on_events(event: PlayerEvent, onevent: &str) -> Option<io::Result<Child>> {
let mut env_vars = HashMap::new(); let mut env_vars = HashMap::new();
match event { match event {
PlayerEvent::Changed { PlayerEvent::Changed {
@ -25,14 +25,15 @@ pub fn run_program_on_events(event: PlayerEvent, onevent: &str) -> io::Result<Ch
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base62()); env_vars.insert("OLD_TRACK_ID", old_track_id.to_base62());
env_vars.insert("TRACK_ID", new_track_id.to_base62()); env_vars.insert("TRACK_ID", new_track_id.to_base62());
} }
PlayerEvent::Started { track_id } => { PlayerEvent::Started { track_id, .. } => {
env_vars.insert("PLAYER_EVENT", "start".to_string()); env_vars.insert("PLAYER_EVENT", "start".to_string());
env_vars.insert("TRACK_ID", track_id.to_base62()); env_vars.insert("TRACK_ID", track_id.to_base62());
} }
PlayerEvent::Stopped { track_id } => { PlayerEvent::Stopped { track_id, .. } => {
env_vars.insert("PLAYER_EVENT", "stop".to_string()); env_vars.insert("PLAYER_EVENT", "stop".to_string());
env_vars.insert("TRACK_ID", track_id.to_base62()); env_vars.insert("TRACK_ID", track_id.to_base62());
} }
_ => return None,
} }
run_program(onevent, env_vars) Some(run_program(onevent, env_vars))
} }