skip to Main Content

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.

2

Answers


  1. Chosen as BEST ANSWER

    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.


  2. 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)
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search