【问题标题】:UISlider to control AVAudioPlayerUISlider 控制 AVAudioPlayer
【发布时间】:2011-02-08 22:28:58
【问题描述】:

我正在尝试在我的应用中实现一个小功能。我目前正在以 AVAudioPlayers 播放声音,效果很好。我想补充的是用 UISlider 控制声音的位置(currentTime):有没有简单的方法来做到这一点?

我查看了一个 Apple 项目,但它非常混乱......你有样品或建议吗?

提前感谢大家

【问题讨论】:

标签: iphone objective-c avaudioplayer uislider


【解决方案1】:

应该没问题 - 只需将滑块设置为连续并在加载声音文件后将最大值设置为播放器的持续时间。

编辑

我刚刚做了这个,它对我有用......

- (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 中配置,作为开始播放的按钮。

Swift 3.0 更新:

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

【讨论】:

  • 我这样编码,它可以正确构建但它不起作用:_progressBar.continuous = YES; _progressBar.value = [suono01 当前时间]; _progressBar.minimumValue = 0.0; _progressBar.maximumValue = suono01.duration;
  • 谢谢伙计..这工作非常好..我有一个问题...如果我滑动我的搜索栏它会崩溃
  • 感谢@Paul,请提供手动更改滑块值时寻找音频的代码。
【解决方案2】:

要扩展 paull 的答案,您可以将滑块设置为与音频播放器的 duration 的最大值连续,然后添加您的一些对象(可能是视图控制器)作为滑块 @987654322 的目标@ 事件;当您收到操作消息时,您会将AVAudioPlayercurrentTime 属性设置为滑块的值。 您可能还想使用NSTimer 在音频播放器播放时更新滑块的值; +scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 是最简单的方法。

【讨论】:

  • 我是 iphone 开发的新手,所以我轻轻地请你在做任何事情时跟随我......所以在 viewDidLoad 方法中我启动了音频,并添加了这个:_progressBar.continuous =是的; _progressBar.minimumValue = 0.0; _progressBar.maximumValue = suono01.duration; [NSTimer scheduleTimerWithTimeInterval:1.0 target:self 选择器:@selector(onTimer) userInfo:nil repeats:YES];现在我需要使用 UIControlEventValueChanged 做什么?
  • 我继续这样,但如果我移动滑块,currentTime 不会移动: - (void)onTimer{ UISlider *sliderozzo = _progressBar; [sliderozzo addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventValueChanged]; _progressBar.value = [suono01 当前时间]; } - (void)buttonClicked{ _progressBar.value = [suono01 currentTime]; }
【解决方案3】:

我需要稍微调整一下上面的答案才能让它发挥作用。问题是使用

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);

【讨论】:

  • 确实有效。我认为不需要将此值转换为秒,因为它们已经以秒为单位......
【解决方案4】:

如果您在拖动之间不需要任何数据,那么您应该简单地设置: 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
            }
        }
    }

【讨论】:

    【解决方案5】:

    我在播放音频文件、显示开始/结束时间以及使用 UISlider 控制歌曲时遇到的问题。

    1. 没有下载到临时文件夹就不能直接播放音频。
    2. UISlider 在较低的 iOS 版本(即 12.4/13.1)的主线程上崩溃
    3. UISlider 的平滑滚动。
    4. 计算和更新歌曲的开始/结束时间。

    这个答案需要一些编辑,但毫无疑问它会起作用。

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

    【讨论】:

      【解决方案6】:

      如果有人在 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)
      

      【讨论】:

        【解决方案7】:

        这是AVAudioPlayer 的完整设置。 handleScrubbing()fingerLiftedFromSlider() 中的一些代码是重复的,但无论如何...

        这将让您显示 currentTimeLabel(通常在左侧)和 totalDurationLabel(通常在右侧)上的内容,中间有滑动条/滑块。当您滑动滑块时,currentTime 将更新以显示滑块所在的位置。

        有一些事情需要注意。如果在您触摸滑块之前播放器正在播放,当您滑动滑块时,播放器仍在播放。在.began 中,您需要检查播放器是否正在播放,如果是则暂停播放,并将类似 wasAudioPlayerPlayingBeforeSliderWasTouched 的变量设置为 true 以便在抬起手指时它会继续播放从您抬起手指的任何地方播放。如果您不暂停播放器,则滑块不会平滑滑动。

        当您抬起手指时,会检查onFingerLiftedStartAudioPlayerIfItWasPlayingBeforeTouchesBegan() 以查看滑块是否处于结束时间。如果不是播放,它将运行audioEndTimeStopEverything() 中的代码。

        startAudioPlayer 方法中,我使用了AVURLAsset 来设置实际 url 的持续时间。我是从 answer 那里得到的,它有很好的解释。

        我将此代码与本地 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)
                }
            }
        }
        

        这是我从here 得到的strFromTimeInterval(interval: ) 函数。我只使用它是因为我不想打扰毫秒。上面的代码是使用带有分钟和秒的音频文件运行的,而不是小时。如果您对工作时间有任何问题,也可以将此功能换成this answer

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-09-03
          • 1970-01-01
          • 1970-01-01
          • 2012-01-25
          • 1970-01-01
          • 2016-02-06
          • 1970-01-01
          相关资源
          最近更新 更多