Mellon이나 Apple Music과 같이
audio를 재생시키는 앱은 어떻게 구현되어 있을까 라는 궁금증이 생겼습니다.
iOS 앱에서 audio를 재생시키는 방법은 꽤 많았는데요.
오늘은 그중 하나인 AVPlayer
에 대해 소개해볼까 합니다.
먼저 AVPlayer
를 알아보기 전에
AVFoundation
에 대해 알고 가면 좋을 것 같아서
간단하게 짚고 넘어가 보겠습니다.
Apple의 미디어 Framework인 AVFoundation
은
지금 설명하려는 audio playback 기능 말고도
많은 미디어 처리 작업을 제공합니다.(video, 카메라, 편집 등)
또한 이 Framework를 사용하여
HTTP Live Streaming을 통해
실시간으로 mp3파일을 재생하고 제어할 수 있다고 합니다.
하지만 AVFoundation
은 하단 이미지와 같이
UIKit Framework보다 Low한 level에 위치해 있어
재생과 관련된 UI를 제공해주지 않고 있습니다.
그래서 Apple은 player Interface를 제공할 수 있고,
보다 빠른 player의 기능 구현을 진행할 수 있는
AVKit
Framework를 UIKit 상단에 마련해 두었습니다.
그럼 AVKit
의 AVPlayerViewController에 대해서 알아볼까요?
아래는 AVKit
내부에 있는 AVPlayerViewController를 사용해서
player를 구현하는 방법입니다.
@objc private func didTapButton() {
var player: AVPlayer?
let playerController = AVPlayerViewController()
guard let url = URL(string: "https://......") else {
return
}
player = AVPlayer(url: url)
playerController.player = player
playerController.player?.play()
present(playerController, animated: true, completion: nil)
}
개발자가 직접 UI에 관여하지 않아도
오디오 제어에 대해 접근성이 굉장히 뛰어난걸 확인할 수 있습니다.
하지만 대부분 player를 구현할 때 이런 심플한 모습은 원하지 않았을 것입니다.😂
그렇다면 AVPlayerViewController에서 제공하고 있는 인터페이스를 직접 구현하는 방법은 어떤게 있을까요?
위에서 잠깐 설명 드렸던
보다 low한 level인 AVFoundation
Framework의
AVPlayer를 사용하여 audio를 재생시키고,
의도하고자 하는 UI로 player를 커스텀하면 될 것 같은데요!
그럼 AVPlayer에 대해 알아보겠습니다.
일단 기능구현에 앞서
mp3 URL, button, slider를 준비하고,
그리고 AVPlayer와 AVPlayerItem을 만들도록 하겠습니다.
let player = AVPlayer()
var playerItem: AVPlayerItem?
그리고 stream을 진행할 mp3 URL을 사용하여
AVPlayerItem을 만들고
player의 playerItem으로 할당 해주도록 하겠습니다.
guard let url = URL(string: "https:......") else {
return
}
playerItem = AVPlayerItem(url: url)
player.replaceCurrentItem(with: playerItem)
다음은 버튼을 터치했을때 Action을 정의 해주도록 하겠습니다.
이제 버튼을 터치했을 때 해당 URL이 가진 음원이 재생되는 걸 확인할 수 수 있을 것입니다.
@objc private func didTapButton() {
// timeControlStatus에 따른 분기
if player.timeControlStatus == .paused {
player.play()
} else {
player.pause()
}
button.isSelected.toggle()
}
총 재생 시간 표기와 currentTimeLabel, currentTimeSlider의 value set은 아래와 같습니다.🙌
// duration(총 재생 시간)
guard let durationTime = self?.player.currentItem?.duration else { return }
let totalSeconds = Int(CMTimeGetSeconds(durationTime))
let seconds = totalSeconds % 60
let minutes = totalSeconds / 60
let timeFormatString = String(format: "%02d:%02d", minutes, seconds)
self?.durationLabel.text = timeFormatString
// 미디어의 재생시간의 흐름을 알고 싶거나 현재 재생시간에 따라 인터페이스를 변경하고 싶을 때
// time은 CMTime 타입의 값으로, 입력한 interval 주기를 가진채로 completion으로 나오게 됩니다.
let interval = CMTimeMakeWithSeconds(1, preferredTimescale: Int32(NSEC_PER_SEC)) // 1초
player.addPeriodicTimeObserver(forInterval: interval, queue: .main) { [weak self](time) in
// update currentLabel
let totalSeconds = Int(CMTimeGetSeconds(time))
let seconds = totalSeconds % 60
let minutes = totalSeconds / 60
let timeFormatString = String(format: "%02d:%02d", minutes, seconds)
self?.currentTime.text = timeFormatString
// update slider
let currentTimeSeconds = CMTimeGetSeconds(self?.player.currentTime()) // 현재 진행 위치
let durationSeconds = CMTimeGetSeconds(self?.player.currentItem?.duration ??
CMTimeMake(value: 1, timescale: 1)) // 총 길이
let percentage = currentTimeSeconds / durationSeconds // 현재 위치 / 총 길이
self?.songSlider.value = Float(percentage) // set slider value
}