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.

4.8 (4) · 1 Review

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

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:

  1. It initializes the Media Projection Manager, which is an Android Service with all the necessary logic to capture audio.
  2. 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.

Note: At this point, you may wonder why you’re asking for permissions again if you already had them for the 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:

Permission prompt starting with: Start recording or casting with Cat Sounds?

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:

  1. First, you check whether the app requested the correct permission, which is the one you added in your companion object.
  2. Next, you check if the result was OK, meaning the user clicked on the START NOW button.
  3. If they clicked the START NOW button, you’ll show a toast saying the cast will start.
  4. 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:

  1. 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.
  2. Then you start the service in the foreground. Starting audio capturing in the background may cause trouble if Android kills your app.
  3. 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.

Start Audio Capture Toast

And the other if you choose to stop the audio capture.

Stop Audio Capture Toast