효율적인 iOS 동영상 재생을 위한 AVPlayerLayer 활용법

0

서론

iOS 앱에서 동영상 재생 화면을 구현할 때, AVPlayerLayer를 어떻게 사용하는지가 사용자 경험에 큰 영향을 미칩니다.
AVPlayerLayer는 Core Animation을 통해 비디오를 직접 렌더링하는데,
이를 어떻게 활용하느냐에 따라 화면 전환과 애니메이션 성능이 달라질 수 있습니다.
이 글에서는 AVPlayerLayer를 이용해 동영상을 표시하는 두 가지 방법을 소개하고, 각각의 장단점을 비교하겠습니다.


시작하기 전에 : AVPlayerLayer를 사용하는 이유

AVPlayer는 비디오 파일을 로드하고 재생하는데 필요한 기능을 제공합니다.
하지만, 실제로 비디오를 화면에 렌더링하기 위해서는 AVPlayerLayer가 필요합니다.

AVPlayerLayer는 Core Animation을 사용해 비디오를 렌더링합니다.
또한, 비율(aspect ratio)과 정렬(videoGravity) 설정을 지원하여 UIView의 영역에 맞게 조정할 수 있습니다.
비디오를 그릴 때 사용할 Core Image 필터나 애니메이션 효과를 적용할 수 있어 추가적인 시각 효과도 제공합니다.


방법 1 : AVPlayerLayer를 addSubLayer로 추가하는 방법

AVPlayerLayer는 CALayer를 상속합니다.
따라서 AVPlayerLayer를 UIViewController의 view에 addSubLayer하여 미디어를 재생할 layer를 추가할 수 있습니다.
그런데, CALayer는 Autolayout을 지원하지 않기 때문에 layout의 변경이 필요한 경우 수동으로 frame 값을 조정해 주어야 합니다.
그래서 viewDidLayoutSubviews() 함수에서 AVplayerLayer의 frame을 수동으로 변경시켜 주어야 합니다.

import UIKit
import AVFoundation

final class ContentViewController: UIViewController {
    private var player: AVPlayer?
    private var playerLayer: AVPlayerLayer?
    
    override func viewDidLoad() {
        setupPlayer()
    }
    
    func setupPlayer() {
        guard let url = URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4") else { return }
        player = AVPlayer(url: consume url)
        playerLayer = AVPlayerLayer(player: player)
        playerLayer?.frame = view.bounds
        playerLayer?.videoGravity = .resizeAspect
        
        if let playerLayer {
            view.layer.addSublayer(playerLayer)
        }
        player?.play()
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        playerLayer?.frame = view.bounds
    }
}

방법 2 : UIView의 layer를 AVPlayerLayer로 치환해서 사용하는 방법

UIView의 기본 CALayer는 더 전문화된 기능을 사용하기 위해 다른 타입의 layer로 치환할 수 있습니다.
예를 들면 CAShapeLayer, CAGradientLayer, CAEmitterLayer 등을 사용하여 더 화려한 View를 그릴 수 있죠.
그리고 AVPlayerLayer도 CALayer를 상속하는 만큼 동일하게 치환할 수 있습니다.
이렇게 치환하게 되면 AVPlayerLayer가 UIView에 적용된 AutoLayout을 그대로 따라가게 됩니다.
layout이 바뀔 때마다 수동으로 frame을 조정할 필요가 없기 때문에 가독성이 높고 유지 보수가 편리한 코드가 만들어 집니다.

import UIKit
import AVFoundation

final class AVPlayerView: UIView {
    override class var layerClass: AnyClass { AVPlayerLayer.self }
    private var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer }
    private(set) var player: AVPlayer? {
        get { playerLayer.player }
        set { playerLayer.player = newValue }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        playerLayer.videoGravity = .resizeAspect
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        playerLayer.videoGravity = .resizeAspect
    }
    
    func loadVideo(from url: URL) {
        player = AVPlayer(url: url)
        player?.play()
    }
}

final class ContentViewController: UIViewController {
    private var avPlayerView: AVPlayerView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpPlayer()
    }
    
    func setUpPlayer() {
        let avPlayerView = AVPlayerView()
        avPlayerView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(avPlayerView)
        
        NSLayoutConstraint.activate([
            avPlayerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            avPlayerView.topAnchor.constraint(equalTo: view.topAnchor),
            avPlayerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            avPlayerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        
        if let url = URL(string: "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4") {
            avPlayerView.loadVideo(from: consume url)
        }
        
        self.avPlayerView = avPlayerView
    }
}

두 방법중에 선택해야 할 방법은?

두 가지 방법 중 더 권장되는 방식은 두 번째 방법입니다.
첫 번째 방법은 viewDidLayoutSubviews() 에서 frame을 수동으로 조정해야 하므로 성능에 부담이 될 수 있습니다.
layout 계산을 수동으로 처리해야 하며, 반복적으로 호출되고, 최적화가 되지 않습니다.
하지만 NSLayoutConstraint를 활용해 AutoLayout을 적용한 경우는 위의 경우보다 더 좋은 성능을 제공합니다.
또한 코드가 간결해지고 유지 보수가 간편해집니다.

그리고, 위에 첨부한 영상을 자세히 보면 두 영상의 애니메이션이 다름을 확인할 수 있습니다.
첫 번째 영상의 애니메이션은 무언가가 밀어 넣는듯한 느낌이 있지만, 두 번째 영상은 자연스럽게 전환이 됩니다.
AutoLayout을 활용했을 때 훨씬 부드럽고 자연스러운 애니메이션 효과가 나타남을 확인할 수 있습니다.


추가 : 동영상 플레이어를 개발할 때 UIKit을 사용하는 이유

우리는 UIKit에서 AVPlayerLayer, 또는 SwiftUI에서 VideoPlayer를 사용할 수 있습니다.
그러나 단순한 View를 구성하는 경우가 아니라면 성능과 커스터마이징의 용이함으로 인해 AVPlayerLayer를 사용하는게 유리합니다.

1. UI 업데이트 및 상태 관리

  • VideoPlayer(SwiftUI) : 상태 변화가 발생하면 View를 다시 그리고, 이에 따라 추가적인 오버헤드가 발생할 수 있습니다.
  • AVPlayerLayer(UIKit) : UIKit의 단일 레이어이기 때문에 오버헤드가 거의 없으며, 필요한 프레임만을 업데이트하기 때문에 메모리와 CPU 사용이 더 가볍고 효율적입니다.

2. 초기 진입(로딩) 속도

  • VideoPlayer(SwiftUI) : SwiftUI가 전체 View 계층을 Run time에 관리하므로 초기 로드시 UI 생성 시간이 더 걸릴 수 있습니다.
  • AVPlayerLayer(UIKit) : 단일 AVPlayerLayer가 직접 레이어에 추가되므로, 초기 로드 속도가 약간 더 빠를 수 있습니다.

3. 애니메이션 및 화면 전환

  • VideoPlayer(SwiftUI) : 아직 SwiftUI의 애니메니션과 화면 전환은 UIKit보다 최적화가 부족합니다. 빠른 화면 전환과 애니메이션에서 프레임 드랍이 발생할 수 있습니다.
  • AVPlayerLayer(UIKit) : UIKit의 AVPlayerLayer가 화면 전환과 애니메이션에 더 최적화되어 있습니다.

4. 커스터마이징

  • VideoPlayer(SwiftUI) : 재생, 일시 정지, 되감기 등 기능 추가에 별도의 상태 관리 및 뷰 작업이 필요하므로 성능에 좋지 않을 수 있습니다.
  • AVPlayerLayer(UIKit) : sublayer로 자유롭게 커스터마이징이 가능하고, 성능에 영향이 없게 인터페이스를 구현할 수 있습니다.
profile
https://github.com/sustainable-git

0개의 댓글

관련 채용 정보