Developing a Voice Recorder Android App in Kotlin: Capture Audio with MediaRecorder

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:

  1. Setting up the user interface with controls for recording, pausing, and stopping.
  2. Implementing logic in Kotlin to initialize and manage the MediaRecorder for audio capture.
  3. Handling permissions to access the device's microphone for recording.
  4. Saving recorded audio files to local storage and managing file operations.

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.

Step 1: Set up Permissions in AndroidManifest.xml

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" />

Step 2: Create Layout (res/layout/activity_main.xml)

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>

Step 3: Implement MainActivity (MainActivity.kt)

        
            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()
                }
            }
        
    

Explanation:

  • Permissions: The app requests runtime permissions for recording audio and writing to external storage.
  • Layout: Contains buttons for Record, Play, Clear, and a TextView to display recording time.
  • Recording: Uses MediaRecorder to start and stop recording, saving the file in a specified directory with a timestamped name.
  • Playing: Uses MediaPlayer to play the recorded audio file.
  • Clearing: Deletes the recorded file and resets UI components.
  • Time Display: Updates the TextView to show the current recording time.

Notes:

  • Ensure you handle permissions correctly based on Android version (runtime permissions for Android 6.0+).
  • Modify paths and file extensions as needed for your project.
  • Error handling and edge cases (like no storage permission, file not found, etc.) should be added based on your app's requirements.

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.

Android Voice Recorder App Script in Java

        
            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.)
                        }
                    }
                }
            }