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
36 changes: 36 additions & 0 deletions ZXing.Net.MAUI.Tests/CameraManagerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
41 changes: 32 additions & 9 deletions ZXing.Net.MAUI/Apple/CameraManager.ios.maccatalyst.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public static partial bool IsSupported
AVCaptureVideoPreviewLayer videoPreviewLayer;
CaptureDelegate captureDelegate;
DispatchQueue dispatchQueue;
CameraConfiguration? appliedConfiguration;
bool isConnected;
Dictionary<NSString, MSize> Resolutions => new()
{
{ AVCaptureSession.Preset352x288, new MSize(352, 288) },
Expand Down Expand Up @@ -73,7 +75,10 @@ public NativePlatformCameraPreviewView CreateNativeView()

public void Connect()
{
UpdateCamera(startIfStopped: true);
if (isConnected)
return;

isConnected = true;
view?.StartObservingOrientationChanges();

if (videoDataOutput == null)
Expand Down Expand Up @@ -106,27 +111,41 @@ public void Connect()
videoDataOutput.SetSampleBufferDelegate(captureDelegate, dispatchQueue);
}

captureSession.AddOutput(videoDataOutput);
UpdateCamera(startIfStopped: true);
}

public void UpdateCamera()
=> UpdateCamera(startIfStopped: false);

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;
}
Expand Down Expand Up @@ -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
Expand All @@ -224,7 +244,7 @@ void UpdateCamera(bool startIfStopped)
captureSession.CommitConfiguration();
}

if (wasRunning || startIfStopped)
if (startIfStopped && !captureSession.Running)
captureSession.StartRunning();

ApplyZoomFactor();
Expand Down Expand Up @@ -316,6 +336,7 @@ public Task<IReadOnlyList<CameraInfo>> GetAvailableCameras()

public void Disconnect()
{
isConnected = false;
view?.StopObservingOrientationChanges();

if (captureSession != null)
Expand All @@ -342,6 +363,8 @@ public void Disconnect()
captureDevice.Dispose();
captureDevice = null;
}

appliedConfiguration = null;
}
}

Expand Down
14 changes: 14 additions & 0 deletions ZXing.Net.MAUI/CameraManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/// <summary>
/// Gets a value indicating whether barcode scanning is supported on this device.
Expand Down Expand Up @@ -81,6 +83,13 @@ internal static bool ContainsReference<T>(IReadOnlyCollection<T> 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))
Expand All @@ -98,4 +107,9 @@ public static async Task<bool> CheckPermissions()
private static partial bool ShouldApplyPlatformCameraOptions(CameraManagerOptions currentOptions, CameraManagerOptions nextOptions);
partial void ApplyZoomFactor();
}

internal readonly record struct CameraConfiguration(
CameraLocation CameraLocation,
string SelectedCameraDeviceId,
CameraResolutionSelectorDelegate CameraResolutionSelector);
}
Loading