Reputation: 690
I'm making a simple audio player app, and it allows a user to change where they are in a track using the UISlider.
What it doesn't do: the slider doesn't update based on the track's progress. I found a similar question here, and the second answer was responsive to AVAudioPlayer
-- suggesting using currentTime
to update the slider. I want to try to do that.
Here's my code:
import UIKit
import AVFoundation
class MusicViewController: UIViewController {
....
@IBOutlet var Slider: UISlider!
@IBAction func changeAudioTime(_ sender: Any) {
Slider.maximumValue = Float(audioPlayer.duration)
audioPlayer.stop()
audioPlayer.currentTime = TimeInterval(Slider.value)
audioPlayer.prepareToPlay()
audioPlayer.play()
}
func updateSlider(){
Slider.value = Float(audioPlayer.currentTime)
print("Changing works")
}
The first function works fine -- the user can change where they are. However, the second function to me should change the Slider.value
based on the currentTime
of the AudioPlayer. It doesn't do that.
It's also not printing, so my belief is it isn't firing.
I'm new to Swift so I am likely missing something. Any ideas?
Upvotes: 3
Views: 5129
Reputation: 1964
Apple's suggested solution is adding a periodicTimeObserver to the AVPlayer object.
private(set) var duration: TimeInterval = 0.0
private(set) var currentTime: TimeInterval = 0.0
private let player = AVPlayer()
private var timeObserver: Any?
/// Adds an observer of the player timing.
private func addPeriodicTimeObserver() {
// Create a 0.5 second interval time.
let interval = CMTime(value: 1, timescale: 2)
timeObserver = player.addPeriodicTimeObserver(forInterval: interval,
queue: .main) { [weak self] time in
guard let self else { return }
// Update the currentTime and duration values.
// Update your slider or time-elapsed label values
currentTime = time.seconds
duration = player.currentItem?.duration.seconds ?? 0.0
}
}
/// Removes the time observer from the player.
private func removePeriodicTimeObserver() {
guard let timeObserver else { return }
player.removeTimeObserver(timeObserver)
self.timeObserver = nil
}
If you want to read more refer to original doc by Apple
Upvotes: 0
Reputation: 164
You need to set up a CADisplayLink
and update the position of the slider on each invocation of its selector. Here's how the whole AVAudioPlayer
-UISlider
binding can be done:
// 1
@IBOutlet weak var slider: UISlider! {
didSet {
slider.isContinuous = false
slider.addTarget(self, action: #selector(didBeginDraggingSlider), for: .touchDown)
slider.addTarget(self, action: #selector(didEndDraggingSlider), for: .valueChanged)
}
}
// 2
var player: AVAudioPlayer
// 3
lazy var displayLink: CADisplayLink = CADisplayLink(target: self, selector: #selector(updatePlaybackStatus))
// 4
func startUpdatingPlaybackStatus() {
displayLink.add(to: .main, forMode: .common)
}
func stopUpdatingPlaybackStatus() {
displayLink.invalidate()
}
// 5
@objc
func updatePlaybackStatus() {
let playbackProgress = Float(player.currentTime / player.duration)
slider.setValue(playbackProgress, animated: true)
}
// 6
@objc
func didBeginDraggingSlider() {
displayLink.isPaused = true
}
@objc
func didEndDraggingSlider() {
let newPosition = player.duration * Double(slider.value)
player.currentTime = newPosition
displayLink.isPaused = false
}
touchDown
and valueChanged
events to get notified when the user begins and ends dragging the slider respectively. Also, set one's isContinuous
property to false
, so the valueChanged
event is reported only when the user releases the slider. This setup can also be done with Interface Builder and IBActions;self
as the target, the initialization of CADisplayLink should be done after initializing the parent class to be able to correctly reference it. For that purpose I made it lazy;Source - my blog post
Upvotes: 2
Reputation: 141
Tested code:-
Add timer variable
var timer: Timer?
in a function where you start playing your audio:
slidderrr.value = 0.0
slidderrr.maximumValue = Float((player?.duration)!)
audioPlayer.play()
timer = Timer.scheduledTimer(timeInterval: 0.0001, target: self, selector: #selector(self.updateSlider), userInfo: nil, repeats: true)
Don't forget to invalidate timer when you don't want to update slider
func stopPlayer() {
player?.stop()
timer?.invalidate()
print("Player and timer stopped")
}
Upvotes: 3
Reputation: 1
Have you tried
func setValue(Float, animated: Bool)
Sets the slider’s current value, allowing you to animate the change visually.
So something like slider.setValue(index, animated: true)
can be found here
https://developer.apple.com/documentation/uikit/uislider
let me know if that works!
Upvotes: 0