Audio playback progress as UISlider in Swift
Solution 1
Thanks to the suggestion of Dare above, here's how I accomplished this:
var updater : CADisplayLink! = nil
@IBAction func playAudio(sender: AnyObject) {
playButton.selected = !(playButton.selected)
if playButton.selected {
updater = CADisplayLink(target: self, selector: Selector("trackAudio"))
updater.frameInterval = 1
updater.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
let fileURL = NSURL(string: toPass)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.numberOfLoops = -1 // play indefinitely
player.prepareToPlay()
player.delegate = self
player.play()
startTime.text = "\(player.currentTime)"
theProgressBar.minimumValue = 0
theProgressBar.maximumValue = 100 // Percentage
} else {
player.stop()
}
}
func trackAudio() {
var normalizedTime = Float(player.currentTime * 100.0 / player.duration)
theProgressBar.value = normalizedTime
}
@IBAction func cancelClicked(sender: AnyObject) {
player.stop()
updater.invalidate()
dismissViewControllerAnimated(true, completion: nil)
}
Solution 2
Just to elaborate on my previous comment, this is how I implemented it and it seems to work pretty well. Any Swift corrections are more than welcome, I'm still an Obj-C guy for now.
@IBAction func playAudio(sender: AnyObject) {
var playing = false
if let currentPlayer = player {
playing = player.playing;
}else{
return;
}
if !playing {
let filePath = NSBundle.mainBundle().pathForResource("3e6129f2-8d6d-4cf4-a5ec-1b51b6c8e18b", ofType: "wav")
if let path = filePath{
let fileURL = NSURL(string: path)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.numberOfLoops = -1 // play indefinitely
player.prepareToPlay()
player.delegate = self
player.play()
displayLink = CADisplayLink(target: self, selector: ("updateSliderProgress"))
displayLink.addToRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode!)
}
} else {
player.stop()
displayLink.invalidate()
}
}
func updateSliderProgress(){
var progress = player.currentTime / player.duration
timeSlider.setValue(Float(progress), animated: false)
}
*I set time slider's range between 0 and 1 on a storyboard
Solution 3
Syntax has now changed in Swift 4:
updater = CADisplayLink(target: self, selector: #selector(self.trackAudio))
updater.preferredFramesPerSecond = 1
updater.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)
And the function (I have previously set the progressSlider.maxValue to player.duration):
@objc func trackAudio() {
progressSlider.value = Float(player!.currentTime)
}
Solution 4
Specifically for Swift I was able to handle it like this:
I set the maximum value of the
scrubSlider
to the duration of the music file(.mp3) that was loaded in this methodoverride func viewDidLoad() { super.viewDidLoad() do { try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("bach", ofType: "mp3")!)) scrubSlider.maximumValue = Float(player.duration) } catch { //Error } _ = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: #selector(ViewController.updateScrubSlider), userInfo: nil, repeats: true) }
I player was set to play the music at the time set by the value of the scrubber.
@IBAction func scrub(sender: AnyObject) { player.currentTime = NSTimeInterval(scrubSlider.value) }
Related videos on Youtube
Comments
-
allocate about 4 years
I've seen some posts about accomplishing this in Objective-C but I've been unable to do the same via Swift.
Specifically, I can't figure out how to implement
addPeriodicTimeObserverForInterval
in the below.var player : AVAudioPlayer! = nil @IBAction func playAudio(sender: AnyObject) { playButton.selected = !(playButton.selected) if playButton.selected { let fileURL = NSURL(string: toPass) player = AVAudioPlayer(contentsOfURL: fileURL, error: nil) player.numberOfLoops = -1 // play indefinitely player.prepareToPlay() player.delegate = self player.play() startTime.text = "\(player.currentTime)" endTime.text = NSString(format: "%.1f", player.duration) } else { player.stop() }
Any assistance would be appreciated.
-
Dare about 9 yearsIf you are trying to update the slider as audio progresses, I like to use a display link. CADisplayLink will sync up to the screen refresh rate and call a selector on refresh like a graphics optimized NSTimer. They're also super simple to use and prevent you from having to queue up UIUpdates if the timer fires faster than the screen redraws. These are the docs. You can set the selector the display link calls to check the progress and update your slider position.
-
allocate about 9 years@Dare, thanks for that. I had no idea that existed.
-
-
Dare about 9 yearsOne quick note – Make sure you invalidate the display link when you're done with it. If you don't it's gonna fire ad infinitum regardless of the audio player's state until something crashes.
-
allocate about 9 years@Dare thanks for that. I'd incorrectly assumed dismissing the ViewController would terminate that (and I was very wrong). Code updated with the global declaration and the invalidate() call.
-
ChuckZHB over 4 yearsThat is what I need, in my case there is an additional audio playback timer under the slider bar. And touching the slider to pause the updater
updater.isPaused = true
and to resume it when user releases dragging from the slider bar. -
ChuckZHB over 4 yearsHow does your
updater
worked in background mode? I just find that my slider cannot resume to process when re-open it from locked screen command center.