AVPlayer는 로컬 mp3, mp4, HTTP live streaming, http url의 동영상을 재생하게 해줍니다. 현재 재생중인 동영상을 바꿀 때 replaceCurrentItem(with:) 함수를 사용하면 됩니다. 그러나 재생 시에 한 동영상만 재생이 가능하다는 사실.
여러 동영상을 추가했다면 AVQueuePlayer를 통해 AVPlayerItem들을 관리할 수 있습니다. Queue의 성격을 갖고 있어 AVPlayerItem 동영상에 대해 insert, remove 등의 연산이 가능합니다.
AVPlayerViewController로 present. 화면 전환해서 player를 통해 동영상을 실행할 수 있지만, ContainerView ( UIView등의 타입)를 선언하고 그 안에 AVPlayer와 AVPlayerLayer를 설정함으로 한 화면에 여러 UIView와 함께 동영상을 출력할 수도 있습니다.
AVPlayer와 AVPlayerLayer를 통한 동영상 실행은 정말 간단합니다.
var movieContainer: UIView!
var playerLayer: AVPlayerLayer!
정말 짧은 CALayer 설명
AVPlayer는 NSObject입니다. UIView가 아니기에 view.addSubview등의 추가를 하지 못합니다. 따라서 화면 안에서 동영상 재생 될 레이아웃은 AVPlayerLayer에 의해 관리됩니다. AVPlayerLayer는 CALayer의 subclass 인데 CALayer는 Core Animation framework입니다. UIView에도 CALayer가 있는데 draw나 animation 관련 작업은 Core Animation에 delegate합니다. UIView는 CALayer의 wrapper입니다. 각각의 UIView는 한개의 root CALayer가 있고 여러개의 subLayers를 포함할 수 있습니다....
요.. CALayer를 이용해서 UIView의 sublayer에 추가를 함으로 화면에 출력할 수 있다는 사실..
func makePlayer(urlString: String, completion: @escaping(Bool)->Void) {
guard let hasUrl = URL(string: urlString) else {
completion(false)
return
}
let asset = AVAsset(url: hasUrl)
let item = AVPlayerItem(asset: asset)
playerLayer = AVPlayerLayer(player: AVPlayer(playerItem: item))
setupPlayerLayers(playerLayer)
completion(true)
}
그리고 AVPlayer를 만드는 함수를 설정합니다.
url은 미디어 관련 모델인 AVAsset로 관리합니다. QuickTime movie, mp3, http live streaming관련을 다룹니다. AVPlayer를 통해 한 개 또는 여러개의 미디어 asset를 재생할 수 있습니다. 맨 위에서 언급했지만 replaceCurrentItem(with:) 로 다른미디어 asset를 재생할 수있지만 동시간대에 한 개의 미디어 재생만 가능합니다.
AVAsset은 동영상 재생 관련 model이라기 보단 재생 기간, 생성일 등에 관한 정보가 담겨있습니다. asset를 플레이하기 위해서 AVPlayerItem 인스턴스를 동적으로 만들어야 합니다. 이 object는 타이밍, assert play's presentation state 등의 모델입니다. ( AVPlayer에 의해 재생되는 asset의 상태 관련)
AVPlayer는 상태가 계속 dynamic하게 변화합니다. 그래서 AVPlayer의 상태 관찰할 수 있는 방법이 두 가지 있습니다. ( 나중에 .. 다시 다뤄보도록 하겠습니다.)
AVPlayerLayer는 custom interface를 만들 때 사용합니다. 위에서 아주 살짝 언급했지만,, AVAsset -> AVPlayerItem을 통해 생성된 playerItem은 AVPlayer에 추가할 수 있는데, AVPlayer는 NSObjcet입니다. UIView가 아니라 view에 추가할 수 없습니다. 하지만 AVPlayerLayer를 통한 layer 계층 구조를 지정함으로 추가할 수 있습니다. 약간의 단점이라면 재생 컨트롤 기능은 나타나지 않습니다. ( 추가해야 한다는,,) 오직 자신이 속한 Container UIView의 특정 범위에서 visual content 출력을 합니다.
func setupPlayerLayers(_ target: AVPlayerLayer) {
movieContainer.layer.addSublayer(target)
target.videoGravity = .resizeAspectFill
movieContainer.layer.shadowOffset = CGSize(width: 0, height: 10)
movieContainer.layer.shadowColor = UIColor.black.cgColor
movieContainer.layer.shadowOpacity = 0.23
}
제 코드에서 AVPlayerLayer의 container view는 movieContainer 변수입니다.
이 변수의 레이아웃을 설정하고, movieContainer의 layer의 sublayer로 AVPlayerLayer를 추가합니다. 그리고
AVPlayerLayer의 videoGravity를 지정해야 합니다.
1. AVLayerVideoGravity.resizeAspectFill
// Container에 맞게 확장. (굿)
2. AVLayerVideoGravity.resizeAspect
// default == 원본
3. AVLayerVideoGravity.resize
AVPlayer's movie isn't showing issue.
var url: String! {
didSet {
makePlayer(urlString: url) {
guard $0 else {
print("url가 잘못됬거나 player 초기화가 잘못됨.")
// 동영상이 안 보여질 때 초록초록하게 해봄
self.movieContainer.backgroundColor = .systemGreen
return
}
self.playerLayer?.frame = self.movieContainer.bounds // x
self.playerLayer.player?.play()
}
}
}
url을 받으면 movieContainer에 동영상을 재생시켰습니다. 하지만 movieContainer의 background만 보이고, 동영상의 소리만 들리는 문제가 있었습니다. 보이는 경우가 있었습니다. 그 원인은 playerLayer의 bounds가 변경될 수 있기 때문입니다.
이땐 viewDldLayoutSubviews()를 오바리이딩해서 UIView의 layout 관련 메서드들이 전부 호출 된 이후에( 레이아웃이 결정된 이후 ) 특정 뷰의 크기, layer등을 최종적으로 조정할 때 사용합니다. 이 함수를 통해 ViewController에게 추가적인 layout 변경을 알려줄 수 있습니다.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
playerLayer?.frame = movieContainer.bounds
}