Android Studio/base_Project

Android 녹음기 만들기 [2/3] (기능 구현)

내손은개발 🐾 2024. 2. 19. 18:21

1. 녹음 기능 구현

    private var recorder: MediaRecorder? = null

 

    private fun onRecord(start: Boolean) = if (start) {
        startRecording()
    } else {
        stopRecording()
    }

 

1-1) start와 stop으로 나눠주었다.

    private fun startRecording() {
        state = State.RECORDING

        recorder = MediaRecorder(this).apply {
            setAudioSource(MediaRecorder.AudioSource.MIC) //마이크
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) //저장 방식 (3GPP:기본 원시 파일)
            setOutputFile(fileName)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //지원 형식

            try {
                prepare()
            } catch (e: IOException) {
                Log.e("App", "prepare() failed $e")
            }
            //이상 없으면
            start()
        }

        binding.recordButton.setImageDrawable(
            ContextCompat.getDrawable(
                this,
                R.drawable.icon_stop
            )
        )
        binding.playButton.isEnabled = false //시작버튼 비활성화
        binding.playButton.alpha = 0.3f // 시작버튼 색 변경 (초기값: 1.0)
    }
    private fun stopRecording() {
        recorder?.apply {
            stop()
            release()

        }
        recorder = null
        state = State.RELEASE

        binding.recordButton.setImageDrawable(
            ContextCompat.getDrawable(
                this,
                R.drawable.baseline_circle_24
            )
        )
        binding.playButton.isEnabled = true //시작버튼 활성화
        binding.playButton.alpha = 1.0f // 시작버튼 색 변경
    }

 

 

3가지 상태로 나눔

    private var state: State = State.RELEASE
    private enum class State {
        RELEASE, RECORDING, PLAYING
    }
	onCreate() {
    	
        ...
        
        binding.recordButton.setOnClickListener {
            when (state) {
                State.RELEASE -> {
                    record()
                }

                State.RECORDING -> {
                    onRecord(false)
                }

                State.PLAYING -> {}
            }

        }
        
        ...
        
        }

 

 

 

 

 

 

지원형식 (Encoder)

 

2. 파일 저장

internal: 항상 접근 가능, 파일을 저장한 앱에서만 접근 가능하고, 앱 제거시 함께 사라진다.

external: removable한 저장소 (usb, disk 등은 항상 제거될 수 있다), 어디서든 접근 가능, 앱 제거해도 그대로 남아있다.

 

    private var fileName: String = ""
    
    
    onCreate(){
    ...
    fileName = "${externalCacheDir?.absolutePath}/audiorecordtext.3gp"
	//absolutePath 절대경로
    //audiorecordtext 파일이름 뒤에 붙여질
    //3gp 형식
    ...

 

 

3. 재생 구현

    private var player: MediaPlayer? = null
        binding.playButton.setOnClickListener {
            when (state) {
                State.RELEASE -> {
                    onPlay(true)
                }

                State.RECORDING -> { }

                State.PLAYING -> {
                    onPlay(false)
                }
            }
        }
    private fun onPlay(start: Boolean) = if (start) startPlaying() else stopPlaying()

startPlaying과 stopPlaying 생성

    private fun startPlaying() {
        state = State.PLAYING

        player = MediaPlayer().apply {
            try {
                setDataSource(fileName)
                prepare()
            } catch (e: IOException) {
                Log.e("APP", "media player prepare fail $e")
            }
            start()
        }

        //파일 재생이 끝났을 경우
        player?.setOnCompletionListener {
            stopPlaying()
        }

        binding.recordButton.isEnabled = false
        binding.recordButton.alpha = 0.3f
    }

    private fun stopPlaying() {
        state = State.RELEASE

        player?.release()
        player = null
        binding.recordButton.isEnabled = true
        binding.recordButton.alpha = 1.0f
    }

 

 

        binding.playButton.setOnClickListener {
            when (state) {
                State.RELEASE -> {
                    onPlay(true)
                }
                else -> {
                    /*
                    State.RECORDING -> { }
                    State.PLAYING -> { }
                    */
                }

            }
        }

        binding.stopButton.setOnClickListener {
            when (state) {
                State.PLAYING -> {
                    onPlay(false)
                }
                else -> {
/*
                    State.RELEASE -> { }
                    State.RECORDING -> { }
*/

                }
            }
        }

 

 

전체코드

더보기

//MainActivity
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.ColorStateList
import android.graphics.Color
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
import android.util.Log
import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.example.kotlin_recorder.databinding.ActivityMainBinding
import java.io.IOException

class MainActivity : AppCompatActivity() {

companion object {
private const val REQUEST_RECORD_AUDIO_CODE = 200
}

private lateinit var binding: ActivityMainBinding
private var recorder: MediaRecorder? = null
private var player: MediaPlayer? = null
private var fileName: String = ""
private var state: State = State.RELEASE

private enum class State {
RELEASE, RECORDING, PLAYING
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)

fileName = "${externalCacheDir?.absolutePath}/audiorecordtext.3gp" //파일명

binding.recordButton.setOnClickListener {
when (state) {
State.RELEASE -> {
record()
}

State.RECORDING -> {
onRecord(false)
}

State.PLAYING -> {}
}

}

binding.playButton.setOnClickListener {
when (state) {
State.RELEASE -> {
onPlay(true)
}
else -> {
/*
State.RECORDING -> { }
State.PLAYING -> { }
*/
}

}
}

binding.stopButton.setOnClickListener {
when (state) {
State.PLAYING -> {
onPlay(false)
}
else -> {
/*
State.RELEASE -> { }
State.RECORDING -> { }
*/

}
}
}

}


private fun record() {
when {
ContextCompat.checkSelfPermission(
this, //CONTEXT -> this (activity에서의 CONTEXT는 자신이니까 this)
Manifest.permission.RECORD_AUDIO // RECORD_AUDIO로 변경
) == PackageManager.PERMISSION_GRANTED -> { //권한이 습득되어 있다면
onRecord(true)
}

ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.RECORD_AUDIO
) -> {
showPermissionRationalDialog() //AlertDialog 호출
}

else -> {
ActivityCompat.requestPermissions(
this, // CONTEXT -> this (activity에서의 CONTEXT는 자신이니까 this)
arrayOf(Manifest.permission.RECORD_AUDIO),
REQUEST_RECORD_AUDIO_CODE
)
}
}
}

private fun onRecord(start: Boolean) = if (start) startRecording() else stopRecording()

private fun onPlay(start: Boolean) = if (start) startPlaying() else stopPlaying()

private fun startRecording() {
state = State.RECORDING

recorder = MediaRecorder(this).apply {
setAudioSource(MediaRecorder.AudioSource.MIC) //마이크
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP) //저장 방식 (3GPP:기본 원시 파일)
setOutputFile(fileName)
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB) //지원 형식

try {
prepare()
} catch (e: IOException) {
Log.e("App", "prepare() failed $e")
}
//이상 없으면
start()
}
//녹음시작 시 버튼 변경
binding.recordButton.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.icon_stop
)
)
binding.playButton.isEnabled = false //시작버튼 비활성화
binding.playButton.alpha = 0.3f // 시작버튼 색 변경 (초기값: 1.0)
}

private fun stopRecording() {
recorder?.apply { //? : null일 경우는 실행되지 않도록
stop()
release()

}
recorder = null
state = State.RELEASE

binding.recordButton.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.baseline_circle_24
)
)
binding.playButton.isEnabled = true //시작버튼 활성화
binding.playButton.alpha = 1.0f // 시작버튼 색 변경
}

private fun startPlaying() {
state = State.PLAYING

player = MediaPlayer().apply {
try {
setDataSource(fileName)
prepare()
} catch (e: IOException) {
Log.e("APP", "media player prepare fail $e")
}
start()
}

//파일 재생이 끝났을 경우
player?.setOnCompletionListener {
stopPlaying()
}

binding.recordButton.isEnabled = false
binding.recordButton.alpha = 0.3f
}

private fun stopPlaying() {
state = State.RELEASE

player?.release()
player = null
binding.recordButton.isEnabled = true
binding.recordButton.alpha = 1.0f
}


private fun showPermissionRationalDialog() {
AlertDialog.Builder(this)
.setMessage("녹음 권한을 허용하겠습니까?")
.setPositiveButton("허용") { _, _ ->
ActivityCompat.requestPermissions( //requestPermissions androd에서 사용해서 ActivityCompat을 붙여주어서 androidx에 있는 requestPermissions을 사용
this, // CONTEXT -> this (activity에서의 CONTEXT는 자신이니까 this)
arrayOf(Manifest.permission.RECORD_AUDIO),
REQUEST_RECORD_AUDIO_CODE
)
}
.setNegativeButton("취소") { dialogInterface, _ -> dialogInterface.cancel() }
.show()
}

private fun showPermissionSettingDialog() {
AlertDialog.Builder(this)
.setMessage("녹음 권한을 허용해야 앱을 사용 가능합니다. 앱 설정 화면으로 이동하시겠습니까?")
.setPositiveButton("권한 허용하러 가기") { _, _ ->
navigateToAppSetting()
}
.setNegativeButton("취소") { dialogInterface, _ -> dialogInterface.cancel() }
.show()
}

private fun navigateToAppSetting() {
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", packageName, null)
}
startActivity(intent)
}


//허용되지 않은 경우도 설정해줘야 한다.
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

//권한이 허용되었는지 안되어있는지 체크
val audioRecordPermissionGranted = requestCode == REQUEST_RECORD_AUDIO_CODE
&& grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED

if (audioRecordPermissionGranted) {
onRecord(true)
} else {
if (ActivityCompat.shouldShowRequestPermissionRationale(
this,
Manifest.permission.RECORD_AUDIO
)
) {
showPermissionRationalDialog()
} else {
showPermissionSettingDialog()
}

}
}
}