How to close previous AVPlayer and AVPlayerItem
Asked Answered
B

3

8

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()
    }

}

`

Barograph answered 28/6, 2016 at 7:31 Comment(1)
whenever the next video is shown, previous ones stay open behind it Because you keep adding them one on top of the other with self.view.layer.addSublayer(playerLayer). You should remove the previous one before adding a new one.Leopold
B
5

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()
 }
}

`

Barograph answered 29/6, 2016 at 22:3 Comment(2)
I had the same problem with the 16 layers limit. thanks for your advice and codeJournalize
Please note that you need to remove the Observer when you close the playerJournalize
M
2

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

Moxie answered 28/6, 2016 at 7:42 Comment(3)
This looks promising. I tried implementing this in various ways, but couldn't seem to get it to work. Could you copy and paste my code above to show how you'd implement replaceCurrentItemWithPlayerItem with it? I also just added my player and PlayerItem variables.Barograph
I never used it, I can't tell you but it should work. Otherwise, as @Eric D suggested, do playerLayer.removeFromSuperlayer() when the video ends and add a new layer for the next one.Moxie
the problem is that when changing replaceCurrentItemWithPlayerItem you get a flash effect and it's really annoyingPurapurblind
G
0

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];
}
Gardie answered 28/6, 2016 at 7:45 Comment(1)
I tried using this (swift) suggestion in various ways. When using it at the start of the showVideo() function where I think is the most obvious place to use it, the game crashes and throws a fatal error. any ideas on this?Barograph

© 2022 - 2024 — McMap. All rights reserved.