AVPlayer not able to play after playing some items - ios

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.

Related

Error while playing You Tube videos in IOS Swift app

My following code plays the "fox village.mp4" video, which is commented out, but not the you tube link and gives this error:
AddInstanceForFactory: No factory registered for id F8BB1C28-BAE8-11D6-9C31-00039315CD46
Please help if you can. Thanks!!
let videoURL = URL(string: "https://youtu.be/MTNCcC_H3oM")!
// let videoURL = URL(string: "https://wolverine.raywenderlich.com/content/ios/tutorials/video_streaming/foxVillage.mp4")!
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
present(playerViewController, animated: true) {
player.play()
The first thing you need to clear that AVPlayer does not have the capacity to play youtube video from URL.
Note:
One thing you need to keep in your mind, AVPlayer is able played video if URL is direct downloadable.
In your case, you need to go with below solutions
Solution: 1
To play a youtube video, You need to go with the WKWebView or WKWebView with iFrame
OR
I suggest you need to go with YTPlayerView
OR
How to embed a Youtube video into my app?

Notification AVPlayerItemDidPlayToEndTime not fired when streaming via AirPlay

I use AVPlayer to play a simple MP4 video. As I need to know when the video finishes, I add an observer for the notification AVPlayerItemDidPlayToEndTime.
This works fine, if the video is played on the device, but the notification is never posted if we are streaming the video from a device to an Apple TV (4th gen) with tvOS 12.2 via AirPlay.
Strangely, this problem only occurs with MP4 videos, not with HLS streams (m3u8), which makes me wonder if this might be a tvOS (or AirPlay) bug.
I created a small POC to isolate the problem, and this is what I do:
let bunny = "https://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4"
// create a new player with every call to startVideo() for demo purposes
func startVideo() {
// remove layer of current play from playerContainer as
// a new one will be created below
if let sublayers = playerContainer.layer.sublayers {
for layer in sublayers {
layer.removeFromSuperlayer()
}
}
if let videoUrl = URL(string: bunny) {
// create player with sample video URL
self.player = AVPlayer(url: videoUrl)
guard let player = self.player else {return}
// add player layer to playerContainer
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = playerContainer.bounds
playerLayer.videoGravity = .resizeAspectFill
playerContainer.layer.addSublayer(playerLayer)
// add block-based observer to listen for AVPlayerItemDidPlayToEndTime event
let _ = NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: player.currentItem,
queue: .main) { notification in
print("Item did finish playing! \(notification)")
}
player.play()
}
}
Just to clarify again, this works fine, if one of the following conditions is true:
The video is played on the device, and no AirPlay is involved.
The video is streamed to an AppleTV 3rd gen.
An HLS instead of an MP4 video is used.
I also tried the following things, but the result was the same:
Don't set currentItem as object when adding observer.
Don't specify the queue when adding observer.
Don't use block-based but selector-based method to add an observer.
Any help is appreciated. This might be a duplicate of this question, but as there are no answers, and linked question is about HLS and not MP4, I decided to post this question anyway.

Using NotificationCenter to alternate videos files in AVPlayer

I'm working on implementing a video player in Swift that will detect if a video has stopped playing, and then play the second one. When the second one has stopped playing, the first video should play again.
Here's where I set up the player, assets, and player items:
//Create URLs
let movieOneURL: URL = URL(fileURLWithPath: movieOnePath)
let movieTwoURL: URL = URL(fileURLWithPath: movieTwoPath)
//Create Assets
let assetOne = AVAsset(url: movieOneURL)
let assetTwo = AVAsset(url: movieTwoURL)
//Create Player Items
avPlayerItemOne = AVPlayerItem(asset: assetOne)
avPlayerItemTwo = AVPlayerItem(asset: assetTwo)
avplayer = AVPlayer(playerItem: avPlayerItemOne)
let avPlayerLayer = AVPlayerLayer(player: avplayer)
avPlayerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
avPlayerLayer.frame = UIScreen.main.bounds
movieView.layer.addSublayer(avPlayerLayer)
//Config player
avplayer .seek(to: kCMTimeZero)
avplayer.volume = 0.0
And here's where I set up a notification to detect if the player reached the end of the video file:
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: avplayer.currentItem)
...which calls this selector:
func playerItemDidReachEnd(_ notification: Notification) {
// avplayer.seek(to: kCMTimeZero)
changePlayerAsset()
// avplayer.play()
}
...which will then switch out the asset:
func changePlayerAsset(){
if avplayer.currentItem == avPlayerItemOne {
avplayer.replaceCurrentItem(with: avPlayerItemTwo)
avplayer.play()
} else if avplayer.currentItem == avPlayerItemTwo {
avplayer.replaceCurrentItem(with: avPlayerItemOne)
avplayer.play()
}
}
This works perfectly the first time through - when the first movie has finished playing, the next one will then start playing.
The problem I'm having is that my notification observer only seems to register once; at the end of the first video...the notification isn't fired when the second video stops playing at all.
Anyone have an idea why that would be the case
The reason your notification handler isn’t getting called for the second item is this bit, at the end of where you register the notification handler: object: avplayer.currentItem. The handler gets called once, when that item finishes playing, but then when the next item finishes, the notification gets posted with a different object—the other item—which doesn’t match what you registered for, and so your handler doesn’t get called. If you change object to nil when you register the handler, it’ll get called when any item finishes, which is closer to what you’re after.
That said, this isn’t a great way to do what you want—manually swapping out items is likely to incur the cost and delay of loading each item each time it’s about to play. You’d be much better off using the built-in functionality for playing videos in sequence and looping them, namely AVQueuePlayer and AVPlayerLooper. There’s an example of how to use both in the answer to this question.

How to close previous AVPlayer and AVPlayerItem

I'm making an iOS app in Swift that plays a video in a loop in a small layer in the top right corner of the screen which shows a video of specific coloured item. the user then taps the corresponding coloured item on the screen. when they do, the videoName variable is randomly changed to the next colour and the corresponding video is triggered. I have no problem raising, playing and looping the video with AVPlayer, AVPlayerItem as seen in the attached code.
Where I'm stuck is that whenever the next video is shown, previous ones stay open behind it. Also, After 16 videos have played, the player disappears altogether on my iPad. I've tried many suggestions presented in this and other sites but either swift finds a problem with them or it just doesn't work.
So question: within my code here, how do I tell it "hey the next video has started to play, remove the previous video and it's layer and free up the memory so i can play as many videos as needed"?
//set variables for video play
var playerItem:AVPlayerItem?
var player:AVPlayer?
//variables that contain video file path, name and extension
var videoPath = NSBundle.mainBundle().resourcePath!
var videoName = "blue"
let videoExtension = ".mp4"
//DISPLAY VIDEO
func showVideo(){
//Assign url path
let url = NSURL(fileURLWithPath: videoPath+"/Base.lproj/"+videoName+videoExtension)
playerItem = AVPlayerItem(URL: url)
player=AVPlayer(playerItem: playerItem!)
let playerLayer=AVPlayerLayer(player: player!)
//setplayser location in uiview and show video
playerLayer.frame=CGRectMake(700, 5, 350, 350)
self.view.layer.addSublayer(playerLayer)
player!.play()
// Add notification to know when the video ends, then replay it again. THIS IS A CONTINUAL LOOP
NSNotificationCenter.defaultCenter().addObserverForName(AVPlayerItemDidPlayToEndTimeNotification, object: player!.currentItem, queue: nil)
{ notification in
let t1 = CMTimeMake(5, 100);
self.player!.seekToTime(t1)
self.player!.play()
}
}
`
I adapted #Anupam Mishra's Swift code suggestion. It wasn't working at first but finally figured I had to take the playerLayer outside the function and close the playerLayer after I set the player to nil. Also. instead of using 'if(player!.rate>0 ...)' which would no doubt still work, I created a variable switch to indicate when to say "kill the player AND the layer" as seen below. It may not be pretty but it works! The following is for absolute newbies like myself - WHAT I LEARNED FROM THIS EXPERIENCE: it seems to me that an ios device only allows 16 layers to be added to a viewController (or superLayer). so each layer needs to be deleted before opening the next layer with its player unless you really want 16 layers running all at once. WHAT THIS CODE BELOW ACTUALLY DOES FOR YOU: this code creates a re-sizable layer over an existing viewController and plays a video from your bundle in an endless loop. When the next video is about to be called, the current video and the layer are totally removed, freeing up the memory, and then a new layer with the new video is played. The video layer size and location is totally customizable using the playerLayer.frame=CGRectMake(left, top, width, height) parameters. HOW TO MAKE IT ALL WORK: Assuming you've already added your videos to you bundle, create another function for your button tap. in that function, first call the 'stopPlayer()' function, change the 'videoName' variable to the new video name you desire, then call the 'showVideo()' function. (if you need to change the video extension, change 'videoExtension' from a let to a var.
`
//set variables for video play
var playerItem:AVPlayerItem?
var player:AVPlayer?
var playerLayer = AVPlayerLayer() //NEW playerLayer var location
//variables that contain video file path, name and extension
var videoPath = NSBundle.mainBundle().resourcePath!
var videoName = "blue"
let videoExtension = ".mp4"
var createLayerSwitch = true /*NEW switch to say whether on not to create the layer when referenced by the closePlayer and showVideo functions*/
//DISPLAY VIDEO
func showVideo(){
//Assign url path
let url = NSURL(fileURLWithPath: videoPath+"/Base.lproj/"+videoName+videoExtension)
playerItem = AVPlayerItem(URL: url)
player=AVPlayer(playerItem: playerItem!)
playerLayer=AVPlayerLayer(player: player!) //NEW: remove 'let' from playeLayer here.
//setplayser location in uiview and show video
playerLayer.frame=CGRectMake(700, 5, 350, 350)
self.view.layer.addSublayer(playerLayer)
player!.play()
createLayerSwitch = false //NEW switch to tell if a layer is already created or not. I set the switch to false so that when the next tapped item/button references the closePlayer() function, the condition is triggered to close the player AND the layer
// Add notification to know when the video ends, then replay it again without a pause between replays. THIS IS A CONTINUAL LOOP
NSNotificationCenter.defaultCenter().addObserverForName(AVPlayerItemDidPlayToEndTimeNotification, object: player!.currentItem, queue: nil)
{ notification in
let t1 = CMTimeMake(5, 100);
self.player!.seekToTime(t1)
self.player!.play()
}
}
//NEW function to kill the current player and layer before playing the next video
func closePlayer(){
if (createLayerSwitch == false) {
player!.pause()
player = nil
playerLayer.removefromsuperlayer()
}
}
`
Why don't just use replaceCurrentItemWithPlayerItem on your player ? You will keep the same player for all your videos. I think it's a better way to do.
Edit : replaceCurrentItemWithPlayerItem has to be call on the main thread
Before moving on next track first check, is player having any videos or music, to stop it do the following checks:
Swift Code-
if player!.rate > 0 && player!.error == nil
{
player!.pause()
player = nil
}
Objective-C Code-
if (player.rate > 0 && !player.error)
{
[player setRate:0.0];
}

AVPlayer not playing local mp4

I am trying to use an AVPlayer to play a video that has been recorded in my app. However, the player won't play the video. I know for a fact that this is a properly recorded mp4 file, because I can take it and play it on my Mac just fine. Here's the setup for the player:
let documents = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).first!
let URL = NSURL(fileURLWithPath: "tempVideo", relativeToURL: documentsDirectory)
let asset = AVAsset(URL: URL)
let item = AVPlayerItem(asset: asset)
//videoPlayer is a property on the view controller being used
videoPlayer = AVPlayer()
//videoPlayerLayer is a property on the view controller being used
videoPlayerLayer = AVPlayerLayer(player: videoPlayer)
videoPlayerLayer.frame.size = view.frame.size
videoPlayerLayer.backgroundColor = UIColor.redColor().CGColor
view.layer.addSublayer(videoPlayerLayer!)
//wait 5 seconds
videoPlayer.play()
I know for sure that the videoPlayer is, in fact, ready to play, because I've checked its status property. I also know that videoPlayerLayer has properly been added to view.layer because its visible and takes up the whole screen. When I call videoPlayer.play(), the music playing on the device stops, but videoPlayerLayer doesn't show anything.
Any ideas? Thank you in advance for the help!
EDIT: I forgot to show that videoPlayerLayer is indeed connected to videoPlayer, I have updated my question to reflect this.
The correct answer was given by #Dershowitz123, but he or she left it in a comment so I can't mark it as correct. The solution was to change the URL to include the .mp4 extension. Thank you for your help.

Resources