AVPlayer 분석하기 – 동영상 플레이어 뜯어보기

근데 단순히 영상 틀어주는 객체라고만 생각하면... 큰 코 다칠 수 있어요 ㅋㅎㅋㅎㅋ
AVPlayer는 생각보다 복잡한 상태 변화를 추적해야 하거든요
그럼 오늘은 AVPlayer를 제대로 분석해 볼게요
이미지 출처: 김종권의 iOS 앱 개발 알아가기 - 티스토리
AVFoundation 프레임워크의 핵심 클래스인 AVPlayer는 미디어 재생을 담당하는 객체예요
단순히 play() 메서드만 호출하면 끝! 이 아니라는 거죠
AVPlayer는 동적으로 상태가 계속 변하는 객체라서 KVO(Key-Value Observing)로 상태를 관찰해야 해요
재생 속도, 현재 재생 중인 아이템, 버퍼링 상태 같은 것들이 실시간으로 바뀌거든요
AVPlayer와 AVPlayerItem의 관계
AVPlayer만 있으면 동영상 재생할 수 있는 거 아님???
아니에요!
AVPlayer는 실제 미디어 자산을 직접 다루지 않아요
대신 AVPlayerItem이라는 객체를 통해 미디어를 관리하죠
let url = URL(string: "https://example.com/video.mp4")!
let playerItem = AVPlayerItem(url: url)
let player = AVPlayer(playerItem: playerItem)
이렇게 AVPlayerItem으로 미디어 자산(AVAsset)을 감싸서 AVPlayer에게 전달하는 구조예요
AVPlayerItem은 현재 재생 시간, 버퍼 상태 같은 동적 정보를 추적해요
AVPlayer 상태 종류
AVPlayer를 제대로 다루려면 어떤 상태들이 있는지 알아야 해요
먼저 AVPlayer.Status가 있어요
- unknown: 아직 미디어를 로드하기 전
- readyToPlay: 재생 준비 완료
- failed: 뭔가 문제 생김
그리고 timeControlStatus도 중요해요
재생 중인지, 일시정지인지, 버퍼링 중인지 알려주거든요
버퍼링 관련 상태도 체크해야 하는데요
- isPlaybackLikelyToKeepUp: 버퍼가 충분해서 끊김 없이 재생 가능할 것 같음
- isPlaybackBufferEmpty: 버퍼 비어서 로딩 표시해야 함
- isPlaybackBufferFull: 버퍼 가득 참

이미지 출처: Enyou's Atelier
KVO로 상태 관찰하기
그럼 이 상태들을 어떻게 추적할까요?
바로 KVO를 사용하면 돼요
MVVM 구조로 ViewModel을 만들어볼게요
class VideoPlayerViewModel: NSObject, ObservableObject {
let player: AVPlayer
@Published var playerStatus: AVPlayerItem.Status = .unknown
@Published var isBuffering: Bool = false
private var playerContext = 0
private var statusObserver: NSKeyValueObservation?
private var timeControlObserver: NSKeyValueObservation?
override init() {
self.player = AVPlayer()
super.init()
setupObservers()
}
private func setupObservers() {
statusObserver = player.currentItem?.observe(\.status, options: [.new, .old]) { [weak self] item, change in
self?.playerStatus = item.status
}
timeControlObserver = player.observe(\.timeControlStatus, options: [.new, .old]) { [weak self] player, change in
self?.isBuffering = player.timeControlStatus == .waitingToPlayAtSpecifiedRate
}
}
deinit {
statusObserver?.invalidate()
timeControlObserver?.invalidate()
}
}
observeValue 메서드를 직접 구현하는 방식도 있지만
위처럼 observe(_:options:changeHandler:)를 쓰면 더 간결해요
observe 메서드 파라미터 뭐냐??
- keyPath: 관찰할 속성 (\.status, \.timeControlStatus 등)
- options: 변화 감지 옵션 (.new, .old 등)
- changeHandler: 값이 바뀔 때 실행될 클로저
성능 분석하는 법 (iOS 18+)
iOS 18부터는 AVMetricEvents라는 게 생겼어요
HLS 스트리밍할 때 재생 성능을 추적할 수 있어요
playlist 로드, media segment 다운로드, content key 요청 같은 이벤트를 기록하죠
재생이 멈춘 횟수(stall), variant 전환 같은 것도 다 추적 가능해요
let playerItem = AVPlayerItem(url: streamURL)
// 메트릭 이벤트 수집 시작
let metricCollector = playerItem.metricCollector
// Summary Event로 전체 세션 요약 확인
metricCollector?.summaryEvent.observe { event in
print("Total stalls: \(event.numberOfStalls)")
print("Startup time: \(event.startupTime)")
}
WWDC24에서 강조했던 부분인데
버퍼링 시간이나 재생 시작 지연 같은 걸 분석해서 CDN 문제를 찾아낼 수 있대요
이미지 출처: 김종권의 iOS 앱 개발 알아가기 - 티스토리
AVPlayer 사용 시 주의할 점들
시간 변화는 KVO로 관찰 안 돼요!
대신 addPeriodicTimeObserver(forInterval:queue:using:) 메서드를 써야 해요
let interval = CMTime(seconds: 0.5, preferredTimescale: 600)
player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { time in
print("Current time: \(time.seconds)")
}
실시간 스트리밍에는 AVPlayer가 최적이지만
로컬 오디오 파일 재생할 거면 AVAudioPlayer 쓰는 게 나을 수도 있어요
오류 로그 확인하고 싶으면 playerItem.accessLog()나 errorLog() 메서드 활용하세요
iOS 18부터는 AVMetricEvents로 더 상세한 정보 얻을 수 있고요
테이블뷰나 컬렉션뷰에서 여러 AVPlayer 쓸 때
셀마다 AVPlayer 만들면 스크롤 성능 개판 됩니다ㅎㅎㅎ
셀이 화면에 보일 때만 재생하고, 안 보이면 pause 처리하세요
그리고 가능하면 AVPlayer 인스턴스를 재사용하는 게 좋아요
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// 보이는 셀만 재생, 안 보이는 건 pause
visibleCells.forEach { cell in
if isVisible(cell) {
cell.player.play()
} else {
cell.player.pause()
}
}
}
정리하자면요
AVPlayer는 단순한 재생 객체가 아니라 동적 상태를 추적해야 하는 복잡한 객체예요
KVO로 상태 변화를 관찰하고
버퍼링, 재생 준비, 오류 상태를 적절히 처리해야 좋은 사용자 경험을 만들 수 있어요
iOS 18부터는 성능 분석 도구도 생겼으니 활용해 보시길 바래요!
resource:
https://developer.apple.com/documentation/avfoundation/avplayer
https://developer.apple.com/videos/play/wwdc2024/10190/
https://medium.com/@sergey.mingalev/avplayer-video-optimization-part-1-8b4f2e38ac85
이전 글 보기