@@ -13,7 +13,7 @@ namespace CommunityToolkit.Maui.Views;
13
13
[ SupportedOSPlatform ( "android21.0" ) ]
14
14
[ SupportedOSPlatform ( "ios" ) ]
15
15
[ SupportedOSPlatform ( "maccatalyst" ) ]
16
- public partial class CameraView : View , ICameraView
16
+ public partial class CameraView : View , ICameraView , IDisposable
17
17
{
18
18
static readonly BindablePropertyKey isAvailablePropertyKey =
19
19
BindableProperty . CreateReadOnly ( nameof ( IsAvailable ) , typeof ( bool ) , typeof ( CameraView ) , CameraViewDefaults . IsAvailable ) ;
@@ -65,22 +65,26 @@ public partial class CameraView : View, ICameraView
65
65
/// Backing BindableProperty for the <see cref="CaptureImageCommand"/> property.
66
66
/// </summary>
67
67
public static readonly BindableProperty CaptureImageCommandProperty =
68
- BindableProperty . CreateReadOnly ( nameof ( CaptureImageCommand ) , typeof ( Command < CancellationToken > ) , typeof ( CameraView ) , default , BindingMode . OneWayToSource , defaultValueCreator : CameraViewDefaults . CreateCaptureImageCommand ) . BindableProperty ;
68
+ BindableProperty . CreateReadOnly ( nameof ( CaptureImageCommand ) , typeof ( Command < CancellationToken > ) , typeof ( CameraView ) , null , BindingMode . OneWayToSource , defaultValueCreator : CameraViewDefaults . CreateCaptureImageCommand ) . BindableProperty ;
69
69
70
70
/// <summary>
71
71
/// Backing BindableProperty for the <see cref="StartCameraPreviewCommand"/> property.
72
72
/// </summary>
73
73
public static readonly BindableProperty StartCameraPreviewCommandProperty =
74
- BindableProperty . CreateReadOnly ( nameof ( StartCameraPreviewCommand ) , typeof ( Command < CancellationToken > ) , typeof ( CameraView ) , default , BindingMode . OneWayToSource , defaultValueCreator : CameraViewDefaults . CreateStartCameraPreviewCommand ) . BindableProperty ;
74
+ BindableProperty . CreateReadOnly ( nameof ( StartCameraPreviewCommand ) , typeof ( Command < CancellationToken > ) , typeof ( CameraView ) , null , BindingMode . OneWayToSource , defaultValueCreator : CameraViewDefaults . CreateStartCameraPreviewCommand ) . BindableProperty ;
75
75
76
76
/// <summary>
77
77
/// Backing BindableProperty for the <see cref="StopCameraPreviewCommand"/> property.
78
78
/// </summary>
79
79
public static readonly BindableProperty StopCameraPreviewCommandProperty =
80
- BindableProperty . CreateReadOnly ( nameof ( StopCameraPreviewCommand ) , typeof ( ICommand ) , typeof ( CameraView ) , default , BindingMode . OneWayToSource , defaultValueCreator : CameraViewDefaults . CreateStopCameraPreviewCommand ) . BindableProperty ;
80
+ BindableProperty . CreateReadOnly ( nameof ( StopCameraPreviewCommand ) , typeof ( ICommand ) , typeof ( CameraView ) , null , BindingMode . OneWayToSource , defaultValueCreator : CameraViewDefaults . CreateStopCameraPreviewCommand ) . BindableProperty ;
81
81
82
+
83
+ readonly SemaphoreSlim captureImageSemaphoreSlim = new ( 1 , 1 ) ;
82
84
readonly WeakEventManager weakEventManager = new ( ) ;
83
85
86
+ bool isDisposed ;
87
+
84
88
/// <summary>
85
89
/// Event that is raised when the camera capture fails.
86
90
/// </summary>
@@ -188,7 +192,14 @@ bool ICameraView.IsBusy
188
192
set => SetValue ( isCameraBusyPropertyKey , value ) ;
189
193
}
190
194
191
- private protected new CameraViewHandler Handler => ( CameraViewHandler ) ( base . Handler ?? throw new InvalidOperationException ( "Unable to retrieve Handler" ) ) ;
195
+ new CameraViewHandler Handler => ( CameraViewHandler ) ( base . Handler ?? throw new InvalidOperationException ( "Unable to retrieve Handler" ) ) ;
196
+
197
+ /// <inheritdoc/>
198
+ public void Dispose ( )
199
+ {
200
+ Dispose ( disposing : true ) ;
201
+ GC . SuppressFinalize ( this ) ;
202
+ }
192
203
193
204
/// <inheritdoc cref="ICameraView.GetAvailableCameras"/>
194
205
public async ValueTask < IReadOnlyList < CameraInfo > > GetAvailableCameras ( CancellationToken token )
@@ -207,8 +218,37 @@ public async ValueTask<IReadOnlyList<CameraInfo>> GetAvailableCameras(Cancellati
207
218
}
208
219
209
220
/// <inheritdoc cref="ICameraView.CaptureImage"/>
210
- public ValueTask CaptureImage ( CancellationToken token ) =>
211
- Handler . CameraManager . TakePicture ( token ) ;
221
+ public async Task < Stream > CaptureImage ( CancellationToken token )
222
+ {
223
+ // Use SemaphoreSlim to ensure `MediaCaptured` and `MediaCaptureFailed` events are unsubscribed before calling `TakePicture` again
224
+ // Without this SemaphoreSlim, previous calls to this method will fire `MediaCaptured` and/or `MediaCaptureFailed` events causing this method to return the wrong Stream or throw the wrong Exception
225
+ await captureImageSemaphoreSlim . WaitAsync ( token ) ;
226
+
227
+ var mediaStreamTCS = new TaskCompletionSource < Stream > ( TaskCreationOptions . RunContinuationsAsynchronously ) ;
228
+
229
+ MediaCaptured += HandleMediaCaptured ;
230
+ MediaCaptureFailed += HandleMediaCapturedFailed ;
231
+
232
+ try
233
+ {
234
+ await Handler . CameraManager . TakePicture ( token ) ;
235
+
236
+ var stream = await mediaStreamTCS . Task . WaitAsync ( token ) ;
237
+ return stream ;
238
+ }
239
+ finally
240
+ {
241
+ MediaCaptured -= HandleMediaCaptured ;
242
+ MediaCaptureFailed -= HandleMediaCapturedFailed ;
243
+
244
+ // Release SemaphoreSlim after `MediaCaptured` and `MediaCaptureFailed` events are unsubscribed
245
+ captureImageSemaphoreSlim . Release ( ) ;
246
+ }
247
+
248
+ void HandleMediaCaptured ( object ? sender , MediaCapturedEventArgs e ) => mediaStreamTCS . SetResult ( e . Media ) ;
249
+
250
+ void HandleMediaCapturedFailed ( object ? sender , MediaCaptureFailedEventArgs e ) => mediaStreamTCS . SetException ( new CameraException ( e . FailureReason ) ) ;
251
+ }
212
252
213
253
/// <inheritdoc cref="ICameraView.StartCameraPreview"/>
214
254
public Task StartCameraPreview ( CancellationToken token ) =>
@@ -218,6 +258,20 @@ public Task StartCameraPreview(CancellationToken token) =>
218
258
public void StopCameraPreview ( ) =>
219
259
Handler . CameraManager . StopCameraPreview ( ) ;
220
260
261
+ /// <inheritdoc/>
262
+ protected virtual void Dispose ( bool disposing )
263
+ {
264
+ if ( ! isDisposed )
265
+ {
266
+ if ( disposing )
267
+ {
268
+ captureImageSemaphoreSlim . Dispose ( ) ;
269
+ }
270
+
271
+ isDisposed = true ;
272
+ }
273
+ }
274
+
221
275
void ICameraView . OnMediaCaptured ( Stream imageData )
222
276
{
223
277
weakEventManager . HandleEvent ( this , new MediaCapturedEventArgs ( imageData ) , nameof ( MediaCaptured ) ) ;
0 commit comments