AVPlayerViewController memory leak/retain cycle? - ios

I keep getting a problem with my app where AVPlayerViewController won't get purged from memory after being dismissed, it just builds up more after each presentation.
This is on iOS 12, try the following in an empty project, I do it in viewDidAppear for instance to automatically make it pop up a few seconds after it's dismissed. You'll notice after a bunch of dismissals if you go to the "Debug Memory Graph" tool at the bottom of Xcode that AVPlayerViewController stays in memory and there's a bunch of instances of it.
The key though is LET THE VIDEO PLAY UNTIL THE END where the controls pop back up.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(6)) { [weak self] in
let assetURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!
let player = AVPlayer(url: assetURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self?.navigationController?.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
}

Try this
self?.navigationController?.present(playerViewController, animated: true) { [weak playerViewController]
playerViewController?.player!.play()
}

Related

Swift YouTube video in Full Screen & Auto Play with SFSafariViewController

Problem:
I would like SFSafariViewController to autoplay a YouTube video in full screen.
Code:
import SafariServices
#objc func handleYTTap(_ sender: UIGestureRecognizer) {
let youtubeVideoURL = "https://youtu.be/d9MyW72ELq0"
let bookmark = NSURL(string: youtubeVideoURL)
let safari = SFSafariViewController(url: bookmark as! URL)
if(UIDevice.current.userInterfaceIdiom == .pad) {
safari.modalPresentationStyle = .fullScreen
}else{
safari.modalPresentationStyle = .fullScreen
}
self.present(safari, animated: true, completion: nil)
}
Tried Solutions:
I have tried to implement
"?playsinline=1?autoplay=1".
to the url but still cant get the SFSafariViewController to autoplay in full screen. is this even possible to do without creating a custom view or using a library of some sort?

Too many AVPlayers causes Terminated due to memory issue

I have a vc that has an AVPlayer inside of it. From that vc I can push on a different vc with another player inside of it and I can keep pushing on more vcs with a player inside them also. After about the 14th vc getting pushed on the app crashes with Terminated due to memory issue.
When I look at the memory graph (9th icon in left pane) it's at about 70mb so there isn't an obscene jump in memory. All of my video files are saved to and retrieved from disk and whenever I pop a vc I have a print statement inside Deinit that always runs so there isn't anything else causing the memory issue. This led me to believe the other SO answers that said that there is a limit to 16 AVPlayers at the same time. The reason I think all of these players are causing this memory crash is because once I comment out the player initialization code I can push on 30 vcs with no crashes whatsoever.
I was about to completely remove the player, playerItem, its observers, and player layer from the parent vc in viewWillDisappear/viewDidDisappear and then once the child is popped reinitialize everything again in viewWillAppear/viewDidAppear but then I came across this blog that says
platform limitation on the number of video “render pipelines” shared
between apps on the device. It turns out that setting the AVPlayer to
nil does not free up a playback pipeline and that it is actually the
association of a playerItem with a player that creates the pipeline in
the first place
and this answer that says
It is not a limit on the number of instances of AVPlayer, or
AVPlayerItem. Rather,it is the association of AVPlayerItem with an
AVPlayer which creates a "render pipeline"
The question is when pushing/popping on a new vc (it will have a player inside of it) do I need to completely remove/readd everything associated with the player or will setting the AVPlayerItem to nil then reinitializing it again resolve the issue?
If the render pipelines are causing the problem it would seem that the limit isn't on the players but on the playerItems.
code:
override func viewDidLoad() {
super.viewDidLoad()
configurePlayer(with: self.videoUrl)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// only runs when popping back
if !isMovingToParent {
// I can either
let asset = AVAsset(url: selfvideoUrl)
self.playerItem = AVPlayerItem(asset: asset)
self.player?.replaceCurrentItem(with: playerItem!)
// or just reinitialize everything
configurePlayer(with: self.videoUrl)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// would these 2 lines be enough suffice to prevent the issue?
self.player?.replaceCurrentItem(with: nil)
self.playerItem = nil
// or do I also need to nil out everything?
self.player = nil
self.avPlayerView.removeFromSuperView()
self.playerStatusObserver = nil
self.playerRateObserver = nil
self.playerTimeControlStatusObserver = nil
}
func configurePlayer(with videoUrl: URL) {
let asset = AVAsset(url: videoUrl)
self.playerItem = AVPlayerItem(asset: asset)
self.player = AVPlayer()
self.playerLayer = AVPlayerLayer(player: player)
self.playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
self.player?.automaticallyWaitsToMinimizeStalling = false
self.playerItem.preferredForwardBufferDuration = TimeInterval(1.0)
view.addSubview(avPlayerView) // this is just a view with a CALayer for the playerLayer
self.playerLayer?.frame = avPlayerView.bounds
self.avPlayerView.layer.addSublayer(playerLayer!)
self.avPlayerView.playerLayer = playerLayer
self.player?.replaceCurrentItem(with: playerItem!)
// add endTimeNotification
setNSKeyValueObservers()
}
func setNSKeyValueObservers() {
self.playerStatusObserver = player?.observe(\.currentItem?.status, options: [.new, .old]) {
[weak self] (player, change) in ... }
self.playerRateObserver = player?.observe(\.rate, options: [.new, .old], changeHandler: {
[weak self](player, change) in ... }
self.playerTimeControlStatusObserver = player?.observe(\.timeControlStatus, options: [.new, .old]) {
[weak self](player, change) in ... }
}
I just tested it and setting this to nil and reinitializing it is what allowed me to push on 30 vcs each with an AVPlayer inside of each one without any crashes whatsoever.
player?.replaceCurrentItem(with: nil)
So the issue isn't the total amount of AVPlayers, it's like this guy said, the association of AVPlayerItem with an AVPlayer which creates a "render pipeline and too many of them at the same time is what causes the problem.
override func viewDidLoad() {
super.viewDidLoad()
configurePlayer(with: self.videoUrl)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let playerItem = playerItem {
self.player?.replaceCurrentItem(with: playerItem)
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.player?.replaceCurrentItem(with: nil)
}

Sceneview freezes when present web view controller

I'm using Safari Services kit to present a web view when an SCNode is clicked. However, when I go back from the safariVC to my VC with the sceneView, the sceneView is frozen even though I restart the session by re-running the session.
I read on this website that presenting another VC kills the scene view thread and I would just like confirmation. Basically I want to know whether it is possible to present another VC and be able to go back to the sceneView and "resume" the scenView
Note:
VC = ViewController
Code
How I present my safari web VC
func presentWebView(){
if let url = URL(string: "https://www.amazon.com/"){
let safariVC = SFSafariViewController(url: url)
self.present(safariVC, animated: true, completion: nil)
}
}
How I restart my session
if let worldSessionConfig = sessionConfig as? ARWorldTrackingSessionConfiguration {
worldSessionConfig.planeDetection = .horizontal
session.run(worldSessionConfig, options: [.resetTracking, .removeExistingAnchors])
}
Use sceneView.session.pause() when you present safariVC and then use sceneView.session.run to restart session when safariVC is dismissed.

ios9 - Swift 2.1 - SFSafariViewController

I'm looking to use the SFSafariViewController and have it automatically load a website when the app is opened. Is this possible?
What I have so far (brand new single ios app):
// ViewController.swift
let urlString = "https://stackoverflow.com"
#IBAction func launchSFSafariViewController(sender: AnyObject) {
if let url = NSURL(string: urlString) {
let vc = SFSafariViewController(URL: url, entersReaderIfAvailable: true)
vc.delegate = self
presentViewController(vc, animated: true, completion: nil)
}
}
How do I connect the action to the main view controller via the storyboard? All the articles I see only discuss connecting a button to an action.
Implement viewDidAppear to perform the presentation. (You will need to use a Bool flag, though, to make sure this doesn't happen when you dismiss the presented view controller and viewDidAppear is called again!)

AVPlayerViewController - UIButton not triggering video

I am using AVPlayerViewController in order to play a .mov file (TeethingFinal.mov) in my project. The funny thing is, I have the feature working perfectly with two other buttons on the same ViewController but for some reason will not launch the AVPlayer! The tap gesture is being recognized, because I can see the button highlight when pressed but nothing happens. I would post error message but there is not one!
#IBAction func sugarBugPlay(sender: UIButton) {
let moviePlayerController = AVPlayerViewController()
let path = NSBundle.mainBundle().pathForResource("TeethingFinal", ofType:"mov")
let url = NSURL.fileURLWithPath(path!)
let playerVC = AVPlayerViewController()
var player: AVPlayer = AVPlayer()
playerVC.player = AVPlayer(URL: url)
self.presentViewController(playerVC, animated: true, completion: nil)
}
So sometimes Xcode is funny. I created a completely new ViewController the exact same way and reattached all of my outlets and it worked!

Resources