Do it! 스위프트로 아이폰 앱 만들기
13장 음악 재생하고 녹음하기
- 'Audio Player' 레이블 배치하기
- Progress View 를 그 아래에 배치하기
- Progress View 아래에 시간을 나타내기 위한 레이블 2개 배치하기
- 'Play', 'Pause', 'Stop' 3개의 버튼 배치하기
- 'Volume' 레이블 배치하기
- Slider 를 그 오른쪽에 배치하기
- 가로로 나란히 배치된 컴포넌트들을 다중 선택한 후 메뉴에서
[Editor] -> [Embed In] -> [Stack View] 를 통해 묶어주기 (가로 스택 뷰)- 모든 컴포넌트를 선택하여 같은 방식으로 묶어주기 (세로 스택 뷰)
- 세로 스택 뷰를 선택하고 [Align] -> [Horizontally in Container] 체크하여 수평 가운데 정렬 적용하기
- [Add New Constrains] 에서 위 간격을 40으로 설정하기
- Progress View 을 선택하고 [Add New Constraints] -> [Width] = 280 설정하기
- 'currentTime', 'endTime' 을 선택하고 [Add New Constraints] -> [Width] = 100 설정하기
- 각 레이블의 [Size inspector] 에서 Width 의 부호를 [>=] 로 변경하기
- Slider 를 선택하고 [Add New Constraints] -> [Width] = 230 설정하기
- [Size inspector] 에서 Width 의 부호를 [>=] 로 변경하기
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var audioPlayer: AVAudioPlayer!
var audioFile: URL!
let MAX_VOLUME: Float = 10.0
var progressTimer: Timer!
@IBOutlet var pvProgressPlay: UIProgressView!
@IBOutlet var lblCurrentTime: UILabel!
@IBOutlet var lblEndTime: UILabel!
@IBOutlet var btnPlay: UIButton!
@IBOutlet var btnPause: UIButton!
@IBOutlet var btnStop: UIButton!
@IBOutlet var slVolume: UISlider!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
audioFile = Bundle.main.url(forResource: "Sicilian_Breeze", withExtension: "mp3")
initPlay()
}
func initPlay() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile)
} catch let error as NSError {
print("Error-initPlay : \(error)")
}
slVolume.maximumValue = MAX_VOLUME
slVolume.value = 1.0
pvProgressPlay.progress = 0
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.volume = slVolume.value
}
- prepareToPlay()
- 오디오 파일을 로드하고 초기화 작업을 수행하는 함수
- 오디오 데이터가 메모리에 사전 캐시되도록 한다.
- 볼륨 조절, 반복 모드 설정 등의 환경 설정이 가능하다.
- 버퍼링을 처리하여 지연 없이 연속적인 재생이 가능하게 한다.
func initPlay() {
...
lblEndTime.text = convertNSTimeInterval2String(audioPlayer.duration)
lblCurrentTime.text = convertNSTimeInterval2String(0)
}
func convertNSTimeInterval2String(_ time:TimeInterval) -> String {
let min = Int(time/60)
let sec = Int(time.truncatingRemainder(dividingBy: 60))
let strTime = String(format: "%02d:%02d", min, sec)
return strTime
}
func initPlay() {
...
setPlayButtons(true, pause: false, stop: false)
}
func setPlayButtons(_ play:Bool, pause:Bool, stop:Bool) {
btnPlay.isEnabled = play
btnPlay.isEnabled = pause
btnStop.isEnabled = stop
}
@IBAction func btnPlayAudio(_ sender: UIButton) {
audioPlayer.play()
setPlayButtons(false, pause: true, stop: true)
}
@IBAction func btnPauseAudio(_ sender: UIButton) {
audioPlayer.pause()
setPlayButtons(true, pause: false, stop: true)
}
@IBAction func btnStopAudio(_ sender: UIButton) {
audioPlayer.stop()
setPlayButtons(true, pause: false, stop: false)
}
class ViewController: UIViewController, AVAudioPlayerDelegate {
...
let timePlayerSelector:Selector = #selector(ViewController.updatePlayTime)
...
@IBAction func btnPlayAudio(_ sender: UIButton) {
...
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timePlayerSelector, userInfo: nil, repeats: true)
}
@objc func updatePlayTime() {
lblCurrentTime.text = convertNSTimeInterval2String(audioPlayer.currentTime)
pvProgressPlay.progress = Float(audioPlayer.currentTime/audioPlayer.duration)
}
@IBAction func btnStopAudio(_ sender: UIButton) {
audioPlayer.stop()
audioPlayer.currentTime = 0
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(true, pause: false, stop: false)
progressTimer.invalidate()
}
@IBAction func slChangeVolume(_ sender: UISlider) {
audioPlayer.volume = slVolume.value
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
progressTimer.invalidate()
setPlayButtons(true, pause: false, stop: false)
}
}
- [Play] 버튼을 누르면 0.1초마다 타이머가 구동되고, 구동될 때마다 updatePlayTime() 함수가 실행된다.
- [Stop] 버튼을 누르거나 오디오 재생이 끝나면 타이머를 무효화시킨다.
- 'Record' 버튼과 시간 레이블에 대한 아웃렛 변수 추가하기
- Switch 와 'Record' 버튼에 대한 액션 함수 추가하기
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
...
var audioRecorder: AVAudioRecorder!
var isRecordMode = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
selectAudioFile()
if !isRecordMode {
initPlay()
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
}
else {
initRecord()
}
}
func selectAudioFile() {
if !isRecordMode {
audioFile = Bundle.main.url(forResource: "Sicilian_Breeze", withExtension: "mp3")
}
else {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
audioFile = documentDirectory.appendingPathComponent("recordFile.m4a")
}
}
func initRecord() {
let recordSettings = [
AVFormatIDKey: NSNumber(value: kAudioFormatAppleLossless as UInt32), //포맷
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue, //최대 음질
AVEncoderBitRateKey: 320000, //비트율
AVNumberOfChannelsKey: 2, //오디오 채널
AVSampleRateKey: 44100.0] as [String : Any] //샘플률
do {
audioRecorder = try AVAudioRecorder(url: audioFile, settings: recordSettings)
} catch let error as NSError {
print("Error-initRecord : \(error)")
}
audioRecorder.delegate = self
slVolume.value = 1.0
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(0)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(false, pause: false, stop: false)
let session = AVAudioSession.sharedInstance()
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print(" Error-setCategory : \(error)")
}
do {
try session.setActive(true)
} catch let error as NSError {
print(" Error-setActive : \(error)")
}
}
- isRecordMode 값에 따라 audioFile 과 초기화를 다르게 설정하기
- 녹음 모드라면 문서 디렉토리에 새로운 파일 생성하기
- 일반적으로 앱은 하나의 문서 디렉토리를 가지므로 첫번째 요소가 사용된다.
- AVAudioSession: iOS 에서 오디오 관련 작업을 제어하기 위한 인터페이스
- 앱에서 오디오를 재생하거나 녹음하려면 AVAudioSession 인스턴스를 사하여 앱의 오디오 동작 환경을 구성해야 한다.
- sharedInstance(): 앱 전체에서 동일한 오디오 세션 설정에 접근할 수 있도록 해준다.
- setCategory(): 앱이 주로 오디오 재생에 사용될 것임을 나타낸다.
- setActive(): 오디오 세션을 활성화하고 다른 앱이나 시스템과 충돌하지 않도록 한다.
@IBAction func swRecordMode(_ sender: UISwitch) {
if sender.isOn {
audioPlayer.stop()
audioPlayer.currentTime = 0
lblRecordTime!.text = convertNSTimeInterval2String(0)
isRecordMode = true
btnRecord.isEnabled = true
lblRecordTime.isEnabled = true
}
else {
isRecordMode = false
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
lblRecordTime.text = convertNSTimeInterval2String(0)
}
selectAudioFile()
if !isRecordMode {
initPlay()
}
else {
initRecord()
}
}
@IBAction func btnRecord(_ sender: UIButton) {
if (sender as AnyObject).titleLabel?.text == "Record" {
audioRecorder.record()
(sender as AnyObject).setTitle("Stop", for: UIControl.State())
}
else {
audioRecorder.stop()
(sender as AnyObject).setTitle("Record", for: UIControl.State())
btnPlay.isEnabled = true
initPlay()
}
}
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
...
let timeRecordSelector:Selector = #selector(ViewController.updateRecordTime)
...
@IBAction func btnRecord(_ sender: UIButton) {
if (sender as AnyObject).titleLabel?.text == "Record" {
audioRecorder.record()
(sender as AnyObject).setTitle("Stop", for: UIControl.State())
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timeRecordSelector, userInfo: nil, repeats: true)
}
else {
audioRecorder.stop()
progressTimer.invalidate()
(sender as AnyObject).setTitle("Record", for: UIControl.State())
btnPlay.isEnabled = true
initPlay()
}
}
@objc func updateRecordTime() {
lblRecordTime.text = convertNSTimeInterval2String(audioRecorder.currentTime)
}
}
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
var audioPlayer: AVAudioPlayer!
var audioFile: URL!
let MAX_VOLUME: Float = 10.0
var progressTimer: Timer!
let timePlayerSelector:Selector = #selector(ViewController.updatePlayTime)
let timeRecordSelector:Selector = #selector(ViewController.updateRecordTime)
@IBOutlet var pvProgressPlay: UIProgressView!
@IBOutlet var lblCurrentTime: UILabel!
@IBOutlet var lblEndTime: UILabel!
@IBOutlet var btnPlay: UIButton!
@IBOutlet var btnPause: UIButton!
@IBOutlet var btnStop: UIButton!
@IBOutlet var slVolume: UISlider!
@IBOutlet var btnRecord: UIButton!
@IBOutlet var lblRecordTime: UILabel!
var audioRecorder: AVAudioRecorder!
var isRecordMode = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
selectAudioFile()
if !isRecordMode {
initPlay()
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
}
else {
initRecord()
}
}
func selectAudioFile() {
if !isRecordMode {
audioFile = Bundle.main.url(forResource: "Sicilian_Breeze", withExtension: "mp3")
}
else {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
audioFile = documentDirectory.appendingPathComponent("recordFile.m4a")
}
}
func initRecord() {
let recordSettings = [
AVFormatIDKey: NSNumber(value: kAudioFormatAppleLossless as UInt32), //포맷
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue, //최대 음질
AVEncoderBitRateKey: 320000, //비트율
AVNumberOfChannelsKey: 2, //오디오 채널
AVSampleRateKey: 44100.0] as [String : Any] //샘플률
do {
audioRecorder = try AVAudioRecorder(url: audioFile, settings: recordSettings)
} catch let error as NSError {
print("Error-initRecord : \(error)")
}
audioRecorder.delegate = self
slVolume.value = 1.0
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(0)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(false, pause: false, stop: false)
let session = AVAudioSession.sharedInstance()
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print(" Error-setCategory : \(error)")
}
do {
try session.setActive(true)
} catch let error as NSError {
print(" Error-setActive : \(error)")
}
}
func initPlay() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile)
} catch let error as NSError {
print("Error-initPlay : \(error)")
}
slVolume.maximumValue = MAX_VOLUME
slVolume.value = 1.0
pvProgressPlay.progress = 0
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(audioPlayer.duration)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(true, pause: false, stop: false)
}
func setPlayButtons(_ play:Bool, pause:Bool, stop:Bool) {
btnPlay.isEnabled = play
btnPause.isEnabled = pause
btnStop.isEnabled = stop
}
func convertNSTimeInterval2String(_ time:TimeInterval) -> String {
let min = Int(time/60)
let sec = Int(time.truncatingRemainder(dividingBy: 60))
let strTime = String(format: "%02d:%02d", min, sec)
return strTime
}
@IBAction func btnPlayAudio(_ sender: UIButton) {
audioPlayer.play()
setPlayButtons(false, pause: true, stop: true)
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timePlayerSelector, userInfo: nil, repeats: true)
}
@objc func updatePlayTime() {
lblCurrentTime.text = convertNSTimeInterval2String(audioPlayer.currentTime)
pvProgressPlay.progress = Float(audioPlayer.currentTime/audioPlayer.duration)
}
@IBAction func btnPauseAudio(_ sender: UIButton) {
audioPlayer.pause()
setPlayButtons(true, pause: false, stop: true)
}
@IBAction func btnStopAudio(_ sender: UIButton) {
audioPlayer.stop()
audioPlayer.currentTime = 0
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(true, pause: false, stop: false)
progressTimer.invalidate()
}
@IBAction func slChangeVolume(_ sender: UISlider) {
audioPlayer.volume = slVolume.value
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
progressTimer.invalidate()
setPlayButtons(true, pause: false, stop: false)
}
@IBAction func swRecordMode(_ sender: UISwitch) {
if sender.isOn {
audioPlayer.stop()
audioPlayer.currentTime = 0
lblRecordTime!.text = convertNSTimeInterval2String(0)
isRecordMode = true
btnRecord.isEnabled = true
lblRecordTime.isEnabled = true
}
else {
isRecordMode = false
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
lblRecordTime.text = convertNSTimeInterval2String(0)
}
selectAudioFile()
if !isRecordMode {
initPlay()
}
else {
initRecord()
}
}
@IBAction func btnRecord(_ sender: UIButton) {
if (sender as AnyObject).titleLabel?.text == "Record" {
audioRecorder.record()
(sender as AnyObject).setTitle("Stop", for: UIControl.State())
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timeRecordSelector, userInfo: nil, repeats: true)
}
else {
audioRecorder.stop()
progressTimer.invalidate()
(sender as AnyObject).setTitle("Record", for: UIControl.State())
btnPlay.isEnabled = true
initPlay()
}
}
@objc func updateRecordTime() {
lblRecordTime.text = convertNSTimeInterval2String(audioRecorder.currentTime)
}
}
- 이미지 뷰 배치하기
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate, AVAudioRecorderDelegate {
var audioPlayer: AVAudioPlayer!
var audioFile: URL!
let MAX_VOLUME: Float = 10.0
var progressTimer: Timer!
let timePlayerSelector:Selector = #selector(ViewController.updatePlayTime)
let timeRecordSelector:Selector = #selector(ViewController.updateRecordTime)
@IBOutlet var pvProgressPlay: UIProgressView!
@IBOutlet var lblCurrentTime: UILabel!
@IBOutlet var lblEndTime: UILabel!
@IBOutlet var btnPlay: UIButton!
@IBOutlet var btnPause: UIButton!
@IBOutlet var btnStop: UIButton!
@IBOutlet var slVolume: UISlider!
@IBOutlet var btnRecord: UIButton!
@IBOutlet var lblRecordTime: UILabel!
@IBOutlet var imgView: UIImageView!
var audioRecorder: AVAudioRecorder!
var isRecordMode = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
selectAudioFile()
if !isRecordMode {
initPlay()
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
}
else {
initRecord()
}
imgView.image = UIImage(named: "stop.png")
}
func selectAudioFile() {
if !isRecordMode {
audioFile = Bundle.main.url(forResource: "Sicilian_Breeze", withExtension: "mp3")
}
else {
let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
audioFile = documentDirectory.appendingPathComponent("recordFile.m4a")
}
}
func initRecord() {
let recordSettings = [
AVFormatIDKey: NSNumber(value: kAudioFormatAppleLossless as UInt32), //포맷
AVEncoderAudioQualityKey: AVAudioQuality.max.rawValue, //최대 음질
AVEncoderBitRateKey: 320000, //비트율
AVNumberOfChannelsKey: 2, //오디오 채널
AVSampleRateKey: 44100.0] as [String : Any] //샘플률
do {
audioRecorder = try AVAudioRecorder(url: audioFile, settings: recordSettings)
} catch let error as NSError {
print("Error-initRecord : \(error)")
}
audioRecorder.delegate = self
slVolume.value = 1.0
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(0)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(false, pause: false, stop: false)
let session = AVAudioSession.sharedInstance()
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print(" Error-setCategory : \(error)")
}
do {
try session.setActive(true)
} catch let error as NSError {
print(" Error-setActive : \(error)")
}
}
func initPlay() {
do {
audioPlayer = try AVAudioPlayer(contentsOf: audioFile)
} catch let error as NSError {
print("Error-initPlay : \(error)")
}
slVolume.maximumValue = MAX_VOLUME
slVolume.value = 1.0
pvProgressPlay.progress = 0
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.volume = slVolume.value
lblEndTime.text = convertNSTimeInterval2String(audioPlayer.duration)
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(true, pause: false, stop: false)
}
func setPlayButtons(_ play:Bool, pause:Bool, stop:Bool) {
btnPlay.isEnabled = play
btnPause.isEnabled = pause
btnStop.isEnabled = stop
}
func convertNSTimeInterval2String(_ time:TimeInterval) -> String {
let min = Int(time/60)
let sec = Int(time.truncatingRemainder(dividingBy: 60))
let strTime = String(format: "%02d:%02d", min, sec)
return strTime
}
@IBAction func btnPlayAudio(_ sender: UIButton) {
audioPlayer.play()
setPlayButtons(false, pause: true, stop: true)
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timePlayerSelector, userInfo: nil, repeats: true)
imgView.image = UIImage(named: "play.png")
}
@objc func updatePlayTime() {
lblCurrentTime.text = convertNSTimeInterval2String(audioPlayer.currentTime)
pvProgressPlay.progress = Float(audioPlayer.currentTime/audioPlayer.duration)
}
@IBAction func btnPauseAudio(_ sender: UIButton) {
audioPlayer.pause()
setPlayButtons(true, pause: false, stop: true)
imgView.image = UIImage(named: "pause.png")
}
@IBAction func btnStopAudio(_ sender: UIButton) {
audioPlayer.stop()
audioPlayer.currentTime = 0
lblCurrentTime.text = convertNSTimeInterval2String(0)
setPlayButtons(true, pause: false, stop: false)
progressTimer.invalidate()
imgView.image = UIImage(named: "stop.png")
}
@IBAction func slChangeVolume(_ sender: UISlider) {
audioPlayer.volume = slVolume.value
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
progressTimer.invalidate()
setPlayButtons(true, pause: false, stop: false)
imgView.image = UIImage(named: "stop.png")
}
@IBAction func swRecordMode(_ sender: UISwitch) {
if sender.isOn {
audioPlayer.stop()
audioPlayer.currentTime = 0
lblRecordTime!.text = convertNSTimeInterval2String(0)
isRecordMode = true
btnRecord.isEnabled = true
lblRecordTime.isEnabled = true
}
else {
isRecordMode = false
btnRecord.isEnabled = false
lblRecordTime.isEnabled = false
lblRecordTime.text = convertNSTimeInterval2String(0)
}
selectAudioFile()
if !isRecordMode {
initPlay()
}
else {
initRecord()
}
}
@IBAction func btnRecord(_ sender: UIButton) {
if (sender as AnyObject).titleLabel?.text == "Record" {
audioRecorder.record()
(sender as AnyObject).setTitle("Stop", for: UIControl.State())
progressTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: timeRecordSelector, userInfo: nil, repeats: true)
imgView.image = UIImage(named: "record.png")
}
else {
audioRecorder.stop()
progressTimer.invalidate()
(sender as AnyObject).setTitle("Record", for: UIControl.State())
btnPlay.isEnabled = true
initPlay()
imgView.image = UIImage(named: "stop.png")
}
}
@objc func updateRecordTime() {
lblRecordTime.text = convertNSTimeInterval2String(audioRecorder.currentTime)
}
}