Skip to content

Commit 165f770

Browse files
authored
Merge pull request #466 from orottier/feature/continue-playing-after-drop
Online AudioContext improvements (continue playing after drop, terminate event loop)
2 parents 3b5a485 + 67edea9 commit 165f770

File tree

9 files changed

+57
-33
lines changed

9 files changed

+57
-33
lines changed

examples/roundtrip_latency_test.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ fn main() {
138138

139139
let estimated_latency = Arc::new(AtomicF64::new(0.));
140140

141-
let _context = if test {
141+
if test {
142142
// emulate loopback
143143
let latency: f32 = 0.017;
144144
println!(
@@ -169,8 +169,6 @@ fn main() {
169169

170170
latency_tester.connect(&delay);
171171
delay.connect(&latency_tester);
172-
173-
context
174172
} else {
175173
// full round trip
176174
let devices = enumerate_devices_sync();
@@ -221,8 +219,6 @@ fn main() {
221219
// create media stream source node with mic stream
222220
let stream_source = context.create_media_stream_source(&mic);
223221
stream_source.connect(&latency_tester);
224-
225-
context
226222
};
227223

228224
loop {

src/context/concrete_base.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,14 @@ impl ConcreteBaseAudioContext {
356356

357357
/// Updates state of current context
358358
pub(super) fn set_state(&self, state: AudioContextState) {
359+
// Only to be used from OfflineAudioContext, the online one should emit the state changes
360+
// from the render thread
361+
debug_assert!(self.offline());
362+
359363
let current_state = self.state();
360364
if current_state != state {
361365
self.inner.state.store(state as u8, Ordering::Release);
362-
let _ = self.send_event(EventDispatch::state_change());
366+
let _ = self.send_event(EventDispatch::state_change(state));
363367
}
364368
}
365369

src/context/online.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::sync::Mutex;
44

55
use crate::context::{AudioContextState, BaseAudioContext, ConcreteBaseAudioContext};
66
use crate::events::{EventDispatch, EventHandler, EventPayload, EventType};
7-
use crate::io::{self, AudioBackendManager, ControlThreadInit, RenderThreadInit};
7+
use crate::io::{self, AudioBackendManager, ControlThreadInit, NoneBackend, RenderThreadInit};
88
use crate::media_devices::{enumerate_devices_sync, MediaDeviceInfoKind};
99
use crate::media_streams::{MediaStream, MediaStreamTrack};
1010
use crate::message::{ControlMessage, OneshotNotify};
@@ -129,6 +129,17 @@ impl std::fmt::Debug for AudioContext {
129129
}
130130
}
131131

132+
impl Drop for AudioContext {
133+
fn drop(&mut self) {
134+
// Continue playing the stream if the AudioContext goes out of scope
135+
if self.state() == AudioContextState::Running {
136+
let tombstone = Box::new(NoneBackend::void());
137+
let original = std::mem::replace(self.backend_manager.get_mut().unwrap(), tombstone);
138+
Box::leak(original);
139+
}
140+
}
141+
}
142+
132143
impl BaseAudioContext for AudioContext {
133144
fn base(&self) -> &ConcreteBaseAudioContext {
134145
&self.base

src/events.rs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::context::AudioNodeId;
1+
use crate::context::{AudioContextState, AudioNodeId};
22
use crate::{AudioBuffer, AudioRenderCapacityEvent};
33

44
use std::any::Any;
@@ -55,6 +55,7 @@ pub(crate) enum EventPayload {
5555
ProcessorError(ErrorEvent),
5656
Diagnostics(Vec<u8>),
5757
Message(Box<dyn Any + Send + 'static>),
58+
AudioContextState(AudioContextState),
5859
}
5960

6061
#[derive(Debug)]
@@ -78,10 +79,10 @@ impl EventDispatch {
7879
}
7980
}
8081

81-
pub fn state_change() -> Self {
82+
pub fn state_change(state: AudioContextState) -> Self {
8283
EventDispatch {
8384
type_: EventType::StateChange,
84-
payload: EventPayload::None,
85+
payload: EventPayload::AudioContextState(state),
8586
}
8687
}
8788

@@ -130,11 +131,22 @@ impl EventLoop {
130131
}
131132

132133
pub fn run(&self, event_channel: Receiver<EventDispatch>) {
134+
log::debug!("Entering event loop");
133135
let self_clone = self.clone();
134136

135-
std::thread::spawn(move || loop {
136-
// this thread is dedicated to event handling so we can block
137-
for event in event_channel.iter() {
137+
std::thread::spawn(move || {
138+
// This thread is dedicated to event handling so we can block
139+
for mut event in event_channel.iter() {
140+
// Terminate the event loop when the audio context is closing
141+
let mut terminate = false;
142+
if matches!(
143+
event.payload,
144+
EventPayload::AudioContextState(AudioContextState::Closed)
145+
) {
146+
event.payload = EventPayload::None; // the statechange handler takes no argument
147+
terminate = true;
148+
}
149+
138150
let mut event_handler_lock = self_clone.event_handlers.lock().unwrap();
139151
let callback_option = event_handler_lock.remove(&event.type_);
140152
drop(event_handler_lock); // release Mutex while running callback
@@ -152,7 +164,13 @@ impl EventLoop {
152164
}
153165
};
154166
}
167+
168+
if terminate {
169+
break;
170+
}
155171
}
172+
173+
log::debug!("Event loop has terminated");
156174
});
157175
}
158176

src/io/cpal.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -417,10 +417,6 @@ impl AudioBackendManager for CpalBackend {
417417
self.sink_id.as_str()
418418
}
419419

420-
fn boxed_clone(&self) -> Box<dyn AudioBackendManager> {
421-
Box::new(self.clone())
422-
}
423-
424420
fn enumerate_devices_sync() -> Vec<MediaDeviceInfo>
425421
where
426422
Self: Sized,

src/io/cubeb.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -395,10 +395,6 @@ impl AudioBackendManager for CubebBackend {
395395
self.sink_id.as_str()
396396
}
397397

398-
fn boxed_clone(&self) -> Box<dyn AudioBackendManager> {
399-
Box::new(self.clone())
400-
}
401-
402398
fn enumerate_devices_sync() -> Vec<MediaDeviceInfo>
403399
where
404400
Self: Sized,

src/io/mod.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::message::ControlMessage;
1414
use crate::{AudioRenderCapacityLoad, RENDER_QUANTUM_SIZE};
1515

1616
mod none;
17+
pub(crate) use none::NoneBackend;
1718

1819
#[cfg(feature = "cpal")]
1920
mod cpal;
@@ -91,7 +92,7 @@ pub(crate) fn build_output(
9192
render_thread_init: RenderThreadInit,
9293
) -> Box<dyn AudioBackendManager> {
9394
if options.sink_id == "none" {
94-
let backend = none::NoneBackend::build_output(options, render_thread_init);
95+
let backend = NoneBackend::build_output(options, render_thread_init);
9596
return Box::new(backend);
9697
}
9798

@@ -185,9 +186,6 @@ pub(crate) trait AudioBackendManager: Send + Sync + 'static {
185186
/// The audio output device - `""` means the default device
186187
fn sink_id(&self) -> &str;
187188

188-
/// Clone the stream reference
189-
fn boxed_clone(&self) -> Box<dyn AudioBackendManager>;
190-
191189
fn enumerate_devices_sync() -> Vec<MediaDeviceInfo>
192190
where
193191
Self: Sized;
@@ -225,12 +223,12 @@ fn buffer_size_for_latency_category(
225223
pub(crate) fn enumerate_devices_sync() -> Vec<MediaDeviceInfo> {
226224
#[cfg(feature = "cubeb")]
227225
{
228-
crate::io::cubeb::CubebBackend::enumerate_devices_sync()
226+
cubeb::CubebBackend::enumerate_devices_sync()
229227
}
230228

231229
#[cfg(all(not(feature = "cubeb"), feature = "cpal"))]
232230
{
233-
crate::io::cpal::CpalBackend::enumerate_devices_sync()
231+
cpal::CpalBackend::enumerate_devices_sync()
234232
}
235233

236234
#[cfg(all(not(feature = "cubeb"), not(feature = "cpal")))]

src/io/none.rs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ pub(crate) struct NoneBackend {
2323
sample_rate: f32,
2424
}
2525

26+
impl NoneBackend {
27+
/// Creates a mock backend to be used as tombstones
28+
pub(crate) fn void() -> Self {
29+
Self {
30+
sample_rate: 0.,
31+
sender: crossbeam_channel::bounded(0).0,
32+
}
33+
}
34+
}
35+
2636
struct Callback {
2737
receiver: Receiver<NoneBackendMessage>,
2838
render_thread: RenderThread,
@@ -160,11 +170,6 @@ impl AudioBackendManager for NoneBackend {
160170
"none"
161171
}
162172

163-
/// Clone the stream reference
164-
fn boxed_clone(&self) -> Box<dyn AudioBackendManager> {
165-
Box::new(self.clone())
166-
}
167-
168173
fn enumerate_devices_sync() -> Vec<MediaDeviceInfo>
169174
where
170175
Self: Sized,

src/render/thread.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ impl RenderThread {
461461
fn set_state(&self, state: AudioContextState) {
462462
self.state.store(state as u8, Ordering::Relaxed);
463463
if let Some(sender) = self.event_sender.as_ref() {
464-
sender.try_send(EventDispatch::state_change()).ok();
464+
sender.try_send(EventDispatch::state_change(state)).ok();
465465
}
466466
}
467467
}

0 commit comments

Comments
 (0)