#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::Create() { auto view = winrt::make_self(); 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 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( (static_cast(targetWidth) / static_cast(softwareBitmap.PixelWidth())) * static_cast(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 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(quality)); mediaType = ReactCameraConstants::MediaTypeMP4; break; case ReactCameraConstants::CameraVideoCodecWMV: encodingProfile = winrt::MediaEncodingProfile::CreateWmv(static_cast(quality)); mediaType = ReactCameraConstants::MediaTypeWMV; break; default: videoCodec = ReactCameraConstants::CameraVideoCodecH264; encodingProfile = winrt::MediaEncodingProfile::CreateMp4(static_cast(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 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(1000 * totalRecordingInSecs); std::chrono::duration 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(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(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(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(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()) { 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 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 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(); 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 ReactCameraView::GetBase64DataAsync( winrt::Windows::Storage::Streams::IRandomAccessStream stream) { auto streamSize = static_cast(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 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(value.Value())); break; case PropertyType::Single: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::Double: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::Int16: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::Int32: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::Int64: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::UInt16: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::UInt32: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::UInt64: exifObject[key] = JSValue(unbox_value(value.Value())); break; case PropertyType::String: exifObject[key] = JSValue(winrt::to_string(unbox_value(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