iOS 8 Swift Audio Playback Execute Method On Completion
It depends on how you're playing it. Look for a delegate method, a notification, or an observable property whose state change can trigger a KVO notification.
For example, if you're using an AVAudioPlayer, its delegate
gets notified by a delegate method, which you can find out about here: https://developer.apple.com/library/ios/documentation/avfoundation/reference/AVAudioPlayerDelegateProtocol
Reference/index.html#//apple_ref/doc/uid/TP40008068
Swift iOS, Play tracks sequentially from array using AVAudioPlayer, with some function in between each track?
The approach using audioPlayerDidFinishPlaying(_:,successfully:)
is correct. In your code when you call playScript(c:)
you will iterate through all audio files from conditions
array and call startRecord
method really quickly, while first audio is playing. All sounds will be placed in a queue and play one by one. I don't know contents of startRecord
method, but I don't think that it was supposed to be called like that.
In order to avoid those problems you need to call playScript(c:)
and then wait for method startRecord()
to finish, and then call playScript(c:)
again. So you can try something like this:
import AVFoundation
class SomeClass: NSObject, AVAudioPlayerDelegate {
var player: AVAudioPlayer?
var conditions = [String]()
var instructions: [() -> ()] = []
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished playing \(player.url)")
startRecord {
playNextInstruction()
}
}
func playScript(c: String)
{
let path = Bundle.main.path(forResource: c, ofType:"m4a")!
let url = URL(fileURLWithPath: path)
do {
self.player = try AVAudioPlayer(contentsOf: url)
self.player?.delegate = self
self.player?.play()
print(c)
print("playing script for " + c)
} catch {
print("couldn't load script")
}
}
func beginTest() {
instructions = conditions.shuffled().map { [unowned self] condition in
return {
self.playScript(c: condition)
}
}
playNextInstruction()
}
func playNextInstruction() {
if instructions.count != 0 {
instructions.remove(at: 0)()
} else {
exportData()
}
}
func exportData() {
}
func startRecord(completion: (() -> ())) {
}
}
If it won't work then I suggest to try searching problem somewhere else in your code.
How to play a sound using Swift?
Most preferably you might want to use AVFoundation.
It provides all the essentials for working with audiovisual media.
Update: Compatible with Swift 2, Swift 3 and Swift 4 as suggested by some of you in the comments.
Swift 2.3
import AVFoundation
var player: AVAudioPlayer?
func playSound() {
let url = NSBundle.mainBundle().URLForResource("soundName", withExtension: "mp3")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error as NSError {
print(error.description)
}
}
Swift 3
import AVFoundation
var player: AVAudioPlayer?
func playSound() {
guard let url = Bundle.main.url(forResource: "soundName", withExtension: "mp3") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
let player = try AVAudioPlayer(contentsOf: url)
player.play()
} catch let error {
print(error.localizedDescription)
}
}
Swift 4 (iOS 13 compatible)
import AVFoundation
var player: AVAudioPlayer?
func playSound() {
guard let url = Bundle.main.url(forResource: "soundName", withExtension: "mp3") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
/* The following line is required for the player to work on iOS 11. Change the file type accordingly*/
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */
guard let player = player else { return }
player.play()
} catch let error {
print(error.localizedDescription)
}
}
Make sure to change the name of your tune as well as the extension.
The file needs to be properly imported (Project Build Phases
>Copy Bundle Resources
). You might want to place it inassets.xcassets
for
greater convenience.
For short sound files you might want to go for non-compressed audio formats such as .wav
since they have the best quality and a low cpu impact. The higher disk-space consumption should not be a big deal for short sound files. The longer the files are, you might want to go for a compressed format such as .mp3
etc. pp. Check the compatible audio formats of CoreAudio
.
Fun-fact: There are neat little libraries which make playing sounds even easier. :)
For example: SwiftySound
swift. AVPlayer. How to track when song finished playing?
Something like this works:
func play(url: NSURL) {
let item = AVPlayerItem(URL: url)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: item)
let player = AVPlayer(playerItem: item)
player.play()
}
func playerDidFinishPlaying(note: NSNotification) {
// Your code here
}
Don't forget to remove the observer when you're done (or in deinit
)!
completionHandler of AVAudioPlayerNode.scheduleFile() is called too early
The AVAudioEngine docs from back in the iOS 8 days must have just been wrong. In the meantime, as a workaround, I noticed if you instead use scheduleBuffer:atTime:options:completionHandler:
the callback is fired as expected (after playback finishes).
Example code:
AVAudioFile *file = [[AVAudioFile alloc] initForReading:_fileURL commonFormat:AVAudioPCMFormatFloat32 interleaved:NO error:nil];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat frameCapacity:(AVAudioFrameCount)file.length];
[file readIntoBuffer:buffer error:&error];
[_player scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
// reminder: we're not on the main thread in here
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"done playing, as expected!");
});
}];
start playing audio from a background task via AVAudioPlayer in Xcode
From what I've learned after writing an player App, it seems that you can not start playing audio when your App is already in background for longer than X seconds, even if you have configured everything right.
To fix it, you have to use background task wisely.
The most important thing is that you must NOT call endBackgroundTask
immediately after playSoundFile
. Delay it for about 5-10 seconds.
Here is how I managed doing it for iOS6 and iOS7.
1, Add audio UIBackgroundModes in plist.
2, Set audio session category:
3, Create an AVQueuePlayer in the main thread and enqueue an audio asset before the App enter background.
*For continues playing in background (like playing a playlist)*
4, Make sure the audio queue in AVQueuePlayer never become empty, otherwise your App will be suspended when it finishes playing the last asset.
5, If 5 seconds is not enough for you to get the next asset you can employ background task to delay the suspend. When the asset is ready you should call endBackgroundTask
after 10 seconds.
Related Topics
How to Compare Two Strings to Check If They Have Same Characters Swift 4
Swift - How to Set a Singleton to Nil
Linking Error When Building Parse in Xcode 7
How to Get All Text from a PDF in Swift
Updated Approach to Reauthenticate a User
Custom Cell with Uitableview Inside Uicollectionviewcell
How Does One Use Nsdateformatter's Islenient Option
"Ambiguous Use of 'Children'" When Trying to Use Nstreecontroller.Arrangedobjects in Swift 3.0
Simple Swift Class Does Not Compile
Advancedby' in Swift Is Unavailable
How to Stop an Infinitely Rotating Image? and How Does One Implement Removeallanimations
Passing Unknown Number of Arguments in a Function
Composing Video and Audio - Video's Audio Is Gone
Randomize Two Arrays the Same Way Swift