Capture and Record Android Screen using MediaProjection APIs - Truiton
Skip to content

Capture and Record Android Screen using MediaProjection APIs

Android MediaProjection Featured

One of the new APIs introduced in Android 5.0 is the MediaProjection API. According to official documentation this API gives us the ability to capture screen contents with audio recording. As you may know screen cast is one of the new features introduced in Android. This Android MediaProjection API is the core API through which the screen cast takes place. By using this Android MediaProjection API we can also share screen over the network. This API also solves a very basic function which was needed in Android i.e. recording of screen programmatically. But sadly there are no samples available on how to do this. Therefore in this Capture and Record Android Screen using MediaProjection APIs tutorial, I would make an example where a video of device screen would be recorded, with audio.

Although Android media projection API solves the problem of recording and capturing device’s screen, but since it was introduced in lollipop version of android therefore as of now it cannot be used on API version lower than 21.

Android Media Projection Example : Record Screen Video by Code

To record our screen activity using Android media projection class we will be using MediaRecorder class, which would actually record the screen activity. Android media recorder class is a simple class used for recording any type of audio and video. In this example we will be using the MediaRecorder class to capture the output of MediaProjection class once the capture intent is fired. But before doing so please add these permissions in your manifest.

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

Please Note: If you don’t wish to record audio, then you don’t need to add the record audio permission. Only write external storage would be required.

Next, to use Android MediaProjection API lets make a layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="#FFFFFF"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                tools:context=".MainActivity">


    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:text="Recording Status: "
        android:textAppearance="?android:attr/textAppearanceLarge"/>

    <ToggleButton
        android:id="@+id/toggle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/textView"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="5dp"
        android:text="Start"/>

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="150dp"
        android:layout_height="150dp"
        android:layout_below="@+id/toggle"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="85dp"
        android:src="@mipmap/truiton_short"/>
</RelativeLayout>

Please note: truiton_short is just a drawable image, in your code, you may choose exclude it.

The above file would give a layout like this:

Android Record Screen using MediaProjection

Next lets have a look at the main activity where screen recording would take place using the Android media projection APIs.

package com.truiton.screencapture;

import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.media.MediaRecorder;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Surface;
import android.view.View;
import android.widget.Toast;
import android.widget.ToggleButton;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final int REQUEST_CODE = 1000;
    private int mScreenDensity;
    private MediaProjectionManager mProjectionManager;
    private static final int DISPLAY_WIDTH = 720;
    private static final int DISPLAY_HEIGHT = 1280;
    private MediaProjection mMediaProjection;
    private VirtualDisplay mVirtualDisplay;
    private MediaProjectionCallback mMediaProjectionCallback;
    private ToggleButton mToggleButton;
    private MediaRecorder mMediaRecorder;
    private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
    private static final int REQUEST_PERMISSIONS = 10;

    static {
        ORIENTATIONS.append(Surface.ROTATION_0, 90);
        ORIENTATIONS.append(Surface.ROTATION_90, 0);
        ORIENTATIONS.append(Surface.ROTATION_180, 270);
        ORIENTATIONS.append(Surface.ROTATION_270, 180);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DisplayMetrics metrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(metrics);
        mScreenDensity = metrics.densityDpi;

        mMediaRecorder = new MediaRecorder();

        mProjectionManager = (MediaProjectionManager) getSystemService
                (Context.MEDIA_PROJECTION_SERVICE);

        mToggleButton = (ToggleButton) findViewById(R.id.toggle);
        mToggleButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (ContextCompat.checkSelfPermission(MainActivity.this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE) + ContextCompat
                        .checkSelfPermission(MainActivity.this,
                                Manifest.permission.RECORD_AUDIO)
                        != PackageManager.PERMISSION_GRANTED) {
                    if (ActivityCompat.shouldShowRequestPermissionRationale
                            (MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ||
                            ActivityCompat.shouldShowRequestPermissionRationale
                                    (MainActivity.this, Manifest.permission.RECORD_AUDIO)) {
                        mToggleButton.setChecked(false);
                        Snackbar.make(findViewById(android.R.id.content), R.string.label_permissions,
                                Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                                new View.OnClickListener() {
                                    @Override
                                    public void onClick(View v) {
                                        ActivityCompat.requestPermissions(MainActivity.this,
                                                new String[]{Manifest.permission
                                                        .WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO},
                                                REQUEST_PERMISSIONS);
                                    }
                                }).show();
                    } else {
                        ActivityCompat.requestPermissions(MainActivity.this,
                                new String[]{Manifest.permission
                                        .WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO},
                                REQUEST_PERMISSIONS);
                    }
                } else {
                    onToggleScreenShare(v);
                }
            }
        });
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != REQUEST_CODE) {
            Log.e(TAG, "Unknown request code: " + requestCode);
            return;
        }
        if (resultCode != RESULT_OK) {
            Toast.makeText(this,
                    "Screen Cast Permission Denied", Toast.LENGTH_SHORT).show();
            mToggleButton.setChecked(false);
            return;
        }
        mMediaProjectionCallback = new MediaProjectionCallback();
        mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
        mMediaProjection.registerCallback(mMediaProjectionCallback, null);
        mVirtualDisplay = createVirtualDisplay();
        mMediaRecorder.start();
    }

    public void onToggleScreenShare(View view) {
        if (((ToggleButton) view).isChecked()) {
            initRecorder();
            shareScreen();
        } else {
            mMediaRecorder.stop();
            mMediaRecorder.reset();
            Log.v(TAG, "Stopping Recording");
            stopScreenSharing();
        }
    }

    private void shareScreen() {
        if (mMediaProjection == null) {
            startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_CODE);
            return;
        }
        mVirtualDisplay = createVirtualDisplay();
        mMediaRecorder.start();
    }

    private VirtualDisplay createVirtualDisplay() {
        return mMediaProjection.createVirtualDisplay("MainActivity",
                DISPLAY_WIDTH, DISPLAY_HEIGHT, mScreenDensity,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                mMediaRecorder.getSurface(), null /*Callbacks*/, null
                /*Handler*/);
    }

    private void initRecorder() {
        try {
            mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
            mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            mMediaRecorder.setOutputFile(Environment
                    .getExternalStoragePublicDirectory(Environment
                            .DIRECTORY_DOWNLOADS) + "/video.mp4");
            mMediaRecorder.setVideoSize(DISPLAY_WIDTH, DISPLAY_HEIGHT);
            mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
            mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            mMediaRecorder.setVideoEncodingBitRate(512 * 1000);
            mMediaRecorder.setVideoFrameRate(30);
            int rotation = getWindowManager().getDefaultDisplay().getRotation();
            int orientation = ORIENTATIONS.get(rotation + 90);
            mMediaRecorder.setOrientationHint(orientation);
            mMediaRecorder.prepare();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private class MediaProjectionCallback extends MediaProjection.Callback {
        @Override
        public void onStop() {
            if (mToggleButton.isChecked()) {
                mToggleButton.setChecked(false);
                mMediaRecorder.stop();
                mMediaRecorder.reset();
                Log.v(TAG, "Recording Stopped");
            }
            mMediaProjection = null;
            stopScreenSharing();
        }
    }

    private void stopScreenSharing() {
        if (mVirtualDisplay == null) {
            return;
        }
        mVirtualDisplay.release();
        //mMediaRecorder.release(); //If used: mMediaRecorder object cannot
        // be reused again
        destroyMediaProjection();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        destroyMediaProjection();
    }

    private void destroyMediaProjection() {
        if (mMediaProjection != null) {
            mMediaProjection.unregisterCallback(mMediaProjectionCallback);
            mMediaProjection.stop();
            mMediaProjection = null;
        }
        Log.i(TAG, "MediaProjection Stopped");
    }

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull String permissions[],
                                           @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_PERMISSIONS: {
                if ((grantResults.length > 0) && (grantResults[0] +
                        grantResults[1]) == PackageManager.PERMISSION_GRANTED) {
                    onToggleScreenShare(mToggleButton);
                } else {
                    mToggleButton.setChecked(false);
                    Snackbar.make(findViewById(android.R.id.content), R.string.label_permissions,
                            Snackbar.LENGTH_INDEFINITE).setAction("ENABLE",
                            new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    Intent intent = new Intent();
                                    intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                                    intent.addCategory(Intent.CATEGORY_DEFAULT);
                                    intent.setData(Uri.parse("package:" + getPackageName()));
                                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                                    startActivity(intent);
                                }
                            }).show();
                }
                return;
            }
        }
    }
}

As you may see above, to start a media projection first an intent is created using createScreenCaptureIntent() method of MediaProjectionManager class. When user approves the screen cast request, media projection is started from the onActivityResult method, along with the media recording. By doing this, we are allowing the app to start a screen cast which is recorded on the device itself. This would generate an audio/video file in mp4 format on your SD card. Although if you don’t wish to record audio, you can can remove the setAudioSource and setAudioEncoder lines from the initRecorder() method. To view the full source code please visit the repo below:

Full Source

The place where both Android MediaProjection class and MediaRecording class interact is when creating a virtual display using the createVirtualDisplay method. This method basically creates a virtual display where the screen contents are buffered on to a Surface. To capture the screen contents in a MediaRecorder, I initialized a media recorder above with video source as a surface using the MediaRecorder.VideoSource.SURFACE parameter. Then in the createVirtualDisplay method I specified the surface of media recorder by using mMediaRecorder.getSurface() method. By doing this I gave the input to media recorder from the media projection class in real time, through which a screen capture video recording file is generated. Hope this helps. Connect with us on Facebook, Twitter, and Google+.

50 thoughts on “Capture and Record Android Screen using MediaProjection APIs”

  1. Thanks for the article!!! This is amazing and I learned a lot. Do you know how to capture system audio as well? Actually what I’m trying to do is capturing screen and sound at the same time but I’m not sure that’s possible without rooting.

  2. Hello Mohit,
    Thanks for this nice tutorial, actually i m doing work on the app that can share my screen to another android device so can i achieve this functionality using this code?

  3. Hi above code is not working on android M , Fatal error is coming at mediarecorder.stop due to illegalstate exception.
    can you please check and let me know the solution.

  4. above code is working fine for Android L , but for android M its not working .
    its giving fatal error in mediarecorder.stop
    can you please check and provide some solution ?

  5. Have problem setting up the audio in mediaRecorder (setAudioSource failed.). I commented out the audio code, but now it crashes when I press the toggle button.
    Phone used : Nexus 5 with Android M
    Error: failed to get surface in “createVirtualDisplay()” method.

  6. Thank you for the excellent code.
    I can run it on Android 5.1. But the frame rate of the recorded video is less than 1 frame per second. Any idea? Thanks.

  7. Hello nice tutorial..
    But is it possible to directly grant permission without asking every time while screen recording?

  8. Hi Mohit I am using this same code…
    Its working fine on most of the devices but crashing on few device of android 6.0

    mainly in MI-3S PRIME

  9. Hi,
    Can you please show a sample of how to use this for a single screenshot ? Or one after another, by demand?

  10. Hi,

    Your post is great, but one issue:

    Your solution works fabulously on Android 6 & Android 7, but on Android 5 it records only the voice. I’m working on Genymotion emulators.

    Regards,
    Harlan

  11. Hi Mohit,

    Its very useful code in my scenario. But I would like to ask one more question is that it is possible to record screen on particular area not whole screen.

    Thanks in advance.

    Regards
    Raj K

  12. Thank you very much for the tutorial. It helped me a lot.

    Do you know how to improve the quality of the video taken? It looks very pixelated.

  13. the code works fine, how ever there is an issue,

    the problem is if we start recording in portrait mode , the video starts recording the full screen, how ever while we are recording, if we change orientation to landscape the width and height of video remains same, but the screen recorded is smaller and remaining space is taken by black screen. There are some apps which continue to change the width and height on the go even when orientation is changed, and record the whole screen.

    So the question is how can we maintain the screen recording to whole screen and not cause the black screen, without stopping the recording?

    1. Hi pawaom! Were you able to solve this? I found a repo somewhere that does it but It doesn’t seem to record the MIC audio (a feature I need). Cheers!

  14. Thank you very Much Sir for sharing the codes ..

    My question is where does the recorded file goes ?

    Because I cant find them on my mobile phone .. Thank you …

  15. Hello Sir

    The above code is working perfectly for screen recording.

    But how to record only a single relative layout (which is holding some child views) ?

    Thanks..

  16. Hi Mohit! I run into your code for something very similar I’m trying to do. Have you tried using this code to record the screen on landscape mode? It records, but it adds black padding on top and bottom…making the whole recording on landscape mode pointless. I don’t know if it’s a virtual display thing, a media recorder orientation hint thing (have tried varying both) but I can’t seem to get it right. Any thoughts? Cheers!

  17. Is there any tutorial how to cast a stream for the screen instead of recording ? , like sending it as rtsp or tcp or udp frames to a receiver such omxplayer or ffmpeg?
    if there is . please contact me and i’ll donate for your activity

  18. thank you for your code but how to modify it little like start recording on activity pause and start on resume can you plz help me out in this regard ?

Leave a Reply

Your email address will not be published. Required fields are marked *