diff --git a/ZXing.Net.MAUI.Tests/CameraManagerTests.cs b/ZXing.Net.MAUI.Tests/CameraManagerTests.cs index 3bfd33e..03d8ac6 100644 --- a/ZXing.Net.MAUI.Tests/CameraManagerTests.cs +++ b/ZXing.Net.MAUI.Tests/CameraManagerTests.cs @@ -98,4 +98,40 @@ public void ContainsReferenceReturnsTrueOnlyForSameInstance() Assert.True(CameraManager.ContainsReference(items, shared)); Assert.False(CameraManager.ContainsReference(items, sameValueDifferentInstance)); } + + [Fact] + public void RequiresCameraConfigurationSkipsAnAlreadyAppliedConfiguration() + { + CameraResolutionSelectorDelegate selector = availableResolutions => availableResolutions.FirstOrDefault(); + var configuration = new CameraConfiguration(CameraLocation.Rear, "rear-camera", selector); + + Assert.False(CameraManager.RequiresCameraConfiguration(configuration, configuration, hasInput: true, hasOutput: true)); + } + + [Fact] + public void RequiresCameraConfigurationReconfiguresWhenStateOrSessionGraphChanges() + { + CameraResolutionSelectorDelegate selector = availableResolutions => availableResolutions.FirstOrDefault(); + CameraResolutionSelectorDelegate otherSelector = availableResolutions => availableResolutions.LastOrDefault(); + var configuration = new CameraConfiguration(CameraLocation.Rear, "rear-camera", selector); + + Assert.True(CameraManager.RequiresCameraConfiguration(null, configuration, hasInput: true, hasOutput: true)); + Assert.True(CameraManager.RequiresCameraConfiguration(configuration, configuration, hasInput: false, hasOutput: true)); + Assert.True(CameraManager.RequiresCameraConfiguration(configuration, configuration, hasInput: true, hasOutput: false)); + Assert.True(CameraManager.RequiresCameraConfiguration( + configuration, + new CameraConfiguration(CameraLocation.Front, "front-camera", selector), + hasInput: true, + hasOutput: true)); + Assert.True(CameraManager.RequiresCameraConfiguration( + configuration, + new CameraConfiguration(CameraLocation.Rear, "other-rear-camera", selector), + hasInput: true, + hasOutput: true)); + Assert.True(CameraManager.RequiresCameraConfiguration( + configuration, + new CameraConfiguration(CameraLocation.Rear, "rear-camera", otherSelector), + hasInput: true, + hasOutput: true)); + } } diff --git a/ZXing.Net.MAUI/Apple/CameraManager.ios.maccatalyst.cs b/ZXing.Net.MAUI/Apple/CameraManager.ios.maccatalyst.cs index 2d88c97..71abac9 100644 --- a/ZXing.Net.MAUI/Apple/CameraManager.ios.maccatalyst.cs +++ b/ZXing.Net.MAUI/Apple/CameraManager.ios.maccatalyst.cs @@ -46,6 +46,8 @@ public static partial bool IsSupported AVCaptureVideoPreviewLayer videoPreviewLayer; CaptureDelegate captureDelegate; DispatchQueue dispatchQueue; + CameraConfiguration? appliedConfiguration; + bool isConnected; Dictionary Resolutions => new() { { AVCaptureSession.Preset352x288, new MSize(352, 288) }, @@ -73,7 +75,10 @@ public NativePlatformCameraPreviewView CreateNativeView() public void Connect() { - UpdateCamera(startIfStopped: true); + if (isConnected) + return; + + isConnected = true; view?.StartObservingOrientationChanges(); if (videoDataOutput == null) @@ -106,7 +111,7 @@ public void Connect() videoDataOutput.SetSampleBufferDelegate(captureDelegate, dispatchQueue); } - captureSession.AddOutput(videoDataOutput); + UpdateCamera(startIfStopped: true); } public void UpdateCamera() @@ -114,19 +119,33 @@ public void UpdateCamera() void UpdateCamera(bool startIfStopped) { - if (captureSession != null) + if (captureSession != null && isConnected) { - var wasRunning = captureSession.Running; - if (wasRunning) - captureSession.StopRunning(); + var configuration = Configuration; + var hasInput = ContainsReference(captureSession.Inputs, captureInput); + var hasOutput = ContainsReference(captureSession.Outputs, videoDataOutput); + + if (!RequiresCameraConfiguration(appliedConfiguration, configuration, hasInput, hasOutput)) + { + if (startIfStopped && !captureSession.Running) + captureSession.StartRunning(); + + ApplyZoomFactor(); + return; + } captureSession.BeginConfiguration(); try { + if (!hasOutput) + captureSession.AddOutput(videoDataOutput); + // Cleanup old input - if (captureInput != null && captureSession.Inputs.Length > 0 && captureSession.Inputs.Contains(captureInput)) + if (captureInput != null) { - captureSession.RemoveInput(captureInput); + if (hasInput) + captureSession.RemoveInput(captureInput); + captureInput.Dispose(); captureInput = null; } @@ -207,6 +226,7 @@ void UpdateCamera(bool startIfStopped) captureSession.AddInput(captureInput); ApplySelectedResolution(); + appliedConfiguration = configuration; #if IOS // Enable multitasking camera access for iPadOS Windowed Apps mode @@ -224,7 +244,7 @@ void UpdateCamera(bool startIfStopped) captureSession.CommitConfiguration(); } - if (wasRunning || startIfStopped) + if (startIfStopped && !captureSession.Running) captureSession.StartRunning(); ApplyZoomFactor(); @@ -316,6 +336,7 @@ public Task> GetAvailableCameras() public void Disconnect() { + isConnected = false; view?.StopObservingOrientationChanges(); if (captureSession != null) @@ -342,6 +363,8 @@ public void Disconnect() captureDevice.Dispose(); captureDevice = null; } + + appliedConfiguration = null; } } diff --git a/ZXing.Net.MAUI/CameraManager.cs b/ZXing.Net.MAUI/CameraManager.cs index e8f8a69..c987b4f 100644 --- a/ZXing.Net.MAUI/CameraManager.cs +++ b/ZXing.Net.MAUI/CameraManager.cs @@ -26,6 +26,8 @@ public CameraManager(IMauiContext context, CameraLocation cameraLocation) public float ZoomFactor { get; private set; } protected CameraManagerOptions Options => options; + internal CameraConfiguration Configuration + => new(CameraLocation, SelectedCamera?.DeviceId, Options.CameraResolutionSelector); /// /// Gets a value indicating whether barcode scanning is supported on this device. @@ -81,6 +83,13 @@ internal static bool ContainsReference(IReadOnlyCollection items, object i return false; } + internal static bool RequiresCameraConfiguration( + CameraConfiguration? appliedConfiguration, + CameraConfiguration configuration, + bool hasInput, + bool hasOutput) + => !hasInput || !hasOutput || appliedConfiguration != configuration; + public void UpdateZoomFactor(float zoomFactor) { if (float.IsNaN(zoomFactor)) @@ -98,4 +107,9 @@ public static async Task CheckPermissions() private static partial bool ShouldApplyPlatformCameraOptions(CameraManagerOptions currentOptions, CameraManagerOptions nextOptions); partial void ApplyZoomFactor(); } + + internal readonly record struct CameraConfiguration( + CameraLocation CameraLocation, + string SelectedCameraDeviceId, + CameraResolutionSelectorDelegate CameraResolutionSelector); }