Open In App

Audio Recorder in Android with Example

Last Updated : 22 Apr, 2025
Summarize
Comments
Improve
Suggest changes
Share
Like Article
Like
Report

In Android for recording audio or video, there is a built-in class called MediaRecorder. This class in Android helps to easily record video and audio files. The Android multimedia framework provides built-in support for capturing and encoding common audio and video formats. In android for recording audio, we will use a device microphone along with MediaRecorder Class and for recording video, we will use the user's device Camera and MediaRecorder Class. Now in this article, we will see the implementation of an audio recorder in Android.

Audio-Recorder-in-Android-with-Example


Important Methods of MediaRecorder Class

Method 

Description

setAudioSource()This method will specify the source of the audio to be recorded.
setAudioEncoder()This method is used to specify the audio encoder.
setOutputFormat()This method is used to specify the output format of our audio.
setOutputFile()This method is used to specify the path of recorded audio files that are to be stored.
stop()This method is used to stop the recording process. 
start()This method is used to start the recording process. 
release()This method is used to release the resource that is associated with the Media recorder class.

Step by Step Implementation

Step 1: Create a New Project

To create a new project in Android Studio please refer to How to Create/Start a New Project in Android Studio.

Step 2: Working with manifest file

Navigate to app > manifests > AndroidManifest.xml and add the following permission under the <manifest/> tag. 

<uses-permission android:name="android.permission.RECORD_AUDIO"/>

In the same file, under the <application/> tag add the following lines of code for FileProvider.

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.provider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

Navigate to app > res > xml, right click and select New > XML Resource File and set the name as file_paths.xml and add the following code to the file.

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-files-path
        name="external_files"
        path="." />
</paths>


Step 3: Adding dependencies

Navigate to Gradle Scripts > build.gradle.kts (Module :app) and add the following dependencies

dependencies {
    ...
    implementation ("com.github.massoudss:waveformSeekBar:5.0.2")
    implementation ("com.github.lincollincol:amplituda:2.2.2")
}

Refer to the this repo to read the documentation for the dependencies.

Navigate to Gradle Scripts > settings.gradle.kts and add the following line under repositories {}

dependencyResolutionManagement {
    ...
    repositories {
        ...
        maven { setUrl("https://jitpack.io") }
    }
}


Step 4: Adding drawable

Navigate to app > res > drawables, right click on the folder and select New Drawable Resource file and add the following drawables.

play.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="32dp"
    android:viewportWidth="283"
    android:viewportHeight="283">
  <path
      android:pathData="M211.99,211.9C250.99,172.9 250.99,109.67 211.99,70.67C172.99,31.68 109.76,31.68 70.77,70.67C31.77,109.67 31.77,172.9 70.77,211.9C109.76,250.89 172.99,250.89 211.99,211.9Z"
      android:fillColor="#8DD998"/>
  <path
      android:pathData="M197.5,141.29L113.32,192.72L113.32,89.85L197.5,141.29Z"
      android:fillColor="#ffffff"/>
</vector>
pause.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="32dp"
    android:height="32dp"
    android:viewportWidth="284"
    android:viewportHeight="283">
  <path
      android:pathData="M212.64,212.17C251.63,173.18 251.63,109.95 212.64,70.95C173.64,31.95 110.41,31.95 71.42,70.95C32.42,109.95 32.42,173.18 71.42,212.17C110.41,251.17 173.64,251.17 212.64,212.17Z"
      android:fillColor="#8DD998"/>
  <path
      android:pathData="M130.45,94.48H102.79V188.64H130.45V94.48Z"
      android:fillColor="#ffffff"/>
  <path
      android:pathData="M181.31,94.48H153.65V188.64H181.31V94.48Z"
      android:fillColor="#ffffff"/>
</vector>
play_square.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="214"
    android:viewportHeight="200">
  <path
      android:pathData="M16.59,0.05L197.99,0.05A16,16 0,0 1,213.99 16.05L213.99,183.11A16,16 0,0 1,197.99 199.11L16.59,199.11A16,16 0,0 1,0.59 183.11L0.59,16.05A16,16 0,0 1,16.59 0.05z"
      android:fillColor="#A0CDAE"/>
  <path
      android:pathData="M148.22,99.58L86.82,137.1L86.82,62.07L148.22,99.58Z"
      android:fillColor="#ffffff"/>
</vector>
pause_square.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="214"
    android:viewportHeight="201">
  <path
      android:pathData="M16.09,0.98L197.5,0.98A16,16 0,0 1,213.5 16.98L213.5,184.04A16,16 0,0 1,197.5 200.04L16.09,200.04A16,16 0,0 1,0.09 184.04L0.09,16.98A16,16 0,0 1,16.09 0.98z"
      android:fillColor="#A0CDAE"/>
  <path
      android:pathData="M95.19,53.43H67.53V147.58H95.19V53.43Z"
      android:fillColor="#ffffff"/>
  <path
      android:pathData="M146.06,53.43H118.4V147.58H146.06V53.43Z"
      android:fillColor="#ffffff"/>
</vector>
mic_open.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="606"
    android:viewportHeight="606">
  <path
      android:pathData="M302.84,605.6C469.98,605.6 605.48,470.11 605.48,302.96C605.48,135.82 469.98,0.33 302.84,0.33C135.7,0.33 0.2,135.82 0.2,302.96C0.2,470.11 135.7,605.6 302.84,605.6Z"
      android:strokeAlpha="0.13"
      android:fillColor="#258D46"
      android:fillAlpha="0.13"/>
  <path
      android:pathData="M302.84,570.61C450.66,570.61 570.49,450.78 570.49,302.97C570.49,155.15 450.66,35.32 302.84,35.32C155.02,35.32 35.2,155.15 35.2,302.97C35.2,450.78 155.02,570.61 302.84,570.61Z"
      android:strokeAlpha="0.35"
      android:fillColor="#258D46"
      android:fillAlpha="0.35"/>
  <path
      android:pathData="M302.84,521.85C423.73,521.85 521.73,423.86 521.73,302.97C521.73,182.08 423.73,84.08 302.84,84.08C181.95,84.08 83.95,182.08 83.95,302.97C83.95,423.86 181.95,521.85 302.84,521.85Z"
      android:fillColor="#258D46"/>
  <path
      android:pathData="M307.01,338.05H298.68C279.11,338.05 263.25,322.19 263.25,302.63V236.64C263.25,217.07 279.11,201.21 298.68,201.21H307.01C326.58,201.21 342.44,217.07 342.44,236.64V302.63C342.44,322.19 326.58,338.05 307.01,338.05Z"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
  <path
      android:pathData="M358.81,322.89C349.09,347.3 330.23,357.69 303.11,357.69C275.48,357.69 256.25,346.64 246.88,321.5"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
  <path
      android:pathData="M303.28,363.91V398.45"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
  <path
      android:pathData="M262.55,404.72H343.13"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
</vector>
mic_close.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="200dp"
    android:height="200dp"
    android:viewportWidth="606"
    android:viewportHeight="606">
  <path
      android:pathData="M302.91,605.6C470.05,605.6 605.54,470.11 605.54,302.96C605.54,135.82 470.05,0.33 302.91,0.33C135.76,0.33 0.27,135.82 0.27,302.96C0.27,470.11 135.76,605.6 302.91,605.6Z"
      android:strokeAlpha="0.13"
      android:fillColor="#000000"
      android:fillAlpha="0.13"/>
  <path
      android:pathData="M302.91,570.61C450.72,570.61 570.55,450.78 570.55,302.97C570.55,155.15 450.72,35.32 302.91,35.32C155.09,35.32 35.26,155.15 35.26,302.97C35.26,450.78 155.09,570.61 302.91,570.61Z"
      android:strokeAlpha="0.35"
      android:fillColor="#000000"
      android:fillAlpha="0.35"/>
  <path
      android:pathData="M302.91,521.85C423.8,521.85 521.8,423.86 521.8,302.97C521.8,182.08 423.8,84.08 302.91,84.08C182.02,84.08 84.02,182.08 84.02,302.97C84.02,423.86 182.02,521.85 302.91,521.85Z"
      android:fillColor="#070707"/>
  <path
      android:pathData="M307.07,338.05H298.74C279.17,338.05 263.31,322.19 263.31,302.63V236.64C263.31,217.07 279.17,201.21 298.74,201.21H307.07C326.64,201.21 342.5,217.07 342.5,236.64V302.63C342.5,322.19 326.64,338.05 307.07,338.05Z"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
  <path
      android:pathData="M358.87,322.89C349.16,347.3 330.3,357.69 303.17,357.69C275.54,357.69 256.32,346.64 246.94,321.5"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
  <path
      android:pathData="M303.34,363.91V398.45"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
  <path
      android:pathData="M262.62,404.72H343.19"
      android:strokeLineJoin="round"
      android:strokeWidth="18.096"
      android:fillColor="#00000000"
      android:strokeColor="#ffffff"
      android:strokeLineCap="round"/>
</vector>
delete.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="960"
    android:viewportHeight="960">
  <path
      android:pathData="M280,840q-33,0 -56.5,-23.5T200,760v-520h-40v-80h200v-40h240v40h200v80h-40v520q0,33 -23.5,56.5T680,840L280,840ZM680,240L280,240v520h400v-520ZM360,680h80v-360h-80v360ZM520,680h80v-360h-80v360ZM280,240v520,-520Z"
      android:fillColor="#e8eaed"/>
</vector>
share.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="960"
    android:viewportHeight="960">
  <path
      android:pathData="M680,880q-50,0 -85,-35t-35,-85q0,-6 3,-28L282,568q-16,15 -37,23.5t-45,8.5q-50,0 -85,-35t-35,-85q0,-50 35,-85t85,-35q24,0 45,8.5t37,23.5l281,-164q-2,-7 -2.5,-13.5T560,200q0,-50 35,-85t85,-35q50,0 85,35t35,85q0,50 -35,85t-85,35q-24,0 -45,-8.5T598,288L317,452q2,7 2.5,13.5t0.5,14.5q0,8 -0.5,14.5T317,508l281,164q16,-15 37,-23.5t45,-8.5q50,0 85,35t35,85q0,50 -35,85t-85,35ZM680,800q17,0 28.5,-11.5T720,760q0,-17 -11.5,-28.5T680,720q-17,0 -28.5,11.5T640,760q0,17 11.5,28.5T680,800ZM200,520q17,0 28.5,-11.5T240,480q0,-17 -11.5,-28.5T200,440q-17,0 -28.5,11.5T160,480q0,17 11.5,28.5T200,520ZM680,240q17,0 28.5,-11.5T720,200q0,-17 -11.5,-28.5T680,160q-17,0 -28.5,11.5T640,200q0,17 11.5,28.5T680,240ZM680,760ZM200,480ZM680,200Z"
      android:fillColor="#e8eaed"/>
</vector>


Step 5: Working with the activity_main.xml file

Navigate to the app > res > layout > activity_main.xml. Comments are added inside the code to understand the code in more detail. Create a new layout for each item of the recycler view and name it item_audio_recording.xml.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<!-- to make the entire page scroll -->
<!-- nest scroll view for smoother scrolling -->
<androidx.core.widget.NestedScrollView 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="@color/white"
    tools:context=".MainActivity">

    <!--  main layout  -->
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:gravity="center_horizontal">

        <!--  Mic image that updates on recording status  -->
        <ImageView
            android:id="@+id/micImage"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:layout_marginTop="64dp"
            android:src="@drawable/mic_close" />

        <!--  Status text that updates on recording/Playing  -->
        <TextView
            android:id="@+id/statusText"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="Status will appear here"
            android:textColor="@android:color/black"
            android:textSize="16sp" />

        <!-- to display audio progress -->
        <com.masoudss.lib.WaveformSeekBar
            android:id="@+id/waveformView"
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:layout_marginHorizontal="48dp"
            app:wave_padding_top="2dp"
            app:wave_padding_Bottom="2dp"
            app:wave_padding_left="2dp"
            app:wave_padding_right="2dp"
            app:wave_gap="4dp"
            app:wave_width="3dp"
            app:wave_gravity="center"
            app:wave_corner_radius="2dp"
            app:wave_background_color="@color/grey"
            app:wave_progress_color="@color/colorPrimary" />

        <!--  Play/Stop saved recording button  -->
        <ImageView
            android:id="@+id/playStopButton"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:src="@drawable/play"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/waveformView" />


        <!--  Start/Stop recording button  -->
        <Button
            android:id="@+id/startStopRecordingButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Start Recording"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/playStopButton" />

        <!-- to display list of saved recordings -->
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recordingsRecyclerView"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="8dp"
            tools:listitem="@layout/item_audio_recording"
            android:clipToPadding="false" />

    </LinearLayout>

</androidx.core.widget.NestedScrollView>
item_audio_recording.xml
<androidx.constraintlayout.widget.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="8dp">

    <!--  Play pause button  -->
    <ImageView
        android:id="@+id/playPauseButton"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:src="@drawable/play_square"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.5" />

    <!-- to display file name -->
    <TextView
        android:id="@+id/fileName"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginEnd="16dp"
        android:ellipsize="end"
        android:maxLines="1"
        android:text="Recording 1"
        android:textSize="16sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/seekBar"
        app:layout_constraintEnd_toEndOf="@+id/seekBar"
        app:layout_constraintStart_toEndOf="@+id/playPauseButton"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_chainStyle="packed" />

    <!--  to show audio progress  -->
    <SeekBar
        android:id="@+id/seekBar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginEnd="16dp"
        android:max="100"
        android:progressTint="@color/colorPrimary"
        android:thumbTint="@color/colorPrimary"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/share"
        app:layout_constraintStart_toEndOf="@+id/playPauseButton"
        app:layout_constraintTop_toBottomOf="@+id/fileName" />

    <!--  to delete a file  -->
    <ImageView
        android:id="@+id/delete"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginEnd="16dp"
        android:src="@drawable/delete"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:tint="@color/darker_grey" />

    <!--  to share a file  -->
    <ImageView
        android:id="@+id/share"
        android:layout_width="24dp"
        android:layout_height="24dp"
        android:layout_marginEnd="16dp"
        android:src="@drawable/share"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/delete"
        app:layout_constraintTop_toTopOf="parent"
        app:tint="@color/darker_grey" />
</androidx.constraintlayout.widget.ConstraintLayout>


Design UI:

audio-recorder-app


Step 6: Creating a Model class

Navigate to app > java > {package-name}, right click and select New Java/Kotlin Class and set the name as AudioRecording and add the following code.

AudioRecording File:

AudioRecording.java
package org.geeksforgeeks.demo;

// Model class representing an audio recording
public class AudioRecording {

    // path where the file is located
    private final String filePath;

    // name of the audio file
    private final String fileName;

    // to check whether the audio is playing or not
    private boolean isPlaying;

    public AudioRecording(String filePath, String fileName) {
        this.filePath = filePath;
        this.fileName = fileName;
        this.isPlaying = false;
    }

    public String getFilePath() {
        return filePath;
    }

    public String getFileName() {
        return fileName;
    }

    public boolean isPlaying() {
        return isPlaying;
    }

    public void setPlaying(boolean playing) {
        isPlaying = playing;
    }
}
AudioRecording.kt
package org.geeksforgeeks.demo

// Model class representing an audio recording
data class AudioRecording(
    // path where the file is located
    val filePath: String,
    // name of the audio file
    val fileName: String,
    // to check whether the audio is playing or not
    var isPlaying: Boolean = false
)


Step 7: Creating an Adapter

Navigate to app > java > {package-name}, right click and select New Java/Kotlin Class and set the name as AudioRecordingAdapter and add the following code.

AudioRecordingAdapter File:

AudioRecordingAdapter.java
package org.geeksforgeeks.demo;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

// Adapter to display and manage a list of audio recordings
public class AudioRecordingAdapter extends RecyclerView.Adapter<AudioRecordingAdapter.AudioViewHolder> {

    private final List<AudioRecording> recordings;
    private final OnPlayClickListener onPlayClick;
    private final OnDeleteClickListener onDeleteClick;
    private final OnShareClickListener onShareClick;

    public AudioRecordingAdapter(List<AudioRecording> recordings,
                                 OnPlayClickListener onPlayClick,
                                 OnDeleteClickListener onDeleteClick,
                                 OnShareClickListener onShareClick) {
        this.recordings = recordings;
        this.onPlayClick = onPlayClick;
        this.onDeleteClick = onDeleteClick;
        this.onShareClick = onShareClick;
    }

    // ViewHolder class holds the views for each list item
    public static class AudioViewHolder extends RecyclerView.ViewHolder {
        ImageView playButton;
        TextView fileNameText;
        SeekBar seekBar;
        ImageView deleteButton;
        ImageView shareButton;

        public AudioViewHolder(@NonNull View itemView) {
            super(itemView);
            playButton = itemView.findViewById(R.id.playPauseButton);
            fileNameText = itemView.findViewById(R.id.fileName);
            seekBar = itemView.findViewById(R.id.seekBar);
            deleteButton = itemView.findViewById(R.id.delete);
            shareButton = itemView.findViewById(R.id.share);
        }
    }

    // Inflates the layout for each item in the list
    @NonNull
    @Override
    public AudioViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_audio_recording, parent, false);
        return new AudioViewHolder(view);
    }

    // Binds data to each item and sets up click listeners
    @Override
    public void onBindViewHolder(@NonNull AudioViewHolder holder, int position) {
        AudioRecording recording = recordings.get(position);

        holder.fileNameText.setText(recording.getFileName());

        // Update play button based on playback state
        holder.playButton.setImageResource(
                recording.isPlaying() ? R.drawable.pause_square : R.drawable.play_square
        );

        // Play button click triggers callback
        holder.playButton.setOnClickListener(v -> onPlayClick.onPlayClick(recording, holder.getAdapterPosition()));

        // Delete button click triggers callback
        holder.deleteButton.setOnClickListener(v -> onDeleteClick.onDeleteClick(recording, holder.getAdapterPosition()));

        // Share button click triggers callback
        holder.shareButton.setOnClickListener(v -> onShareClick.onShareClick(recording));

        // Reset seekBar to the beginning for now
        holder.seekBar.setProgress(0);
    }

    // Returns the size of the dataset
    @Override
    public int getItemCount() {
        return recordings.size();
    }

    // Interfaces for click callbacks
    public interface OnPlayClickListener {
        void onPlayClick(AudioRecording recording, int position);
    }

    public interface OnDeleteClickListener {
        void onDeleteClick(AudioRecording recording, int position);
    }

    public interface OnShareClickListener {
        void onShareClick(AudioRecording recording);
    }
}
AudioRecordingAdapter.kt
package org.geeksforgeeks.demo

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.SeekBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

// Adapter to display and manage a list of audio recordings
class AudioRecordingAdapter(
    private val recordings: MutableList<AudioRecording>,
    private val onPlayClick: (AudioRecording, Int) -> Unit,
    private val onDeleteClick: (AudioRecording, Int) -> Unit,
    private val onShareClick: (AudioRecording) -> Unit
) : RecyclerView.Adapter<AudioRecordingAdapter.AudioViewHolder>() {

    // ViewHolder class holds the views for each list item
    inner class AudioViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val playButton: ImageView = itemView.findViewById(R.id.playPauseButton)
        val fileNameText: TextView = itemView.findViewById(R.id.fileName)
        val seekBar: SeekBar = itemView.findViewById(R.id.seekBar)
        val deleteButton: ImageView = itemView.findViewById(R.id.delete)
        val shareButton: ImageView = itemView.findViewById(R.id.share)
    }

    // Inflates the layout for each item in the list
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AudioViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_audio_recording, parent, false)
        return AudioViewHolder(view)
    }

    // Binds data to each item and sets up click listeners
    override fun onBindViewHolder(holder: AudioViewHolder, position: Int) {
        val recording = recordings[position]

        holder.fileNameText.text = recording.fileName

        // Update play button based on playback state
        holder.playButton.setImageResource(
            if (recording.isPlaying) R.drawable.pause_square else R.drawable.play_square
        )

        // Play button click triggers callback
        holder.playButton.setOnClickListener {
            onPlayClick(recording, holder.adapterPosition)
        }

        // Delete button click triggers callback
        holder.deleteButton.setOnClickListener {
            onDeleteClick(recording, holder.adapterPosition)
        }

        // Share button click triggers callback
        holder.shareButton.setOnClickListener {
            onShareClick(recording)
        }

        // Reset seekBar to the beginning for now
        holder.seekBar.progress = 0
    }

    // Returns the size of the dataset
    override fun getItemCount(): Int = recordings.size
}


Step 8: Working with the MainActivity file

Navigate to the app > java > Your app's package name > MainActivity. Below is the code for the MainActivity file. Comments are added inside the code to understand the code in more detail. 

MainActivity.java
package org.geeksforgeeks.demo;

import android.Manifest;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.widget.*;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.masoudss.lib.WaveformSeekBar;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    // UI components
    private Button startStopRecordingButton;
    private ImageView playStopButton;
    private ImageView micImage;
    private TextView statusText;
    private WaveformSeekBar waveformView;
    private RecyclerView recyclerView;

    // Media components
    private MediaRecorder mRecorder;
    private MediaPlayer mPlayer;
    private String audioFilePath;

    // Flags
    private boolean isRecording = false;
    private boolean isPlaying = false;

    // List of saved recordings
    private final List<AudioRecording> recordings = new ArrayList<>();
    private AudioRecordingAdapter adapter;

    // State for currently playing item in the list
    private MediaPlayer playingPlayer = null;
    private int playingIndex = -1;
    private final Handler seekHandler = new Handler();

    // Called when the activity is first created
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Initialize UI components
        micImage = findViewById(R.id.micImage);
        playStopButton = findViewById(R.id.playStopButton);
        startStopRecordingButton = findViewById(R.id.startStopRecordingButton);
        statusText = findViewById(R.id.statusText);
        waveformView = findViewById(R.id.waveformView);
        recyclerView = findViewById(R.id.recordingsRecyclerView);

        // Set up RecyclerView with linear layout and adapter
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new AudioRecordingAdapter(
                recordings,
                this::playRecording,
                this::deleteRecording,
                this::shareRecording
        );
        recyclerView.setAdapter(adapter);

        // Load any saved recordings from storage
        loadRecordings();

        // Ask for mic permission if not already granted
        if (!checkPermissions()) requestPermissions();

        // Set click listener to start or stop recording
        startStopRecordingButton.setOnClickListener(v -> {
            if (!isRecording) startRecording();
            else stopRecording();
        });

        // Set click listener to play or stop last recorded file
        playStopButton.setOnClickListener(v -> {
            if (!isPlaying) playAudio();
            else stopAudio();
        });
    }

    // Generate a unique file path for each new recording
    private String getNewFilePath() {
        File dir = getExternalFilesDir(null);
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault());
        String timeStamp = formatter.format(new Date());
        return new File(dir, "Recording_" + timeStamp + ".3gp").getAbsolutePath();
    }

    // Start recording using the MediaRecorder API
    private void startRecording() {
        if (!checkPermissions()) {
            requestPermissions();
            return;
        }

        audioFilePath = getNewFilePath();

        mRecorder = new MediaRecorder();
        mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
        mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
        mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
        mRecorder.setOutputFile(audioFilePath);

        try {
            mRecorder.prepare();
            mRecorder.start();
            isRecording = true;
            startStopRecordingButton.setText("Stop Recording");
            micImage.setImageResource(R.drawable.mic_open);
            statusText.setText("Recording started");
        } catch (IOException e) {
            Log.e("MainActivity", "Recording failed: " + e.getLocalizedMessage());
            statusText.setText("Recording failed");
        }
    }

    // Stop recording and save the file to list
    private void stopRecording() {
        if (mRecorder != null) {
            mRecorder.stop();
            mRecorder.release();
        }
        mRecorder = null;
        isRecording = false;
        startStopRecordingButton.setText("Start Recording");
        micImage.setImageResource(R.drawable.mic_close);
        statusText.setText("Recording saved");

        String fileName = new File(audioFilePath).getName();
        recordings.add(new AudioRecording(audioFilePath, fileName));
        adapter.notifyItemInserted(recordings.size() - 1);
    }

    // Play the most recent audio and show waveform animation
    private void playAudio() {
        if (audioFilePath == null || !new File(audioFilePath).exists()) {
            Toast.makeText(this, "No recording found to play", Toast.LENGTH_SHORT).show();
            return;
        }

        mPlayer = new MediaPlayer();
        try {
            mPlayer.setDataSource(audioFilePath);
            mPlayer.prepare();
            mPlayer.start();
            waveformView.setSampleFrom(audioFilePath);
            isPlaying = true;
            playStopButton.setImageResource(R.drawable.pause);
            statusText.setText("Playing...");
            seekHandler.post(updateWaveformRunnable);

            mPlayer.setOnCompletionListener(mp -> {
                stopAudio();
                statusText.setText("Playback complete");
            });
        } catch (IOException e) {
            Log.e("MainActivity", "Playback failed: " + e.getLocalizedMessage());
            statusText.setText("Playback failed");
        }
    }

    // Stop current audio playback
    private void stopAudio() {
        if (mPlayer != null) {
            mPlayer.release();
            mPlayer = null;
        }
        isPlaying = false;
        playStopButton.setImageResource(R.drawable.play);
        statusText.setText("Playback stopped");
        seekHandler.removeCallbacks(updateWaveformRunnable);
        waveformView.setProgress(0f);
    }

    // Periodically updates waveform progress while audio plays
    private final Runnable updateWaveformRunnable = new Runnable() {
        @Override
        public void run() {
            if (mPlayer != null && mPlayer.isPlaying()) {
                float progress = (mPlayer.getCurrentPosition() * 100f) / mPlayer.getDuration();
                waveformView.setProgress(progress);
                seekHandler.postDelayed(this, 100);
            }
        }
    };

    // Play audio from a recording in the list (by index)
    private void playRecording(AudioRecording recording, int index) {
        // Stop previously playing audio if different
        if (playingPlayer != null && playingIndex != index) stopPlaying();

        if (recording.isPlaying()) {
            stopPlaying();
        } else {
            playingPlayer = new MediaPlayer();
            try {
                playingPlayer.setDataSource(recording.getFilePath());
                playingPlayer.prepare();
                playingPlayer.start();
                recording.setPlaying(true);
                playingIndex = index;
                adapter.notifyItemChanged(index);

                playingPlayer.setOnCompletionListener(mp -> {
                    recording.setPlaying(false);
                    adapter.notifyItemChanged(index);
                    playingPlayer = null;
                    playingIndex = -1;
                });

                updateSeekBar(index);

            } catch (IOException e) {
                Log.e("MainActivity", "Playback failed: " + e.getLocalizedMessage());
            }
        }
    }

    // Stops currently playing list item
    private void stopPlaying() {
        if (playingPlayer != null) {
            playingPlayer.release();
            playingPlayer = null;
        }
        if (playingIndex >= 0 && playingIndex < recordings.size()) {
            recordings.get(playingIndex).setPlaying(false);
            adapter.notifyItemChanged(playingIndex);
            playingIndex = -1;
        }
    }

    // Updates the progress bar (seek bar) for the item being played
    private void updateSeekBar(int index) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                if (playingPlayer != null && playingPlayer.isPlaying() && index == playingIndex) {
                    int percent = (100 * playingPlayer.getCurrentPosition()) / playingPlayer.getDuration();
                    AudioRecordingAdapter.AudioViewHolder holder = (AudioRecordingAdapter.AudioViewHolder) recyclerView.findViewHolderForAdapterPosition(index);
                    if (holder != null) {
                        holder.seekBar.setProgress(percent);
                    }
                    seekHandler.postDelayed(this, 500);
                }
            }
        };
        seekHandler.post(runnable);
    }

    // Loads all previously recorded .3gp files from app storage
    private void loadRecordings() {
        File dir = getExternalFilesDir(null);
        if (dir != null) {
            File[] files = dir.listFiles();
            if (files != null) {
                for (File file : files) {
                    if (file.getName().endsWith(".3gp")) {
                        recordings.add(new AudioRecording(file.getAbsolutePath(), file.getName()));
                    }
                }
            }
        }
        adapter.notifyDataSetChanged();
    }

    // Deletes the selected recording from list and file system
    private void deleteRecording(AudioRecording recording, int index) {
        File file = new File(recording.getFilePath());
        if (file.exists()) file.delete();

        recordings.remove(index);
        adapter.notifyItemRemoved(index);
        Toast.makeText(this, "Recording deleted", Toast.LENGTH_SHORT).show();
    }

    // Shares the selected audio file using external apps
    private void shareRecording(AudioRecording recording) {
        File file = new File(recording.getFilePath());
        if (!file.exists()) return;

        Uri uri = FileProvider.getUriForFile(
                this,
                getPackageName() + ".provider",
                file
        );

        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("audio/*");
        shareIntent.putExtra(Intent.EXTRA_STREAM, uri);
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        startActivity(Intent.createChooser(shareIntent, "Share recording via"));
    }

    // Checks if microphone permission is granted
    private boolean checkPermissions() {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED;
    }

    // Requests microphone permission
    private void requestPermissions() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, REQUEST_AUDIO_PERMISSION_CODE);
    }

    // Handles result from permission request dialog
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == REQUEST_AUDIO_PERMISSION_CODE) {
            statusText.setText(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED ? "Permission granted" : "Permission denied");
        }
    }

    // Constant for permission request code
    public static final int REQUEST_AUDIO_PERMISSION_CODE = 200;
}
MainActivity.kt
package org.geeksforgeeks.demo

import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.media.MediaPlayer
import android.media.MediaRecorder
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.widget.*
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.content.FileProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.masoudss.lib.WaveformSeekBar
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

class MainActivity : AppCompatActivity() {

    // UI components
    private lateinit var startStopRecordingButton: Button
    private lateinit var playStopButton: ImageView
    private lateinit var micImage: ImageView
    private lateinit var statusText: TextView
    private lateinit var waveformView: WaveformSeekBar
    private lateinit var recyclerView: RecyclerView

    // Media components
    private var mRecorder: MediaRecorder? = null
    private var mPlayer: MediaPlayer? = null
    private lateinit var audioFilePath: String

    // Flags
    private var isRecording = false
    private var isPlaying = false

    // List of saved recordings
    private val recordings = mutableListOf<AudioRecording>()
    private lateinit var adapter: AudioRecordingAdapter

    // State for currently playing item in the list
    private var playingPlayer: MediaPlayer? = null
    private var playingIndex = -1
    private val seekHandler = Handler()

    // Called when the activity is first created
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize UI components
        micImage = findViewById(R.id.micImage)
        playStopButton = findViewById(R.id.playStopButton)
        startStopRecordingButton = findViewById(R.id.startStopRecordingButton)
        statusText = findViewById(R.id.statusText)
        waveformView = findViewById(R.id.waveformView)
        recyclerView = findViewById(R.id.recordingsRecyclerView)

        // Set up RecyclerView with linear layout and adapter
        recyclerView.layoutManager = LinearLayoutManager(this)
        adapter = AudioRecordingAdapter(
            recordings,
            onPlayClick = { recording, index -> playRecording(recording, index) },
            onDeleteClick = { recording, index -> deleteRecording(recording, index) },
            onShareClick = { recording -> shareRecording(recording) }
        )
        recyclerView.adapter = adapter

        // Load any saved recordings from storage
        loadRecordings()

        // Ask for mic permission if not already granted
        if (!checkPermissions()) requestPermissions()

        // Set click listener to start or stop recording
        startStopRecordingButton.setOnClickListener {
            if (!isRecording) startRecording() else stopRecording()
        }

        // Set click listener to play or stop last recorded file
        playStopButton.setOnClickListener {
            if (!isPlaying) playAudio() else stopAudio()
        }
    }

    // Generate a unique file path for each new recording
    private fun getNewFilePath(): String {
        val dir = getExternalFilesDir(null)
        val formatter = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.getDefault())
        val timeStamp = formatter.format(Date())
        return File(dir, "Recording_$timeStamp.3gp").absolutePath
    }

    // Start recording using the MediaRecorder API
    private fun startRecording() {
        if (!checkPermissions()) {
            requestPermissions()
            return
        }

        audioFilePath = getNewFilePath()

        mRecorder = MediaRecorder().apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
            setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
            setOutputFile(audioFilePath)

            try {
                prepare()
                start()
                isRecording = true
                startStopRecordingButton.text = "Stop Recording"
                micImage.setImageResource(R.drawable.mic_open)
                statusText.text = "Recording started"
            } catch (e: IOException) {
                Log.e("MainActivity", "Recording failed: ${e.localizedMessage}")
                statusText.text = "Recording failed"
            }
        }
    }

    // Stop recording and save the file to list
    private fun stopRecording() {
        mRecorder?.apply {
            stop()
            release()
        }

        mRecorder = null
        isRecording = false
        startStopRecordingButton.text = "Start Recording"
        micImage.setImageResource(R.drawable.mic_close)
        statusText.text = "Recording saved"

        val fileName = File(audioFilePath).name
        recordings.add(AudioRecording(audioFilePath, fileName))
        adapter.notifyItemInserted(recordings.size - 1)
    }

    // Play the most recent audio and show waveform animation
    private fun playAudio() {
        if (!::audioFilePath.isInitialized || !File(audioFilePath).exists()) {
            Toast.makeText(this, "No recording found to play", Toast.LENGTH_SHORT).show()
            return
        }

        mPlayer = MediaPlayer().apply {
            try {
                setDataSource(audioFilePath)
                prepare()
                start()
                waveformView.setSampleFrom(audioFilePath)
                this@MainActivity.isPlaying = true
                playStopButton.setImageResource(R.drawable.pause)
                statusText.text = "Playing..."
                handler.post(updateWaveformRunnable)

                setOnCompletionListener {
                    stopAudio()
                    statusText.text = "Playback complete"
                }

            } catch (e: IOException) {
                Log.e("MainActivity", "Playback failed: ${e.localizedMessage}")
                statusText.text = "Playback failed"
            }
        }
    }

    // Stop current audio playback
    private fun stopAudio() {
        mPlayer?.release()
        mPlayer = null
        isPlaying = false
        playStopButton.setImageResource(R.drawable.play)
        statusText.text = "Playback stopped"
        handler.removeCallbacks(updateWaveformRunnable)
        waveformView.progress = 0f
    }

    // Periodically updates waveform progress while audio plays
    private val handler = Handler()
    private val updateWaveformRunnable = object : Runnable {
        override fun run() {
            mPlayer?.let {
                if (it.isPlaying) {
                    val progress = (it.currentPosition.toFloat() / it.duration) * 100
                    waveformView.progress = progress
                    handler.postDelayed(this, 100)
                }
            }
        }
    }

    // Play audio from a recording in the list (by index)
    private fun playRecording(recording: AudioRecording, index: Int) {
        // Stop previously playing audio if different
        if (playingPlayer != null && playingIndex != index) stopPlaying()

        if (recording.isPlaying) {
            stopPlaying()
        } else {
            playingPlayer = MediaPlayer().apply {
                try {
                    setDataSource(recording.filePath)
                    prepare()
                    start()
                    recording.isPlaying = true
                    playingIndex = index
                    adapter.notifyItemChanged(index)

                    setOnCompletionListener {
                        recording.isPlaying = false
                        adapter.notifyItemChanged(index)
                        playingPlayer = null
                        playingIndex = -1
                    }

                    updateSeekBar(index)

                } catch (e: IOException) {
                    Log.e("MainActivity", "Playback failed: ${e.localizedMessage}")
                }
            }
        }
    }

    // Stops currently playing list item
    private fun stopPlaying() {
        playingPlayer?.release()
        playingPlayer = null
        if (playingIndex >= 0 && playingIndex < recordings.size) {
            recordings[playingIndex].isPlaying = false
            adapter.notifyItemChanged(playingIndex)
            playingIndex = -1
        }
    }

    // Updates the progress bar (seek bar) for the item being played
    private fun updateSeekBar(index: Int) {
        val runnable = object : Runnable {
            override fun run() {
                val player = playingPlayer ?: return
                if (player.isPlaying && index == playingIndex) {
                    val percent = (100 * player.currentPosition) / player.duration
                    val holder = recyclerView.findViewHolderForAdapterPosition(index)
                            as? AudioRecordingAdapter.AudioViewHolder
                    holder?.seekBar?.progress = percent
                    seekHandler.postDelayed(this, 500)
                }
            }
        }
        seekHandler.post(runnable)
    }

    // Loads all previously recorded .3gp files from app storage
    private fun loadRecordings() {
        val dir = getExternalFilesDir(null)
        dir?.listFiles()?.filter { it.name.endsWith(".3gp") }?.sortedBy { it.name }?.forEach {
            recordings.add(AudioRecording(it.absolutePath, it.name))
        }
        adapter.notifyDataSetChanged()
    }

    // Deletes the selected recording from list and file system
    private fun deleteRecording(recording: AudioRecording, index: Int) {
        val file = File(recording.filePath)
        if (file.exists()) file.delete()

        recordings.removeAt(index)
        adapter.notifyItemRemoved(index)
        Toast.makeText(this, "Recording deleted", Toast.LENGTH_SHORT).show()
    }

    // Shares the selected audio file using external apps
    private fun shareRecording(recording: AudioRecording) {
        val file = File(recording.filePath)
        if (!file.exists()) return

        val uri: Uri = FileProvider.getUriForFile(
            this,
            "${packageName}.provider",
            file
        )

        val shareIntent = Intent(Intent.ACTION_SEND).apply {
            type = "audio/*"
            putExtra(Intent.EXTRA_STREAM, uri)
            addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }

        startActivity(Intent.createChooser(shareIntent, "Share recording via"))
    }

    // Checks if microphone permission is granted
    private fun checkPermissions(): Boolean {
        return ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.RECORD_AUDIO
        ) == PackageManager.PERMISSION_GRANTED
    }

    // Requests microphone permission
    private fun requestPermissions() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.RECORD_AUDIO),
            REQUEST_AUDIO_PERMISSION_CODE
        )
    }

    // Handles result from permission request dialog
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_AUDIO_PERMISSION_CODE) {
            statusText.text = if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)
                "Permission granted" else "Permission denied"
        }
    }

    // Constant for permission request code
    companion object {
        const val REQUEST_AUDIO_PERMISSION_CODE = 200
    }
}

Refer to the following github repo to get the entire code: Audio-Recorder-in-Android

Output:



Next Article

Similar Reads