Skip to content

Commit a15a70a

Browse files
committed
AudioScheduledSourceNode can return tail_time false when not scheduled
The render thread will not garbage collect them as long as the control handle is kept. Once the control handle is dropped, and the node is still not scheduled to start (start_at != f64::MAX) we can safely drop the node from the audio graph. Fixes #462
1 parent bb70ed1 commit a15a70a

File tree

3 files changed

+29
-4
lines changed

3 files changed

+29
-4
lines changed

src/node/audio_buffer_source.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,14 +422,18 @@ impl AudioProcessor for AudioBufferSourceRenderer {
422422
// Return early if start_time is beyond this block
423423
if self.start_time >= next_block_time {
424424
output.make_silent();
425-
return true;
425+
// #462 AudioScheduledSourceNodes that have not been scheduled to start can safely
426+
// return tail_time false in order to be collected if their control handle drops.
427+
return self.start_time != f64::MAX;
426428
}
427429

428430
// If the buffer has not been set wait for it.
429431
let buffer = match &self.buffer {
430432
None => {
431433
output.make_silent();
432-
return true;
434+
// #462 like the above arm, we can safely return tail_time false if this node has
435+
// no buffer set.
436+
return false;
433437
}
434438
Some(b) => b,
435439
};

src/node/constant_source.rs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ impl AudioProcessor for ConstantSourceRenderer {
192192

193193
if self.start_time >= next_block_time {
194194
output.make_silent();
195-
return true;
195+
// #462 AudioScheduledSourceNodes that have not been scheduled to start can safely
196+
// return tail_time false in order to be collected if their control handle drops.
197+
return self.start_time != f64::MAX;
196198
}
197199

198200
output.force_mono();
@@ -314,6 +316,23 @@ mod tests {
314316
assert_float_eq!(channel[128..], vec![1.; 128][..], abs_all <= 0.);
315317
}
316318

319+
#[test]
320+
fn test_start_in_the_future_while_dropped() {
321+
let sample_rate = 48000.;
322+
let mut context = OfflineAudioContext::new(1, 4 * 128, sample_rate);
323+
324+
let mut src = context.create_constant_source();
325+
src.connect(&context.destination());
326+
src.start_at(258. / sample_rate as f64); // in 3rd block
327+
drop(src); // explicit drop
328+
329+
let buffer = context.start_rendering_sync();
330+
let channel = buffer.get_channel_data(0);
331+
332+
assert_float_eq!(channel[0..258], vec![0.; 258][..], abs_all <= 0.);
333+
assert_float_eq!(channel[258..], vec![1.; 254][..], abs_all <= 0.);
334+
}
335+
317336
#[test]
318337
#[should_panic]
319338
fn test_start_twice() {

src/node/oscillator.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,9 @@ impl AudioProcessor for OscillatorRenderer {
373373

374374
if self.start_time >= next_block_time {
375375
output.make_silent();
376-
return true;
376+
// #462 AudioScheduledSourceNodes that have not been scheduled to start can safely
377+
// return tail_time false in order to be collected if their control handle drops.
378+
return self.start_time != f64::MAX;
377379
} else if self.stop_time < scope.current_time {
378380
output.make_silent();
379381

0 commit comments

Comments
 (0)