나는 재생/일시정지/이전, 다음곡 재생/재생 위치 변경 의 기본적인 스트리밍 혹은 음악 재생 앱들이 사용하는 기능들을 썼다.
//싱글톤 객체 참조 변수 선언
let center = MPRemoteCommandCenter.shared()
//playButton 음악 재생
center.playCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.playSong()
return .success
}
//pauseButton 음악 일시정지
center.pauseCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.pauseSong()
return .success
}
//nextTrackButton 다음곡 재생
center.nextTrackCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.playNext()
return .success
}
//prevTrackButton 이전곡 재생
center.previousTrackCommand.addTarget { (_) -> MPRemoteCommandHandlerStatus in
self.playPrev()
return .success
}
//slider 재생 위치 변경
center.changePlaybackPositionCommand.addTarget { (event) -> MPRemoteCommandHandlerStatus in
if let positionTime = (event as? MPChangePlaybackPositionCommandEvent)?.positionTime {
let seekTime = CMTime(value: Int64(positionTime), timescale: 1)
self.player.seek(to: seekTime)
}
return .success
}
Queue의 items() 라는 메소드를 통해 Queue 대기열 아이템(AVPlayerItem)을 받아올 수 있는데, 스트리밍의 경우 보안을 위해 URL을 무한대로 사용할 수 없는 구조도 있다고 한다. 따라서, AVQueuePlayer보다는 AVPlayer를 사용하여 직접 Queue 구현을 했다.
replaceCurrentItem(with: AVPlayerItem)을 사용하여 현재 관리하는 하나의 미디어를 관리할 수 있다.
AVPlayer 상태 변경 Observe
Delegate가 없나보다. AVAudioPlayer에는 있던 것 같은데... 그렇다고 AVAudioPlayer를 쓰자니 버그인지 내가 모르는 건지 안되는 부분들이 있더라...
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...)
딱 경계시간에서 호출되진 않고, 가까운 지점에서 호출될 수도 있고, 호출되지 않을 때도 있다고함
유투브 영상 볼 때, 광고에 사용되는 느낌!
두 메소드를 이용하여 주기적/경계별로 시간 변경을 관찰할 수 있다.
나는 현재 음악이 얼마나 재생되었는지 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 사용할 때보다는 진짜 진짜 진짜 훨씬 쉬웠다...
정상적으로 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()는 오디오를 재생할 때까지 미루는 것이 좋다고 한다.