Skip to content

Commit 59ef105

Browse files
authored
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)) )); ```
1 parent fe678e1 commit 59ef105

File tree

2 files changed

+104
-6
lines changed

2 files changed

+104
-6
lines changed

crates/bevy_audio/src/audio.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ pub struct PlaybackSettings {
5757
/// Optional scale factor applied to the positions of this audio source and the listener,
5858
/// overriding the default value configured on [`AudioPlugin::default_spatial_scale`](crate::AudioPlugin::default_spatial_scale).
5959
pub spatial_scale: Option<SpatialScale>,
60+
/// The point in time in the audio clip where playback should start. If set to `None`, it will
61+
/// play from the beginning of the clip.
62+
///
63+
/// If the playback mode is set to `Loop`, each loop will start from this position.
64+
pub start_position: Option<core::time::Duration>,
65+
/// How long the audio should play before stopping. If set, the clip will play for at most
66+
/// the specified duration. If set to `None`, it will play for as long as it can.
67+
///
68+
/// If the playback mode is set to `Loop`, each loop will last for this duration.
69+
pub duration: Option<core::time::Duration>,
6070
}
6171

6272
impl Default for PlaybackSettings {
@@ -81,6 +91,8 @@ impl PlaybackSettings {
8191
muted: false,
8292
spatial: false,
8393
spatial_scale: None,
94+
start_position: None,
95+
duration: None,
8496
};
8597

8698
/// Will play the associated audio source in a loop.
@@ -136,6 +148,18 @@ impl PlaybackSettings {
136148
self.spatial_scale = Some(spatial_scale);
137149
self
138150
}
151+
152+
/// Helper to use a custom playback start position.
153+
pub const fn with_start_position(mut self, start_position: core::time::Duration) -> Self {
154+
self.start_position = Some(start_position);
155+
self
156+
}
157+
158+
/// Helper to use a custom playback duration.
159+
pub const fn with_duration(mut self, duration: core::time::Duration) -> Self {
160+
self.duration = Some(duration);
161+
self
162+
}
139163
}
140164

141165
/// Settings for the listener for spatial audio sources.

crates/bevy_audio/src/audio_output.rs

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,12 +156,49 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
156156
}
157157
};
158158

159+
let decoder = audio_source.decoder();
160+
159161
match settings.mode {
160-
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
162+
PlaybackMode::Loop => match (settings.start_position, settings.duration) {
163+
// custom start position and duration
164+
(Some(start_position), Some(duration)) => sink.append(
165+
decoder
166+
.skip_duration(start_position)
167+
.take_duration(duration)
168+
.repeat_infinite(),
169+
),
170+
171+
// custom start position
172+
(Some(start_position), None) => {
173+
sink.append(decoder.skip_duration(start_position).repeat_infinite());
174+
}
175+
176+
// custom duration
177+
(None, Some(duration)) => {
178+
sink.append(decoder.take_duration(duration).repeat_infinite());
179+
}
180+
181+
// full clip
182+
(None, None) => sink.append(decoder.repeat_infinite()),
183+
},
161184
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
162-
sink.append(audio_source.decoder());
185+
match (settings.start_position, settings.duration) {
186+
(Some(start_position), Some(duration)) => sink.append(
187+
decoder
188+
.skip_duration(start_position)
189+
.take_duration(duration),
190+
),
191+
192+
(Some(start_position), None) => {
193+
sink.append(decoder.skip_duration(start_position));
194+
}
195+
196+
(None, Some(duration)) => sink.append(decoder.take_duration(duration)),
197+
198+
(None, None) => sink.append(decoder),
199+
}
163200
}
164-
};
201+
}
165202

166203
let mut sink = SpatialAudioSink::new(sink);
167204

@@ -196,12 +233,49 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
196233
}
197234
};
198235

236+
let decoder = audio_source.decoder();
237+
199238
match settings.mode {
200-
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
239+
PlaybackMode::Loop => match (settings.start_position, settings.duration) {
240+
// custom start position and duration
241+
(Some(start_position), Some(duration)) => sink.append(
242+
decoder
243+
.skip_duration(start_position)
244+
.take_duration(duration)
245+
.repeat_infinite(),
246+
),
247+
248+
// custom start position
249+
(Some(start_position), None) => {
250+
sink.append(decoder.skip_duration(start_position).repeat_infinite());
251+
}
252+
253+
// custom duration
254+
(None, Some(duration)) => {
255+
sink.append(decoder.take_duration(duration).repeat_infinite());
256+
}
257+
258+
// full clip
259+
(None, None) => sink.append(decoder.repeat_infinite()),
260+
},
201261
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
202-
sink.append(audio_source.decoder());
262+
match (settings.start_position, settings.duration) {
263+
(Some(start_position), Some(duration)) => sink.append(
264+
decoder
265+
.skip_duration(start_position)
266+
.take_duration(duration),
267+
),
268+
269+
(Some(start_position), None) => {
270+
sink.append(decoder.skip_duration(start_position));
271+
}
272+
273+
(None, Some(duration)) => sink.append(decoder.take_duration(duration)),
274+
275+
(None, None) => sink.append(decoder),
276+
}
203277
}
204-
};
278+
}
205279

206280
let mut sink = AudioSink::new(sink);
207281

0 commit comments

Comments
 (0)