Allow restricting audio playback to a custom region (#19400)
# Objective Adds the ability to restrict playback of an audio source to a certain region in time. In other words, you can set a custom start position and duration for the audio clip. These options are set via the `PlaybackSettings` component, and it works on all kinds of audio sources. ## Solution - Added public `start_position` and `duration` fields to `PlaybackSettings`, both of type `Option<std::time::Duration>`. - Used rodio's `Source::skip_duration` and `Source::take_duration` functions to implement start position and duration, respectively. - If the audio is looping, it interacts as you might expect - the loop will start at the start position and end after the duration. - If the start position is None (the default value), the audio will start from the beginning, like normal. Similarly, if the duration is None (default), the audio source will play for as long as possible. ## Testing I tried adding a custom start position to all the existing audio examples to test a bunch of different audio sources and settings, and they all worked fine. I verified that it skips the right amount of time, and that it skips the entire audio clip if the start position is longer than the length of the clip. All my testing was done on Fedora Linux. Update: I did similar testing for duration, and ensured that the two options worked together in combination and interacted well with looping audio. --- ## Showcase ```rust // Play a 10 second segment of a song, starting at 0:30.5 commands.spawn(( AudioPlayer::new(song_handle), PlaybackSettings::LOOP .with_start_position(Duration::from_secs_f32(30.5)) .with_duration(Duration::from_secs(10)) )); ```
This commit is contained in:
parent
fe678e1eeb
commit
59ef10562a
@ -57,6 +57,16 @@ pub struct PlaybackSettings {
|
||||
/// Optional scale factor applied to the positions of this audio source and the listener,
|
||||
/// overriding the default value configured on [`AudioPlugin::default_spatial_scale`](crate::AudioPlugin::default_spatial_scale).
|
||||
pub spatial_scale: Option<SpatialScale>,
|
||||
/// The point in time in the audio clip where playback should start. If set to `None`, it will
|
||||
/// play from the beginning of the clip.
|
||||
///
|
||||
/// If the playback mode is set to `Loop`, each loop will start from this position.
|
||||
pub start_position: Option<core::time::Duration>,
|
||||
/// How long the audio should play before stopping. If set, the clip will play for at most
|
||||
/// the specified duration. If set to `None`, it will play for as long as it can.
|
||||
///
|
||||
/// If the playback mode is set to `Loop`, each loop will last for this duration.
|
||||
pub duration: Option<core::time::Duration>,
|
||||
}
|
||||
|
||||
impl Default for PlaybackSettings {
|
||||
@ -81,6 +91,8 @@ impl PlaybackSettings {
|
||||
muted: false,
|
||||
spatial: false,
|
||||
spatial_scale: None,
|
||||
start_position: None,
|
||||
duration: None,
|
||||
};
|
||||
|
||||
/// Will play the associated audio source in a loop.
|
||||
@ -136,6 +148,18 @@ impl PlaybackSettings {
|
||||
self.spatial_scale = Some(spatial_scale);
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper to use a custom playback start position.
|
||||
pub const fn with_start_position(mut self, start_position: core::time::Duration) -> Self {
|
||||
self.start_position = Some(start_position);
|
||||
self
|
||||
}
|
||||
|
||||
/// Helper to use a custom playback duration.
|
||||
pub const fn with_duration(mut self, duration: core::time::Duration) -> Self {
|
||||
self.duration = Some(duration);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Settings for the listener for spatial audio sources.
|
||||
|
@ -156,12 +156,49 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
}
|
||||
};
|
||||
|
||||
let decoder = audio_source.decoder();
|
||||
|
||||
match settings.mode {
|
||||
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
|
||||
PlaybackMode::Loop => match (settings.start_position, settings.duration) {
|
||||
// custom start position and duration
|
||||
(Some(start_position), Some(duration)) => sink.append(
|
||||
decoder
|
||||
.skip_duration(start_position)
|
||||
.take_duration(duration)
|
||||
.repeat_infinite(),
|
||||
),
|
||||
|
||||
// custom start position
|
||||
(Some(start_position), None) => {
|
||||
sink.append(decoder.skip_duration(start_position).repeat_infinite());
|
||||
}
|
||||
|
||||
// custom duration
|
||||
(None, Some(duration)) => {
|
||||
sink.append(decoder.take_duration(duration).repeat_infinite());
|
||||
}
|
||||
|
||||
// full clip
|
||||
(None, None) => sink.append(decoder.repeat_infinite()),
|
||||
},
|
||||
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
|
||||
sink.append(audio_source.decoder());
|
||||
match (settings.start_position, settings.duration) {
|
||||
(Some(start_position), Some(duration)) => sink.append(
|
||||
decoder
|
||||
.skip_duration(start_position)
|
||||
.take_duration(duration),
|
||||
),
|
||||
|
||||
(Some(start_position), None) => {
|
||||
sink.append(decoder.skip_duration(start_position));
|
||||
}
|
||||
|
||||
(None, Some(duration)) => sink.append(decoder.take_duration(duration)),
|
||||
|
||||
(None, None) => sink.append(decoder),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut sink = SpatialAudioSink::new(sink);
|
||||
|
||||
@ -196,12 +233,49 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
|
||||
}
|
||||
};
|
||||
|
||||
let decoder = audio_source.decoder();
|
||||
|
||||
match settings.mode {
|
||||
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
|
||||
PlaybackMode::Loop => match (settings.start_position, settings.duration) {
|
||||
// custom start position and duration
|
||||
(Some(start_position), Some(duration)) => sink.append(
|
||||
decoder
|
||||
.skip_duration(start_position)
|
||||
.take_duration(duration)
|
||||
.repeat_infinite(),
|
||||
),
|
||||
|
||||
// custom start position
|
||||
(Some(start_position), None) => {
|
||||
sink.append(decoder.skip_duration(start_position).repeat_infinite());
|
||||
}
|
||||
|
||||
// custom duration
|
||||
(None, Some(duration)) => {
|
||||
sink.append(decoder.take_duration(duration).repeat_infinite());
|
||||
}
|
||||
|
||||
// full clip
|
||||
(None, None) => sink.append(decoder.repeat_infinite()),
|
||||
},
|
||||
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
|
||||
sink.append(audio_source.decoder());
|
||||
match (settings.start_position, settings.duration) {
|
||||
(Some(start_position), Some(duration)) => sink.append(
|
||||
decoder
|
||||
.skip_duration(start_position)
|
||||
.take_duration(duration),
|
||||
),
|
||||
|
||||
(Some(start_position), None) => {
|
||||
sink.append(decoder.skip_duration(start_position));
|
||||
}
|
||||
|
||||
(None, Some(duration)) => sink.append(decoder.take_duration(duration)),
|
||||
|
||||
(None, None) => sink.append(decoder),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
let mut sink = AudioSink::new(sink);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user