Repository: ruffle-rs/ruffle (language: rust)

Issue:
KR Wizard Flash Belt - Missing Sound
### Describe the bug

Reported by Mega Gamer: "I don’t hear the transformation sounds whenever I do a transformation."

### Expected behavior

The sound is expected to play.

### Content Location

https://www.newgrounds.com/portal/view/779798

### Affected platform

Self-hosted version

### Operating system

Windows 11

### Browser

Google Chrome 126.0.6478.127

### Additional information

_No response_
Audio
### Describe the bug

I have audio issue like it cut out or not their at all 

### Expected behavior

I just listen to my favorite games

### Content Location

https://www.newgrounds.com/portal/view/813661 

### Affected platform

Browser's extension

### Operating system

IOS

### Browser

Chrome

### Additional information

_No response_


Discussion / hints:

Fix patch:
diff --git a/core/src/backend/audio.rs b/core/src/backend/audio.rs
index 0cecc2b6c87e9..aef3598d58a3e 100644
--- a/core/src/backend/audio.rs
+++ b/core/src/backend/audio.rs
@@ -532,6 +532,26 @@ impl<'gc> AudioManager<'gc> {
         });
     }
 
+    /// Stops any sound associated with the given Display Object and its children. The DO must represent the parent.
+    /// Sounds associated with DOs are an AVM1/Timeline concept and should not be called from AVM2 scripts.
+    pub fn stop_sounds_on_parent_and_children(
+        &mut self,
+        audio: &mut dyn AudioBackend,
+        display_object: DisplayObject<'gc>,
+    ) {
+        self.sounds.retain(move |sound| {
+            let mut other = sound.display_object;
+            while let Some(other_do) = other {
+                if DisplayObject::ptr_eq(other_do, display_object) {
+                    audio.stop_sound(sound.instance);
+                    return false;
+                }
+                other = other_do.parent();
+            }
+            true
+        });
+    }
+
     pub fn stop_all_sounds(&mut self, audio: &mut dyn AudioBackend) {
         self.sounds.clear();
         audio.stop_all_sounds();
diff --git a/core/src/context.rs b/core/src/context.rs
index 44c539ef61736..ab7d99e2ca31f 100644
--- a/core/src/context.rs
+++ b/core/src/context.rs
@@ -287,6 +287,11 @@ impl<'gc> UpdateContext<'gc> {
             .stop_sounds_with_display_object(self.audio, display_object)
     }
 
+    pub fn stop_sounds_on_parent_and_children(&mut self, display_object: DisplayObject<'gc>) {
+        self.audio_manager
+            .stop_sounds_on_parent_and_children(self.audio, display_object)
+    }
+
     pub fn stop_all_sounds(&mut self) {
         self.audio_manager.stop_all_sounds(self.audio)
     }
diff --git a/core/src/display_object.rs b/core/src/display_object.rs
index 5cf16baf4d13e..59a43340caac1 100644
--- a/core/src/display_object.rs
+++ b/core/src/display_object.rs
@@ -2147,10 +2147,6 @@ pub trait TDisplayObject<'gc>:
             }
         }
 
-        context
-            .audio_manager
-            .stop_sounds_with_display_object(context.audio, (*self).into());
-
         self.set_avm1_removed(context.gc_context, true);
     }
 
diff --git a/core/src/display_object/avm1_button.rs b/core/src/display_object/avm1_button.rs
index eecdcd6bead03..03d1a9cb1a633 100644
--- a/core/src/display_object/avm1_button.rs
+++ b/core/src/display_object/avm1_button.rs
@@ -404,9 +404,7 @@ impl<'gc> TDisplayObject<'gc> for Avm1Button<'gc> {
         } else if let Some(node) = self.masker() {
             node.set_maskee(context.gc(), None, true);
         }
-        context
-            .audio_manager
-            .stop_sounds_with_display_object(context.audio, (*self).into());
+
         self.set_avm1_removed(context.gc(), true);
     }
 }
diff --git a/core/src/display_object/edit_text.rs b/core/src/display_object/edit_text.rs
index 114aa46c347fc..e521847c86ca8 100644
--- a/core/src/display_object/edit_text.rs
+++ b/core/src/display_object/edit_text.rs
@@ -2413,10 +2413,6 @@ impl<'gc> TDisplayObject<'gc> for EditText<'gc> {
                 .retain(|&text_field| !DisplayObject::ptr_eq(text_field.into(), (*self).into()));
         }
 
-        context
-            .audio_manager
-            .stop_sounds_with_display_object(context.audio, (*self).into());
-
         self.set_avm1_removed(context.gc_context, true);
     }
 }
diff --git a/core/src/display_object/movie_clip.rs b/core/src/display_object/movie_clip.rs
index e406b7520d53d..bd897797a220c 100644
--- a/core/src/display_object/movie_clip.rs
+++ b/core/src/display_object/movie_clip.rs
@@ -2925,9 +2925,11 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
             mc.stop_audio_stream(context);
         }
 
-        context
-            .audio_manager
-            .stop_sounds_with_display_object(context.audio, (*self).into());
+        if self.is_root() {
+            context
+                .audio_manager
+                .stop_sounds_on_parent_and_children(context.audio, (*self).into());
+        }
 
         // If this clip is currently pending removal, then it unload event will have already been dispatched
         if !self.avm1_pending_removal() {


Test patch: