hjam40 / Camera.MAUI

A CameraView Control for preview, take photos and control the camera options
MIT License
458 stars 75 forks source link

Result keeps returning "AccessDenied" when using "StartRecordingAsync" method #88

Open bluedrakim opened 1 year ago

bluedrakim commented 1 year ago

I'm trying to use StartRecordingAsync and StopRecordingAsync method to record a video. But I can never record it and while I was debugging I realized, result always returned AccessDenied right after StartRecordingAsync starts. In the code below I put a couple debug statements: Debug.WriteLine("before recording result " + result); and Debug.WriteLine("Start recording result " + result);. And for the first debug statement the result returns Success. But for the second debug statement which is right after the StartRecordingAsync method is used, the result returns AccessDenied.

var result = CameraResult.Success;
var uniqueId = Guid.NewGuid().ToString();
var fileName = $"{DateTime.Now.Ticks}_{uniqueId}.mp4";
videoFilePath = Path.Combine(Path.Combine(FileSystem.Current.CacheDirectory, fileName));
Debug.WriteLine("before recording result " + result);

// Start video recording
result = await cameraView.StartRecordingAsync(Path.Combine(FileSystem.Current.CacheDirectory, videoFilePath));

 Debug.WriteLine("Start recording result " + result);

The part where I'm confused is that the code inside the StartRecordingAsync method doesn't directly return AccessDenied. This is the code in StartRecordingAsync method.

public async Task<CameraResult> StartRecordingAsync(string file, Size Resolution = default(Size))
        {
            CameraResult cameraResult = CameraResult.AccessError;
            if (Camera != null)
            {
                if (Resolution.Width != 0.0 && Resolution.Height != 0.0 && !Camera.AvailableResolutions.Any((Size r) => r.Width == Resolution.Width && r.Height == Resolution.Height))
                {
                    return CameraResult.ResolutionNotAvailable;
                }

                if (base.Handler != null)
                {
                    CameraViewHandler cameraViewHandler = base.Handler as CameraViewHandler;
                    if (cameraViewHandler != null)
                    {
                        cameraResult = await cameraViewHandler.StartRecordingAsync(file, Resolution);
                        if (cameraResult == CameraResult.Success)
                        {
                            BarCodeResults = null;
                            OnPropertyChanged("MinZoomFactor");
                            OnPropertyChanged("MaxZoomFactor");
                        }
                    }
                }
            }
            else
            {
                cameraResult = CameraResult.NoCameraSelected;
            }

            return cameraResult;
        }

Does anyone know where the AccessDenied is coming from? I'm using Android so I put all the permissions in the AndroidManifest file such as permission for camera, record_audio, and record_video.

bluedrakim commented 1 year ago

I just found out, in the Android file, there is a code for StartRecordingAsync that returns AccessDenied when permission is not granted. So I utilized that to see what permission I didn't have and it turned out storage write permission is false. But I'm using Android 13.0 (API level 33) and even though I ask for storage write permission, it doesn't show up. I did some more research on this and figured out that at that API level, there is no need for the storage write permission. But if that is the case how can I make the boolean value for storage write true? Because, right now, I can't use StartRecordingAsnyc because the debug statement returns to be AccessDenied and the reason for that is because the storage write permission is false.

internal async Task<CameraResult> StartRecordingAsync(string file, Microsoft.Maui.Graphics.Size Resolution)
    {
        var result = CameraResult.Success;
        if (initiated && !recording)
        {
            if (await CameraView.RequestPermissions(true, true))
            {
                if (started) StopCamera();
                if (cameraView.Camera != null)
                {
                    try
                    {
                        camChars = cameraManager.GetCameraCharacteristics(cameraView.Camera.DeviceId);

                        StreamConfigurationMap map = (StreamConfigurationMap)camChars.Get(CameraCharacteristics.ScalerStreamConfigurationMap);
                        videoSize = ChooseVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
                        recording = true;

                        if (OperatingSystem.IsAndroidVersionAtLeast(31))
                            mediaRecorder = new MediaRecorder(context);
                        else
                            mediaRecorder = new MediaRecorder();
                        audioManager.Mode = Mode.Normal;
                        mediaRecorder.SetAudioSource(AudioSource.Mic);
                        mediaRecorder.SetVideoSource(VideoSource.Surface);
                        mediaRecorder.SetOutputFormat(OutputFormat.Mpeg4);
                        mediaRecorder.SetOutputFile(file);
                        mediaRecorder.SetVideoEncodingBitRate(10000000);
                        mediaRecorder.SetVideoFrameRate(30);

                        var maxVideoSize = ChooseMaxVideoSize(map.GetOutputSizes(Class.FromType(typeof(ImageReader))));
                        if (Resolution.Width != 0 && Resolution.Height != 0)
                            maxVideoSize = new((int)Resolution.Width, (int)Resolution.Height);
                        mediaRecorder.SetVideoSize(maxVideoSize.Width, maxVideoSize.Height);

                        mediaRecorder.SetVideoEncoder(VideoEncoder.H264);
                        mediaRecorder.SetAudioEncoder(AudioEncoder.Aac);
                        IWindowManager windowManager = context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
                        int rotation = (int)windowManager.DefaultDisplay.Rotation;
                        int orientation = ORIENTATIONS.Get(rotation);
                        mediaRecorder.SetOrientationHint(orientation);
                        mediaRecorder.Prepare();

                        if (OperatingSystem.IsAndroidVersionAtLeast(28))
                            cameraManager.OpenCamera(cameraView.Camera.DeviceId, executorService, stateListener);
                        else
                            cameraManager.OpenCamera(cameraView.Camera.DeviceId, stateListener, null);
                        started = true;
                    }
                    catch
                    {
                        result = CameraResult.AccessError;
                    }
                }
                else
                    result = CameraResult.NoCameraSelected;
            }
            else
                result = CameraResult.AccessDenied;
        }
        else
            result = CameraResult.NotInitiated;

        return result;
    }
mancda commented 1 year ago

I have the same problem. Does StartRecordingAsync support Android API 33?

bluedrakim commented 1 year ago

I have the same problem. Does StartRecordingAsync support Android API 33?

I have no clue yet. I use Android API 33 as well and went on some debugging and realized that it's because the storage write permission is false. But I'm confused because for an Android 13 (API 33) device you do not need to request write permissions as you have it by default.

mancda commented 1 year ago

Hi Blue,

If I remove the write permission from the Android manifest the control errors out complaining that it is missing.

bluedrakim commented 1 year ago

Yes. You need to have <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> to use StartRecordingAsync method or it will give an error. It's just that unlike other permissions WRITE_EXTERNAL_STORAGE doesn't show a popup that requests the user to manually give permission so I'm confused how to change the value from false to true

mancda commented 1 year ago

Hi bluedrakim

Thanks for the heads-up on the permissions. Have you found a workaround to this problem? I am not sure this plugin is still supported. :(

minhiclick commented 1 year ago

@mancda you can use MediaPicker.Default.CaptureVideoAsync();

thedee commented 1 month ago

My guess your problem is the resolution size you are setting. For example, when I do this it works: var result = await cameraView.StartRecordingAsync(Path.Combine(directory, "Video.mp4"), new Size(1920, 1080));

When I do this it fails with the AccessError: var result = await cameraView.StartRecordingAsync(Path.Combine(directory, "Video.mp4"), cameraView.Camera.AvailableResolutions.First());