Audio Playback Capture in Android X
Learn how to integrate the Android Playback Capture API into your app, allowing you to record and play back audio from other apps. By Evana Margain Puig.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Audio Playback Capture in Android X
30 mins
- Getting Started
- What Is Audio Playback Capture API?
- Managing Permissions for Audio Capture
- Requesting Permissions
- Checking for Permissions in Your Activity
- Changing Permissions
- Recording Audio From Other Apps
- Requesting Permission Before a Capturing Session
- Checking Permissions
- Overriding onActivityResult
- Creating MediaCaptureService
- Starting the Service and Getting Notifications
- Triggering the Audio Capture Playback
- Starting the Playback Audio Capture
- Stopping the Audio Capture
- Connecting the Service
- Adding Your Service to the Android Manifest
- Disabling Audio Playback Capture
- Listening to Recorded Audio
- Getting the Files From Memory
- Listening to Your Files
- Where to Go From Here?
Requesting Permission Before a Capturing Session
Your next task is to request this additional permission with a prompt that displays every time the user clicks the Start Audio Capture button.
In RecordFragment.kt, you’ll find another TODO at the top to create a variable. Add this code below the class declaration to do so:
private lateinit var mediaProjectionManager: MediaProjectionManager
Then, below the two functions you created for the listeners, create a new function:
private fun startMediaProjectionRequest() {
// 1
mediaProjectionManager = requireContext().getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
// 2
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), MEDIA_PROJECTION_REQUEST_CODE)
}
The code above is a standard function to request this permission. Here’s what it does:
- It initializes the Media Projection Manager, which is an Android Service with all the necessary logic to capture audio.
-
startActivityForResult
is an Android method that executes when you want to get a callback after executing another activity. In this case, you execute it after the permission result.
Next, create a companion object with the MEDIA_PROJECTION_REQUEST_CODE
code:
companion object {
private const val MEDIA_PROJECTION_REQUEST_CODE = 13
}
You placed that variable in a companion object because they won’t change at any moment.
Finally, replace Toast
in startCapturing()
with the following:
if (!isRecordAudioPermissionGranted()) {
requestRecordAudioPermission()
} else {
startMediaProjectionRequest()
}
The code above checks for audio record permissions. If the user authorized them, you start the audio capture.
MainActivity
. In the latest versions of Android, the OS is much stricter with permissions and personal data, so every time you use a service that requires permissions, it’s necessary to check again.
Next, you’ll make sure that your app has the necessary permissions before proceeding. You will soon understand all the pieces you just put together. For now, go ahead and check the permissions:
Checking Permissions
You don’t have the functions for checking permissions yet, so add them below startCapturing()
.
private fun isRecordAudioPermissionGranted(): Boolean {
return ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.RECORD_AUDIO
) == PackageManager.PERMISSION_GRANTED
}
private fun requestRecordAudioPermission() {
ActivityCompat.requestPermissions(
requireActivity(),
arrayOf(Manifest.permission.RECORD_AUDIO),
RECORD_AUDIO_PERMISSION_REQUEST_CODE
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == RECORD_AUDIO_PERMISSION_REQUEST_CODE) {
if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(
requireContext(),
"Permissions to capture audio granted.",
Toast.LENGTH_SHORT
).show()
} else {
Toast.makeText(
requireContext(), "Permissions to capture audio denied.",
Toast.LENGTH_SHORT
).show()
}
}
}
After adding those functions to the fragment, you’ll get some missing import errors. Resolve these by adding:
import android.Manifest
import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import android.content.Context
import android.media.projection.MediaProjectionManager
import androidx.core.content.ContextCompat
Finally, you’ll get an error on RECORD_AUDIO_PERMISSION_REQUEST_CODE
, which tells you that you don’t have a variable or constant with that name. Go to your companion object at the bottom of the class and add the constant:
private const val RECORD_AUDIO_PERMISSION_REQUEST_CODE = 42
Build and run. Now, click the START AUDIO CAPTURE button and you’ll see a permission prompt:
Click START NOW now, and nothing happens. That’s because you need to override onActivityResult
. You’ll do that next.
Overriding onActivityResult
As mentioned above, once this alert appears, an activity result will return. onActivityResult()
is a method that’s part of your fragment, but you need to override it to start the cast once the user gives permission.
To do this, add the following code after startMediaProjectionRequest()
:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// 1
if (requestCode == MEDIA_PROJECTION_REQUEST_CODE) {
// 2
if (resultCode == Activity.RESULT_OK) {
// 3
Toast.makeText(
requireContext(),
"MediaProjection permission obtained. Foreground service will start to capture audio.",
Toast.LENGTH_SHORT
).show()
}
} else {
// 4
Toast.makeText(
requireContext(), "Request to get MediaProjection denied.",
Toast.LENGTH_SHORT
).show()
}
}
This may look like a big function, but don’t worry. It’s just a series of validations that check whether the user started or canceled the cast. Look at it in greater detail:
- First, you check whether the app requested the correct permission, which is the one you added in your companion object.
- Next, you check if the result was OK, meaning the user clicked on the START NOW button.
- If they clicked the START NOW button, you’ll show a toast saying the cast will start.
- Otherwise, you show another toast saying the user denied the permission.
if you get an onActivity Result Overrides nothing error, make sure that you imported Intent
and Activity
properly by adding the code below to imports:
import android.content.Intent
import android.app.Activity
Permissions are a major part of using such APIs. Now that you have taken care of that, you are going to implement the Audio Capture API itself.
Creating MediaCaptureService
In the following section, you’ll create an Android Service that takes care of the Audio Capture.
You don’t need to know what an Android Service is for this tutorial, but if you’re curious, take a look at the Android Services tutorial.
In the Android View of the project, go to com.raywenderlich.android.cat_audio/services/MediaCaptureService. This file, and the code that you’ll add next, will be the same for almost any project you create. Some code has been pre-populated because the service is a really long class.
Starting the Service and Getting Notifications
Inside the service’s onCreate
, you’ll find a TODO to start the service and notifications. Add the code below to it:
// 1
createNotificationChannel()
// 2
startForeground(SERVICE_ID, NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID).build())
// 3
mediaProjectionManager = applicationContext.getSystemService(Context.MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
Here’s a breakdown of the code to understand what’s going on:
- First you create a notification channel. This is one of the prepopulated functions. If you don’t know what a notification channel is, you can learn more about it in this Notifications Tutorial.
- Then you start the service in the foreground. Starting audio capturing in the background may cause trouble if Android kills your app.
- Finally, you get the media projection service that the system provides, which will help capture the audio.
After adding that function, you may, again, see a couple of errors due to missing imports. Add the following to the top of the file:
import android.content.Context
import androidx.core.app.NotificationCompat
import android.media.projection.MediaProjectionManager
Build and run. You’ll now see toasts, one for when you click START NOW in the dialog that asks you whether you want to start the casting.
And the other if you choose to stop the audio capture.