我正在尝试在我的应用程序中实现一个小功能。我目前正在以 AVAudioPlayers 播放声音,效果很好。我想补充的是用 UISlider 控制声音的位置(currentTime):有没有简单的方法来做到这一点?
我查看了一个 Apple 项目,但它非常混乱......你有样品或建议吗?
提前感谢大家
我正在尝试在我的应用程序中实现一个小功能。我目前正在以 AVAudioPlayers 播放声音,效果很好。我想补充的是用 UISlider 控制声音的位置(currentTime):有没有简单的方法来做到这一点?
我查看了一个 Apple 项目,但它非常混乱......你有样品或建议吗?
提前感谢大家
不应该是一个问题 - 只需将滑块设置为连续并将最大值设置为加载声音文件后播放器的持续时间。
编辑
我刚做了这个,它对我有用......
- (IBAction)slide {
player.currentTime = slider.value;
}
- (void)updateTime:(NSTimer *)timer {
slider.value = player.currentTime;
}
- (IBAction)play:(id)sender {
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"sound.caf" ofType:nil]];
NSError *error;
player = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
if (!player) NSLog(@"Error: %@", error);
[player prepareToPlay];
slider.maximumValue = [player duration];
slider.value = 0.0;
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTime:) userInfo:nil repeats:YES];
[player play];
}
滑块在 IB 中配置,作为开始播放的按钮。
var player: AVAudioPlayer!
var sliderr: UISlider!
@IBAction func play(_ sender: Any) {
var url = URL(fileURLWithPath: Bundle.main.path(forResource: "sound.caf", ofType: nil)!)
var error: Error?
do {
player = try AVAudioPlayer(contentsOf: url)
}
catch let error {
}
if player == nil {
print("Error: \(error)")
}
player.prepareToPlay()
sliderr.maximumValue = Float(player.duration)
sliderr.value = 0.0
Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.updateTime), userInfo: nil, repeats: true)
player.play()
}
func updateTime(_ timer: Timer) {
sliderr.value = Float(player.currentTime)
}
@IBAction func slide(_ slider: UISlider) {
player.currentTime = TimeInterval(slider.value)
}
为了扩展保罗的回答,您将滑块设置为与音频播放器的最大值连续duration
,然后添加您的一些对象(可能是视图控制器)作为滑块UIControlEventValueChanged
事件的目标;当您收到操作消息时,您会将AVAudioPlayer
'currentTime
属性设置为滑块的值。您可能还想NSTimer
在音频播放器播放时使用 更新滑块的值;+scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
是最简单的方法。
我需要稍微调整一下上面的答案才能让它工作。问题是使用
slider.maximumValue = [player duration];
slider.value = player.currentTime;
player.currentTime = slider.value;
不工作,因为滑块需要一个浮点数并且播放器 currentTime 和 dration 返回 CMTime。为了使这些工作,我将它们改编为:
slider.maximumValue = CMTimeGetSeconds([player duration]);
slider.value = CMTimeGetSeconds(player.currentTime);
player.currentTime = CMTimeMakeWithSeconds((int)slider.value,1);
如果您在拖动之间不需要任何数据,那么您应该简单地设置:
mySlider.isContinuous = false
否则,请尝试以下代码来控制触摸的每个阶段。
// audio slider bar
private lazy var slider: UISlider = {
let slider = UISlider()
slider.translatesAutoresizingMaskIntoConstraints = false
slider.minimumTrackTintColor = .red
slider.maximumTrackTintColor = .white
slider.setThumbImage(UIImage(named: "sliderThumb"), for: .normal)
slider.addTarget(self, action: #selector(onSliderValChanged(slider:event:)), for: .valueChanged)
// slider.isContinuous = false
return slider
}()
@objc func onSliderValChanged(slider: UISlider, event: UIEvent) {
guard let player = AudioPlayer.shared.player else { return }
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
// handle drag began
// I would stop the timer when drag begin
timer.invalidate()
case .moved:
// handle drag moved
// Update label's text for current playing time
case .ended:
// update the player's currTime and re-create the timer when drag is done.
player.currentTime = TimeInterval(slider.value)
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTime(_:)), userInfo: nil, repeats: true)
default:
break
}
}
}
我在播放音频文件和显示开始/结束时间以及使用 UISlider 控制歌曲时遇到的问题。
这个答案需要一些编辑,但毫无疑问它会起作用。
//UISlider init
lazy var slider: UISlider = {
let progress = UISlider()
progress.minimumValue = 0.0
progress.maximumValue = 100.0
progress.tintColor = UIColor.init(named: "ApplicationColor")
return progress
}()
var audioPlayer : AVAudioPlayer?
//First I've downloaded the audio and then playing it.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(trackAudio), userInfo: nil, repeats: true)
if let audioURLString = audioURL{
let urlstring = URL(string: audioURLString)!
downloadFromURL(url: urlstring) { (localURL, response, error) in
if let localURL = localURL{
self.playAudioFile(url: localURL)
}
}
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopTimer()
}
// Stop TimeInterval After View disappear
func stopTimer() {
if timer != nil {
timer?.invalidate()
audioPlayer?.stop()
audioPlayer = nil
timer = nil
}
}
@objc func sliderSelected(_ sender : UISlider){
if audioPlayer != nil{
if !isPlaying{
self.audioPlayer?.play()
playButton.setImage(UIImage.init(named: "AudioPause"), for: .normal)
isPlaying = true
}else{
self.audioPlayer?.currentTime = TimeInterval(Float(sender.value) * Float(self.audioPlayer!.duration) / 100.0)
if (sender.value / 100.0 == 1.0){
//Do something if audio ends while dragging the UISlider.
}
}
}
}
func downloadFromURL(url:URL,completion: @escaping((_ downladedURL: URL?,_ response :URLResponse?,_ error: Error?) -> Void)){
var downloadTask:URLSessionDownloadTask
downloadTask = URLSession.shared.downloadTask(with: url) {(URL, response, error) in
if let url = URL{
completion(url,nil,nil)
}else if let response = response{
completion(nil,response,nil)
}
if let error = error{
completion(nil,nil,error)
}
}
downloadTask.resume()
}
func playAudioFile(url:URL){
do{
self.audioPlayer = try AVAudioPlayer(contentsOf: url)
self.audioPlayer?.prepareToPlay()
self.audioPlayer?.delegate = self
self.audioPlayer?.play()
let audioDuration = audioPlayer?.duration
let audioDurationSeconds = audioDuration
minutes = Int(audioDurationSeconds!/60);
seconds = Int(audioDurationSeconds!.truncatingRemainder(dividingBy: 60))
} catch{
print("AVAudioPlayer init failed")
}
}
@objc func trackAudio() {
if audioPlayer != nil{
DispatchQueue.main.async {
print("HI")
let normalizedTime = Float(self.audioPlayer!.currentTime * 100.0 / self.audioPlayer!.duration)
self.slider.setValue(normalizedTime, animated: true)
let currentTime = self.audioPlayer?.currentTime
self.currentMinutes = Int(currentTime!/60);
self.currentSeconds = Int(currentTime!.truncatingRemainder(dividingBy: 60))
self.startTimeLabel.text = String(format: "%02i:%02i", self.currentMinutes, self.currentSeconds)
self.endTimeLabel.text = String(format: "%02i:%02i", self.minutes, self.seconds)
}
}
}
如果有人在 UI 滑块上寻找一个简单的 TouchDown 和 TouchUp,那么这很简单:
slider.addTarget(self, action: #selector(changeVlaue(_:)), for: .valueChanged)
slider.addTarget(self, action: #selector(sliderTapped), for: .touchDown)
slider.addTarget(self, action: #selector(sliderUntouched), for: .touchUpInside)
这是AVAudioPlayer
. handleScrubbing()和fingerLiftedFromSlider()中的一些代码是重复的,但无论如何......
这将让您显示 currentTimeLabel (通常在左侧)和 totalDurationLabel (通常在右侧)上的内容,中间有洗涤器/滑块。当您滑动滑块时,currentTime 将更新以显示滑块所在的位置。
有一些事情需要注意。如果在您触摸滑块之前播放器正在播放,当您滑动滑块时,播放器仍在播放。.began
您需要检查播放器是否正在播放,如果是则暂停播放,并将诸如 wasAudioPlayerPlayingBeforeSliderWasTouched 之类的变量设置为true,这样当您抬起手指时,它会从您抬起手指的任何位置继续播放。如果您不暂停播放器,则滑块不会平滑滑动。
当您抬起手指时,会检查onFingerLiftedStartAudioPlayerIfItWasPlayingBeforeTouchesBegan()
滑块是否处于结束时间。如果不是播放,它将运行audioEndTimeStopEverything()
.
在startAudioPlayer方法中,我使用了AVURLAsset
一个设置实际 url 的持续时间。我从这个答案中得到了它,它有一个很好的解释。
我将此代码与本地 url 一起使用,不确定这将如何与远程 url 一起使用。
import UIKit
import AVFoundation
class MyAudioController: UIViewController {
lazy var currentTimeLabel ... { ... }()
lazy var totalDurationLabel ... { ... }()
lazy vay pausePlayButton ... { ... }()
lazy var fastForwardButton ... { ... }()
lazy var rewindButton ... { ... }()
lazy var slider: UISlider = {
let s = UISlider()
s.translatesAutoresizingMaskIntoConstraints = false
s.isContinuous = true
s.minimumTrackTintColor = UIColor.red
s.maximumTrackTintColor = UIColor.blue
s.setThumbImage(UIImage(named: "circleIcon"), for: .normal)
s.addTarget(self, action: #selector(sliderValChanged(slider:event:)), for: .valueChanged)
return s
}()
weak var timer: Timer? // *** MAKE SURE THIS IS WEAK ***
var audioPlayer: AVAudioPlayer?
var wasAudioPlayerPlayingBeforeSliderWasTouched = false
override func viewDidLoad() {
super.viewDidLoad()
guard let myAudioUrl = URL(string: "...") else { return }
setAudio(with: myAudioUrl)
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print(error)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopAudioPlayer()
}
// 1. init your AVAudioPlayer here
func setAudioPlayer(with audioTrack: URL) {
do {
stopAudioPlayer() // if something was previously playing
audioPlayer = try AVAudioPlayer(contentsOf: audioTrack)
audioPlayer?.delegate = self
audioPlayer?.prepareToPlay()
audioPlayer?.volume = audioVolume
startAudioPlayer()
} catch let err as NSError {
print(err.localizedDescription)
}
}
// 2. Audio PLAYER - start / stop funcs
stopAudioPlayer() {
stopAudioTimer()
audioPlayer?.pause()
audioPlayer?.stop()
}
func startAudioPlayer() {
if let audioPlayer = audioPlayer, audioPlayer.isPlaying {
audioPlayer.pause()
}
audioPlayer?.currentTime = 0
audioPlayer?.play()
pausePlayButton.setImage(UIImage(named: "pauseIcon"), for: .normal)
startAudioTimer()
}
func startAudioTimer() {
stopAudioTimer()
slider.value = 0
currentTimeLabel.text = "00:00"
totalDurationLabel.text = "00:00"
guard let url = audioPlayer?.url else { return }
let assetOpts = [AVURLAssetPreferPreciseDurationAndTimingKey: true]
let asset = AVURLAsset(url: url, options: assetOpts)
let assetDuration: CMTime = asset.duration
let assetDurationInSecs: Float64 = CMTimeGetSeconds(assetDuration)
slider.maximumValue = Float(assetDurationInSecs)
totalDurationLabel.text = strFromTimeInterval(interval: TimeInterval(assetDurationInSecs))
runAudioTimer()
}
// 3. TIMER funcs
func runAudioTimer() {
if timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [weak self](_) in
self?.audioTimerIsRunning()
})
}
}
func audioTimerIsRunning() {
guard let audioPlayer = audioPlayer else { return }
let currentTime = audioPlayer.currentTime
if Float(currentTime) >= Float(slider.maximumValue) {
stopAudioTimer()
}
currentTimeLabel.text = strFromTimeInterval(interval: currentTime)
slider.value = Float(currentTime)
}
func stopAudioTimer() {
if timer != nil {
timer?.invalidate()
timer = nil
}
}
// slider funcs
@objc func sliderValChanged(slider: UISlider, event: UIEvent) {
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
checkIfAudioPlayerWasPlayingWhenSliderIsFirstTouched()
stopAudioTimer()
print("Finger Touched")
case .moved:
handleScrubbing()
print("Finger is Moving Scrubber")
case .ended:
print("Finger Lifted")
onFingerLiftedStartAudioPlayerIfItWasPlayingBeforeTouchesBegan()
fingerLiftedFromSlider()
default:
print("Something Else Happened In Slider")
}
}
}
func checkIfAudioPlayerWasPlayingWhenSliderIsFirstTouched() {
guard let audioPlayer = audioPlayer else { return }
if audioPlayer.isPlaying {
audioPlayer.pause()
wasAudioPlayerPlayingBeforeSliderWasTouched = true
}
}
func handleScrubbing() {
guard let audioPlayer = audioPlayer else { return }
let sliderValue = TimeInterval(slider.value)
currentTimeLabel.text = strFromTimeInterval(interval: sliderValue)
audioPlayer.currentTime = sliderValue
if audioPlayer.currentTime >= audioPlayer.duration {
audioEndTimeStopEverything()
}
}
func onFingerLiftedStartAudioPlayerIfItWasPlayingBeforeTouchesBegan() {
if wasAudioPlayerPlayingBeforeSliderWasTouched {
wasAudioPlayerPlayingBeforeSliderWasTouched = false
guard let audioPlayer = audioPlayer else { return }
if slider.value >= slider.maximumValue {
audioEndTimeStopEverything()
} else {
audioPlayer.play()
}
}
}
func fingerLiftedFromSlider() {
guard let audioPlayer = audioPlayer else { return }
if !audioPlayer.isPlaying { // this check is necessary because if you paused the audioPlayer, then started sliding, it should still be paused when you lift you finger up. It it's paused there is no need for the timer function to run.
let sliderValue = TimeInterval(slider.value)
currentTimeLabel.text = strFromTimeInterval(interval: sliderValue)
audioPlayer.currentTime = sliderValue
return
}
runAudioTimer()
}
func audioEndTimeStopEverything() {
stopAudioPlayer()
pausePlayButton.setImage(UIImage("named: playIcon"), for: .normal)
guard let audioPlayer = audioPlayer else { return }
// for some reason when the audioPlayer would reach its end time it kept resetting its currentTime property to zero. I don't know if that's meant to happen or a bug but the currentTime would be zero and the slider would be at the end. To rectify the issue I set them both to their end times
audioPlayer.currentTime = audioPlayer.duration
slider.value = slider.maximumValue
}
}
extension MyAudioController: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
audioEndTimeStopEverything()
}
func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
if let error = error {
print(error.localizedDescription)
}
}
}
这是strFromTimeInterval(interval: )
我从这里得到的功能。我只使用它是因为我不想打扰毫秒。上面的代码是使用带有分钟和秒的音频文件运行的,而不是小时。如果您对工作时间有任何问题,您也可以将此功能换成此答案
extension MyAudioController {
func strFromTimeInterval(interval: TimeInterval) -> String {
let time = NSInteger(interval)
let seconds = time % 60
let minutes = (time / 60) % 60
let hours = (time / 3600)
var formatString = ""
if hours == 0 {
if (minutes < 10) {
formatString = "%2d:%0.2d"
} else {
formatString = "%0.2d:%0.2d"
}
return String(format: formatString,minutes,seconds)
} else {
formatString = "%2d:%0.2d:%0.2d"
return String(format: formatString,hours,minutes,seconds)
}
}
}