mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge branch 'dev' into tokio_migration
This commit is contained in:
commit
f5274f5ada
10 changed files with 141 additions and 138 deletions
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
toolchain:
|
toolchain:
|
||||||
- 1.42.0 # MSRV (Minimum supported rust version)
|
- 1.41.1 # MSRV (Minimum supported rust version)
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
experimental: [false]
|
experimental: [false]
|
||||||
|
|
|
@ -13,7 +13,7 @@ curl https://sh.rustup.rs -sSf | sh
|
||||||
|
|
||||||
Follow any prompts it gives you to install Rust. Once that’s done, Rust's standard tools should be setup and ready to use.
|
Follow any prompts it gives you to install Rust. Once that’s done, Rust's standard tools should be setup and ready to use.
|
||||||
|
|
||||||
*Note: The current minimum required Rust version at the time of writing is 1.40.0, you can find the current minimum version specified in the `.github/workflow/test.yml` file.*
|
*Note: The current minimum required Rust version at the time of writing is 1.41, you can find the current minimum version specified in the `.github/workflow/test.yml` file.*
|
||||||
|
|
||||||
#### Additional Rust tools - `rustfmt`
|
#### Additional Rust tools - `rustfmt`
|
||||||
To ensure a consistent codebase, we utilise [`rustfmt`](https://github.com/rust-lang/rustfmt), which is installed by default with `rustup` these days, else it can be installed manually with:
|
To ensure a consistent codebase, we utilise [`rustfmt`](https://github.com/rust-lang/rustfmt), which is installed by default with `rustup` these days, else it can be installed manually with:
|
||||||
|
|
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -139,9 +139,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "backtrace"
|
name = "backtrace"
|
||||||
version = "0.3.56"
|
version = "0.3.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
|
checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"addr2line",
|
"addr2line",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
|
@ -1718,9 +1718,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "object"
|
name = "object"
|
||||||
version = "0.23.0"
|
version = "0.22.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
|
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oboe"
|
name = "oboe"
|
||||||
|
|
|
@ -5,75 +5,32 @@ use std::fmt;
|
||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
fn write_headers<T: Read + Seek>(
|
fn get_header<T>(code: u8, rdr: &mut PacketReader<T>) -> Result<Box<[u8]>, PassthroughError>
|
||||||
rdr: &mut PacketReader<T>,
|
|
||||||
wtr: &mut PacketWriter<Vec<u8>>,
|
|
||||||
) -> Result<u32, PassthroughError> {
|
|
||||||
let mut stream_serial: u32 = SystemTime::now()
|
|
||||||
.duration_since(UNIX_EPOCH)
|
|
||||||
.unwrap()
|
|
||||||
.as_millis() as u32;
|
|
||||||
|
|
||||||
// search for ident, comment, setup
|
|
||||||
get_header(1, rdr, wtr, &mut stream_serial, PacketWriteEndInfo::EndPage)?;
|
|
||||||
get_header(
|
|
||||||
3,
|
|
||||||
rdr,
|
|
||||||
wtr,
|
|
||||||
&mut stream_serial,
|
|
||||||
PacketWriteEndInfo::NormalPacket,
|
|
||||||
)?;
|
|
||||||
get_header(5, rdr, wtr, &mut stream_serial, PacketWriteEndInfo::EndPage)?;
|
|
||||||
|
|
||||||
// remove un-needed packets
|
|
||||||
rdr.delete_unread_packets();
|
|
||||||
Ok(stream_serial)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_header<T>(
|
|
||||||
code: u8,
|
|
||||||
rdr: &mut PacketReader<T>,
|
|
||||||
wtr: &mut PacketWriter<Vec<u8>>,
|
|
||||||
stream_serial: &mut u32,
|
|
||||||
info: PacketWriteEndInfo,
|
|
||||||
) -> Result<u32, PassthroughError>
|
|
||||||
where
|
where
|
||||||
T: Read + Seek,
|
T: Read + Seek,
|
||||||
{
|
{
|
||||||
let pck: Packet = rdr.read_packet_expected()?;
|
let pck: Packet = rdr.read_packet_expected()?;
|
||||||
|
|
||||||
// set a unique serial number
|
|
||||||
if pck.stream_serial() != 0 {
|
|
||||||
*stream_serial = pck.stream_serial();
|
|
||||||
}
|
|
||||||
|
|
||||||
let pkt_type = pck.data[0];
|
let pkt_type = pck.data[0];
|
||||||
debug!("Vorbis header type{}", &pkt_type);
|
debug!("Vorbis header type{}", &pkt_type);
|
||||||
|
|
||||||
// all headers are mandatory
|
|
||||||
if pkt_type != code {
|
if pkt_type != code {
|
||||||
return Err(PassthroughError(OggReadError::InvalidData));
|
return Err(PassthroughError(OggReadError::InvalidData));
|
||||||
}
|
}
|
||||||
|
|
||||||
// headers keep original granule number
|
Ok(pck.data.into_boxed_slice())
|
||||||
let absgp_page = pck.absgp_page();
|
|
||||||
wtr.write_packet(
|
|
||||||
pck.data.into_boxed_slice(),
|
|
||||||
*stream_serial,
|
|
||||||
info,
|
|
||||||
absgp_page,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
Ok(*stream_serial)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PassthroughDecoder<R: Read + Seek> {
|
pub struct PassthroughDecoder<R: Read + Seek> {
|
||||||
rdr: PacketReader<R>,
|
rdr: PacketReader<R>,
|
||||||
wtr: PacketWriter<Vec<u8>>,
|
wtr: PacketWriter<Vec<u8>>,
|
||||||
lastgp_page: Option<u64>,
|
eos: bool,
|
||||||
absgp_page: u64,
|
bos: bool,
|
||||||
|
ofsgp_page: u64,
|
||||||
stream_serial: u32,
|
stream_serial: u32,
|
||||||
|
ident: Box<[u8]>,
|
||||||
|
comment: Box<[u8]>,
|
||||||
|
setup: Box<[u8]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PassthroughError(ogg::OggReadError);
|
pub struct PassthroughError(ogg::OggReadError);
|
||||||
|
@ -82,17 +39,31 @@ impl<R: Read + Seek> PassthroughDecoder<R> {
|
||||||
/// Constructs a new Decoder from a given implementation of `Read + Seek`.
|
/// Constructs a new Decoder from a given implementation of `Read + Seek`.
|
||||||
pub fn new(rdr: R) -> Result<Self, PassthroughError> {
|
pub fn new(rdr: R) -> Result<Self, PassthroughError> {
|
||||||
let mut rdr = PacketReader::new(rdr);
|
let mut rdr = PacketReader::new(rdr);
|
||||||
let mut wtr = PacketWriter::new(Vec::new());
|
let stream_serial = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis() as u32;
|
||||||
|
|
||||||
let stream_serial = write_headers(&mut rdr, &mut wtr)?;
|
|
||||||
info!("Starting passthrough track with serial {}", stream_serial);
|
info!("Starting passthrough track with serial {}", stream_serial);
|
||||||
|
|
||||||
|
// search for ident, comment, setup
|
||||||
|
let ident = get_header(1, &mut rdr)?;
|
||||||
|
let comment = get_header(3, &mut rdr)?;
|
||||||
|
let setup = get_header(5, &mut rdr)?;
|
||||||
|
|
||||||
|
// remove un-needed packets
|
||||||
|
rdr.delete_unread_packets();
|
||||||
|
|
||||||
Ok(PassthroughDecoder {
|
Ok(PassthroughDecoder {
|
||||||
rdr,
|
rdr,
|
||||||
wtr,
|
wtr: PacketWriter::new(Vec::new()),
|
||||||
lastgp_page: Some(0),
|
ofsgp_page: 0,
|
||||||
absgp_page: 0,
|
|
||||||
stream_serial,
|
stream_serial,
|
||||||
|
ident,
|
||||||
|
comment,
|
||||||
|
setup,
|
||||||
|
eos: false,
|
||||||
|
bos: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,52 +71,94 @@ impl<R: Read + Seek> PassthroughDecoder<R> {
|
||||||
impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
|
impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
|
||||||
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
|
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
|
||||||
info!("Seeking to {}", ms);
|
info!("Seeking to {}", ms);
|
||||||
self.lastgp_page = match ms {
|
|
||||||
0 => Some(0),
|
// add an eos to previous stream if missing
|
||||||
_ => None,
|
if self.bos && !self.eos {
|
||||||
};
|
match self.rdr.read_packet() {
|
||||||
|
Ok(Some(pck)) => {
|
||||||
|
let absgp_page = pck.absgp_page() - self.ofsgp_page;
|
||||||
|
self.wtr
|
||||||
|
.write_packet(
|
||||||
|
pck.data.into_boxed_slice(),
|
||||||
|
self.stream_serial,
|
||||||
|
PacketWriteEndInfo::EndStream,
|
||||||
|
absgp_page,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
_ => warn! {"Cannot write EoS after seeking"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
self.eos = false;
|
||||||
|
self.bos = false;
|
||||||
|
self.ofsgp_page = 0;
|
||||||
|
self.stream_serial += 1;
|
||||||
|
|
||||||
// hard-coded to 44.1 kHz
|
// hard-coded to 44.1 kHz
|
||||||
match self.rdr.seek_absgp(None, (ms * 44100 / 1000) as u64) {
|
match self.rdr.seek_absgp(None, (ms * 44100 / 1000) as u64) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => {
|
||||||
|
// need to set some offset for next_page()
|
||||||
|
let pck = self.rdr.read_packet().unwrap().unwrap();
|
||||||
|
self.ofsgp_page = pck.absgp_page();
|
||||||
|
debug!("Seek to offset page {}", self.ofsgp_page);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
Err(err) => Err(AudioError::PassthroughError(err.into())),
|
Err(err) => Err(AudioError::PassthroughError(err.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
|
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
|
||||||
let mut skip = self.lastgp_page.is_none();
|
// write headers if we are (re)starting
|
||||||
|
if self.bos == false {
|
||||||
|
self.wtr
|
||||||
|
.write_packet(
|
||||||
|
self.ident.clone(),
|
||||||
|
self.stream_serial,
|
||||||
|
PacketWriteEndInfo::EndPage,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
self.wtr
|
||||||
|
.write_packet(
|
||||||
|
self.comment.clone(),
|
||||||
|
self.stream_serial,
|
||||||
|
PacketWriteEndInfo::NormalPacket,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
self.wtr
|
||||||
|
.write_packet(
|
||||||
|
self.setup.clone(),
|
||||||
|
self.stream_serial,
|
||||||
|
PacketWriteEndInfo::EndPage,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
self.bos = true;
|
||||||
|
debug!("Wrote Ogg headers");
|
||||||
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let pck = match self.rdr.read_packet() {
|
let pck = match self.rdr.read_packet() {
|
||||||
Ok(Some(pck)) => pck,
|
Ok(Some(pck)) => pck,
|
||||||
|
|
||||||
Ok(None) | Err(OggReadError::NoCapturePatternFound) => {
|
Ok(None) | Err(OggReadError::NoCapturePatternFound) => {
|
||||||
info!("end of streaming");
|
info!("end of streaming");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(err) => return Err(AudioError::PassthroughError(err.into())),
|
Err(err) => return Err(AudioError::PassthroughError(err.into())),
|
||||||
};
|
};
|
||||||
|
|
||||||
let pckgp_page = pck.absgp_page();
|
let pckgp_page = pck.absgp_page();
|
||||||
let lastgp_page = self.lastgp_page.get_or_insert(pckgp_page);
|
|
||||||
|
|
||||||
// consume packets till next page to get a granule reference
|
// skip till we have audio and a calculable granule position
|
||||||
if skip {
|
if pckgp_page == 0 || pckgp_page == self.ofsgp_page {
|
||||||
if *lastgp_page == pckgp_page {
|
continue;
|
||||||
debug!("skipping packet");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
skip = false;
|
|
||||||
info!("skipped at {}", pckgp_page);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// now we can calculate absolute granule
|
|
||||||
self.absgp_page += pckgp_page - *lastgp_page;
|
|
||||||
self.lastgp_page = Some(pckgp_page);
|
|
||||||
|
|
||||||
// set packet type
|
// set packet type
|
||||||
let inf = if pck.last_in_stream() {
|
let inf = if pck.last_in_stream() {
|
||||||
self.lastgp_page = Some(0);
|
self.eos = true;
|
||||||
PacketWriteEndInfo::EndStream
|
PacketWriteEndInfo::EndStream
|
||||||
} else if pck.last_in_page() {
|
} else if pck.last_in_page() {
|
||||||
PacketWriteEndInfo::EndPage
|
PacketWriteEndInfo::EndPage
|
||||||
|
@ -158,7 +171,7 @@ impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
|
||||||
pck.data.into_boxed_slice(),
|
pck.data.into_boxed_slice(),
|
||||||
self.stream_serial,
|
self.stream_serial,
|
||||||
inf,
|
inf,
|
||||||
self.absgp_page,
|
pckgp_page - self.ofsgp_page,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
|
|
@ -106,7 +106,7 @@ fn initial_state() -> State {
|
||||||
fn initial_device_state(config: ConnectConfig) -> DeviceState {
|
fn initial_device_state(config: ConnectConfig) -> DeviceState {
|
||||||
{
|
{
|
||||||
let mut msg = DeviceState::new();
|
let mut msg = DeviceState::new();
|
||||||
msg.set_sw_version(version::version_string());
|
msg.set_sw_version(version::VERSION_STRING.to_string());
|
||||||
msg.set_is_active(false);
|
msg.set_is_active(false);
|
||||||
msg.set_can_play(true);
|
msg.set_can_play(true);
|
||||||
msg.set_volume(0);
|
msg.set_volume(0);
|
||||||
|
|
|
@ -12,5 +12,6 @@ fn main() {
|
||||||
.take(8)
|
.take(8)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
println!("cargo:rustc-env=VERGEN_BUILD_ID={}", build_id);
|
|
||||||
|
println!("cargo:rustc-env=LIBRESPOT_BUILD_ID={}", build_id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ impl Default for SessionConfig {
|
||||||
fn default() -> SessionConfig {
|
fn default() -> SessionConfig {
|
||||||
let device_id = uuid::Uuid::new_v4().to_hyphenated().to_string();
|
let device_id = uuid::Uuid::new_v4().to_hyphenated().to_string();
|
||||||
SessionConfig {
|
SessionConfig {
|
||||||
user_agent: crate::version::version_string(),
|
user_agent: crate::version::VERSION_STRING.to_string(),
|
||||||
device_id,
|
device_id,
|
||||||
proxy: None,
|
proxy: None,
|
||||||
ap_port: None,
|
ap_port: None,
|
||||||
|
|
|
@ -131,13 +131,13 @@ pub async fn authenticate(
|
||||||
.mut_system_info()
|
.mut_system_info()
|
||||||
.set_system_information_string(format!(
|
.set_system_information_string(format!(
|
||||||
"librespot_{}_{}",
|
"librespot_{}_{}",
|
||||||
version::short_sha(),
|
version::SHA_SHORT,
|
||||||
version::build_id()
|
version::BUILD_ID
|
||||||
));
|
));
|
||||||
packet
|
packet
|
||||||
.mut_system_info()
|
.mut_system_info()
|
||||||
.set_device_id(device_id.to_string());
|
.set_device_id(device_id.to_string());
|
||||||
packet.set_version_string(version::version_string());
|
packet.set_version_string(version::VERSION_STRING.to_string());
|
||||||
|
|
||||||
let cmd = 0xab;
|
let cmd = 0xab;
|
||||||
let data = packet.write_to_bytes().unwrap();
|
let data = packet.write_to_bytes().unwrap();
|
||||||
|
|
|
@ -1,44 +1,17 @@
|
||||||
pub fn version_string() -> String {
|
/// Version string of the form "librespot-<sha>"
|
||||||
format!("librespot-{}", short_sha())
|
pub const VERSION_STRING: &str = concat!("librespot-", env!("VERGEN_SHA_SHORT"));
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a timestamp representing now (UTC) in RFC3339 format.
|
/// Generate a timestamp string representing the build date (UTC).
|
||||||
pub fn now() -> &'static str {
|
pub const BUILD_DATE: &str = env!("VERGEN_BUILD_DATE");
|
||||||
env!("VERGEN_BUILD_TIMESTAMP")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a timstamp string representing now (UTC).
|
/// Short sha of the latest git commit.
|
||||||
pub fn short_now() -> &'static str {
|
pub const SHA_SHORT: &str = env!("VERGEN_SHA_SHORT");
|
||||||
env!("VERGEN_BUILD_DATE")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a SHA string
|
/// Date of the latest git commit.
|
||||||
pub fn sha() -> &'static str {
|
pub const COMMIT_DATE: &str = env!("VERGEN_COMMIT_DATE");
|
||||||
env!("VERGEN_SHA")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a short SHA string
|
/// Librespot crate version.
|
||||||
pub fn short_sha() -> &'static str {
|
pub const SEMVER: &str = env!("CARGO_PKG_VERSION");
|
||||||
env!("VERGEN_SHA_SHORT")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the commit date string
|
/// A random build id.
|
||||||
pub fn commit_date() -> &'static str {
|
pub const BUILD_ID: &str = env!("LIBRESPOT_BUILD_ID");
|
||||||
env!("VERGEN_COMMIT_DATE")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate the target triple string
|
|
||||||
pub fn target() -> &'static str {
|
|
||||||
env!("VERGEN_TARGET_TRIPLE")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a semver string
|
|
||||||
pub fn semver() -> &'static str {
|
|
||||||
// env!("VERGEN_SEMVER")
|
|
||||||
env!("CARGO_PKG_VERSION")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a random build id.
|
|
||||||
pub fn build_id() -> &'static str {
|
|
||||||
env!("VERGEN_BUILD_ID")
|
|
||||||
}
|
|
||||||
|
|
32
src/main.rs
32
src/main.rs
|
@ -93,6 +93,16 @@ pub fn get_credentials<F: FnOnce(&String) -> Option<String>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_version() {
|
||||||
|
println!(
|
||||||
|
"librespot {semver} {sha} (Built on {build_date}, Build ID: {build_id})",
|
||||||
|
semver = version::SEMVER,
|
||||||
|
sha = version::SHA_SHORT,
|
||||||
|
build_date = version::BUILD_DATE,
|
||||||
|
build_id = version::BUILD_ID
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Setup {
|
struct Setup {
|
||||||
backend: fn(Option<String>) -> Box<dyn Sink + Send + 'static>,
|
backend: fn(Option<String>) -> Box<dyn Sink + Send + 'static>,
|
||||||
|
@ -125,7 +135,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
"Path to a directory where system files (credentials, volume) will be cached. Can be different from cache option value",
|
"Path to a directory where system files (credentials, volume) will be cached. Can be different from cache option value",
|
||||||
"SYTEMCACHE",
|
"SYTEMCACHE",
|
||||||
).optflag("", "disable-audio-cache", "Disable caching of the audio data.")
|
).optflag("", "disable-audio-cache", "Disable caching of the audio data.")
|
||||||
.reqopt("n", "name", "Device name", "NAME")
|
.optopt("n", "name", "Device name", "NAME")
|
||||||
.optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
|
.optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
|
||||||
.optopt(
|
.optopt(
|
||||||
"b",
|
"b",
|
||||||
|
@ -141,6 +151,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
)
|
)
|
||||||
.optflag("", "emit-sink-events", "Run program set by --onevent before sink is opened and after it is closed.")
|
.optflag("", "emit-sink-events", "Run program set by --onevent before sink is opened and after it is closed.")
|
||||||
.optflag("v", "verbose", "Enable verbose output")
|
.optflag("v", "verbose", "Enable verbose output")
|
||||||
|
.optflag("V", "version", "Display librespot version string")
|
||||||
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
.optopt("u", "username", "Username to sign in with", "USERNAME")
|
||||||
.optopt("p", "password", "Password", "PASSWORD")
|
.optopt("p", "password", "Password", "PASSWORD")
|
||||||
.optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
|
.optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
|
||||||
|
@ -241,15 +252,20 @@ fn setup(args: &[String]) -> Setup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if matches.opt_present("version") {
|
||||||
|
print_version();
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
let verbose = matches.opt_present("verbose");
|
let verbose = matches.opt_present("verbose");
|
||||||
setup_logging(verbose);
|
setup_logging(verbose);
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
"librespot {} ({}). Built on {}. Build ID: {}",
|
"librespot {semver} {sha} (Built on {build_date}, Build ID: {build_id})",
|
||||||
version::short_sha(),
|
semver = version::SEMVER,
|
||||||
version::commit_date(),
|
sha = version::SHA_SHORT,
|
||||||
version::short_now(),
|
build_date = version::BUILD_DATE,
|
||||||
version::build_id()
|
build_id = version::BUILD_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
let backend_name = matches.opt_str("backend");
|
let backend_name = matches.opt_str("backend");
|
||||||
|
@ -329,7 +345,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
.map(|port| port.parse::<u16>().unwrap())
|
.map(|port| port.parse::<u16>().unwrap())
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
let name = matches.opt_str("name").unwrap();
|
let name = matches.opt_str("name").unwrap_or("Librespot".to_string());
|
||||||
|
|
||||||
let credentials = {
|
let credentials = {
|
||||||
let cached_credentials = cache.as_ref().and_then(Cache::credentials);
|
let cached_credentials = cache.as_ref().and_then(Cache::credentials);
|
||||||
|
@ -352,7 +368,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
let device_id = device_id(&name);
|
let device_id = device_id(&name);
|
||||||
|
|
||||||
SessionConfig {
|
SessionConfig {
|
||||||
user_agent: version::version_string(),
|
user_agent: version::VERSION_STRING.to_string(),
|
||||||
device_id,
|
device_id,
|
||||||
proxy: matches.opt_str("proxy").or_else(|| std::env::var("http_proxy").ok()).map(
|
proxy: matches.opt_str("proxy").or_else(|| std::env::var("http_proxy").ok()).map(
|
||||||
|s| {
|
|s| {
|
||||||
|
|
Loading…
Reference in a new issue