Audio Recorder in Android with Example (original) (raw)

Last Updated : 23 Jul, 2025

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

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 ****** tag.

In the same file, under the ****** tag add the following lines of code for FileProvider.

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.

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 `

pause.xml

play\_square.xml

pause\_square.xml

mic\_open.xml

mic\_close.xml

delete.xml

share.xml

`

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 `

<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, 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: