Build iOS Video Player on Top of TCA — SuperPlayer
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.
SuperPlayerViewController
will act as the intermediate object that performs the actual subscription to both store and theAVFoundation
playback states.SuperPlayerViewController
hasAVPlayerLayer
as its sublayer to display the video fromAVPlayer
.- Constructing view for
SuperPlayerViewController
- 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.
- Import SuperPlayer
- Add
SuperPlayerState
andSuperPlayerAction
to pullbackSuperPlayerReducer
.1loadVideo
action will act as a trigger to load the video when we call it fromViewController
later. - 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. - Combine
appReducer
andsuperPlayerReducer
to merge all the effects. We create a pullback to transformssuperPlayerReducer
into state management that works on global state management.
Get back toViewController.swift
and set up the store.
- Setup
store
andviewStore
to pass states as objects to interact with view. - Scope to transform a store into SuperPlayer’s store that deals with local state and actions.
- Send
loadVideo
action load the video. - 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
- Import
ComposableArchitecture
,Combine
andSuperPlayer
. - Create an emitter to tell the parent that
playButton
is tapped, later we will use this to trigger pause and play. - SuperPlayer provided some states, such as
currentTimeLabel
,playIcon
, etc. to help with the video control callSuperPlayerControlState
. Createstore
andviewStore
forSuperPlayerControlState
andSuperPlayerAction
. - 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 useAnyCancellable
type to cancelCombine
publishers. - Add store parameter on init for scope store in the parent.
- Use
SuperPlayerControlState
‘splayIcon
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
- Import Combine
- Scope store to satisfy
PlayerControlView
to exposes local state and actions. - Create
disposeBag
to cancelCombine
publishers. - 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.
- Since we can have
00:00:00
or00:00
, we better update thetimeIndicator
constraint. We need to listen toactualDuration
state and make a condition like the code above. - 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
- Import
Combine
,ComposableArchitecture
, andSuperPlayer
- Create
store
,viewStore
anddisposeBag
. barNodeDidLayout
will help to prevent repeated seek bar width update inlayoutSubview()
.- Add store parameter in init for scope store in the parent.
- Listen to
loadedTimes
to show the video has sufficient buffer to play. - SuperPlayer helps to calculate seeker (slider thumb) position from the current time. The calculation is stored in
seekerPosition
state. - 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.)
- After finish seeking time, or cancel seeking, we need to play the video again.
- We need to tell SuperPlayer the bar width (slider width) we construct in the view, so SuperPlayer can recalculate the seeker position for us.
- 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. - 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!
Credits
Thanks to Adityo Rancaka to make SuperPlayer happen. Also kudos to all developers who helped make this happen.