Nice! Can you autoplay that video? – iOS


When he was a kid, he always wanted to play with every new toy you see! He grew up and became a product manager, now he wants to play every video he sees, he says, “Nice! Can you autoplay that video?”. Let me tell you how I built a video player, to autoplay video in iOS, as soon as the videos are displayed on the screen. Yes, just like Instagram!

Le’go!

How can we play videos in our iOS App?

Apple has provided a default video player (allergic to PMs), AVPlayer.

There are two ways:

  1. AVPlayerViewController – If you want to just play the video with the default player and controls, you can just use AVPlayerViewController. You can present this controller and be worry free! Easy!
  2. AVPlayerLayer – If you want to play the video with your own custom controls and custom logics, you can use AVPlayerLayer and implement everything yourself! Easy? (Ask him!)

We will not be covering how to play the video with the default AVPlayerViewController.

We will rather be doing the tough job of creating our own custom player view, which we will use to autoplay video in our iOS app.

Initial Project Setup

For setting up the project to get started with lightening speed, follow this article on PrettyConstraints.

If you want to go with the basic setup you get with the default new project, that’s also cool! This tutorial will follow the project with PrettyConstraints.

Custom Player View

If you run the project once you are done with the setup with PrettyConstraints, you can see a screen with “Hello world”.

Now, to begin with, let’s first understand how AVPlayerLayer works.

Normally, To use AVPlayerLayer, we need to add it as a sub-layer to a UIView.

But we will do something different:

import UIKit
import AVKit

class PlayerView: UIView { 

     override class var layerClass: AnyClass {
         return AVPlayerLayer.self
     }

     init() {
         super.init(frame: .zero)
         initialSetup()
     }
     
     required init?(coder: NSCoder) {
         super.init(frame: .zero)
         initialSetup()
     }
     
     private func initialSetup() {
         if let layer = self.layer as? AVPlayerLayer {
             // Do any configuration you want to the layer
             // We'll set the video gravity
             layer.videoGravity = AVLayerVideoGravity.resizeAspect
         }
     }
}

We created a custom UIView subclass, and override it’s layer property to be AVPlayerLayer!

Then we created a function for configurations, initialSetup, where we’ll do any configurations we need when the PlayerView is initialised.

We have our PlayerView with a layer, where we can play the video, great!

Let’s add a function to setup the layer:

//..
 private var urlAsset: AVURLAsset?
 
 func prepareToPlay(withUrl url:URL) {
     // 1.
     let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
     let urlAsset = AVURLAsset(url: url, options: options)
     self.urlAsset = urlAsset
     
     // 2.
     let keys = ["tracks"]
     urlAsset.loadValuesAsynchronously(forKeys: keys, completionHandler: { [weak self] in
         guard let strongSelf = self else { return }
         // Assets loaded
     })
 }
 //..

First step here would be to load he video from the url string, and to do so we have used AVURLAsset.

AVURLAsset is used to load video asset from local or remote url. With AVURLAsset, we can control the initialisation with options listed here. To exemplify the usage, we have used AVURLAssetPreferPreciseDurationAndTimingKey to get the accurate duration from the asset.

We will save the reference to urlAsset to be used later.

Once we have initialised the asset, now at step two, we are loading the video tracks asynchronously using loadValuesAsynchronously.

Once the tracks are loaded, in the completion handler, we can use the asset further:

//..
 private var playerItem:AVPlayerItem?
 // 6.
 private var assetPlayer:AVPlayer? {
     didSet {
         DispatchQueue.main.async {
             if let layer = self.layer as? AVPlayerLayer {
                 layer.player = self.assetPlayer
             }
         }
     }
 }
 private func startLoading(_ asset: AVURLAsset) {
     // 3.
     var error:NSError?
     let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)
     if status == AVKeyValueStatus.loaded {
         // 4.
         let item = AVPlayerItem(asset: asset)
         self.playerItem = item
         // 5.
         let player = AVPlayer(playerItem: item)
         self.assetPlayer = player
         player.play()
     }
 }
 //..

In the completion handler, we will call this function startLoading.

At step 3, we now get the status for the tracks since the loading is complete.

If tracks are loaded, at step 4, we initialise the AVPlayerItem with the asset, we’ll save the reference to it for future use.

Finally, at step 5, we create AVPlayer with AVPlayerItem and hit play()!

But wait, don’t forget step 6, assign the player we created to the AVPlayerLayer! There you go!

Some sugar?

I know you are eager to try this out! Let’s add a few necessary things first!

//..
 func play() {
     guard self.assetPlayer?.isPlaying == false else { return }
     DispatchQueue.main.async {
         self.assetPlayer?.play()
     }
 }
 
 func pause() {
     guard self.assetPlayer?.isPlaying == true else { return }
     DispatchQueue.main.async {
         self.assetPlayer?.pause()
     }
 }
 
 func cleanUp() {
     pause()
     urlAsset?.cancelLoading()
     urlAsset = nil
     assetPlayer = nil
 }
 
 deinit {
     cleanUp()
 }
//..

Nothing fancy, we exposed play & pause functions, which we will use later.

Additionally, we added a clean up method to make sure we don’t leave anything on the memory once the view is not being used anywhere and is safe to de-init.

What? the code does not compile??

Shoot! Add this AVPlayer extension somewhere in your project:

extension AVPlayer {
     
     var isPlaying:Bool {
         get {
             return (self.rate != 0 && self.error == nil)
         }
     }
     
 }

Usage example

Let me be honest, though it is not much, I was already proud of myself that I have my own player!

So like me, you’d want to test this! Sure, why not!

Head over to our ViewController, and add the following code:

class ViewController: UIViewController {
     
     let containerView:UIView = {
         let view = UIView()
         view.backgroundColor = .white
         return view
     }()
     
     let playerView = PlayerView()
     
     override func viewDidLoad() {
         super.viewDidLoad()
         
         self.setUpUI()
         playerView.prepareToPlay(withUrl: URL(string: "https://www.sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4")!)
         
     }
     
     func setUpUI() {
         self.view.backgroundColor = .white
         self.view.addSubview(containerView)
         containerView.applyConstraints(.fitInSafeArea(self.view.safeAreaLayoutGuide))
         
         containerView.addSubview(playerView)
         playerView.applyConstraints(.fitInView(containerView))
     }
 }

Created playerView, added to containerView, hit play with the sample video url! Easy-peasy!

Run the app now and verify if you see this:

autoplay video in iOS
Demo

As you can see, the big-fat-bunny, is lazy! So we are done with the first part of this tutorial series!

First part? Huh? Series? Read it again, big-fat-bunny is me!

To make your life easier, here is the full source code of PlayerView:

class PlayerView: UIView {
     
     override class var layerClass: AnyClass {
         return AVPlayerLayer.self
     }
     
     init() {
         super.init(frame: .zero)
         initialSetup()
     }
     
     required init?(coder: NSCoder) {
         super.init(frame: .zero)
         initialSetup()
     }
     
     private func initialSetup() {
         if let layer = self.layer as? AVPlayerLayer {
             // Do any configuration you want to the layer
             // We'll set the video gravity
             layer.videoGravity = AVLayerVideoGravity.resizeAspect
         }
     }
     private var urlAsset: AVURLAsset?
     
     func prepareToPlay(withUrl url:URL) {
         // 1.
         let options = [AVURLAssetPreferPreciseDurationAndTimingKey : true]
         let urlAsset = AVURLAsset(url: url, options: options)
         self.urlAsset = urlAsset
         
         // 2.
         let keys = ["tracks"]
         urlAsset.loadValuesAsynchronously(forKeys: keys, completionHandler: { [weak self] in
             guard let strongSelf = self else { return }
             // Assets loaded
             strongSelf.startLoading(urlAsset)
         })
     }
     
     private var playerItem:AVPlayerItem?
     // 6.
     private var assetPlayer:AVPlayer? {
         didSet {
             DispatchQueue.main.async {
                 if let layer = self.layer as? AVPlayerLayer {
                     layer.player = self.assetPlayer
                 }
             }
         }
     }
     private func startLoading(_ asset: AVURLAsset) {
         // 3.
         var error:NSError?
         let status:AVKeyValueStatus = asset.statusOfValue(forKey: "tracks", error: &error)
         if status == AVKeyValueStatus.loaded {
             // 4.
             let item = AVPlayerItem(asset: asset)
             self.playerItem = item
             // 5.
             let player = AVPlayer(playerItem: item)
             self.assetPlayer = player
             player.play()
         }
     }
     
     func play() {
          guard self.assetPlayer?.isPlaying == false else { return }
          DispatchQueue.main.async {
              self.assetPlayer?.play()
          }
      }
      
      func pause() {
          guard self.assetPlayer?.isPlaying == true else { return }
          DispatchQueue.main.async {
              self.assetPlayer?.pause()
          }
      }
      
      func cleanUp() {
          pause()
          urlAsset?.cancelLoading()
          urlAsset = nil
          assetPlayer = nil
      }
      
      deinit {
          cleanUp()
      }
 }

We have created our PlayerView, which we will improvise in our next article to use it in the list and autoplay video in iOS.

Don’t worry, the big-fat-bunny is not so lazy!

Here’s the final part of this article series on How to Autoplay video in list in iOS: https://mobiraft.com/ios/for-gods-sake-can-you-autoplay-video-in-list-ios/


Checkout this trending article on How To Add Splash Screen in SwiftUI.

If you want to learn how to use SwiftUI in your existing app, then you can read it here!

or If you want to learn how to use and convert your UIViewControllers in SwiftUI, then you can read it here!


Like it? Share with your friends!

One Comment

Your email address will not be published. Required fields are marked *