1 /* 2 * Copyright 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.example.android.camera2video; 18 19 import android.Manifest; 20 import android.app.Activity; 21 import android.app.AlertDialog; 22 import android.app.Dialog; 23 import android.app.DialogFragment; 24 import android.app.Fragment; 25 import android.content.Context; 26 import android.content.DialogInterface; 27 import android.content.pm.PackageManager; 28 import android.content.res.Configuration; 29 import android.graphics.Matrix; 30 import android.graphics.RectF; 31 import android.graphics.SurfaceTexture; 32 import android.hardware.camera2.CameraAccessException; 33 import android.hardware.camera2.CameraCaptureSession; 34 import android.hardware.camera2.CameraCharacteristics; 35 import android.hardware.camera2.CameraDevice; 36 import android.hardware.camera2.CameraManager; 37 import android.hardware.camera2.CameraMetadata; 38 import android.hardware.camera2.CaptureRequest; 39 import android.hardware.camera2.params.StreamConfigurationMap; 40 import android.media.MediaRecorder; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.HandlerThread; 44 import android.support.annotation.NonNull; 45 import android.support.v13.app.FragmentCompat; 46 import android.support.v4.app.ActivityCompat; 47 import android.util.Log; 48 import android.util.Size; 49 import android.util.SparseIntArray; 50 import android.view.LayoutInflater; 51 import android.view.Surface; 52 import android.view.TextureView; 53 import android.view.View; 54 import android.view.ViewGroup; 55 import android.widget.Button; 56 import android.widget.Toast; 57 58 import java.io.IOException; 59 import java.util.ArrayList; 60 import java.util.Arrays; 61 import java.util.Collections; 62 import java.util.Comparator; 63 import java.util.List; 64 import java.util.concurrent.Semaphore; 65 import java.util.concurrent.TimeUnit; 66 67 public class Camera2VideoFragment extends Fragment 68 implements View.OnClickListener, FragmentCompat.OnRequestPermissionsResultCallback { 69 70 private static final int SENSOR_ORIENTATION_DEFAULT_DEGREES = 90; 71 private static final int SENSOR_ORIENTATION_INVERSE_DEGREES = 270; 72 private static final SparseIntArray DEFAULT_ORIENTATIONS = new SparseIntArray(); 73 private static final SparseIntArray INVERSE_ORIENTATIONS = new SparseIntArray(); 74 75 private static final String TAG = "Camera2VideoFragment"; 76 private static final int REQUEST_VIDEO_PERMISSIONS = 1; 77 private static final String FRAGMENT_DIALOG = "dialog"; 78 79 private static final String[] VIDEO_PERMISSIONS = { 80 Manifest.permission.CAMERA, 81 Manifest.permission.RECORD_AUDIO, 82 }; 83 84 static { DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90)85 DEFAULT_ORIENTATIONS.append(Surface.ROTATION_0, 90); DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0)86 DEFAULT_ORIENTATIONS.append(Surface.ROTATION_90, 0); DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270)87 DEFAULT_ORIENTATIONS.append(Surface.ROTATION_180, 270); DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180)88 DEFAULT_ORIENTATIONS.append(Surface.ROTATION_270, 180); 89 } 90 91 static { INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270)92 INVERSE_ORIENTATIONS.append(Surface.ROTATION_0, 270); INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180)93 INVERSE_ORIENTATIONS.append(Surface.ROTATION_90, 180); INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90)94 INVERSE_ORIENTATIONS.append(Surface.ROTATION_180, 90); INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0)95 INVERSE_ORIENTATIONS.append(Surface.ROTATION_270, 0); 96 } 97 98 /** 99 * An {@link AutoFitTextureView} for camera preview. 100 */ 101 private AutoFitTextureView mTextureView; 102 103 /** 104 * Button to record video 105 */ 106 private Button mButtonVideo; 107 108 /** 109 * A refernce to the opened {@link android.hardware.camera2.CameraDevice}. 110 */ 111 private CameraDevice mCameraDevice; 112 113 /** 114 * A reference to the current {@link android.hardware.camera2.CameraCaptureSession} for 115 * preview. 116 */ 117 private CameraCaptureSession mPreviewSession; 118 119 /** 120 * {@link TextureView.SurfaceTextureListener} handles several lifecycle events on a 121 * {@link TextureView}. 122 */ 123 private TextureView.SurfaceTextureListener mSurfaceTextureListener 124 = new TextureView.SurfaceTextureListener() { 125 126 @Override 127 public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, 128 int width, int height) { 129 openCamera(width, height); 130 } 131 132 @Override 133 public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture, 134 int width, int height) { 135 configureTransform(width, height); 136 } 137 138 @Override 139 public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) { 140 return true; 141 } 142 143 @Override 144 public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { 145 } 146 147 }; 148 149 /** 150 * The {@link android.util.Size} of camera preview. 151 */ 152 private Size mPreviewSize; 153 154 /** 155 * The {@link android.util.Size} of video recording. 156 */ 157 private Size mVideoSize; 158 159 /** 160 * MediaRecorder 161 */ 162 private MediaRecorder mMediaRecorder; 163 164 /** 165 * Whether the app is recording video now 166 */ 167 private boolean mIsRecordingVideo; 168 169 /** 170 * An additional thread for running tasks that shouldn't block the UI. 171 */ 172 private HandlerThread mBackgroundThread; 173 174 /** 175 * A {@link Handler} for running tasks in the background. 176 */ 177 private Handler mBackgroundHandler; 178 179 /** 180 * A {@link Semaphore} to prevent the app from exiting before closing the camera. 181 */ 182 private Semaphore mCameraOpenCloseLock = new Semaphore(1); 183 184 /** 185 * {@link CameraDevice.StateCallback} is called when {@link CameraDevice} changes its status. 186 */ 187 private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() { 188 189 @Override 190 public void onOpened(CameraDevice cameraDevice) { 191 mCameraDevice = cameraDevice; 192 startPreview(); 193 mCameraOpenCloseLock.release(); 194 if (null != mTextureView) { 195 configureTransform(mTextureView.getWidth(), mTextureView.getHeight()); 196 } 197 } 198 199 @Override 200 public void onDisconnected(CameraDevice cameraDevice) { 201 mCameraOpenCloseLock.release(); 202 cameraDevice.close(); 203 mCameraDevice = null; 204 } 205 206 @Override 207 public void onError(CameraDevice cameraDevice, int error) { 208 mCameraOpenCloseLock.release(); 209 cameraDevice.close(); 210 mCameraDevice = null; 211 Activity activity = getActivity(); 212 if (null != activity) { 213 activity.finish(); 214 } 215 } 216 217 }; 218 private Integer mSensorOrientation; 219 private String mNextVideoAbsolutePath; 220 private CaptureRequest.Builder mPreviewBuilder; 221 private Surface mRecorderSurface; 222 newInstance()223 public static Camera2VideoFragment newInstance() { 224 return new Camera2VideoFragment(); 225 } 226 227 /** 228 * In this sample, we choose a video size with 3x4 aspect ratio. Also, we don't use sizes 229 * larger than 1080p, since MediaRecorder cannot handle such a high-resolution video. 230 * 231 * @param choices The list of available sizes 232 * @return The video size 233 */ chooseVideoSize(Size[] choices)234 private static Size chooseVideoSize(Size[] choices) { 235 for (Size size : choices) { 236 if (size.getWidth() == size.getHeight() * 4 / 3 && size.getWidth() <= 1080) { 237 return size; 238 } 239 } 240 Log.e(TAG, "Couldn't find any suitable video size"); 241 return choices[choices.length - 1]; 242 } 243 244 /** 245 * Given {@code choices} of {@code Size}s supported by a camera, chooses the smallest one whose 246 * width and height are at least as large as the respective requested values, and whose aspect 247 * ratio matches with the specified value. 248 * 249 * @param choices The list of sizes that the camera supports for the intended output class 250 * @param width The minimum desired width 251 * @param height The minimum desired height 252 * @param aspectRatio The aspect ratio 253 * @return The optimal {@code Size}, or an arbitrary one if none were big enough 254 */ chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio)255 private static Size chooseOptimalSize(Size[] choices, int width, int height, Size aspectRatio) { 256 // Collect the supported resolutions that are at least as big as the preview Surface 257 List<Size> bigEnough = new ArrayList<Size>(); 258 int w = aspectRatio.getWidth(); 259 int h = aspectRatio.getHeight(); 260 for (Size option : choices) { 261 if (option.getHeight() == option.getWidth() * h / w && 262 option.getWidth() >= width && option.getHeight() >= height) { 263 bigEnough.add(option); 264 } 265 } 266 267 // Pick the smallest of those, assuming we found any 268 if (bigEnough.size() > 0) { 269 return Collections.min(bigEnough, new CompareSizesByArea()); 270 } else { 271 Log.e(TAG, "Couldn't find any suitable preview size"); 272 return choices[0]; 273 } 274 } 275 276 @Override onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)277 public View onCreateView(LayoutInflater inflater, ViewGroup container, 278 Bundle savedInstanceState) { 279 return inflater.inflate(R.layout.fragment_camera2_video, container, false); 280 } 281 282 @Override onViewCreated(final View view, Bundle savedInstanceState)283 public void onViewCreated(final View view, Bundle savedInstanceState) { 284 mTextureView = (AutoFitTextureView) view.findViewById(R.id.texture); 285 mButtonVideo = (Button) view.findViewById(R.id.video); 286 mButtonVideo.setOnClickListener(this); 287 view.findViewById(R.id.info).setOnClickListener(this); 288 } 289 290 @Override onResume()291 public void onResume() { 292 super.onResume(); 293 startBackgroundThread(); 294 if (mTextureView.isAvailable()) { 295 openCamera(mTextureView.getWidth(), mTextureView.getHeight()); 296 } else { 297 mTextureView.setSurfaceTextureListener(mSurfaceTextureListener); 298 } 299 } 300 301 @Override onPause()302 public void onPause() { 303 closeCamera(); 304 stopBackgroundThread(); 305 super.onPause(); 306 } 307 308 @Override onClick(View view)309 public void onClick(View view) { 310 switch (view.getId()) { 311 case R.id.video: { 312 if (mIsRecordingVideo) { 313 stopRecordingVideo(); 314 } else { 315 startRecordingVideo(); 316 } 317 break; 318 } 319 case R.id.info: { 320 Activity activity = getActivity(); 321 if (null != activity) { 322 new AlertDialog.Builder(activity) 323 .setMessage(R.string.intro_message) 324 .setPositiveButton(android.R.string.ok, null) 325 .show(); 326 } 327 break; 328 } 329 } 330 } 331 332 /** 333 * Starts a background thread and its {@link Handler}. 334 */ startBackgroundThread()335 private void startBackgroundThread() { 336 mBackgroundThread = new HandlerThread("CameraBackground"); 337 mBackgroundThread.start(); 338 mBackgroundHandler = new Handler(mBackgroundThread.getLooper()); 339 } 340 341 /** 342 * Stops the background thread and its {@link Handler}. 343 */ stopBackgroundThread()344 private void stopBackgroundThread() { 345 mBackgroundThread.quitSafely(); 346 try { 347 mBackgroundThread.join(); 348 mBackgroundThread = null; 349 mBackgroundHandler = null; 350 } catch (InterruptedException e) { 351 e.printStackTrace(); 352 } 353 } 354 355 /** 356 * Gets whether you should show UI with rationale for requesting permissions. 357 * 358 * @param permissions The permissions your app wants to request. 359 * @return Whether you can show permission rationale UI. 360 */ shouldShowRequestPermissionRationale(String[] permissions)361 private boolean shouldShowRequestPermissionRationale(String[] permissions) { 362 for (String permission : permissions) { 363 if (FragmentCompat.shouldShowRequestPermissionRationale(this, permission)) { 364 return true; 365 } 366 } 367 return false; 368 } 369 370 /** 371 * Requests permissions needed for recording video. 372 */ requestVideoPermissions()373 private void requestVideoPermissions() { 374 if (shouldShowRequestPermissionRationale(VIDEO_PERMISSIONS)) { 375 new ConfirmationDialog().show(getChildFragmentManager(), FRAGMENT_DIALOG); 376 } else { 377 FragmentCompat.requestPermissions(this, VIDEO_PERMISSIONS, REQUEST_VIDEO_PERMISSIONS); 378 } 379 } 380 381 @Override onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)382 public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 383 @NonNull int[] grantResults) { 384 Log.d(TAG, "onRequestPermissionsResult"); 385 if (requestCode == REQUEST_VIDEO_PERMISSIONS) { 386 if (grantResults.length == VIDEO_PERMISSIONS.length) { 387 for (int result : grantResults) { 388 if (result != PackageManager.PERMISSION_GRANTED) { 389 ErrorDialog.newInstance(getString(R.string.permission_request)) 390 .show(getChildFragmentManager(), FRAGMENT_DIALOG); 391 break; 392 } 393 } 394 } else { 395 ErrorDialog.newInstance(getString(R.string.permission_request)) 396 .show(getChildFragmentManager(), FRAGMENT_DIALOG); 397 } 398 } else { 399 super.onRequestPermissionsResult(requestCode, permissions, grantResults); 400 } 401 } 402 hasPermissionsGranted(String[] permissions)403 private boolean hasPermissionsGranted(String[] permissions) { 404 for (String permission : permissions) { 405 if (ActivityCompat.checkSelfPermission(getActivity(), permission) 406 != PackageManager.PERMISSION_GRANTED) { 407 return false; 408 } 409 } 410 return true; 411 } 412 413 /** 414 * Tries to open a {@link CameraDevice}. The result is listened by `mStateCallback`. 415 */ openCamera(int width, int height)416 private void openCamera(int width, int height) { 417 if (!hasPermissionsGranted(VIDEO_PERMISSIONS)) { 418 requestVideoPermissions(); 419 return; 420 } 421 final Activity activity = getActivity(); 422 if (null == activity || activity.isFinishing()) { 423 return; 424 } 425 CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE); 426 try { 427 Log.d(TAG, "tryAcquire"); 428 if (!mCameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) { 429 throw new RuntimeException("Time out waiting to lock camera opening."); 430 } 431 String cameraId = manager.getCameraIdList()[0]; 432 433 // Choose the sizes for camera preview and video recording 434 CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId); 435 StreamConfigurationMap map = characteristics 436 .get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP); 437 mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION); 438 mVideoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder.class)); 439 mPreviewSize = chooseOptimalSize(map.getOutputSizes(SurfaceTexture.class), 440 width, height, mVideoSize); 441 442 int orientation = getResources().getConfiguration().orientation; 443 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 444 mTextureView.setAspectRatio(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 445 } else { 446 mTextureView.setAspectRatio(mPreviewSize.getHeight(), mPreviewSize.getWidth()); 447 } 448 configureTransform(width, height); 449 mMediaRecorder = new MediaRecorder(); 450 manager.openCamera(cameraId, mStateCallback, null); 451 } catch (CameraAccessException e) { 452 Toast.makeText(activity, "Cannot access the camera.", Toast.LENGTH_SHORT).show(); 453 activity.finish(); 454 } catch (NullPointerException e) { 455 // Currently an NPE is thrown when the Camera2API is used but not supported on the 456 // device this code runs. 457 ErrorDialog.newInstance(getString(R.string.camera_error)) 458 .show(getChildFragmentManager(), FRAGMENT_DIALOG); 459 } catch (InterruptedException e) { 460 throw new RuntimeException("Interrupted while trying to lock camera opening."); 461 } 462 } 463 closeCamera()464 private void closeCamera() { 465 try { 466 mCameraOpenCloseLock.acquire(); 467 closePreviewSession(); 468 if (null != mCameraDevice) { 469 mCameraDevice.close(); 470 mCameraDevice = null; 471 } 472 if (null != mMediaRecorder) { 473 mMediaRecorder.release(); 474 mMediaRecorder = null; 475 } 476 } catch (InterruptedException e) { 477 throw new RuntimeException("Interrupted while trying to lock camera closing."); 478 } finally { 479 mCameraOpenCloseLock.release(); 480 } 481 } 482 483 /** 484 * Start the camera preview. 485 */ startPreview()486 private void startPreview() { 487 if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) { 488 return; 489 } 490 try { 491 closePreviewSession(); 492 SurfaceTexture texture = mTextureView.getSurfaceTexture(); 493 assert texture != null; 494 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 495 mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW); 496 497 Surface previewSurface = new Surface(texture); 498 mPreviewBuilder.addTarget(previewSurface); 499 500 mCameraDevice.createCaptureSession(Arrays.asList(previewSurface), new CameraCaptureSession.StateCallback() { 501 502 @Override 503 public void onConfigured(CameraCaptureSession cameraCaptureSession) { 504 mPreviewSession = cameraCaptureSession; 505 updatePreview(); 506 } 507 508 @Override 509 public void onConfigureFailed(CameraCaptureSession cameraCaptureSession) { 510 Activity activity = getActivity(); 511 if (null != activity) { 512 Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); 513 } 514 } 515 }, mBackgroundHandler); 516 } catch (CameraAccessException e) { 517 e.printStackTrace(); 518 } 519 } 520 521 /** 522 * Update the camera preview. {@link #startPreview()} needs to be called in advance. 523 */ updatePreview()524 private void updatePreview() { 525 if (null == mCameraDevice) { 526 return; 527 } 528 try { 529 setUpCaptureRequestBuilder(mPreviewBuilder); 530 HandlerThread thread = new HandlerThread("CameraPreview"); 531 thread.start(); 532 mPreviewSession.setRepeatingRequest(mPreviewBuilder.build(), null, mBackgroundHandler); 533 } catch (CameraAccessException e) { 534 e.printStackTrace(); 535 } 536 } 537 setUpCaptureRequestBuilder(CaptureRequest.Builder builder)538 private void setUpCaptureRequestBuilder(CaptureRequest.Builder builder) { 539 builder.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO); 540 } 541 542 /** 543 * Configures the necessary {@link android.graphics.Matrix} transformation to `mTextureView`. 544 * This method should not to be called until the camera preview size is determined in 545 * openCamera, or until the size of `mTextureView` is fixed. 546 * 547 * @param viewWidth The width of `mTextureView` 548 * @param viewHeight The height of `mTextureView` 549 */ configureTransform(int viewWidth, int viewHeight)550 private void configureTransform(int viewWidth, int viewHeight) { 551 Activity activity = getActivity(); 552 if (null == mTextureView || null == mPreviewSize || null == activity) { 553 return; 554 } 555 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 556 Matrix matrix = new Matrix(); 557 RectF viewRect = new RectF(0, 0, viewWidth, viewHeight); 558 RectF bufferRect = new RectF(0, 0, mPreviewSize.getHeight(), mPreviewSize.getWidth()); 559 float centerX = viewRect.centerX(); 560 float centerY = viewRect.centerY(); 561 if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) { 562 bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY()); 563 matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL); 564 float scale = Math.max( 565 (float) viewHeight / mPreviewSize.getHeight(), 566 (float) viewWidth / mPreviewSize.getWidth()); 567 matrix.postScale(scale, scale, centerX, centerY); 568 matrix.postRotate(90 * (rotation - 2), centerX, centerY); 569 } 570 mTextureView.setTransform(matrix); 571 } 572 setUpMediaRecorder()573 private void setUpMediaRecorder() throws IOException { 574 final Activity activity = getActivity(); 575 if (null == activity) { 576 return; 577 } 578 mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); 579 mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE); 580 mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4); 581 if (mNextVideoAbsolutePath == null || mNextVideoAbsolutePath.isEmpty()) { 582 mNextVideoAbsolutePath = getVideoFilePath(getActivity()); 583 } 584 mMediaRecorder.setOutputFile(mNextVideoAbsolutePath); 585 mMediaRecorder.setVideoEncodingBitRate(10000000); 586 mMediaRecorder.setVideoFrameRate(30); 587 mMediaRecorder.setVideoSize(mVideoSize.getWidth(), mVideoSize.getHeight()); 588 mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264); 589 mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC); 590 int rotation = activity.getWindowManager().getDefaultDisplay().getRotation(); 591 switch (mSensorOrientation) { 592 case SENSOR_ORIENTATION_DEFAULT_DEGREES: 593 mMediaRecorder.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation)); 594 break; 595 case SENSOR_ORIENTATION_INVERSE_DEGREES: 596 mMediaRecorder.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation)); 597 break; 598 } 599 mMediaRecorder.prepare(); 600 } 601 getVideoFilePath(Context context)602 private String getVideoFilePath(Context context) { 603 return context.getExternalFilesDir(null).getAbsolutePath() + "/" 604 + System.currentTimeMillis() + ".mp4"; 605 } 606 startRecordingVideo()607 private void startRecordingVideo() { 608 if (null == mCameraDevice || !mTextureView.isAvailable() || null == mPreviewSize) { 609 return; 610 } 611 try { 612 closePreviewSession(); 613 setUpMediaRecorder(); 614 SurfaceTexture texture = mTextureView.getSurfaceTexture(); 615 assert texture != null; 616 texture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight()); 617 mPreviewBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_RECORD); 618 List<Surface> surfaces = new ArrayList<>(); 619 620 // Set up Surface for the camera preview 621 Surface previewSurface = new Surface(texture); 622 surfaces.add(previewSurface); 623 mPreviewBuilder.addTarget(previewSurface); 624 625 // Set up Surface for the MediaRecorder 626 mRecorderSurface = mMediaRecorder.getSurface(); 627 surfaces.add(mRecorderSurface); 628 mPreviewBuilder.addTarget(mRecorderSurface); 629 630 // Start a capture session 631 // Once the session starts, we can update the UI and start recording 632 mCameraDevice.createCaptureSession(surfaces, new CameraCaptureSession.StateCallback() { 633 634 @Override 635 public void onConfigured(@NonNull CameraCaptureSession cameraCaptureSession) { 636 mPreviewSession = cameraCaptureSession; 637 updatePreview(); 638 getActivity().runOnUiThread(new Runnable() { 639 @Override 640 public void run() { 641 // UI 642 mButtonVideo.setText(R.string.stop); 643 mIsRecordingVideo = true; 644 645 // Start recording 646 mMediaRecorder.start(); 647 } 648 }); 649 } 650 651 @Override 652 public void onConfigureFailed(@NonNull CameraCaptureSession cameraCaptureSession) { 653 Activity activity = getActivity(); 654 if (null != activity) { 655 Toast.makeText(activity, "Failed", Toast.LENGTH_SHORT).show(); 656 } 657 } 658 }, mBackgroundHandler); 659 } catch (CameraAccessException e) { 660 e.printStackTrace(); 661 } catch (IOException e) { 662 e.printStackTrace(); 663 } 664 665 } 666 closePreviewSession()667 private void closePreviewSession() { 668 if (mPreviewSession != null) { 669 mPreviewSession.close(); 670 mPreviewSession = null; 671 } 672 } 673 stopRecordingVideo()674 private void stopRecordingVideo() { 675 // UI 676 mIsRecordingVideo = false; 677 mButtonVideo.setText(R.string.record); 678 // Stop recording 679 mMediaRecorder.stop(); 680 mMediaRecorder.reset(); 681 682 Activity activity = getActivity(); 683 if (null != activity) { 684 Toast.makeText(activity, "Video saved: " + mNextVideoAbsolutePath, 685 Toast.LENGTH_SHORT).show(); 686 Log.d(TAG, "Video saved: " + mNextVideoAbsolutePath); 687 } 688 mNextVideoAbsolutePath = null; 689 startPreview(); 690 } 691 692 /** 693 * Compares two {@code Size}s based on their areas. 694 */ 695 static class CompareSizesByArea implements Comparator<Size> { 696 697 @Override compare(Size lhs, Size rhs)698 public int compare(Size lhs, Size rhs) { 699 // We cast here to ensure the multiplications won't overflow 700 return Long.signum((long) lhs.getWidth() * lhs.getHeight() - 701 (long) rhs.getWidth() * rhs.getHeight()); 702 } 703 704 } 705 706 public static class ErrorDialog extends DialogFragment { 707 708 private static final String ARG_MESSAGE = "message"; 709 newInstance(String message)710 public static ErrorDialog newInstance(String message) { 711 ErrorDialog dialog = new ErrorDialog(); 712 Bundle args = new Bundle(); 713 args.putString(ARG_MESSAGE, message); 714 dialog.setArguments(args); 715 return dialog; 716 } 717 718 @Override onCreateDialog(Bundle savedInstanceState)719 public Dialog onCreateDialog(Bundle savedInstanceState) { 720 final Activity activity = getActivity(); 721 return new AlertDialog.Builder(activity) 722 .setMessage(getArguments().getString(ARG_MESSAGE)) 723 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 724 @Override 725 public void onClick(DialogInterface dialogInterface, int i) { 726 activity.finish(); 727 } 728 }) 729 .create(); 730 } 731 732 } 733 734 public static class ConfirmationDialog extends DialogFragment { 735 736 @Override 737 public Dialog onCreateDialog(Bundle savedInstanceState) { 738 final Fragment parent = getParentFragment(); 739 return new AlertDialog.Builder(getActivity()) 740 .setMessage(R.string.permission_request) 741 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { 742 @Override 743 public void onClick(DialogInterface dialog, int which) { 744 FragmentCompat.requestPermissions(parent, VIDEO_PERMISSIONS, 745 REQUEST_VIDEO_PERMISSIONS); 746 } 747 }) 748 .setNegativeButton(android.R.string.cancel, 749 new DialogInterface.OnClickListener() { 750 @Override 751 public void onClick(DialogInterface dialog, int which) { 752 parent.getActivity().finish(); 753 } 754 }) 755 .create(); 756 } 757 758 } 759 760 } 761