英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论