Swift + iOS/Edwith-Boostcourse

[iOS BoostCourse] PJT1 - AVAudioPlayer

군옥수수수 2018. 4. 6. 23:31

[iOS BoostCourse] PJT1 - AVAudioPlayer


첫 번째 파트에서는 기본적인 iOS 개발에 앞서 필요한 iOS의 지식들과 개발에 관한 전반적인 지식을 배우는 시간을 가졌습니다. 그리고 첫 번째 프로젝트로 AVAudioPlayer 클래스를 사용하여 assets 폴더에 저장되어 있는 음원 파일을 재생하고 조절하는 기능을 제공하는 간단한 어플리케이션을 만들어보는 시간을 가졌습니다.


이번 포스팅에서는 프로젝트를 만들면서 알게 된 내용들 중 AVAudioPlayer에 대해 간략히 정리해보는 시간을 갖도록 하겠습니다.


이번 포스팅에서는 제가 공부하면서 새로 배운 내용들을 기록하기 때문에 스토리보드의 뷰 세팅부터 해서 모든 것들을 다루지는 않고 생략되는 부분들이 존재하기 때문에 보다 자세한 내용과 전체적인 내용은 Edwith 사이트를 참고해주세요.


AVAudioPlayer? AVPlayer?

저는 Remote 파일을 재생하기 위해서 AVPlayer를 사용했었습니다. AVPlayerAVAudioPlayer에는 몇 가지 차이점이 존재합니다. 가장 큰 차이점은 공식문서의 정의에서도 찾아볼 수 있습니다.


  • AVPlayer : You can use an AVPlayer to play local and remote file-based media, such as QuickTime movies and MP3 audio files, as well as audiovisual media served using HTTP Live Streaming.
  • AVAudioPlayer : An audio player that provides playback of audio data from a file or memory

AVPlayer는 로컬에 저장된 파일뿐만아니라 리모트에 저장된 파일을 스트리밍을 통하여 재생할 때 사용합니다. 반면 AVAudioPlayer는 로컬에 저장된 오디오 파일의 재생 기능만을 제공합니다. 또한 AVAudioPlayer는 관련 Delegate를 제공하지만 AVPlayer는 제공하지 않습니다. 더 자세한 내용은 다음 링크를 참고해주세요.



Initialize AVAudioPlayer

이제 본격적으로 AVAudioPlayer를 활용하여 에셋 카탈로그에 저장된 음원 파일을 가져와 재생시키는 방법을 시작으로 오디오 재생을 제어하는 방법을 정리해보도록 하겠습니다.


먼저 boostcourse에서 제공하는 에셋들을 여러분들 프로젝트의 에셋 카탈로그에 넣는 것으로 시작합니다. 저장된 오디오 파일을 가져와보도록 하겠습니다.


이에 앞서 저는 먼저 ViewController의 프로퍼티로 var player:AVAudioPlayer!var timer:Timer! 를 선언해주었습니다.


  1. NSDataAsset을 이용해 에셋 카탈로그 폴더에 접근하여 오디오 파일을 가져올 수 있습니다. 하지만 해당 파일이 존재하지 않을 수 있기 때문에 옵셔널의 형태로 반환됩니다. 그러므로 unwrapping 과정이 필요합니다.
  2. 그리고 가져온 오디오 파일 데이터를 재생할 AVAudioPlayer객체를 생성합니다. 하지만 이때 객체 생성의 실패 위험이 있기 때문에 반드시 예외처리를 해주어야 합니다.
  3. Delegate를 지정해줍니다. (Delegate는 제가 작성한 포스팅을 참고해주시기 바랍니다. Delegation Pattern)
  4. 이렇게 재생할 오디오 파일을 이용해 AVAudioPlayer 생성하면 해당 객체에는 해당 오디오 파일 데이터에 대한 전반적인 정보가 담기게 됩니다. 그 중 하나가 바로 음원의 총 재생 시간을 나타내는 duration입니다. AVAudioPlayer 객체의 duration 프로퍼티를 가져와 음원 재생의 진행 상태를 보여주는 UISlider의 최대 값에 할당하였습니다.

이렇게 재생시킬 파일을 AVAudioPlayer에 할당을 하고 해당 파일의 전체 재생 시간을 슬라이더에 할당해주었습니다. 이젠 기본적인 조작에 대해 알아보도록 하겠습니다.


Play & Pause AVAudioPlayer

저는 재생과 정지를 하기 위한 버튼을 코드로 만들었고 이렇게 만들어진 버튼에 addTarget(:action:for:)를 이용하여 액션을 지정해주었습니다.


그리고 다음과 같이 액션 메소드를 작성해주었습니다.


먼저 버튼을 만들어주는 코드에서 주목해야할 것은 버튼의 상태에 따라 다른 이미지를 보여주기 위해 상태별로 다른 이미지를 설정한 것입니다. 그리고 버튼이 눌렸을 때의 코드를 확인해보도록 하겠습니다. 여기서 이번에 처음 알게 된 것이 있습니다.


그것은 바로 버튼의 isSelected 속성은 버튼이 눌렸는지 눌리지 않았는지에 상관없이 항상 false라는 것입니다. 저는 버튼을 한번 탭하면 isSelectedtrue가 되고 다시 누르면 isSelectedfalse가 되는줄 알았습니다. 하지만 그렇지 않고 이러한 상태의 변화에 대해서는 개발자가 직접 작성해주어야 했습니다.


이를 바탕으로 위의 코드에 설명을 덧붙이자면 기본적으로 어플리케이션이 실행되면 음악은 자동으로 재생되는 것이 아니라 재생 버튼을 눌렀을 때 재생됩니다. 그러므로 최초로 버튼이 눌리면 아직은 isSelectedfalse이고 그의 반대인 true를 할당해줍니다. 그리고 조건문에 따라 isSelectedtrue가 되었으므로 AVAudioPlayer객체의 내장 메소드인 play()를 호출하여 오디오 파일을 재생합니다.


그리고 다시 한번 정지를 위해 버튼을 누르면 true가 담긴 isSelected에 그 반대인 false를 담습니다. 그리고 다시 한번 조건문을 통해 isSelectedfalse이므로 파일 재생을 정지하는 내장 메소드인 pause()를 호출하여 오디오 파일 재생을 정지시킵니다.


이것이 예제 코드에서 사용된 isSelected를 활용한 간단한 재생-정지 알고리즘이였습니다. 하지만 저는 약간 다르게 구현하였었습니다.


AVAudioPlayer객체에는 현재 담긴 파일이 재생 중인지 아닌지를 참거짓으로 반환해주는 isPlaying 프로퍼티가 존재합니다. 그래서 저는 해당 프로퍼티를 이용하여 해당 버튼 기능을 구현하였습니다.


Setup Timer

이젠 재생 중 조작에 대한 코드를 작성할 차례입니다. 하지만 그전에 앞서 먼저 해당 기능을 구현하려면 Timer를 설정해주어야 합니다. 우리의 어플리케이션은 전체 재생 시간에 대한 현재 시간을 UILabel에 출력해주고 UISlider는 이를 트래킹해야합니다. 그리고 이런 행위는 조건에서와 같이 밀리세컨드 단위로 이루어져야 합니다.


조건에서와 마찬가지로 밀리세컨드 단위로 UISliderUILabel을 갱신해주어야 하기 때문에 withTimeInterval 은 0.01이 됩니다. 그리고 이러한 행위는 반복되어야 하기 때문에 repeatstrue입니다. 그리고 block 에는 0.01초마다 해주어야할 작업들을 위시키게 됩니다. 이곳에 UISliderUILabel을 갱신시켜주는 코드가 들어가야겠죠?


[unowned self] 에 대해서는 제가 이전에 작성한 [Swift] Retain cycle, weak, unowned [번역] 글을 참고해주세요.

마지막으로 반드시 fire() 메소드를 통해 타이머를 시작해주어야 합니다 또한 타이머의 역할이 모두 끝나면 반드시 invalidate를 통해 해제시켜주어야 합니다.


그리고 이렇게 Timer를 시작하고 해제시키는 작업은 버튼이 눌렸을 때도 들어가야합니다.


재생이 되는 동안안에는 타이머를 시작하고 정지되어 있는 상태에서는 타이머를 해제시켜주어야 합니다.


Update Current Time Value

이 어플리케이션에서 오디오 파일이 재생되는 동안 갱신되어야 하는 것은 두 가지입니다. 현재 트랙 시간을 보여주는 UILabel과 현재 트랙 시간에 따른 진행 상황을 보여주는 UISlider 입니다. 또한 사용자가 UISlider를 조작하여 원하는 트랙 시간으로 갈 수도 있어야 합니다. 코드는 다음과 같습니다.


먼저 UISlider는 기본적으로 .valueChanged 행위에 대해 액션 메소드를 호출합니다. 슬라이더의 동그라미 모양의 컨트롤러를 조작하여 값을 바꾸는 행위에 대한 이벤트를 말합니다.


updateCurrentTimeLabel은 매개변수로 넘어오는 현재 시간을 원하는 포멧으로 바꾸어 UILabel을 갱신시켜주는 메소드입니다. 현재 시간으로 넘어오는 값은 초 단위이고 부동 소수점으로 넘어오게 됩니다. minute을 구하는 코드는 이해가 갔지만 secondmiliesecond를 구하는 코드는 낯설었습니다.


하지만 truncatingRemainder를 이해하고 값을 하나씩 출력해보면 이해가 가실 겁니다. 기본적으로 부동 소숫점을 나눌 때 나머지를 구하기 위해서는 truncatingRemainder를 사용해야합니다. 그리고 값을 하나씩 출력해보시면 다음과 같은 출력 결과를 확인하실 수 있으실 겁니다.

time : 1.59609977324263 milliesecond : 59
time : 1.60598639455782 milliesecond : 60
time : 1.61634920634921 milliesecond : 61
time : 1.62637188208617 milliesecond : 62
time : 1.63546485260771 milliesecond : 63
time : 1.64630385487528 milliesecond : 64
time : 1.65628117913832 milliesecond : 65
time : 1.66551020408163 milliesecond : 66

즉 소숫점 두자리까지 구하기 위해 먼저 부동 소수점의 형태로 넘어오는 현재 시간의 초 값을 1로 나누고 이에 100을 곱하는 것입니다. 그리고 적절한 포맷의 형태로 문자열의 형태로 바꾸어주어 현재 진행 시간을 나태나는 currentTimeLabel에 할당해줍니다.


그리고 마지막으로 sliderValueChanged 메소드를 살펴보도록 하겠습니다. 슬라이더를 움직여 원하는 재생 구간으로 이동할 때 슬라이더의 값에 따라 currentTimeLabel을 갱신해줍니다.


 if sender.isTracking {return}의 코드가 들어가는 이유는 슬라이더를 움직이는 동안은 재생되는 구간이 바뀌지 않고 슬라이더의 움직임을 멈추었을 때 비로소 해당 위치를 재생하게하므로 움직이는 동안 음원이 끊기는 현상을 막기 위함입니다. 


그리고 슬라이더의 움직임을 멈추었을 때 currentTime의 값을 슬라이더 값에 맞추어 할당해주므로 원하는 구간으로의 이동이 가능해집니다.


Implementation

이젠 비어있던 부분을 위에서 작성된 메소드들과 기능에 따라 채워보고 나머지 필요한 메소드들을 작성해보도록 하겠습니다.


위에서 작성한 setupTimer 메소드입니다. 0.01초마다 행해지는 행위를 block에 명시해주어야 합니다. 슬라이더를 움직일 때는 값들을 밀리새컨드 단위로 갱신하지 않고 슬라이더 값에 따라 갱신하기 위해 if self.progressSlider.isTracking {return} 코드를 넣어주었고 정상적으로 재생되는 동안에는 밀리세컨드 단위로 currentTimeLabelprogressSlider값을 갱신해주었습니다.


그리고 재생이 끝나면 이루어져야 할 작업들을 AVAudioPlayerDelegate 프로토콜이 제공하는 파일의 재생이 끝나면 호출되는 audioPlayerDidFinishPlaying(_,successfully:) 메소드에 명시해줍니다.


마지막으로 다시 한번 메소드가 실행되는 순서대로 코드들을 살펴보면 작동 원리를 파악하실 수 있으실 겁니다.


마무리

이렇게 오늘은 로컬에 저장된 오디오 파일을 다루는 객체인 AVAudioPlayer에 대해 알아보았고 오디오 파일 재생을 조작하는 간단한 행위에 대한 코드들을 살펴보았습니다. AVPlayer는 다뤄봐서 AVAudioPlayer 를 다루는데는 큰 어려움이 없었으나 새로 알게 된 내용들이 있어 이렇게 긴 글로 기록하게 되었습니다. 반드시 본인이 직접 코드를 작성해가며 하나씩 분석해보아야 도움이 되니 스스로 진행해보시는 것을 추천드립니다. 

전체 코드는 아래의 링크에 남겨놓겠습니다. 감사합니다.


Source : github