@@ -81,6 +81,7 @@ have them configurable.
|
||||
* `Shift-u` updates the library cache (tracks, artists, albums, playlists)
|
||||
* `<` and `>` play the previous or next track
|
||||
* `,` and `.` to rewind or skip forward
|
||||
* `-` and `+` decrease or increase the volume
|
||||
* `r` to toggle repeat mode
|
||||
* `z` to toggle shuffle playback
|
||||
* `q` quits ncspot
|
||||
|
||||
@@ -57,6 +57,8 @@ pub enum Command {
|
||||
Delete,
|
||||
Focus(String),
|
||||
Seek(SeekDirection),
|
||||
VolumeUp,
|
||||
VolumeDown,
|
||||
Repeat(Option<RepeatSetting>),
|
||||
Shuffle(Option<bool>),
|
||||
Share(TargetMode),
|
||||
|
||||
@@ -9,7 +9,7 @@ use cursive::views::ViewRef;
|
||||
use cursive::Cursive;
|
||||
use library::Library;
|
||||
use queue::{Queue, RepeatSetting};
|
||||
use spotify::Spotify;
|
||||
use spotify::{Spotify, VOLUME_PERCENT};
|
||||
use traits::ViewExt;
|
||||
use ui::layout::Layout;
|
||||
|
||||
@@ -110,6 +110,17 @@ impl CommandManager {
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
Command::VolumeUp => {
|
||||
let volume = self.spotify.volume().saturating_add(VOLUME_PERCENT);
|
||||
self.spotify.set_volume(volume);
|
||||
Ok(None)
|
||||
}
|
||||
Command::VolumeDown => {
|
||||
let volume = self.spotify.volume().saturating_sub(VOLUME_PERCENT);
|
||||
debug!("vol {}", volume);
|
||||
self.spotify.set_volume(volume);
|
||||
Ok(None)
|
||||
}
|
||||
Command::Search(_)
|
||||
| Command::Move(_, _)
|
||||
| Command::Shift(_, _)
|
||||
@@ -202,6 +213,8 @@ impl CommandManager {
|
||||
kb.insert("/".into(), Command::Focus("search".into()));
|
||||
kb.insert(".".into(), Command::Seek(SeekDirection::Relative(500)));
|
||||
kb.insert(",".into(), Command::Seek(SeekDirection::Relative(-500)));
|
||||
kb.insert("+".into(), Command::VolumeUp);
|
||||
kb.insert("-".into(), Command::VolumeDown);
|
||||
kb.insert("r".into(), Command::Repeat(None));
|
||||
kb.insert("z".into(), Command::Shuffle(None));
|
||||
kb.insert("x".into(), Command::Share(TargetMode::Current));
|
||||
|
||||
@@ -10,6 +10,7 @@ use librespot_playback::config::PlayerConfig;
|
||||
|
||||
use librespot_playback::audio_backend;
|
||||
use librespot_playback::config::Bitrate;
|
||||
use librespot_playback::mixer::Mixer;
|
||||
use librespot_playback::player::Player;
|
||||
|
||||
use rspotify::spotify::client::ApiError;
|
||||
@@ -36,6 +37,7 @@ use tokio_core::reactor::Core;
|
||||
use tokio_timer;
|
||||
use url::Url;
|
||||
|
||||
use std::sync::atomic::{AtomicU16, Ordering};
|
||||
use std::sync::RwLock;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
@@ -45,12 +47,15 @@ use config;
|
||||
use events::{Event, EventManager};
|
||||
use track::Track;
|
||||
|
||||
pub const VOLUME_PERCENT: u16 = ((u16::max_value() as f64) * 1.0 / 100.0) as u16;
|
||||
|
||||
enum WorkerCommand {
|
||||
Load(Box<Track>),
|
||||
Play,
|
||||
Pause,
|
||||
Stop,
|
||||
Seek(u32),
|
||||
SetVolume(u16),
|
||||
RequestToken(oneshot::Sender<Token>),
|
||||
}
|
||||
|
||||
@@ -70,6 +75,7 @@ pub struct Spotify {
|
||||
token_issued: RwLock<Option<SystemTime>>,
|
||||
channel: mpsc::UnboundedSender<WorkerCommand>,
|
||||
user: String,
|
||||
volume: AtomicU16,
|
||||
}
|
||||
|
||||
struct Worker {
|
||||
@@ -81,6 +87,7 @@ struct Worker {
|
||||
refresh_task: Box<dyn futures::Stream<Item = (), Error = tokio_timer::Error>>,
|
||||
token_task: Box<dyn futures::Future<Item = (), Error = MercuryError>>,
|
||||
active: bool,
|
||||
mixer: Box<dyn Mixer>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
@@ -89,6 +96,7 @@ impl Worker {
|
||||
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
||||
session: Session,
|
||||
player: Player,
|
||||
mixer: Box<dyn Mixer>,
|
||||
) -> Worker {
|
||||
Worker {
|
||||
events,
|
||||
@@ -99,6 +107,7 @@ impl Worker {
|
||||
refresh_task: Box::new(futures::stream::empty()),
|
||||
token_task: Box::new(futures::empty()),
|
||||
active: false,
|
||||
mixer,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,6 +163,9 @@ impl futures::Future for Worker {
|
||||
WorkerCommand::Seek(pos) => {
|
||||
self.player.seek(pos);
|
||||
}
|
||||
WorkerCommand::SetVolume(volume) => {
|
||||
self.mixer.set_volume(volume);
|
||||
}
|
||||
WorkerCommand::RequestToken(sender) => {
|
||||
self.token_task = Spotify::get_token(&self.session, sender);
|
||||
progress = true;
|
||||
@@ -206,12 +218,13 @@ impl Spotify {
|
||||
normalisation_pregain: 0.0,
|
||||
};
|
||||
let (user_tx, user_rx) = oneshot::channel();
|
||||
let volume = 0xFFFF;
|
||||
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
{
|
||||
let events = events.clone();
|
||||
thread::spawn(move || {
|
||||
Self::worker(cfg, events, rx, player_config, credentials, user_tx)
|
||||
Self::worker(cfg, events, rx, player_config, credentials, user_tx, volume)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -223,6 +236,7 @@ impl Spotify {
|
||||
token_issued: RwLock::new(None),
|
||||
channel: tx,
|
||||
user: user_rx.wait().expect("error retrieving userid from worker"),
|
||||
volume: AtomicU16::new(volume),
|
||||
};
|
||||
|
||||
// acquire token for web api usage
|
||||
@@ -297,6 +311,7 @@ impl Spotify {
|
||||
player_config: PlayerConfig,
|
||||
credentials: Credentials,
|
||||
user_tx: oneshot::Sender<String>,
|
||||
volume: u16,
|
||||
) {
|
||||
let mut core = Core::new().unwrap();
|
||||
|
||||
@@ -305,13 +320,20 @@ impl Spotify {
|
||||
.send(session.username())
|
||||
.expect("could not pass username back to Spotify::new");
|
||||
|
||||
let backend = audio_backend::find(None).unwrap();
|
||||
let (player, _eventchannel) =
|
||||
Player::new(player_config, session.clone(), None, move || {
|
||||
(backend)(None)
|
||||
});
|
||||
let create_mixer = librespot_playback::mixer::find(Some("softvol".to_owned()))
|
||||
.expect("could not create softvol mixer");
|
||||
let mixer = create_mixer(None);
|
||||
mixer.set_volume(volume);
|
||||
|
||||
let worker = Worker::new(events, commands, session, player);
|
||||
let backend = audio_backend::find(None).unwrap();
|
||||
let (player, _eventchannel) = Player::new(
|
||||
player_config,
|
||||
session.clone(),
|
||||
mixer.get_audio_filter(),
|
||||
move || (backend)(None),
|
||||
);
|
||||
|
||||
let worker = Worker::new(events, commands, session, player, mixer);
|
||||
debug!("worker thread ready.");
|
||||
core.run(worker).unwrap();
|
||||
debug!("worker thread finished.");
|
||||
@@ -701,6 +723,18 @@ impl Spotify {
|
||||
let new = (progress.as_secs() * 1000) as i32 + progress.subsec_millis() as i32 + delta;
|
||||
self.seek(std::cmp::max(0, new) as u32);
|
||||
}
|
||||
|
||||
pub fn volume(&self) -> u16 {
|
||||
self.volume.load(Ordering::Relaxed) as u16
|
||||
}
|
||||
|
||||
pub fn set_volume(&self, volume: u16) {
|
||||
info!("setting volume to {}", volume);
|
||||
self.volume.store(volume, Ordering::Relaxed);
|
||||
self.channel
|
||||
.unbounded_send(WorkerCommand::SetVolume(volume))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub enum URIType {
|
||||
|
||||
@@ -90,7 +90,11 @@ impl View for StatusBar {
|
||||
});
|
||||
|
||||
let updating = if !*self.library.is_done.read().unwrap() {
|
||||
if self.use_nerdfont { "\u{f9e5} " } else { "[U] " }
|
||||
if self.use_nerdfont {
|
||||
"\u{f9e5} "
|
||||
} else {
|
||||
"[U] "
|
||||
}
|
||||
} else {
|
||||
""
|
||||
};
|
||||
@@ -119,6 +123,11 @@ impl View for StatusBar {
|
||||
""
|
||||
};
|
||||
|
||||
let volume = format!(
|
||||
" [{}%]",
|
||||
(self.spotify.volume() as f64 / 0xffff as f64 * 100.0) as u16
|
||||
);
|
||||
|
||||
printer.with_color(style_bar_bg, |printer| {
|
||||
printer.print((0, 0), &"┉".repeat(printer.size.x));
|
||||
});
|
||||
@@ -147,7 +156,8 @@ impl View for StatusBar {
|
||||
+ repeat
|
||||
+ shuffle
|
||||
+ saved
|
||||
+ &format!("{} / {} ", formatted_elapsed, t.duration_str());
|
||||
+ &format!("{} / {}", formatted_elapsed, t.duration_str())
|
||||
+ &volume;
|
||||
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
@@ -160,7 +170,7 @@ impl View for StatusBar {
|
||||
printer.print((0, 0), &"━".repeat(duration_width + 1));
|
||||
});
|
||||
} else {
|
||||
let right = updating.to_string() + repeat + shuffle;
|
||||
let right = updating.to_string() + repeat + shuffle + &volume;
|
||||
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
|
||||
|
||||
printer.with_color(style, |printer| {
|
||||
|
||||
Reference in New Issue
Block a user