I've got the following code tied to a 'Jump Forward' button:
#IBAction func jumpForwardPressed(sender: AnyObject) {
var currentTime = audioPlayer.currentTime
var playForwardSkip = NSTimeInterval()
audioPlayer.stop()
audioPlayer.prepareToPlay()
playForwardSkip = 3.0 // skip forward 3 seconds
var skipF = currentTime + playForwardSkip
println("currentTime:\(currentTime) and playForwardSkip:\(playForwardSkip) and skipF:\(skipF)")
audioPlayer.playAtTime(skipF)
When button is pressed, audio stops, console reads out correct 'skipF' to skip to but audio does not play at that new point in time. It does not play at all.
Any pointers as to what might be wrong with my code?
You have misread the docs. playAtTime: does not seek to a later time in a sound file. It delays the start of playing the sound file (from the start).
To play from the middle of a sound file, set the player's currentTime and start playing.
Related
I am currently working on a workout plan app. Part of the app is an exercise timer that will count up or down (this works). Additionally a sound should be played as soon as the timer finishes. Ideally that sound would not interfere with music being played, no volume changes, no pause, nothing. Just play a loud sound while music keeps playing. I have tried two ways to do that, both have critical drawbacks.
First solution:
I tried using an AVAudioPlayer that does play the alarm sound after x seconds, with x being the time set for the timer. It does work sometimes as expected, but sometimes the sound will play early. If the timer is set to 60 seconds, the alarm might play after 60 seconds, but sometimes after 55-57 seconds. The alarm is implemented like this:
private let audioPlayer: AVAudioPlayer
// init audioPlayer
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
do {
audioPlayer = try AVAudioPlayer(contentsOf: alarmSound)
} catch {
audioPlayer = AVAudioPlayer()
}
audioPlayer.play(atTime: audioPlayer.deviceCurrentTime + timeTarget)
My second solution that always fires at the right time is using a userNotification. This approach has different drawbacks. The notification will not show if my app is in foreground and the sound will not play if the phone is in silent mode. Aditionally, it will interfere with music being played and show a banner, but I want my alert to be as less intrusive as possible. It is implemented like this:
private var notificationCenter: UNUserNotificationCenter
private var alarmMessage: UNMutableNotificationContent
notificationCenter = UNUserNotificationCenter.current()
alarmMessage = UNMutableNotificationContent()
alarmMessage.title = "Workout Plan"
alarmMessage.subtitle = "Exercise Timer"
alarmMessage.categoryIdentifier = "alarm"
alarmMessage.sound = UNNotificationSound(named: UNNotificationSoundName("dummy.wav"))
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeTarget, repeats: false)
let identifier = "exerciseTimer"
let request = UNNotificationRequest(identifier: identifier, content: alarmMessage, trigger: trigger)
notificationCenter.add(request)
How do I play a sound at a specific time without any offsets or annoying the user with useless notifications?
I'm using the following code to play a sound file twice. The second sound plays immediately following the first
Is it possible to leave a time gap (1 second) between them? I've tried to find a solution reading the Apple Docs
var letterSound: AVAudioPlayer!
...
#IBAction func speakLetter(sender: AnyObject) {
let soundFile: String = "SoundFile" + String(letterNumber)
let path = Bundle.main.path(forResource: soundFile, ofType:"mp3")!
let url = URL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOf: url)
letterSound = sound
letterSound.numberOfLoops = 1
letterSound.play()
} catch {
// couldn't load file :(
}
}
One option would be to add some 'white space' or silence at the end of your audio file.
If you don't have an audio editor i'd recommend Audacity, it's free and there are lots of tutorials on how to add silence.
If you want a more code based option you could look in to using the AVAudioPlayerDelegate didFinishPlaying method to detect the end of the first play and then use play(atTime:) to trigger the next loop 1 second from now.
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
player.play(atTime: player.deviceCurrentTime + 1.0)
}
This code is untested and I think it might create an infinite loop so you may need some additional logic here to make sure it only loops once
I am trying to add sound effects to a game app I am making in Xcode 6. Here is the code that I am using to play the sounds:
var jumpSoundPlayer:AVAudioPlayer = AVAudioPlayer()
var jumpAudioPath:String = ""
var jumpSoundError: NSError? = nil
func setUpSoundPlayers(){
jumpAudioPath = NSBundle.mainBundle().pathForResource("Jump", ofType: "mp3")!
jumpSoundError = nil
jumpSoundPlayer = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: jumpAudioPath), fileTypeHint: "mp3", error: &jumpSoundError)
}
func playJumpSound(volume:Float = 1){
jumpSoundPlayer.volume = volume
jumpSoundPlayer.play()
}
I can get sounds to play, but it seems as though it will not play a sound until the one currently playing is finished. How can I get it so that multiple sounds can play at once and overlap each other?
Also, every time a sound plays it drops the frame rate down a little bit. Is there a way to fix that?
I believe you have to use
jumpSoundPlayer.prepareToPlay()
before using
jumpSoundPlayer.play()
Also the only solution to drop the frame rate a little less, is to set up all the AVAudioPlayers before using them or at the beginning of the game.
Im trying to play a pop sound whenever a ball is touched but if the ball is touched again, before the pop sound ends, the sound will not play. I want the pop sound to cut out and start playing again if the ball is touched over and over again. The code i have now is below...
let popSound = NSBundle.mainBundle().URLForResource("pop",withExtension: "wav")
let Pop = AVAudioPlayer(contentsOfURL: popSound, error: nil)
////this is in the touches begin function/////
if touchedNode==ball{
Pop.play()
}
//////////////////////////////////
I fixed it with the help of this post. AVAudioPlayer stop a sound and play it from the beginning
I needed to set the Pop.currentTime to = 0
if touchedNode==ball{
Pop.currentTime=0
Pop.play()
}
I need to make views visible at fixed time while running a MP3 audio. I am using Swift. Any suggestions?
I don't like using timers if drawing depends on them. They may fire more frequently than the screen redraws or out of sync with the screen drawing. I use a CADisplayLink instead. It's similar to a timer but it is designed to fire in synch with the screen's refresh rate. You could implement something like this.
var messagePlaybackTimer: CADisplayLink?
var player: AVAudioPlayer?
func playAudioData(){
let path = NSBundle.mainBundle().pathForResource("audio", ofType: "mp3")
var url: NSURL
if let audioResourcePath = path {
url = NSURL.fileURLWithPath(audioResourcePath)!
}else{
return;
}
player = AVAudioPlayer(contentsOfURL: url, error: nil)
messagePlaybackTimer?.invalidate()
messagePlaybackTimer = nil;
messagePlaybackTimer = CADisplayLink(target: self, selector: Selector("messagePlaybackTimerFired"))
messagePlaybackTimer?.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
player?.play()
}
func messagePlaybackTimerFired(){
println(player?.currentTime)
}
In messagePlaybackTimerFired() you can check the AVAudioPlayer's current time and fire events based on that. (E.g. if player?.currentTime > 5 && player?.currentTime < 6 { do something } )
This will allow you to fire the method at as close to 5 seconds as possible while still being optimal for doing UI work.
When you are done with the CADisplayLink (when the player stops playing) be sure to invalidate it. If you don't, it will keep firing when you don't need it anymore.
Another thing you can do is fire off an NSNotification in messagePlaybackTimerFired() and your UIViewController can subscribe to the notification and show the views when appropriate.
Use an NSTimer or the AVAudioPlayer's addPeriodicTimeObserverForInterval:queue:usingBlock: method to check the value, it's (very most likely) not possible to get updates when a certain time is reached.
Oh and for a Swiftier NSTimer I suggest this