web_audio_api/node/
scheduled_source.rs

1use super::AudioNode;
2use crate::events::{Event, EventHandler, EventType};
3
4/// Interface of source nodes, controlling start and stop times.
5/// The node will emit silence before it is started, and after it has ended.
6pub trait AudioScheduledSourceNode: AudioNode {
7    /// Play immediately
8    ///
9    /// # Panics
10    ///
11    /// Panics if the source was already started
12    fn start(&mut self);
13
14    /// Schedule playback start at given timestamp
15    ///
16    /// # Panics
17    ///
18    /// Panics if the source was already started
19    fn start_at(&mut self, when: f64);
20
21    /// Stop immediately
22    ///
23    /// # Panics
24    ///
25    /// Panics if the source was already stopped
26    fn stop(&mut self);
27
28    /// Schedule playback stop at given timestamp
29    ///
30    /// # Panics
31    ///
32    /// Panics if the source was already stopped
33    fn stop_at(&mut self, when: f64);
34
35    /// Register callback to run when the source node has stopped playing
36    ///
37    /// For all [`AudioScheduledSourceNode`]s, the ended event is dispatched when the stop time
38    /// determined by stop() is reached. For an
39    /// [`AudioBufferSourceNode`](crate::node::AudioBufferSourceNode), the event is also dispatched
40    /// because the duration has been reached or if the entire buffer has been played.
41    ///
42    /// Only a single event handler is active at any time. Calling this method multiple times will
43    /// override the previous event handler.
44    fn set_onended<F: FnOnce(Event) + Send + 'static>(&self, callback: F) {
45        let callback = move |_| callback(Event { type_: "ended" });
46
47        self.context().set_event_handler(
48            EventType::Ended(self.registration().id()),
49            EventHandler::Once(Box::new(callback)),
50        );
51    }
52
53    /// Unset the callback to run when the source node has stopped playing
54    fn clear_onended(&self) {
55        self.context()
56            .clear_event_handler(EventType::Ended(self.registration().id()));
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use crate::context::{AudioContextRegistration, BaseAudioContext, OfflineAudioContext};
63    use crate::node::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
64
65    use std::sync::atomic::{AtomicBool, Ordering};
66    use std::sync::Arc;
67
68    enum ConcreteAudioScheduledSourceNode {
69        Buffer(crate::node::AudioBufferSourceNode),
70        Constant(crate::node::ConstantSourceNode),
71        Oscillator(crate::node::OscillatorNode),
72    }
73    use ConcreteAudioScheduledSourceNode::*;
74
75    impl AudioNode for ConcreteAudioScheduledSourceNode {
76        fn registration(&self) -> &AudioContextRegistration {
77            match self {
78                Buffer(n) => n.registration(),
79                Constant(n) => n.registration(),
80                Oscillator(n) => n.registration(),
81            }
82        }
83
84        fn channel_config(&self) -> &ChannelConfig {
85            match self {
86                Buffer(n) => n.channel_config(),
87                Constant(n) => n.channel_config(),
88                Oscillator(n) => n.channel_config(),
89            }
90        }
91
92        fn number_of_inputs(&self) -> usize {
93            match self {
94                Buffer(n) => n.number_of_inputs(),
95                Constant(n) => n.number_of_inputs(),
96                Oscillator(n) => n.number_of_inputs(),
97            }
98        }
99
100        fn number_of_outputs(&self) -> usize {
101            match self {
102                Buffer(n) => n.number_of_outputs(),
103                Constant(n) => n.number_of_outputs(),
104                Oscillator(n) => n.number_of_outputs(),
105            }
106        }
107    }
108
109    impl AudioScheduledSourceNode for ConcreteAudioScheduledSourceNode {
110        fn start(&mut self) {
111            match self {
112                Buffer(n) => n.start(),
113                Constant(n) => n.start(),
114                Oscillator(n) => n.start(),
115            }
116        }
117
118        fn start_at(&mut self, when: f64) {
119            match self {
120                Buffer(n) => n.start_at(when),
121                Constant(n) => n.start_at(when),
122                Oscillator(n) => n.start_at(when),
123            }
124        }
125
126        fn stop(&mut self) {
127            match self {
128                Buffer(n) => n.stop(),
129                Constant(n) => n.stop(),
130                Oscillator(n) => n.stop(),
131            }
132        }
133
134        fn stop_at(&mut self, when: f64) {
135            match self {
136                Buffer(n) => n.stop_at(when),
137                Constant(n) => n.stop_at(when),
138                Oscillator(n) => n.stop_at(when),
139            }
140        }
141    }
142
143    fn run_ended_event(f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode) {
144        let mut context = OfflineAudioContext::new(2, 44_100, 44_100.);
145        let mut src = f(&context);
146        src.start_at(0.);
147        src.stop_at(0.5);
148
149        let ended = Arc::new(AtomicBool::new(false));
150        let ended_clone = Arc::clone(&ended);
151        src.set_onended(move |_event| {
152            ended_clone.store(true, Ordering::Relaxed);
153        });
154
155        let _ = context.start_rendering_sync();
156        assert!(ended.load(Ordering::Relaxed));
157    }
158
159    #[test]
160    fn test_ended_event_constant_source() {
161        run_ended_event(|c| Constant(c.create_constant_source()));
162    }
163    #[test]
164    fn test_ended_event_buffer_source() {
165        run_ended_event(|c| Buffer(c.create_buffer_source()));
166    }
167    #[test]
168    fn test_ended_event_oscillator() {
169        run_ended_event(|c| Oscillator(c.create_oscillator()));
170    }
171
172    fn run_no_ended_event(
173        f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode,
174    ) {
175        let mut context = OfflineAudioContext::new(2, 44_100, 44_100.);
176        let src = f(&context);
177
178        // do not start the node
179
180        let ended = Arc::new(AtomicBool::new(false));
181        let ended_clone = Arc::clone(&ended);
182        src.set_onended(move |_event| {
183            ended_clone.store(true, Ordering::Relaxed);
184        });
185
186        let _ = context.start_rendering_sync();
187        assert!(!ended.load(Ordering::Relaxed)); // should not have triggered
188    }
189
190    #[test]
191    fn test_no_ended_event_constant_source() {
192        run_no_ended_event(|c| Constant(c.create_constant_source()));
193    }
194    #[test]
195    fn test_no_ended_event_buffer_source() {
196        run_no_ended_event(|c| Buffer(c.create_buffer_source()));
197    }
198    #[test]
199    fn test_no_ended_event_oscillator() {
200        run_no_ended_event(|c| Oscillator(c.create_oscillator()));
201    }
202
203    fn run_exact_ended_event(
204        f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode,
205    ) {
206        let mut context = OfflineAudioContext::new(2, 44_100, 44_100.);
207        let mut src = f(&context);
208        src.start_at(0.);
209        src.stop_at(1.); // end right at the end of the offline buffer
210
211        let ended = Arc::new(AtomicBool::new(false));
212        let ended_clone = Arc::clone(&ended);
213        src.set_onended(move |_event| {
214            ended_clone.store(true, Ordering::Relaxed);
215        });
216
217        let _ = context.start_rendering_sync();
218        assert!(ended.load(Ordering::Relaxed));
219    }
220
221    #[test]
222    fn test_exact_ended_event_constant_source() {
223        run_exact_ended_event(|c| Constant(c.create_constant_source()));
224    }
225    #[test]
226    fn test_exact_ended_event_buffer_source() {
227        run_exact_ended_event(|c| Buffer(c.create_buffer_source()));
228    }
229    #[test]
230    fn test_exact_ended_event_oscillator() {
231        run_exact_ended_event(|c| Oscillator(c.create_oscillator()));
232    }
233
234    fn run_implicit_ended_event(
235        f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode,
236    ) {
237        let mut context = OfflineAudioContext::new(2, 44_100, 44_100.);
238        let mut src = f(&context);
239        src.start_at(0.);
240        // no explicit stop, so we stop at end of offline context
241
242        let ended = Arc::new(AtomicBool::new(false));
243        let ended_clone = Arc::clone(&ended);
244        src.set_onended(move |_event| {
245            ended_clone.store(true, Ordering::Relaxed);
246        });
247
248        let _ = context.start_rendering_sync();
249        assert!(ended.load(Ordering::Relaxed));
250    }
251
252    #[test]
253    fn test_implicit_ended_event_constant_source() {
254        run_implicit_ended_event(|c| Constant(c.create_constant_source()));
255    }
256
257    #[test]
258    fn test_implicit_ended_event_buffer_source() {
259        run_implicit_ended_event(|c| Buffer(c.create_buffer_source()));
260    }
261
262    #[test]
263    fn test_implicit_ended_event_oscillator() {
264        run_implicit_ended_event(|c| Oscillator(c.create_oscillator()));
265    }
266
267    fn run_start_twice(f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode) {
268        let context = OfflineAudioContext::new(2, 1, 44_100.);
269        let mut src = f(&context);
270        src.start();
271        src.start();
272    }
273
274    #[test]
275    #[should_panic]
276    fn test_start_twice_constant_source() {
277        run_start_twice(|c| Constant(c.create_constant_source()));
278    }
279
280    #[test]
281    #[should_panic]
282    fn test_start_twice_buffer_source() {
283        run_start_twice(|c| Buffer(c.create_buffer_source()));
284    }
285
286    #[test]
287    #[should_panic]
288    fn test_start_twice_oscillator() {
289        run_start_twice(|c| Oscillator(c.create_oscillator()));
290    }
291
292    fn run_stop_before_start(
293        f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode,
294    ) {
295        let context = OfflineAudioContext::new(2, 1, 44_100.);
296        let mut src = f(&context);
297        src.stop();
298    }
299
300    #[test]
301    #[should_panic]
302    fn test_stop_before_start_constant_source() {
303        run_stop_before_start(|c| Constant(c.create_constant_source()));
304    }
305
306    #[test]
307    #[should_panic]
308    fn test_stop_before_start_buffer_source() {
309        run_stop_before_start(|c| Buffer(c.create_buffer_source()));
310    }
311
312    #[test]
313    #[should_panic]
314    fn test_stop_before_start_oscillator() {
315        run_stop_before_start(|c| Oscillator(c.create_oscillator()));
316    }
317
318    fn run_stop_twice(f: impl FnOnce(&OfflineAudioContext) -> ConcreteAudioScheduledSourceNode) {
319        let context = OfflineAudioContext::new(2, 1, 44_100.);
320        let mut src = f(&context);
321        src.start();
322        src.stop();
323        src.stop();
324    }
325
326    #[test]
327    #[should_panic]
328    fn test_stop_twice_constant_source() {
329        run_stop_twice(|c| Constant(c.create_constant_source()));
330    }
331    #[test]
332    #[should_panic]
333    fn test_stop_twice_buffer_source() {
334        run_stop_twice(|c| Buffer(c.create_buffer_source()));
335    }
336    #[test]
337    #[should_panic]
338    fn test_stop_twice_oscillator() {
339        run_stop_twice(|c| Oscillator(c.create_oscillator()));
340    }
341}