Updated 2 days ago | GitHub

Audio Playback and Recording

Overview

In Android apps, there are often cases where we need to play audio files. Checking the Supported Media Formats we can see that several audio formats (MP3, AAC, FLAC, Vorbis) are playable by default.

In this guide we will take a look at how to play audio content using the MediaPlayer and capture audio with the MediaRecorder.

Playing Local Audio

To play local audio in the supported formats, first we should put the local audio file into the res/raw folder, e.g. res/raw/sample_audio.mp3 (any short MP3 works — for example this test clip from the Google-hosted ExoPlayer test media).

Note: If you get the compile error message INVALID FILE NAME: MUST CONTAIN ONLY [a-z0-9_.], this is because your MP3 can only have lowercase letters, numbers, periods, and underscores in its name.

Now we can use the MediaPlayer in order to playback any local files:

MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.sample_audio);
mediaPlayer.start();

On call to start() method, the music will start playing from the beginning. If this method is called again after the pause() method, the music would start playing from where it left off and not from the beginning. To play back audio from a local file path, simply do:

Uri myUri = Uri.parse("...."); // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
        new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build());
mediaPlayer.setDataSource(getApplicationContext(), myUri);
// or just mediaPlayer.setDataSource(mFileName);
mediaPlayer.prepare(); // must call prepare first
mediaPlayer.start(); // then start

Note that setAudioAttributes(AudioAttributes) replaces the older stream-type API: setAudioStreamType(AudioManager.STREAM_MUSIC) was deprecated in API 26 with the note “use setAudioAttributes(AudioAttributes)”. The MediaPlayer.create(...) factory in the first snippet builds the player with default AudioAttributes and calls prepare() internally, so per its documentation you cannot (and don’t need to) set audio attributes on the returned instance; use the create(Context, int, AudioAttributes, int) overload if you need custom attributes there.

We can release the resources taken by the player by calling release to free up system resources:

mediaPlayer.release();
mediaPlayer = null;

MediaPlayer has many key methods to control playback such as:

MediaPlayer mediaPlayer = MediaPlayer.create(this, R.raw.sample_audio);
mediaPlayer.start();              // start the audio from last location
mediaPlayer.pause();              // pause audio
mediaPlayer.reset();              // reset audio to beginning of track
mediaPlayer.isPlaying();          // returns true/false indicating the song is playing
mediaPlayer.seekTo(100);           // move song to that particular millisecond
mediaPlayer.getCurrentPosition(); // current position of song in milliseconds
mediaPlayer.getDuration();        // total time duration of song in milliseconds

Check the MediaPlayer docs for a full list of methods.

Playing Streaming Audio

If you are using MediaPlayer to stream network-based content, your application must request network access in the manifest:

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

Now we can stream remote audio files in the supported formats with:

String url = "https://storage.googleapis.com/exoplayer-test-media-0/play.mp3";
final MediaPlayer mediaPlayer = new MediaPlayer();
// Set the audio attributes for media playback
mediaPlayer.setAudioAttributes(
		new AudioAttributes.Builder()
				.setUsage(AudioAttributes.USAGE_MEDIA)
				.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
				.build());
// Listen for if the audio file can't be prepared
mediaPlayer.setOnErrorListener(new OnErrorListener() {
	@Override
	public boolean onError(MediaPlayer mp, int what, int extra) {
		// ... react appropriately ...
        // The MediaPlayer has moved to the Error state, must be reset!
		return false;
	}
});
// Attach to when audio file is prepared for playing
mediaPlayer.setOnPreparedListener(new OnPreparedListener() {
	@Override
	public void onPrepared(MediaPlayer mp) {
		mediaPlayer.start();
	}
});
// Set the data source to the remote URL
mediaPlayer.setDataSource(url);
// Trigger an async preparation which will file listener when completed
mediaPlayer.prepareAsync();

With that, the remote file will start streaming once preparation is completed and loading the audio file won’t block the UI thread. For more details, see the official MediaPlayer overview guide.

Capturing Audio

You can record audio using the MediaRecorder APIs if supported by the device hardware.

First, let’s add the correct permission to our AndroidManifest.xml. RECORD_AUDIO is a runtime (“dangerous”) permission, so on top of the manifest entry you must request it at runtime with the Activity Result API before starting a recording — see Understanding App Permissions. No storage permission is needed, because the snippet below records into the app-specific external directory returned by getExternalFilesDir(...), which the app can always read and write:

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

Recording audio is as simple as starting and stopping the MediaRecorder:

// File path of recorded audio
private String mFileName;
private static final String LOG_TAG = "AudioRecordTest";
// Verify that the device has a mic first
PackageManager pmanager = this.getPackageManager();
if (pmanager.hasSystemFeature(PackageManager.FEATURE_MICROPHONE)) {
    // Set the file location for the audio — an app-specific directory,
    // so no storage permission is required
    mFileName = getExternalFilesDir(Environment.DIRECTORY_MUSIC).getAbsolutePath();
    mFileName += "/audiorecordtest.3gp";
    // Create the recorder. The no-argument constructor was deprecated in API 31
    // ("Use MediaRecorder(Context) instead"), but the Context overload doesn't
    // exist below API 31, so gate on the device's OS version:
    MediaRecorder mediaRecorder;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        mediaRecorder = new MediaRecorder(this);
    } else {
        mediaRecorder = new MediaRecorder();
    }
    // Set the audio format and encoder
    mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
    // Setup the output location
    mediaRecorder.setOutputFile(mFileName);
    // Start the recording
    try {
        mediaRecorder.prepare();
        mediaRecorder.start();
    } catch (IOException e) {
        Log.e(LOG_TAG, "prepare() failed");
    }
} else { // no mic on device
    Toast.makeText(this, "This device doesn't have a mic!", Toast.LENGTH_LONG).show();
}

When you want to stop capturing audio, simply notify the recorder:

// Stop the recording of the audio
mediaRecorder.stop();
mediaRecorder.reset();
mediaRecorder.release();

If you wanted to now playback the recorded audio:

MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioAttributes(
        new AudioAttributes.Builder()
                .setUsage(AudioAttributes.USAGE_MEDIA)
                .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                .build());
mediaPlayer.setDataSource(mFileName);
mediaPlayer.prepare(); // must call prepare first
mediaPlayer.start(); // then start

If you wanted to add this audio to the shared media library on the phone, insert it through MediaStore. On Android 10 (API 29) and higher you describe the file with DISPLAY_NAME and RELATIVE_PATH and stream the bytes through the returned Uri — the system scans inserted items automatically, so the old ACTION_MEDIA_SCANNER_SCAN_FILE broadcast (deprecated in API 29 with the note “Callers should migrate to inserting items directly into MediaStore, where they will be automatically scanned after each mutation”) is no longer needed. The IS_PENDING flag hides the entry from other apps until the copy finishes:

// Requires API 29+ (RELATIVE_PATH / IS_PENDING); on older devices, insert with
// the DATA column instead. From Android 11, DATA is read-only on insert for
// apps targeting API 30+ — use DISPLAY_NAME and RELATIVE_PATH as shown here.
val values = ContentValues().apply {
    put(MediaStore.Audio.Media.DISPLAY_NAME, "audiorecordtest.3gp")
    put(MediaStore.Audio.Media.MIME_TYPE, "audio/3gpp")
    put(MediaStore.Audio.Media.RELATIVE_PATH, Environment.DIRECTORY_MUSIC)
    put(MediaStore.Audio.Media.IS_PENDING, 1)
}
val resolver = contentResolver
val collection = MediaStore.Audio.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val newUri = resolver.insert(collection, values)

newUri?.let { uri ->
    // Copy the recorded file's bytes into the MediaStore entry
    resolver.openOutputStream(uri)?.use { output ->
        File(mFileName).inputStream().use { input -> input.copyTo(output) }
    }
    // Clear the pending flag so other apps can see the new track
    values.clear()
    values.put(MediaStore.Audio.Media.IS_PENDING, 0)
    resolver.update(uri, values, null, null)
    Toast.makeText(this, "Added file $uri", Toast.LENGTH_LONG).show()
}

For more details, check out the official MediaRecorder guide and the shared media storage guide.

References