Video in SwiftUI: Coordinator

So you may think we finish the work on the isMuted property on part 2 of this series? Not quite.

Start the demo app, notice the mute icon on the top right of the video controls, what happens if you tap on that control?

Sure thing it will mute our video, but notice that our toggle command does not update accordingly, it gets out of sync with the state of the underlying video player property.

So how can we sync between the player class and our SwiftUI Video view?

Do you remember the UIViewControllerRepresentable protocol from part 1, beside the makeViewController() and createViewController() methods there is a third method in the protocol: makeCoordinator().

The goal of this method is to create a Coordinator class, that coordinates between our SwiftUI View and the wrapped UIViewController, this looks like exactly what we need for our isMuted property. 

Let’s see how can we implement it; start by creating an internal class called VideoCoordinator:

public class VideoCoordinator: NSObject {
  let video: Video
  var player: AVPlayer?

  init(video: Video) { = video

Then we can implement our makeCoordinator() method:

func makeCoordinator() -> VideoCoordinator {  
  return VideoCoordinator(video: self)

After implementing makeCoordinator, the coordinator class will be available through the context argument in the other methods of the protocol. So now we can connect the player class to our video component.

public func makeUIViewController(context: Context) -> AVPlayerViewController {
  let videoViewController = AVPlayerViewController()
  videoViewController.player = AVPlayer(url: videoURL)
  let videoCoordinator = context.coordinator
  videoCoordinator.player = videoViewController.player

So how can we sync the value of isMuted between the AVPlayer object and the Video component?

If you look at the documentation of AVPlayer you will see that the properties of this class are KVO observable so that we can set a KVO observer for the muted property. Let’s do this using a dedicated method on the coordinator:

func addKVOObservers(to player: AVPlayer?) {
                       forKeyPath: "muted", 
                       options: [.new, .old], 

And then we can use it on the didSet method of our player variable:

var player: AVPlayer? {
  didSet {
    addKVOObserver(to: player)

We are just missing the implementation of the observer method:

override open func observeValue(forKeyPath keyPath: String?, 
  of object: Any?, 
  change: [NSKeyValueChangeKey : Any]?, 
  context: UnsafeMutableRawPointer?) {

  guard keyPath == "muted" else {
    super.observeValue(forKeyPath: keyPath, 
                         of: object,
                         change: change, 
                         context: context)
  if let player = player {
    video.isMuted.wrappedValue = player.isMuted

And we are done if you start your demo app you will see that every time you tap on the mute icon on the video overlay our toggle control updates accordingly.

Just one last thing, in order to avoid crashes let’s remove the KVO observation on the deinit method:

private func removeKVOObservers(from player: AVPlayer?) {
  player?.removeObserver(self, forKeyPath: "muted")

deinit {
  removeKVOObservers(from: player)

And now we are done with our isMuted property.

Join us in part 4 to see how to make this component compatible with macOS.


Leave a Comment

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s