Skip to content

Commit 635150f

Browse files
authored
Merge pull request #409 from orottier/feature/on-state-change
Implement BaseAudioContext::onStateChange
2 parents a460c94 + 2f927b5 commit 635150f

File tree

4 files changed

+56
-3
lines changed

4 files changed

+56
-3
lines changed

src/context/base.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::context::{
66
DESTINATION_NODE_ID,
77
};
88
use crate::decoding::MediaDecoder;
9+
use crate::events::{Event, EventHandler, EventType};
910
use crate::node::{AudioNode, ChannelConfigOptions};
1011
use crate::param::AudioParamDescriptor;
1112
use crate::periodic_wave::{PeriodicWave, PeriodicWaveOptions};
@@ -301,6 +302,23 @@ pub trait BaseAudioContext {
301302
(param, proc_id)
302303
}
303304

305+
/// Register callback to run when the state of the AudioContext has changed
306+
///
307+
/// Only a single event handler is active at any time. Calling this method multiple times will
308+
/// override the previous event handler.
309+
fn set_onstatechange<F: FnMut(Event) + Send + 'static>(&self, mut callback: F) {
310+
let callback = move |_| {
311+
callback(Event {
312+
type_: "onstatechange",
313+
})
314+
};
315+
316+
self.base().set_event_handler(
317+
EventType::StateChange,
318+
EventHandler::Multiple(Box::new(callback)),
319+
);
320+
}
321+
304322
#[cfg(test)]
305323
fn mock_registration(&self) -> AudioContextRegistration {
306324
AudioContextRegistration {

src/context/concrete_base.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,11 @@ impl ConcreteBaseAudioContext {
338338

339339
/// Updates state of current context
340340
pub(super) fn set_state(&self, state: AudioContextState) {
341-
self.inner.state.store(state as u8, Ordering::SeqCst);
341+
let current_state = self.state();
342+
if current_state != state {
343+
self.inner.state.store(state as u8, Ordering::SeqCst);
344+
let _ = self.send_event(EventDispatch::state_change());
345+
}
342346
}
343347

344348
/// The sample rate (in sample-frames per second) at which the `AudioContext` handles audio.

src/events.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ pub struct Event {
1515
pub type_: &'static str,
1616
}
1717

18-
#[derive(Hash, Eq, PartialEq)]
18+
#[derive(Hash, Eq, PartialEq, Debug)]
1919
pub(crate) enum EventType {
2020
Ended(AudioNodeId),
2121
SinkChange,
22+
StateChange,
2223
RenderCapacity,
2324
ProcessorError(AudioNodeId),
2425
Diagnostics,
@@ -36,13 +37,15 @@ pub struct ErrorEvent {
3637
pub event: Event,
3738
}
3839

40+
#[derive(Debug)]
3941
pub(crate) enum EventPayload {
4042
None,
4143
RenderCapacity(AudioRenderCapacityEvent),
4244
ProcessorError(ErrorEvent),
4345
Diagnostics(Vec<u8>),
4446
}
4547

48+
#[derive(Debug)]
4649
pub(crate) struct EventDispatch {
4750
type_: EventType,
4851
payload: EventPayload,
@@ -63,6 +66,13 @@ impl EventDispatch {
6366
}
6467
}
6568

69+
pub fn state_change() -> Self {
70+
EventDispatch {
71+
type_: EventType::StateChange,
72+
payload: EventPayload::None,
73+
}
74+
}
75+
6676
pub fn render_capacity(value: AudioRenderCapacityEvent) -> Self {
6777
EventDispatch {
6878
type_: EventType::RenderCapacity,

tests/online.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use web_audio_api::context::{
88
};
99
use web_audio_api::node::AudioNode;
1010

11-
use std::sync::atomic::{AtomicBool, Ordering};
11+
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
1212
use web_audio_api::MAX_CHANNELS;
1313

1414
fn require_send_sync_static<T: Send + Sync + 'static>(_: T) {}
@@ -87,6 +87,16 @@ fn test_none_sink_id() {
8787
let context = AudioContext::new(options);
8888
assert_eq!(context.sink_id(), "none");
8989

90+
// count the number of state changes
91+
let state_changes = &*Box::leak(Box::new(AtomicU32::new(0)));
92+
context.set_onstatechange(move |_| {
93+
state_changes.fetch_add(1, Ordering::Relaxed);
94+
});
95+
96+
// give event thread some time to pick up events
97+
std::thread::sleep(std::time::Duration::from_millis(20));
98+
assert_eq!(state_changes.load(Ordering::Relaxed), 1); // started
99+
90100
// changing sink_id to 'none' again should make no changes
91101
let sink_stable = &*Box::leak(Box::new(AtomicBool::new(true)));
92102
context.set_onsinkchange(move |_| {
@@ -98,13 +108,24 @@ fn test_none_sink_id() {
98108
context.suspend_sync();
99109
assert_eq!(context.state(), AudioContextState::Suspended);
100110

111+
// give event thread some time to pick up events
112+
std::thread::sleep(std::time::Duration::from_millis(20));
113+
assert_eq!(state_changes.load(Ordering::Relaxed), 2); // suspended
114+
101115
context.resume_sync();
102116
assert_eq!(context.state(), AudioContextState::Running);
103117

118+
// give event thread some time to pick up events
119+
std::thread::sleep(std::time::Duration::from_millis(20));
120+
assert_eq!(state_changes.load(Ordering::Relaxed), 3); // resumed
121+
104122
context.close_sync();
105123
assert_eq!(context.state(), AudioContextState::Closed);
106124

125+
// give event thread some time to pick up events
126+
std::thread::sleep(std::time::Duration::from_millis(20));
107127
assert!(sink_stable.load(Ordering::SeqCst));
128+
assert_eq!(state_changes.load(Ordering::Relaxed), 4); // closed
108129
}
109130

110131
#[test]

0 commit comments

Comments
 (0)