iOS 개발자 되기

[swift] 감동란 만들기로 알아보는 노래 재생

순진이 2022. 2. 16. 22:22

지난 번에 각 달걀 버튼을 누르면 아래 progressView가 진행되는 것까지 해봤쥬?

오늘은 정해진 달걀 요리 시간이 끝나면 노래가 재생되는 걸 만들어 볼게요!

생각보다 엄청 간단해요!!

 


사실 저걸 어떻게 하나 아주 걱정하고 있었는데, 쌤께서 스택오버플로우를 활용하라는 조언을 해주셨져. ㄱ ㄱ

(제법 간단했던 이유)

 

클릭 시 해당페이지로 이동

이 이미지는 stackoverflow 사이트는 아니지만, stackoverflow가 출처인 글이었어요. 그래서 이 코드를 고대로 에그타이머로 가져갔드랬죠?

 

 

1. 가장 먼저 해야할 작업은 import AVFoundation을 해주는 것입니다.

import AVFoundation

AVFoundation이라는 프레임 워크는 시청각 자료 작업, 장치 카메라 제어, 오디오 처리 및 시스템 오디오 상호 작용 구성 등의 작업을 한다고 하네요. 

 

 

애플 플랫폼에서 시청각 미디어의 캡처(capturing), 처리(processing), 합성(synthesizing), 제어(controlling), 가져오기(importing), 내보내기(exporting)의 주요 6가지 기술 영역을 결합하는 프레임워크래요!!

 

 


 

2. 그리고 viewDidLoad 위에 AVAudioPlayer 타입의 player 변수를 하나 선언해주고요.

var player: AVAudioPlayer?

AVAudioPlayer 클래스는 파일 또는 buffer에서 오디오 데이터를 재생하는 개체래요.

 


 

3. playSound 메서드 만들기

저는 playSound라는 메서드를 만들어서 위 코드를 붙여넣었습니당.

    @objc func playSound() {
        guard let url = Bundle.main.url(forResource: "After_the_Soft_Rains", withExtension: "mp3") else { return }
        
        do {
            try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
            try AVAudioSession.sharedInstance().setActive(true)
            
            /* The following line is required for the player to work on iOS 11. Change the file type accordingly*/
            player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)

            guard let player = player else { return }
            
            player.play()
            
        } catch let error {
            print(error.localizedDescription)
        }
    }

하나씩 공부해볼게요. 

 

1. url

guard let url = Bundle.main.url(forResource: "After_the_Soft_Rains", withExtension: "mp3") else { return }

Bundle이라는 클래스는 "디스크의 Bundle 디렉터리에 저장된 코드 및 리소스"라고 하네요. 우리가 저장하는 이미지나 사운드 등의 리소스를 말하져. 에그 타이머에서는 사운드가 되겠져?

 

리소스에 접근하기 위해서는 먼저 리소스를 포함하는 번들을 지정해야 하는데, 이 때 가장 자주 사용되는 생성자가 main생성자입니다. main 번들은 현재 실행 중인 코드가 포함된 Bundle 디렉토리를 나타내고, 이 main 번들을 통해 앱과 함께 제공된 리소스에 액세스할 수 있다네요.

그러면 main Bundle에서 음원파일을 어떻게 찾을까요?

바로 아래 url(forResource:withExtension:) 메서드를 통해서 찾아요.

 

forResource에서는 리소스 파일의 이름을, withExtension에는 리소스 파일의 확장자를 적어요.

파라미터와 리턴 타입이 옵셔널이기 때문에 guard let 바인딩을 통해 옵셔널 값을 벗겨줍니다.

이제 옵셔널이 벗겨진 값이 url 상수에 담겼겠죠?

 

 

2. AVAudioPlayer

다음으로 할 일은? 플레이어를 만들어주어야 해요.

플레이어는 위에서 AVAudioPlayer 클래스 타입으로 선언해주었져?

이제 아래 생성자를 이용해볼게요.

 

 

이 생성자는 throws가 있는 걸로 봐서 에러를 던질 수 있는 생성자인가봐요. 

그래서 우리는 do-catch문 안에서 사용을 해주어야 합니다.

 

do {
    try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
    try AVAudioSession.sharedInstance().setActive(true)
            
    player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
            
    guard let player = player else { return }
            
    player.play()
}

do-catch 문을 사용할 때는 try키워드를 잊어서는 안되겠져?

contentsOf 파라미터에는 바인딩한 url을 넣어주고, fileTypeHint에는 파일 형식의 UTI(Uniform Type Identifier) 문자열을 넣으래요.

 

 

에엥?? UTI(Uniform Type Identifier) 문자열이 뭔데?

위키피디아에서는 애플이 제공하는 소프트웨어에서 주어진 클래스나 아이템의 타입을 고유하게 식별하기 위해 사용하는 텍스트 문자열이라고 해요. 무슨 말인지 모르는 찰나에 

discussion을 보니, AVFileType을 참고하라네요. 고고

 

 

AVFileType 구조체는 다양한 파일 형식에 대한 통일된 타입 식별자래요.

파일 타입을 보니까, ac3, aifc, aiff 등 제가 모르는 형식부터 m4a, mp4, mp3 등 우리가 잘 아는 파일 포멧의 UTI 들이 타입 속성으로 선언이 되어 있군요.

 

이 중에서 mp3라는 타입 속성을 선택한 후, rawValue라는 생성자를 사용합니다. (원시 문자열 값에서 파일 형식을 만드는 생성자래요.)

 

player가 옵셔널 값이기 때문에 다시 한 번 guard let 바인딩을 사용해서 옵셔널 값을 벗겨줍니당.

그리고 player를 play해주면 끝!

 

3. AVAudioSession 

사실 이 부분이 제일 이해가 가지 않았는데요ㅠㅠ

AVAudioSession은 앱과 오디오 하드웨어 간의 중계자 역할이라고 합니다. 이 오디오세션만 있으면 하드웨어한테 어떤 설명 없이 앱의 오디오 특성을 알릴 수가 있는가봅니다. 

 

 

앱의 생명주기 동안 오디오 하드웨어와 앱과의 상호작용은 이 오디오 세션이 도맡아 하나봐요. 그래서 이 오디오세션을 통해 우리가 원하는 설정(?)을 하드웨어에게 알려줘야 하는거죠.

 

 

오디오세션은 싱글톤이기 때문에, 유일한 객체를 찍어내주고요.

오디오 세션은 기본적인 설정이 몇가지 있대요. (녹음 비허용/무음 모드에서 오디오 무음 등)

그 외에는 해당 범주를 설정해서 오디오 세션의 동작을 정의해주어야 해요.

여기서는 setCategory라는 메서드를 사용할거에요. 

try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)

 

 

playback은 가장 많이 사용되는 타입속성 중에 하나로, 앱의 성공적 사용에 중요한 녹음 음악이나 기타 사운드를 재생하는 범주래요. 이 놈으로 픽하고, mode는 default로 해줄게요.

 

 

그리고 마지막으로 오디오 세션을 활성해줍니다.

try AVAudioSession.sharedInstance().setActive(true)

 

이제 do문을 완성했으니, catch문을 완성해야겠죠? 

do {
    try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
    try AVAudioSession.sharedInstance().setActive(true)
            
    player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.mp3.rawValue)
            
    guard let player = player else { return }
            
    player.play()
} catch let error {
    print(error.localizedDescription)
}

 

(오디오세션은 곧 다시 공부해야겠어요 ㅠㅠ 넘나리 어려운 것 ㅠㅠ)


자 이제 이렇게 만든 메서드를 노래가 재생되야 할 곳에 심어볼게요.

 

언제냐?!

타이머 시간이 다 됐을 때!!

정해진 타이머 시간이 완료되면, mainLbl을 "다 됐다!"로 바꾸고, 노래가 재생되도록 할게요.

    @objc func updateTime() {
        
        if timePassed < totalTime {
            timePassed += 1
            let percentage = Float(timePassed) / Float(totalTime)
            progressBar.setProgress(percentage, animated: true)
            print(timePassed)
        } else {
            timer.invalidate()
            timePassed = 0
            playSound() // ⭐️
            mainLbl.text = "다 됐다!"
        }
    }

 

근데 다른 에그를 눌렀을 때도 계속 노래가 재생되면 곤란하겠져?

다른 버튼을 눌렀을 때는 노래가 멈추도록 설정도 해주어야 합니당.

    @objc func BtnTapped(_ sender: UIButton) {
        player?.stop() // ⭐️
        timer.invalidate()
        progressBar.progress = 0.0
        mainLbl.text = "달걀🥚삶기"
        
        // switch문 생략

        // timer 생략
    }

 

감동란 만들기로 알아본 Timer와 play sound 시간 끝!

틀린 부분 있으면 언제든 알려주세요 슨배님덜!! 감사합니다!