Reputation: 382
Oh hey! I've been stuck trying to figure out how to have my AVAudioPlayerNode()
automatically stop playback when it reaches the end of the file in my SwiftUI project. As you can see below, my audio files that don't go through the AVAudioEngine
but rather the AVAudioPlayer
have an audioPlayerDidFinishPlaying
function and that works great. However for the audio files that go through the engine using the play
function don't have anything like this, and I can't figure out how to get it to behave similarly.
I've tried building something like
func audioPlayerDidFinishPlaying2(_ player: AVAudioPlayerNode, successfully flag: Bool) {
if flag {
isPlaying = false
print("Playback Engine Stopped")
}
}
But this doesn't appear to do anything.
Stopping the engine's playback manually by calling stopPlayback2()
works just fine.
I'm calling the engine player using
do {
try self.audioPlayer.play(self.audioURL)
}
catch let error as NSError {
print(error.localizedDescription)
}
I've looked at other SO posts [here][1] and [here][2] but neither solutions are working for me. I would really appreciate your input if you have any suggestions! Thanks!!
AudioPlayer.swift
class AudioPlayer: NSObject, ObservableObject, AVAudioPlayerDelegate {
let objectWillChange = PassthroughSubject<AudioPlayer, Never>()
var isPlaying = false {
didSet {
objectWillChange.send(self)
}
}
var audioPlayer: AVAudioPlayer!
func startPlayback (audio: URL) {
let playbackSession = AVAudioSession.sharedInstance()
do {
try playbackSession.overrideOutputAudioPort(AVAudioSession.PortOverride.speaker)
} catch {
print("Playing over the device's speakers failed")
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: audio)
audioPlayer.delegate = self
audioPlayer.play()
isPlaying = true
} catch {
print("Playback failed.")
}
}
func stopPlayback() {
audioPlayer.stop()
isPlaying = false
}
func stopPlayback2() {
engine.stop()
isPlaying = false
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if flag {
isPlaying = false
print("Playback Stopped")
}
}
let engine = AVAudioEngine()
let speedControl = AVAudioUnitVarispeed()
let pitchControl = AVAudioUnitTimePitch()
func play(_ url: URL) throws {
let file = try! AVAudioFile(forReading: url)
let avPlayer = AVAudioPlayerNode()
engine.attach(avPlayer)
engine.attach(pitchControl)
engine.attach(speedControl)
engine.connect(avPlayer, to: speedControl, format: nil)
engine.connect(speedControl, to: pitchControl, format: nil)
engine.connect(pitchControl, to: engine.mainMixerNode, format: nil)
avPlayer.scheduleFile(file, at: nil)
isPlaying = true
try engine.start()
avPlayer.play()
}
```
[1]: https://stackoverflow.com/questions/34238432/avaudioengine-avaudioplayernode-didfinish-method-like-avaudioplayer
[2]: https://stackoverflow.com/questions/59080708/calling-stop-on-avaudioplayernode-after-finished-playing-causes-crash
Upvotes: 3
Views: 2259
Reputation: 52575
Instead of using avPlayer.scheduleFile(file, at: nil)
, use the form of the method with a completion handler:
avPlayer.scheduleFile(file, at: nil, completionCallbackType: .dataPlayedBack) { _ in
//call your completion function here
print("Done playing")
}
Documentation: https://developer.apple.com/documentation/avfaudio/avaudioplayernodecompletioncallbacktype/dataplayedback
Upvotes: 3