PiP works on iPhone but not iPad [closed] - ios

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 2 years ago.
Improve this question
In iOS 14, when I set up an AVPlayerViewController, and set the player's delegate, if I press the home button on an iPhone, the delegate function playerViewControllerDidStartPictureInPicture gets called and I can see the PiP on the phone's home screen. However, in the same app, if I press the home button on an iPad, that delegate method does not get called, and PiP doesn't start.
Is there anything I could possibly be missing? Is there a way to programmatically activate PiP? The goal is to show PiP on iPad when the user presses the home button.
This is what the code looks like:
let videoURL = URL(string: url)!
let player = AVPlayer(url: videoURL)
playerView = AVPlayerViewController()
guard let playerView = playerView else { return }
playerView.player = player
playerView.delegate = self
playerView.allowsPictureInPicturePlayback = true

Related

addPeriodicTimeObserver keeps AVPlayer instances

After being all over Stack Overflow and the deepest corners of the internet, I'm yet to find the solution. I hope someone out there will be able help me out with a problem I've had for days now.
The app in question has a collection view with lots of items. When you click on it you get to a preview collection view. And finally (where I need help) when you click "GO" you come to a collection view with cells that fills out the entire screen. It consists of an AVPlayerLayer and AVPlayer. Each time you scroll to right or left you see another video. The following code works:
UICollectionViewCell
class PageCell: UICollectionViewCell {
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
var playerItem: AVPlayerItem?
var videoAsset: AVAsset?
var indexPath: IndexPath?
var page: Page? {
didSet {
guard let unwrappedPage = page, let unwrappedIndexPath = indexPath else { return }
addingVideo(videoID: unwrappedPage.steps[unwrappedIndexPath.item].video)
}
}
func setupPlayerView() {
player = AVPlayer()
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = .resizeAspectFill
videoView.layer.addSublayer(playerLayer!)
layoutIfNeeded()
playerLayer?.frame = videoView.bounds
}
func addingVideo(videoID: String) {
setupPlayerView()
guard let url = URL(string: videoID) else { return }
videoAsset = AVAsset(url: url)
activityIndicatorView.startAnimating()
videoAsset?.loadValuesAsynchronously(forKeys: ["duration"]) {
guard self.videoAsset?.statusOfValue(forKey: "duration", error: nil) == .loaded else { return }
self.player?.play()
self.playerItem = AVPlayerItem(asset: self.videoAsset!)
self.player?.replaceCurrentItem(with: self.playerItem)
}
}
In the UICollectionViewController I'm reusing cells. Since there is no way to know the number of AVPlayer instances, I simply made a helper variable inside the PageCell and it seems like three cells are getting reused (the normal amount when dequeuing and reusing cells). Now when I close this UICollectionViewController the AVPlayer instances seems to disappear/close.
Now, the problem arises when I want to loop the videos. Using AVPlayerLooper is not an option because it simply is too laggy (I've implemented it in a dusin different ways without luck). So my solution was to use a period time observer inside the videoAsset?.loadValuesAsynchronously block:
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.global(), using: { (progressTime) in
if let totalDuration = self.player?.currentItem?.duration{
if progressTime == totalDuration {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
}
})
Problem
Video showcasing problem: Problem video
The problem arises when having +17 AVPlayer instances running simultaneously then suddenly the videos won't load anymore. iOS devices have a hardware limitation of everything from 4 to 18 AVPlayer instances running at the same time (most likely depending on RAM), see the following Stack Overflow posts just to mention a few:
AVPlayerItemStatusFailed error when too many AVPlayer instances created
How many AVPlayers are allowed to be created at the same time?
AVPlayer not able to play after playing some items
Loading issues with AVPlayerItem from local URL
See also these articles for more insight:
Building native video Pins
Too Many AVPlayers?
Because the problem only occurs when adding the time observer I suspect that it keeps the AVPlayer instances "alive" even though I've closed the collection view.
Notes to Problem video: Every time I press "GO" one AVPlayer instance is created. When I swipe to the right two more cells are created hence two more AVPlayer instances. All in all 3 AVPlayer instances are created each time accumulating in the end to about 17 instances and the videos will not load anymore. I've tried scrolling through all the videos each time I've pressed "GO" but this does not change the outcome with a maximum of three cells being reused all the time.
What I've tried to solve the problem
Try 1:
I made playerLayer a global variable and in viewDidDisappear inside the UICollectionViewController I added:
playerLayer?.player = nil
This resulted in one AVPlayer instance to disappear/close when I closed the cells onto the "GO" page (i.e. the view disappeared). This meant that I hit 17 instances a bit later than when I didn't add this code. Together with the code above I also tried adding playerLayer = nil, playerLayer?.player?.replaceCurrentItem(with: nil) and playerLayer?.removeFromSuperlayer() but the only thing that changed anything was playerLayer?.player = nil. It should be noted that if I do not scroll and simply open the first video and close, open the first video and close and so on, I could do it "forever" without any problems. So, in this case one instance was created and then closed/disappeared afterwards.
Video showcasing try 1: Try 1 video
Try 2:
I changed the addPeriodicTimeObserver block to:
self.timeObserver = playerLayer?.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale:
2), queue: DispatchQueue.global(), using: { (progressTime) in
if let totalDuration = playerLayer?.player?.currentItem?.duration{
if progressTime == totalDuration {
playerLayer?.player?.seek(to: kCMTimeZero)
playerLayer?.player?.play()
}
}
})
In this block I essentially changed all self.player? to playerLayer?.player?.
This made me able to open and close the view with the videos as much as I wanted - so the AVPlayer instances were somehow closing/disappearing. Though now the looping didn't work. The first video would loop initially. But then I swiped to the second cell and this video would not loop. Then I swiped back to the first cell and now this wouldn't loop either. If I added playerLayer?.player = nil like in "Try 1" no effect occurred with neither the open and close nor the loops.
Video showcasing try 2: Try 2 video
Try 3:
I made the timeObserver variable global and tried many many things. But when I tried to remove the observer(s) it always resulted in the following error:
'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'
The error indicates that the time observers are clearly all over the place. This was clear when I added helper variables (counters) inside the addPeriodicTimeObserver block and found out that the time observers were quickly adding up. At one point with all the different combinations of the code presented in this post, I was able to remove the time observer at any time during the scrolling. But this resulted in only removing a single time observer out of many - resulting in the same situation as in "Try 1" where I was able to remove a single AVPlayer instance each time I closed the view.
General note:
To test whether or not it really was only three AVPlayer instances that were created each time I press "GO" and swiped I made a test where I scrolled through over 20 videos, but there was no problem loading them. Therefore I'm quite sure, as mentioned earlier, that a maximum of three AVPlayer instances are created in the view each time.
Conclusion
The problem is, as I see it, that when I add the time observers in order to loop the videos they accumulate and keep the AVPlayer instances "alive". Remember that I had no problem at all without the time observers. When applying playerLayer?.player = nil it seems as though, I was able to "save" an instance but then again it's so hard to tell when I don't know the amount of AVPlayer instances currently "active". Simply put, all I wanna do is to delete all AVPlayer instances the moment the view disappears and it will be happy days. If anyone got this far, I will be overly grateful if you are able to help me out.
There is a retain cycle. Use weak self in escaping closures. The retain cycle you created was:
cell -> Player -> observer closure -> cell
Try this:
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.global(), using: { [weak self] (progressTime) in
if let totalDuration = self?.player?.currentItem?.duration{
if progressTime == totalDuration {
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
}
})

AVPlayer does not display any video on physical device but displays on the simulator

At first I want to mention that I'm using MobilePlayer library but it's just a skin above AVPlayer.
When I tap to play video it only plays sound and I do not see any video. I've checked view bounds but the problem is not there. At the beginning of the video it indicates loading progress but later I hear only the sound without any video.
My code looks like:
let bundle = Bundle.main
let config = MobilePlayerConfig(fileURL: bundle.url(
forResource: "Skin",
withExtension: "json")!)
let videoURL = NSURL(string: channelURL!)
self.playerVC = MobilePlayerViewController(contentURL: videoURL! as URL, config: config)
self.playerVC.activityItems = [videoURL! as URL]
self.present(self.playerVC, animated: true)
I also want to mention that I see the video when I run on the simulator but on the real device there is no video at all. What can be a problem?
If you need any part of the code just ask me please

Understanding simple code in swift [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
I found an answer to implement a link to get rated on the app store (App store link for "rate/review this app"). Answer is:
let appID = "Your App ID on App Store"
let urlStr = "itms-apps://itunes.apple.com/app/id\(appID)" //
(Option 1) Open App Page
let urlStr = "itms-apps://itunes.apple.com/app/viewContentsUserReviews?
id=\(appID)" // (Option 2) Open App Review Tab
if let url = URL(string: urlStr), UIApplication.shared.canOpenURL(url)
{
if #available(iOS 10.0, *) {
UIApplication.shared.open(url, options: [:], completionHandler:
nil)
} else {
UIApplication.shared.openURL(url)
}
}
I am having trouble understanding how to implement this. Particularly I don't understand how the if statement will take the user to the app store.
Thanks.
The first part
let appID = "Your App ID on App Store"
let urlStr = "itms-apps://itunes.apple.com/app/id\(appID)" //
(Option 1) Open App Page
let urlStr = "itms-apps://itunes.apple.com/app/viewContentsUserReviews?
id=\(appID)" // (Option 2) Open App Review Tab
gets the url of your app in itunes review section so user can write a review
The second part
if let url = URL(string: urlStr), UIApplication.shared.canOpenURL(url)
checks if the url is valid and can be opened by application.shared if ok it's check the needed function according to the current version.
Actually system know that the app needs to open the url , when it executes the statement , look at open
UIApplication.shared.open(url, options: [:], completionHandler:nil)
Or
UIApplication.shared.openURL(url)
you are opening url with itms-apps:// scheme. So system itself knows what app should handle this.

AVPlayer not able to play after playing some items

I am building one simple game where I need to display 2 videos to user frequently videos are in my bundle locally. I just want to play that videos on my viewcontroller. My code for that is :
let playerItem = AVPlayerItem(URL: NSURL.fileURLWithPath("\(vdoPath)")) //vdoPath will be random of that two local video files from bundle
let player = AVPlayer(playerItem: playerItem)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
playerViewController.view.backgroundColor = UIColor.clearColor()
playerViewController.showsPlaybackControls = false
playerViewController.view.frame = self.playerView.bounds
self.playerView.addSubview(playerViewController.view)
self.addChildViewController(playerViewController)
player.play()
AVPlayer plays videos for sometimes like I am not facing issue till 12-15 times but when I continue to play more AVPlayer not able to play it showing like this:
There is no issue with my file and path because before getting this I am able to play 12-15 times as I said. please give me proper solution and reason behind this.
For people facing this issue in the future and #JAck's solution not working, here's a solution that worked for me. By the way, my screen was just black. It didn't have the play button with a slash through it... But anyways, the underlying issue for me was that I had too many instances of AVPlayer and AVPlayerLayer (Apple apparently caps this number). Though I was removing the layer and pausing the player when dismissing the view controller, the player was still being retained (I'm guessing). So, my fix was to nil out BOTH the player instance and the player layer instance. Here's my code:
var player: AVPlayer?
var layer: AVPlayerLayer?
Right before dismissing my view controller, I add this snippet:
DispatchQueue.main.async {
self.player?.pause()
self.player = nil
self.layer?.removeFromSuperlayer()
self.layer = nil
}
You need to use player.replacePlayerItem(item) instead of twice calling
if still not working ask me anytime. First let AVPlayer play first video then wait your controller till completion then start another.

iOS UIDocumentInteractionController LaunchServices: invalidationHandler called

I'm having an issue when I try to Launch Instagram from my app. Everything works and I'm able to launch IG and even see the photo I sent etc.
The problem is that the UIDocumentInteractionController is crashing my app. And YES I have done my research.
I've seen the posts LIKE THIS that indicate that this is an Apple bug as as long as you can fix the crash, you should be able fine and ignore the Launch Services message.
The problem is I am still having the crash and trying to figure out how to resolve it.
I found a post that talks about adding an IF-STATEMENT after presenting the ViewController HERE, this post was written in Objective-C, and the example was not for a UIDocumentInteractionController.
I tried to take a stab at it in Swift, but it is still not working out for me. Would appreciate if someone can help out.
dic = UIDocumentInteractionController(URL: imageURL!)
dic.delegate = self
var annotationDictionary: [String: String] = ["InstagramCaption": "Test"]
dic.annotation = annotationDictionary
dic.UTI = "com.instagram.exclusivegram"
dic.presentOpenInMenuFromRect(CGRectMake(1, 1, 1, 1), inView: self.view, animated: true)
if dic.respondsToSelector("popoverPresentationController") {
Scripts.log("InstagramVC Did Respond to popoverPresentationController")
var popoverController: UIPopoverPresentationController = self.popoverPresentationController!
popoverPresentationController?.sourceView = self.view
}
The fix in my case was to declare the UIDocumentInteractionController variable as part of the viewcontroller's class instead of creating it in the same function where I set up the annotation and UTI and called .presentOpenInMenuFromRect
So near the top of my class outside of any functions I declared the variable:
var docController = UIDocumentInteractionController()
And then when I was ready to use it, I configured everything about the already existing UIDocumentInteractionController instead of creating one:
docController = UIDocumentInteractionController(URL: imageURL!)
docController.UTI = "com.instagram.exclusivegram"
docController.delegate = self
docController.annotation = ["InstagramCaption":"Text"]
docController.presentOpenInMenuFromRect(rect, inView: self.view, animated: true)
The app stopped crashing and Instagram now loads with the image/text assigned. 😃👍
I found the suggestion that led me to this fix here: https://stackoverflow.com/a/16057399/428981 and then adapted for Swift
It sounds like your instance of UIDocumentInteractionController is going out of scope. Try making it a property on the class or some other way to retain it.
I had the same problem: sending the picture to Instagram worked but instantaneously crashed my app. I think it has something to do with how the UIDocumentInteractionController is handled by the system once another app opens. If you try to send the picture via the embedded Facebook or Twitter frameworks that open a popover on top of your app, then no crash happens...
In any case the way I finally made it work is by NOT declaring my viewController as delegate, so:
// dic.delegate = self
The downside being that you can't use any of the delegate methods. In my case I wasn't using them anyway.

Resources