diff --git a/components/CameraPreview/OpenSolution.bat b/components/CameraPreview/OpenSolution.bat
new file mode 100644
index 00000000..814a56d4
--- /dev/null
+++ b/components/CameraPreview/OpenSolution.bat
@@ -0,0 +1,3 @@
+@ECHO OFF
+
+powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
\ No newline at end of file
diff --git a/components/CameraPreview/samples/CameraPreview.Samples.csproj b/components/CameraPreview/samples/CameraPreview.Samples.csproj
new file mode 100644
index 00000000..b910ea2a
--- /dev/null
+++ b/components/CameraPreview/samples/CameraPreview.Samples.csproj
@@ -0,0 +1,8 @@
+
+
+ CameraPreview
+
+
+
+
+
diff --git a/components/CameraPreview/samples/CameraPreview.md b/components/CameraPreview/samples/CameraPreview.md
new file mode 100644
index 00000000..e63a76a6
--- /dev/null
+++ b/components/CameraPreview/samples/CameraPreview.md
@@ -0,0 +1,92 @@
+---
+title: CameraPreview
+author: skommireddi
+description: The CameraPreview control allows to easily preview video in the MediaPlayerElement from available camera frame source groups. You can subscribe and get real time video frames and software bitmaps as they arrive from the selected camera source. It shows only frame sources that support color video preview or video record streams.
+keywords: CameraPreview, Control, skommireddi
+dev_langs:
+ - csharp
+category: Controls
+subcategory: Layout
+discussion-id: 0
+issue-id: 0
+---
+
+# CameraPreview
+
+The **CameraPreview** control allows to easily preview video in the MediaPlayerElement from available camera frame source groups. You can subscribe and get real time video frames and software bitmaps as they arrive from the selected camera source. It shows only frame sources that support color video preview or video record streams.
+
+> [!IMPORTANT]
+> Make sure you have the [webcam capability](/windows/uwp/packaging/app-capability-declarations#device-capabilities) enabled for your app to access the device's camera.
+
+> [!Sample CameraPreviewSample]
+
+## Syntax
+
+```xaml
+
+
+```
+
+```csharp
+
+CameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed;
+await CameraPreviewControl.StartAsync();
+CameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived;
+
+
+private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e)
+{
+ var videoFrame = e.VideoFrame;
+ var softwareBitmap = videoFrame.SoftwareBitmap;
+}
+
+private void CameraPreviewControl_PreviewFailed(object sender, PreviewFailedEventArgs e)
+{
+ var errorMessage = e.Error;
+}
+```
+
+
+> [!IMPORTANT]
+> As a developer, you will need to make sure the CameraHelper resources used by the control are cleaned up when appropriate. See [CameraHelper documentation](../helpers/CameraHelper.md) for more details
+
+## Properties
+
+| Property | Type | Description |
+| -- | -- | -- |
+| CameraHelper| [CameraHelper](../helpers/CameraHelper.md) | Gets the CameraHelper associated with the control. |
+| IsFrameSourceGroupButtonVisible | bool| Set this property to hide or show Frame Source Group Button. Note: This button is conditionally visible based on more than one source being available. |
+
+```xaml
+
+```
+
+## Methods
+
+| Methods | Return Type | Description |
+| -- | -- | -- |
+| StartAsync() | Task | Initializes camera preview control with a default Camera Helper instance and starts preview and frame capture. |
+| StartAsync(CameraHelper cameraHelper) | Task | Initializes camera preview control with provided Camera Helper instance. |
+| Stop() | void | Stops camera preview and disposes MediaPlayer. |
+
+## Events
+
+| Events | Description |
+| -- | -- |
+| PreviewFailed | Fires when camera preview fails. You can get the error reason from the PreviewFailedEventArgs.|
+
+## Examples
+
+Demonstrates using the camera control and camera helper to preview video from a specific media frame source group.
+
+```csharp
+var availableFrameSourceGroups = await CameraHelper.GetFrameSourceGroupsAsync();
+if(availableFrameSourceGroups != null)
+{
+ CameraHelper cameraHelper = new CameraHelper() { FrameSourceGroup = availableFrameSourceGroups.FirstOrDefault() };
+ _cameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed;
+ await _cameraPreviewControl.StartAsync(cameraHelper);
+ _cameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived;
+}
+```
diff --git a/components/CameraPreview/samples/CameraPreviewSample.xaml b/components/CameraPreview/samples/CameraPreviewSample.xaml
new file mode 100644
index 00000000..1bc13079
--- /dev/null
+++ b/components/CameraPreview/samples/CameraPreviewSample.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/components/CameraPreview/samples/CameraPreviewSample.xaml.cs b/components/CameraPreview/samples/CameraPreviewSample.xaml.cs
new file mode 100644
index 00000000..0936f765
--- /dev/null
+++ b/components/CameraPreview/samples/CameraPreviewSample.xaml.cs
@@ -0,0 +1,148 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Controls;
+using CommunityToolkit.WinUI.Helpers;
+using Windows.Graphics.Imaging;
+using Windows.Media;
+using Windows.ApplicationModel;
+#if WINAPPSDK
+using Microsoft.UI.Xaml.Media.Imaging;
+#else
+using Windows.UI.Xaml.Media.Imaging;
+#endif
+
+namespace CameraPreviewExperiment.Samples;
+
+[ToolkitSampleBoolOption("ShowCamera", true, Title = "Show camera toggle button")]
+[ToolkitSample(id: nameof(CameraPreviewSample), "CameraPreview", description: $"A sample for showing how to create and use a {nameof(CameraPreview)} control.")]
+public sealed partial class CameraPreviewSample : Page
+{
+ private static SemaphoreSlim? semaphoreSlim;
+ private VideoFrame _currentVideoFrame;
+ private SoftwareBitmapSource _softwareBitmapSource;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ public CameraPreviewSample()
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ {
+ this.InitializeComponent();
+ this.Loaded += this.CameraPreviewSample_Loaded;
+ semaphoreSlim = new SemaphoreSlim(1);
+
+ }
+
+ private void CameraPreviewSample_Loaded(object sender, RoutedEventArgs e)
+ {
+ Load();
+ }
+
+ private async void Load()
+ {
+ // Using a semaphore lock for synchronization.
+ // This method gets called multiple times when accessing the page from Latest Pages
+ // and creates unused duplicate references to Camera and memory leaks.
+ await semaphoreSlim!.WaitAsync();
+
+ var cameraHelper = CameraPreviewControl?.CameraHelper;
+ UnsubscribeFromEvents();
+
+ if (CameraPreviewControl != null)
+ {
+ CameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed!;
+ await CameraPreviewControl.StartAsync(cameraHelper!);
+ CameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived!;
+ }
+
+ _softwareBitmapSource = new SoftwareBitmapSource();
+ CurrentFrameImage.Source = _softwareBitmapSource;
+
+ semaphoreSlim.Release();
+ }
+
+ protected override void OnNavigatedTo(NavigationEventArgs e)
+ {
+ base.OnNavigatedTo(e);
+#if !WINAPPSDK
+ Application.Current.Suspending += Application_Suspending;
+ Application.Current.Resuming += Application_Resuming;
+#endif
+ }
+
+ protected async override void OnNavigatedFrom(NavigationEventArgs e)
+ {
+ base.OnNavigatedFrom(e);
+#if !WINAPPSDK
+ Application.Current.Suspending -= Application_Suspending;
+ Application.Current.Resuming -= Application_Resuming;
+#endif
+ await CleanUpAsync();
+ }
+
+ private async void Application_Suspending(object sender, SuspendingEventArgs e)
+ {
+ if (Frame?.CurrentSourcePageType == typeof(CameraPreviewSample))
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ await CleanUpAsync();
+ deferral.Complete();
+ }
+ }
+
+ private async void Application_Resuming(object sender, object e)
+ {
+ if (CameraPreviewControl != null)
+ {
+ var cameraHelper = CameraPreviewControl.CameraHelper;
+ CameraPreviewControl.PreviewFailed += CameraPreviewControl_PreviewFailed!;
+ await CameraPreviewControl.StartAsync(cameraHelper);
+ CameraPreviewControl.CameraHelper.FrameArrived += CameraPreviewControl_FrameArrived!;
+ }
+ }
+
+ private void CameraPreviewControl_FrameArrived(object sender, FrameEventArgs e)
+ {
+ _currentVideoFrame = e.VideoFrame;
+ }
+
+ private void CameraPreviewControl_PreviewFailed(object sender, PreviewFailedEventArgs e)
+ {
+ ErrorBar.Message = e.Error;
+ ErrorBar.IsOpen = true;
+ }
+
+
+ private async void CaptureButton_Click(object sender, RoutedEventArgs e)
+ {
+ var softwareBitmap = _currentVideoFrame?.SoftwareBitmap;
+
+ if (softwareBitmap != null)
+ {
+ if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
+ {
+ softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
+ }
+
+ await _softwareBitmapSource!.SetBitmapAsync(softwareBitmap);
+ }
+ }
+
+ private void UnsubscribeFromEvents()
+ {
+ if (CameraPreviewControl.CameraHelper != null)
+ {
+ CameraPreviewControl.CameraHelper.FrameArrived -= CameraPreviewControl_FrameArrived!;
+ }
+
+ CameraPreviewControl.PreviewFailed -= CameraPreviewControl_PreviewFailed!;
+ }
+
+ private async Task CleanUpAsync()
+ {
+ UnsubscribeFromEvents();
+
+ CameraPreviewControl.Stop();
+ await CameraPreviewControl.CameraHelper.CleanUpAsync();
+ }
+}
diff --git a/components/CameraPreview/samples/Dependencies.props b/components/CameraPreview/samples/Dependencies.props
new file mode 100644
index 00000000..e622e1df
--- /dev/null
+++ b/components/CameraPreview/samples/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/CameraPreview/src/AdditionalAssemblyInfo.cs b/components/CameraPreview/src/AdditionalAssemblyInfo.cs
new file mode 100644
index 00000000..f3578634
--- /dev/null
+++ b/components/CameraPreview/src/AdditionalAssemblyInfo.cs
@@ -0,0 +1,13 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+// These `InternalsVisibleTo` calls are intended to make it easier for
+// for any internal code to be testable in all the different test projects
+// used with the Labs infrastructure.
+[assembly: InternalsVisibleTo("CameraPreview.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CameraPreview.Tests.WinAppSdk")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.Uwp")]
+[assembly: InternalsVisibleTo("CommunityToolkit.Tests.WinAppSdk")]
diff --git a/components/CameraPreview/src/CameraPreview.Constants.cs b/components/CameraPreview/src/CameraPreview.Constants.cs
new file mode 100644
index 00000000..e8e61652
--- /dev/null
+++ b/components/CameraPreview/src/CameraPreview.Constants.cs
@@ -0,0 +1,21 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// Camera Control to preview video. Can subscribe to video frames, software bitmap when they arrive.
+///
+public partial class CameraPreview
+{
+ ///
+ /// Key for the MediaPlayerElement Control inside the Camera Preview Control
+ ///
+ private const string Preview_MediaPlayerElementControl = "MediaPlayerElementControl";
+
+ ///
+ /// Key for the Frame Source Group Toggle Button Control inside the Camera Preview Control
+ ///
+ private const string Preview_FrameSourceGroupButton = "FrameSourceGroupButton";
+}
diff --git a/components/CameraPreview/src/CameraPreview.Events.cs b/components/CameraPreview/src/CameraPreview.Events.cs
new file mode 100644
index 00000000..49eaab27
--- /dev/null
+++ b/components/CameraPreview/src/CameraPreview.Events.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// Camera Control to preview video. Can subscribe to video frames, software bitmap when they arrive.
+///
+public partial class CameraPreview
+{
+ ///
+ /// Event raised when camera preview fails.
+ ///
+ public event EventHandler PreviewFailed;
+}
diff --git a/components/CameraPreview/src/CameraPreview.Properties.cs b/components/CameraPreview/src/CameraPreview.Properties.cs
new file mode 100644
index 00000000..c5af22a6
--- /dev/null
+++ b/components/CameraPreview/src/CameraPreview.Properties.cs
@@ -0,0 +1,37 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// Camera Control to preview video. Can subscribe to video frames, software bitmap when they arrive.
+///
+public partial class CameraPreview
+{
+ ///
+ /// Using a DependencyProperty as the backing store for . This enables animation, styling, binding, etc...
+ ///
+ public static readonly DependencyProperty IsFrameSourceGroupButtonVisibleProperty =
+ DependencyProperty.Register(nameof(IsFrameSourceGroupButtonVisible), typeof(bool), typeof(CameraPreview), new PropertyMetadata(true, IsFrameSourceGroupButtonVisibleChanged));
+
+ ///
+ /// Gets or sets a value indicating whether Frame Source Group Button is visible or not
+ ///
+ public bool IsFrameSourceGroupButtonVisible
+ {
+ get { return (bool)GetValue(IsFrameSourceGroupButtonVisibleProperty); }
+ set { SetValue(IsFrameSourceGroupButtonVisibleProperty, value); }
+ }
+
+ private static void IsFrameSourceGroupButtonVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is CameraPreview cameraPreview)
+ {
+ if (cameraPreview._frameSourceGroupButton != null)
+ {
+ cameraPreview.SetFrameSourceGroupButtonVisibility();
+ }
+ }
+ }
+}
diff --git a/components/CameraPreview/src/CameraPreview.cs b/components/CameraPreview/src/CameraPreview.cs
new file mode 100644
index 00000000..629a8c4a
--- /dev/null
+++ b/components/CameraPreview/src/CameraPreview.cs
@@ -0,0 +1,194 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.Media.Capture.Frames;
+using Windows.Media.Core;
+using Windows.Media.Playback;
+using CommunityToolkit.WinUI.Helpers;
+
+namespace CommunityToolkit.WinUI.Controls;
+
+/// Camera Control to preview video. Can subscribe to video frames, software bitmap when they arrive.
+///
+[TemplatePart(Name = Preview_MediaPlayerElementControl, Type = typeof(MediaPlayerElement))]
+[TemplatePart(Name = Preview_FrameSourceGroupButton, Type = typeof(Button))]
+public partial class CameraPreview : Control
+{
+ private CameraHelper _cameraHelper;
+ private MediaPlayer _mediaPlayer;
+ private MediaPlayerElement _mediaPlayerElementControl;
+ private Button _frameSourceGroupButton;
+
+ private IReadOnlyList _frameSourceGroups;
+
+ private bool IsFrameSourceGroupButtonAvailable => _frameSourceGroups != null && _frameSourceGroups.Count > 1;
+
+ ///
+ /// Gets Camera Helper
+ ///
+ public CameraHelper CameraHelper { get => _cameraHelper; private set => _cameraHelper = value; }
+
+ ///
+ /// Initialize control with a default CameraHelper instance for video preview and frame capture.
+ ///
+ /// A representing the asynchronous operation.
+ public async Task StartAsync()
+ {
+ await StartAsync(new CameraHelper());
+ }
+
+ ///
+ /// Initialize control with a CameraHelper instance for video preview and frame capture.
+ ///
+ ///
+ /// A representing the asynchronous operation.
+ public async Task StartAsync(CameraHelper cameraHelper)
+ {
+ if (cameraHelper == null)
+ {
+ cameraHelper = new CameraHelper();
+ }
+
+ _cameraHelper = cameraHelper;
+ _frameSourceGroups = await CameraHelper.GetFrameSourceGroupsAsync();
+
+ // UI controls exist and are initialized
+ if (_mediaPlayerElementControl != null)
+ {
+ await InitializeAsync();
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ public CameraPreview()
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ {
+ this.DefaultStyleKey = typeof(CameraPreview);
+ }
+
+ ///
+ protected async override void OnApplyTemplate()
+ {
+ base.OnApplyTemplate();
+
+ if (_frameSourceGroupButton != null)
+ {
+ _frameSourceGroupButton.Click -= FrameSourceGroupButton_ClickAsync;
+ }
+
+ _mediaPlayerElementControl = (MediaPlayerElement)GetTemplateChild(Preview_MediaPlayerElementControl);
+ _frameSourceGroupButton = (Button)GetTemplateChild(Preview_FrameSourceGroupButton);
+
+ if (_frameSourceGroupButton != null)
+ {
+ _frameSourceGroupButton.Click += FrameSourceGroupButton_ClickAsync;
+ _frameSourceGroupButton.IsEnabled = false;
+ _frameSourceGroupButton.Visibility = Visibility.Collapsed;
+ }
+
+ if (_cameraHelper != null)
+ {
+ await InitializeAsync();
+ }
+ }
+
+ private async Task InitializeAsync()
+ {
+ var result = await _cameraHelper.InitializeAndStartCaptureAsync();
+ if (result != CameraHelperResult.Success)
+ {
+ InvokePreviewFailed(result.ToString());
+ }
+
+ SetUIControls(result);
+ }
+
+ private async void FrameSourceGroupButton_ClickAsync(object sender, RoutedEventArgs e)
+ {
+ var oldGroup = _cameraHelper.FrameSourceGroup;
+ var currentIndex = _frameSourceGroups.Select((grp, index) => new { grp, index }).First(v => v.grp.Id == oldGroup.Id).index;
+ var newIndex = currentIndex < (_frameSourceGroups.Count - 1) ? currentIndex + 1 : 0;
+ var group = _frameSourceGroups[newIndex];
+ _frameSourceGroupButton.IsEnabled = false;
+ _cameraHelper.FrameSourceGroup = group;
+ await InitializeAsync();
+ }
+
+ private void InvokePreviewFailed(string error)
+ {
+ EventHandler handler = PreviewFailed;
+ handler?.Invoke(this, new PreviewFailedEventArgs { Error = error });
+ }
+
+ private void SetMediaPlayerSource()
+ {
+ try
+ {
+ var frameSource = _cameraHelper?.PreviewFrameSource;
+ if (frameSource != null)
+ {
+ if (_mediaPlayer == null)
+ {
+ _mediaPlayer = new MediaPlayer
+ {
+ AutoPlay = true,
+ RealTimePlayback = true
+ };
+ }
+
+ _mediaPlayer.Source = MediaSource.CreateFromMediaFrameSource(frameSource);
+ _mediaPlayerElementControl.SetMediaPlayer(_mediaPlayer);
+ }
+ }
+ catch (Exception ex)
+ {
+ InvokePreviewFailed(ex.Message);
+ }
+ }
+
+ private void SetUIControls(CameraHelperResult result)
+ {
+ var success = result == CameraHelperResult.Success;
+ if (success)
+ {
+ SetMediaPlayerSource();
+ }
+ else
+ {
+ _mediaPlayerElementControl.SetMediaPlayer(null);
+ }
+
+ _frameSourceGroupButton.IsEnabled = IsFrameSourceGroupButtonAvailable;
+ SetFrameSourceGroupButtonVisibility();
+ }
+
+ private void SetFrameSourceGroupButtonVisibility()
+ {
+ _frameSourceGroupButton.Visibility = IsFrameSourceGroupButtonAvailable && IsFrameSourceGroupButtonVisible
+ ? Visibility.Visible
+ : Visibility.Collapsed;
+ }
+
+ ///
+ /// Stops preview.
+ ///
+ public void Stop()
+ {
+ if (_mediaPlayerElementControl != null)
+ {
+ _mediaPlayerElementControl.SetMediaPlayer(null);
+ }
+
+ if (_mediaPlayer != null)
+ {
+ _mediaPlayer.Dispose();
+#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
+ _mediaPlayer = null;
+#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
+ }
+ }
+}
diff --git a/components/CameraPreview/src/CameraPreview.xaml b/components/CameraPreview/src/CameraPreview.xaml
new file mode 100644
index 00000000..22ae4e13
--- /dev/null
+++ b/components/CameraPreview/src/CameraPreview.xaml
@@ -0,0 +1,23 @@
+
+
+
+
diff --git a/components/CameraPreview/src/CommunityToolkit.WinUI.Controls.CameraPreview.csproj b/components/CameraPreview/src/CommunityToolkit.WinUI.Controls.CameraPreview.csproj
new file mode 100644
index 00000000..ad0c9fe2
--- /dev/null
+++ b/components/CameraPreview/src/CommunityToolkit.WinUI.Controls.CameraPreview.csproj
@@ -0,0 +1,17 @@
+
+
+ CameraPreview
+ This package contains CameraPreview.
+ 8.0.0-beta.1
+
+
+ CommunityToolkit.WinUI.Controls.CameraPreviewRns
+
+
+
+
+
+
+
+
+
diff --git a/components/CameraPreview/src/Dependencies.props b/components/CameraPreview/src/Dependencies.props
new file mode 100644
index 00000000..e622e1df
--- /dev/null
+++ b/components/CameraPreview/src/Dependencies.props
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/CameraPreview/src/MultiTarget.props b/components/CameraPreview/src/MultiTarget.props
new file mode 100644
index 00000000..18f6c7c9
--- /dev/null
+++ b/components/CameraPreview/src/MultiTarget.props
@@ -0,0 +1,9 @@
+
+
+
+ uwp;wasdk;
+
+
diff --git a/components/CameraPreview/src/PreviewFailedEventArgs.cs b/components/CameraPreview/src/PreviewFailedEventArgs.cs
new file mode 100644
index 00000000..851a69ac
--- /dev/null
+++ b/components/CameraPreview/src/PreviewFailedEventArgs.cs
@@ -0,0 +1,16 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Controls;
+
+///
+/// PreviewFailed Event Args
+///
+public class PreviewFailedEventArgs : EventArgs
+{
+ ///
+ /// Gets error information about failure
+ ///
+ public string? Error { get; internal set; }
+}
diff --git a/components/CameraPreview/src/Themes/Generic.xaml b/components/CameraPreview/src/Themes/Generic.xaml
new file mode 100644
index 00000000..86709b58
--- /dev/null
+++ b/components/CameraPreview/src/Themes/Generic.xaml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
diff --git a/components/CameraPreview/tests/CameraPreview.Tests.projitems b/components/CameraPreview/tests/CameraPreview.Tests.projitems
new file mode 100644
index 00000000..68b9c977
--- /dev/null
+++ b/components/CameraPreview/tests/CameraPreview.Tests.projitems
@@ -0,0 +1,11 @@
+
+
+
+ $(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+ true
+ E4B3B38E-CB22-4C76-8906-018E2191DA41
+
+
+ CameraPreviewExperiment.Tests
+
+
\ No newline at end of file
diff --git a/components/CameraPreview/tests/CameraPreview.Tests.shproj b/components/CameraPreview/tests/CameraPreview.Tests.shproj
new file mode 100644
index 00000000..ef467af8
--- /dev/null
+++ b/components/CameraPreview/tests/CameraPreview.Tests.shproj
@@ -0,0 +1,13 @@
+
+
+
+ E4B3B38E-CB22-4C76-8906-018E2191DA41
+ 14.0
+
+
+
+
+
+
+
+
diff --git a/components/Helpers/samples/CameraHelper.md b/components/Helpers/samples/CameraHelper.md
new file mode 100644
index 00000000..e68a53e7
--- /dev/null
+++ b/components/Helpers/samples/CameraHelper.md
@@ -0,0 +1,113 @@
+---
+title: CameraHelper
+author: skommireddi
+description: The CameraHelper provides helper methods to easily use the available camera frame sources to preview video, capture video frames and software bitmaps.
+keywords: Helpers, CameraHelper, Camera, Frame Source, Video Frame, Software Bitmap
+dev_langs:
+ - csharp
+category: Helpers
+subcategory: Layout
+discussion-id: 0
+issue-id: 0
+---
+
+# CameraHelper
+
+The **CameraHelper** provides helper methods to easily use the available camera frame sources to preview video, capture video frames and software bitmaps. The helper currently shows camera frame sources that support color video preview or video record streams.
+
+> [!IMPORTANT]
+> Make sure you have the [webcam capability](/windows/uwp/packaging/app-capability-declarations#device-capabilities) enabled for your app to access the device's camera.
+
+> [!Sample CameraHelperSample]
+
+## Syntax
+
+```csharp
+// Creates a Camera Helper and gets video frames from an available frame source.
+using Microsoft.Toolkit.Uwp.Helpers.CameraHelper;
+
+CameraHelper _cameraHelper = new CameraHelper();
+var result = await _cameraHelper.InitializeAndStartCaptureAsync();
+
+// Camera Initialization and Capture failed for some reason
+if(result != CameraHelperResult.Success)
+{
+ // get error information
+ var errorMessage = result.ToString();
+}
+else
+{
+ // Subscribe to get frames as they arrive
+ _cameraHelper.FrameArrived += CameraHelper_FrameArrived;
+}
+
+private void CameraHelper_FrameArrived(object sender, FrameEventArgs e)
+{
+ // Gets the current video frame
+ VideoFrame currentVideoFrame = e.VideoFrame;
+
+ // Gets the software bitmap image
+ SoftwareBitmap softwareBitmap = currentVideoFrame.SoftwareBitmap;
+}
+```
+
+
+## Cleaning up resources
+
+As a developer, you will need to make sure the CameraHelper resources are cleaned up when appropriate. For example, if the CameraHelper is only used on one page, make sure to clean up the CameraHelper when navigating away from the page.
+
+Likewise, make sure to handle app [suspending](/windows/uwp/launch-resume/suspend-an-app) and [resuming](/windows/uwp/launch-resume/resume-an-app) - CameraHelper should be cleaned up when suspending and re-initialized when resuming.
+
+Call `CameraHelper.CleanupAsync()` to clean up all internal resources. See the [CameraHelper sample page in the sample app](https://github.com/windows-toolkit/WindowsCommunityToolkit/tree/rel/7.1.0/Microsoft.Toolkit.Uwp.SampleApp/SamplePages/CameraHelper) for full example.
+
+## Properties
+
+| Property | Type | Description |
+| -- | -- | -- |
+| FrameSourceGroup | MediaFrameSourceGroup | Gets the currently selected MediaFrameSourceGroup for video preview. User can set this property to preview video from a specific source. If no MediaFrameSourceGroup is provided, Camera Helper selects the first available camera source to use for media capture. |
+| PreviewFrameSource | MediaFrameSource | Gets the currently selected MediaFrameSource for video preview. |
+
+## Methods
+
+| Methods | Return Type | Description |
+| -- | -- | -- |
+| GetFrameSourceGroupsAsync() | Task> | Gets a read only list of MediaFrameSourceGroups that support color video record or video preview streams. |
+| InitializeAndStartCaptureAsync() | Task\ | Initializes Media Capture and Frame Reader for video preview and capture frames in real time. |
+| CleanUpAsync() | Task | Use this asynchronous method to dispose Camera Helper resources |
+| Dispose() | void | Use this method to dispose Camera Helper resources |
+
+## Events
+
+| Events | Description |
+| -- | -- |
+| FrameArrived| Fires when a new frame arrives.|
+
+## Examples
+
+Demonstrates using Camera Helper to get video frames from a specific media frame source group.
+
+```csharp
+
+using Microsoft.Toolkit.Uwp.Helpers.CameraHelper;
+
+var availableFrameSourceGroups = await CameraHelper.GetFrameSourceGroupsAsync();
+if(availableFrameSourceGroups != null)
+{
+ CameraHelper cameraHelper = new CameraHelper() { FrameSourceGroup = availableFrameSourceGroups.FirstOrDefault() };
+ var result = await cameraHelper.InitializeAndStartCaptureAsync();
+
+ // Camera Initialization succeeded
+ if(result == CameraHelperResult.Success)
+ {
+ // Subscribe to get frames as they arrive
+ cameraHelper.FrameArrived += CameraHelper_FrameArrived;
+
+ // Optionally set a different frame source format
+ var newFormat = cameraHelper.FrameFormatsAvailable.Find((format) => format.VideoFormat.Width == 640);
+ if (newFormat != null)
+ {
+ await cameraHelper.PreviewFrameSource.SetFormatAsync(newFormat);
+ }
+ }
+}
+```
diff --git a/components/Helpers/samples/CameraHelperSample.xaml b/components/Helpers/samples/CameraHelperSample.xaml
new file mode 100644
index 00000000..435c5684
--- /dev/null
+++ b/components/Helpers/samples/CameraHelperSample.xaml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/components/Helpers/samples/CameraHelperSample.xaml.cs b/components/Helpers/samples/CameraHelperSample.xaml.cs
new file mode 100644
index 00000000..3acdb45c
--- /dev/null
+++ b/components/Helpers/samples/CameraHelperSample.xaml.cs
@@ -0,0 +1,161 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using CommunityToolkit.WinUI.Helpers;
+using Windows.Graphics.Imaging;
+using Windows.Media;
+using Windows.Media.Capture.Frames;
+using Windows.ApplicationModel;
+#if WINAPPSDK
+using Microsoft.UI.Xaml.Media.Imaging;
+#else
+using Windows.UI.Xaml.Media.Imaging;
+#endif
+
+namespace HelpersExperiment.Samples;
+
+[ToolkitSample(id: nameof(CameraHelperSample), "CameraHelper", description: $"A sample for showing how to use {nameof(CameraHelper)}.")]
+public sealed partial class CameraHelperSample : UserControl
+{
+ private CameraHelper _cameraHelper;
+ private VideoFrame _currentVideoFrame;
+ private SoftwareBitmapSource _softwareBitmapSource;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ public CameraHelperSample()
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+ {
+ this.InitializeComponent();
+
+ Loaded += CameraHelperSample_Loaded;
+ Unloaded += CameraHelperSample_Unloaded;
+ }
+
+ private async void CameraHelperSample_Loaded(object sender, RoutedEventArgs e)
+ {
+ Loaded -= CameraHelperSample_Loaded;
+
+ Setup();
+ await InitializeAsync();
+ }
+
+ private async void CameraHelperSample_Unloaded(object sender, RoutedEventArgs e)
+ {
+ Unloaded -= CameraHelperSample_Unloaded;
+
+#if !WINAPPSDK
+ Application.Current.Suspending -= Application_Suspending;
+ Application.Current.Resuming -= Application_Resuming;
+#endif
+ await CleanUpAsync();
+ }
+
+ private void Setup()
+ {
+ _softwareBitmapSource = new SoftwareBitmapSource();
+ CurrentFrameImage.Source = _softwareBitmapSource;
+#if !WINAPPSDK
+ // Application.Current.Suspending += Application_Suspending;
+ // Application.Current.Resuming += Application_Resuming;
+#endif
+ }
+
+ private async void Application_Suspending(object sender, SuspendingEventArgs e)
+ {
+ if (IsLoaded)
+ {
+ var deferral = e.SuspendingOperation.GetDeferral();
+ await CleanUpAsync();
+ deferral.Complete();
+ }
+ }
+
+ private async void Application_Resuming(object sender, object e)
+ {
+ await InitializeAsync();
+ }
+
+ private void CameraHelper_FrameArrived(object sender, FrameEventArgs e)
+ {
+ _currentVideoFrame = e.VideoFrame;
+ }
+
+ private async Task InitializeAsync()
+ {
+ var frameSourceGroups = await CameraHelper.GetFrameSourceGroupsAsync();
+ if (_cameraHelper == null)
+ {
+ _cameraHelper = new CameraHelper();
+ }
+
+ var result = await _cameraHelper.InitializeAndStartCaptureAsync();
+ if (result == CameraHelperResult.Success)
+ {
+ // Subscribe to the video frame as they arrive
+ _cameraHelper.FrameArrived += CameraHelper_FrameArrived!;
+ FrameSourceGroupCombo.ItemsSource = frameSourceGroups;
+ FrameSourceGroupCombo.SelectionChanged += FrameSourceGroupCombo_SelectionChanged;
+ FrameSourceGroupCombo.SelectedIndex = 0;
+ }
+
+ SetUIControls(result);
+ }
+
+ private async void FrameSourceGroupCombo_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ {
+ if (FrameSourceGroupCombo.SelectedItem is MediaFrameSourceGroup selectedGroup && _cameraHelper != null)
+ {
+ _cameraHelper.FrameSourceGroup = selectedGroup;
+ var result = await _cameraHelper.InitializeAndStartCaptureAsync();
+ SetUIControls(result);
+ }
+ }
+
+ private void SetUIControls(CameraHelperResult result)
+ {
+ var success = result == CameraHelperResult.Success;
+ if (!success)
+ {
+ _currentVideoFrame = null!;
+ }
+
+ ErrorBar.Title = result.ToString();
+ ErrorBar.IsOpen = !success;
+
+ CaptureButton.IsEnabled = success;
+ CurrentFrameImage.Opacity = success ? 1 : 0.5;
+ }
+
+ private async void CaptureButton_Click(object sender, RoutedEventArgs e)
+ {
+ var softwareBitmap = _currentVideoFrame?.SoftwareBitmap;
+
+ if (softwareBitmap != null)
+ {
+ if (softwareBitmap.BitmapPixelFormat != BitmapPixelFormat.Bgra8 || softwareBitmap.BitmapAlphaMode == BitmapAlphaMode.Straight)
+ {
+ softwareBitmap = SoftwareBitmap.Convert(softwareBitmap, BitmapPixelFormat.Bgra8, BitmapAlphaMode.Premultiplied);
+ }
+ if (_softwareBitmapSource != null)
+ {
+ await _softwareBitmapSource.SetBitmapAsync(softwareBitmap);
+ }
+ }
+ }
+
+ private async Task CleanUpAsync()
+ {
+ if (FrameSourceGroupCombo != null)
+ {
+ FrameSourceGroupCombo.SelectionChanged -= FrameSourceGroupCombo_SelectionChanged;
+ }
+
+ if (_cameraHelper != null)
+ {
+ _cameraHelper.FrameArrived -= CameraHelper_FrameArrived!;
+ await _cameraHelper.CleanUpAsync();
+ _cameraHelper = null!;
+ }
+ }
+}
diff --git a/components/Helpers/src/CameraHelper/CameraHelper.cs b/components/Helpers/src/CameraHelper/CameraHelper.cs
new file mode 100644
index 00000000..45622e9a
--- /dev/null
+++ b/components/Helpers/src/CameraHelper/CameraHelper.cs
@@ -0,0 +1,312 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.Devices.Enumeration;
+using Windows.Media.Capture;
+using Windows.Media.Capture.Frames;
+using Windows.Media.MediaProperties;
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+///
+/// Helper class for capturing frames from available camera sources.
+/// Make sure you have the capability webcam enabled for your app to access the device's camera.
+///
+#pragma warning disable CA1063 // Implement IDisposable Correctly
+public class CameraHelper : IDisposable
+#pragma warning restore CA1063 // Implement IDisposable Correctly
+{
+ private static IReadOnlyList? _frameSourceGroups;
+#pragma warning disable CA2213 // Disposable fields should be disposed
+ private readonly SemaphoreSlim _semaphoreSlim = new SemaphoreSlim(1);
+#pragma warning restore CA2213 // Disposable fields should be disposed
+ private MediaCapture? _mediaCapture;
+ private MediaFrameReader? _frameReader;
+ private MediaFrameSourceGroup? _group;
+ private bool groupChanged = false;
+ private bool _initialized;
+
+ ///
+ /// Gets a list of available for video preview or video record.
+ ///
+ /// A list.
+ public static async Task> GetFrameSourceGroupsAsync()
+ {
+ if (_frameSourceGroups == null)
+ {
+ var videoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
+ var groups = await MediaFrameSourceGroup.FindAllAsync();
+
+ // Filter out color video preview and video record type sources and remove duplicates video devices.
+ _frameSourceGroups = groups.Where(g => g.SourceInfos.Any(s => s.SourceKind == MediaFrameSourceKind.Color &&
+ (s.MediaStreamType == MediaStreamType.VideoPreview || s.MediaStreamType == MediaStreamType.VideoRecord))
+ && g.SourceInfos.All(sourceInfo => videoDevices.Any(vd => vd.Id == sourceInfo.DeviceInformation.Id))).ToList();
+ }
+
+ return _frameSourceGroups;
+ }
+
+ ///
+ /// Gets a list of available on the source.
+ ///
+ public IReadOnlyList? FrameFormatsAvailable { get; private set; }
+
+ ///
+ /// Gets or sets the source group for camera video preview.
+ ///
+ public MediaFrameSourceGroup FrameSourceGroup
+ {
+ get => _group!;
+ set
+ {
+ groupChanged = _group != value;
+ _group = value;
+ }
+ }
+
+ ///
+ /// Gets the currently selected for video preview.
+ ///
+ public MediaFrameSource? PreviewFrameSource { get; private set; }
+
+ ///
+ /// Occurs when a new frame arrives.
+ ///
+ public event EventHandler? FrameArrived;
+
+ ///
+ /// Initializes Camera Media Capture settings and initializes Frame Reader to capture frames in real time.
+ /// If no MediaFrameSourceGroup is provided, it selects the first available camera source to use for media capture.
+ /// You could select a specific MediaFrameSourceGroup from the available sources using the CameraHelper FrameSourceGroups property.
+ ///
+ /// Result of the async operation.
+ public async Task InitializeAndStartCaptureAsync()
+ {
+ CameraHelperResult result;
+ try
+ {
+ await _semaphoreSlim.WaitAsync();
+
+ // if FrameSourceGroup hasn't changed from last initialization, just return back.
+ if (_initialized && _group != null && !groupChanged)
+ {
+ return CameraHelperResult.Success;
+ }
+
+ groupChanged = false;
+
+ await StopReaderAsync();
+
+ if (_mediaCapture != null)
+ {
+ _mediaCapture.Dispose();
+ _mediaCapture = null;
+ }
+
+ if (_frameSourceGroups == null)
+ {
+ _frameSourceGroups = await GetFrameSourceGroupsAsync();
+ }
+
+ if (_group == null)
+ {
+ _group = _frameSourceGroups.FirstOrDefault();
+ }
+ else
+ {
+ // Verify selected group is part of existing FrameSourceGroups
+ _group = _frameSourceGroups.FirstOrDefault(g => g.Id == _group.Id);
+ }
+
+ // If there is no camera source available, we can't proceed
+ if (_group == null)
+ {
+ return CameraHelperResult.NoFrameSourceGroupAvailable;
+ }
+
+ result = await InitializeMediaCaptureAsync();
+
+ if (PreviewFrameSource != null && _mediaCapture != null)
+ {
+ _frameReader = await _mediaCapture.CreateFrameReaderAsync(PreviewFrameSource);
+ _frameReader.AcquisitionMode = MediaFrameReaderAcquisitionMode.Realtime;
+
+ _frameReader.FrameArrived += Reader_FrameArrived;
+
+ if (_frameReader == null)
+ {
+ result = CameraHelperResult.CreateFrameReaderFailed;
+ }
+ else
+ {
+ MediaFrameReaderStartStatus statusResult = await _frameReader.StartAsync();
+ if (statusResult != MediaFrameReaderStartStatus.Success)
+ {
+ result = CameraHelperResult.StartFrameReaderFailed;
+ }
+ }
+ }
+
+ _initialized = result == CameraHelperResult.Success;
+ return result;
+ }
+ catch (Exception)
+ {
+ await CleanUpAsync();
+ return CameraHelperResult.InitializationFailed_UnknownError;
+ }
+ finally
+ {
+ _semaphoreSlim.Release();
+ }
+ }
+
+ ///
+ /// Clean up the Camera Helper resources
+ ///
+ /// A representing the asynchronous operation.
+ public async Task CleanUpAsync()
+ {
+ await _semaphoreSlim.WaitAsync();
+ try
+ {
+ _initialized = false;
+ await StopReaderAsync();
+
+ if (_mediaCapture != null)
+ {
+ _mediaCapture.Dispose();
+ _mediaCapture = null;
+ }
+ }
+ finally
+ {
+ _semaphoreSlim.Release();
+ }
+ }
+
+ private async Task InitializeMediaCaptureAsync()
+ {
+ if (_mediaCapture == null)
+ {
+ _mediaCapture = new MediaCapture();
+ }
+
+ var settings = new MediaCaptureInitializationSettings()
+ {
+ SourceGroup = _group,
+ MemoryPreference = MediaCaptureMemoryPreference.Cpu,
+ StreamingCaptureMode = StreamingCaptureMode.Video
+ };
+
+ try
+ {
+ await _mediaCapture.InitializeAsync(settings);
+
+ // Find the first video preview or record stream available
+ PreviewFrameSource = _mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoPreview
+ && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
+ if (PreviewFrameSource == null)
+ {
+ PreviewFrameSource = _mediaCapture.FrameSources.FirstOrDefault(source => source.Value.Info.MediaStreamType == MediaStreamType.VideoRecord
+ && source.Value.Info.SourceKind == MediaFrameSourceKind.Color).Value;
+ }
+
+ if (PreviewFrameSource == null)
+ {
+ return CameraHelperResult.NoFrameSourceAvailable;
+ }
+
+ // Get only formats of a certain frame-rate and compatible subtype for previewing, order them by resolution
+ FrameFormatsAvailable = PreviewFrameSource.SupportedFormats.Where(format =>
+ format.FrameRate.Numerator / format.FrameRate.Denominator >= 15 // fps
+ && (string.Compare(format.Subtype, MediaEncodingSubtypes.Nv12, true) == 0
+ || string.Compare(format.Subtype, MediaEncodingSubtypes.Bgra8, true) == 0
+ || string.Compare(format.Subtype, MediaEncodingSubtypes.Yuy2, true) == 0
+ || string.Compare(format.Subtype, MediaEncodingSubtypes.Rgb32, true) == 0))?.OrderBy(format => format.VideoFormat.Width * format.VideoFormat.Height).ToList();
+
+ if (FrameFormatsAvailable == null || !FrameFormatsAvailable.Any())
+ {
+ return CameraHelperResult.NoCompatibleFrameFormatAvailable;
+ }
+
+ // Set the format with the highest resolution available by default
+ var defaultFormat = FrameFormatsAvailable.Last();
+ await PreviewFrameSource.SetFormatAsync(defaultFormat);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ await StopReaderAsync();
+
+ if (_mediaCapture != null)
+ {
+ _mediaCapture.Dispose();
+ _mediaCapture = null;
+ }
+
+ return CameraHelperResult.CameraAccessDenied;
+ }
+ catch (Exception)
+ {
+ await StopReaderAsync();
+
+ if (_mediaCapture != null)
+ {
+ _mediaCapture.Dispose();
+ _mediaCapture = null;
+ }
+
+ return CameraHelperResult.InitializationFailed_UnknownError;
+ }
+
+ return CameraHelperResult.Success;
+ }
+
+ ///
+ /// Stops reading from the frame reader and disposes of the reader.
+ ///
+ /// A representing the asynchronous operation.
+ private async Task StopReaderAsync()
+ {
+ if (_frameReader != null)
+ {
+ _frameReader.FrameArrived -= Reader_FrameArrived;
+ await _frameReader.StopAsync();
+ _frameReader.Dispose();
+ _frameReader = null;
+ }
+ }
+
+ ///
+ /// Handles the frame arrived event by converting the frame to a displayable
+ /// format and rendering it to the screen.
+ ///
+ private void Reader_FrameArrived(MediaFrameReader sender, MediaFrameArrivedEventArgs args)
+ {
+ // TryAcquireLatestFrame will return the latest frame that has not yet been acquired.
+ // This can return null if there is no such frame, or if the reader is not in the
+ // "Started" state. The latter can occur if a FrameArrived event was in flight
+ // when the reader was stopped.
+ var frame = sender.TryAcquireLatestFrame();
+ if (frame != null)
+ {
+ var vmf = frame.VideoMediaFrame;
+ EventHandler handler = FrameArrived!;
+ var frameArgs = new FrameEventArgs() { VideoFrame = vmf.GetVideoFrame() };
+ handler?.Invoke(sender, frameArgs);
+ }
+ }
+
+ private bool disposedValue = false;
+
+ ///
+ public async void Dispose()
+ {
+ if (!disposedValue)
+ {
+ disposedValue = true;
+ await CleanUpAsync();
+ }
+ }
+}
diff --git a/components/Helpers/src/CameraHelper/CameraHelperResult.cs b/components/Helpers/src/CameraHelper/CameraHelperResult.cs
new file mode 100644
index 00000000..e874e42b
--- /dev/null
+++ b/components/Helpers/src/CameraHelper/CameraHelperResult.cs
@@ -0,0 +1,51 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+///
+/// Enum indicating result of initialization.
+///
+public enum CameraHelperResult
+{
+ ///
+ /// Initialization was successful.
+ ///
+ Success,
+
+ ///
+ /// Initialization failed; Frame Reader Creation failed.
+ ///
+ CreateFrameReaderFailed,
+
+ ///
+ /// Initialization failed; Unable to start Frame Reader.
+ ///
+ StartFrameReaderFailed,
+
+ ///
+ /// Initialization failed; Frame Source Group is null.
+ ///
+ NoFrameSourceGroupAvailable,
+
+ ///
+ /// Initialization failed; Frame Source is null.
+ ///
+ NoFrameSourceAvailable,
+
+ ///
+ /// Access to the camera is denied.
+ ///
+ CameraAccessDenied,
+
+ ///
+ /// Initialization failed due to an exception.
+ ///
+ InitializationFailed_UnknownError,
+
+ ///
+ /// Initialization failed; No compatible frame format exposed by the frame source.
+ ///
+ NoCompatibleFrameFormatAvailable
+}
diff --git a/components/Helpers/src/CameraHelper/FrameEventArgs.cs b/components/Helpers/src/CameraHelper/FrameEventArgs.cs
new file mode 100644
index 00000000..a83bea71
--- /dev/null
+++ b/components/Helpers/src/CameraHelper/FrameEventArgs.cs
@@ -0,0 +1,53 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using Windows.Graphics.Imaging;
+using Windows.Media;
+
+namespace CommunityToolkit.WinUI.Helpers;
+
+///
+/// Provides data for the event.
+///
+#pragma warning disable CA1001 // Types that own disposable fields should be disposable
+public class FrameEventArgs : EventArgs
+#pragma warning restore CA1001 // Types that own disposable fields should be disposable
+{
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
+ private VideoFrame? _videoFrame;
+ private VideoFrame? _videoFrameCopy;
+
+ ///
+ /// Gets the video frame.
+ ///
+ public VideoFrame VideoFrame
+ {
+ get
+ {
+ _semaphore.Wait();
+
+ // The VideoFrame could be disposed at any time so we need to create a copy we can use.
+ if (_videoFrameCopy == null &&
+ _videoFrame != null &&
+ _videoFrame.SoftwareBitmap != null)
+ {
+ try
+ {
+ _videoFrameCopy = VideoFrame.CreateWithSoftwareBitmap(SoftwareBitmap.Copy(_videoFrame.SoftwareBitmap));
+ }
+ catch (Exception)
+ {
+ }
+ }
+
+ _semaphore.Release();
+ return _videoFrameCopy! ?? _videoFrame!;
+ }
+
+ internal set
+ {
+ _videoFrame = value;
+ }
+ }
+}
diff --git a/tooling b/tooling
index f5ca2c47..6bc1f28e 160000
--- a/tooling
+++ b/tooling
@@ -1 +1 @@
-Subproject commit f5ca2c47036a84d2c3d5983c8b17008daac39a45
+Subproject commit 6bc1f28e5e3dc5af9432316e700a761fef14a8d2