react-native-camera/windows/ReactNativeCameraCPP/ReactCameraView.cpp
Jon Thysell b990a2465c
feat(windows): updates for RNW 0.63 (#2960)
Build:
* Updated huycn.zxingcpp.winrt to v1.1.0, enables ARM64 build on RNW >=
  0.61
* Updated ReactNativeCameraCPP.sln with RNW 0.63 dependencies
* Added ReactNativeCameraCPP62.sln for building against RNW 0.62
* Cleaned up ReactNativeCameraCPP61.sln for building against RNW 0.61

ReactNativeCamera RNCamera component:
* Passing unspecified maxDuration to recordAsync no longer stops
  recording immediately

Documentation:
* Updated installation docs for RNW 0.63 auto-linking

Closes #2942, #2944
2020-09-01 22:24:04 -03:00

1115 lines
40 KiB
C++

#include "pch.h"
#include "NativeModules.h"
#include "ReactCameraConstants.h"
#include "ReactCameraView.h"
#include "ReactCameraViewManager.h"
namespace winrt {
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Media;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Media::MediaProperties;
using namespace Windows::Storage;
using namespace Windows::Storage::Streams;
using namespace Windows::UI::Core;
using namespace Windows::Media::Core;
using namespace Windows::Media::Playback;
using namespace Windows::Media::Capture;
using namespace Windows::Graphics::Imaging;
using namespace Windows::Media::Devices;
using namespace Windows::System::Display;
using namespace Microsoft::ReactNative;
} // namespace winrt
using namespace std::chrono;
namespace winrt::ReactNativeCameraCPP {
/*static*/ winrt::com_ptr<ReactCameraView> ReactCameraView::Create() {
auto view = winrt::make_self<ReactCameraView>();
view->Initialize();
return view;
}
ReactCameraView::~ReactCameraView() {
m_unloadedEventToken.revoke();
}
void ReactCameraView::Initialize() {
m_childElement = winrt::CaptureElement();
Children().Append(m_childElement);
// RNW does not support DropView yet, so we need to manually register to Unloaded event and remove self
// from the static view list
m_unloadedEventToken = Unloaded(winrt::auto_revoke, [ref = get_weak()](auto const &, auto const &) {
if (auto self = ref.get()) {
auto unloadedAction{self->OnUnloaded()};
unloadedAction.Completed([self](auto && /*sender*/, AsyncStatus const /* args */) {
winrt::ReactNativeCameraCPP::implementation::ReactCameraViewManager::RemoveViewFromList(self);
});
}
});
}
void ReactCameraView::UpdateProperties(IJSValueReader const &propertyMapReader) noexcept {
const JSValueObject &propertyMap = JSValue::ReadObjectFrom(propertyMapReader);
for (auto const &pair : propertyMap) {
auto const &propertyName = pair.first;
auto const &propertyValue = pair.second;
if (!propertyValue.IsNull()) {
if (propertyName == "flashMode") {
UpdateFlashMode(propertyValue.AsInt32());
} else if (propertyName == "autoFocus") {
UpdateAutoFocus(propertyValue.AsInt32());
} else if (propertyName == "whiteBalance") {
UpdateWhiteBalance(propertyValue.AsInt32());
} else if (propertyName == "type") {
UpdateDeviceType(propertyValue.AsInt32());
} else if (propertyName == "cameraId") {
UpdateDeviceId(propertyValue.AsString());
} else if (propertyName == "keepAwake") {
UpdateKeepAwake(propertyValue.AsBoolean());
} else if (propertyName == "mirrorVideo") {
UpdateMirrorVideo(propertyValue.AsBoolean());
} else if (propertyName == "aspect") {
UpdateAspect(propertyValue.AsInt32());
} else if (propertyName == "defaultVideoQuality") {
UpdateDefaultVideoQuality(propertyValue.AsInt32());
} else if (propertyName == "barCodeScannerEnabled") {
UpdateBarcodeScannerEnabled(propertyValue.AsBoolean());
} else if (propertyName == "barCodeTypes") {
UpdateBarcodeTypes(propertyValue.AsArray());
} else if (propertyName == "barCodeReadIntervalMS") {
UpdateBarcodeReadIntervalMS(propertyValue.AsInt32());
}
}
}
}
IAsyncAction ReactCameraView::UpdateFilePropertiesAsync(StorageFile storageFile, JSValueObject const &options) {
auto props = co_await storageFile.Properties().GetImagePropertiesAsync();
auto searchTitle = options.find("title");
if (searchTitle != options.end()) {
const auto &titleValue = options.at("title");
if (titleValue.Type() == JSValueType::String) {
auto titleString = titleValue.AsString();
props.Title(winrt::to_hstring(titleString));
} else {
throw winrt::hresult_invalid_argument();
}
co_await props.SavePropertiesAsync();
}
}
IAsyncAction ReactCameraView::TakePictureAsync(
JSValueObject const &options,
ReactPromise<JSValueObject> const &result) noexcept {
auto capturedPromise = result;
auto capturedOptions = options.Copy();
if (!m_isInitialized) {
capturedPromise.Reject(L"Media device is not initialized.");
co_return;
}
StopBarcodeScanner();
auto dispatcher = Dispatcher();
co_await resume_foreground(dispatcher);
if (auto mediaCapture = m_childElement.Source()) {
// Default with no options is to save the image to the temp folder and return the uri
// This follows the expectations of RNCamera without requiring extra app capabilities
// Devs who want to only capture to memory (and get a base64 encoded image) can specify:
// 1. doNotSave = true and base64 = true OR
// 2. target = "memory"
// Devs who want to capture to the Camera Roll can specify:
// 1. target = "cameraRoll"
// Devs who want to capture to the Pictures Library can specify:
// 1. target = "disk"
// Both will require the Pictures Library app capability to succeed
bool doNotSave;
TryGetValueAsBool(capturedOptions, "doNotSave", doNotSave, false);
int target;
TryGetValueAsInt(
capturedOptions,
"target",
target,
doNotSave ? ReactCameraConstants::CameraCaptureTargetMemory : ReactCameraConstants::CameraCaptureTargetTemp);
// Prepare encoder options
auto encoderPropertySet = winrt::BitmapPropertySet();
// JPEG image quality
float quality;
TryGetValueAsFloat(capturedOptions, "quality", quality, 1.0f);
auto imageQualityValue = winrt::BitmapTypedValue(winrt::box_value(quality), PropertyType::Single);
encoderPropertySet.Insert(hstring(L"ImageQuality"), imageQualityValue);
// Capture the image and relevant EXIF metadata
auto lowLagCapture = co_await mediaCapture.PrepareLowLagPhotoCaptureAsync(
winrt::ImageEncodingProperties().CreateUncompressed(winrt::MediaPixelFormat::Bgra8));
auto capturedPhoto = co_await lowLagCapture.CaptureAsync();
auto softwareBitmap = capturedPhoto.Frame().SoftwareBitmap();
auto capturedProperties = capturedPhoto.Frame().BitmapProperties();
co_await lowLagCapture.FinishAsync();
// writeExif allows callers to (not) save EXIF metadata
// TODO: writeExif can also be passed an object to allow callers to specify additional EXIF
// metadata, however we don't have an easy API for doing so at the moment
bool writeExif;
TryGetValueAsBool(capturedOptions, "writeExif", writeExif, true);
// Add additional metadata to what the camera provided
auto photoOrientation = m_rotationHelper.GetConvertedCameraCaptureOrientation();
auto photoOrientationValue =
winrt::BitmapTypedValue(winrt::box_value(photoOrientation), winrt::PropertyType::UInt16);
capturedProperties.Insert(hstring(L"System.Photo.Orientation"), photoOrientationValue);
// Get transform options
auto targetWidth = softwareBitmap.PixelWidth();
auto targetHeight = softwareBitmap.PixelHeight();
auto bitmapTransform = winrt::BitmapTransform();
int resizeWidth;
if (TryGetValueAsInt(capturedOptions, "width", resizeWidth, 0) && resizeWidth > 0 &&
resizeWidth != softwareBitmap.PixelWidth()) {
targetWidth = resizeWidth;
targetHeight = static_cast<int32_t>(
(static_cast<double>(targetWidth) / static_cast<double>(softwareBitmap.PixelWidth())) *
static_cast<double>(softwareBitmap.PixelHeight()));
// Resize output
bitmapTransform.ScaledWidth(targetWidth);
bitmapTransform.ScaledHeight(targetHeight);
bitmapTransform.InterpolationMode(BitmapInterpolationMode::Fant);
}
bool mirrorImage;
if (TryGetValueAsBool(capturedOptions, "mirrorImage", mirrorImage, false) && mirrorImage) {
bitmapTransform.Flip(winrt::BitmapFlip::Horizontal);
}
// Start creating result
winrt::JSValueObject resultObject;
resultObject["width"] = targetWidth;
resultObject["height"] = targetHeight;
// Return exif data in result
bool exif;
TryGetValueAsBool(capturedOptions, "exif", exif, false);
if (exif) {
resultObject["exif"] = GetExifObject(capturedProperties);
}
if (target == ReactCameraConstants::CameraCaptureTargetMemory) {
// Get memory output stream
auto outputStream = winrt::InMemoryRandomAccessStream();
// Encode Jpeg to output stream
auto encoder = co_await winrt::BitmapEncoder::CreateAsync(
winrt::BitmapEncoder::JpegEncoderId(), outputStream, encoderPropertySet);
encoder.SetSoftwareBitmap(softwareBitmap);
// Copy transformation
encoder.BitmapTransform().ScaledWidth(bitmapTransform.ScaledWidth());
encoder.BitmapTransform().ScaledHeight(bitmapTransform.ScaledHeight());
encoder.BitmapTransform().InterpolationMode(bitmapTransform.InterpolationMode());
encoder.BitmapTransform().Flip(bitmapTransform.Flip());
if (writeExif) {
co_await encoder.BitmapProperties().SetPropertiesAsync(capturedProperties);
}
co_await encoder.FlushAsync();
// Get base64-encoded data to return
auto base64String = co_await GetBase64DataAsync(outputStream);
// Resolve promise with base64 encoded image
resultObject["base64"] = winrt::to_string(base64String);
capturedPromise.Resolve(resultObject);
} else {
try {
auto storageFile = co_await GetOutputStorageFileAsync(ReactCameraConstants::MediaTypeJPG, target);
if (storageFile) {
// Get file output stream
auto outputStream = co_await storageFile.OpenAsync(FileAccessMode::ReadWrite);
// Encode Jpeg to output stream
auto encoder = co_await winrt::BitmapEncoder::CreateAsync(
winrt::BitmapEncoder::JpegEncoderId(), outputStream, encoderPropertySet);
encoder.SetSoftwareBitmap(softwareBitmap);
// Copy transformation
encoder.BitmapTransform().ScaledWidth(bitmapTransform.ScaledWidth());
encoder.BitmapTransform().ScaledHeight(bitmapTransform.ScaledHeight());
encoder.BitmapTransform().InterpolationMode(bitmapTransform.InterpolationMode());
encoder.BitmapTransform().Flip(bitmapTransform.Flip());
if (writeExif) {
co_await encoder.BitmapProperties().SetPropertiesAsync(capturedProperties);
}
co_await encoder.FlushAsync();
co_await UpdateFilePropertiesAsync(storageFile, capturedOptions);
// Resolve promise with uri, and optionally the base64 encoded image
resultObject["uri"] = winrt::to_string(storageFile.Path());
bool includeBase64 = false;
TryGetValueAsBool(capturedOptions, "base64", includeBase64, false);
if (includeBase64) {
auto base64String = co_await GetBase64DataAsync(outputStream);
resultObject["base64"] = winrt::to_string(base64String);
}
capturedPromise.Resolve(resultObject);
}
} catch (...) {
capturedPromise.Reject(L"Unable to capture to disk, check app capabilities.");
}
}
} else {
capturedPromise.Reject(L"Media device is not initialized.");
}
co_await resume_background();
StartBarcodeScanner();
}
IAsyncAction ReactCameraView::RecordAsync(
JSValueObject const &options,
ReactPromise<JSValueObject> const &result) noexcept {
auto capturedPromise = result;
auto capturedOptions = options.Copy();
if (!m_isInitialized) {
capturedPromise.Reject(L"Media device is not initialized.");
co_return;
}
StopBarcodeScanner();
auto dispatcher = Dispatcher();
co_await resume_foreground(dispatcher);
if (auto mediaCapture = m_childElement.Source()) {
int quality;
TryGetValueAsInt(capturedOptions, "quality", quality, m_defaultVideoQuality);
// Update the stream with the requested quality
co_await UpdateMediaStreamPropertiesAsync(quality);
// Create an encoding profile for the requested codec
int mediaType;
winrt::MediaEncodingProfile encodingProfile = nullptr;
int videoCodec;
TryGetValueAsInt(capturedOptions, "codec", videoCodec, ReactCameraConstants::CameraVideoCodecH264);
switch (videoCodec) {
case ReactCameraConstants::CameraVideoCodecHEVC:
encodingProfile = winrt::MediaEncodingProfile::CreateHevc(static_cast<VideoEncodingQuality>(quality));
mediaType = ReactCameraConstants::MediaTypeMP4;
break;
case ReactCameraConstants::CameraVideoCodecWMV:
encodingProfile = winrt::MediaEncodingProfile::CreateWmv(static_cast<VideoEncodingQuality>(quality));
mediaType = ReactCameraConstants::MediaTypeWMV;
break;
default:
videoCodec = ReactCameraConstants::CameraVideoCodecH264;
encodingProfile = winrt::MediaEncodingProfile::CreateMp4(static_cast<VideoEncodingQuality>(quality));
mediaType = ReactCameraConstants::MediaTypeMP4;
break;
}
int videoBitrate;
if (TryGetValueAsInt(capturedOptions, "videoBitrate", videoBitrate, 0) && videoBitrate > 0) {
encodingProfile.Video().Bitrate(videoBitrate);
}
bool muteAudio;
TryGetValueAsBool(capturedOptions, "mute", muteAudio, false);
mediaCapture.AudioDeviceController().Muted(muteAudio);
float maxDurationInSeconds;
TryGetValueAsFloat(capturedOptions, "maxDuration", maxDurationInSeconds, 0.0f);
// Default with no options is to save the video to the temp folder and return the uri
// This follows the expectations of RNCamera without requiring extra app capabilities
// Devs who want to only capture to memory (and get a base64 encoded video) can specify:
// 1. target = "memory"
// Devs who want to capture to the Camera Roll can specify:
// 1. target = "cameraRoll"
// This will require the Pictures Library app capability to succeed
// Devs who want to capture to the Videos Library can specify:
// 1. target = "disk"
// This will require the Videos Library app capability to succeed
int target;
TryGetValueAsInt(capturedOptions, "target", target, ReactCameraConstants::CameraCaptureTargetTemp);
// Start creating result
winrt::JSValueObject resultObject;
resultObject["codec"] = videoCodec;
m_isRecording = true;
if (target == ReactCameraConstants::CameraCaptureTargetMemory) {
auto randomStream = winrt::InMemoryRandomAccessStream();
m_mediaRecording = co_await mediaCapture.PrepareLowLagRecordToStreamAsync(encodingProfile, randomStream);
co_await m_mediaRecording.StartAsync();
DelayStopRecording(maxDurationInSeconds);
co_await WaitAndStopRecording();
auto string = co_await GetBase64DataAsync(randomStream);
resultObject["base64"] = winrt::to_string(string);
capturedPromise.Resolve(resultObject);
} else {
try {
auto storageFile = co_await GetOutputStorageFileAsync(mediaType, target);
m_mediaRecording = co_await mediaCapture.PrepareLowLagRecordToStorageFileAsync(encodingProfile, storageFile);
co_await m_mediaRecording.StartAsync();
DelayStopRecording(maxDurationInSeconds);
co_await WaitAndStopRecording();
co_await UpdateFilePropertiesAsync(storageFile, capturedOptions);
resultObject["uri"] = winrt::to_string(storageFile.Path());
capturedPromise.Resolve(resultObject);
} catch (...) {
capturedPromise.Reject(L"Unable to capture to disk, check app capabilities.");
}
}
m_isRecording = false;
} else {
capturedPromise.Reject("No media capture device found");
}
co_await resume_background();
StartBarcodeScanner();
}
IAsyncAction ReactCameraView::StopRecordAsync() noexcept {
if (!m_isInitialized) {
co_return;
}
SetEvent(m_signal.get());
}
IAsyncAction ReactCameraView::IsRecordingAsync(
winrt::Microsoft::ReactNative::ReactPromise<bool> const &result) noexcept {
auto capturedPromise = result;
if (!m_isInitialized) {
capturedPromise.Reject(L"Media device is not initialized.");
co_return;
}
capturedPromise.Resolve(m_isRecording);
}
IAsyncAction ReactCameraView::PausePreviewAsync() noexcept {
if (!m_isInitialized) {
co_return;
}
auto dispatcher = Dispatcher();
co_await resume_foreground(dispatcher);
if (auto mediaCapture = m_childElement.Source()) {
if (m_isPreview) {
co_await mediaCapture.StopPreviewAsync();
m_isPreview = false;
}
}
co_await resume_background();
}
IAsyncAction ReactCameraView::ResumePreviewAsync() noexcept {
if (!m_isInitialized) {
co_return;
}
auto dispatcher = Dispatcher();
co_await resume_foreground(dispatcher);
if (auto mediaCapture = m_childElement.Source()) {
if (!m_isPreview) {
co_await mediaCapture.StartPreviewAsync();
m_isPreview = true;
}
}
co_await resume_background();
}
// start a timer to end the recording after the specified time
void ReactCameraView::DelayStopRecording(float totalRecordingInSecs) {
ResetEvent(m_signal.get());
auto totalRecordingInMs = static_cast<int32_t>(1000 * totalRecordingInSecs);
std::chrono::duration<int32_t, std::milli> secs(totalRecordingInMs <= 0 ? INT32_MAX : totalRecordingInMs);
m_recordTimer = winrt::Windows::System::Threading::ThreadPoolTimer::CreateTimer(
[this](const winrt::Windows::System::Threading::ThreadPoolTimer) noexcept { SetEvent(m_signal.get()); }, secs);
}
IAsyncAction ReactCameraView::WaitAndStopRecording() {
co_await resume_on_signal(m_signal.get());
if (m_recordTimer) {
m_recordTimer.Cancel();
}
// Switch to UI thread to stop recording
auto dispatcher = Dispatcher();
co_await resume_foreground(dispatcher);
co_await m_mediaRecording.StopAsync();
// Reset stream to default
co_await UpdateMediaStreamPropertiesAsync();
co_await resume_background();
}
// Select a particular camera, need to clean up and reinitialize the mediaCapture object
fire_and_forget ReactCameraView::UpdateDeviceId(std::string cameraId) {
if (m_cameraId == cameraId && m_isInitialized) {
return;
}
m_cameraId = cameraId;
if (m_isInitialized) {
co_await CleanupMediaCaptureAsync();
}
co_await InitializeAsync();
}
// Switch between front and back cameras, need to clean up and reinitialize the mediaCapture object
fire_and_forget ReactCameraView::UpdateDeviceType(int type) {
winrt::Windows::Devices::Enumeration::Panel newPanelType =
static_cast<winrt::Windows::Devices::Enumeration::Panel>(type);
if (m_panelType == newPanelType && m_isInitialized) {
return;
}
m_panelType = newPanelType;
if (m_isInitialized) {
co_await CleanupMediaCaptureAsync();
}
co_await InitializeAsync();
}
// Request monitor to not turn off if keepAwake is true
void ReactCameraView::UpdateKeepAwake(bool keepAwake) {
if (m_keepAwake != keepAwake) {
m_keepAwake = keepAwake;
if (m_keepAwake) {
if (m_displayRequest == nullptr) {
m_displayRequest = DisplayRequest();
m_displayRequest.RequestActive();
}
} else {
m_displayRequest.RequestRelease();
}
}
}
void ReactCameraView::UpdateFlashMode(int flashMode) {
m_flashMode = flashMode;
if (auto mediaCapture = m_childElement.Source()) {
auto flashControl = mediaCapture.VideoDeviceController().FlashControl();
if (flashControl.Supported()) {
flashControl.Enabled(flashMode == ReactCameraConstants::CameraFlashModeOn);
flashControl.Auto(flashMode == ReactCameraConstants::CameraFlashModeAuto);
}
}
}
void ReactCameraView::UpdateAutoFocus(int focusMode) {
m_focusMode = focusMode;
if (auto mediaCapture = m_childElement.Source()) {
auto focusControl = mediaCapture.VideoDeviceController().FocusControl();
if (focusControl.Supported()) {
auto asyncOp = focusControl.SetPresetAsync(static_cast<winrt::FocusPreset>(focusMode));
}
}
}
void ReactCameraView::UpdateWhiteBalance(int whiteBalance) {
m_whiteBalance = whiteBalance;
if (auto mediaCapture = m_childElement.Source()) {
auto whiteBalanceControl = mediaCapture.VideoDeviceController().WhiteBalanceControl();
if (whiteBalanceControl.Supported()) {
auto asyncOp = whiteBalanceControl.SetPresetAsync(static_cast<winrt::ColorTemperaturePreset>(whiteBalance));
}
}
}
void ReactCameraView::UpdateMirrorVideo(bool mirrorVideo) {
m_mirrorVideo = mirrorVideo;
m_childElement.FlowDirection(mirrorVideo ? winrt::FlowDirection::RightToLeft : winrt::FlowDirection::LeftToRight);
}
void ReactCameraView::UpdateAspect(int aspect) {
switch (aspect) {
case ReactCameraConstants::CameraAspectFill:
m_childElement.Stretch(Stretch::Uniform);
break;
case ReactCameraConstants::CameraAspectFit:
m_childElement.Stretch(Stretch::UniformToFill);
break;
case ReactCameraConstants::CameraAspectStretch:
m_childElement.Stretch(Stretch::Fill);
break;
default:
m_childElement.Stretch(Stretch::None);
break;
}
}
void ReactCameraView::UpdateDefaultVideoQuality(int videoQuality) {
if (m_defaultVideoQuality != videoQuality) {
m_defaultVideoQuality = videoQuality;
auto asyncOp = UpdateMediaStreamPropertiesAsync();
}
}
void ReactCameraView::UpdateBarcodeScannerEnabled(bool barcodeScannerEnabled) {
m_barcodeScannerEnabled = barcodeScannerEnabled;
if (m_barcodeScannerEnabled) {
StartBarcodeScanner();
} else {
StopBarcodeScanner();
}
}
void ReactCameraView::UpdateBarcodeTypes(winrt::JSValueArray const &barcodeTypes) {
m_barcodeTypes.clear();
for (size_t i = 0; i < barcodeTypes.size(); i++) {
auto barCodeTypeStr = barcodeTypes[i].AsString();
if (barCodeTypeStr == "AZTEC") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::AZTEC);
} else if (barCodeTypeStr == "CODABAR") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::CODABAR);
} else if (barCodeTypeStr == "CODE_39") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::CODE_39);
} else if (barCodeTypeStr == "CODE_93") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::CODE_93);
} else if (barCodeTypeStr == "CODE_128") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::CODE_128);
} else if (barCodeTypeStr == "DATA_MATRIX") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::DATA_MATRIX);
} else if (barCodeTypeStr == "EAN_8") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::EAN_8);
} else if (barCodeTypeStr == "EAN_13") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::EAN_13);
} else if (barCodeTypeStr == "ITF") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::ITF);
} else if (barCodeTypeStr == "MAXICODE") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::MAXICODE);
} else if (barCodeTypeStr == "PDF_417") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::PDF_417);
} else if (barCodeTypeStr == "QR_CODE") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::QR_CODE);
} else if (barCodeTypeStr == "RSS_14") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::RSS_14);
} else if (barCodeTypeStr == "RSS_EXPANDED") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::RSS_EXPANDED);
} else if (barCodeTypeStr == "UPC_A") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::UPC_A);
} else if (barCodeTypeStr == "UPC_E") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::UPC_E);
} else if (barCodeTypeStr == "UPC_EAN_EXTENSION") {
m_barcodeTypes.push_back(winrt::ZXing::BarcodeType::UPC_EAN_EXTENSION);
}
}
}
void ReactCameraView::UpdateBarcodeReadIntervalMS(int barcodeReadIntervalMS) {
m_barcodeReadIntervalMS = std::max<int>(ReactCameraConstants::BarcodeReadIntervalMinMS, barcodeReadIntervalMS);
}
// Intialization takes care few things below:
// 1. Register rotation helper to update preview if rotation changes.
// 2. Takes care connected standby scenarios to cleanup and reintialize when suspend/resume
IAsyncAction ReactCameraView::InitializeAsync() {
try {
auto device = co_await FindCameraDeviceAsync();
if (device != nullptr) {
auto settings = winrt::Windows::Media::Capture::MediaCaptureInitializationSettings();
settings.VideoDeviceId(device.Id());
auto mediaCapture = winrt::Windows::Media::Capture::MediaCapture();
co_await mediaCapture.InitializeAsync(settings);
m_availableVideoEncodingProperties =
mediaCapture.VideoDeviceController().GetAvailableMediaStreamProperties(winrt::MediaStreamType::VideoPreview);
m_childElement.Source(mediaCapture);
co_await UpdateMediaStreamPropertiesAsync();
UpdateFlashMode(m_flashMode);
UpdateAutoFocus(m_focusMode);
UpdateWhiteBalance(m_whiteBalance);
UpdateKeepAwake(m_keepAwake);
UpdateMirrorVideo(m_mirrorVideo);
UpdateBarcodeScannerEnabled(m_barcodeScannerEnabled);
co_await mediaCapture.StartPreviewAsync();
m_isPreview = true;
m_rotationHelper = CameraRotationHelper(device.EnclosureLocation());
m_rotationEventToken =
m_rotationHelper.OrientationChanged([ref = get_weak()](const auto &, const bool updatePreview) {
if (auto self = ref.get()) {
self->OnOrientationChanged(updatePreview);
}
});
co_await UpdatePreviewOrientationAsync();
m_applicationSuspendingEventToken =
winrt::Application::Current().Suspending(winrt::auto_revoke, [ref = get_weak()](auto const &, auto const &) {
if (auto self = ref.get()) {
self->OnApplicationSuspending();
}
});
m_applicationResumingEventToken =
winrt::Application::Current().Resuming(winrt::auto_revoke, [ref = get_weak()](auto const &, auto const &) {
if (auto self = ref.get()) {
self->OnApplicationResuming();
}
});
m_isInitialized = true;
}
} catch (winrt::hresult_error const &) {
m_isInitialized = false;
}
}
IAsyncAction ReactCameraView::UpdateMediaStreamPropertiesAsync() {
co_await UpdateMediaStreamPropertiesAsync(m_defaultVideoQuality);
}
IAsyncAction ReactCameraView::UpdateMediaStreamPropertiesAsync(int videoQuality) {
if (auto mediaCapture = m_childElement.Source()) {
winrt::VideoEncodingProperties foundProperties = nullptr;
uint32_t foundResolution = 0;
uint32_t foundFrameRate = 0;
winrt::VideoEncodingProperties bestProperties = nullptr;
uint32_t bestResolution = 0;
uint32_t bestFrameRate = 0;
for (auto mediaEncodingProperties : m_availableVideoEncodingProperties) {
if (auto videoEncodingProperties = mediaEncodingProperties.try_as<winrt::VideoEncodingProperties>()) {
auto resolution = videoEncodingProperties.Width() * videoEncodingProperties.Height();
auto frameRate = videoEncodingProperties.FrameRate().Denominator() > 0
? videoEncodingProperties.FrameRate().Numerator() / videoEncodingProperties.FrameRate().Denominator()
: 0;
// Save the best encoding for later, in case the target cannot be found
if (bestProperties == nullptr || (resolution >= bestResolution && frameRate >= bestFrameRate)) {
bestProperties = videoEncodingProperties;
bestResolution = resolution;
bestFrameRate = frameRate;
}
bool resolutionMatch = (videoQuality == ReactCameraConstants::CameraVideoQuality2160P &&
videoEncodingProperties.Width() == 3840 && videoEncodingProperties.Height() == 2160) ||
(videoQuality == ReactCameraConstants::CameraVideoQuality1080P && videoEncodingProperties.Width() == 1920 &&
videoEncodingProperties.Height() == 1080) ||
(videoQuality == ReactCameraConstants::CameraVideoQuality720P && videoEncodingProperties.Width() == 1280 &&
videoEncodingProperties.Height() == 720) ||
(videoQuality == ReactCameraConstants::CameraVideoQualityWVGA && videoEncodingProperties.Width() == 800 &&
videoEncodingProperties.Height() == 480) ||
(videoQuality == ReactCameraConstants::CameraVideoQualityVGA && videoEncodingProperties.Width() == 640 &&
videoEncodingProperties.Height() == 480);
// Save this encoding if it:
// 1. Has the correct resolution AND
// 2. Has a better framerate
if (resolutionMatch && frameRate >= foundFrameRate) {
foundProperties = videoEncodingProperties;
foundResolution = resolution;
foundFrameRate = frameRate;
}
}
}
// Default to the best encoding if the target could not be found, or if auto was requested
if (foundProperties == nullptr || videoQuality == ReactCameraConstants::CameraVideoQualityAuto) {
foundProperties = bestProperties;
}
if (foundProperties) {
co_await mediaCapture.VideoDeviceController().SetMediaStreamPropertiesAsync(
winrt::MediaStreamType::VideoPreview, foundProperties);
}
}
co_return;
}
IAsyncAction ReactCameraView::CleanupMediaCaptureAsync() {
if (m_isInitialized) {
SetEvent(m_signal.get()); // In case recording is still going on
if (auto mediaCapture = m_childElement.Source()) {
StopBarcodeScanner();
if (m_isPreview) {
co_await mediaCapture.StopPreviewAsync();
m_isPreview = false;
}
if (m_rotationHelper != nullptr) {
m_rotationHelper.OrientationChanged(m_rotationEventToken);
m_rotationHelper = nullptr;
}
m_childElement.Source(nullptr);
}
m_isInitialized = false;
}
}
IAsyncOperation<winrt::DeviceInformation> ReactCameraView::FindCameraDeviceAsync() {
// Get available devices for capturing pictures
auto allVideoDevices = co_await winrt::DeviceInformation::FindAllAsync(winrt::DeviceClass::VideoCapture);
winrt::DeviceInformation targetDevice = nullptr;
// Id specified, search by Id
if (m_cameraId != nullptr && m_cameraId != "") {
for (auto cameraDeviceInfo : allVideoDevices) {
if (cameraDeviceInfo.IsEnabled() && m_cameraId == winrt::to_string(cameraDeviceInfo.Id())) {
// Exact id match
targetDevice = cameraDeviceInfo;
break;
}
}
}
// Target not found by id, search by type
if (targetDevice == nullptr) {
for (auto cameraDeviceInfo : allVideoDevices) {
if (cameraDeviceInfo.IsEnabled()) {
if (cameraDeviceInfo.EnclosureLocation() != nullptr &&
cameraDeviceInfo.EnclosureLocation().Panel() == m_panelType) {
// Device matches the panel requested (front/back/etc), take it
targetDevice = cameraDeviceInfo;
break;
} else if (
cameraDeviceInfo.EnclosureLocation() == nullptr ||
m_panelType == winrt::Windows::Devices::Enumeration::Panel::Unknown) {
// Device has no panel info, save it but keep looking
targetDevice = cameraDeviceInfo;
}
}
}
}
// Target not found by id or by type, take the first enabled camera
if (targetDevice == nullptr) {
for (auto cameraDeviceInfo : allVideoDevices) {
if (cameraDeviceInfo.IsEnabled()) {
targetDevice = cameraDeviceInfo;
break;
}
}
}
// Return whichever device we've found
co_return targetDevice;
}
void ReactCameraView::StartBarcodeScanner() {
if (m_barcodeScannerEnabled && !m_barcodeScanTimer) {
m_barcodeScanTimer = winrt::Windows::System::Threading::ThreadPoolTimer::CreatePeriodicTimer(
[ref = this->get_strong()](const winrt::Windows::System::Threading::ThreadPoolTimer) noexcept {
auto asyncOp = ref->ScanForBarcodeAsync();
asyncOp.wait_for(std::chrono::milliseconds(ReactCameraConstants::BarcodeReadTimeoutMS));
},
std::chrono::milliseconds(m_barcodeReadIntervalMS));
}
}
void ReactCameraView::StopBarcodeScanner() {
if (m_barcodeScanTimer) {
m_barcodeScanTimer.Cancel();
m_barcodeScanTimer = nullptr;
}
}
winrt::Windows::Foundation::IAsyncAction ReactCameraView::ScanForBarcodeAsync() {
if (!m_isInitialized || !m_barcodeScannerEnabled || !m_isPreview) {
co_return;
}
StopBarcodeScanner();
auto dispatcher = Dispatcher();
co_await resume_foreground(dispatcher);
try {
if (auto mediaCapture = m_childElement.Source()) {
// Capture the image
auto lowLagCapture = co_await mediaCapture.PrepareLowLagPhotoCaptureAsync(
winrt::ImageEncodingProperties().CreateUncompressed(winrt::MediaPixelFormat::Bgra8));
auto capturedPhoto = co_await lowLagCapture.CaptureAsync();
auto softwareBitmap = capturedPhoto.Frame().SoftwareBitmap();
co_await lowLagCapture.FinishAsync();
if (softwareBitmap) {
// Try to read barcode
winrt::array_view<winrt::ZXing::BarcodeType const> barcodeTypes{m_barcodeTypes};
auto barcodeReader = barcodeTypes.size() > 0 ? winrt::ZXing::BarcodeReader(true, true, barcodeTypes)
: winrt::ZXing::BarcodeReader(true, true);
auto barcodeResult = barcodeReader.Read(softwareBitmap, 0, 0);
auto control = this->get_strong().try_as<winrt::FrameworkElement>();
if (barcodeResult && m_reactContext && control) {
m_reactContext.DispatchEvent(
control,
BarcodeReadEvent,
[barcodeResult](winrt::Microsoft::ReactNative::IJSValueWriter const &eventDataWriter) noexcept {
auto result = winrt::JSValueObject();
result["data"] = winrt::to_string(barcodeResult.Text());
result["type"] = winrt::to_string(barcodeResult.Format());
result.WriteTo(eventDataWriter);
});
}
}
}
} catch (...) {
// We can't do anything since this is running in it's own thread,
// there's no way to report the exception, and we want the code to cleanup here
}
co_await resume_background();
StartBarcodeScanner();
}
// update preview if display orientation changes.
void ReactCameraView::OnOrientationChanged(const bool updatePreview) {
if (updatePreview) {
UpdatePreviewOrientationAsync();
}
}
void ReactCameraView::OnApplicationSuspending() {
if (m_keepAwake) {
m_displayRequest.RequestRelease();
}
CleanupMediaCaptureAsync();
}
IAsyncAction ReactCameraView::OnUnloaded() {
co_await CleanupMediaCaptureAsync();
}
void ReactCameraView::OnApplicationResuming() {
if (m_keepAwake) {
m_displayRequest.RequestActive();
}
InitializeAsync();
}
// update preview considering current orientation
IAsyncAction ReactCameraView::UpdatePreviewOrientationAsync() {
if (m_isInitialized) {
if (auto mediaCapture = m_childElement.Source()) {
const GUID RotationKey = {0xC380465D, 0x2271, 0x428C, {0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1}};
auto props = mediaCapture.VideoDeviceController().GetMediaStreamProperties(MediaStreamType::VideoPreview);
props.Properties().Insert(RotationKey, winrt::box_value(m_rotationHelper.GetCameraPreviewClockwiseDegrees()));
co_await mediaCapture.SetEncodingPropertiesAsync(MediaStreamType::VideoPreview, props, nullptr);
}
}
}
IAsyncOperation<winrt::hstring> ReactCameraView::GetBase64DataAsync(
winrt::Windows::Storage::Streams::IRandomAccessStream stream) {
auto streamSize = static_cast<uint32_t>(stream.Size());
auto inputStream = stream.GetInputStreamAt(0);
auto dataReader = winrt::Windows::Storage::Streams::DataReader(inputStream);
co_await dataReader.LoadAsync(streamSize);
auto buffer = dataReader.ReadBuffer(streamSize);
co_return winrt::Windows::Security::Cryptography::CryptographicBuffer::EncodeToBase64String(buffer);
}
IAsyncOperation<winrt::StorageFile> ReactCameraView::GetOutputStorageFileAsync(int type, int target) {
std::string ext;
switch (type) {
case ReactCameraConstants::MediaTypeJPG:
ext = ".jpg";
break;
case ReactCameraConstants::MediaTypeMP4:
ext = ".mp4";
break;
case ReactCameraConstants::MediaTypeWMV:
ext = ".wmv";
break;
}
auto now = winrt::clock::now();
auto ttnow = winrt::clock::to_time_t(now);
struct tm time;
_localtime64_s(&time, &ttnow);
wchar_t buf[35];
swprintf_s(
buf,
ARRAYSIZE(buf),
L"%04d%02d%02d_%02d%02d%02d",
1900 + time.tm_year,
1 + time.tm_mon,
time.tm_mday,
time.tm_hour,
time.tm_min,
time.tm_sec);
auto filename = winrt::to_hstring(buf) + winrt::to_hstring(ext);
switch (target) {
case ReactCameraConstants::CameraCaptureTargetMemory:
case ReactCameraConstants::CameraCaptureTargetTemp:
return winrt::ApplicationData::Current().TemporaryFolder().CreateFileAsync(filename);
case ReactCameraConstants::CameraCaptureTargetCameraRoll:
return winrt::KnownFolders::CameraRoll().CreateFileAsync(filename);
case ReactCameraConstants::CameraCaptureTargetDisk:
if (type == ReactCameraConstants::MediaTypeJPG) {
auto result = winrt::KnownFolders::PicturesLibrary().CreateFileAsync(filename);
return result;
} else {
return winrt::KnownFolders::VideosLibrary().CreateFileAsync(filename);
}
}
return nullptr;
}
void ReactCameraView::SetContext(winrt::Microsoft::ReactNative::IReactContext const &reactContext) {
m_reactContext = reactContext;
}
winrt::JSValueObject ReactCameraView::GetExifObject(winrt::BitmapPropertySet const &properties) noexcept {
winrt::JSValueObject exifObject;
for (auto it : properties) {
auto key = winrt::to_string(it.Key());
auto value = it.Value();
switch (value.Type()) {
case PropertyType::Boolean:
exifObject[key] = JSValue(unbox_value<bool>(value.Value()));
break;
case PropertyType::Single:
exifObject[key] = JSValue(unbox_value<float_t>(value.Value()));
break;
case PropertyType::Double:
exifObject[key] = JSValue(unbox_value<double_t>(value.Value()));
break;
case PropertyType::Int16:
exifObject[key] = JSValue(unbox_value<int16_t>(value.Value()));
break;
case PropertyType::Int32:
exifObject[key] = JSValue(unbox_value<int32_t>(value.Value()));
break;
case PropertyType::Int64:
exifObject[key] = JSValue(unbox_value<int64_t>(value.Value()));
break;
case PropertyType::UInt16:
exifObject[key] = JSValue(unbox_value<uint16_t>(value.Value()));
break;
case PropertyType::UInt32:
exifObject[key] = JSValue(unbox_value<uint32_t>(value.Value()));
break;
case PropertyType::UInt64:
exifObject[key] = JSValue(unbox_value<uint64_t>(value.Value()));
break;
case PropertyType::String:
exifObject[key] = JSValue(winrt::to_string(unbox_value<winrt::hstring>(value.Value())));
break;
}
}
return exifObject;
}
bool ReactCameraView::TryGetValueAsInt(
winrt::JSValueObject const &options,
const std::string key,
int &value,
const int defaultValue) noexcept {
auto search = options.find(key);
bool found = search != options.end();
value = found ? options[key].AsInt32() : defaultValue;
return found;
}
bool ReactCameraView::TryGetValueAsBool(
winrt::JSValueObject const &options,
const std::string key,
bool &value,
const bool defaultValue) noexcept {
auto search = options.find(key);
bool found = search != options.end();
value = found ? options[key].AsBoolean() : defaultValue;
return found;
}
bool ReactCameraView::TryGetValueAsFloat(
winrt::JSValueObject const &options,
const std::string key,
float &value,
const float defaultValue) noexcept {
auto search = options.find(key);
bool found = search != options.end();
value = found ? options[key].AsSingle() : defaultValue;
return found;
}
} // namespace winrt::ReactNativeCameraCPP