Search code examples
iosswiftavplayeravplayerviewcontrolleravplayerlayer

Sync video in AVPlayerLayer and AVPlayerViewController


I'm working with AVPlayerto show the playing video using a URL in it. There are 2 parts to it:

1. Firstly, I've embedded the AVPlayer to a view's sublayer using AVPlayerLayer, i.e.

var player: AVPlayer?

func configure() {
    let urlString = "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"
    if let url = URL(string: urlString) {
        self.player = AVPlayer(url: url)
        let playerLayer = AVPlayerLayer(player: self.player)
        playerLayer.frame = self.view.bounds
        self.view.layer.addSublayer(playerLayer)
        player.play()
    }
}

The above code is working fine and the video is playing.

2. Secondly, on a UIButton tap, I'm presenting a AVPlayerViewController using the same AVPlayer instance that I created earlier, i.e.

@IBAction func onTapVideoButton(_ sender: UIButton) {
    self.player?.pause()
    let controller = AVPlayerViewController()
    controller.player = self.player
    self.present(controller, animated: true) {
        self.player?.play()

    }
}

The problem I'm facing here is, after the AVPlayerViewController opens, the video stops playing but the audio still plays on.

What I want is to sync the video in both AVPlayerLayer and AVPlayerViewController.


Solution

  • I think there is a problem when sharing a player already created to the AVPlayerViewController. I'm not sure why is stoping but it wont happen if you create a new AVPlayer for that controller. A way to sync your player and your AVPlayerViewController could be like this:

    First, you create a Notification Name that you'll use when the AVPlayerViewController is dismiss (apple does not give you a way to know when the user dismiss the AVPlayerViewController):

    extension Notification.Name {
        static let avPlayerDidDismiss = Notification.Name("avPlayerDidDismiss")
    }
    

    Then, you extend AVPlayerViewController to post this notification when is going to be dismiss and to send the time when the user left the video:

    extension AVPlayerViewController {
    
        open override func viewWillDisappear(_ animated: Bool) {
            super.viewWillDisappear(animated)
            if let seekTime = player?.currentTime() {
                let userInfo = ["seekTime": seekTime]
                NotificationCenter.default.post(name: .avPlayerDidDismiss, object: nil, userInfo: userInfo)
            }
        }
    
    }
    

    And in your ViewController you observe that notification, get the seekTime you want to go and use it to setup your avPlayer:

    class ViewController: UIViewController {
        var player: AVPlayer?
    
        deinit {
            NotificationCenter.default.removeObserver(self)
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            configure()
            NotificationCenter.default.addObserver(self, selector: #selector(avPlayerDidDismiss), name: .avPlayerDidDismiss, object: nil)
        }
    
        func configure() {
            self.player = getPlayer()
            let playerLayer = AVPlayerLayer(player: self.player)
            playerLayer.frame = self.view.bounds
            self.view.layer.addSublayer(playerLayer)
            self.player?.play()
        }
    
        @IBAction func onTapVideoButton(_ sender: UIButton) {
            self.player?.pause()
            let controllerPlayer = getPlayer()
            controllerPlayer.currentItem?.seek(to: self.player!.currentTime(), completionHandler: nil)
            let controller = AVPlayerViewController()
            controller.player = controllerPlayer
            self.present(controller, animated: true, completion: {
                controller.player?.play()
            })
        }
    
        func getPlayer() -> AVPlayer {
            let url = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")!
            return AVPlayer(url: url)
        }
    
        @objc
        private func avPlayerDidDismiss(_ notification: Notification) {
            if let seekTime = notification.userInfo?["seekTime"] as? CMTime {
                player?.currentItem?.seek(to: seekTime, completionHandler: nil)
                player?.play()
            }
        }
    }
    

    Cons: It will send the notification for every AVPlayerViewController. You use add you as an observer when you need this info. Hope it can help.