Search code examples
androidkotlinonactivityresult

registerForActivityResult(ActivityResultContracts.TakeVideo()) is always null


I use the following codes for capturing video. Everything is ok but after capturing video, the registerForActivityResult(ActivityResultContracts.TakeVideo()) is always null. The captured video exists in the given path. In what ways I can resolve this issue?

 private fun openCameraForMovie() {
        val packageManager = requireContext().packageManager
        Intent(MediaStore.ACTION_VIDEO_CAPTURE).also { takeVideoIntent ->
            takeVideoIntent.resolveActivity(packageManager)?.also {
                startRecording()
            }
        }
    }




 private fun startRecording() {
        try {
            val root = File(
                requireContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES)
                    .toString() + "/media/"
            )

            if (!root.exists()) {
                root.mkdir()
            }

            val videoName = "vid_" + System.currentTimeMillis() + ".mp4"
            val sdImageMainDirectory = File(root, videoName)

            var fileUri: Uri? = FileProvider.getUriForFile(
                requireContext(),
                context?.applicationContext?.packageName + ".provider",
                sdImageMainDirectory
            ) ?: return

        
            mViewModel.videoUri.postValue(fileUri)

        } catch (ex: Exception) {
            Timber.d("startRecording ${ex.localizedMessage}")
        }
    }

This code is always null:

  private val recordVideoResult =
        registerForActivityResult(ActivityResultContracts.TakeVideo()) { it ->
            try {
                if (it != null) {
                    Timber.d(">>>> Video Bitmap: $it") 
                }
            } catch (ex: Exception) {
                Timber.d(">>>> Video Bitmap: ${ex.localizedMessage}")
            }
        }

Solution

  • What's happening?

    An ActivityResultContract to take a video saving it into the provided content-Uri.

    Returns a thumbnail.

    Source TakeVideo docs, emphasis mine.

    You don't want a thumbnail (which is optional anyway), you want the Uri that points to the actual video. But you already know the Uri, because you supplied it as the input.

    Now what?

    There are several steps required to make this work:

    1. Store the input Uri in a variable and correctly handle saved state, so your app knows you've been recording video when you get back from the Camera app.

    2. Write custom TakeVideo contract which returns if video capture was successful. Checking for non-null preview Bitmap cannot be used, because preview is optional.

    It may look something like this:

    private AtomicReference<Uri> recordingUri = new AtomicReference<>();
    
    private ActivityResultLauncher<Uri> recordVideoResult =
            registerForActivityResult(new TakeVideo2(), success -> {
                final Uri uri = recordingUri.getAndSet(null);
                if (success) {
                    // Do something with the video.
                }
            });
    
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        // ...
        if (savedInstanceState != null) {
            recordingUri.set(savedInstanceState.getParcelable("recordingUri"));
        }
    }
    
    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
        // ...
        outState.putParcelable("recordingUri", recordingUri.get());
    }
    
    private void startRecording() {
        final Uri uri = // ...;
        recordingUri.set(uri);
        recordVideoResult.launch(uri);
    }
    
    public static class TakeVideo2 extends ActivityResultContract<Uri, Boolean> {
    
        @CallSuper
        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @NonNull Uri input) {
            return new Intent(MediaStore.ACTION_VIDEO_CAPTURE)
                    .putExtra(MediaStore.EXTRA_OUTPUT, input);
        }
    
        @Nullable
        @Override
        public final SynchronousResult<Boolean> getSynchronousResult(@NonNull Context context,
                @NonNull Uri input) {
            return null;
        }
    
        @Override
        public final Boolean parseResult(int resultCode, @Nullable Intent intent) {
            return resultCode == Activity.RESULT_OK;
        }
    }