AVKit
AVFoundation 기반의 고급 미디어 재생 UI입니다. 시스템 표준 비디오 플레이어, PIP(Picture-in-Picture), AirPlay 라우팅, 재생 컨트롤, 자막/캡션 디스플레이를 제공합니다. Swift 6.3 / iOS 26+를 대상으로 합니다.
목차
- 설정
- AVPlayerViewController
- SwiftUI VideoPlayer
- Picture-in-Picture
- AirPlay
- 재생 컨트롤 및 재생 속도
- 자막 및 폐쇄 자막
- 일반적인 실수
- 검토 체크리스트
- 참고 자료
설정
오디오 세션 구성
재생 전에 오디오 세션과 백그라운드 모드를 구성하세요. 이 작업을 수행하지 않으면 PiP와 백그라운드 오디오가 자동으로 실패합니다.
- Info.plist의
UIBackgroundModes에audio백그라운드 모드를 추가합니다. - 오디오 세션 카테고리를
.playback으로 설정합니다.
import AVFoundation
func configureAudioSession() {
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(.playback, mode: .moviePlayback)
try session.setActive(true)
} catch {
print("오디오 세션 구성 실패: \(error)")
}
}
임포트
import AVKit // AVPlayerViewController, VideoPlayer, PiP
import AVFoundation // AVPlayer, AVPlayerItem, AVAsset
AVPlayerViewController
AVPlayerViewController는 표준 UIKit 플레이어입니다. 시스템 재생 컨트롤, PiP, AirPlay, 자막 및 프레임 분석을 기본으로 제공합니다. 서브클래싱하지 마세요.
기본 프레젠테이션 (전체 화면)
import AVKit
func presentPlayer(from viewController: UIViewController, url: URL) {
let player = AVPlayer(url: url)
let playerVC = AVPlayerViewController()
playerVC.player = player
viewController.present(playerVC, animated: true) {
player.play()
}
}
인라인 (임베디드) 재생
인라인 재생을 위해 AVPlayerViewController를 자식 뷰 컨트롤러로 추가하세요. addChild를 호출하고, 제약 조건과 함께 뷰를 추가한 다음 didMove(toParent:)를 호출합니다.
func embedPlayer(in parent: UIViewController, container: UIView, url: URL) {
let playerVC = AVPlayerViewController()
playerVC.player = AVPlayer(url: url)
parent.addChild(playerVC)
container.addSubview(playerVC.view)
playerVC.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
playerVC.view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
playerVC.view.trailingAnchor.constraint(equalTo: container.trailingAnchor),
playerVC.view.topAnchor.constraint(equalTo: container.topAnchor),
playerVC.view.bottomAnchor.constraint(equalTo: container.bottomAnchor)
])
playerVC.didMove(toParent: parent)
}
주요 속성
playerVC.showsPlaybackControls = true // 시스템 컨트롤 표시/숨기기
playerVC.videoGravity = .resizeAspect // .resizeAspectFill로 자르기
playerVC.entersFullScreenWhenPlaybackBegins = false
playerVC.exitsFullScreenWhenPlaybackEnds = true
playerVC.updatesNowPlayingInfoCenter = true // MPNowPlayingInfoCenter 자동 업데이트
contentOverlayView를 사용하여 비디오와 재생 컨트롤 사이에 상호작용하지 않는 뷰(워터마크, 로고)를 추가하세요.
델리게이트
AVPlayerViewControllerDelegate를 채택하여 전체 화면 전환, PiP 생명주기 이벤트, 인터스티셜 재생 및 미디어 선택 변경에 응답하세요. 전환 코디네이터의 animate(alongsideTransition:completion:)을 사용하여 전체 화면 애니메이션과 UI를 동기화하세요.
표시 준비 상태
검은 화면 깜빡임을 방지하려면 플레이어를 표시하기 전에 isReadyForDisplay를 관찰하세요.
let observation = playerVC.observe(\.isReadyForDisplay) { observed, _ in
if observed.isReadyForDisplay {
// 플레이어 뷰를 안전하게 표시할 수 있음
}
}
SwiftUI VideoPlayer
VideoPlayer SwiftUI 뷰는 AVKit의 재생 UI를 래핑합니다.
기본 사용법
import SwiftUI
import AVKit
struct PlayerView: View {
@State private var player: AVPlayer?
var body: some View {
Group {
if let player {
VideoPlayer(player: player)
.frame(height: 300)
} else {
ProgressView()
}
}
.task {
let url = URL(string: "https://example.com/video.m3u8")!
player = AVPlayer(url: url)
}
}
}
비디오 오버레이
비디오 콘텐츠 위에 상호작용하지 않는 SwiftUI 오버레이를 추가하세요.
VideoPlayer(player: player) {
VStack {
Spacer()
HStack {
Image("logo")
.resizable()
.frame(width: 40, height: 40)
.padding()
Spacer()
}
}
}
고급 제어를 위한 UIKit 호스팅
VideoPlayer는 모든 AVPlayerViewController 속성을 노출하지 않습니다. PiP 구성, 델리게이트 콜백 또는 재생 속도 제어를 위해 AVPlayerViewController를 UIViewControllerRepresentable로 래핑하세요. 전체 패턴은 references/avkit-patterns.md에서 확인하세요.
Picture-in-Picture
PiP를 사용하면 사용자가 다른 앱을 사용하는 동안 부동 창에서 비디오를 시청할 수 있습니다. AVPlayerViewController는 오디오 세션이 구성되면 자동으로 PiP를 지원합니다. 사용자 정의 플레이어 UI의 경우 AVPictureInPictureController를 직접 사용하세요.
전제 조건
- 오디오 세션 카테고리가
.playback으로 설정됨 (설정 참조) UIBackgroundModes에audio백그라운드 모드
표준 플레이어 PiP
PiP는 AVPlayerViewController에서 기본적으로 활성화됩니다. 자동 활성화 및 인라인-PiP 전환을 제어하세요.
let playerVC = AVPlayerViewController()
playerVC.player = player
// PiP는 기본적으로 활성화됨; 비활성화하려면 false로 설정
playerVC.allowsPictureInPicturePlayback = true
// 앱이 백그라운드로 전환될 때 PiP 자동 시작 (인라인/비전체 화면 플레이어용)
playerVC.canStartPictureInPictureAutomaticallyFromInline = true
PiP 중단 시 UI 복원
사용자가 PiP에서 복원 버튼을 탭하면 델리게이트 메서드를 구현하여 플레이어를 다시 표시하세요. 시스템이 복원 애니메이션을 완료할 수 있도록 완료 핸들러를 true와 함께 호출하세요.
func playerViewController(
_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
// 플레이어 뷰 컨트롤러를 다시 표시하거나 다시 임베드
present(playerViewController, animated: false) {
completionHandler(true)
}
}
사용자 정의 플레이어 PiP
사용자 정의 플레이어 UI의 경우 AVPictureInPictureController를 AVPlayerLayer 또는 샘플 버퍼 콘텐츠 소스와 함께 사용하세요. 먼저 isPictureInPictureSupported()를 확인하세요. 전체 사용자 정의 플레이어 및 샘플 버퍼 PiP 패턴은 references/avkit-patterns.md에서 확인하세요.
guard AVPictureInPictureController.isPictureInPictureSupported() else { return }
let pipController = AVPictureInPictureController(playerLayer: playerLayer)
pipController.delegate = self
pipController.canStartPictureInPictureAutomaticallyFromInline = true
광고 중 선형 재생
광고 또는 법적 고지 중에는 requiresLinearPlayback을 토글하여 탐색을 방지하세요.
// 광고 중
playerVC.requiresLinearPlayback = true
// 광고 완료 후
playerVC.requiresLinearPlayback = false
AirPlay
AVPlayerViewController는 AirPlay를 자동으로 지원합니다. 표준 플레이어를 사용할 때는 추가 코드가 필요하지 않습니다. AirPlay 지원 기기가 있는 경우 시스템이 재생 컨트롤에 AirPlay 버튼을 표시합니다.
AVRoutePickerView
플레이어 UI 외부에 독립형 AirPlay 경로 선택기 버튼을 추가하세요.
import AVKit
func addRoutePicker(to containerView: UIView) {
let routePicker = AVRoutePickerView(frame: CGRect(x: 0, y: 0, width: 44, height: 44))
routePicker.activeTintColor = .systemBlue
routePicker.prioritizesVideoDevices = true // 비디오 지원 경로를 먼저 표시
containerView.addSubview(routePicker)
}
외부 재생 활성화
AVPlayer가 외부 재생을 허용하는지 확인하세요 (기본적으로 활성화됨).
player.allowsExternalPlayback = true
player.usesExternalPlaybackWhileExternalScreenIsActive = true
재생 컨트롤 및 재생 속도
사용자 정의 재생 속도
플레이어 UI에서 사용자가 선택할 수 있는 재생 속도를 제공하세요.
let playerVC = AVPlayerViewController()
playerVC.speeds = [
AVPlaybackSpeed(rate: 0.5, localizedName: "반속"),
AVPlaybackSpeed(rate: 1.0, localizedName: "보통"),
AVPlaybackSpeed(rate: 1.5, localizedName: "1.5배속"),
AVPlaybackSpeed(rate: 2.0, localizedName: "2배속")
]
AVPlaybackSpeed.systemDefaultSpeeds를 사용하여 기본 속도 옵션을 복원하세요.
건너뛰기 동작
앞으로/뒤로 건너뛰기 컨트롤을 구성하세요.
playerVC.isSkipForwardEnabled = true
playerVC.isSkipBackwardEnabled = true
Now Playing 통합
AVPlayerViewController는 기본적으로 MPNowPlayingInfoCenter를 자동으로 업데이트합니다. Now Playing 정보를 수동으로 관리하는 경우 이 기능을 비활성화하세요.
playerVC.updatesNowPlayingInfoCenter = false
자막 및 폐쇄 자막
AVKit은 미디어에 적절한 텍스트 트랙이 포함된 경우 자막 및 폐쇄 자막 표시를 자동으로 처리합니다. 사용자는 설정 > 손쉬운 사용 > 자막 및 청각 장애인용 자막에서 자막 기본 설정을 제어합니다.
자막 언어 제한
// 영어와 스페인어 자막 옵션만 표시
playerVC.allowedSubtitleOptionLanguages = ["en", "es"]
자막 필수 설정
항상 자막을 표시하도록 강제합니다 (사용자가 끌 수 없음).
playerVC.requiresFullSubtitles = true
미디어 선택 (델리게이트)
사용자가 자막 또는 오디오 트랙 선택을 변경하는 것에 응답하세요.
func playerViewController(
_ playerViewController: AVPlayerViewController,
didSelect mediaSelectionOption: AVMediaSelectionOption?,
in mediaSelectionGroup: AVMediaSelectionGroup
) {
if let option = mediaSelectionOption {
print("선택됨: \(option.displayName)")
}
}
HLS에 자막 트랙 제공
자막 및 폐쇄 자막은 HLS 매니페스트에 포함됩니다. AVKit은 AVAsset의 AVMediaSelectionGroup에서 이를 읽습니다. 로컬 파일의 경우 AVMutableComposition을 사용하여 AVMediaCharacteristic.legible 트랙을 추가하세요.
일반적인 실수
하지 마세요: AVPlayerViewController 서브클래싱
Apple은 이 방법을 명시적으로 지원하지 않습니다. 향후 OS 버전에서 정의되지 않은 동작이나 충돌이 발생할 수 있습니다.
// 잘못됨
class MyPlayerVC: AVPlayerViewController { } // 지원되지 않음
// 올바름: 델리게이트와 함께 컴포지션 사용
let playerVC = AVPlayerViewController()
playerVC.delegate = coordinator
하지 마세요: PiP를 위해 오디오 세션 구성 건너뛰기
오디오 세션이 .playback으로 설정되지 않으면 PiP가 자동으로 실패합니다. 백그라운드 오디오도 작동을 멈춥니다.
// 잘못됨: 기본 오디오 세션
let playerVC = AVPlayerViewController()
playerVC.player = player // PiP가 작동하지 않음
// 올바름: 먼저 오디오 세션 구성
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .moviePlayback)
try AVAudioSession.sharedInstance().setActive(true)
let playerVC = AVPlayerViewController()
playerVC.player = player
하지 마세요: PiP 복원 델리게이트 또는 완료 핸들러 잊기
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler가 없으면 시스템이 사용자를 플레이어로 되돌릴 수 없습니다. completionHandler(true)를 호출하지 않으면 시스템이 일관되지 않은 상태가 됩니다.
// 잘못됨: 델리게이트 메서드 없음 또는 completionHandler 호출 누락
// 사용자가 PiP에서 복원을 탭하면 아무 일도 일어나지 않거나 애니메이션이 중단됨
// 올바름
func playerViewController(
_ playerViewController: AVPlayerViewController,
restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void
) {
present(playerViewController, animated: false) {
completionHandler(true)
}
}
하지 마세요: SwiftUI 뷰의 init에서 AVPlayer 생성
플레이어를 미리 생성하면 성능 문제가 발생합니다. SwiftUI는 뷰를 여러 번 다시 생성할 수 있습니다.
// 잘못됨: 모든 뷰 init에서 생성됨
struct PlayerView: View {
let player = AVPlayer(url: videoURL) // 모든 뷰 평가에서 다시 생성됨
var body: some View { VideoPlayer(player: player) }
}
// 올바름: @State 사용 및 생성 지연
struct PlayerView: View {
@State private var player: AVPlayer?
var body: some View {
VideoPlayer(player: player)
.task { player = AVPlayer(url: videoURL) }
}
}
검토 체크리스트
- 오디오 세션 카테고리가
.playback으로 설정되고mode: .moviePlayback사용 -
UIBackgroundModes에audio백그라운드 모드가 Info.plist에 추가됨 -
AVPlayerViewController가 서브클래싱되지 않음 - PiP 복원 델리게이트 메서드가 구현되고
completionHandler(true)호출 - SwiftUI에서
AVPlayer가.task로 지연됨 (미리 생성되지 않음) - 인라인 플레이어에 대해
canStartPictureInPictureAutomaticallyFromInline이 설정됨 -
requiresLinearPlayback이 필요한 광고/법적 구간에서만 토글됨 - AirPlay 지원을 위해
AVPlayer에서allowsExternalPlayback이 활성화됨 - 자막 언어 제한이 실제 미디어 트랙으로 테스트됨
- 비디오 중력이 적절하게 설정됨 (
.resizeAspectvs.resizeAspectFill) - 플레이어 뷰를 표시하기 전에
isReadyForDisplay가 관찰됨 - 네트워크 스트리밍 콘텐츠에 대한 오류 처리 (HLS 오류, 시간 초과)
참고 자료
- 고급 패턴 (사용자 정의 플레이어 UI, 인터스티셜, 백그라운드 재생, 오류 처리): references/avkit-patterns.md
- AVKit 프레임워크
- AVPlayerViewController
- VideoPlayer (SwiftUI)