【问题标题】:Jumpy UISlider when scrubbing - Using UISlider with AVPlayer擦洗时跳跃的 UISlider - 将 UISlider 与 AVPlayer 一起使用
【发布时间】:2017-06-04 23:46:12
【问题描述】:

我正在使用 AvPlayer 并尝试设置一个滑块以允许擦除音频文件。我在选择滑块时遇到问题,它会在整个地方跳跃。然后它会回到原点一秒钟,然后再回到它被拖到的位置。

您在 Gif 上看不到我的光标,但平滑拉长的拖动是我移动旋钮,然后快速鞭子是滑块行为不端。

我花了几个小时在 Stack Overflow 上搜索和梳理,但无法弄清楚我在这里做错了什么,很多类似的问题在 ObjC 中都很古老。

这是我认为导致问题的代码部分,它确实处理了滑块被移动的事件:我也尝试过不使用 if 语句,但没有看到不同的结果。

@IBAction func horizontalSliderActioned(_ sender: Any) {

    horizontalSlider.isContinuous = true

    if self.horizontalSlider.isTouchInside {

        audioPlayer?.pause()
            let seconds : Int64 = Int64(horizontalSlider.value)
                let preferredTimeScale : Int32 = 1
                    let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
                        audioPlayerItem?.seek(to: seekTime)
                            audioPlayer?.play()

    } else {

        let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
            let seconds : Float64 = CMTimeGetSeconds(duration)
                self.horizontalSlider.value = Float(seconds)
    }
}

我将在下面列出我的整个班级以供参考。

import UIKit
import Parse
import AVFoundation
import AVKit


class PlayerViewController: UIViewController, AVAudioPlayerDelegate {

    @IBOutlet var horizontalSlider: UISlider!

    var selectedAudio: String!

    var audioPlayer: AVPlayer?
    var audioPlayerItem: AVPlayerItem?

    var timer: Timer?


    func getAudio() {

        let query = PFQuery(className: "Part")
                query.whereKey("objectId", equalTo: selectedAudio)
                    query.getFirstObjectInBackground { (object, error) in

                if error != nil || object == nil {
                    print("The getFirstObject request failed.")

                } else {
                    print("There is an object now get the Audio. ")

                        let audioFileURL = (object?.object(forKey: "partAudio") as! PFFile).url
                            self.audioPlayerItem = AVPlayerItem(url: NSURL(string: audioFileURL!) as! URL)
                                self.audioPlayer = AVPlayer(playerItem: self.audioPlayerItem)
                                    let playerLayer = AVPlayerLayer(player: self.audioPlayer)
                                        playerLayer.frame = CGRect(x: 0, y: 0, width: 10, height: 10)
                                            self.view.layer.addSublayer(playerLayer)

                        let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
                            let seconds : Float64 = CMTimeGetSeconds(duration)
                                let maxTime : Float = Float(seconds)
                                    self.horizontalSlider.maximumValue = maxTime

                        self.audioPlayer?.play()

                        self.timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(PlayerViewController.audioSliderUpdate), userInfo: nil, repeats: true)
                }
            }
    }


    @IBOutlet var playerButton: UIButton!


    func playerButtonTapped() {

        if audioPlayer?.rate == 0 {
            audioPlayer?.play()
                self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)

        } else {
            audioPlayer?.pause()
                self.playerButton.setImage(UIImage(named: "pause"), for: UIControlState.normal)
        }

    }


    override func viewDidLoad() {
        super.viewDidLoad()

        horizontalSlider.minimumValue = 0
            horizontalSlider.value = 0

                self.playerButton.addTarget(self, action: #selector(PlayerViewController.playerButtonTapped), for: UIControlEvents.touchUpInside)

                    getAudio()
    }



    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        NotificationCenter.default.addObserver(self, selector: #selector(PlayerViewController.finishedPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.audioPlayerItem)

    }



    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillAppear(animated)
            // remove the timer
                self.timer?.invalidate()
                    // remove the observer when leaving page
                        NotificationCenter.default.removeObserver(audioPlayer?.currentItem! as Any)
        }



    func finishedPlaying() {

        // need option to play next track

        self.playerButton.setImage(UIImage(named: "play"), for: UIControlState.normal)

            let seconds : Int64 = 0
                let preferredTimeScale : Int32 = 1
                    let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)

                        audioPlayerItem!.seek(to: seekTime)
    }

    @IBAction func horizontalSliderActioned(_ sender: Any) {

        horizontalSlider.isContinuous = true

        if self.horizontalSlider.isTouchInside {

            audioPlayer?.pause()
                let seconds : Int64 = Int64(horizontalSlider.value)
                    let preferredTimeScale : Int32 = 1
                        let seekTime : CMTime = CMTimeMake(seconds, preferredTimeScale)
                            audioPlayerItem?.seek(to: seekTime)
                                audioPlayer?.play()

        } else {

            let duration : CMTime = (self.audioPlayer?.currentItem!.asset.duration)!
                let seconds : Float64 = CMTimeGetSeconds(duration)
                    self.horizontalSlider.value = Float(seconds)
        }
    }



    func audioSliderUpdate() {

        let currentTime : CMTime = (self.audioPlayerItem?.currentTime())!
            let seconds : Float64 = CMTimeGetSeconds(currentTime)
                let time : Float = Float(seconds)
                    self.horizontalSlider.value = time
    }

}

【问题讨论】:

    标签: ios swift avplayer uislider


    【解决方案1】:

    您需要在用户选择滑块上的拇指后立即删除观察者并使计时器无效,并在拖动完成后再次添加它们

    在加载播放器的位置添加这样的目标:

        mySlider.addTarget(self, 
    action: #selector(PlayerViewController.mySliderBeganTracking(_:)),
    forControlEvents:.TouchDown)
    
        mySlider.addTarget(self,
    action: #selector(PlayerViewController.mySliderEndedTracking(_:)),
    forControlEvents: .TouchUpInside)
    
       mySlider.addTarget(self,
    action: #selector(PlayerViewController.mySliderEndedTracking(_:)),
    forControlEvents: .TouchUpOutside )
    

    并在mySliderBeganTracking 中删除观察者并使计时器无效,然后在mySliderEndedTracking 中添加观察者 为了更好地控制播放器中发生的事情,请编写 2 个函数:addObserversremoveObservers 并在需要时调用它们

    【讨论】:

    • 嘿Mohy,你解决问题了吗?我也遇到过。
    • 是的@JamesRao,它是由我的观察者和计时器引起的(用于在几秒钟后自动显示/隐藏工具栏),每次用户触摸滑块的拇指图像时我都会删除它们,并将它们添加回来当用户释放滑块时:)
    • 我终于通过 Pause at touch down 和 Play at touch up 做到了。太感谢了! :)
    • @JamesRao 即使我遇到同样的问题,我也遵循了这个解决方案,但仍然遇到同样的问题,你能检查一下这个stackoverflow.com/questions/62774834/…
    • @JamesRao 我仍然遇到同样的问题,你能检查一下吗? stackoverflow.com/questions/62774834/…
    【解决方案2】:

    尝试使用如下的时间滑块值:

     @IBAction func timeSliderDidChange(_ sender: UISlider) {
                AVPlayerManager.sharedInstance.currentTime = Double(sender.value)
            }
    
    var currentTime: Double {
            get {
                return CMTimeGetSeconds(player.currentTime())
            }
    
            set {
                if self.player.status == .readyToPlay {
                    let newTime = CMTimeMakeWithSeconds(newValue, 1)
                    player.seek(to: newTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero) { ( _ ) in
                        self.updatePlayerInfo()
                    }
                }
            }
        }
    

    并在用户释放滑块时传递滑块的值,当用户在滑块上进行交互时也不更新当前播放的滑块值

    【讨论】:

      【解决方案3】:

      Swift 5、Xcode 11

      我遇到了同样的问题,显然是periodicTimeObserver 导致返回不正确的时间,从而导致滑块滞后或跳跃。我通过在滑块更改时删除周期性时间观察器并在调用完成处理程序时将其添加回来来解决它。

      @objc func sliderValueChanged(_ playbackSlider: UISlider, event: UIEvent){
      
          let seconds : Float = Float(playbackSlider.value)
          let targetTime:CMTime = CMTimeMake(value: Int64(seconds), timescale: 1)
      
      
          if let touchEvent = event.allTouches?.first {
              switch touchEvent.phase {
              case .began:
                  // handle drag began
                  //Remove observer when dragging is in progress
                  self.removePeriodicTimeObserver()
      
      
                  break
              case .moved:
                  // handle drag moved
      
                  break
              case .ended:
                  // handle drag ended
      
                  //Add Observer back when seeking got completed
                  player.seek(to: targetTime, toleranceBefore: .zero, toleranceAfter: .zero) { [weak self] (value) in
                      self?.addTimeObserver()
                  }
      
                  break
              default:
                  break
              }
          }
      }
      

      【讨论】:

      • @kedarSurekar 我已经尝试过你的解决方案,但我在向前拖动时仍然会跳动滑块,你能帮帮我吗?
      • @KedarSurekar 你能检查一下这个stackoverflow.com/questions/62774834/…
      • @KedarSurekar 我已经解决了这个问题。谢谢
      • 很高兴听到....有什么问题?? @AnilkumariOS-ReactNative
      • @KedarSurekhar 我已经回答了我的问题,你可以在那里查看我更新的答案
      【解决方案4】:

      确保执行以下操作:

      1. 滑块的 isContinuous NOT 设置为 false。
      2. 在搜索之前暂停播放器。
      3. 寻找位置并使用完成处理程序以继续播放。

      示例代码:

      @objc func sliderValueChanged(sender: UISlider, event: UIEvent) {
      
          let roundedValue = sender.value.rounded()
          guard let touchEvent = event.allTouches?.first else { return }
          
          switch touchEvent.phase {
              
              case .began:
                  PlayerManager.shared.pause()
                  
              case .moved:
                  print("Slider moved")
      
              case .ended:
                  PlayerManager.shared.seek(to: roundedValue, playAfterSeeking: true)
              
              default: ()
          }
      }
      

      这是寻找的功能:

      func seek(to: Float, playAfterSeeking: Bool) {
          player?.seek(to: CMTime(value: CMTimeValue(to), timescale: 1), completionHandler: { [weak self] (status) in
              if playAfterSeeking {
                  self?.play()
              }
          })
      }
      

      【讨论】:

        【解决方案5】:

        这对我来说是一个临时解决方案,我观察到反弹只有一次,所以我设置了一个int值isSeekInProgress

        1. sliderDidFinishisSeekInProgress = 0
        2. 回复 avplayer 时间变化:
            if (self.isSeekInProgress > 1) {
                float sliderValue = 1.f / (self.slider.maximumValue - self.slider.minimumValue) * progress;
                //    if (sliderValue > self.slider.value ) {
                self.slider.value = sliderValue;
            }else {
                self.isSeekInProgress += 1;
            }
        
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多