音频播放器在滑块每秒更新时出现卡顿。

huangapple go评论94阅读模式
英文:

audio player stutters when slider updates every second

问题

我在我的电视应用中使用TvOSSlider来创建一个进度条并使用AVAudioPlayer播放音频轨道。我的问题是,每次通过代码更新进度条/滑块时都会出现卡顿。所以目前它被设置为每1.0秒卡顿一次。tvOS不支持UISlider,这就是我使用这个不同库的原因。

func playAudio(url: URL, completion: (() -> Void)? = nil) {
    stopAudio()
    do {
        audioPlayer = try AVAudioPlayer(contentsOf: url as URL)
        audioPlayer?.delegate = self
        audioPlayer?.prepareToPlay()
        audioPlayer?.play()
        isPaused = false
        // 启动播放计时器以更新进度条
        DispatchQueue.main.async { [self] in
            // 这是设置计时器并调用updateSlider方法的地方。我尝试将它从0.1秒设置到5秒
            playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
        }
        completion?()
    } catch let error as NSError {
        print("初始化AVAudioPlayer时出错:\(error.localizedDescription)")
        completion?()
    }
}

// 根据当前播放时间更新滑块值
@objc func updateSlider() {
    guard let audioPlayer = audioPlayer else { return }
    slider?.setValue(Float(audioPlayer.currentTime / audioPlayer.duration), animated: true) // 这一行导致卡顿
}

我尝试使用后台DispatchQueue,但它会导致应用崩溃,提示必须从主线程调用slider.setValue,因为它更新了UI。我还尝试在后台线程中播放音频,但这从一开始就是一个注定失败的想法。以下是我的音频管理器代码:

import Foundation
import AVFAudio
import TvOSSlider

class AudioManager {
    static let shared = AudioManager()
    
    private var audioPlayer: AVAudioPlayer?
    var playbackTimer: Timer?
    private var isPaused = false
    var slider: TvOSSlider? // 添加对滑块的引用
    
    private init() { }
    
    func playAudio(url: URL, completion: (() -> Void)? = nil) {
        stopAudio()
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url as URL)
            audioPlayer?.prepareToPlay()
            audioPlayer?.play()
            isPaused = false
            // 启动播放计时器以更新进度条
            DispatchQueue.main.async { [self] in
                playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
            }
            completion?()
        } catch let error as NSError {
            print("初始化AVAudioPlayer时出错:\(error.localizedDescription)")
            completion?()
        }
    }
    
    // 其他方法...
}

这是我在视图控制器中使用它的方式:

import TvOSSlider

class MyViewController: UIViewController {
...

    @IBOutlet weak var tvosSlider: TvOSSlider!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
        playSongFromUrl()
        getData()
        
    }

    func setupView(){
        
        AudioManager.shared.slider = tvosSlider
        AudioManager.shared.slider?.addTarget(AudioManager.shared, action: #selector(AudioManager.sliderValueChanged(_:)), for: .valueChanged)
        // 其他设置...
    }

    func play(url: URL) {
        AudioManager.shared.playAudio(url: url){
            self.stopLoader()
        }
    }
...
}

现在我无法找到解决方案。任何帮助将不胜感激。

英文:

I am using TvOSSlider in my tv app to create a seek-bar and play audio track with AVAudioPlayer. My problem is that it is stuttering every time i update the seekbar/slider through code. So currently it is set to stutter every 1.0seconds. UISlider is unavailable in tvOS that is why i am using this different library.

func playAudio(url: URL, completion: (() -> Void)? = nil) {
    stopAudio()
    do {
        audioPlayer = try AVAudioPlayer(contentsOf: url as URL)
        audioPlayer?.delegate = self
        audioPlayer?.prepareToPlay()
        audioPlayer?.play()
        isPaused = false
        // Start the playback timer to update the seek bar
        DispatchQueue.main.async { [self] in
            // This is where timer is set and update Slider method is called. I tried to set it from 0.1 sec to 5 sec
            playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
        }
        completion?()
    } catch let error as NSError {
        print("Error initializing AVAudioPlayer: \(error.localizedDescription)")
        completion?()
    }
}
// Update the slider value based on the current playback time
@objc func updateSlider() {  // This function gets called above
    guard let audioPlayer = audioPlayer else { return }
    slider?.setValue(Float(audioPlayer.currentTime / audioPlayer.duration), animated: true) // This line causes the stuttering
}

I have tried background DispatchQueue but it crashes the app saying slider.setvalue must be called from main thread as it updates ui. I also tried playing audio in the background thread but that was a doomed idea to begin with. Following is my audio manager code

import AVFAudio
import TvOSSlider

class AudioManager {
    static let shared = AudioManager()
    
    private var audioPlayer: AVAudioPlayer?
    var playbackTimer: Timer?
    private var isPaused = false
    var slider: TvOSSlider? // Add a reference to the slider
    
    private init() { }
    
    func playAudio(url: URL, completion: (() -> Void)? = nil) {
        stopAudio()
        do {
            audioPlayer = try AVAudioPlayer(contentsOf: url as URL)
            audioPlayer?.prepareToPlay()
            audioPlayer?.play()
            isPaused = false
            // Start the playback timer to update the seek bar
            DispatchQueue.main.async { [self] in
                playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
            }
            completion?()
        } catch let error as NSError {
            print("Error initializing AVAudioPlayer: \(error.localizedDescription)")
            completion?()
        }
    }
    
    func togglePlayPause() {
        guard let audioPlayer = audioPlayer else {
            return
        }
        
        if audioPlayer.isPlaying {
            audioPlayer.pause()
            playbackTimer?.invalidate()
            playbackTimer = nil
            isPaused = true
        } else {
            audioPlayer.play()
            playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
            isPaused = false
        }
    }

    func stopAudio() {
        audioPlayer?.stop()
        audioPlayer = nil
        isPaused = false
        
        playbackTimer?.invalidate()
        playbackTimer = nil
    }
    
    // MARK: - Slider methods
    
    // Update the slider value based on the current playback time
    @objc func updateSlider() {
        guard let audioPlayer = audioPlayer else { return }
        slider?.setValue(Float(audioPlayer.currentTime / audioPlayer.duration), animated: true)
    }
    
    // Seek to the desired position in the audio
    @objc func sliderValueChanged(_ sender: TvOSSlider) {
        guard let audioPlayer = audioPlayer else { return }
        let targetTime = TimeInterval(sender.value) * audioPlayer.duration
        audioPlayer.currentTime = targetTime
    }
    
}

And here is my use of this in my view controller

import TvOSSlider

class MyViewController: UIViewController {
...

    @IBOutlet weak var tvosSlider: TvOSSlider!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        setupView()
        playSongFromUrl()
        getData()
        
    }

    func setupView(){
        
//        // Set the AudioManager's slider property
        AudioManager.shared.slider = tvosSlider
        AudioManager.shared.slider?.addTarget(AudioManager.shared, action: #selector(AudioManager.sliderValueChanged(_:)), for: .valueChanged)
        ...
    }

    func play(url: URL) {
        AudioManager.shared.playAudio(url: url){
            self.stopLoader()
        }
    }
...
}
    

Now i am unable to find the solution. Any help will be appreciated.

答案1

得分: 0

class AudioManager {
    // 从
    // playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
    // 到这将提供更流畅的体验(1.0 到 0.1)
    playbackTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
}
英文:
class AudioManager {
// From 
// playbackTimer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)
// To this will give smoother experience (1.0 to 0.1)
playbackTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updateSlider), userInfo: nil, repeats: true)

}

答案2

得分: 0

我最终编辑了库TvOSSlider的代码来解决卡顿问题。

此方法用于更新滑块slider?.setValue(Float(audioPlayer.currentTime / audioPlayer.duration), animated: true)。我右键单击setValue方法,然后选择跳转到定义,在那里我点击解锁以进行代码更改,并进行了以下更改。

/// 用于从连续范围中选择单个值的控件。
public final class TvOSSlider: UIControl {
...

    public func setValue(_ value: Float, animated: Bool) {
        self.value = value
//        停止减速计时器() \\ 我注释掉了这一行以防止卡顿。
        
        if animated {
            UIView.animate(withDuration: animationDuration) {
                self.setNeedsLayout()
                self.layoutIfNeeded()
            }
        }
    }
}

所以在上面的方法中,我唯一改变的是我注释掉了stopDeceleratingTimer()
现在我不完全明白它的作用。然而,这解决了我的问题,没有引入新问题。所以庆祝一下。

英文:

I ended up editing in the library TvOSSlider's code to solve the stuttering problem.

This method is used to update the slider slider?.setValue(Float(audioPlayer.currentTime / audioPlayer.duration), animated: true) I right click on setValue method and select Jump to definition there I click unlock on making changes to the code and made following changes.

/// A control used to select a single value from a continuous range of values.
public final class TvOSSlider: UIControl {
...

    public func setValue(_ value: Float, animated: Bool) {
        self.value = value
//        stopDeceleratingTimer() \\ I commented this line to prevent stuttering.
        
        if animated {
            UIView.animate(withDuration: animationDuration) {
                self.setNeedsLayout()
                self.layoutIfNeeded()
            }
        }
    }
}

So in above method the only thing I changed was i commented stopDeceleratingTimer().
Now I do not have a complete grasp of what it does. However, this solved my problem without creating a new one. So cheers.

huangapple
  • 本文由 发表于 2023年7月4日 19:59:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/76612403.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定