@@ -457,6 +457,13 @@ impl AudioProcessor for OscillatorRenderer {
457457
458458 log:: warn!( "OscillatorRenderer: Dropping incoming message {msg:?}" ) ;
459459 }
460+
461+ fn before_drop ( & mut self , scope : & AudioWorkletGlobalScope ) {
462+ if !self . ended_triggered && scope. current_time >= self . start_time {
463+ scope. send_ended_event ( ) ;
464+ self . ended_triggered = true ;
465+ }
466+ }
460467}
461468
462469impl OscillatorRenderer {
@@ -608,6 +615,9 @@ mod tests {
608615 use float_eq:: assert_float_eq;
609616 use std:: f64:: consts:: PI ;
610617
618+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
619+ use std:: sync:: Arc ;
620+
611621 use crate :: context:: { BaseAudioContext , OfflineAudioContext } ;
612622 use crate :: node:: { AudioNode , AudioScheduledSourceNode } ;
613623 use crate :: periodic_wave:: { PeriodicWave , PeriodicWaveOptions } ;
@@ -1247,4 +1257,72 @@ mod tests {
12471257 osc. stop ( ) ;
12481258 osc. stop ( ) ;
12491259 }
1260+
1261+ #[ test]
1262+ fn test_ended_event ( ) {
1263+ let mut context = OfflineAudioContext :: new ( 2 , 44_100 , 44_100. ) ;
1264+ let mut src = context. create_oscillator ( ) ;
1265+ src. start_at ( 0. ) ;
1266+ src. stop_at ( 0.5 ) ;
1267+
1268+ let ended = Arc :: new ( AtomicBool :: new ( false ) ) ;
1269+ let ended_clone = Arc :: clone ( & ended) ;
1270+ src. set_onended ( move |_event| {
1271+ ended_clone. store ( true , Ordering :: Relaxed ) ;
1272+ } ) ;
1273+
1274+ let _ = context. start_rendering_sync ( ) ;
1275+ assert ! ( ended. load( Ordering :: Relaxed ) ) ;
1276+ }
1277+
1278+ #[ test]
1279+ fn test_no_ended_event ( ) {
1280+ let mut context = OfflineAudioContext :: new ( 2 , 44_100 , 44_100. ) ;
1281+ let src = context. create_oscillator ( ) ;
1282+
1283+ // do not start the node
1284+
1285+ let ended = Arc :: new ( AtomicBool :: new ( false ) ) ;
1286+ let ended_clone = Arc :: clone ( & ended) ;
1287+ src. set_onended ( move |_event| {
1288+ ended_clone. store ( true , Ordering :: Relaxed ) ;
1289+ } ) ;
1290+
1291+ let _ = context. start_rendering_sync ( ) ;
1292+ assert ! ( !ended. load( Ordering :: Relaxed ) ) ; // should not have triggered
1293+ }
1294+
1295+ #[ test]
1296+ fn test_exact_ended_event ( ) {
1297+ let mut context = OfflineAudioContext :: new ( 2 , 44_100 , 44_100. ) ;
1298+ let mut src = context. create_oscillator ( ) ;
1299+ src. start_at ( 0. ) ;
1300+ src. stop_at ( 1. ) ; // end right at the end of the offline buffer
1301+
1302+ let ended = Arc :: new ( AtomicBool :: new ( false ) ) ;
1303+ let ended_clone = Arc :: clone ( & ended) ;
1304+ src. set_onended ( move |_event| {
1305+ ended_clone. store ( true , Ordering :: Relaxed ) ;
1306+ } ) ;
1307+
1308+ let _ = context. start_rendering_sync ( ) ;
1309+ assert ! ( ended. load( Ordering :: Relaxed ) ) ;
1310+ }
1311+
1312+ #[ test]
1313+ fn test_implicit_ended_event ( ) {
1314+ let mut context = OfflineAudioContext :: new ( 2 , 44_100 , 44_100. ) ;
1315+ let mut src = context. create_oscillator ( ) ;
1316+ src. start_at ( 0. ) ;
1317+ // no explicit stop, so we stop at end of offline context
1318+
1319+ let ended = Arc :: new ( AtomicBool :: new ( false ) ) ;
1320+ let ended_clone = Arc :: clone ( & ended) ;
1321+ src. set_onended ( move |_event| {
1322+ ended_clone. store ( true , Ordering :: Relaxed ) ;
1323+ } ) ;
1324+
1325+ let _ = context. start_rendering_sync ( ) ;
1326+ assert ! ( ended. load( Ordering :: Relaxed ) ) ;
1327+ }
12501328}
0 commit comments