/* VideoRecorder.java Copyright (c) 2014 NTT DOCOMO,INC. Released under the MIT license http://opensource.org/licenses/mit-license.php */ package org.deviceconnect.android.deviceplugin.host.recorder.video; import android.Manifest; import android.app.Activity; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.Camera; import android.media.MediaMetadataRetriever; import android.media.MediaRecorder; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.ResultReceiver; import android.provider.MediaStore; import android.provider.MediaStore.Video; import android.support.annotation.NonNull; import android.util.Log; import android.view.KeyEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; import android.view.View.OnClickListener; import android.view.Window; import android.widget.Button; import org.deviceconnect.android.activity.PermissionUtility; import org.deviceconnect.android.deviceplugin.host.BuildConfig; import org.deviceconnect.android.deviceplugin.host.R; import org.deviceconnect.android.deviceplugin.host.file.HostFileProvider; import org.deviceconnect.android.deviceplugin.host.mediaplayer.VideoConst; import org.deviceconnect.android.deviceplugin.host.recorder.HostDeviceRecorder; import org.deviceconnect.android.provider.FileManager; import java.io.File; import java.util.logging.Logger; /** * Video Recorder. * * @author NTT DOCOMO, INC. */ @SuppressWarnings("deprecation") public class VideoRecorderActivity extends Activity implements SurfaceHolder.Callback { /** ロガー. */ private final Logger mLogger = Logger.getLogger("host.dplugin"); /** MediaRecorder. */ private MediaRecorder mMediaRecorder; /** SurfaceHolder. */ private SurfaceHolder mHolder; /** Camera. */ private Camera mCamera; /** Picture size. */ private HostDeviceRecorder.PictureSize mPictureSize; /** フレームレート. */ private int mFps; /** ファイル管理クラス. */ private FileManager mFileMgr; /** フォルダURI. */ private File mFile; /** ファイル名. */ private String mFileName; /** Ready flag. */ private Boolean mIsReady = false; /** 初期化完了フラグ. */ private boolean mIsInitialized = false; /** 開始インテント。 */ private Intent mIntent; /** コールバック。 */ private ResultReceiver mCallback; /** 本アクティビティを起動したレコーダーID. */ private String mRecorderId; @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(R.layout.video_main); SurfaceView surfaceView = (SurfaceView) findViewById(R.id.surface_view); mHolder = surfaceView.getHolder(); mHolder.addCallback(this); Button stopBtn = (Button) findViewById(R.id.btn_stop); stopBtn.setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { finish(); } }); } @Override protected void onResume() { super.onResume(); // レシーバーを登録 IntentFilter filter = new IntentFilter(); filter.addAction(VideoConst.SEND_HOSTDP_TO_VIDEO); registerReceiver(mMyReceiver, filter); mIntent = getIntent(); if (mIntent == null) { finish(); return; } mCallback = mIntent.getParcelableExtra(VideoConst.EXTRA_CALLBACK); if (mCallback == null) { finish(); return; } mRecorderId = mIntent.getStringExtra(VideoConst.EXTRA_RECORDER_ID); if (mRecorderId == null) { finish(); return; } sendRecordingEvent(); if (!mIsInitialized) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PermissionUtility.requestPermissions(this, new Handler(Looper.getMainLooper()), new String[] { Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO, Manifest.permission.WRITE_EXTERNAL_STORAGE }, new PermissionUtility.PermissionRequestCallback() { @Override public void onSuccess() { try { initVideoContext(); mCallback.send(Activity.RESULT_OK, null); } catch (Exception e) { sendErrorCallback(e.getClass().getSimpleName() + ": " + e.getLocalizedMessage()); finish(); } } @Override public void onFail(@NonNull String deniedPermission) { sendErrorCallback("Permission " + deniedPermission + " not granted."); finish(); } }); } else { try { initVideoContext(); } catch (Exception e) { // e.printStackTrace(); Bundle data = new Bundle(); data.putString(VideoConst.EXTRA_CALLBACK_ERROR_MESSAGE, e.getClass().getSimpleName() + ": " + e.getLocalizedMessage()); mCallback.send(Activity.RESULT_CANCELED, data); finish(); } } } } private void sendErrorCallback(final String message) { Bundle data = new Bundle(); data.putString(VideoConst.EXTRA_CALLBACK_ERROR_MESSAGE, message); mCallback.send(Activity.RESULT_CANCELED, data); } private void sendRecordingEvent() { sendRecorderStateEvent(HostDeviceRecorder.RecorderState.RECORDING); } private void sendInactiveEvent() { sendRecorderStateEvent(HostDeviceRecorder.RecorderState.INACTTIVE); } private void sendRecorderStateEvent(final HostDeviceRecorder.RecorderState state) { mLogger.info("sendRecorderStateEvent: recorderId = " + mRecorderId + ", state = " + state.name()); Intent intent = new Intent(VideoConst.SEND_VIDEO_TO_HOSTDP); intent.putExtra(VideoConst.EXTRA_RECORDER_ID, mRecorderId); intent.putExtra(VideoConst.EXTRA_VIDEO_RECORDER_STATE, state); sendBroadcast(intent); } private void initVideoContext() { mFileMgr = new FileManager(this, HostFileProvider.class.getName()); mMediaRecorder = new MediaRecorder(); final int cameraId = mIntent.getIntExtra(VideoConst.EXTRA_CAMERA_ID, -1); mPictureSize = mIntent.getParcelableExtra(VideoConst.EXTRA_PICTURE_SIZE); mFps = mIntent.getIntExtra(VideoConst.EXTRA_FRAME_RATE, 10); mCamera = getCameraInstance(cameraId); setRequestParameters(mCamera); mCamera.unlock(); mFileName = mIntent.getStringExtra(VideoConst.EXTRA_FILE_NAME); if (mFileName != null) { mMediaRecorder.setCamera(mCamera); mFile = new File(mFileMgr.getBasePath(), mFileName); mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.DEFAULT); mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT); mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.MPEG_4_SP); mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); mMediaRecorder.setVideoSize(mPictureSize.getWidth(), mPictureSize.getHeight()); mMediaRecorder.setOutputFile(mFile.toString()); mLogger.info("VideoRecorderActivity: " + mPictureSize); mCallback.send(Activity.RESULT_OK, null); } else { Bundle data = new Bundle(); data.putString(VideoConst.EXTRA_CALLBACK_ERROR_MESSAGE, "File name must be specified."); mCallback.send(Activity.RESULT_CANCELED, data); finish(); return; } mIsInitialized = true; } private void setRequestParameters(final Camera camera) { HostDeviceRecorder.PictureSize currentSize = mPictureSize; if (camera != null && currentSize != null) { Camera.Parameters params = camera.getParameters(); params.setPictureSize(currentSize.getWidth(), currentSize.getHeight()); camera.setParameters(params); } } @Override protected void onPause() { super.onPause(); releaseMediaRecorder(); releaseCamera(); mIsInitialized = false; if (mHolder != null) { mHolder = null; } // レシーバーを削除 unregisterReceiver(mMyReceiver); if (checkVideoFile()) { // Content Providerに登録する. MediaMetadataRetriever mediaMeta = new MediaMetadataRetriever(); mediaMeta.setDataSource(mFile.toString()); ContentResolver resolver = getApplicationContext().getContentResolver(); ContentValues values = new ContentValues(); values.put(Video.Media.TITLE, mFileName); values.put(Video.Media.DISPLAY_NAME, mFileName); values.put(Video.Media.ARTIST, "DeviceConnect"); values.put(Video.Media.MIME_TYPE, VideoConst.FORMAT_TYPE); values.put(Video.Media.DATA, mFile.toString()); resolver.insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values); } sendInactiveEvent(); } /** * Check the existence of file. * * @return true is exist */ private boolean checkVideoFile() { return mFile != null && mFile.exists() && mFile.length() > 0; } /** * MediaRecorderを解放. */ private void releaseMediaRecorder() { if (mMediaRecorder != null) { if (mIsReady) { try { mMediaRecorder.stop(); } catch (RuntimeException e) { // stop failed mLogger.warning("stop failed"); } } mMediaRecorder.reset(); mMediaRecorder.release(); mMediaRecorder = null; mIsReady = false; } } /** * Cameraのインスタンスを取得. * * @return Camera ID. * @return Cameraのインスタンス */ private synchronized Camera getCameraInstance(final int cameraId) { return Camera.open(cameraId); } /** * Release camera. */ private synchronized void releaseCamera() { if (mCamera != null) { mCamera.lock(); mCamera.release(); mCamera = null; } } @Override public void surfaceCreated(final SurfaceHolder holder) { } @Override public void surfaceChanged(final SurfaceHolder holder, final int format, final int width, final int height) { if (mIsInitialized) { try { mHolder = holder; if (mIsReady) { mMediaRecorder.setPreviewDisplay(null); } mMediaRecorder.setPreviewDisplay(mHolder.getSurface()); mMediaRecorder.prepare(); mMediaRecorder.start(); mIsReady = true; } catch (Throwable throwable) { if (BuildConfig.DEBUG) { throwable.printStackTrace(); } } } } @Override public void surfaceDestroyed(final SurfaceHolder holder) { } @Override public boolean onKeyDown(final int keyCode, final KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_HOME) { finish(); return true; } else { return false; } } /** * 受信用のReceiver. */ private BroadcastReceiver mMyReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { if (intent.getAction().equals(VideoConst.SEND_HOSTDP_TO_VIDEO)) { String videoAction = intent.getStringExtra(VideoConst.EXTRA_NAME); if (videoAction.equals(VideoConst.EXTRA_VALUE_VIDEO_RECORD_STOP)) { finish(); } } } }; }