Search code examples
swiftswiftuirealitykitreality-composer

If I assign a sound in Reality Composer, can I stop it programmatically in RealityKit?


I assign a sound to a scene in Reality Composer as in the image below

enter image description here

I would like to create an UIButton that can be pressed to stop the sound. Can I do that?


Solution

  • I created three Reality Composer actions for box scene: they are Play Sound, Spin and Notify. As you can see loop playback is turned ON.

    enter image description here

    SwiftUI

    My code is as simple as that. Primary stop() methods are for immediate stop of audio and animation. Secondary stop() methods are the content of completion handler for onAction property.

    import SwiftUI
    import RealityKit
    
    struct ContentView : View {
        
        @State var completion: ((Entity?) -> ()) = { _ in }
        @State var arView = ARView(frame: .zero)
        @State var boxScene = try! Experience.loadBox()
        
        var body: some View {
            ZStack {
                ARViewContainer(arView: $arView, boxScene: $boxScene)
                   .ignoresSafeArea()
                VStack {
                    Button("Stop") {
                        boxScene.steelBox?.stopAllAudio()              // 1
                        boxScene.steelBox?.stopAllAnimations()         // 1
                        print("Actions are stopped.")
                        
                        completion = {
                            $0?.stopAllAudio()                         // 2
                            $0?.stopAllAnimations()                    // 2
                            $0?.scale = [1,15,1]
                            print("Both actions were completely stopped.")
                        }
    
                        boxScene.actions.occured.onAction = completion
                    }
                    Spacer()
                }
            }
        }
    }
    

    And here's an ordinary binding.

    struct ARViewContainer : UIViewRepresentable {
        
        @Binding var arView: ARView
        @Binding var boxScene: Experience.Box
        
        func makeUIView(context: Context) -> ARView {
            arView.scene.anchors.append(boxScene)
            return arView
        }
        func updateUIView(_ view: ARView, context: Context) { }
    }
    

    Note that Notify action is the last one in the sequence. After what time the secondary stop() methods will be applied (I mean the content of the completion handler), depends on the duration of the audio file or animation (my audio's duration is 14 sec).

    enter image description here

    UIKit

    UIKit solution is somehow different and slightly simpler.

    import UIKit
    import RealityKit
    
    class ViewController : UIViewController {       
        @IBOutlet var arView: ARView!
        let boxScene = try! Experience.loadBox()
        var completion: ((Entity?) -> Void)? = { _ in }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            let rect = CGRect(x: 50, y: 50, width: 100, height: 50)
            let stopButton = UIButton(frame: rect)
            stopButton.setTitle("Stop", for: .normal)
            stopButton.addTarget(self, 
                               action: #selector(stopPlayingAudioAndAnime), 
                                  for: .touchUpInside)
            arView.addSubview(stopButton)
            arView.scene.anchors.append(boxScene)
        }
    
        @objc private func stopPlayingAudioAndAnime() {            
            boxScene.steelBox?.stopAllAudio()                     // 1
            boxScene.steelBox?.stopAllAnimations()                // 1
            
            completion = { entity in
                entity?.stopAllAudio()                            // 2
                entity?.stopAllAnimations()                       // 2
                print("Completely Stopped")
            }           
            boxScene.actions.occured.onAction = completion
        }
    }
    

    P. S.

    However, if you'll be using Play Music action (in other words, on the scene basis, not on the object basis, like Play Sound action), you can implement your idea this way:

    arView.scene.anchors[0].children[0].stopAllAudio()
        
    completion = { entity in
        entity?.scene?.anchors[0].children[0].stopAllAudio()
    }