Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<Picker
Title="Cameras"
ItemsSource="{Binding Cameras}"
ItemDisplayBinding="{Binding Name}"
ItemDisplayBinding="{Binding Name, x:DataType={x:Null}}"
SelectedItem="{Binding SelectedCamera}" />

<Picker
Expand Down
69 changes: 46 additions & 23 deletions src/CommunityToolkit.Maui.Camera/CameraManager.android.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,13 @@ public async partial ValueTask UpdateCaptureResolution(Size resolution, Cancella
resolutionSelector?.Dispose();

resolutionSelector = new ResolutionSelector.Builder()
.SetAllowedResolutionMode(ResolutionSelector.PreferHigherResolutionOverCaptureRate)
.SetAllowedResolutionMode(ResolutionSelector.PreferHigherResolutionOverCaptureRate)?
.SetResolutionFilter(resolutionFilter)
.Build();
?.Build() ?? throw new InvalidOperationException("Unable to Set Resolution Filter");

// `.SetResolutionFilter()` should never return null
// According to the Android docs, `ResolutionSelector.Builder.setResolutionFilter(ResolutionFilter)` returns a `NonNull` object
// `ResolutionSelector.Builder.SetResolutionFilter(ResolutionFilter)` returning a nullable object in .NET for Android is likely a C# Binding mistake

if (IsInitialized)
{
Expand Down Expand Up @@ -229,23 +233,33 @@ protected async Task StartUseCase(CancellationToken token)
videoCapture?.Dispose();
videoRecorder?.Dispose();

cameraPreview = new Preview.Builder().SetResolutionSelector(resolutionSelector).Build();
cameraPreview.SetSurfaceProvider(cameraExecutor, previewView?.SurfaceProvider);
cameraPreview = new Preview.Builder().SetResolutionSelector(resolutionSelector)?.Build();
cameraPreview?.SetSurfaceProvider(cameraExecutor, previewView?.SurfaceProvider);

imageCapture = new ImageCapture.Builder()
.SetCaptureMode(ImageCapture.CaptureModeMaximizeQuality)
.SetCaptureMode(ImageCapture.CaptureModeMaximizeQuality)?
.SetResolutionSelector(resolutionSelector)
.Build();
?.Build() ?? throw new InvalidOperationException("Unable to set resolution selector");

// `.SetResolutionFilter()` should never return null
// According to the Android docs, `ResolutionSelector.Builder.SetResolutionFilter(ResolutionFilter)` returns a `NonNull` object
// `ResolutionSelector.Builder.SetResolutionFilter(ResolutionFilter)` returning a nullable object in .NET for Android is likely a C# Binding mistake
// https://developer.android.com/reference/androidx/camera/core/resolutionselector/ResolutionSelector.Builder#setResolutionFilter(androidx.camera.core.resolutionselector.ResolutionFilter)

var videoRecorderBuilder = new Recorder.Builder()
.SetExecutor(cameraExecutor);
.SetExecutor(cameraExecutor) ?? throw new InvalidOperationException("Unable to set video recorder executor");

// `.SetExecutor()` should never return null
// According to the Android docs, `ResolutionSelector.Builder.setExecutor(ResolutionFilter)` returns a `NonNull` object
// `ResolutionSelector.Builder.SetExecutor(ResolutionFilter)` returning a nullable object in .NET for Android is likely a C# Binding mistake
// https://developer.android.com/reference/androidx/camera/video/Recorder.Builder#setExecutor(java.util.concurrent.Executor)

if (Quality.Highest is not null)
{
videoRecorderBuilder = videoRecorderBuilder.SetQualitySelector(QualitySelector.From(Quality.Highest));
videoRecorderBuilder = videoRecorderBuilder?.SetQualitySelector(QualitySelector.From(Quality.Highest));
}

videoRecorder = videoRecorderBuilder.Build();
videoRecorder = videoRecorderBuilder?.Build();
videoCapture = VideoCapture.WithOutput(videoRecorder);

await StartCameraPreview(token);
Expand All @@ -271,9 +285,9 @@ protected virtual async partial Task PlatformStartCameraPreview(CancellationToke
camera = await RebindCamera(processCameraProvider, cameraView.SelectedCamera, token, cameraPreview, imageCapture, videoCapture);
cameraControl = camera.CameraControl;

var point = previewView.MeteringPointFactory.CreatePoint(previewView.Width / 2.0f, previewView.Height / 2.0f, 0.1f);
var point = previewView.MeteringPointFactory?.CreatePoint(previewView.Width / 2.0f, previewView.Height / 2.0f, 0.1f);
var action = new FocusMeteringAction.Builder(point).Build();
camera.CameraControl.StartFocusAndMetering(action);
camera.CameraControl?.StartFocusAndMetering(action);

IsInitialized = true;
OnLoaded.Invoke();
Expand Down Expand Up @@ -344,8 +358,13 @@ protected virtual async partial Task PlatformStartVideoRecording(Stream stream,
var executor = ContextCompat.GetMainExecutor(context) ?? throw new CameraException($"Unable to retrieve {nameof(IExecutorService)}");
videoRecording = videoRecorder
.PrepareRecording(context, outputOptions)
.WithAudioEnabled()
.Start(executor, captureListener);
?.WithAudioEnabled()
.Start(executor, captureListener) ?? throw new InvalidOperationException("Unable to prepare recording");

// `.PrepareRecording()` should never return null
// According to the Android docs, `Recorder.prepareRecording(Context, eMediaSoreOutputOptions)` returns a `NonNull` object
// `Recorder.PrepareRecording(Context, eMediaSoreOutputOptions)` returning a nullable object in .NET for Android is likely a C# Binding mistake
// https://developer.android.com/reference/androidx/camera/video/Recorder#prepareRecording(android.content.Context,androidx.camera.video.MediaStoreOutputOptions)
}

protected virtual async partial Task<Stream> PlatformStopVideoRecording(CancellationToken token)
Expand Down Expand Up @@ -415,7 +434,9 @@ async Task<CameraSelector> EnableModes(CameraInfo selectedCamera, CancellationTo
return;
}

var extensionsManagerFuture = ExtensionsManager.GetInstanceAsync(context, cameraProviderInstance);
var extensionsManagerFuture = ExtensionsManager.GetInstanceAsync(context, cameraProviderInstance)
?? throw new InvalidOperationException("Unable to get listenable future for camera provider");;

extensionsManagerFuture.AddListener(new Runnable(() =>
{
var extensionsManager = (ExtensionsManager?)extensionsManagerFuture.Get();
Expand Down Expand Up @@ -455,10 +476,10 @@ void SetImageCaptureTargetRotation(int rotation)

sealed class ImageCallBack(ICameraView cameraView) : ImageCapture.OnImageCapturedCallback
{
public override void OnCaptureSuccess(IImageProxy image)
public override void OnCaptureSuccess(IImageProxy? image)
{
base.OnCaptureSuccess(image);
var img = image.Image;
var img = image?.Image;

if (img is null)
{
Expand All @@ -471,7 +492,7 @@ public override void OnCaptureSuccess(IImageProxy image)
if (buffer is null)
{
cameraView.OnMediaCapturedFailed("Unable to obtain a buffer for the image plane.");
image.Close();
image?.Close();
return;
}

Expand All @@ -489,7 +510,7 @@ public override void OnCaptureSuccess(IImageProxy image)
}
finally
{
image.Close();
image?.Close();
}

static Image.Plane? GetFirstPlane(Image.Plane[]? planes)
Expand All @@ -503,24 +524,26 @@ public override void OnCaptureSuccess(IImageProxy image)
}
}

public override void OnError(ImageCaptureException exception)
public override void OnError(ImageCaptureException? exception)
{
base.OnError(exception);
cameraView.OnMediaCapturedFailed(exception.Message ?? "An unknown error occurred.");
cameraView.OnMediaCapturedFailed(exception?.Message ?? "An unknown error occurred.");
}
}

sealed class ResolutionFilter(Android.Util.Size size) : Object, IResolutionFilter
{
public Android.Util.Size TargetSize { get; set; } = size;

public IList<Android.Util.Size> Filter(IList<Android.Util.Size> supportedSizes, int rotationDegrees)
public IList<Android.Util.Size> Filter(IList<Android.Util.Size>? supportedSizes, int rotationDegrees)
{
var filteredList = supportedSizes
var filteredList = supportedSizes?
.Where(size => size.Width <= TargetSize.Width && size.Height <= TargetSize.Height)
.OrderByDescending(size => size.Width * size.Height).ToList();

return filteredList.Count is 0 ? supportedSizes : filteredList;
return filteredList is null || filteredList.Count is 0
? supportedSizes ?? []
: filteredList;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,12 @@

<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">
<!-- Required NuGet Packages -->
<PackageReference Include="Xamarin.AndroidX.Camera.Camera2" Version="1.4.2.2" />
<PackageReference Include="Xamarin.AndroidX.Camera.View" Version="1.4.2.2" />
<PackageReference Include="Xamarin.AndroidX.Camera.Extensions" Version="1.4.2.2" />
<PackageReference Include="Xamarin.AndroidX.Camera.Camera2" Version="1.5.0" />
<PackageReference Include="Xamarin.AndroidX.Camera.View" Version="1.5.0" />
<PackageReference Include="Xamarin.AndroidX.Camera.Extensions" Version="1.5.0" />

<PackageReference Include="Xamarin.AndroidX.Activity.Ktx" Version="1.11.0" />

<!-- Ensure Linker does not remove required libraries -->
<None Include="linker.xml" Pack="true" PackagePath="build\$(PackageId).LinkerConfigurationFile.xml" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ public async partial ValueTask RefreshAvailableCameras(CancellationToken token)
foreach (var cameraXInfo in processCameraProvider.AvailableCameraInfos)
{
var camera2Info = Camera2CameraInfo.From(cameraXInfo);
if (camera2Info is null)
{
// `Camera2CameraInfo.From` should never return `null`
// According to the Android Docs, `Camera2CameraInfo.From` returns a `NonNull`
// `Camera2CameraInfo.From` returning a nullable `Camera2CameraInfo` object is likely a C# binding mistake
// https://developer.android.com/reference/androidx/camera/camera2/interop/Camera2CameraInfo
continue;
}

var (name, position) = cameraXInfo.LensFacing switch
{
Expand Down Expand Up @@ -68,13 +76,13 @@ public async partial ValueTask RefreshAvailableCameras(CancellationToken token)
}

var cameraInfo = new CameraInfo(name,
camera2Info.CameraId,
camera2Info.CameraId ?? throw new InvalidOperationException("Unable to retrieve Camera ID"),
position,
cameraXInfo.HasFlashUnit,
(cameraXInfo.ZoomState.Value as IZoomState)?.MinZoomRatio ?? 1.0f,
(cameraXInfo.ZoomState.Value as IZoomState)?.MaxZoomRatio ?? 1.0f,
(cameraXInfo.ZoomState?.Value as IZoomState)?.MinZoomRatio ?? 1.0f,
(cameraXInfo.ZoomState?.Value as IZoomState)?.MaxZoomRatio ?? 1.0f,
supportedResolutions,
cameraXInfo.CameraSelector);
cameraXInfo.CameraSelector ?? throw new InvalidOperationException($"Unable to retrieve {nameof(ICameraInfo.CameraSelector)}"));

availableCameras.Add(cameraInfo);
}
Expand Down
Loading