com.example.android.mediarecorder.MainActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.example.android.mediarecorder.MainActivity.java

Source

/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.mediarecorder;

import android.Manifest;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.Camera;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
import android.media.CamcorderProfile;
import android.media.MediaRecorder;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.Size;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.Menu;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.example.android.common.media.CameraHelper;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *  This activity uses the camera/camcorder as the A/V source for the {@link android.media.MediaRecorder} API.
 *  A {@link android.view.TextureView} is used as the camera preview which limits the code to API 14+. This
 *  can be easily replaced with a {@link android.view.SurfaceView} to run on older devices.
 */
public class MainActivity extends Activity
        implements SurfaceHolder.Callback, ActivityCompat.OnRequestPermissionsResultCallback {

    private static final int REQUEST_ALL_PERMISSIONS = 1;
    private Camera mCamera;
    private SurfaceView mPreview;
    private MediaRecorder mMediaRecorder;
    private File mOutputFile;

    private boolean isRecording = false;
    private static final String TAG = "Recorder";
    private Button captureButton;
    private SurfaceHolder mHolder;
    private int sensorOrientation;
    private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90;
    private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270;
    private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray();
    private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray();

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

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

    private int orientation;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample_main);

        mPreview = (SurfaceView) findViewById(R.id.surface_view);
        captureButton = (Button) findViewById(R.id.button_capture);
        if (checkForPermissions()) {
            preparePreview();
        }
    }

    /**
     * The capture button controls all user interaction. When recording, the button click
     * stops recording, releases {@link android.media.MediaRecorder} and {@link android.hardware.Camera}. When not recording,
     * it prepares the {@link android.media.MediaRecorder} and starts recording.
     *
     * @param view the view generating the event.
     */
    public void onCaptureClick(View view) {
        Log.i(TAG, "ON CAPTURE CLICK ");
        if (isRecording) {
            // BEGIN_INCLUDE(stop_release_media_recorder)

            // stop recording and release camera
            Toast.makeText(this, "Stopping Capture", Toast.LENGTH_SHORT).show();
            try {
                Log.i(TAG, "Stopping Capture");
                mMediaRecorder.stop(); // stop the recording
                Toast.makeText(this, "Saving file: " + mOutputFile, Toast.LENGTH_SHORT).show();
            } catch (RuntimeException e) {
                // RuntimeException is thrown when stop() is called immediately after start().
                // In this case the output file is not properly constructed and should be deleted.
                Log.d(TAG, "RuntimeException: stop() is called immediately after start()");
                Toast.makeText(this, "Failed to save video", Toast.LENGTH_SHORT).show();
                //noinspection ResultOfMethodCallIgnored
                mOutputFile.delete();
            }
            releaseMediaRecorder(); // release the MediaRecorder object
            mCamera.lock(); // take camera access back from MediaRecorder

            // inform the user that recording has stopped
            setCaptureButtonText("Capture");
            isRecording = false;
            releaseCamera();
            preparePreview();
            // END_INCLUDE(stop_release_media_recorder)
            Log.i(TAG, "Starting new Camera Preview");
            preparePreview();
        } else {

            // BEGIN_INCLUDE(prepare_start_media_recorder)

            new MediaPrepareTask().execute(null, null, null);

            // END_INCLUDE(prepare_start_media_recorder)

        }
    }

    private void setCaptureButtonText(String title) {
        captureButton.setText(title);
    }

    @Override
    protected void onPause() {
        super.onPause();
        // if we are using MediaRecorder, release it first
        releaseMediaRecorder();
        // release the camera immediately on pause event
        releaseCamera();
    }

    private void releaseMediaRecorder() {
        if (mMediaRecorder != null) {
            // clear recorder configuration
            mMediaRecorder.reset();
            // release the recorder object
            mMediaRecorder.release();
            mMediaRecorder = null;
            // Lock camera for later use i.e taking it back from MediaRecorder.
            // MediaRecorder doesn't need it anymore and we will release it if the activity pauses.
            mCamera.lock();
        }
    }

    private void releaseCamera() {
        if (mCamera != null) {
            // release the camera for other applications
            mCamera.release();
            mCamera = null;
        }
    }

    private boolean preparePreview() {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
            try {
                CameraManager manager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);
                String id = manager.getCameraIdList()[0];
                CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
                sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
            } catch (CameraAccessException e) {
                Log.e("ERROR", "Cannot access Camera2 API");
                e.printStackTrace();
            }
        }

        Log.i(TAG, "Preparing Camera Preview");
        // BEGIN_INCLUDE (configure_preview)
        //        mCamera = CameraHelper.getDefaultCameraInstance();

        // We need to make sure that our preview and recording video size are supported by the
        // camera. Query camera to find all the sizes and choose the optimal size given the
        // dimensions of our preview surface.
        Camera.Parameters parameters = mCamera.getParameters();
        List<Camera.Size> mSupportedPreviewSizes = parameters.getSupportedPreviewSizes();
        List<Camera.Size> mSupportedVideoSizes = parameters.getSupportedVideoSizes();
        Camera.Size optimalSize = CameraHelper.getOptimalVideoSize(mSupportedVideoSizes, mSupportedPreviewSizes,
                mPreview.getWidth(), mPreview.getHeight());

        // Use the same size for recording profile.
        CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
        profile.videoFrameWidth = optimalSize.width;
        profile.videoFrameHeight = optimalSize.height;

        // likewise for the camera object itself.
        parameters.setPreviewSize(profile.videoFrameWidth, profile.videoFrameHeight);
        mCamera.setParameters(parameters);
        try {
            // Requires API level 11+, For backward compatibility use {@link setPreviewDisplay}
            // with {@link SurfaceView}
            //                mCamera.setPreviewTexture(mPreview.getSurfaceTexture());

            mHolder = mPreview.getHolder();
            mHolder.addCallback(this);
            mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
            //            mHolder.setKeepScreenOn(true);

            setCameraDisplayOrientation(0);
            mCamera.setPreviewDisplay(mPreview.getHolder());
            mCamera.startPreview();
            Log.i(TAG, "Setting Preview Display");
        } catch (IOException e) {
            Log.e(TAG, "Surface texture is unavailable or unsuitable" + e.getMessage());
            return false;
        }
        // END_INCLUDE (configure_preview)
        return true;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private boolean prepareVideoRecorder() {
        if (!preparePreview()) {
            return false;
        }
        CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);

        // BEGIN_INCLUDE (configure_media_recorder)
        mMediaRecorder = new MediaRecorder();

        // Step 1: Unlock and set camera to MediaRecorder
        mCamera.unlock();
        mMediaRecorder.setCamera(mCamera);

        // Step 2: Set sources
        mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
        mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);

        // Step 3: Set a CamcorderProfile (requires API Level 8 or higher)
        mMediaRecorder.setProfile(profile);

        // orientation
        int rotation = getWindowManager().getDefaultDisplay().getRotation();
        Log.i("TESTING", "Sensor Orientation: " + sensorOrientation);
        if (sensorOrientation == SENSOR_ORIENTATION_DEFAULT_DEGREES) {
            Log.i("TESTING", "Recorder: DEFAULT ROTATION: " + DEFAULT_ORIENTATIONS.get(rotation));
            mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation));
        } else {
            Log.i("TESTING", "Recorder: INVERSING ROTATION: " + INVERSE_ORIENTATIONS.get(rotation));
            mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation));
        }

        // Step 4: Set output file
        mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO);
        if (mOutputFile == null) {
            return false;
        }
        mMediaRecorder.setOutputFile(mOutputFile.getPath());
        // END_INCLUDE (configure_media_recorder)

        // Step 5: Prepare configured MediaRecorder
        try {
            mMediaRecorder.prepare();
        } catch (IllegalStateException e) {
            Log.d(TAG, "IllegalStateException preparing MediaRecorder: " + e.getMessage());
            releaseMediaRecorder();
            return false;
        } catch (IOException e) {
            Log.d(TAG, "IOException preparing MediaRecorder: " + e.getMessage());
            releaseMediaRecorder();
            return false;
        }
        return true;
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            if (mCamera != null) {
                mCamera.setPreviewDisplay(mHolder);
            }
        } catch (IOException exception) {
            Log.e(TAG, "IOException caused by setPreviewDisplay()", exception);
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        Log.i("TESTING", "Surface changed called");

        // If your preview can change or rotate, take care of those events here.
        // Make sure to stop the preview before resizing or reformatting it.
        if (mHolder.getSurface() == null) {
            Log.e("ERROR", "Preview Surface does not exist!");
            return;
        }

        // stop preview before making changes
        try {
            mCamera.stopPreview();
        } catch (Exception e) {
            Log.e("ERROR", "Tried to stop a non-existent preview!");
        }

        // set preview size and make any resize, rotate or
        // reformatting changes here

        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(mHolder);
            mCamera.startPreview();
        } catch (Exception e) {
            Log.d("ERROR", "Error starting mCamera preview: " + e.getMessage());
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        try {
            releaseCamera();
        } catch (Exception e) {
            Log.e(TAG, "Camera release failure.");
        }
    }

    public void setCameraDisplayOrientation(int cameraId) {
        Log.i("TESTING", "Set Camera Display Orientation Called");
        android.hardware.Camera.CameraInfo info = new android.hardware.Camera.CameraInfo();
        android.hardware.Camera.getCameraInfo(cameraId, info);
        int rotation = getWindowManager().getDefaultDisplay().getRotation();

        Log.i("TESTING", "Screen Rotation: " + rotation);
        Log.i("TESTING", "Sensor Orientation: " + sensorOrientation);
        if (sensorOrientation == SENSOR_ORIENTATION_DEFAULT_DEGREES) {
            Log.i("TESTING", "Camera: DEFAULT ROTATION: " + DEFAULT_ORIENTATIONS.get(rotation));

            int degrees = 0;
            switch (rotation) {
            case Surface.ROTATION_0:
                degrees = 0;
                break;
            case Surface.ROTATION_90:
                degrees = 90;
                break;
            case Surface.ROTATION_180:
                degrees = 180;
                break;
            case Surface.ROTATION_270:
                degrees = 270;
                break;
            }

            if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                orientation = (info.orientation + degrees) % 360;
                orientation = (360 - orientation) % 360; // compensate the mirror
                Log.i("TESTING", "Front Facing Camera Orientation: " + orientation);
            } else { // back-facing
                orientation = (info.orientation - degrees + 360) % 360;
                Log.i("TESTING", "Back Facing Camera Orientation: " + orientation);
            }
            Log.i("TESTING", "Set Camera Display Orientation Finished");
        } else {
            Log.i("TESTING", "Camera: INVERSING ROTATION: " + INVERSE_ORIENTATIONS.get(rotation));
            orientation = INVERSE_ORIENTATIONS.get(rotation);
        }
        mCamera.setDisplayOrientation(orientation);
    }

    /**
     * Asynchronous task for preparing the {@link android.media.MediaRecorder} since it's a long blocking
     * operation.
     */

    class MediaPrepareTask extends AsyncTask<Void, Void, Boolean> {
        @Override
        protected Boolean doInBackground(Void... voids) {
            // initialize video camera
            if (prepareVideoRecorder()) {
                // Camera is available and unlocked, MediaRecorder is prepared,
                // now you can start recording
                mMediaRecorder.start();

                isRecording = true;
            } else {
                // prepare didn't work, release the camera
                releaseMediaRecorder();
                return false;
            }
            return true;
        }

        @Override
        protected void onPostExecute(Boolean result) {
            if (!result) {
                MainActivity.this.finish();
            }
            // inform the user that recording has started
            setCaptureButtonText("Stop");
        }
    }

    public boolean checkForPermissions() {
        ArrayList<String> permissionsToGrant = new ArrayList<>();

        // "Dangerous" Permissions:
        // http://stackoverflow.com/questions/36936914/list-of-android-permissions-normal-permissions-and-dangerous-permissions-in-api
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(
                    android.Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                permissionsToGrant.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
            } else {
                Log.v("VERBOSE", "External storage permission is granted");
            }
            if (ActivityCompat.checkSelfPermission(MainActivity.this,
                    Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                permissionsToGrant.add(Manifest.permission.CAMERA);
            } else {
                Log.v("VERBOSE", "Camera Permission granted");
            }
        }
        // permission is automatically granted on sdk<23 upon installation
        Log.v("VERBOSE", "External storage permission is automatically granted");

        if (permissionsToGrant.size() > 0) {
            ActivityCompat.requestPermissions(MainActivity.this,
                    permissionsToGrant.toArray(new String[permissionsToGrant.size()]), REQUEST_ALL_PERMISSIONS);
            return false;
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        Log.i("TESTING", "onRequestPermissions Called");
        switch (requestCode) {
        case REQUEST_ALL_PERMISSIONS: {
            Log.i("TESTING", "Permissions granted: Loading...");
            Toast.makeText(this, "Permissions granted: Loading...", Toast.LENGTH_SHORT).show();
            preparePreview();
            return;
        }
        default: {
            Log.e("ERROR", "Permissions denied: Cannot load UI");
            Toast.makeText(this, "Permissions denied: Cannot load UI", Toast.LENGTH_SHORT).show();
            return;
        }
        }
    }
}