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:
- You need to add a second AVPlayer which will be responsible for playback during scrubbing
- When you start scrubbing, start playing the video in the second AVPlayer from where the first player currently was
- 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)
- 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
}
}
}
}