Home > OS >  AVPlayer -Play sound while seeking/scrubbing
AVPlayer -Play sound while seeking/scrubbing

Time:03-19

I have a uislider attached to my avplayer, is there a way to play sound from the video while scrubbing?

var player: AVPlayer?

func createPlayer() {
    player = AVPlayer()
    // init player with playerItem, asset, eventually add to playerLayer ...
    player?.volume = 1
}

@objc func sliderValueChanged(_ slider: UISlider) {

    let sliderValue = slider.value

    let seekTime = CMTimeMakeWithSeconds(Double(sliderValue), preferredTimescale: 1000)
    player?.seek(to: seekTime, toleranceBefore: .zero, toleranceAfter: .zero)
}

CodePudding user response:

Since the UISlider's values update continuously while you interact with it, the AVPlayer doesn't get a chance to play any audio since the seek function gets executed every time the slider's value is updated.

I used this SO example from the comments with some minor tweaks to get this working

At a high level:

  1. You need to add a second AVPlayer which will be responsible for playback during scrubbing
  2. When you start scrubbing, start playing the video in the second AVPlayer from where the first player currently was
  3. As soon as you start playing the second player, it should be locked from being updated by the scrubber and it should play for a short period of of time 1/4 to 1/2 a second (experiment with the time duration)
  4. After this time has elapsed, pause the second player and open it to be updated again - this is what creates that jagged noise

Here is a small example that gave me something close to what you are after:

import UIKit
import AVKit

class AVPlayerScrubVC: UIViewController {
    
    let playerViewController = AVPlayerViewController()
    var player: AVPlayer?
    var player2: AVPlayer?
    
    var isScrubbingLocked = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        title = "Player Scrub"
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        configurePlayer()
        configureScrubber()
    }
    
    private func configurePlayer()
    {
        // Add your own video URL
        let videoURL
            = Bundle.main.url(forResource: "sample_1",
                              withExtension: "mp4")
        
        if let videoURL = videoURL {
            player = AVPlayer(url: videoURL)
            
            player2 = AVPlayer(url: videoURL)
            player2?.volume = 5
            
            playerViewController.player = player
            playerViewController.showsPlaybackControls = false
            
            // Add the AVPlayerController as a child of the current
            // view controller
            self.addChild(playerViewController)
            
            // Configure the player view
            let playerView = playerViewController.view
            playerView?.backgroundColor = .clear
            playerView?.frame = self.view.bounds
            
            // Add the AVPlayerViewController's view as a subview
            // of the current view
            self.view.addSubview(playerView!)
            playerViewController.didMove(toParent: self)
            
            // Start playing the content
            playerViewController.player?.play()
        }
    }
    
    // Configure the UISlider
    private func configureScrubber() {
        let slider = UISlider()
        slider.translatesAutoresizingMaskIntoConstraints = false
        slider.minimumValue = 0
        
        let videoTime
            = Float(CMTimeGetSeconds((player?.currentItem?.asset.duration)!))
        
        slider.maximumValue = videoTime
        slider.addTarget(self, action: #selector(sliderValueChanged(_:)),
                         for: .valueChanged)
        
        view.addSubview(slider)
        
        view.addConstraints([
            
            slider.leadingAnchor
                .constraint(equalTo: view.leadingAnchor,
                                            constant: 16),
            slider.bottomAnchor
                .constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor,
                                           constant: -16),
            slider.trailingAnchor
                .constraint(equalTo: view.trailingAnchor,
                                             constant: -16),
            slider.heightAnchor
                .constraint(equalToConstant: 70)
            
        ])
    }
    
    @objc
    func sliderValueChanged(_ slider: UISlider) {
        
        let sliderValue = slider.value
        
        let seekTime = CMTimeMakeWithSeconds(Double(sliderValue),
                                             preferredTimescale: 1000)
        
        player?.seek(to: seekTime,
                     toleranceBefore: .zero,
                     toleranceAfter: .zero)
        
        if !isScrubbingLocked {
            audioScrub(to: seekTime)
        }
    }
    
    func audioScrub(to currentTime: CMTime) {
        
        DispatchQueue.main.async {
            
            // Lock the scrubbing
            self.isScrubbingLocked = true
            
            // play the simultaneous player
            self.player2?.play()
            
            // make sure that it plays for at least 0.5 of a second
            // before it stops to get that scrubbing effect
            // Play around with the delay to get the results best for you
            DispatchQueue.main.asyncAfter(deadline: .now()   0.5) {
                
                // now that 1/2 of a second has passed
                // (you can make it longer or shorter as you please)
                // - pause the simultaneous player
                self.player2?.pause()
                
                // now move the simultaneous player to the same point as the
                // original video player:
                self.player2?.seek(to: currentTime)
                
                // setting this variable back to true allows the process to be
                // repeated as long as video is being scrubbed:
                self.isScrubbingLocked = false
            }
        }
    }
}
  • Related