Hi i've a classic player
var player = AVPlayer(url: fileUrl)
player.play()
Is possibile to set FPS of player? For example i want to play a slow motion video (240fps) at 30fps
i try
player.play()
player.rate = 0.5
but this only play a 240fps video at 120fps.
Is possible to change the FPS during video playback
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: false) {_ in
player.rate = 0.5
}
this works great but cannot set 30FPS like above
Thanks!!
If I understand the question properly, you just want to slow the content, so setting the rate is the correct way (and you can do it also during content playback).
If you want to go from 240 to 30 fps than it means you want to set the rate to 30/240 which means 1/8.
So this should do:
player.rate = Float(1)/Float(8)
You should also set the playerItem audioPitchAlgorithm to something other than lowQualityZeroLatency to allow it to go below 0.5 up to 1/32
lowQualityZeroLatency
This algorithm is suitable for brief fast-forward and rewind effects as well as low quality voice. The rate is snapped to {0.5, 0.666667, 0.8, 1.0, 1.25, 1.5, 2.0}.
This snipped that I tested correctly went to 1/8th of the speed.
let playerItem = AVPlayerItem(url: URL(string:"https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8")!)
playerItem.audioTimePitchAlgorithm = .varispeed
let avPlayer = AVPlayer(playerItem: playerItem)
let vc = AVPlayerViewController()
vc.player = avPlayer
avPlayer.rate = 0.125
self.present(vc, animated: true)
Related
I run the code
DispatchQueue.main.async { [weak self] in
self?.sliderView.elapsedLabel.text = self?.player.currentTime.secondsToString()
}
Which every second of an observer firing that watches the audio file progression, will update a label of remaning seconds. It forces main thread because its UI related...
player.event.secondElapse.addListener(self, handleAudioPlayerSecondElapsed)
Meanwhile I have an animation loop playing in the background using AVKit
I am finding that, when the label updates, it stops the animation, as both are real time main thread updates?
How is this handled to allow 2 processes to update the UI at the same time without clashing with each other?
Updated code for the animating video:
let playerItem = AVPlayerItem(url: file)
videoPlayer = AVQueuePlayer(items: [playerItem])
playerLooper = AVPlayerLooper(player: videoPlayer!, templateItem: playerItem)
videoPlayerLayer = AVPlayerLayer(player: videoPlayer)
videoPlayerLayer!.frame = inView.bounds
videoPlayerLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill
then I call the standard
videoPlayer?.play()
I'm using an AVPlayer to play a remote progressive download (i.e. non-HLS) video. But, I can't figure out how to control its buffering behavior.
I would like to pre-fetch 2 seconds of the video before it's ready to play, and also to stop buffering when the video is paused.
Here's my setup:
let asset = AVURLAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
let player = AVPlayer()
I tried the following, without success:
// doesn't start buffering
playerItem.preferredForwardBufferDuration = 2.0
// doesn't stop buffering
playerItem.preferredForwardBufferDuration = 2.0
player.replaceCurrentItem(with: playerItem)
I tried player.automaticallyWaitsToMinimizeStalling = true in both cases, and in combination with various player.pause() or player.rate = 0 - doesn't work.
A potential approach that comes to mind is to observe for loadedTimeRanges until the first 2 seconds loaded and set current item of the player to nil.
let c = playerItem.publisher(for: \.loadedTimeRanges, options: .new)
.compactMap { $0.first as? CMTimeRange }
.sink {
if $0.duration.seconds - $0.start.seconds > 2 {
player.replaceCurrentItem(with: nil)
}
}
This would work for pre-buffer, but it doesn't work for pausing, because it makes the video blank instead of paused. (And at this point, I feel I'm attempting to reimplement/interfere with some core buffering functionality)
I am trying to add reverse playback to video which I am playing with AVPlayer :
let videoURL = Bundle.main.url(forResource: "video", withExtension: "mov")
videoPlayer = AVPlayer(url:videoURL!)
let playerLayer = AVPlayerLayer(player: videoPlayer)
playerLayer.frame = self.view.frame
videoView.layer.addSublayer(playerLayer)
videoPlayer.play()
I searched and found if I change AVPlayer's rate to -1 the movie plays in reverse mode :
func reverseVideo() {
videoPlayer.play()
videoPlayer.rate = -1
}
This does work fine but reverse playback contains lots of lag and it doesn't play smoothly, is there any possible way to fix this issue. I have read other topic in here but did't help.
this code is worked for me to play video in avplayer in reverse from local filesystem.
let duration = (self.playeritem?.asset.duration)!
let durationSec = CMTimeGetSeconds((self.playeritem?.asset.duration)!)
self.avPlayer?.seek(to: duration, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
self.avPlayer?.rate = -1
Try Replacing
videoPlayer.play()
videoPlayer.rate = -1 with
videoPlayer.playImmediately(atRate: -1)
I am not sure if following would work correctly since I have not tried playing an asset in reverse before. I think following is a good idea to try out to seek to end before starting to play.
player.seek(to: player.currentItem!.duration,
toleranceBefore: kCMTimeZero,
toleranceAfter: kCMTimeZero,
completionHandler: { (finished) in
player.play()
player.rate = -1
})
var durTime: CMTime = queuePlayer.currentItem.asset.duration
durationTime = CMTimeGetSeconds(durTime)
if CMTIME_IS_VALID(durTime) {
queuePlayer.seek(toTime: durTime, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
else {
print("In valid time")
}
Now set Rate of AVPlayer to negative value
queuePlayer.rate = -1
I am reading a 30s MP4 video with AVPlayer. When it comes to the end, I would like to keep play it backward, then normally, then backward, and so on...
It is working fine with the following code, but I am having a black frame every time I am playing the video again in the other way.
Here is my code is viewDidLoad:
let playerVideoURL = Bundle.main.url(forResource: "my_video", withExtension: "mp4")!
self.backgroundPlayer = AVPlayer(url: playerVideoURL)
self.backgroundPlayerLayer = AVPlayerLayer(player: self.backgroundPlayer)
self.backgroundPlayerLayer.videoGravity = AVLayerVideoGravityResize
self.backgroundPlayer.volume = 0
self.backgroundPlayer.actionAtItemEnd = .none
self.backgroundPlayerLayer.frame = view.layer.bounds
self.view.layer.insertSublayer(self.backgroundPlayerLayer, at: 0)
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: self.backgroundPlayer.currentItem)
And here is the method called each time the video finishes playing:
func playerItemDidReachEnd(notification: Notification) {
self.backgroundPlayer.rate *= -1
}
Thank you if you have any idea. I think it might be because the video is not buffered instantly when I'm changing the rate, but I didn't find what I am looking for in the AVPlayer documentation...
I am able to successfully create a player but was annoyed with the initial black screen. I decided to overlay a UIImageView and hide it once the player started. This worked, but I didn't want the hassle of creating and maintaining images for all my videos.
I was able to achieve the exact results I wanted by playing and immediately pausing the player after instantiating it. The only issue was that sometimes the state of the player was getting recorded incorrectly, so when I went to start the player again, the status was listed as already "playing" even though the player was paused.
I starting looking into using AVPlayerItem seekToTime but haven't found any feasible solutions. Is there a "non hacky" way of achieving this?
If you're using an AVPlayerViewController, this is a perfect use of the player controller's contentOverlayView property. This is a UIView between the player layer and the controls exposed exactly for this purpose:
First, create the screenshot:
let asset = AVAsset(URL: URL(string: "")!) // link to some video
let imageGenerator = AVAssetImageGenerator(asset: asset)
let screenshotTime = CMTime(seconds: 1, preferredTimescale: 1)
if let imageRef = try? imageGenerator.copyCGImageAtTime(screenshotTime, actualTime: nil) {
let image = UIImage(CGImage: imageRef)
// see part 2 below
}
Now, add the image as a subview of the contentOverlayView in the player controller:
// in the same try block
let imageView = UIImageView(image: image)
let playerVC = AVPlayerViewController()
let playerItem = AVPlayerItem(asset: asset)
playerVC.player = AVPlayer(playerItem: playerItem)
self.presentViewController(playerVC, animated: true) {
playerVC.contentOverlayView?.addSubview(imageView)
// adjust the frame of your imageView to fit on the player controller's contentOverlayView
}
Then, remove the imageView subview when the player starts playing the asset, or when buffering completes.
The AVPlayerLayer associated with your player has a readyForDisplay property.
You can try to make your host view an observer for this value, and do a refresh as soon as it is set to true.
It is set to true when the first frame is ready to be rendered, so before the player has enough data to play and set its status to readyToPlay.