12/08/2018, 13:08

Custom trình duyệt video sử dụng AVPlayer-AVFoundation

Hiện nay có rất nhiều ứng dụng iOS có chức năng quay video và play video. iOS đã cung cấp các thư viện để chúng ta có thể dễ dàng play video, đơn giản nhất mà chúng ta hay sử dụng đó là MPMoviePlayerController. Tuy nhiên, từ iOS 9 trở đi thì đã không khuyến cáo sử dụng class này nữa, thay vào đó là ...

Hiện nay có rất nhiều ứng dụng iOS có chức năng quay video và play video. iOS đã cung cấp các thư viện để chúng ta có thể dễ dàng play video, đơn giản nhất mà chúng ta hay sử dụng đó là MPMoviePlayerController. Tuy nhiên, từ iOS 9 trở đi thì đã không khuyến cáo sử dụng class này nữa, thay vào đó là AVPictureInPictureController hoặc AVPictureInPictureController của AVKit framework, nhưng nếu muốn can thiệp sâu hơn vào video, ví dụ như play một danh sách video, chỉnh sửa layout hiển thị,... thì chúng ta phải sử dụng tới AVPlayer (AVFoundation framework). AVPlayer rất linh hoạt, tuy nhiên tài liệu thì lại rất hạn chế. Nó giúp bạn sử dụng rất nhiều Key-Value Observing (KVO) để kiểm tra các trạng thái và dễ dàng xây dựng một player theo cách của bạn. Hôm nay tôi sẽ hướng dẫn tự xây dựng 1 trình duyệt video đơn giản.

1 Tạo project mới

Screen Shot 2015-12-29 at 9.37.04 AM.png

2. Tạo 1 cocoa touch class mới đặt tên là FRVideoController

  • Tạo một control view gồm: slider, các playback cần thiết như play/pause, next, previous,...

2.png

Chú ý cần phải thiết lập App transport security settings trong info.plist

Screen Shot 2015-12-29 at 9.48.14 AM.png

3. Hàm khởi tạo FRVideoController

init(withListVideo list: [String], playAtIndex index: Int = 0)
    {
        super.init(nibName: kControllerNibName, bundle: nil)
        listVideoUrl = list
        currentVideoIndex = index
    }

Dữ liệu khởi tạo sẽ là một mảng các đường dẫn video listVideoUrl, ngoài ra có cung cấp biến playAtIndex để chỉ định khi khởi tạo sẽ chạy video nào đầu tiên, mặc định sẽ là video ở vị trí 0

4. Bắt đầu video player

  • Khởi tạo 1 video player với AVPlayerLayer
let currentPlayerItem = AVPlayerItem(URL: NSURL(string: listVideoUrl[currentVideoIndex])!)
 self.player = AVPlayer(playerItem: currentPlayerItem)
 self.playerLayer = AVPlayerLayer(player: self.player)
 self.playerLayer!.videoGravity = AVLayerVideoGravityResizeAspect //AVLayerVideoGravityResizeAspectFill
 self.view.layer.addSublayer(self.playerLayer!)
 self.playerLayer!.frame = CGRectMake(0, 0, kWidthScreen, kHeightScreen)
 //create control
 controlView.frame = CGRectMake(0, 0, kWidthScreen, kHeightScreen)
 self.view.addSubview(controlView)

Mỗi video sẽ tương ứng với một AVPlayerItem, sử dụng AVPlayerLayer để tạo 1 layer play video, và add control view vào controller view chính. Chú ý video có 2 mode hiển thị hay sử dụng là AVLayerVideoGravityResizeAspect và AVLayerVideoGravityResizeAspectFill

  • startObservers là hàm quan trọng nhất trong project này, sử dụng để tính toán thời gian chạy của video (seeking)
 func startObservers() {
        if (timeObserver == nil) {
            weak var wSelf = self
            timeObserver = player?.addPeriodicTimeObserverForInterval(CMTimeMake(1, 100), queue: dispatch_get_main_queue(),
                usingBlock: { (time: CMTime) -> Void in
                    let currentItem = wSelf!.player?.currentItem!
                    let endTime = CMTimeConvertScale(currentItem!.asset.duration, time.timescale, CMTimeRoundingMethod.RoundHalfAwayFromZero)
                    let currentTimeF: Float = (Float)(CMTimeGetSeconds(time))
                    let endTimeF: Float = (Float)(CMTimeGetSeconds(endTime))
                    if (!isnan(currentTimeF) && !isinf(currentTimeF) && !isnan(endTimeF) && !isinf(endTimeF)) {
                        wSelf?.currentEndTime = endTime
                        wSelf!.updateTime(currentTimeF, endTime: endTimeF)
                    }
            })
        }
        player?.currentItem!.addObserver(self, forKeyPath: kPlayerStatus, options: NSKeyValueObservingOptions.New, context: nil)
 }

Khi đã add 1 Observing thì phải stop Observing khi không sử dụng đến nữa.

  • Hàm kiểm tra thay đổi trạng thái của video: sẵn sàng để chạy, lỗi, ...
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>){
        if keyPath! == kPlayerStatus {
            let status: AVPlayerStatus = self.player!.status
            switch (status) {
            case AVPlayerStatus.ReadyToPlay:
                self.loadingView.hidden = true
                self.loadingView.stopAnimating()
                self.playVideo()
                break
            case AVPlayerStatus.Unknown, AVPlayerStatus.Failed:
                break
            }
        }
}
  • Mỗi lần thay đổi video khác thì chỉ cần replace AVPlayerItem
let currentPlayerItem = AVPlayerItem(URL: NSURL(string: listVideoUrl[index])!)
player?.replaceCurrentItemWithPlayerItem(currentPlayerItem)
player?.currentItem!.addObserver(self, forKeyPath: kPlayerStatus, options: NSKeyValueObservingOptions.New, context: nil)

5. Gọi player từ viewcontroller chính

@IBAction func playVideoTouched() {
        var listVideoUrl: [String] = []
        listVideoUrl.append("http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")
        listVideoUrl.append("http://clips.vorwaerts-gmbh.de/VfE_html5.mp4")
        let videoController = FRVideoController(withListVideo: listVideoUrl)
        self.presentViewController(videoController, animated: true) { () -> Void in
        }
    }

Đây là kết quả của chúng ta

Simulator Screen Shot Dec 29, 2015, 9.56.39 AM.png

Tôi đã tóm tắt các hàm cơ bản như trên, ngoài ra các bạn có thể tham khảo code tại địa chỉ này https://github.com/phanthanhhai/FRVideoPlayer.git

0