1

I have a tvOS app which has a video playing in it. There are basically two videos (different speed versions of the same video). One is 12MB in size and another is 1.9MB.

When the app starts, it runs fine (Xcode shows 191MB). However, when clicking normal speed button once, the memory shoots to 350MB. As and when I click normal and fast buttons respectively, this goes on increasing and at one point it becomes 1GB+. You can see the attachment. It even went to 3GB when the video stuttered and the app stopped.enter image description here

Is there any way to solve the memory issue and save the app from stopping?

Another problem is: when in Apple TV, we go to another app from this app and come back, the video again stops. However, in Simulator, it is not happening. Can someone help me to solve these two issues?

Here is the code I am using:

var avPlayerLayer: AVPlayerLayer!
var paused: Bool = false

func playmyVideo(myString: String) {

    let bundle: Bundle = Bundle.main
    let videoPlayer: String = bundle.path(forResource: myString, ofType: "mov")!
    let movieUrl : NSURL = NSURL.fileURL(withPath: videoPlayer) as NSURL

    print(movieUrl)

    viewVideo.playVideoWithURL(url: movieUrl)


}
@IBAction func normalPressed(_ sender: Any) {

    playmyVideo(myString: "normal")


}


@IBAction func forwardPressed(_ sender: Any) {

    playmyVideo(myString: "fast")

}

class VideoPlay: UIView {


private var player : AVPlayer!

private var playerLayer : AVPlayerLayer!

init() {

    super.init(frame: CGRect.zero)
    self.initializePlayerLayer()

}

override init(frame: CGRect) {
    super.init(frame: frame)
    self.initializePlayerLayer()
    self.autoresizesSubviews = false
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.initializePlayerLayer()

}



private func initializePlayerLayer() {

    playerLayer = AVPlayerLayer()
    playerLayer.backgroundColor = UIColor.clear.cgColor



    playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill


    self.layer.addSublayer(playerLayer)

    playerLayer.frame = UIScreen.main.bounds


}

func playVideoWithURL(url: NSURL) {

    player = AVPlayer(url: url as URL)
    player.isMuted = false

    playerLayer.player = player

    player.play()

    loopVideo(videoPlayer: player)
}

func toggleMute() {
    player.isMuted = !player.isMuted
}

func isMuted() -> Bool
{
    return player.isMuted
}

func loopVideo(videoPlayer: AVPlayer) {

    NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil, queue: nil) { notification in
        let t1 = CMTimeMake(5, 100);
        self.player.seek(to: t1)
        videoPlayer.seek(to: kCMTimeZero)
        self.player.play()
    }
}

}
Michał Ciuba
  • 7,876
  • 2
  • 33
  • 59
BizDev
  • 371
  • 1
  • 4
  • 21

1 Answers1

1

I see two problems in your code:

  1. Each time playVideoWithURL method is called, you create new AVPlayer instance, instead of reusing already existing one. You can call replaceCurrentItem(with:) method on your player property when you want to play another URL.

That itself is a bit inefficient, but shouldn't cause the memory issue you described. I think the reason is:

  1. Each time loopVideo method is called, you pass a closure to NotificationCenter.default.addObserver. This closure creates a strong reference to videoPlayer. You never remove the observer from the notification center.

As loopVideo is called each time you create new AVPlayer instance, these instances are never deallocated, leading to the memory issue you described.

To fix it, you can:

  • initialize player property only once in playVideoWithURL, then use replaceCurrentItem when you want to play another video
  • also change the "loop" logic, so that you call NotificationCenter.default.addObserver only once
  • the closure you pass to NotificationCenter.default.addObserver creates a memory leak (see this question). You can get rid of it by capturing self weakly:

    NotificationCenter.default.addObserver(forName:
    NSNotification.Name.AVPlayerItemDidPlayToEndTime,object: nil, queue: nil) { [weak self], notification in

      self?.player.seek(to: kCMTimeZero)
      self?.player.play()
    

    }

also remember to call removeObserver in deinit method of VideoPlay class.

Community
  • 1
  • 1
Michał Ciuba
  • 7,876
  • 2
  • 33
  • 59