用Swift实现UISlider作为音频播放进度条

21

我看过一些使用Objective-C实现此功能的帖子,但我无法通过Swift实现相同的功能。

具体而言,我无法弄清如何在下面实现addPeriodicTimeObserverForInterval

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()
    }
任何协助将不胜感激。

5
如果你试图在音频播放过程中更新滑块,我建议使用 CADisplayLink 来更新。CADisplayLink 可以与屏幕刷新率同步,并在每次刷新时调用选择器,就像 NSTimer 的图形优化版本一样,使用起来也非常简单,可以避免在计时器触发速度快于屏幕重绘时排队 UI 更新。这些是文档链接。你可以设置选择器,使显示链接调用并检查进度,然后更新滑块位置。 - Dare
@ Dare,谢谢你。我不知道那个存在。 - allocate
4个回答

33

感谢 Dare 上面的建议,以下是我如何实现这个目标的方法:

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)

}

一个快速的提示 - 确保在使用完显示链接后使其无效。如果不这样做,它将会无限制地触发,而不管音频播放器的状态如何,直到某些东西崩溃。 - Dare
@Dare,感谢您的帮助。我错误地认为解雇 ViewController 将终止它(但我非常错了)。代码已更新,包括全局声明和 invalidate() 调用。 - allocate
这就是我所需要的,对于我的情况,在滑动条下面还有一个额外的音频播放计时器。当触摸滑动条以暂停更新器updater.isPaused = true,并在用户释放从滑动条拖动时恢复它。 - Zhou Haibo
你的 updater 在后台模式下是如何工作的?我发现当我从锁定屏幕命令中心重新打开时,我的滑块无法恢复处理进程。 - Zhou Haibo

9

仅就我之前的评论作进一步说明,以下是我实现此功能的方法,看起来效果不错。欢迎提出任何Swift纠正意见,目前我还是个Obj-C爱好者。

@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)
}

我在故事板上设置了时间滑块的范围为0到1。

5

在Swift 4中,语法已经发生了变化:

updater = CADisplayLink(target: self, selector: #selector(self.trackAudio))
updater.preferredFramesPerSecond = 1
updater.add(to: RunLoop.current, forMode: RunLoopMode.commonModes)

而这个函数(我之前已经将 progressSlider.maxValue 设置为 player.duration):

@objc func trackAudio() {
    progressSlider.value = Float(player!.currentTime)
 }

5

对于Swift,我可以这样处理:

  1. I set the maximum value of the scrubSlider to the duration of the music file(.mp3) that was loaded in this method

    override 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)
    
    }
    
  2. 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)
    
    }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接