I'm trying to have avPlayer restart the current song when the skip back button is selected if it's after about 5 seconds into the song, and jump to times according to where the slider is moved to. Here's my code for these functions:
#IBAction func buttonBackTouchDown(sender: AnyObject) {
if (Float(CMTimeGetSeconds((avPlayerItem?.currentTime())!)) > 5) {
//restartSong = true
avPlayer.currentItem!.seekToTime(kCMTimeZero)
//self.loadSongValues()
//restartSongTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("setRestartSongToFalse"), userInfo: nil, repeats: false)
}
else if (Float(CMTimeGetSeconds((avPlayerItem?.currentTime())!)) < 5) {
self.previousSong()
}
}
#IBAction func sliderTouchDown(sender: AnyObject) {
self.sliderTimer.invalidate()
avPlayer.pause()
self.overlayOn()
}
#IBAction func sliderTouchUpInside(sender: AnyObject) {
//restartSongTimer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("setRestartSongToFalse"), userInfo: nil, repeats: false)
//NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("startSliderTimer"), userInfo: nil, repeats: false)
self.startLabelTimer()
avPlayer.play()
self.updateNowPlayingInfoCenter()
self.overlayOff()
}
The problem I'm having is that if the song is over 1 minute in, or it's almost finished and I move the slider too far back, it SOMETIMES triggers this observer:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "skipSong", name: AVPlayerItemDidPlayToEndTimeNotification, object: avPlayerItem)
I tried looking in Apple's references and looked to other questions here on StackOverflow, but couldn't find anything.
Maybe I'm not understanding how to properly use CMTime, or something is weird with the files, but this is really weird to me.
Any help is appreciated. Thanks in advance!
I'm experiencing also a lot of Issues with the AVPlayer and particularly the SeekToTime method.
When you do
avPlayer.currentItem!.seekToTime(kCMTimeZero)
avPlayer is Jumping to the 0 Frame and not the First frame as it should. the Frame #0 and the last frame seem to be the Same for the AVPlayer. Try the Following: (like jumping to time 0.001 instead of 0)
let seekTime : CMTime = CMTimeMake(1, 1000)
avPlayer!.seekToTime(seekTime)
Related
This question already has answers here:
How can I use Timer (formerly NSTimer) in Swift?
(16 answers)
Closed 4 years ago.
I am quite new to Swift and would love to know how to create a loop that runs some code every 30 seconds? I feel as though it is a basic question but I can't really find what I'm looking for.
You can make Use of Timer
Timer.scheduledTimerWithTimeInterval(0.30, target: self, selector: #selector(timerFired), userInfo: userInfo, repeats: true)
timerFired: is the method that gets called after interval
#objc func timerFired() {
print("Timer Called with interval 30seconds")
}
You need to use scheduled timer. In viewDidLoad()
Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(YourFuncName), userInfo: nil, repeats: true)
and func for selector
#objc func YourFuncName() {
print("every 30seconds")
}
func startTimer() {
timer = Timer.scheduledTimer(timeInterval: 30, target: self, selector: #selector(UpdateTimer), userInfo: nil, repeats: true)
}
#objc func UpdateTimer() {
counter += 1
}
to start
startTimer();
and to stop
timer.invalidate()
I have scheduled timers that add sprite nodes to the screen as obstacles
func timers(){
personTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(spawnPerson), userInfo: nil, repeats: true)
bikeTimer = Timer.scheduledTimer(timeInterval: 2, target: self, selector: #selector(spawnBike), userInfo: nil, repeats: true)
motorcycleTimer = Timer.scheduledTimer(timeInterval: 2.5, target: self, selector: #selector(spawnMotorcycle), userInfo: nil, repeats: true)
}
I added a function to invalidate those timers. so that a bonus "level" can be ran.
func invalidateTimers(){
// Obstacles
personTimer.invalidate()
bikeTimer.invalidate()
motorcycleTimer.invalidate()
}
When the bonus is called
func bonus() {
invalidateTimers()
bonusTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(spawnDiamonds), userInfo: nil, repeats: true)
}
The problem that I'm having is that when the bonus is done running I invalidate the bonusTimer and recall timers(). But when I do all the timers in the function seem to be firing twice. Whats an easy workaround for that since they can't just be paused.
Instead of using timers, consider using SKActions, as they work well with SpriteKit. To start the timer, run:
let wait1 = SKAction.wait(forDuration: 1)
let personTimer = SKAction.repeatForever(SKAction.sequence([wait1, SKAction.run {
spawnPerson() // spawnBike() etc. for each different timer
}]))
self.run(personTimer, withKey: "spawnPerson")
with modified wait values and function calls for each different timer. Then to stop the timer, run:
self.removeAction(forKey: "spawnPerson")
for each action using a different key.
Instead of using timers, you can use update: method of your SKScene subclass. It calls once per frame and has currentTime parameter. So you can easily calculate time intervals you need and trigger corresponding methods.
I have a problem with two timers running in the same View Controller. I need one of them to launch when the other is invalidated and go back on after the start button for the first is tapped again. I tried creating two variables and it builds successfully, but the behavior is erratic. What would be the right approach? Thanks
#IBAction func Start(_ sender: AnyObject) {
timer2.invalidate()
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(ViewController.update), userInfo: nil, repeats: true)
}
#IBAction func pauseTimer(_ sender: AnyObject) {
timer.invalidate()
timer2 = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: Selector(("increaseTimer")), userInfo: nil, repeats: true)
}
So you need a sort of "flip flop" timer effect?
Hav you thought about replacing var timerHasFinishedRunning: Bool = false with checking the invalidation?
e.g.
// When you invalidate, rather
timer1.invalidate()
timer1 = nil
// As goes for timer2
timer2.invalidate()
timer2 = nil
That way, you can have your timer checking done view computed properties:
var timer1HasFinishedRunning: Bool {
return self.timer1 == nil
}
var timer1HasFinishedRunning: Bool {
return self.timer2 == nil
}
Also you mention that they behave "erratically", could you elaborate? Your timer interval is 1 second, so if anything erratic happens, "within 1 second" it's probably because of the long interval. e.g. the checking of each is only done once per second, so sometimes could take as long as 1.999999 seconds to notice that a timer was invalidated.
Personally, I'd have the interval at 0.1 rather than 1.0 for greater accuracy.
Is there any way to merge all UIView returned from function "snapshotViewAfterScreenUpdates" and create a video from them?
Currently i am taking screenshot of screen in Timer event using below code
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: NSSelectorFromString("screenshotTimerEvent"), userInfo: nil, repeats: true)
// Timer event function to capture screenshot and add in Array
func screenshotTimerEvent()
{
self.arrImagesScreenShots.addObject(self.view.snapshotViewAfterScreenUpdates(true))
}
Then after that when the recording is stopped i use below code to show all those screenshots as a video.
timer = NSTimer.scheduledTimerWithTimeInterval(0.10, target: self, selector: NSSelectorFromString("showImages"), userInfo: nil, repeats: true)
// In this function i am adding uiview from start to end so it shows like a video is playing
func showImages()
{
oldView.removeFromSuperview()
if(arrImagesScreenShots.count > count)
{
oldView = arrImagesScreenShots.objectAtIndex(count) as! UIView
self.view.addSubview(oldView)
count += 1
}
else
{
timer.invalidate()
count = 0
}
}
I am not sure if this is the best way to do it but for now its working.
Next thing i want is to merge these UIView into a video and upload to server.
Is there any way?
I am trying to use the NSTimer to increment the progress bar in my app when recording voice (see the screenshot)
let timedClock = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
internal func Counting(timer: NSTimer!) {
if timeCount == 0 {
//self.timedClock = nil
stopRecording(self) //performs segue to another view controller
} else {
timeCount--;
self.timer.text = "\(timeCount)"
}
print("counting called!")
progressBar.progress += 0.2
}
The progress bar works only for the first time after I compile and run the project. When the recording is finished, the app performs segue to another view controller to play the recorded audio. However, when I go back to the view for recording, the timer/progress bar automatically runs. I suspect the NSTimer object is still alive on the NSRunLoop. So I was wondering how to prevent the NSTimer from automatically running.
Inspired by the answer in this SO thread, I tried the following, but the NSTimer still automatically runs.
let timedClock = NSTimer(timeInterval: 1, target: self, selector: "Counting:", userInfo: nil, repeats: true)
NSRunLoop.currentRunLoop().addTimer(timedClock, forMode: NSRunLoopCommonModes)
This happens because when your controller created it's properties are automatically initialized. According to Apple Docs (and method's name) scheduledTimerWithTimeInterval create and return scheduled timer. So if you only want create your timer and call it by trigger function use it like this:
class MyClass {
var timer: NSTimer?
...
func enableTimer() {
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("Counting:"), userInfo: nil, repeats: true)
}
func disableTimer() {
timer?.invalidate()
timer = nil
}
...
}
Sorry for the quick self-answer, as I just found out that I can use the invalidate() method to prevent the timer from automatically firing:
timedClock.invalidate()
Hope it helps someone in the future!