관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] 1. 오디오 처리 - AVPlayer, AVAudioPlayer 개념 본문

iOS 응용 (swift)

[iOS - swift] 1. 오디오 처리 - AVPlayer, AVAudioPlayer 개념

jake-kim 2022. 4. 29. 23:21

1. 오디오 처리 - AVPlayer, AVAudioPlayer 개념 (실시간 스트리밍, 로컬 파일 재생)

2. 오디오 처리 - AVAudioRecoder 개념 (녹음)

 

비교

  • 실시간 스트리밍 -> AVPlayer 사용
  • 로컬 파일 재생 -> AVAudioPlayer 사용
  • 음성 특수 효과 or 변조 -> AVAudioEngine 사용

cf) 녹음 -> AVAudioRecoder 사용

비동기 처리의 편의를 위해서 사용한 프레임워크

pod 'RxSwift'
pod 'RxCocoa'
pod 'RxAVFoundation'

커스텀 Rx extension

// Rx+Extension.swift

extension Reactive where Base: UIControl {
  public var isHighlighted: Observable<Bool> {
    self.base.rx.methodInvoked(#selector(setter: self.base.isHighlighted))
      .compactMap { $0.first as? Bool }
      .startWith(self.base.isHighlighted)
      .distinctUntilChanged()
      .share()
  }
  public var isSelected: Observable<Bool> {
    self.base.rx.methodInvoked(#selector(setter: self.base.isSelected))
      .compactMap { $0.first as? Bool }
      .startWith(self.base.isSelected)
      .distinctUntilChanged()
      .share()
  }
}

예제로 사용할 mp3 준비

  • 서버로부터 mp3의 url을 받아서 스트리밍 하고 싶은 경우 사용
  • 예제로 사용할 mp3 url 준비
// 출처: https://www.soundhelix.com/audio-examples
https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3
  • mp3파일을 다운받아서 Xcode에 이동

AVPlayer 사용 방법

  • 실시간 스트리밍에 사용되므로, mp3 url이 있으면 그 값을 가지고 플레이가 가능
  • AVPlayer는 AVPlayerItem이라는 것을 가지고 있고, AVPlayer가 이 item을 변경해가면서 url을 바꾸어서 사용
self.player.replaceCurrentItem(with: self.item)
  • item에 접근하여 오디오 파일의 전체 시간과 경과 시간을 획득
item.rx.status
  .filter { $0 == .readyToPlay }
  .observe(on: MainScheduler.asyncInstance)
  .map { _ in item.asset.duration.seconds }
  .bind { print("전체 시간 = \($0)")  }
  .disposed(by: self.itemDisposeBag)

self.player.rx
  .periodicTimeObserver(interval: .init(seconds: 0.5, preferredTimescale: Int32(NSEC_PER_SEC)))
  .map(\.seconds)
  .bind { print("경과 시간 = \($0)") }
  .disposed(by: self.disposeBag)
  • Player는 최초에 한 번 초기화 해놓고, url이 변경될때 이 값을가지고 item을 만들어서 player의 item을 변경해주는 방식으로 사용
// MyPlayerView.swift

private let player = AVPlayer()
var url: URL? {
  didSet {
    guard let url = self.url else { return self.player.replaceCurrentItem(with: nil) }
    
    let item = AVPlayerItem(url: url)
    item.rx.status
      .filter { $0 == .readyToPlay }
      .observe(on: MainScheduler.asyncInstance)
      .map { _ in item.asset.duration.seconds }
      .bind { print("전체 시간 = \($0)")  }
      .disposed(by: self.itemDisposeBag)

    self.player.rx
      .periodicTimeObserver(interval: .init(seconds: 0.5, preferredTimescale: Int32(NSEC_PER_SEC)))
      .map(\.seconds)
      .bind { print("경과 시간 = \($0)") }
      .disposed(by: self.disposeBag)
    
    self.player.replaceCurrentItem(with: item)
  }
}
  • play, stop 메소드
// MyPlayerView.swift

func play() {
  self.player.play()
}
func stop() {
  self.player.pause()
  self.player.seek(to: .zero)
}
  • 사용하는쪽
// ViewController.swift

private let playerView = MyPlayerView()

...

let url = URL(string: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-6.mp3")!
self.playerView.url = url

AVAudioPlayer 사용 방법

  • AVAudioPlayer는 로컬에 있는 mp3파일과 같은 것을 재생시키기 위함
    • 스트리밍 재생은 불가
  • AVPlayer와는 다르게 item을 변경해주면서 쓰지 않고, AVAudioPlayer 객체에 url을 넣어서 새로 생성하여 사용
    • delegate로 오류가났는지, 끝났는지 체크
// MyAudioPlayerView.swift

private var audioPlayer: AVAudioPlayer? {
  didSet {
    self.audioPlayer?.delegate = self
    self.audioPlayer?.prepareToPlay()
  }
}

extension MyAudioPlayerView: AVAudioPlayerDelegate {
  func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
    guard let error = error else { return }
    print("occured error = \(error)")
  }
  func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
    print("did finish playing")
  }
}
  • play할때 url을 가지고 AVAudioPlayer인스턴스를 생성하여, 재생
// MyAudioPlayerView.swift

func play() {
  guard let url = url else { return }
  self.audioPlayer = try? AVAudioPlayer(contentsOf: url)
  
  Observable<Int>
    .interval(.milliseconds(500), scheduler: MainScheduler.asyncInstance)
    .compactMap { [weak self] _ in self?.audioPlayer?.currentTime }
    .bind { print("경과 시간 = \($0)") }
    .disposed(by: self.playDisposeBag)
  
  self.audioPlayer?.play()
}
  • stop() 메소드
// MyAudioPlayerView.swift

private func stop() {
  self.audioPlayer?.pause()
  self.playerDisposeBag = DisposeBag()
}

* 전체 코드: https://github.com/JK0369/ExAudioPlayer

* 참고

https://github.com/pmick/RxAVFoundation

https://loganberry.tistory.com/20

https://etst.tistory.com/80

https://larztech.com/posts/2020/05/effects-avaudioengine/

https://nsios.tistory.com/124

 

Comments