I'm trying to track an AVAudioPlayerNode's playback state via its Combine Publisher:
import Cocoa
import AVFoundation
import Combine
@main
class AppDelegate: NSObject, NSApplicationDelegate {
let engine = AVAudioEngine()
let player = AVAudioPlayerNode()
var cancellable: AnyCancellable?
func applicationDidFinishLaunching(_ aNotification: Notification) {
cancellable = player.publisher(for: \.isPlaying)
.sink { newValue in
print("is playing: \(newValue)")
}
let url = Bundle.main.url(forResource: "blues", withExtension: "aiff")!
let file = try! AVAudioFile(forReading: url)
let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: UInt32(file.length))!
try! file.read(into: buffer)
engine.attach(player)
engine.connect(player, to: engine.mainMixerNode, format: file.processingFormat)
player.scheduleBuffer(buffer, at: nil, options: .loops, completionHandler: nil)
try! engine.start()
player.play()
}
}
The sink gets called just once when it's initialised, and never again. I would expect it to get called when playback starts, but it doesn't... Any ideas why?
AVAudioPlayerNode
does not seem to be KVO-compliant for the isPlaying
property. Normally the docs mention explicitly when properties are KVO-compliant but the docs for AVAudioPlayerNode don't address it directly
I ran a simple test using addObserver(_:forKeyPath:options:context:)
and confirmed that KVO notifications are not emitted when play()
or stop()
is called.
As for why the Combine publisher exists, I assume that NSObject.KeyValueObservingPublisher
works for all ObjC @property
declarations whether or not the ObjC class internally handles the property in a KVO-compliant way (by sending -willChangeValueForKey:
and -didChangeValueForKey:
).