Build iOS Video Player on Top of TCA — SuperPlayer

Alvin Matthew Pratama
6 min readOct 18, 2021

--

Before we start, I recommend you to read this article so you can get a glimpse of SuperPlayer.

In this article, you will see how to set up SuperPlayer on top of
The Composable Architecture or TCA for short. Let’s get started!

We will create a video like IGTV or Twitter Video, but much simpler. It can play, pause and resume, show and seek time. We will build this using TCA, UIKit, and SuperPlayer.

Clone this Github repository. This repository includes packages and helpers that we will use. You will see this file structure after cloning.

Try to build the project and you will see this.

Let’s add SuperPlayer package using SwiftPackageManager. https://github.com/tokopedia/ios-superplayer

File > Swift Packages > Add Package Dependency…

Let’s play the video from the link below. http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4

Open ViewController.swift, Import ComposableArchitecture and SuperPlayer

import ComposableArchitecture
import SuperPlayer

Create a function call handleVideo() , we will handle video setup in this function.

  1. SuperPlayerViewController will act as the intermediate object that performs the actual subscription to both store and the AVFoundation playback states. SuperPlayerViewController has AVPlayerLayer as its sublayer to display the video from AVPlayer.
  2. Constructing view for SuperPlayerViewController
  3. Send the action to load the video.

Call the function inside viewDidLoad before setupView() . Build the project and you’ll see the video.

SuperPlayer is supposed to working with TCA, so why not create our TCA state management to play the video. Open AppReducer.swift file.

  1. Import SuperPlayer
  2. Add SuperPlayerState and SuperPlayerAction to pullback SuperPlayerReducer.1
    loadVideo action will act as a trigger to load the video when we call it from ViewController later.
  3. Usually, the environment is used for producing Effects such as API clients, analytics clients, etc. For this case let’s directly return the video URL we want to play.
  4. Combine appReducer and superPlayerReducer to merge all the effects. We create a pullback to transforms superPlayerReducer into state management that works on global state management.

Get back toViewController.swift and set up the store.

  1. Setup store and viewStore to pass states as objects to interact with view.
  2. Scope to transform a store into SuperPlayer’s store that deals with local state and actions.
  3. Send loadVideo action load the video.
  4. Update SuperPlayerViewController parameter to use the store that was scoped before.

Build the project, and it will show the same result. Now we can play video without setting up many AVPlayer states.

Video player is not a video player without pause play and other control. We have custom video control. You can create your own video control too, but for now, let’s use this one, I will show you how to incorporate the control with SuperPlayer.

First, the pause and play function. Open PlayerControlView.swift file.

Don’t remove the other code

  1. Import ComposableArchitecture, Combine and SuperPlayer.
  2. Create an emitter to tell the parent that playButton is tapped, later we will use this to trigger pause and play.
  3. SuperPlayer provided some states, such as currentTimeLabel, playIcon, etc. to help with the video control call SuperPlayerControlState. Create store and viewStore for SuperPlayerControlState and SuperPlayerAction.
  4. If you are familiar with RxSwift or reactive programming, after subscribing to an observer, we need to dispose of it, in order to prevent wasted memory. Let’s call it disposeBag and use AnyCancellable type to cancel Combine publishers.
  5. Add store parameter on init for scope store in the parent.
  6. Use SuperPlayerControlState‘s playIcon to change the button image.

Okay! all set, let’s move to AppReducer.swift to handle SuperPlayer pause and play effects.

Add the functionality for handlePausePlayVideo action we created before. SuperPlayerState provides us what method is active now. The method is an enum that contains pause, play, and the other. Get the state value and make a condition for pause and play like below.

Let’s finish the pause and play in ViewController.swift

  1. Import Combine
  2. Scope store to satisfy PlayerControlView to exposes local state and actions.
  3. Create disposeBag to cancel Combine publishers.
  4. Listen to the emitter from PlayerControlView to send action to the reducer.

Now you can pause and play your video, give it a try! Well done, you deserve a flower.

Now we are about to update the time indicator that hangs in the right of the screen. This one below ⤵️

Open PlayerControlView.swift, SuperPlayer provided all states that we need to update the time indicator.

  1. Since we can have 00:00:00 or 00:00, we better update the timeIndicator constraint. We need to listen to actualDuration state and make a condition like the code above.
  2. We need the current time and the actual duration. We use CombineLatest, so when either state emits a new value, the code block will execute and update the time indicator label.

Build the app again and the time indicator should be working.

Last but not least, let’s do the video slider. Let’s call it SeekBar View. Open PlayerSeekBarView.swift file.

Don’t remove the other code

  1. Import Combine, ComposableArchitecture, and SuperPlayer
  2. Create store, viewStore and disposeBag.
  3. barNodeDidLayout will help to prevent repeated seek bar width update in layoutSubview().
  4. Add store parameter in init for scope store in the parent.
  5. Listen to loadedTimes to show the video has sufficient buffer to play.
  6. SuperPlayer helps to calculate seeker (slider thumb) position from the current time. The calculation is stored in seekerPosition state.
  7. Before we start seeking time, we need to pause the player first. It’s common UX used in almost streaming platforms (Youtube, Netflix, Hotstar, etc.)
  8. After finish seeking time, or cancel seeking, we need to play the video again.
  9. We need to tell SuperPlayer the bar width (slider width) we construct in the view, so SuperPlayer can recalculate the seeker position for us.
  10. When start sliding, we need to send seekingSeeker(to: CGFloat), store the x coordinate of the pan location to the parameter. This is for SuperPlayer to help us calculate the new current time.
  11. When pan gesture end, we need to send doneSeeking action for SuperPlayer to play the video at the desired time.

Now back to PlayerControlView.swift to satisfy PlayerSeekBarView.

Now let’s see the Final result!

Final result in this Github repository

Cool right! You can create your own video player with and use SuperPlayer design patterns to reused them among features. With SuperPlayer we can increase the readability and maintainability of the code that leads to saving more development time.

Contribute🚨

Want to make a suggestion or feedback? SuperPlayer is open source and we’re excited to hear and learn from you. Your experiences will benefit others who use SuperPlayer. Let’s make it even better!

https://github.com/tokopedia/ios-superplayer

Credits

Thanks to Adityo Rancaka to make SuperPlayer happen. Also kudos to all developers who helped make this happen.

References

--

--