can AVAudioPlayer trigger events on predefined intervals? - swift

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

Related

Swift: How do I play a sound after exactly x seconds to show that a timer has finished?

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?

SKAction completion handler reset upon additional calls?

I have some animations timed to audio using the completion handler in playSoundFileNamed - currently if I call it multiple times the animation actions are removed at the end of the first audio - so the audio from the additional calls continues but the animations stops (which makes sense) I have tried a variety of checks/if/else/etc but cannot figure out a way to have it keep animating as long as the audio is playing - w/o going to a delegate and using AVplayer which doesn't work as well for multiple sounds at once - IIRC...
example
//Animation
func animateBass() {
let danceAction = SKAction.repeatForever(SKAction.animate(with: animatedBass, timePerFrame: 0.1, resize: false, restore: true))
bass.run(danceAction)
}
//Action
let randomBass = bassLick.randomElement()
let bassAction = SKAction.playSoundFileNamed(randomBass!, waitForCompletion: true)
//Bass call
animateBass()
bassPlayer.run(bassAction) { [weak self] in
self?.bassPlayer.removeAllActions()
}
this works great if I only call it once but hoping to smash the button a million times and have the animation continue until the audio is truly finished!
Thanks!!

How to get current length of AVCaptureMovieFileOutput while recording to display on screen?

As title says, I am looking to display the current length of the recording while a user is recording video with AVFoundation. I have done something similar with AVAudioRecorder where I create a timer that sets a label's text as follows:
recordDurationLabel.text = timeString((audioRecorder?.currentTime)!)
I was wondering if there is a "currentTime" feature for recording video? My only other thought would be to start a repeated 1 second timer in the captureOutput:didStartRecordingToOutputFileAtURL function but I want to ensure accuracy.
Thanks in advance!
Edit:
For now this is the function I've implemented in the captureOutput:didStartRecordingToOutputFileAtURL delegate method, but again, would be cleaner to have something directly related to the video.
func startRecordingUpdateTimer() {
recordingUpdateTimer = NSTimer(timeInterval: 1.0/45.0, target: self, selector: #selector(recordedVideo), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(recordingUpdateTimer!, forMode: NSRunLoopCommonModes)
}
func recordedVideo() {
durationTime = durationTime + 1.0/45.0
let durationString = timeString(durationTime)
recordDurationLabel.text = durationString
}
With an AVCaptureMovieFileOutput, you can get the duration of the movie by using recordedDuration which is read only and will return you a CMTime when the recording has started. See AVCaptureFileOutput.
If you use captureOutput:didOutputSampleBuffer:fromConnection: you'll receive CMSampleBufferobjects which contain a CMTimethat you can access with CMSampleBufferGetPresentationTimeStamp.
Doing a CMTime operation like CMTimeSubstract will allow you to calculate the time difference between the current buffer and the first one you recorded.
If you have never manipulated sampleBuffers, you should take a look at this example code from Apple: RosyWriter

ios8 Swift SpriteKit - Pause and Resume NSTimers in swift

I have searched many times on the internet but could not find the answer to this question. I know how to pause and resume NSTimers by using the invalidate functions - timer.invalidate. and I know how to resume them. But I have a SpriteKit game. When I pause my game, I stop everything and the timers. I know that I can stop them using .invalidate but when I invalidate them:
For example lets say I have a 5 second timer that runs continously that spawns one block.
When the timer reaches second 3 of the cycle and when I paused the game, and invalidate the timers. When I resume, Now the timers second goes back to 0 and I must wait another 5 seconds. I want it to continue from where it left off, 3 , and wait 2 seconds for the block to spawn.
blockGenerator.generationTimer?.invalidate()
self.isGamePaused = true
self.addChild(self.pauseText)
self.runAction(SKAction.runBlock(self.pauseGame))
e`
and when I resume it:
blockGenerator.generationTimer = ...
I have to wait another 5 seconds, I want the timer to continue from where it left off
If you can help me, I appreciate it thank you.
There is a way to pause/resume Timer instances, because using repeating timers we know the next fire date.
This is a simple class SRTimer and a protocol SRTimerDelegate
Protocol SRTimerDelegate
protocol SRTimerDelegate : AnyObject {
func timerWillStart(_ timer : SRTimer)
func timerDidFire(_ timer : SRTimer)
func timerDidPause(_ timer : SRTimer)
func timerWillResume(_ timer : SRTimer)
func timerDidStop(_ timer : SRTimer)
}
Class SRTimer
class SRTimer : NSObject {
var timer : Timer?
var interval : TimeInterval
var difference : TimeInterval = 0.0
var delegate : SRTimerDelegate?
init(interval: TimeInterval, delegate: SRTimerDelegate?)
{
self.interval = interval
self.delegate = delegate
}
#objc func start(_ aTimer : Timer?)
{
if aTimer != nil { fire(self) }
if timer == nil {
delegate?.timerWillStart(self)
timer = Timer.scheduledTimer(timeInterval: interval, target: self, selector: #selector(fire), userInfo: nil, repeats: true)
}
}
func pause()
{
if timer != nil {
difference = timer!.fireDate.timeIntervalSince(Date())
timer!.invalidate()
timer = nil
delegate?.timerDidPause(self)
}
}
func resume()
{
if timer == nil {
delegate?.timerWillResume(self)
if difference == 0.0 {
start(nil)
} else {
Timer.scheduledTimer(timeInterval: difference, target: self, selector: #selector(start), userInfo: nil, repeats: false)
difference = 0.0
}
}
}
func stop()
{
if timer != nil {
difference = 0.0
timer!.invalidate()
timer = nil
delegate?.timerDidStop(self)
}
}
#objc func fire(_ sender : SRTimer)
{
delegate?.timerDidFire(self)
}
}
Make your class conform to the protocol SRTimerDelegate and initialize a SRTimer instance with
var timer : SRTimer!
timer = SRTimer(interval: 5.0, delegate: self)
Methods
start() calls the delegate method timerWillStart and starts the timer.
pause() saves the difference between the current date and the next fire date, invalidates the timer and calls the delegate method timerDidPause.
resume() calls the delegate method timerWillResume, creates a temporary one shot timer with the saved difference time interval. When this timer fires the main timer will be restarted.
stop() calls the delegate method timerDidStop and invalidates the timer.
When the timer fires, the delegate method timerDidFire is called.
First, let me say this - it is not possible to do with just NSTimer, there is no inbuilt function to do that (you can build logic around that as the answer from Vadian suggests). BUT.
Why NSTimer is not good idea
Lets stop and think for a little. For game objects and precise spawning, you should never use NSTimer in the first place. The problem is implementation of NSTimer (quoting the docs):
Because of the various input sources a typical run loop manages, the
effective resolution of the time interval for a timer is limited to on
the order of 50-100 milliseconds. If a timer’s firing time occurs
during a long callout or while the run loop is in a mode that is not
monitoring the timer, the timer does not fire until the next time the
run loop checks the timer. Therefore, the actual time at which the
timer fires potentially can be a significant period of time after the
scheduled firing time.
There are other problems with NSTimer but that is out of scope of that question.
Solution
What you can do instead, you should listen to delta time change in each update call
let delta = currentPreciseTime - previousPreciseTime
Now, when you have that delta, you can have your counter : Double, and on each update, you increase counter by delta.
let counter : Double
counter += delta
Now that your "timer" is running properly, you can check with simple condition if your period of time already passed, or do whatever you want with it:
let SPAWN_OBJECT_AFTER : Double = 5.0
if counter > SPAWN_OBJECT_AFTER {
// Do something on fire event
self.spawn()
// This line effectively restarts timer
counter -= SPAWN_OBJECT_AFTER
}
You can easily build your own, very easy timer class to do it. Also! This way you have control over what happens in your update call, which is where the update logic belongs. Timer breaks that model by allowing method execution outside that - it might be intended, but usually is not).
I built a games running in production every day and this is I'd say most common solution for periodic events, as it saves the most resources when used appropriately. Obviously not fitting for everything but definitely fits your need.
Hope it helps!
I don't believe there is a way to pause/resume a NSTimer in the way you are talking about. You must use timer.invalidate() and timer.fire(). However, perhaps you can use an int (that starts at 5 and goes down every second) to keep track of how many seconds the initial timer has before fires again and once the times fires again, make sure the new int value is passed to start the initial timer from the correct point in time.

What is the "cleartimeout" equivalent for Swift?

I'm trying to set up a timeout on a text input field that only implements the inner code a second after the user stops typing. So while the user is typing, I would continually call a cleartimeout and re-initiate the setTimeout.
I was originally looking at the performSelector function in Objective C, but it looks like there is no Swift equivalent for this.
Then I moved on to the GCD functions in Swift, looking for a way to execute this.
Here is what I came up with:
var delta: Int64 = 1 * Int64(NSEC_PER_SEC)
var time = dispatch_time(DISPATCH_TIME_NOW, delta)
dispatch_suspend(dispatch_get_main_queue())
dispatch_after(time, dispatch_get_main_queue(), {
println("timeout")
});
The dispatch_suspend function is not working as I was hoping.
Maybe the dispatch functions are not appropriate here?
You can use dispatch_after rather than one of the performSelector. But I don't think either of these is what you want.
If you are looking to call a block of code only after it's been idle for one second, then I think you want to use a timer (e.g. Timer is fine, or you could use a dispatch timer). Bottom line, every time you get keyboard interaction, see if there is a pending timer, and if so, invalidate it, and then schedule the new one.
So I might be inclined to do something like the following in Swift 3. For example, in iOS 10 and later, you can use the block rendition:
weak var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: 1.0, repeats: false) { [weak self] timer in
// do whatever you want when idle after certain period of time
}
}
Or, if you need to support earlier iOS versions that do not have block-based timers:
weak var timer: Timer?
func resetTimer() {
timer?.invalidate()
timer = .scheduledTimer(timeInterval: 1, target: self, selector: #selector(handleIdleEvent(_:)), userInfo: nil, repeats: false)
}
#objc func handleIdleEvent(_ timer: Timer) {
// do whatever you want when idle after certain period of time
}
If you use this latter approach, though, recognize that this Timer keeps a strong reference to its target, so you might want to cancel the timer in viewDidDisappear (but not deinit). This is why we prefer the block-based rendition or GCD timers.
By the way, I am not sure what your intent of dispatch_suspend was, but don't suspend the main queue. You never want to do anything that could potentially interfere with the main queue's timely processing of events (i.e., never block/suspend the main queue).