Search code examples
iosswiftswiftuiavaudiosession

Observing system volume in SwiftUI


I am trying to show a volume indicator in my app, but first I need to monitor the systems current volume.

I am using an observer, and while the print statement shows the correct value, the UI never does.

import SwiftUI
import MediaPlayer

struct ContentView: View {
    @State var vol: Float = 1.0

    // Audio session object
    private let session = AVAudioSession.sharedInstance()
    // Observer
    private var progressObserver: NSKeyValueObservation!

    init() {
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
                try session.setActive(true, options: .notifyOthersOnDeactivation)
                self.vol = 1.0
            } catch {
                print("cannot activate session")
            }

            progressObserver = session.observe(\.outputVolume) { [self] (session, value) in
                print(session.outputVolume)
                self.vol = session.outputVolume
            }
        }

    var body: some View {
        Text(String(self.vol))
    }
}

// fixed (set category to ambient)(updated above code) Also, every time the application is launched, it stops all currently playing music.


Solution

  • Solved. Created a class that conforms to ObservableObject and use the ObservedObject property in the view. Also, the volume observer doesn't work in the simulator, only on device.

    VolumeObserver.swift

    import Foundation
    import MediaPlayer
    
    final class VolumeObserver: ObservableObject {
        
        @Published var volume: Float = AVAudioSession.sharedInstance().outputVolume
        
        // Audio session object
        private let session = AVAudioSession.sharedInstance()
        
        // Observer
        private var progressObserver: NSKeyValueObservation!
        
        func subscribe() {
            do {
                try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.ambient)
                try session.setActive(true, options: .notifyOthersOnDeactivation)
            } catch {
                print("cannot activate session")
            }
            
            progressObserver = session.observe(\.outputVolume) { [self] (session, value) in
                DispatchQueue.main.async {
                    self.volume = session.outputVolume
                }
            }
        }
        
        func unsubscribe() {
            self.progressObserver.invalidate()
        }
        
        init() {
            subscribe()
        }
    }
    

    ContentView.swift

    import SwiftUI
    import MediaPlayer
    
    struct ContentView: View {
    
        @StateObject private var volObserver = VolumeObserver()
        
        init() {
            print(volObserver.volume)
        }
        
        var body: some View {
            Text(String(volObserver.volume))
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }