Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Porting CameraHelper and CameraPreview #71

Merged
merged 11 commits into from
May 27, 2023
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
3 changes: 3 additions & 0 deletions components/CameraPreview/OpenSolution.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
@ECHO OFF

powershell ..\..\tooling\ProjectHeads\GenerateSingleSampleHeads.ps1 -componentPath %CD% %*
8 changes: 8 additions & 0 deletions components/CameraPreview/samples/CameraPreview.Samples.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Project Sdk="MSBuild.Sdk.Extras/3.0.23">
<PropertyGroup>
<ToolkitComponentName>CameraPreview</ToolkitComponentName>
</PropertyGroup>

<!-- Sets this up as a toolkit component's sample project -->
<Import Project="$(ToolingDirectory)\ToolkitComponent.SampleProject.props" />
</Project>
92 changes: 92 additions & 0 deletions components/CameraPreview/samples/CameraPreview.md
Original file line number Diff line number Diff line change
@@ -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
<controls:CameraPreview x:Name="CameraPreviewControl">
</controls:CameraPreview>
```

```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
<controls:CameraPreview x:Name="CameraPreviewControl" IsFrameSourceGroupButtonVisible="false"
</controls:CameraPreview>
```

## 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;
}
```
29 changes: 29 additions & 0 deletions components/CameraPreview/samples/CameraPreviewSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!-- 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. -->
<Page x:Class="CameraPreviewExperiment.Samples.CameraPreviewSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:CameraPreviewExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:muxc="using:Microsoft.UI.Xaml.Controls"
mc:Ignorable="d">

<StackPanel HorizontalAlignment="Center"
Orientation="Vertical"
Spacing="12">
<controls:CameraPreview x:Name="CameraPreviewControl"
Height="320"
IsFrameSourceGroupButtonVisible="{x:Bind ShowCamera, Mode=OneWay}" />
<muxc:InfoBar x:Name="ErrorBar"
Title="Error"
IsOpen="False"
Severity="Error" />
<Image x:Name="CurrentFrameImage"
Height="240"
HorizontalAlignment="Center" />
<Button HorizontalAlignment="Center"
Click="CaptureButton_Click"
Content="Capture" />
</StackPanel>
</Page>
148 changes: 148 additions & 0 deletions components/CameraPreview/samples/CameraPreviewSample.xaml.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
31 changes: 31 additions & 0 deletions components/CameraPreview/samples/Dependencies.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!--
WinUI 2 under UWP uses TargetFramework uap10.0.*
WinUI 3 under WinAppSdk uses TargetFramework net6.0-windows10.*
However, under Uno-powered platforms, both WinUI 2 and 3 can share the same TargetFramework.

MSBuild doesn't play nicely with this out of the box, so we've made it easy for you.

For .NET Standard packages, you can use the Nuget Package Manager in Visual Studio.
For UWP / WinAppSDK / Uno packages, place the package references here.
-->
<Project>
<!-- WinUI 2 / UWP -->
<ItemGroup Condition="'$(IsUwp)' == 'true'">
<!-- <PackageReference Include="Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 2 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '2'">
<!-- <PackageReference Include="Uno.Microsoft.Toolkit.Uwp.UI.Controls.Primitives" Version="7.1.11"/> -->
</ItemGroup>

<!-- WinUI 3 / WinAppSdk -->
<ItemGroup Condition="'$(IsWinAppSdk)' == 'true'">
<!-- <PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2"/> -->
</ItemGroup>

<!-- WinUI 3 / Uno -->
<ItemGroup Condition="'$(IsUno)' == 'true' AND '$(WinUIMajorVersion)' == '3'">
<!-- <PackageReference Include="Uno.CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.100-dev.15.g12261e2626"/> -->
</ItemGroup>
</Project>
13 changes: 13 additions & 0 deletions components/CameraPreview/src/AdditionalAssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -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")]
21 changes: 21 additions & 0 deletions components/CameraPreview/src/CameraPreview.Constants.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Camera Control to preview video. Can subscribe to video frames, software bitmap when they arrive.
/// </summary>
public partial class CameraPreview
{
/// <summary>
/// Key for the MediaPlayerElement Control inside the Camera Preview Control
/// </summary>
private const string Preview_MediaPlayerElementControl = "MediaPlayerElementControl";

/// <summary>
/// Key for the Frame Source Group Toggle Button Control inside the Camera Preview Control
/// </summary>
private const string Preview_FrameSourceGroupButton = "FrameSourceGroupButton";
}
16 changes: 16 additions & 0 deletions components/CameraPreview/src/CameraPreview.Events.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Camera Control to preview video. Can subscribe to video frames, software bitmap when they arrive.
/// </summary>
public partial class CameraPreview
{
/// <summary>
/// Event raised when camera preview fails.
/// </summary>
public event EventHandler<PreviewFailedEventArgs> PreviewFailed;
}
Loading