오늘은 앱에 동영상을 넣기 위해 VideoPlayer를 사용해 볼 것이다.
추가로 나는 온보딩 화면에 영상을 넣고 싶어서 그 부분도 구현해 볼 것이다.
애플 공식문서에 아주 간단한 예제가 있어서 바로 이해 가능할 것이다.
VideoPlayer 예제
import SwiftUI
import AVKit
struct VideoView: View {
@State private var player: AVPlayer? //1
@State private var isPlaying = false
var body: some View {
VStack {
if let player {
//2
VideoPlayer(player: player)
.frame(width: 320, height: 180, alignment: .center)
//3
Button {
isPlaying ? player.pause() : player.play()
isPlaying.toggle()
player.seek(to: .zero)
} label: {
Image(systemName: isPlaying ? "stop" : "play")
.padding()
}
}
}
//4
.task {
let url = URL(string: "여기에 url 넣기")!
player = AVPlayer(url: url)
}
}
}
#Preview {
VideoView()
}
주요 포인트는 4가지다.
AVPlayer를 옵셔널로 뒀는데 AVPlayer 인스턴스를 선언 시점이 아닌 task에서 간접적으로 생성하는 방식을 사용한다.
그 이유는 성능 문제나 기타 예기치 않은 부작용(Side effects)을 방지하는 데 도움이 되기 때문이다.
VideoPlayer 객체를 통해 비디오를 띄울 곳이다. frame 모디파이어를 통해 크기 조절이 가능하다.
pause() 와 play() 는 각각 영상을 멈추고 재생하는 메서드고, seek(to: .zero) 이 친구는 영상을 0초 지점으로 되돌리는 메서드다.
비디오 플레이어 생성을 뒤로 미루기 위해 task 모디파이어를 사용한다. SwiftUI가 해당 뷰를 처음으로 나타낼 때, 딱 한 번만 플레이어를 생성하도록 보장하기 위함이다.
그리고 넣고 싶은 영상은 로컬 or 리모트 둘 다 url로 넣어주면 된다.
온보딩에서 VideoPlayer
앞에서 동영상을 뷰에 구현하는 것은 간단했다. 그러나 나는 온보딩 뷰에서 각각의 튜토리얼 동영상을 넣고싶고, 각각의 영상은 계속 반복되게 하고 싶다.
그러기 위해서는 3가지 객체를 알아야 한다.
바로 AVPlayerItem, AVQueuePlayer, AVPlayerLooper 이다.
1. AVPlayerItem : 이 친구는 영상(item) 하나 하나를 담고 있는 객체다.
2. AVQueuePlayer : 이 친구는 여러 영상(item)을 재생하는 플레이어다.
3. AVPlayerLooper : 특정 영상(item)을 계속 queue에 추가해 반복 구현 해주는 객체다.
비유하자면
AVPlayerItem → 재생할 노래파일 (MP3 파일)
↓
AVQueuePlayer → 플레이 리스트 (노래가 끝나면 다음 노래로 자동으로 넘어감)
↓
AVPlayerLooper → "한 곡 반복" 버튼 (재생 목록에 노래가 끝나면, 알아서 그 노래를 다시 재생 목록에 추가해줌)
음악 앱을 생각해보면 된다.
온보딩 예제
import SwiftUI
import AVKit
struct OnboardingView: View {
@State private var currentPage = 0
// 제미나이가 추천해준 url 샘플
let videoUrls = [
// 1. 토끼 애니메이션 (Big Buck Bunny)
URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4"),
// 2. 코끼리 꿈 애니메이션 (Elephants Dream)
URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4"),
// 3. 오픈 소스 영화 (For Bigger Blazes)
URL(string: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4")
].compactMap { $0 }
var body: some View {
TabView(selection: $currentPage) {
ForEach(0..<videoUrls.count, id: \.self) { index in
OnboardingVideoCell(
videoURL: videoUrls[index],
isCurrentPage: currentPage == index
)
.tag(index)
}
}
.tabViewStyle(.page(indexDisplayMode: .always))
.ignoresSafeArea()
}
}
먼저 온보딩 뷰에서는 제미나이한테 추천받은 url 샘플 3개와 함께 탭뷰로 3개의 페이지를 만들었다.
struct OnboardingVideoCell: View {
let videoURL: URL
let isCurrentPage: Bool
//1
@State private var player: AVQueuePlayer?
@State private var looper: AVPlayerLooper?
var body: some View {
ZStack {
if let player = player {
VideoPlayer(player: player)
.disabled(true) // 재생 컨트롤 숨김
.aspectRatio(contentMode: .fill)
}
}
//2
.task {
setupPlayer()
}
//3
.onChange(of: isCurrentPage) { _, newValue in
newValue ? player?.play() : player?.pause()
}
//4
.onDisappear {
player?.pause() // 화면에서 완전히 사라질 때 정지
}
}
//5
private func setupPlayer() {
// 1) 중복 생성 방지: 이미 있으면 또 만들지 않고 재생만 확인
guard player == nil else {
if isCurrentPage { player?.play() }
return
}
// 2) 플레이어 아이템(동영상) -> 큐플레이어(플레이리스트) -> 플레이어 루퍼(무한재생 도우미)
let item = AVPlayerItem(url: videoURL)
let queuePlayer = AVQueuePlayer(playerItem: item)
looper = AVPlayerLooper(player: queuePlayer, templateItem: item)
// 3) 할당 및 재생
self.player = queuePlayer
if isCurrentPage { queuePlayer.play() }
}
}
AVQueuePlayer과 AVPlayerLooper를 옵셔널로 둬서 .task에서 간접적으로 생성 해준다.setupPlayer)한다. 만약 영상을 다 불러오기 전에 사용자가 화면을 넘겨버리면, 진행 중이던 로딩 작업을 알아서 취소해 리소스를 아낀다.newValue == true) 재생, 옆으로 넘기면 즉시 정지시킨다.AVPlayerItem 로 동영상 하나를 담아두고 AVQueuePlayer 대기열에 추가시켜준다. 그 후 AVPlayerLooper로 플레이 리스트 안에 내가 지정한 영상 하나를 무한 반복시켜주는 것이다.마무리
오늘은 VideoPlayer로 앱에 동영상 넣는 예제를 해보았는데, 기억에 남는거라면 AVQueuePlayer 부분이다. Queue(큐)는 CS에서 나오는 개념일텐데 아무래도 다들 CS를 중요하게 생각하는 이유가 이런부분에서 배경지식이 있다면 쉽게 원리를 이해할 수 있어서가 아닐까 싶다. CS를 개발과 구분짓지말고 엮어서 볼 수 있는 시각을 길러나가야겠다.
🍎 참고