Merge branch 'dev' into tokio_migration

This commit is contained in:
johannesd3 2021-04-09 10:34:59 +02:00
commit f5274f5ada
10 changed files with 141 additions and 138 deletions

View file

@ -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]

View file

@ -13,7 +13,7 @@ curl https://sh.rustup.rs -sSf | sh
Follow any prompts it gives you to install Rust. Once thats done, Rust's standard tools should be setup and ready to use. Follow any prompts it gives you to install Rust. Once thats 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
View file

@ -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"

View file

@ -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 {
debug!("skipping packet");
continue; 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();

View file

@ -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);

View file

@ -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);
} }

View file

@ -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,

View file

@ -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();

View file

@ -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")
}

View file

@ -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| {