diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1f661ffd..f1ed0faf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: pull_request: push: - branches: [ main, master ] + branches: [main, master] env: RUSTFLAGS: "-C debuginfo=0 -D warnings" @@ -18,8 +18,8 @@ jobs: strategy: fail-fast: false matrix: - os: [ macos-latest, windows-latest, ubuntu-latest ] - toolchain: [ stable, beta, nightly ] + os: [macos-latest, windows-latest, ubuntu-latest] + toolchain: [stable, beta, nightly] include: - os: macos-latest MACOS: true @@ -49,6 +49,5 @@ jobs: # `cargo test` does not check benchmarks and `cargo test --all-targets` excludes # documentation tests. Therefore, we need an additional docs test command here. - run: cargo test --doc - # Check minimal build. `tests/seek.rs` fails if there are no decoders at all, - # adding one to make the tests check pass. - - run: cargo check --tests --lib --no-default-features --features mp3 + # Check minimal build. + - run: cargo check --tests --lib --no-default-features diff --git a/Cargo.toml b/Cargo.toml index 28d1cf01..6630b82e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,10 @@ minimp3_fixed = { version = "0.5.4", optional = true } symphonia = { version = "0.5.4", optional = true, default-features = false } crossbeam-channel = { version = "0.5.15", optional = true } -rand = { version = "0.9.0", features = ["small_rng", "os_rng"], optional = true } +rand = { version = "0.9.0", features = [ + "small_rng", + "os_rng", +], optional = true } tracing = { version = "0.1.40", optional = true } atomic_float = { version = "1.1.0", optional = true } @@ -72,27 +75,91 @@ divan = "0.1.14" [[bench]] name = "effects" harness = false +required-features = ["wav"] [[bench]] name = "conversions" harness = false +required-features = ["wav"] [[bench]] name = "resampler" harness = false +required-features = ["wav"] [[bench]] name = "pipeline" harness = false +required-features = ["wav"] + +[[example]] +name = "automatic_gain_control" +required-features = ["playback", "flac"] + +[[example]] +name = "basic" +required-features = ["playback", "vorbis"] + +[[example]] +name = "callback_on_end" +required-features = ["playback", "wav"] + +[[example]] +name = "custom_config" +required-features = ["playback", "wav"] + +[[example]] +name = "error_callback" +required-features = ["playback"] + +[[example]] +name = "into_file" +required-features = ["mp3", "wav"] + +[[example]] +name = "low_pass" +required-features = ["playback", "wav"] + +[[example]] +name = "mix_multiple_sources" +required-features = ["playback"] [[example]] name = "music_m4a" -required-features = ["symphonia-isomp4", "symphonia-aac"] +required-features = ["playback", "symphonia-isomp4", "symphonia-aac"] + +[[example]] +name = "music_mp3" +required-features = ["playback", "mp3"] + +[[example]] +name = "music_ogg" +required-features = ["playback", "vorbis"] + +[[example]] +name = "music_wav" +required-features = ["playback", "wav"] [[example]] name = "noise_generator" -required-features = ["noise"] +required-features = ["playback", "noise"] [[example]] -name = "into_file" -required-features = ["wav", "mp3"] +name = "reverb" +required-features = ["playback", "vorbis"] + +[[example]] +name = "seek_mp3" +required-features = ["playback", "mp3"] + +[[example]] +name = "signal_generator" +required-features = ["playback"] + +[[example]] +name = "spatial" +required-features = ["playback", "vorbis"] + +[[example]] +name = "stereo" +required-features = ["playback", "vorbis"] diff --git a/examples/noise_generator.rs b/examples/noise_generator.rs index f22d3712..03a8a5d5 100644 --- a/examples/noise_generator.rs +++ b/examples/noise_generator.rs @@ -2,7 +2,6 @@ use std::error::Error; -#[cfg(feature = "noise")] fn main() -> Result<(), Box> { use rodio::source::{pink, white, Source}; use std::thread; @@ -29,9 +28,3 @@ fn main() -> Result<(), Box> { Ok(()) } - -#[cfg(not(feature = "noise"))] -fn main() { - println!("rodio has not been compiled with noise sources, use `--features noise` to enable this feature."); - println!("Exiting..."); -} diff --git a/src/decoder/mod.rs b/src/decoder/mod.rs index ef2486e3..ac0b4fc7 100644 --- a/src/decoder/mod.rs +++ b/src/decoder/mod.rs @@ -119,8 +119,14 @@ enum DecoderImpl { Mp3(mp3::Mp3Decoder), #[cfg(feature = "symphonia")] Symphonia(symphonia::SymphoniaDecoder, PhantomData), + // This variant is here just to satisfy the compiler when there are no decoders enabled. + // It is unreachable and should never be constructed. + #[allow(dead_code)] + None(Unreachable, PhantomData), } +enum Unreachable {} + impl DecoderImpl { #[inline] fn next(&mut self) -> Option { @@ -135,6 +141,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.next(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.next(), + DecoderImpl::None(_, _) => unreachable!(), } } @@ -151,6 +158,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.size_hint(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.size_hint(), + DecoderImpl::None(_, _) => unreachable!(), } } @@ -167,6 +175,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.current_span_len(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.current_span_len(), + DecoderImpl::None(_, _) => unreachable!(), } } @@ -183,6 +192,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.channels(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.channels(), + DecoderImpl::None(_, _) => unreachable!(), } } @@ -199,6 +209,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.sample_rate(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.sample_rate(), + DecoderImpl::None(_, _) => unreachable!(), } } @@ -221,6 +232,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.total_duration(), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.total_duration(), + DecoderImpl::None(_, _) => unreachable!(), } } @@ -237,6 +249,7 @@ impl DecoderImpl { DecoderImpl::Mp3(source) => source.try_seek(pos), #[cfg(feature = "symphonia")] DecoderImpl::Symphonia(source, PhantomData) => source.try_seek(pos), + DecoderImpl::None(_, _) => unreachable!(), } } } diff --git a/src/lib.rs b/src/lib.rs index 07457d91..18dfd2a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,8 @@ //! //! Here is a complete example of how you would play an audio file: //! -//! ```no_run +#![cfg_attr(not(feature = "playback"), doc = "```ignore")] +#![cfg_attr(feature = "playback", doc = "```no_run")] //! use std::fs::File; //! use rodio::{Decoder, OutputStream, source::Source}; //! @@ -34,7 +35,8 @@ //! ``` //! //! [`rodio::play()`](crate::play) helps to simplify the above -//! ```no_run +#![cfg_attr(not(feature = "playback"), doc = "```ignore")] +#![cfg_attr(feature = "playback", doc = "```no_run")] //! use std::fs::File; //! use std::io::BufReader; //! use rodio::{Decoder, OutputStream, source::Source}; @@ -63,7 +65,8 @@ //! To play a soung Create a [`Sink`] connect it to the output stream, //! and [`.append()`](Sink::append) your sound to it. //! -//! ```no_run +#![cfg_attr(not(feature = "playback"), doc = "```ignore")] +#![cfg_attr(feature = "playback", doc = "```no_run")] //! use std::time::Duration; //! use rodio::{OutputStream, Sink}; //! use rodio::source::{SineWave, Source}; @@ -96,7 +99,8 @@ //! //! Example: //! -//! ``` +#![cfg_attr(not(feature = "playback"), doc = "```ignore")] +#![cfg_attr(feature = "playback", doc = "```")] //! use rodio::Source; //! use std::time::Duration; //! @@ -147,7 +151,15 @@ //! on the number of sinks that can be created (except for the fact that creating too many will slow //! down your program). -#![cfg_attr(test, deny(missing_docs))] +#![cfg_attr( + any(test, not(feature = "playback")), + deny(missing_docs), + allow(dead_code), + allow(unused_imports), + allow(unused_variables), + allow(unreachable_code) +)] + #[cfg(feature = "playback")] pub use cpal::{ self, traits::DeviceTrait, Device, Devices, DevicesError, InputDevices, OutputDevices, diff --git a/src/source/speed.rs b/src/source/speed.rs index 5f47e8da..9454ed98 100644 --- a/src/source/speed.rs +++ b/src/source/speed.rs @@ -11,7 +11,8 @@ //! To speed up a source from sink all you need to do is call the `set_speed(factor: f32)` function //! For example, here is how you speed up your sound by using sink or playing raw: //! -//! ```no_run +#![cfg_attr(not(feature = "playback"), doc = "```ignore")] +#![cfg_attr(feature = "playback", doc = "```no_run")] //!# use std::fs::File; //!# use rodio::{Decoder, Sink, OutputStream, source::{Source, SineWave}}; //! @@ -28,7 +29,8 @@ //! std::thread::sleep(std::time::Duration::from_secs(5)); //! ``` //! Here is how you would do it using the sink: -//!```no_run +#![cfg_attr(not(feature = "playback"), doc = "```ignore")] +#![cfg_attr(feature = "playback", doc = "```no_run")] //! use rodio::source::{Source, SineWave}; //! let source = SineWave::new(440.0) //! .take_duration(std::time::Duration::from_secs_f32(20.25)) diff --git a/tests/flac_test.rs b/tests/flac_test.rs index 3db4c1f2..be61bd75 100644 --- a/tests/flac_test.rs +++ b/tests/flac_test.rs @@ -1,6 +1,6 @@ -#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] +#[cfg(any(feature = "flac", feature = "symphonia-flac"))] use rodio::Source; -#[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] +#[cfg(any(feature = "flac", feature = "symphonia-flac"))] use std::time::Duration; #[cfg(any(feature = "flac", feature = "symphonia-flac"))] @@ -12,8 +12,6 @@ fn test_flac_encodings() { // File is not just silence assert!(decoder.any(|x| x != 0.0)); - // Symphonia does not expose functionality to get the duration so this check must be disabled - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] assert_eq!(decoder.total_duration(), Some(Duration::from_secs(3))); // duration is calculated correctly // 24 bit FLAC file exported from Audacity (2 channels, various compression levels) @@ -21,7 +19,6 @@ fn test_flac_encodings() { let file = std::fs::File::open(format!("assets/audacity24bit_level{level}.flac")).unwrap(); let mut decoder = rodio::Decoder::try_from(file).unwrap(); assert!(!decoder.all(|x| x != 0.0)); - #[cfg(all(feature = "flac", not(feature = "symphonia-flac")))] assert_eq!(decoder.total_duration(), Some(Duration::from_secs(3))); } } diff --git a/tests/seek.rs b/tests/seek.rs index c4a402e0..64f0963a 100644 --- a/tests/seek.rs +++ b/tests/seek.rs @@ -1,3 +1,6 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + use rodio::{ChannelCount, Decoder, Source}; use rstest::rstest; use rstest_reuse::{self, *}; @@ -5,6 +8,17 @@ use std::io::{Read, Seek}; use std::path::Path; use std::time::Duration; +#[cfg(any( + feature = "flac", + feature = "minimp3", + feature = "symphonia-aac", + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[template] #[rstest] #[cfg_attr( @@ -37,6 +51,14 @@ fn all_decoders( ) { } +#[cfg(any( + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[template] #[rstest] #[cfg_attr( @@ -56,6 +78,16 @@ fn all_decoders( #[cfg_attr(feature = "symphonia-flac", case("flac", "symphonia"))] fn supported_decoders(#[case] format: &'static str, #[case] decoder_name: &'static str) {} +#[cfg(any( + feature = "flac", + feature = "minimp3", + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[apply(all_decoders)] #[trace] fn seek_returns_err_if_unsupported( @@ -68,6 +100,14 @@ fn seek_returns_err_if_unsupported( assert_eq!(res.is_ok(), supports_seek, "decoder: {decoder_name}"); } +#[cfg(any( + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[apply(supported_decoders)] #[trace] fn seek_beyond_end_saturates(#[case] format: &'static str, #[case] decoder_name: &'static str) { @@ -79,6 +119,14 @@ fn seek_beyond_end_saturates(#[case] format: &'static str, #[case] decoder_name: assert!(time_remaining(decoder) < Duration::from_secs(1)); } +#[cfg(any( + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[apply(supported_decoders)] #[trace] fn seek_results_in_correct_remaining_playtime( @@ -107,6 +155,14 @@ fn seek_results_in_correct_remaining_playtime( } } +#[cfg(any( + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[apply(supported_decoders)] #[trace] fn seek_possible_after_exausting_source( @@ -121,6 +177,14 @@ fn seek_possible_after_exausting_source( assert!(source.next().is_some()); } +#[cfg(any( + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[apply(supported_decoders)] #[trace] fn seek_does_not_break_channel_order( @@ -175,10 +239,7 @@ fn seek_does_not_break_channel_order( } } -fn second_channel_beep_range(source: &mut R) -> std::ops::Range -where - R: Iterator, -{ +fn second_channel_beep_range(source: &mut R) -> std::ops::Range { let channels = source.channels() as usize; let samples: Vec = source.by_ref().collect(); diff --git a/tests/total_duration.rs b/tests/total_duration.rs index fecaaa3f..0738c86d 100644 --- a/tests/total_duration.rs +++ b/tests/total_duration.rs @@ -1,3 +1,6 @@ +#![allow(dead_code)] +#![allow(unused_imports)] + use std::io::{Read, Seek}; use std::path::Path; use std::time::Duration; @@ -7,6 +10,17 @@ use rodio::{Decoder, Source}; use rstest::rstest; use rstest_reuse::{self, *}; +#[cfg(any( + feature = "flac", + feature = "minimp3", + feature = "symphonia-aac", + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[template] #[rstest] #[cfg_attr( @@ -61,6 +75,16 @@ fn get_music(format: &str) -> Decoder { .unwrap() } +#[cfg(any( + feature = "flac", + feature = "minimp3", + feature = "symphonia-flac", + feature = "symphonia-mp3", + feature = "symphonia-isomp4", + feature = "symphonia-ogg", + feature = "symphonia-wav", + feature = "wav", +))] #[apply(all_decoders)] #[trace] fn decoder_returns_total_duration( @@ -72,9 +96,7 @@ fn decoder_returns_total_duration( let decoder = get_music(format); let res = decoder .total_duration() - .expect(&format!( - "did not return a total duration, decoder: {decoder_name}" - )) + .unwrap_or_else(|| panic!("did not return a total duration, decoder: {decoder_name}")) .as_secs_f64(); let correct_duration = correct_duration.as_secs_f64(); let abs_diff = (res - correct_duration).abs();