Black frame when changing rate of AVPlayer item - ios

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...

Related

Play a video from url and validate if whole video was watched

I have this json and I have to play a video
What is the best way to play video and validate or check if user watched whole video?Video could be (Tiktok - Vimeo - Dayli Motion) video but no Youtube Video
I tried to use AVPlayer but it doesn't work :
let videoURL = NSURL(string: "https://vimeo.com/601097657")
let player = AVPlayer(url: videoURL! as URL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
I think the possible solution it could be a webView but I'm not sure if its possible to validate if user watched whole video
AVPlayer sends notifications on occasions like that.
Simply subscribe to notifications you need. In your case you need
NSNotification.Name.AVPlayerItemDidPlayToEndTime
implementing this would look something like this:
NotificationCenter.default.addObserver(self,
selector: #selector(itemDidPlayToEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: nil)
And implement a selector for handling notification:
#objc private func itemDidPlayToEnd() {
// do smth
}
This is working for me.
class Player: AVPlayerViewController {
init(url: URL) {
super.init(nibName: nil, bundle: nil)
player = AVPlayer(url: url)
player?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main, using: { time in
if self.player?.currentItem?.status == .readyToPlay {
let currenTime = CMTimeGetSeconds((self.player?.currentTime())!)
let secs = Int(currenTime)
print(NSString(format: "%02d:%02d", secs/60, secs%60) as String)
}
})
}
This will print out how much of the video they have watched in seconds, which you could then check if this is equal to the total amount of time the video is.
Obviously you would have to implement methods to check if they have skipped part of the video or not, but that's for another question.
Check out more info Time watched. and Total Video Time

Swift AVPlayer - The proper way to loop a video with custom rate

So I have an AVPlayer which I would like to continuously loop. This is not an issue as this works fine.
However, the user has the ability to edit the rate of the video. If the rate is changed, it will loop fine for 2-3 times and then the AVPlayer get stuck on the first frame.
The completion block is not called after it gets stuck.
Here is my code for looping the AVPlayer
// Loop video notification
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { [weak self] _ in
guard let self = self, let player = self.player else { return }
player.seek(to: .zero)
player.play()
player.rate = self.playerRate
}

AVPlayer full screen issue only for iPhone x

I created a splash video using Av player view controller. its is coming properly for all the devices except I Phone X. I tried changing video gravity frame and everything but it won't work. any Idea about this? here is the sample code :
guard let videoPath = Bundle.main.path(forResource: "Redtaxi-splash", ofType:"mov") else {
return
}
let videoURL = URL(fileURLWithPath: videoPath)
let player = AVPlayer(url: videoURL)
playerViewController = AVPlayerViewController()
playerViewController?.player = player
playerViewController?.showsPlaybackControls = false
playerViewController?.view.frame = view.frame
playerViewController?.view.backgroundColor = .white
playerViewController?.view.contentMode = .scaleAspectFill
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying(note:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player.currentItem)
view.addSubview((playerViewController?.view)!)
playerViewController?.player?.play()
I fixed this by giving the video gravity for the player view controller :
playerViewController.videoGravity = .resizeAspectFill
this will fix the issue by using the video gravity as aspect fill for the avplayer view controller. here is the documentation for video gravity:
The video gravity determines how the video content is scaled or stretched within the player layer’s bounds.
Swift 4
playerViewController.videoGravity = AVLayerVideoGravity.resizeAspectFill.rawValue

Stretch AVPlayerLayer to fit into iPhone X format

I have an app where I play a video in the background in a certain view.
This video is always supposed to play fullscreen. I recently asked a question about stretching this video to fill up an iPhone plus model (make AVPlayer fill the entire screen) However, I just tested on an iPhone X, and the following happens:
I made the background of the view containing the AVPlayerLayer purple, just to clarify. As you can see, the video plays in the top of the screen (appx. 80pt), then there's the complete video, and then there's nothingness. How is this possible if my view does actually cover the entire screen?
This is how the video is made:
#objc fileprivate func playVideo() {
log.debug("playVideo")
let file = self.videoName.components(separatedBy: ".")
guard let path = Bundle.main.path(forResource: file[0], ofType:file[1]) else {
debugPrint( "\(file.joined(separator: ".")) not found")
return
}
self.player = AVPlayer(url: URL(fileURLWithPath: path))
let playerLayer = AVPlayerLayer(player: self.player)
playerLayer.frame = self.bounds
self.layer.addSublayer(playerLayer)
self.play()
if (self.loop) {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.player.currentItem, queue: .main) { _ in
self.restart()
}
}
self.backgroundColor = .purple
NotificationCenter.default.addObserver(
self,
selector: #selector(self.restart),
name: Notification.Name("playVideos"),
object: nil)
}
This method is called after layoutSubviews().
How do I make the video stretch fully (with some overflow on the sides to make it visible of course)? Thanks :-).

Swift: AVPlayer release memory / resources

I am writing an app that need display different video according to the selection of the user. When the user select a video, the function playVideo will be called. And after the video finish playing, then the videoView will be hidden again.
My code is as follows:
var player: AVPlayer?
func playVideo(String: videoFile) {
self.videoView.isHidden = false
let videoURL: NSURL = Bundle.main.url(forResource: videoFile, withExtension: "mp4")! as NSURL
self.player = AVPlayer(url: videoURL as URL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.videoView.frame
self.videoView.layer.addSublayer(playerLayer)
let duration : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(duration, preferredTimeScale)
self.player?.seek(to: seekTime)
self.player?.play()
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
#objc func playerItemDidReachEnd()
{
self.player?.pause()
self.videoView.isHidden = true
NotificationCenter.default.removeObserver(self)
}
However, with the code above, i have several question:
How to delete / deallocate the player gracefully? If just using my current code, will it consume lots of memory?
Every time, when the user press a button, the function playVideo will be called, and the corresponding player will be created and play. Is this the right way to do so? Is there any other method or more efficient way or elegant way to do so?
I did try to replace the code on creation of the player by the following, but it fails to play the video.
let playerItem: AVPlayerItem = AVPlayerItem(url: videoURL as URL)
self.player? = AVPlayer(playerItem: playerItem)
Thank you

Resources