Discover how to create a Voice Recorder Android app in Kotlin by leveraging the MediaRecorder API to capture audio and effectively manage the recording lifecycle. This guide walks you through the essential steps to implement a robust recording feature in your Kotlin-based Android application, ensuring seamless functionality and user experience.
Submitted on July 07, 2024
In this tutorial, you will learn how to create a Voice Recorder Android application using Kotlin. The tutorial will guide you through the process of implementing functionality to capture audio using the MediaRecorder class, which facilitates recording audio from the device's microphone. You'll explore the complete lifecycle management of the recording process, including starting, pausing, resuming, stopping, and saving recordings to storage.
Key components covered in the tutorial include:
By following this tutorial, you'll gain practical experience in integrating audio recording capabilities into Android applications using Kotlin. You'll understand how to handle audio capture efficiently, manage recording states, and ensure a user-friendly experience for your Voice Recorder app users.
Add the necessary permissions for recording audio and accessing external storage (if you plan to save recordings):
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Design the layout for the main activity with necessary buttons and text views.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btnRecord"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Record"
android:layout_centerHorizontal="true"
android:layout_marginTop="24dp" />
<Button
android:id="@+id/btnPlay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Play"
android:layout_below="@id/btnRecord"
android:layout_marginTop="24dp"
android:layout_marginStart="16dp"
android:layout_alignStart="@id/btnRecord" />
<Button
android:id="@+id/btnClear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Clear"
android:layout_below="@id/btnRecord"
android:layout_marginTop="24dp"
android:layout_marginEnd="16dp"
android:layout_alignEnd="@id/btnRecord" />
<TextView
android:id="@+id/tvRecordingTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="00:00"
android:layout_below="@id/btnPlay"
android:layout_centerHorizontal="true"
android:layout_marginTop="24dp" />
</RelativeLayout>
import android.Manifest
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*
class MainActivity : AppCompatActivity() {
private lateinit var btnRecord: Button
private lateinit var btnPlay: Button
private lateinit var btnClear: Button
private lateinit var tvRecordingTime: TextView
private var mediaRecorder: MediaRecorder? = null
private var mediaPlayer: MediaPlayer? = null
private var output: String? = null
private var isRecording = false
private var isPlaying = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btnRecord = findViewById(R.id.btnRecord)
btnPlay = findViewById(R.id.btnPlay)
btnClear = findViewById(R.id.btnClear)
tvRecordingTime = findViewById(R.id.tvRecordingTime)
// Request permissions at runtime if needed
if (!arePermissionsGranted()) {
requestPermissions()
}
btnRecord.setOnClickListener {
if (!isRecording) {
startRecording()
} else {
stopRecording()
}
}
btnPlay.setOnClickListener {
if (!isPlaying && output != null) {
startPlaying()
} else {
stopPlaying()
}
}
btnClear.setOnClickListener {
clearRecording()
}
}
private fun startRecording() {
if (checkPermissions()) {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val audioDir = Environment.getExternalStorageDirectory().absolutePath + "/VoiceRecorder"
val dir = File(audioDir)
if (!dir.exists()) {
dir.mkdirs()
}
output = "$audioDir/REC_${timeStamp}.mp3"
mediaRecorder = MediaRecorder()
mediaRecorder?.setAudioSource(MediaRecorder.AudioSource.MIC)
mediaRecorder?.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
mediaRecorder?.setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
mediaRecorder?.setOutputFile(output)
try {
mediaRecorder?.prepare()
mediaRecorder?.start()
isRecording = true
updateRecordingTime()
btnRecord.text = "Stop"
} catch (e: IOException) {
e.printStackTrace()
}
} else {
requestPermissions()
}
}
private fun stopRecording() {
mediaRecorder?.apply {
stop()
release()
}
mediaRecorder = null
isRecording = false
btnRecord.text = "Record"
tvRecordingTime.text = "00:00"
}
private fun startPlaying() {
mediaPlayer = MediaPlayer()
try {
mediaPlayer?.setDataSource(output)
mediaPlayer?.prepare()
mediaPlayer?.start()
isPlaying = true
btnPlay.text = "Stop"
} catch (e: IOException) {
e.printStackTrace()
}
mediaPlayer?.setOnCompletionListener {
stopPlaying()
}
}
private fun stopPlaying() {
mediaPlayer?.release()
mediaPlayer = null
isPlaying = false
btnPlay.text = "Play"
}
private fun clearRecording() {
output?.let {
val file = File(it)
if (file.exists()) {
file.delete()
}
}
output = null
btnRecord.text = "Record"
btnPlay.text = "Play"
tvRecordingTime.text = "00:00"
}
private fun updateRecordingTime() {
val handler = android.os.Handler()
handler.postDelayed(object: Runnable {
override fun run() {
if (isRecording) {
val minutes = seconds / 60
val seconds = seconds % 60
tvRecordingTime.text = String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds)
handler.postDelayed(this, 1000)
}
}
}, 1000)
}
private fun arePermissionsGranted(): Boolean {
val recordPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
val storagePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
return recordPermission == PackageManager.PERMISSION_GRANTED && storagePermission == PackageManager.PERMISSION_GRANTED
}
private fun checkPermissions(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!arePermissionsGranted()) {
requestPermissions()
return false
}
}
return true
}
private fun requestPermissions() {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE),
REQUEST_PERMISSION_CODE
)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_PERMISSION_CODE) {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
// Permissions granted
} else {
// Permissions denied
// Handle accordingly (disable functionality, show message, etc.)
}
}
}
companion object {
private const val REQUEST_PERMISSION_CODE = 101
}
override fun onDestroy() {
super.onDestroy()
mediaRecorder?.release()
mediaPlayer?.release()
}
}
This example provides a basic framework for a Voice Recorder app in Kotlin. Customize it further according to your specific needs, such as adding features for renaming, sharing, or editing recordings.
import android.Manifest;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
public class MainActivity extends AppCompatActivity {
private Button btnRecord;
private Button btnPlay;
private Button btnClear;
private TextView tvRecordingTime;
private MediaRecorder mediaRecorder;
private MediaPlayer mediaPlayer;
private String output;
private boolean isRecording = false;
private boolean isPlaying = false;
private static final int REQUEST_PERMISSION_CODE = 101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnRecord = findViewById(R.id.btnRecord);
btnPlay = findViewById(R.id.btnPlay);
btnClear = findViewById(R.id.btnClear);
tvRecordingTime = findViewById(R.id.tvRecordingTime);
// Request permissions at runtime if needed
if (!arePermissionsGranted()) {
requestPermissions();
}
btnRecord.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isRecording) {
startRecording();
} else {
stopRecording();
}
}
});
btnPlay.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (!isPlaying && output != null) {
startPlaying();
} else {
stopPlaying();
}
}
});
btnClear.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clearRecording();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mediaRecorder != null) {
mediaRecorder.release();
mediaRecorder = null;
}
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
}
private void startRecording() {
if (checkPermissions()) {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
String audioDir = Environment.getExternalStorageDirectory().getAbsolutePath() + "/VoiceRecorder";
File dir = new File(audioDir);
if (!dir.exists()) {
dir.mkdirs();
}
output = audioDir + "/REC_" + timeStamp + ".mp3";
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mediaRecorder.setOutputFile(output);
try {
mediaRecorder.prepare();
mediaRecorder.start();
isRecording = true;
updateRecordingTime();
btnRecord.setText("Stop");
} catch (IOException e) {
e.printStackTrace();
}
} else {
requestPermissions();
}
}
private void stopRecording() {
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
}
isRecording = false;
btnRecord.setText("Record");
tvRecordingTime.setText("00:00");
}
private void startPlaying() {
mediaPlayer = new MediaPlayer();
try {
mediaPlayer.setDataSource(output);
mediaPlayer.prepare();
mediaPlayer.start();
isPlaying = true;
btnPlay.setText("Stop");
} catch (IOException e) {
e.printStackTrace();
}
mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
stopPlaying();
}
});
}
private void stopPlaying() {
if (mediaPlayer != null) {
mediaPlayer.release();
mediaPlayer = null;
}
isPlaying = false;
btnPlay.setText("Play");
}
private void clearRecording() {
if (output != null) {
File file = new File(output);
if (file.exists()) {
file.delete();
}
}
output = null;
btnRecord.setText("Record");
btnPlay.setText("Play");
tvRecordingTime.setText("00:00");
}
private void updateRecordingTime() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
int seconds = 0;
@Override
public void run() {
if (isRecording) {
int minutes = seconds / 60;
seconds = seconds % 60;
tvRecordingTime.setText(String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds));
seconds++;
handler.postDelayed(this, 1000);
}
}
}, 1000);
}
private boolean arePermissionsGranted() {
int recordPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO);
int storagePermission = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
return recordPermission == PackageManager.PERMISSION_GRANTED && storagePermission == PackageManager.PERMISSION_GRANTED;
}
private boolean checkPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!arePermissionsGranted()) {
requestPermissions();
return false;
}
}
return true;
}
private void requestPermissions() {
ActivityCompat.requestPermissions(
this,
new String[] { Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE },
REQUEST_PERMISSION_CODE
);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
// Permissions granted
} else {
// Permissions denied
// Handle accordingly (disable functionality, show message, etc.)
}
}
}
}