AVPlayer
- 미디어를 관리하는 데에 사용되는 컨트롤러 Object
- Video, MP3와 같은 오디오 파일을 재생 가능
- Local과 Remote File 기반, HTTP을 통한 Streaming 가능
- 한 번에 하나의 미디어를 재생할 수 있음
- AVQueuePlayer라는 하위 클래스를 사용하여 대기열을 만들고 관리할 수 있다.
- Queue의 items() 라는 메소드를 통해 Queue 대기열 아이템(AVPlayerItem)을 받아올 수 있는데,
스트리밍의 경우 보안을 위해 URL을 무한대로 사용할 수 없는 구조도 있다고 한다.
따라서, AVQueuePlayer보다는 AVPlayer를 사용하여 직접 Queue 구현을 했다.
- Queue의 items() 라는 메소드를 통해 Queue 대기열 아이템(AVPlayerItem)을 받아올 수 있는데,
- replaceCurrentItem(with: AVPlayerItem)을 사용하여 현재 관리하는 하나의 미디어를 관리할 수 있다.
AVPlayer 상태 변경 Observe
Delegate가 없나보다. AVAudioPlayer에는 있던 것 같은데... 그렇다고 AVAudioPlayer를 쓰자니 버그인지 내가 모르는 건지 안되는 부분들이 있더라...
상태 관찰
KVO 를 사용해서 상태 변화를 알아챌 수 있다.
Main Thread에서 등록/해제 해야함(다른 Thread에서 해도 MainThread에서 호출한다고 한다.)
currentItem, playback rate 등 Observe 가능
observeValue(forKeyPath:of:change:context:) -> Observe 메소드이다. (KVO는 옵씨 런타임에 의존한다고해서 Swift로 개발할 경우에는 잘 안쓴다고 한다.)
나는 현재 재생중인 곡이 끝났는지 확인을 위해 Status Observing이 필요했다.
KVO에 익숙치도 않고 다른 로직들도 NotificationCenter로 작성되어 있어서, NotificationCenter를 이용하는 방법을 찾아보니 방법이 있었다.
NSNotification.Name.AVPlayerItemDidPlayToEndTime 이 있더라, 그래서 썼다..
//NotificationCenter 이용
NotificationCenter.default.addObserver(self, selector: #selector(playingMusicFinish(_:)), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
@objc func playingMusicFinish(_ notification: Notification) {
//필요한 정보나 객체가 있으면 object를 통해서 받아서 이용하면 된다.
//이 메소드는 현재 진행중인 PlayerItem이 EndTime에 도달하면 호출된다.
}
시간 상태 관찰
시간은 지속적으로 변하기에 KVO는 부적합하다고 한다. (이부분에 대해서는 따로 공부가 필요할 듯 KVO...)
AVPlayer는 시간 변화를 관찰하기 위해 2가지 메소드를 제공한다.
- addPeriodicTimeObserver(forInterval:queue:using:)
- forInterval 마다
- queue 에서
- using 을 동작시킨다.
- addBoundaryTimeObserver(forTimes:queue:using:)
- forTimes 에
- queue에서
- using을 동작
- 딱 경계시간에서 호출되진 않고, 가까운 지점에서 호출될 수도 있고, 호출되지 않을 때도 있다고함
- 유투브 영상 볼 때, 광고에 사용되는 느낌!
두 메소드를 이용하여 주기적/경계별로 시간 변경을 관찰할 수 있다.
나는 현재 음악이 얼마나 재생되었는지 UISlider에 표시하기 위해 사용했다.
func addPeriodicTimeObserver(closurePerInterval: @escaping (Float) -> Void) {
let time = CMTime(seconds: 0.01, preferredTimescale: CMTimeScale(NSEC_PER_SEC))
MusicPlayer.shared.player.addPeriodicTimeObserver(forInterval: time, queue: .main) { progressTime in
//현재 진행된 progressTime을 '초'로 변경
let seconds = CMTimeGetSeconds(progressTime)
//00:00 할 때, '초'/'분'
// let secondsString = String(format: "%02d", Int(seconds) % 60)
// let minutesString = String(format: "%02d", Int(seconds / 60))
if let duration = MusicPlayer.shared.player.currentItem?.duration{
let durationSeconds = CMTimeGetSeconds(duration)
//UISlider에 적용하기 위해 비율로 변경
closurePerInterval(Float(seconds / durationSeconds))
}
}
}
addPeriodicTimeObserver { [weak self] currentDuration in
guard let self = self else { return }
self.slider.value = currentDuration
}
음악이 재생되면서 정삭적으로 0.01초 마다 UISlider의 Track이 이동하는 것을 볼 수 있다.
Background Mode
이전에 Location, Fetch, Processing 사용할 때보다는 진짜 진짜 진짜 훨씬 쉬웠다...
do{
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
}catch{
print(error.localizedDescription)
}
정상적으로 background에서도 Audio가 들린다. (실 기기에서는 문제없는데, Simulator로 할 때 노이즈가 낄 때가 있는데 무슨 문제인지는 모르겠다!)
Note
You can activate the audio session at any time after setting its category, but it’s generally preferable to defer this call until your app begins audio playback. Deferring the call ensures that you won’t prematurely interrupt any other background audio that may be in progress.
언제든지 카테고리를 설정해도 된다. 하지만 setActive()는 오디오를 재생할 때까지 미루는 것이 좋다고 한다.
'iOS' 카테고리의 다른 글
[iOS] Animation - 2 (feat. Frame-Based Layout vs AutoLayout) (0) | 2021.03.25 |
---|---|
[iOS] MPNowPlayingInfoCenter/MPRemoteCommandCenter 를 통해 백그라운드 미디어 재생 관리 (0) | 2021.03.23 |
[iOS] UITableView 선택/삽입/삭제/이동 (0) | 2021.03.18 |
[iOS] Animation - 1 (0) | 2021.03.17 |
[iOS] Navigation BackButton 조작 + NavigationItem vs NavigationBar (0) | 2021.03.11 |