web_audio_api/
capacity.rs1use crossbeam_channel::{Receiver, Sender};
2use std::sync::{Arc, Mutex};
3
4use crate::context::{BaseAudioContext, ConcreteBaseAudioContext};
5use crate::events::{EventDispatch, EventHandler, EventPayload, EventType};
6use crate::Event;
7
8#[derive(Copy, Clone, Debug)]
9pub(crate) struct AudioRenderCapacityLoad {
10 pub render_timestamp: f64,
11 pub load_value: f64,
12}
13
14#[derive(Clone, Debug)]
16pub struct AudioRenderCapacityOptions {
17 pub update_interval: f64,
19}
20
21impl Default for AudioRenderCapacityOptions {
22 fn default() -> Self {
23 Self {
24 update_interval: 1.,
25 }
26 }
27}
28
29#[derive(Clone, Debug)]
31pub struct AudioRenderCapacityEvent {
32 pub timestamp: f64,
34 pub average_load: f64,
36 pub peak_load: f64,
38 pub underrun_ratio: f64,
40 pub event: Event,
42}
43
44impl AudioRenderCapacityEvent {
45 fn new(timestamp: f64, average_load: f64, peak_load: f64, underrun_ratio: f64) -> Self {
46 Self {
49 timestamp,
50 average_load: (average_load * 100.).round() / 100.,
51 peak_load: (peak_load * 100.).round() / 100.,
52 underrun_ratio: (underrun_ratio * 100.).ceil() / 100.,
53 event: Event {
54 type_: "AudioRenderCapacityEvent",
55 },
56 }
57 }
58}
59
60#[derive(Clone)]
69pub struct AudioRenderCapacity {
70 context: ConcreteBaseAudioContext,
71 receiver: Receiver<AudioRenderCapacityLoad>,
72 stop_send: Arc<Mutex<Option<Sender<()>>>>,
73}
74
75impl std::fmt::Debug for AudioRenderCapacity {
76 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77 f.debug_struct("AudioRenderCapacity")
78 .field(
79 "context",
80 &format!("BaseAudioContext@{}", self.context.address()),
81 )
82 .finish_non_exhaustive()
83 }
84}
85
86impl AudioRenderCapacity {
87 pub(crate) fn new(
88 context: ConcreteBaseAudioContext,
89 receiver: Receiver<AudioRenderCapacityLoad>,
90 ) -> Self {
91 let stop_send = Arc::new(Mutex::new(None));
92
93 Self {
94 context,
95 receiver,
96 stop_send,
97 }
98 }
99
100 #[allow(clippy::missing_panics_doc)]
102 pub fn start(&self, options: AudioRenderCapacityOptions) {
103 self.stop();
105
106 let receiver = self.receiver.clone();
107 let (stop_send, stop_recv) = crossbeam_channel::bounded(0);
108 *self.stop_send.lock().unwrap() = Some(stop_send);
109
110 let mut timestamp: f64 = self.context.current_time();
111 let mut load_sum: f64 = 0.;
112 let mut counter = 0;
113 let mut peak_load: f64 = 0.;
114 let mut underrun_sum = 0;
115
116 let mut next_checkpoint = timestamp + options.update_interval;
117 let base_context = self.context.clone();
118 std::thread::spawn(move || loop {
119 let try_item = crossbeam_channel::select! {
120 recv(receiver) -> item => item,
121 recv(stop_recv) -> _ => return,
122 };
123
124 let item = match try_item {
126 Err(_) => return,
127 Ok(item) => item,
128 };
129
130 let AudioRenderCapacityLoad {
131 render_timestamp,
132 load_value,
133 } = item;
134
135 counter += 1;
136 load_sum += load_value;
137 peak_load = peak_load.max(load_value);
138 if load_value > 1. {
139 underrun_sum += 1;
140 }
141
142 if render_timestamp >= next_checkpoint {
143 let event = AudioRenderCapacityEvent::new(
144 timestamp,
145 load_sum / counter as f64,
146 peak_load,
147 underrun_sum as f64 / counter as f64,
148 );
149
150 let send_result = base_context.send_event(EventDispatch::render_capacity(event));
151 if send_result.is_err() {
152 break;
153 }
154
155 next_checkpoint += options.update_interval;
156 timestamp = render_timestamp;
157 load_sum = 0.;
158 counter = 0;
159 peak_load = 0.;
160 underrun_sum = 0;
161 }
162 });
163 }
164
165 #[allow(clippy::missing_panics_doc)]
167 pub fn stop(&self) {
168 if let Some(stop_send) = self.stop_send.lock().unwrap().take() {
170 let _ = stop_send.send(());
171 }
172 }
173
174 pub fn set_onupdate<F: FnMut(AudioRenderCapacityEvent) + Send + 'static>(
179 &self,
180 mut callback: F,
181 ) {
182 let callback = move |v| match v {
183 EventPayload::RenderCapacity(v) => callback(v),
184 _ => unreachable!(),
185 };
186
187 self.context.set_event_handler(
188 EventType::RenderCapacity,
189 EventHandler::Multiple(Box::new(callback)),
190 );
191 }
192
193 pub fn clear_onupdate(&self) {
195 self.context.clear_event_handler(EventType::RenderCapacity);
196 }
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202 use crate::context::{AudioContext, AudioContextOptions};
203
204 #[test]
205 fn test_same_instance() {
206 let options = AudioContextOptions {
207 sink_id: "none".into(),
208 ..AudioContextOptions::default()
209 };
210 let context = AudioContext::new(options);
211
212 let rc1 = context.render_capacity();
213 let rc2 = context.render_capacity();
214 let rc3 = rc2.clone();
215
216 assert!(Arc::ptr_eq(&rc1.stop_send, &rc2.stop_send));
218 assert!(Arc::ptr_eq(&rc1.stop_send, &rc3.stop_send));
219 }
220
221 #[test]
222 fn test_stop_when_not_running() {
223 let options = AudioContextOptions {
224 sink_id: "none".into(),
225 ..AudioContextOptions::default()
226 };
227 let context = AudioContext::new(options);
228
229 let rc = context.render_capacity();
230 rc.stop();
231 }
232
233 #[test]
234 fn test_render_capacity() {
235 let options = AudioContextOptions {
236 sink_id: "none".into(),
237 ..AudioContextOptions::default()
238 };
239 let context = AudioContext::new(options);
240
241 let rc = context.render_capacity();
242 let (send, recv) = crossbeam_channel::bounded(1);
243 rc.set_onupdate(move |e| send.send(e).unwrap());
244 rc.start(AudioRenderCapacityOptions {
245 update_interval: 0.05,
246 });
247 let event = recv.recv().unwrap();
248
249 assert!(event.timestamp >= 0.);
250 assert!(event.average_load >= 0.);
251 assert!(event.peak_load >= 0.);
252 assert!(event.underrun_ratio >= 0.);
253
254 assert!(event.timestamp.is_finite());
255 assert!(event.average_load.is_finite());
256 assert!(event.peak_load.is_finite());
257 assert!(event.underrun_ratio.is_finite());
258
259 assert_eq!(event.event.type_, "AudioRenderCapacityEvent");
260 }
261}