diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index ca5c2556fae6e..d4e60d86ac777 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -24,6 +24,10 @@ parking_lot = "0.11.0" [target.'cfg(target_arch = "wasm32")'.dependencies] rodio = { version = "0.14", default-features = false, features = ["wasm-bindgen"] } +[dev-dependencies] +# bevy +bevy_internal = { path = "../bevy_internal", version = "0.5.0" } + [features] mp3 = ["rodio/mp3"] flac = ["rodio/flac"] diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 7a7fe12b3f99f..1a8ae32b88d9c 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -3,26 +3,36 @@ use bevy_asset::{Asset, Handle}; use parking_lot::RwLock; use std::{collections::VecDeque, fmt}; -/// The external struct used to play audio -pub struct Audio

+/// Use this resource to play audio +/// +/// ``` +/// # use bevy_ecs::system::Res; +/// # use bevy_asset::AssetServer; +/// # use bevy_audio::Audio; +/// fn play_audio_system(asset_server: Res, audio: Res

+impl fmt::Debug for Audio where - P: Decodable, + Source: Decodable, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Audio").field("queue", &self.queue).finish() } } -impl

Default for Audio

+impl Default for Audio where - P: Asset + Decodable, + Source: Asset + Decodable, { fn default() -> Self { Self { @@ -31,13 +41,21 @@ where } } -impl

Audio

+impl Audio where - P: Asset + Decodable, -

::Decoder: rodio::Source + Send + Sync, - <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, + Source: Asset + Decodable, { - pub fn play(&self, audio_source: Handle

) { + /// Play audio from a [`Handle`] to the audio source + /// + /// ``` + /// # use bevy_ecs::system::Res; + /// # use bevy_asset::AssetServer; + /// # use bevy_audio::Audio; + /// fn play_audio_system(asset_server: Res, audio: Res

+pub struct AudioOutput where - P: Decodable, + Source: Decodable, { _stream: Option, stream_handle: Option, - phantom: PhantomData

, + phantom: PhantomData, } -impl

Default for AudioOutput

+impl Default for AudioOutput where - P: Decodable, + Source: Decodable, { fn default() -> Self { if let Ok((stream, stream_handle)) = OutputStream::try_default() { @@ -37,13 +37,11 @@ where } } -impl

AudioOutput

+impl AudioOutput where - P: Asset + Decodable, -

::Decoder: rodio::Source + Send + Sync, - <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, + Source: Asset + Decodable, { - fn play_source(&self, audio_source: &P) { + fn play_source(&self, audio_source: &Source) { if let Some(stream_handle) = &self.stream_handle { let sink = Sink::try_new(stream_handle).unwrap(); sink.append(audio_source.decoder()); @@ -51,7 +49,7 @@ where } } - fn try_play_queued(&self, audio_sources: &Assets

, audio: &mut Audio

) { + fn try_play_queued(&self, audio_sources: &Assets, audio: &mut Audio) { let mut queue = audio.queue.write(); let len = queue.len(); let mut i = 0; @@ -69,17 +67,15 @@ where } /// Plays audio currently queued in the [`Audio`] resource through the [`AudioOutput`] resource -pub fn play_queued_audio_system(world: &mut World) +pub fn play_queued_audio_system(world: &mut World) where - P: Decodable, -

::Decoder: rodio::Source + Send + Sync, - <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, + Source: Decodable, { let world = world.cell(); - let audio_output = world.get_non_send::>().unwrap(); - let mut audio = world.get_resource_mut::>().unwrap(); + let audio_output = world.get_non_send::>().unwrap(); + let mut audio = world.get_resource_mut::>().unwrap(); - if let Some(audio_sources) = world.get_resource::>() { + if let Some(audio_sources) = world.get_resource::>() { audio_output.try_play_queued(&*audio_sources, &mut *audio); }; } diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 0aa9d95f186bc..823526046c7c3 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -8,6 +8,7 @@ use std::{io::Cursor, sync::Arc}; #[derive(Debug, Clone, TypeUuid)] #[uuid = "7a14806a-672b-443b-8d16-4f18afefa463"] pub struct AudioSource { + /// Raw data of the audio source pub bytes: Arc<[u8]>, } @@ -17,11 +18,18 @@ impl AsRef<[u8]> for AudioSource { } } -/// Loads mp3 files as [`AudioSource`] [`Assets`](bevy_asset::Assets) +/// Loads files as [`AudioSource`] [`Assets`](bevy_asset::Assets) +/// +/// This asset loader supports different audio formats based on the enable Bevy features. +/// The feature `bevy/vorbis` enables loading from `.ogg` files and is enabled by default. +/// Other file endings can be loaded from with additional features: +/// `.mp3` with `bevy/mp3` +/// `.flac` with `bevy/flac` +/// `.wav` with `bevy/wav` #[derive(Default)] -pub struct Mp3Loader; +pub struct AudioLoader; -impl AssetLoader for Mp3Loader { +impl AssetLoader for AudioLoader { fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> BoxedFuture> { load_context.set_default_asset(LoadedAsset::new(AudioSource { bytes: bytes.into(), @@ -43,14 +51,20 @@ impl AssetLoader for Mp3Loader { } } +/// A type implementing this trait can be decoded as a rodio source pub trait Decodable: Send + Sync + 'static { - type Decoder; + /// The decoder that can decode the implemeting type + type Decoder: rodio::Source + Send + Sync + Iterator; + /// A single value given by the decoder + type DecoderItem: rodio::Sample + Send + Sync; + /// Build and return a [`Self::Decoder`] for the implementing type fn decoder(&self) -> Self::Decoder; } impl Decodable for AudioSource { type Decoder = rodio::Decoder>; + type DecoderItem = > as Iterator>::Item; fn decoder(&self) -> Self::Decoder { rodio::Decoder::new(Cursor::new(self.clone())).unwrap() diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 5f44d68f9f40b..0bf910cbba9be 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -1,7 +1,38 @@ +//! Audio support for the game engine Bevy +//! +//! ``` +//! # use bevy_ecs::{system::Res, event::EventWriter}; +//! # use bevy_audio::{Audio, AudioPlugin}; +//! # use bevy_asset::{AssetPlugin, AssetServer}; +//! # use bevy_app::{App, AppExit}; +//! # use bevy_internal::MinimalPlugins; +//! fn main() { +//! App::new() +//! .add_plugins(MinimalPlugins) +//! .add_plugin(AssetPlugin) +//! .add_plugin(AudioPlugin) +//! # .add_system(stop) +//! .add_startup_system(play_background_audio) +//! .run(); +//! } +//! +//! fn play_background_audio(asset_server: Res, audio: Res