Skip to content

Commit

Permalink
Migrate to rodio 0.12 using thread local resources (#692)
Browse files Browse the repository at this point in the history
Migrate to rodio 0.12 using thread local resources
  • Loading branch information
Dash-L authored Oct 20, 2020
1 parent 67f87e1 commit 0dbba3e
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 43 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
- New methods `Color::rgb_linear` and `Color::rgba_linear` will accept colors already in linear sRGB (the old behavior)
- Individual color-components must now be accessed through setters and getters: `.r`, `.g`, `.b`, `.a`, `.set_r`, `.set_g`, `.set_b`, `.set_a`, and the corresponding methods with the `*_linear` suffix.
- Despawning an entity multiple times causes a debug-level log message to be emitted instead of a panic [649] [651]
- Breaking Change: Migrated to rodio 0.12, this means:
- Playing an mp3 no longer sometimes panics in debug mode
- New method of playing audio can be found in the audio example (an intermediary `Audio` struct is used instead of `AudioOutput` directly)

[696]: https://github.com/bevyengine/bevy/pull/696
[689]: https://github.com/bevyengine/bevy/pull/689
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_audio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ bevy_utils = { path = "../bevy_utils", version = "0.2.1" }

# other
anyhow = "1.0"
rodio = { version = "0.11", default-features = false }
rodio = { version = "0.12", default-features = false }
parking_lot = "0.11.0"

[features]
Expand Down
43 changes: 43 additions & 0 deletions crates/bevy_audio/src/audio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use crate::{AudioSource, Decodable};
use bevy_asset::Handle;
use parking_lot::RwLock;
use std::{collections::VecDeque, fmt};

/// The external struct used to play audio
pub struct Audio<P = AudioSource>
where
P: Decodable,
{
pub queue: RwLock<VecDeque<Handle<P>>>,
}

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

impl<P> Default for Audio<P>
where
P: Decodable,
{
fn default() -> Self {
Self {
queue: Default::default(),
}
}
}

impl<P> Audio<P>
where
P: Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
{
pub fn play(&self, audio_source: Handle<P>) {
self.queue.write().push_front(audio_source);
}
}
63 changes: 27 additions & 36 deletions crates/bevy_audio/src/audio_output.rs
Original file line number Diff line number Diff line change
@@ -1,38 +1,30 @@
use crate::{AudioSource, Decodable};
use bevy_asset::{Asset, Assets, Handle};
use bevy_ecs::Res;
use parking_lot::RwLock;
use rodio::{Device, Sink};
use std::{collections::VecDeque, fmt};
use crate::{Audio, AudioSource, Decodable};
use bevy_asset::{Asset, Assets};
use bevy_ecs::{Resources, World};
use rodio::{OutputStream, OutputStreamHandle, Sink};
use std::marker::PhantomData;

/// Used to play audio on the current "audio device"
/// Used internally to play audio on the current "audio device"
pub struct AudioOutput<P = AudioSource>
where
P: Decodable,
{
device: Device,
queue: RwLock<VecDeque<Handle<P>>>,
}

impl<P> fmt::Debug for AudioOutput<P>
where
P: Decodable,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("AudioOutput")
.field("queue", &self.queue)
.finish()
}
_stream: OutputStream,
stream_handle: OutputStreamHandle,
phantom: PhantomData<P>,
}

impl<P> Default for AudioOutput<P>
where
P: Decodable,
{
fn default() -> Self {
let (stream, stream_handle) = OutputStream::try_default().unwrap();

Self {
device: rodio::default_output_device().unwrap(),
queue: Default::default(),
_stream: stream,
stream_handle,
phantom: PhantomData,
}
}
}
Expand All @@ -43,18 +35,14 @@ where
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
{
pub fn play_source(&self, audio_source: &P) {
let sink = Sink::new(&self.device);
fn play_source(&self, audio_source: &P) {
let sink = Sink::try_new(&self.stream_handle).unwrap();
sink.append(audio_source.decoder());
sink.detach();
}

pub fn play(&self, audio_source: Handle<P>) {
self.queue.write().push_front(audio_source);
}

pub fn try_play_queued(&self, audio_sources: &Assets<P>) {
let mut queue = self.queue.write();
fn try_play_queued(&self, audio_sources: &Assets<P>, audio: &mut Audio<P>) {
let mut queue = audio.queue.write();
let len = queue.len();
let mut i = 0;
while i < len {
Expand All @@ -70,14 +58,17 @@ where
}
}

/// Plays audio currently queued in the [AudioOutput] resource
pub fn play_queued_audio_system<P: Asset>(
audio_sources: Res<Assets<P>>,
audio_output: Res<AudioOutput<P>>,
) where
/// Plays audio currently queued in the [Audio] resource through the [AudioOutput] resource
pub fn play_queued_audio_system<P: Asset>(_world: &mut World, resources: &mut Resources)
where
P: Decodable,
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
{
audio_output.try_play_queued(&audio_sources);
let audio_output = resources.get_thread_local::<AudioOutput<P>>().unwrap();
let mut audio = resources.get_mut::<Audio<P>>().unwrap();

if let Some(audio_sources) = resources.get::<Assets<P>>() {
audio_output.try_play_queued(&*audio_sources, &mut *audio);
}
}
11 changes: 7 additions & 4 deletions crates/bevy_audio/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
mod audio;
mod audio_output;
mod audio_source;

pub use audio::*;
pub use audio_output::*;
pub use audio_source::*;

pub mod prelude {
pub use crate::{AudioOutput, AudioSource, Decodable};
pub use crate::{Audio, AudioOutput, AudioSource, Decodable};
}

use bevy_app::prelude::*;
use bevy_asset::AddAsset;
use bevy_ecs::IntoQuerySystem;
use bevy_ecs::IntoThreadLocalSystem;

/// Adds support for audio playback to an App
#[derive(Default)]
pub struct AudioPlugin;

impl Plugin for AudioPlugin {
fn build(&self, app: &mut AppBuilder) {
app.init_resource::<AudioOutput<AudioSource>>()
app.init_thread_local_resource::<AudioOutput<AudioSource>>()
.add_asset::<AudioSource>()
.init_asset_loader::<Mp3Loader>()
.init_resource::<Audio<AudioSource>>()
.add_system_to_stage(
stage::POST_UPDATE,
play_queued_audio_system::<AudioSource>.system(),
play_queued_audio_system::<AudioSource>.thread_local_system(),
);
}
}
4 changes: 2 additions & 2 deletions examples/audio/audio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fn main() {
.run();
}

fn setup(asset_server: Res<AssetServer>, audio_output: Res<AudioOutput>) {
fn setup(asset_server: Res<AssetServer>, audio: Res<Audio>) {
let music = asset_server.load("sounds/Windless Slopes.mp3");
audio_output.play(music);
audio.play(music);
}

0 comments on commit 0dbba3e

Please sign in to comment.