-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Merged by Bors] - Audio control - play, pause, volume, speed, loop #3948
Changes from all commits
5ac2cd3
04afb00
84947c7
2778876
2e87fa0
3b4d4bb
1ce4a21
563fc59
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,9 @@ | ||
use crate::{Audio, AudioSource, Decodable}; | ||
use bevy_asset::{Asset, Assets}; | ||
use bevy_ecs::world::World; | ||
use bevy_reflect::TypeUuid; | ||
use bevy_utils::tracing::warn; | ||
use rodio::{OutputStream, OutputStreamHandle, Sink}; | ||
use rodio::{OutputStream, OutputStreamHandle, Sink, Source}; | ||
use std::marker::PhantomData; | ||
|
||
/// Used internally to play audio on the current "audio device" | ||
|
@@ -41,25 +42,39 @@ impl<Source> AudioOutput<Source> | |
where | ||
Source: Asset + Decodable, | ||
{ | ||
fn play_source(&self, audio_source: &Source) { | ||
fn play_source(&self, audio_source: &Source, repeat: bool) -> Option<Sink> { | ||
mockersf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if let Some(stream_handle) = &self.stream_handle { | ||
let sink = Sink::try_new(stream_handle).unwrap(); | ||
sink.append(audio_source.decoder()); | ||
sink.detach(); | ||
if repeat { | ||
sink.append(audio_source.decoder().repeat_infinite()); | ||
} else { | ||
sink.append(audio_source.decoder()); | ||
} | ||
Some(sink) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn try_play_queued(&self, audio_sources: &Assets<Source>, audio: &mut Audio<Source>) { | ||
fn try_play_queued( | ||
&self, | ||
audio_sources: &Assets<Source>, | ||
audio: &mut Audio<Source>, | ||
sinks: &mut Assets<AudioSink>, | ||
) { | ||
let mut queue = audio.queue.write(); | ||
let len = queue.len(); | ||
let mut i = 0; | ||
while i < len { | ||
let audio_source_handle = queue.pop_back().unwrap(); | ||
if let Some(audio_source) = audio_sources.get(&audio_source_handle) { | ||
self.play_source(audio_source); | ||
let config = queue.pop_front().unwrap(); | ||
if let Some(audio_source) = audio_sources.get(&config.source_handle) { | ||
if let Some(sink) = self.play_source(audio_source, config.repeat) { | ||
// don't keep the strong handle. there is no way to return it to the user here as it is async | ||
let _ = sinks.set(config.sink_handle, AudioSink { sink: Some(sink) }); | ||
} | ||
} else { | ||
// audio source hasn't loaded yet. add it back to the queue | ||
queue.push_front(audio_source_handle); | ||
queue.push_back(config); | ||
} | ||
i += 1; | ||
} | ||
|
@@ -74,8 +89,101 @@ where | |
let world = world.cell(); | ||
let audio_output = world.get_non_send::<AudioOutput<Source>>().unwrap(); | ||
let mut audio = world.get_resource_mut::<Audio<Source>>().unwrap(); | ||
let mut sinks = world.get_resource_mut::<Assets<AudioSink>>().unwrap(); | ||
|
||
if let Some(audio_sources) = world.get_resource::<Assets<Source>>() { | ||
audio_output.try_play_queued(&*audio_sources, &mut *audio); | ||
audio_output.try_play_queued(&*audio_sources, &mut *audio, &mut *sinks); | ||
}; | ||
} | ||
|
||
/// Asset controlling the playback of a sound | ||
/// | ||
/// ``` | ||
/// # use bevy_ecs::system::{Local, Res}; | ||
/// # use bevy_asset::{Assets, Handle}; | ||
/// # use bevy_audio::AudioSink; | ||
/// // Execution of this system should be controlled by a state or input, | ||
/// // otherwise it would just toggle between play and pause every frame. | ||
/// fn pause( | ||
mockersf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// audio_sinks: Res<Assets<AudioSink>>, | ||
/// music_controller: Local<Handle<AudioSink>>, | ||
/// ) { | ||
/// if let Some(sink) = audio_sinks.get(&*music_controller) { | ||
/// if sink.is_paused() { | ||
/// sink.play() | ||
/// } else { | ||
/// sink.pause() | ||
/// } | ||
/// } | ||
/// } | ||
/// ``` | ||
Comment on lines
+101
to
+119
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This example is missing the assertion that the fn is a system. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think that it's needed: the only thing new in this system is the kind of asset, but systems with those parameters are already tested elsewhere. and if |
||
/// | ||
#[derive(TypeUuid)] | ||
#[uuid = "8BEE570C-57C2-4FC0-8CFB-983A22F7D981"] | ||
pub struct AudioSink { | ||
// This field is an Option in order to allow us to have a safe drop that will detach the sink. | ||
// It will never be None during its life | ||
sink: Option<Sink>, | ||
} | ||
|
||
impl Drop for AudioSink { | ||
fn drop(&mut self) { | ||
self.sink.take().unwrap().detach(); | ||
} | ||
} | ||
|
||
impl AudioSink { | ||
/// Gets the volume of the sound. | ||
/// | ||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` | ||
/// will multiply each sample by this value. | ||
pub fn volume(&self) -> f32 { | ||
self.sink.as_ref().unwrap().volume() | ||
} | ||
|
||
/// Changes the volume of the sound. | ||
/// | ||
/// The value `1.0` is the "normal" volume (unfiltered input). Any value other than `1.0` | ||
/// will multiply each sample by this value. | ||
pub fn set_volume(&self, volume: f32) { | ||
self.sink.as_ref().unwrap().set_volume(volume); | ||
} | ||
|
||
/// Gets the speed of the sound. | ||
/// | ||
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` | ||
/// will change the play speed of the sound. | ||
pub fn speed(&self) -> f32 { | ||
self.sink.as_ref().unwrap().speed() | ||
} | ||
|
||
/// Changes the speed of the sound. | ||
/// | ||
/// The value `1.0` is the "normal" speed (unfiltered input). Any value other than `1.0` | ||
/// will change the play speed of the sound. | ||
pub fn set_speed(&self, speed: f32) { | ||
self.sink.as_ref().unwrap().set_speed(speed); | ||
} | ||
|
||
/// Resumes playback of a paused sink. | ||
/// | ||
/// No effect if not paused. | ||
pub fn play(&self) { | ||
self.sink.as_ref().unwrap().play(); | ||
} | ||
|
||
/// Pauses playback of this sink. | ||
/// | ||
/// No effect if already paused. | ||
/// A paused sink can be resumed with [`play`](Self::play). | ||
pub fn pause(&self) { | ||
self.sink.as_ref().unwrap().pause(); | ||
} | ||
|
||
/// Is this sink paused? | ||
/// | ||
/// Sinks can be paused and resumed using [`pause`](Self::pause) and [`play`](Self::play). | ||
pub fn is_paused(&self) -> bool { | ||
self.sink.as_ref().unwrap().is_paused() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
use bevy::{audio::AudioSink, prelude::*}; | ||
|
||
/// This example illustrates how to load and play an audio file, and control how it's played | ||
mockersf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn main() { | ||
App::new() | ||
mockersf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
.add_plugins(DefaultPlugins) | ||
.add_startup_system(setup) | ||
.add_system(update_speed) | ||
.add_system(pause) | ||
.add_system(volume) | ||
.run(); | ||
} | ||
|
||
fn setup( | ||
mut commands: Commands, | ||
asset_server: Res<AssetServer>, | ||
audio: Res<Audio>, | ||
audio_sinks: Res<Assets<AudioSink>>, | ||
) { | ||
let music = asset_server.load("sounds/Windless Slopes.ogg"); | ||
let handle = audio_sinks.get_handle(audio.play(music)); | ||
commands.insert_resource(MusicController(handle)); | ||
} | ||
|
||
struct MusicController(Handle<AudioSink>); | ||
|
||
fn update_speed( | ||
audio_sinks: Res<Assets<AudioSink>>, | ||
music_controller: Res<MusicController>, | ||
time: Res<Time>, | ||
) { | ||
if let Some(sink) = audio_sinks.get(&music_controller.0) { | ||
sink.set_speed(((time.seconds_since_startup() / 5.0).sin() as f32 + 1.0).max(0.1)); | ||
} | ||
} | ||
|
||
fn pause( | ||
keyboard_input: Res<Input<KeyCode>>, | ||
audio_sinks: Res<Assets<AudioSink>>, | ||
music_controller: Res<MusicController>, | ||
) { | ||
if keyboard_input.just_pressed(KeyCode::Space) { | ||
if let Some(sink) = audio_sinks.get(&music_controller.0) { | ||
if sink.is_paused() { | ||
sink.play() | ||
} else { | ||
sink.pause() | ||
} | ||
} | ||
} | ||
} | ||
|
||
fn volume( | ||
keyboard_input: Res<Input<KeyCode>>, | ||
audio_sinks: Res<Assets<AudioSink>>, | ||
music_controller: Res<MusicController>, | ||
) { | ||
if let Some(sink) = audio_sinks.get(&music_controller.0) { | ||
if keyboard_input.just_pressed(KeyCode::Plus) { | ||
sink.set_volume(sink.volume() + 0.1); | ||
} else if keyboard_input.just_pressed(KeyCode::Minus) { | ||
sink.set_volume(sink.volume() - 0.1); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To me this looks like a manual impl of Debug that should be identical to the derive.
Am i missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the automatic derive needs
Source
to beDebug
, but not a manual impl... I'm not quite sure the debug impl is actually useful though, I added it to keep existing functionality