Utilization of swift video progress bar / UIGestureRecognizerDelegate

Following video playback with AVPlayer Create a video playback Progress Bar so that still images of the video are displayed side by side every 1 to 2 seconds.

reference: https://github.com/AppsBoulevard/ABVideoRangeSlider

  1. Take a still image from Viedo: AVAssetImageGenerator (asset :)
public class VideoThumbnailUtils: NSObject{
    
    public static func createVideoThumbnail(asset:AVAsset, targetTime: Double, scaleRate: Float)->CGImage?{
        var videoSize: CGSize?
        for track in asset.tracks {
            if track.mediaType == AVMediaType.video {
                videoSize = track.naturalSize
            }
        }
        let videoHeight = videoSize?.height ?? 0
        let videoWidth = videoSize?.width ?? 0
        var reSize: CGFloat = 0
        if videoHeight >= videoWidth {
            reSize = videoHeight * CGFloat(scaleRate)
        }else{
            reSize = videoWidth * CGFloat(scaleRate)
        }
        
        let assetImageGenerator = AVAssetImageGenerator(asset: asset)
        assetImageGenerator.appliesPreferredTrackTransform = true
        assetImageGenerator.maximumSize = CGSize(width: reSize, height: reSize)
        var actualTime: CMTime = CMTime.zero
        do{
            let imageRef = try assetImageGenerator.copyCGImage(at: CMTimeMakeWithSeconds(targetTime, preferredTimescale: 600), actualTime: &actualTime)
            return imageRef
        }
        catch {
            print("error")
            return nil
        }
    }
}

Create [UIImage] from the still image acquired in 2.1


class ThumbnailsUtils: NSObject {

    var thumbnailsView = [UIImageView]()
    
    func updateThumbnails(view: UIView, videoURL: URL, duration: Float64) -> [UIImageView] {
        var thumbnails = [UIImage]()
        var offset: Float64 = 0
        let imageCount = thumbnailsCount
        
        for i in 0..<imageCount {
            var thumbnail: UIImage?
            let  asset = AVAsset(url: videoURL)
            if let imageData = VideoThumbnailUtils.createVideoThumbnail(asset: asset, targetTime: Double(offset), scaleRate: 0.3){
                thumbnail = UIImage(cgImage: imageData)
            }
            if thumbnail == nil {
                thumbnail = UIImage(systemName: "sun")
            }
            offset = Float64(i)*(duration/Float64(imageCount))
            if thumbnail != nil {
                thumbnails.append(thumbnail!)
            }
        }
        return self.thumbnailsView
    }
}

Put the [UIImageView] obtained in 3.2 into the UIView: addImagesToView

class ThumbnailsUtils: NSObject {
    
    var thumbnailsView = [UIImageView]()
    private var thumbnailsCount: Int = 0
    
    func set(thumbnailsCount count: Int)-> ThumbnailsUtils {
        self.thumbnailsCount = count
        return self
    }
///Implemented on the premise that the number of thumbnails is decided for the time being
    
    private func clearThumbnails(){
        for tView in self.thumbnailsView {
            DispatchQueue.main.async {
                tView.removeFromSuperview()
            }
        }
    }
    
    private func perThumbnailWidth(_ inView: UIView)-> CGFloat{
        return inView.frame.size.width/CGFloat(thumbnailsCount)
    }
    
    private func addImagesToView(images:[UIImage], view: UIView){
        thumbnailsView.removeAll()
        let perWidth = perThumbnailWidth(view)
        var xpos: CGFloat = 0.0
        var width: CGFloat = 0.0
        
        for image in images {
            DispatchQueue.main.async {
                if xpos + perWidth < view.frame.size.width {
                    width = perWidth
                }else{
                    width = view.frame.size.width - xpos
                }
                
                let imageView = UIImageView(image: image)
                imageView.alpha = 0
                imageView.contentMode = UIView.ContentMode.scaleAspectFill
                imageView.clipsToBounds = true
                imageView.frame = CGRect(x: xpos, y: 0.0, width: width, height: view.frame.size.height)
                self.thumbnailsView.append(imageView)
                
                view.addSubview(imageView)
                UIView.animate(withDuration: 0.2, animations: {()->Void in
                    imageView.alpha = 1.0
                })
                view.sendSubviewToBack(imageView)
                xpos += perWidth
            }
        }
    }
    
    @discardableResult
    func updateThumbnails(view: UIView, videoURL: URL, duration: Float64) -> [UIImageView] {
        clearThumbnails()
        
        var thumbnails = [UIImage]()
        var offset: Float64 = 0
        let imageCount = thumbnailsCount
        
        for i in 0..<imageCount {
            var thumbnail: UIImage?
            let  asset = AVAsset(url: videoURL)
            if let imageData = VideoThumbnailUtils.createVideoThumbnail(asset: asset, targetTime: Double(offset), scaleRate: 0.3){
                thumbnail = UIImage(cgImage: imageData)
            }
            if thumbnail == nil {
                thumbnail = UIImage(systemName: "sun")
            }
            offset = Float64(i)*(duration/Float64(imageCount))
            if thumbnail != nil {
                thumbnails.append(thumbnail!)
            }
            
        }
        addImagesToView(images: thumbnails, view: view)
        return self.thumbnailsView
    }
}
  1. Used on the ViewController side

    func loadThubnails(){
        let thumbManager = ThumbnailsUtils().set(thumbnailsCount: 8)
        let duration = ThumbnailsUtils.videoDuration(url: url!)
        thumbManager.updateThumbnails(view: thumbnailsView, videoURL: url!, duration: duration)
    }

It is now displayed. スクリーンショット 2020-12-28 13.58.15.png

  1. Merge Progress Bar with Thumbnail Bar and make Gesture recognized by Thumbnail Bar It looks like this after the merger. スクリーンショット 2020-12-28 16.43.00.png

  2. When displaying a still image thumbnail of a long video, it can be assumed that the width of the screen is too short. I want to be able to scroll left and right still image thumbnails

You can scroll by setting the UIView to ScrollView (fixed height) and remembering to set the width of the child View of ScrollView. However, if you add a GestureRecognizer in the ProgressBar to trim the video, the GestrueRecognizer in the ScrollView and the GestureRecgnizer in the ProgressBar (change the playback position by tapping, Trim, etc.) will conflict and you will not be able to scroll. ..

Here we use UIGestureRecognizerDelegate. Reference: https://qiita.com/ruwatana/items/16997b1b416512c20fb6

UIGestureRecognizerDelegate is mainly used for:

  1. Method that enables simultaneous identification of Gesture
  2. Gesture recognition start method
  3. A method that allows your Gesture to fail
  4. Method to find the failure of the other party's Gesture

When competing as above, both the other side and the own side inherit the same Delegate, and then You need to write each method as needed. I will make a note again next time.

Recommended Posts

Utilization of swift video progress bar / UIGestureRecognizerDelegate
iOS app development: Timer app (8. Implementation of progress bar)
Utilization of Active Hash
Utilization of seed file
iOS app development: Timer app (9. Customize the color of the progress bar)