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:
os: [ubuntu-latest]
toolchain:
- 1.42.0 # MSRV (Minimum supported rust version)
- 1.41.1 # MSRV (Minimum supported rust version)
- stable
- beta
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.
*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`
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]]
name = "backtrace"
version = "0.3.56"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d117600f438b1707d4e4ae15d3595657288f8235a0eb593e80ecc98ab34e1bc"
checksum = "ef5140344c85b01f9bbb4d4b7288a8aa4b3287ccef913a14bcc78a1063623598"
dependencies = [
"addr2line",
"cfg-if 1.0.0",
@ -1718,9 +1718,9 @@ dependencies = [
[[package]]
name = "object"
version = "0.23.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
[[package]]
name = "oboe"

View file

@ -5,75 +5,32 @@ use std::fmt;
use std::io::{Read, Seek};
use std::time::{SystemTime, UNIX_EPOCH};
fn write_headers<T: Read + Seek>(
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>
fn get_header<T>(code: u8, rdr: &mut PacketReader<T>) -> Result<Box<[u8]>, PassthroughError>
where
T: Read + Seek,
{
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];
debug!("Vorbis header type{}", &pkt_type);
// all headers are mandatory
if pkt_type != code {
return Err(PassthroughError(OggReadError::InvalidData));
}
// headers keep original granule number
let absgp_page = pck.absgp_page();
wtr.write_packet(
pck.data.into_boxed_slice(),
*stream_serial,
info,
absgp_page,
)
.unwrap();
Ok(*stream_serial)
Ok(pck.data.into_boxed_slice())
}
pub struct PassthroughDecoder<R: Read + Seek> {
rdr: PacketReader<R>,
wtr: PacketWriter<Vec<u8>>,
lastgp_page: Option<u64>,
absgp_page: u64,
eos: bool,
bos: bool,
ofsgp_page: u64,
stream_serial: u32,
ident: Box<[u8]>,
comment: Box<[u8]>,
setup: Box<[u8]>,
}
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`.
pub fn new(rdr: R) -> Result<Self, PassthroughError> {
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);
// 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 {
rdr,
wtr,
lastgp_page: Some(0),
absgp_page: 0,
wtr: PacketWriter::new(Vec::new()),
ofsgp_page: 0,
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> {
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
info!("Seeking to {}", ms);
self.lastgp_page = match ms {
0 => Some(0),
_ => None,
// add an eos to previous stream if missing
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
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())),
}
}
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 {
let pck = match self.rdr.read_packet() {
Ok(Some(pck)) => pck,
Ok(None) | Err(OggReadError::NoCapturePatternFound) => {
info!("end of streaming");
return Ok(None);
}
Err(err) => return Err(AudioError::PassthroughError(err.into())),
};
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
if skip {
if *lastgp_page == pckgp_page {
debug!("skipping packet");
// skip till we have audio and a calculable granule position
if pckgp_page == 0 || pckgp_page == self.ofsgp_page {
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
let inf = if pck.last_in_stream() {
self.lastgp_page = Some(0);
self.eos = true;
PacketWriteEndInfo::EndStream
} else if pck.last_in_page() {
PacketWriteEndInfo::EndPage
@ -158,7 +171,7 @@ impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
pck.data.into_boxed_slice(),
self.stream_serial,
inf,
self.absgp_page,
pckgp_page - self.ofsgp_page,
)
.unwrap();

View file

@ -106,7 +106,7 @@ fn initial_state() -> State {
fn initial_device_state(config: ConnectConfig) -> DeviceState {
{
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_can_play(true);
msg.set_volume(0);

View file

@ -12,5 +12,6 @@ fn main() {
.take(8)
.map(char::from)
.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 {
let device_id = uuid::Uuid::new_v4().to_hyphenated().to_string();
SessionConfig {
user_agent: crate::version::version_string(),
user_agent: crate::version::VERSION_STRING.to_string(),
device_id,
proxy: None,
ap_port: None,

View file

@ -131,13 +131,13 @@ pub async fn authenticate(
.mut_system_info()
.set_system_information_string(format!(
"librespot_{}_{}",
version::short_sha(),
version::build_id()
version::SHA_SHORT,
version::BUILD_ID
));
packet
.mut_system_info()
.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 data = packet.write_to_bytes().unwrap();

View file

@ -1,44 +1,17 @@
pub fn version_string() -> String {
format!("librespot-{}", short_sha())
}
/// Version string of the form "librespot-<sha>"
pub const VERSION_STRING: &str = concat!("librespot-", env!("VERGEN_SHA_SHORT"));
// Generate a timestamp representing now (UTC) in RFC3339 format.
pub fn now() -> &'static str {
env!("VERGEN_BUILD_TIMESTAMP")
}
/// Generate a timestamp string representing the build date (UTC).
pub const BUILD_DATE: &str = env!("VERGEN_BUILD_DATE");
// Generate a timstamp string representing now (UTC).
pub fn short_now() -> &'static str {
env!("VERGEN_BUILD_DATE")
}
/// Short sha of the latest git commit.
pub const SHA_SHORT: &str = env!("VERGEN_SHA_SHORT");
// Generate a SHA string
pub fn sha() -> &'static str {
env!("VERGEN_SHA")
}
/// Date of the latest git commit.
pub const COMMIT_DATE: &str = env!("VERGEN_COMMIT_DATE");
// Generate a short SHA string
pub fn short_sha() -> &'static str {
env!("VERGEN_SHA_SHORT")
}
/// Librespot crate version.
pub const SEMVER: &str = env!("CARGO_PKG_VERSION");
// Generate the commit date string
pub fn commit_date() -> &'static str {
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")
}
/// A random build id.
pub const BUILD_ID: &str = env!("LIBRESPOT_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)]
struct Setup {
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",
"SYTEMCACHE",
).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(
"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("v", "verbose", "Enable verbose output")
.optflag("V", "version", "Display librespot version string")
.optopt("u", "username", "Username to sign in with", "USERNAME")
.optopt("p", "password", "Password", "PASSWORD")
.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");
setup_logging(verbose);
info!(
"librespot {} ({}). Built on {}. Build ID: {}",
version::short_sha(),
version::commit_date(),
version::short_now(),
version::build_id()
"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
);
let backend_name = matches.opt_str("backend");
@ -329,7 +345,7 @@ fn setup(args: &[String]) -> Setup {
.map(|port| port.parse::<u16>().unwrap())
.unwrap_or(0);
let name = matches.opt_str("name").unwrap();
let name = matches.opt_str("name").unwrap_or("Librespot".to_string());
let 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);
SessionConfig {
user_agent: version::version_string(),
user_agent: version::VERSION_STRING.to_string(),
device_id,
proxy: matches.opt_str("proxy").or_else(|| std::env::var("http_proxy").ok()).map(
|s| {