Migrate to rodio 0.12 using thread local resources (#692)

Migrate to rodio 0.12 using thread local resources
This commit is contained in:
Dashiell Elliott 2020-10-20 14:44:50 -04:00 committed by GitHub
parent 67f87e1d2b
commit 0dbba3efff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 83 additions and 43 deletions

View file

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

View file

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

View file

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

View file

@ -1,28 +1,17 @@
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>
@ -30,9 +19,12 @@ 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,
}
}
}
@ -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 {
@ -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);
}
}

View file

@ -1,16 +1,18 @@
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)]
@ -18,12 +20,13 @@ 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(),
);
}
}

View file

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