[Tutorial] How to trim videos in iOS with ABVideoRangeSlider — Part 1 of 2
This tutorial is made with Swift 3 using the ABVideoRangeSlider library. We included a simple video player in this example.
This example is hosted in our Github repository!
Creating the project
Let’s create our new project using a Single View Application template:
and let’s call it videoeditor.
Preparing the User Interface
First, let’s create our video player. Go to your Main.Storyboard
and add an UIView
and set the constraints
And add two buttons: Play and Pause
Connect these buttons to your ViewController.swift:
@IBOutlet var btnPlay: UIButton!
@IBOutlet var btnPause: UIButton!
Same with the UIView
that will contain our video
@IBOutlet var videoContainer: UIView!
Adding video to our project
Drag and drop a video in the project navigator, in this case I’m using a mp4 video. And make sure that Copy items if needed and your project’s target are checked.
Go to your ViewController.swift and add this line in ViewDidLoad method:
let path = Bundle.main.path(forResource: "test", ofType:"mp4")
Now, in order to play that video we need to import AVFoundation into our controller:
import AVFoundation
and add this two properties to our controller:
let avPlayer = AVPlayer()var avPlayerLayer: AVPlayerLayer!
Initialize the AVPlayer and AVPlayerLayer
After getting the path of our video, we need to create an AVPlayerItem
using that path, and initialize the avPlayer
:
let playerItem = AVPlayerItem(url: URL(fileURLWithPath: path))avPlayer.replaceCurrentItem(with: playerItem)
and set our avPlayerLayer
:
avPlayerLayer = AVPlayerLayer(player: avPlayer)videoContainer.layer.insertSublayer(avPlayerLayer, at: 0)videoContainer.layer.masksToBounds = true
The last two lines, we add that layer to our videoContainer
. In order to be sure our video player fit our container we have to add this line to the viewWillLayoutSubviews
method:
override func viewDidLayoutSubviews() { super.viewWillLayoutSubviews() avPlayerLayer.frame = videoContainer.bounds}
Now add the corresponding actions to our buttons Play and Pause, and connect them through Interface Builder:
@IBAction func playTapped(_ sender: Any) { avPlayer.play() btnPlay.isEnabled = false btnPause.isEnabled = true}@IBAction func pauseTapped(_ sender: Any) { avPlayer.pause() btnPlay.isEnabled = true btnPause.isEnabled = false}
Our ViewController.swift should look like this:
Now we can stop an play our video, but we cannot seek to a specific time (yet!). So, lets add our ABVideoRangeSlider, which has a progress indicator integrated that will help us.
Adding ABVideoRangeSlider pod
Open Terminal and go to your project’s root folder, and create a Podfile
using the following command:
pod init
Open Podfile
and add:
target 'videoeditor' do
use_frameworks!
pod 'ABVideoRangeSlider'
end
And run pod install
. After installing it, remember to close your current project and open the new .xcworkspace
file.
Now that ABVideoRangelSlider is installed, let’s add it to our Main.storyboard
:
Drag an UIView
into your view, and set the constraints. I set the height value to 40, and changed the background color to a dark gray. Then change its class to ABVideoRangeSlider:
And connect this new view to our ViewController.swift:
@IBOutlet var rangeSlider: ABVideoRangeSlider!
Then inside viewDidLoad
, set the video url for our rangeSlider
:
rangeSlider.setVideoURL(videoURL: URL(fileURLWithPath: path))
Run the project and the preview of the video will appear in our range slider. But our progress indicator doesn’t move at all. Let’s add our code to make it work!
We need to add some new properties to our controller first:
var startTime = 0.0;
var endTime = 0.0;
var progressTime = 0.0;
var shouldUpdateProgressIndicator = true
var isSeeking = false
These values will store the start and end time (in seconds) of our video, a flag to tell the app if the progress indicator should be updated, and a flag telling us if the player is seeking.
In our viewDidLoad
, set the endTime as the total duration of the video.
self.endTime = CMTimeGetSeconds((avPlayer.currentItem?.duration)!)
We have set up our rangeSlider, but we didn’t implemented the delegates yet. Add ABVideoRangeSliderDelegate
and add this line after setting the video url:
rangeSlider.delegate = self
and add the delegate methods:
func didChangeValue(videoRangeSlider: ABVideoRangeSlider, startTime: Float64, endTime: Float64) {}func indicatorDidChangePosition(videoRangeSlider: ABVideoRangeSlider, position: Float64) {}
The first method, tracks the start and end indicators, returning the time in seconds of their current positions.
The second method, returns the time in seconds of the progress indicator. And its called when this indicator is dragged.
Add a time observer
Add another property to the controller
var timeObserver: AnyObject!
And initialize it inside viewDidLoad
:
let timeInterval: CMTime = CMTimeMakeWithSeconds(0.01, 100)timeObserver = avPlayer.addPeriodicTimeObserver(forInterval: timeInterval,queue: DispatchQueue.main) { (elapsedTime: CMTime) -> Void inself.observeTime(elapsedTime: elapsedTime) } as AnyObject!
This observer will call observeTime
every 0.01 seconds, and will update our progress indicator.
Now add this method below:
private func observeTime(elapsedTime: CMTime) { let elapsedTime = CMTimeGetSeconds(elapsedTime) if (avPlayer.currentTime().seconds > self.endTime){ avPlayer.pause() btnPlay.isEnabled = true btnPause.isEnabled = false } if self.shouldUpdateProgressIndicator{ rangeSlider.updateProgressIndicator(seconds: elapsedTime) }}
Here, we check if the current time didn’t reach the time it should be paused. Now, the self.endTime
is equal to the total duration of the video. And also, we tell the rangeSlider
to update the position of the progress indicator
Run the app and see how the indicator moves when our video is playing. Finally!
Updating startTime and endTime
If we want to play only the range selected, we need to update startTime
and endTime
. Add these lines to our delegate we implemented before:
func didChangeValue(videoRangeSlider: ABVideoRangeSlider, startTime: Float64, endTime: Float64) { self.endTime = endTime if startTime != self.startTime{
self.startTime = startTime
let timescale = self.avPlayer.currentItem?.asset.duration.timescale let time = CMTimeMakeWithSeconds(self.startTime, timescale!)
if !self.isSeeking{
self.isSeeking = true
avPlayer.seek(to: time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero){_ in self.isSeeking = false
}
}
}
}
Here, we update our values every time the rangeSlider its moved, and seek to the startTime
Seek with the progress indicator
In order to move through the video, we have to track the position of the progress indicator every time it’s dragged. Add these lines to our delegate method:
func indicatorDidChangePosition(videoRangeSlider: ABVideoRangeSlider, position: Float64) {
self.shouldUpdateProgressIndicator = false
avPlayer.pause()
btnPlay.isEnabled = true
btnPause.isEnabled = false
if self.progressPosition != position {
self.progressPosition = position
let timescale = self.avPlayer.currentItem?.asset.duration.timescale
let time = CMTimeMakeWithSeconds(self.progressPosition, timescale!)
if !self.isSeeking{
self.isSeeking = true
avPlayer.seek(to: time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero){_ in self.isSeeking = false } } }
}
And add this line inside the playTapped
method of our button:
self.shouldUpdateProgressIndicator = true
So far, we are able to indicate where to start and end the video, and seek to a specific time with the progress indicator.
Our controller should look like this:
Run the project and test it!
The example project used here is in our Github, so feel free to download it and play with it.
In the part 2 of this tutorial, we’ll trim the video using the startTime
and endTime
, and export it as a new video!