diff --git a/docs/installation.md b/docs/installation.md index 41abae04a..eee8256cd 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -427,14 +427,21 @@ apply plugin: 'com.google.gms.google-services' # Windows -1. `npm install react-native-camera --save` -2. Link the library as described here: [react-native-windows / LinkingLibrariesWindows.md](https://github.com/microsoft/react-native-windows/blob/master/current/docs/LinkingLibrariesWindows.md) - For the last step of this guide, you have to add the following things to your `MainReactNativeHost.cs`: - -- in the import section at the very top: `using RNCamera;` -- in `protected override List Packages => new List` add a new line with `new RNCameraPackage()` - +## Windows RNW C++/WinRT details +1. `yarn install react-native-camera` +2. Link the library as described below: + windows/myapp.sln + Add the ReactNativeCamera project to your solution. + Open the solution in Visual Studio 2019 + Right-click Solution icon in Solution Explorer > Add > Existing Project Select node_modules\react-native-camera\windows\ReactNativeCameraCPP\ReactNativeCameraCPP.vcxproj + windows/myapp/myapp.vcxproj + Add a reference to ReactNativeCameraCPP to your main application project. From Visual Studio 2019: + Right-click main application project > Add > Reference... Check ReactNativeCameraCPP from Solution Projects. +3. Modify files below to add the Camera package providers to your main application project + pch.h + Add #include "winrt/ReactNativeCameraCPP.h". + app.cpp + Add PackageProviders().Append(winrt::ReactNativeCameraCPP::ReactPackageProvider()); before InitializeComponent(); 3. Add the capabilities (permissions) for the webcam and microphone as described here: [docs.microsoft / audio-video-camera](https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/simple-camera-preview-access#add-capability-declarations-to-the-app-manifest) -4. Use `RCTCamera` (RNCamera is not supported yet) like described above Follow the [Q & A](QA.md) section if you are having compilation issues. diff --git a/windows/RNCamera/RCTCameraModule.cs b/windows/RNCamera/RCTCameraModule.cs index 269bd0bc7..df452da18 100644 --- a/windows/RNCamera/RCTCameraModule.cs +++ b/windows/RNCamera/RCTCameraModule.cs @@ -24,8 +24,6 @@ class RCTCameraModule : ReactContextNativeModuleBase, ILifecycleEventListener public const int CameraAspectFill = 0; public const int CameraAspectFit = 1; public const int CameraAspectStretch = 2; - public const int CameraCaptureModeStill = 0; - public const int CameraCaptureModeVideo = 1; public const int CameraCaptureTargetMemory = 0; public const int CameraCaptureTargetDisk = 1; public const int CameraCaptureTargetCameraRoll = 2; @@ -64,7 +62,7 @@ public override string Name { get { - return "CameraModule"; + return "RNCameraModule"; } } @@ -77,8 +75,7 @@ public override IReadOnlyDictionary Constants { "Aspect", GetAspectConstants() }, { "BarCodeType", GetBarcodeConstants() }, { "Type", GetTypeConstants() }, - { "CaptureQuality", GetCaptureQualityConstants() }, - { "CaptureMode", GetCaptureModeConstants() }, + { "VideoQuality", GetCaptureQualityConstants() }, { "CaptureTarget", GetCaptureTargetConstants() }, { "Orientation", GetOrientationConstants() }, { "FlashMode", GetFlashModeConstants() }, @@ -90,9 +87,26 @@ public override IReadOnlyDictionary Constants public CameraForViewManager CameraManager { get; } = new CameraForViewManager(); [ReactMethod] - public async void capture(JObject options, IPromise promise) + public async void checkMediaCapturePermission(IPromise promise) { - var viewTag = options.Value("view"); + MediaCapture newCapture = new MediaCapture(); + try + { + await newCapture.InitializeAsync(); + } + catch + { + promise.Resolve(false); + return; + } + + promise.Resolve(true); + } + + [ReactMethod] + public async void takePicture(JObject options, int viewTag, IPromise promise) + { + //var viewTag = options.Value("view"); var cameraForView = CameraManager.GetCameraForView(viewTag); if (cameraForView == null) { @@ -100,25 +114,31 @@ public async void capture(JObject options, IPromise promise) return; } - var mode = options.Value("mode"); - if (mode == CameraCaptureModeVideo) - { - if (_recordingTask != null) - { - promise.Reject("Cannot run more than one capture operation."); - return; - } + await CapturePhotoAsync(cameraForView, options, promise).ConfigureAwait(false); + } - _recordingCancellation = new CancellationTokenSource(); - _recordingTask = RecordAsync(cameraForView, options, promise, _recordingCancellation.Token); + [ReactMethod] + public void record(JObject options, int viewTag, IPromise promise) + { + //var viewTag = options.Value("view"); + var cameraForView = CameraManager.GetCameraForView(viewTag); + if (cameraForView == null) + { + promise.Reject("No camera found."); return; } - else + + if (_recordingTask != null) { - await CapturePhotoAsync(cameraForView, options, promise).ConfigureAwait(false); + promise.Reject("Cannot run more than one capture operation."); + return; } + + _recordingCancellation = new CancellationTokenSource(); + _recordingTask = RecordAsync(cameraForView, options, promise, _recordingCancellation.Token); } + [ReactMethod] public async void stopCapture(IPromise promise) { @@ -418,15 +438,6 @@ private static IReadOnlyDictionary GetCaptureQualityConstants() }; } - private static IReadOnlyDictionary GetCaptureModeConstants() - { - return new Dictionary - { - { "still", CameraCaptureModeStill }, - { "video", CameraCaptureModeVideo }, - }; - } - private static IReadOnlyDictionary GetCaptureTargetConstants() { return new Dictionary diff --git a/windows/RNCamera/RCTCameraViewManager.cs b/windows/RNCamera/RCTCameraViewManager.cs index 390fd5c59..6e921db3b 100644 --- a/windows/RNCamera/RCTCameraViewManager.cs +++ b/windows/RNCamera/RCTCameraViewManager.cs @@ -23,7 +23,7 @@ public override string Name { get { - return "RCTCamera"; + return "RNCamera"; } } @@ -68,12 +68,6 @@ public async void SetType(CaptureElement view, int type) await camera.UpdatePanelAsync((Windows.Devices.Enumeration.Panel)type); } - [ReactProp("captureQuality")] - public void SetCaptureQuality(CaptureElement view, int captureQuality) - { - // No reason to handle this props valeu here since it's passed to the `capture` method. - } - [ReactProp("torchMode")] public void SetTorchMode(CaptureElement view, int torchMode) { diff --git a/windows/ReactNativeCameraCPP/CameraRotationHelper.cpp b/windows/ReactNativeCameraCPP/CameraRotationHelper.cpp new file mode 100644 index 000000000..e3242fcad --- /dev/null +++ b/windows/ReactNativeCameraCPP/CameraRotationHelper.cpp @@ -0,0 +1,242 @@ +#include "pch.h" +#include "CameraRotationHelper.h" +#include "CameraRotationHelper.g.cpp" + +namespace winrt { + using namespace Windows::Devices::Enumeration; + using namespace Windows::Devices::Sensors; + using namespace Windows::Storage::FileProperties; + using namespace Windows::Graphics::Display; +} //namespace winrt + +namespace winrt::ReactNativeCameraCPP::implementation +{ + CameraRotationHelper::CameraRotationHelper(EnclosureLocation location) + { + m_cameraEnclosureLocation = location; + + if (!IsEnclosureLocationExternal(m_cameraEnclosureLocation) && m_orientationSensor != nullptr) + { + m_sensorOrientationChanged_revoker = m_orientationSensor.OrientationChanged(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) + { + if (auto self = ref.get()) { + self->SimpleOrientationSensor_OrientationChanged(sender, args); + } + }); + } + m_displayOrientationChanged_revoker = m_displayInformation.OrientationChanged(winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) + { + if (auto self = ref.get()) { + self->DisplayInformation_OrientationChanged(sender, args); + } + }); + } + + PhotoOrientation CameraRotationHelper::GetConvertedCameraCaptureOrientation() + { + auto orientation = GetCameraCaptureOrientation(); + return ConvertSimpleOrientationToPhotoOrientation(orientation); + } + + SimpleOrientation CameraRotationHelper::GetCameraCaptureOrientation() + { + if (IsEnclosureLocationExternal(m_cameraEnclosureLocation)) + { + // Cameras that are not attached to the device do not rotate along with it, so apply no rotation + return SimpleOrientation::NotRotated; + } + + // Get the device orientation offset by the camera hardware offset + auto deviceOrientation = m_orientationSensor ? m_orientationSensor.GetCurrentOrientation() : SimpleOrientation::NotRotated; + auto result = SubtractOrientations(deviceOrientation, GetCameraOrientationRelativeToNativeOrientation()); + + // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted + if (ShouldMirrorPreview()) + { + result = MirrorOrientation(result); + } + return result; + } + + SimpleOrientation CameraRotationHelper::GetCameraPreviewOrientation() + { + if (IsEnclosureLocationExternal(m_cameraEnclosureLocation)) + { + // Cameras that are not attached to the device do not rotate along with it, so apply no rotation + return SimpleOrientation::NotRotated; + } + + // Get the app display rotation offset by the camera hardware offset + auto result = ConvertDisplayOrientationToSimpleOrientation(m_displayInformation.CurrentOrientation()); + result = SubtractOrientations(result, GetCameraOrientationRelativeToNativeOrientation()); + + // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted + if (ShouldMirrorPreview()) + { + result = MirrorOrientation(result); + } + return result; + } + + int CameraRotationHelper::GetConvertedCameraPreviewOrientation() + { + auto rotation = GetCameraPreviewOrientation(); + return ConvertSimpleOrientationToClockwiseDegrees(rotation); + } + + PhotoOrientation CameraRotationHelper::ConvertSimpleOrientationToPhotoOrientation(SimpleOrientation orientation) + { + switch (orientation) + { + case SimpleOrientation::Rotated90DegreesCounterclockwise: + return PhotoOrientation::Rotate90; + case SimpleOrientation::Rotated180DegreesCounterclockwise: + return PhotoOrientation::Rotate180; + case SimpleOrientation::Rotated270DegreesCounterclockwise: + return PhotoOrientation::Rotate270; + case SimpleOrientation::NotRotated: + default: + return PhotoOrientation::Normal; + } + } + + int CameraRotationHelper::ConvertSimpleOrientationToClockwiseDegrees(SimpleOrientation orientation) + { + switch (orientation) + { + case SimpleOrientation::Rotated90DegreesCounterclockwise: + return 270; + case SimpleOrientation::Rotated180DegreesCounterclockwise: + return 180; + case SimpleOrientation::Rotated270DegreesCounterclockwise: + return 90; + case SimpleOrientation::NotRotated: + default: + return 0; + } + } + + SimpleOrientation CameraRotationHelper::ConvertDisplayOrientationToSimpleOrientation(DisplayOrientations orientation) + { + SimpleOrientation result; + switch (orientation) + { + case DisplayOrientations::Landscape: + result = SimpleOrientation::NotRotated; + break; + case DisplayOrientations::PortraitFlipped: + result = SimpleOrientation::Rotated90DegreesCounterclockwise; + break; + case DisplayOrientations::LandscapeFlipped: + result = SimpleOrientation::Rotated180DegreesCounterclockwise; + break; + case DisplayOrientations::Portrait: + default: + result = SimpleOrientation::Rotated270DegreesCounterclockwise; + break; + } + + // Above assumes landscape; offset is needed if native orientation is portrait + if (m_displayInformation.NativeOrientation() == DisplayOrientations::Portrait) + { + result = AddOrientations(result, SimpleOrientation::Rotated90DegreesCounterclockwise); + } + + return result; + } + + SimpleOrientation CameraRotationHelper::SubtractOrientations(SimpleOrientation a, SimpleOrientation b) + { + auto aRot = ConvertSimpleOrientationToClockwiseDegrees(a); + auto bRot = ConvertSimpleOrientationToClockwiseDegrees(b); + // Add 360 to ensure the modulus operator does not operate on a negative + auto result = (360 + (aRot - bRot)) % 360; + return ConvertClockwiseDegreesToSimpleOrientation(result); + } + + SimpleOrientation CameraRotationHelper::MirrorOrientation(SimpleOrientation orientation) + { + // This only affects the 90 and 270 degree cases, because rotating 0 and 180 degrees is the same clockwise and counter-clockwise + switch (orientation) + { + case SimpleOrientation::Rotated90DegreesCounterclockwise: + return SimpleOrientation::Rotated270DegreesCounterclockwise; + case SimpleOrientation::Rotated270DegreesCounterclockwise: + return SimpleOrientation::Rotated90DegreesCounterclockwise; + } + return orientation; + } + + SimpleOrientation CameraRotationHelper::AddOrientations(SimpleOrientation a, SimpleOrientation b) + { + auto aRot = ConvertSimpleOrientationToClockwiseDegrees(a); + auto bRot = ConvertSimpleOrientationToClockwiseDegrees(b); + auto result = (aRot + bRot) % 360; + return ConvertClockwiseDegreesToSimpleOrientation(result); + } + + SimpleOrientation CameraRotationHelper::ConvertClockwiseDegreesToSimpleOrientation(int orientation) + { + switch (orientation) + { + case 270: + return SimpleOrientation::Rotated90DegreesCounterclockwise; + case 180: + return SimpleOrientation::Rotated180DegreesCounterclockwise; + case 90: + return SimpleOrientation::Rotated270DegreesCounterclockwise; + case 0: + default: + return SimpleOrientation::NotRotated; + } + } + + void CameraRotationHelper::SimpleOrientationSensor_OrientationChanged(IInspectable const&, IInspectable const&) + { + if (m_orientationSensor.GetCurrentOrientation() != SimpleOrientation::Faceup && m_orientationSensor.GetCurrentOrientation() != SimpleOrientation::Facedown) + { + // Only raise the OrientationChanged event if the device is not parallel to the ground. This allows users to take pictures of documents (FaceUp) + // or the ceiling (FaceDown) in portrait or landscape, by first holding the device in the desired orientation, and then pointing the camera + // either up or down, at the desired subject. + //Note: This assumes that the camera is either facing the same way as the screen, or the opposite way. For devices with cameras mounted + // on other panels, this logic should be adjusted. + m_orientationChangedEvent(*this, false); + } + } + + void CameraRotationHelper::DisplayInformation_OrientationChanged(IInspectable const&, IInspectable const&) + { + m_orientationChangedEvent(*this, true); + } + + bool CameraRotationHelper::ShouldMirrorPreview() + { + // It is recommended that applications mirror the preview for front-facing cameras, as it gives users a more natural experience, since it behaves more like a mirror + return (m_cameraEnclosureLocation.Panel() == Panel::Front); + } + + SimpleOrientation CameraRotationHelper::GetCameraOrientationRelativeToNativeOrientation() + { + // Get the rotation angle of the camera enclosure as it is mounted in the device hardware + auto enclosureAngle = ConvertClockwiseDegreesToSimpleOrientation(static_cast(m_cameraEnclosureLocation.RotationAngleInDegreesClockwise())); + + // Account for the fact that, on portrait-first devices, the built in camera sensor is read at a 90 degree offset to the native orientation + if (m_displayInformation.NativeOrientation() == DisplayOrientations::Portrait && + !IsEnclosureLocationExternal(m_cameraEnclosureLocation)) + { + enclosureAngle = AddOrientations(SimpleOrientation::Rotated90DegreesCounterclockwise, enclosureAngle); + } + + return enclosureAngle; + } + + winrt::event_token CameraRotationHelper::OrientationChanged(Windows::Foundation::EventHandler const& handler) + { + return m_orientationChangedEvent.add(handler); + } + + void CameraRotationHelper::OrientationChanged(winrt::event_token const& token) noexcept + { + m_orientationChangedEvent.remove(token); + } +} diff --git a/windows/ReactNativeCameraCPP/CameraRotationHelper.h b/windows/ReactNativeCameraCPP/CameraRotationHelper.h new file mode 100644 index 000000000..1aaae2cdb --- /dev/null +++ b/windows/ReactNativeCameraCPP/CameraRotationHelper.h @@ -0,0 +1,61 @@ +#pragma once +#include "winrt/Windows.Graphics.Display.h" +#include "CameraRotationHelper.g.h" + +// This is a Cpp/WinRT implementation of the Camera Rotation Helper class(C#) on MSDN: +// https://docs.microsoft.com/en-us/windows/uwp/audio-video-camera/handle-device-orientation-with-mediacapture#camerarotationhelper-full-code-listing + +namespace winrt::ReactNativeCameraCPP::implementation +{ + struct CameraRotationHelper : CameraRotationHelperT + { + public: + CameraRotationHelper(winrt::Windows::Devices::Enumeration::EnclosureLocation location); + winrt::event_token OrientationChanged(Windows::Foundation::EventHandler const& handler); + void OrientationChanged(winrt::event_token const& token) noexcept; + + winrt::Windows::Devices::Sensors::SimpleOrientation GetCameraCaptureOrientation(); + winrt::Windows::Storage::FileProperties::PhotoOrientation GetConvertedCameraCaptureOrientation(); + winrt::Windows::Devices::Sensors::SimpleOrientation GetCameraPreviewOrientation(); + int GetConvertedCameraPreviewOrientation(); + + private: + + bool IsEnclosureLocationExternal(winrt::Windows::Devices::Enumeration::EnclosureLocation enclosureLocation) + { + return (enclosureLocation == nullptr || enclosureLocation.Panel() == winrt::Windows::Devices::Enumeration::Panel::Unknown); + } + bool ShouldMirrorPreview(); + winrt::Windows::Devices::Sensors::SimpleOrientation GetCameraOrientationRelativeToNativeOrientation(); + + winrt::Windows::Devices::Sensors::SimpleOrientation SubtractOrientations( + winrt::Windows::Devices::Sensors::SimpleOrientation a, + winrt::Windows::Devices::Sensors::SimpleOrientation b); + winrt::Windows::Devices::Sensors::SimpleOrientation MirrorOrientation(winrt::Windows::Devices::Sensors::SimpleOrientation orientation); + winrt::Windows::Devices::Sensors::SimpleOrientation AddOrientations(winrt::Windows::Devices::Sensors::SimpleOrientation a, winrt::Windows::Devices::Sensors::SimpleOrientation b); + + winrt::Windows::Storage::FileProperties::PhotoOrientation ConvertSimpleOrientationToPhotoOrientation( + winrt::Windows::Devices::Sensors::SimpleOrientation orientation); + int ConvertSimpleOrientationToClockwiseDegrees(winrt::Windows::Devices::Sensors::SimpleOrientation orientation); + winrt::Windows::Devices::Sensors::SimpleOrientation ConvertDisplayOrientationToSimpleOrientation(Windows::Graphics::Display::DisplayOrientations orientation); + winrt::Windows::Devices::Sensors::SimpleOrientation ConvertClockwiseDegreesToSimpleOrientation(int orientation); + + void SimpleOrientationSensor_OrientationChanged(IInspectable const& sender, IInspectable const& args); + void DisplayInformation_OrientationChanged(IInspectable const& sender, IInspectable const& args); + + winrt::Windows::Devices::Sensors::SimpleOrientationSensor::OrientationChanged_revoker m_sensorOrientationChanged_revoker{}; + winrt::Windows::Graphics::Display::DisplayInformation::OrientationChanged_revoker m_displayOrientationChanged_revoker{}; + + winrt::Windows::Devices::Enumeration::EnclosureLocation m_cameraEnclosureLocation{nullptr}; + winrt::event> m_orientationChangedEvent; + winrt::Windows::Devices::Sensors::SimpleOrientationSensor m_orientationSensor{ winrt::Windows::Devices::Sensors::SimpleOrientationSensor::GetDefault() }; + winrt::Windows::Graphics::Display::DisplayInformation m_displayInformation{ winrt::Windows::Graphics::Display::DisplayInformation::GetForCurrentView() }; + }; +} + +namespace winrt::ReactNativeCameraCPP::factory_implementation +{ + struct CameraRotationHelper : CameraRotationHelperT + { + }; +} diff --git a/windows/ReactNativeCameraCPP/CameraRotationHelper.idl b/windows/ReactNativeCameraCPP/CameraRotationHelper.idl new file mode 100644 index 000000000..f5db861cc --- /dev/null +++ b/windows/ReactNativeCameraCPP/CameraRotationHelper.idl @@ -0,0 +1,14 @@ +namespace ReactNativeCameraCPP +{ + [default_interface] + runtimeclass CameraRotationHelper + { + CameraRotationHelper(Windows.Devices.Enumeration.EnclosureLocation location); + Windows.Devices.Sensors.SimpleOrientation GetCameraCaptureOrientation(); + Windows.Devices.Sensors.SimpleOrientation GetCameraPreviewOrientation(); + Windows.Storage.FileProperties.PhotoOrientation GetConvertedCameraCaptureOrientation(); + Int32 GetConvertedCameraPreviewOrientation(); + + event Windows.Foundation.EventHandler OrientationChanged; + } +} diff --git a/windows/ReactNativeCameraCPP/PropertySheet.props b/windows/ReactNativeCameraCPP/PropertySheet.props new file mode 100644 index 000000000..3e15bb903 --- /dev/null +++ b/windows/ReactNativeCameraCPP/PropertySheet.props @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/windows/ReactNativeCameraCPP/ReactCameraConstants.h b/windows/ReactNativeCameraCPP/ReactCameraConstants.h new file mode 100644 index 000000000..f764f05f8 --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactCameraConstants.h @@ -0,0 +1,140 @@ +#pragma once + +#include + +namespace winrt::ReactNativeCameraCPP +{ + static const int CameraAspectFill = 0; + static const int CameraAspectFit = 1; + static const int CameraAspectStretch = 2; + static const int CameraCaptureTargetMemory = 0; + static const int CameraCaptureTargetDisk = 1; + static const int CameraCaptureTargetCameraRoll = 2; + static const int CameraCaptureTargetTemp = 3; + static const int CameraOrientationAuto = UINT_MAX; + static const int CameraOrientationPortrait = (int)winrt::Windows::Devices::Sensors::SimpleOrientation::NotRotated; + static const int CameraOrientationPortraitUpsideDown = (int)winrt::Windows::Devices::Sensors::SimpleOrientation::Rotated180DegreesCounterclockwise; + static const int CameraOrientationLandscapeLeft = (int)winrt::Windows::Devices::Sensors::SimpleOrientation::Rotated90DegreesCounterclockwise; + static const int CameraOrientationLandscapeRight = (int)winrt::Windows::Devices::Sensors::SimpleOrientation::Rotated270DegreesCounterclockwise; + static const int CameraTypeFront = (int)winrt::Windows::Devices::Enumeration::Panel::Front; + static const int CameraTypeBack = (int)winrt::Windows::Devices::Enumeration::Panel::Back; + static const int CameraFlashModeOff = 0; + static const int CameraFlashModeOn = 1; + static const int CameraFlashModeAuto = 2; + static const int CameraTorchModeOff = 0; + static const int CameraTorchModeOn = 1; + static const int CameraTorchModeAuto = 2; + static const int CameraAutoFocusOff = 0; + static const int CameraAutoFocusOn = 1; + static const int CameraWhiteBalanceAuto = 0; + static const int CameraWhiteBalanceSunny = 1; + static const int CameraWhiteBalanceCloudy = 2; + static const int CameraWhiteBalanceShadow = 3; + static const int CameraWhiteBalanceIncandescent = 4; + static const int CameraWhiteBalanceFluorescent = 5; + static const int CameraCaptureQualityHigh = (int)winrt::Windows::Media::MediaProperties::VideoEncodingQuality::HD1080p; + static const int CameraCaptureQualityLow = (int)winrt::Windows::Media::MediaProperties::VideoEncodingQuality::HD720p; + static const int CameraCaptureQuality1080p = (int)winrt::Windows::Media::MediaProperties::VideoEncodingQuality::HD1080p; + static const int CameraCaptureQuality720p = (int)winrt::Windows::Media::MediaProperties::VideoEncodingQuality::HD720p; + static const int MediaTypeImage = 1; + static const int MediaTypeVideo = 2; + +#pragma warning( push ) +#pragma warning( disable : 4505 ) + static std::map GetAspectConstants() noexcept { + return std::map + { + { L"stretch", CameraAspectStretch }, + { L"fit", CameraAspectFit }, + { L"fill", CameraAspectFill } + }; + } + + static std::map GetBarcodeConstants() noexcept { + return std::map + { + { L"UPC_E", L"32768" }, + { L"CODE_39", L"4" }, + }; + } + + static std::map GetAutoFocusConstants() noexcept { + return std::map + { + { L"off", CameraAutoFocusOff }, + { L"on", CameraAutoFocusOn }, + }; + } + + static std::map GetWhiteBalanceConstants() noexcept { + return std::map + { + { L"auto", CameraWhiteBalanceAuto }, + { L"sunny", CameraWhiteBalanceSunny }, + { L"cloudy", CameraWhiteBalanceCloudy }, + { L"shadow", CameraWhiteBalanceShadow }, + { L"incandescent", CameraWhiteBalanceIncandescent }, + { L"fluorescent", CameraWhiteBalanceFluorescent }, + }; + } + + static std::map GetTypeConstants() noexcept { + return std::map + { + { L"front", CameraTypeFront }, + { L"back", CameraTypeBack }, + }; + } + + static std::map GetCaptureQualityConstants() noexcept { + return std::map + { + { L"low", CameraCaptureQualityLow }, + { L"high", CameraCaptureQualityHigh }, + { L"720p", CameraCaptureQuality720p }, + { L"1080p", CameraCaptureQuality1080p }, + }; + } + + static std::map GetCaptureTargetConstants() noexcept { + return std::map + { + { L"memory", CameraCaptureTargetMemory }, + { L"disk", CameraCaptureTargetDisk }, + { L"cameraRoll", CameraCaptureTargetCameraRoll }, + { L"temp", CameraCaptureTargetTemp }, + }; + } + + static std::map GetOrientationConstants() noexcept { + return std::map + { + { L"auto", CameraOrientationAuto }, + { L"landscapeLeft", CameraOrientationLandscapeLeft }, + { L"landscapeRight", CameraOrientationLandscapeRight }, + { L"portrait", CameraOrientationPortrait }, + { L"portraitUpsideDown", CameraOrientationPortraitUpsideDown }, + }; + } + + static std::map GetFlashModeConstants() noexcept { + return std::map + { + { L"off", CameraFlashModeOff }, + { L"on", CameraFlashModeOn }, + { L"auto", CameraOrientationAuto }, + }; + } + + static std::map GetTorchModeConstants() noexcept { + return std::map + { + { L"off", CameraTorchModeOff }, + { L"on", CameraTorchModeOn }, + { L"auto", CameraTorchModeAuto }, + }; + } + +#pragma warning( pop ) + +}; \ No newline at end of file diff --git a/windows/ReactNativeCameraCPP/ReactCameraModule.h b/windows/ReactNativeCameraCPP/ReactCameraModule.h new file mode 100644 index 000000000..2418ec503 --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactCameraModule.h @@ -0,0 +1,100 @@ +#pragma once + +#include "pch.h" + +#include + +#include "NativeModules.h" +#include +#include +#include +#include +#include + +#include "JSValueTreeWriter.h" +#include "ReactCameraConstants.h" +#include "ReactCameraViewManager.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt { + using namespace Windows::UI::Xaml; + using namespace Windows::UI::Xaml::Media; + using namespace Windows::Media::MediaProperties; + using namespace Windows::UI::Xaml::Controls; + using namespace Windows::Foundation; + using namespace winrt::Windows::UI::Core; + using namespace winrt::Windows::Storage::Streams; +} //namespace winrt +namespace winrt::ReactNativeCameraCPP { + REACT_MODULE(RNCameraModule); + struct RNCameraModule { + const std::string Name = "RNCameraModule"; + +#pragma region Constants + REACT_CONSTANT_PROVIDER(ConstantProvider) + void ConstantProvider(ReactConstantProvider& provider) noexcept { + provider.Add(L"Aspect", GetAspectConstants()); + provider.Add(L"BarCodeType", GetBarcodeConstants()); + provider.Add(L"AutoFocus", GetAutoFocusConstants()); + provider.Add(L"WhiteBalance", GetWhiteBalanceConstants()); + provider.Add(L"Type", GetTypeConstants()); + provider.Add(L"VideoQuality", GetCaptureQualityConstants()); + provider.Add(L"CaptureTarget", GetCaptureTargetConstants()); + provider.Add(L"Orientation", GetOrientationConstants()); + provider.Add(L"FlashMode", GetFlashModeConstants()); + provider.Add(L"TorchMode", GetTorchModeConstants()); + } + + REACT_METHOD(record) + void record( + std::map&& options, + int viewTag, + winrt::Microsoft::ReactNative::ReactPromise&& result) noexcept + { + try { + winrt::ReactNativeCameraCPP::implementation::ReactCameraViewManager::RecordAsync( + options, + viewTag, result).get(); //block on IAsyncAction + } + catch (winrt::hresult_error const& ex) + { + result.Reject(ex.message().c_str()); + } + } + + REACT_METHOD(takePicture) + void takePicture( + std::map&& options, + int viewTag, + winrt::Microsoft::ReactNative::ReactPromise&& result) noexcept + { + try { + winrt::ReactNativeCameraCPP::implementation::ReactCameraViewManager::TakePictureAsync( + options, + viewTag, result).get(); //block on IAsyncAction + } + catch (winrt::hresult_error const& ex) + { + result.Reject(ex.message().c_str()); + } + } + + REACT_METHOD(checkMediaCapturePermission) + void checkMediaCapturePermission(winrt::Microsoft::ReactNative::ReactPromise&& result) noexcept + { + winrt::ReactNativeCameraCPP::implementation::ReactCameraViewManager::CheckMediaCapturePermissionAsync(result).get(); //block on IAsyncAction + } + +#pragma endregion + + public: + RNCameraModule() { + } + + ~RNCameraModule() { + } + + }; + +} // namespace ReactNativeCameraCPP diff --git a/windows/ReactNativeCameraCPP/ReactCameraView.cpp b/windows/ReactNativeCameraCPP/ReactCameraView.cpp new file mode 100644 index 000000000..6deccb515 --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactCameraView.cpp @@ -0,0 +1,537 @@ +#include "pch.h" +#include "ReactCameraView.h" +#include "ReactCameraViewManager.h" +#include "NativeModules.h" +#include "ReactCameraConstants.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) + { + 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 == "torchMode") { + UpdateTorchMode(static_cast(propertyValue.Double())); + } + if (propertyName == "flashMode") { + UpdateFlashMode(static_cast(propertyValue.Double())); + } + else if (propertyName == "type") { + UpdateDeviceType(static_cast(propertyValue.Double())); + } + else if (propertyName == "keepAwake") { + UpdateKeepAwake(propertyValue.Boolean()); + } + else if (propertyName == "aspect") { + UpdateAspect(static_cast(propertyValue.Double())); + } + } + } + } + + IAsyncAction ReactCameraView::UpdateFilePropertiesAsync(StorageFile storageFile, std::map const& options) + { + auto props = co_await storageFile.Properties().GetImagePropertiesAsync(); + auto searchTitle = options.find(L"title"); + if (searchTitle != options.end()) { + const auto& titleValue = options.at(L"title"); + if (titleValue.Type() == JSValueType::String) + { + auto titleString = titleValue.String(); + props.Title(winrt::to_hstring(titleString)); + } + co_await props.SavePropertiesAsync(); + } + } + + // RNW has a bug where the numeric value is set as int in debug but double in release + // ToDo: remove this function once bug https://github.com/microsoft/react-native-windows/issues/4225 is fixed. + bool ReactCameraView::GetNumericValue(std::map const& options, const std::wstring key, int &value) + { + bool found = false; + auto search = options.find(key); + if (search != options.end()) + { + const auto& searchValue = options.at(key); + const bool valueIsInt = (searchValue.Type() == JSValueType::Int64); + const bool valueIsDouble = (searchValue.Type() == JSValueType::Double); + if (valueIsInt || valueIsDouble) + { + found = true; + if (valueIsInt) + { + value = static_cast(searchValue.Int64()); + } + else + { + value = static_cast(searchValue.Double()); + } + } + } + + return found; + } + + IAsyncAction ReactCameraView::TakePictureAsync(std::map const& options + , ReactPromise& result) + { + if (!m_isInitialized) + { + result.Reject(L"Media device is not initialized!"); + return; + } + + auto dispatcher = Dispatcher(); + co_await resume_foreground(dispatcher); // Jump to UI thread + if (auto mediaCapture = m_childElement.Source()) + { + auto encoding = winrt::ImageEncodingProperties().CreateJpeg(); + auto randomStream = winrt::InMemoryRandomAccessStream(); + co_await mediaCapture.CapturePhotoToStreamAsync(encoding, randomStream); + int target; + if (!GetNumericValue(options, L"target", target)) + { + result.Reject(L"target parameter not specified!"); + return; + } + if (target == CameraCaptureTargetMemory) + { + // In memeory returns a base64 string for the captured image + auto string = co_await GetBase64DataAsync(randomStream); + JSValueObject jsObject; + jsObject["data"] = winrt::to_string(string); + result.Resolve(jsObject); + } + else + { + auto storageFile = co_await GetOutputStorageFileAsync(MediaTypeImage, target); + { + auto photoOrientation = m_rotationHelper.GetConvertedCameraCaptureOrientation(); + auto decoder = co_await winrt::BitmapDecoder::CreateAsync(randomStream); + auto outputStream = co_await storageFile.OpenAsync(FileAccessMode::ReadWrite); + auto encoder = co_await winrt::BitmapEncoder::CreateForTranscodingAsync(outputStream, decoder); + auto bitmapTypedValue = winrt::BitmapTypedValue(winrt::box_value(photoOrientation), PropertyType::UInt16); + auto properties = winrt::BitmapPropertySet(); + properties.Insert(hstring(L"System.Photo.Orientation"), bitmapTypedValue); + co_await encoder.BitmapProperties().SetPropertiesAsync(properties); + co_await encoder.FlushAsync(); + } + + co_await UpdateFilePropertiesAsync(storageFile, options); + JSValueObject jsObject; + jsObject["path"] = winrt::to_string(storageFile.Path()); + result.Resolve(jsObject); + } + } + else + { + result.Reject(L"Media device is not initialized!"); + } + + co_await resume_background(); + } + + IAsyncAction ReactCameraView::RecordAsync(std::map const& options, ReactPromise& result) + { + if (!m_isInitialized) + { + result.Reject(L"Media device is not initialized!"); + return; + } + + auto dispatcher = Dispatcher(); + co_await resume_foreground(dispatcher); // Jump to UI thread + if (auto mediaCapture = m_childElement.Source()) + { + int quality = static_cast(VideoEncodingQuality::Auto); + GetNumericValue(options, L"quality", quality); + auto encodingProfile = winrt::MediaEncodingProfile(); + auto encoding = encodingProfile.CreateMp4(static_cast(quality)); + + auto searchAudio = options.find(L"audio"); + if (searchAudio != options.end()) + { + const auto& audioValue = options.at(L"audio"); + mediaCapture.AudioDeviceController().Muted(static_cast(audioValue.Boolean())); + } + + int totalSeconds = INT_MAX; + GetNumericValue(options, L"totalSeconds", totalSeconds); + + int target; + if (!GetNumericValue(options, L"target", target)) + { + result.Reject(L"target parameter not specified!"); + return; + } + if (target == CameraCaptureTargetMemory) + { + auto randomStream = winrt::InMemoryRandomAccessStream(); + m_mediaRecording = co_await mediaCapture.PrepareLowLagRecordToStreamAsync( + encoding, randomStream); + co_await m_mediaRecording.StartAsync(); + co_await DelayStopRecording(totalSeconds); + co_await WaitAndStopRecording(); + + auto string = co_await GetBase64DataAsync(randomStream); + JSValueObject jsObject; + jsObject["data"] = winrt::to_string(string); + result.Resolve(jsObject); + } + else + { + auto storageFile = co_await GetOutputStorageFileAsync(MediaTypeVideo, target); + m_mediaRecording = co_await mediaCapture.PrepareLowLagRecordToStorageFileAsync( + encoding, storageFile); + co_await m_mediaRecording.StartAsync(); + co_await DelayStopRecording(totalSeconds); + co_await WaitAndStopRecording(); + co_await UpdateFilePropertiesAsync(storageFile, options); + + JSValueObject jsObject; + jsObject["path"] = winrt::to_string(storageFile.Path()); + result.Resolve(jsObject); + } + } + else + { + result.Reject("No media capture device found!"); + } + co_await resume_background(); + } + + // async function to wait for specified seconds before signaling to stop recording + IAsyncAction ReactCameraView::DelayStopRecording(int totalRecordingInSecs) + { + ResetEvent(m_signal.get()); + std::chrono::duration secs(totalRecordingInSecs); + co_await secs; + SetEvent(m_signal.get()); + } + + IAsyncAction ReactCameraView::WaitAndStopRecording() + { + co_await resume_on_signal(m_signal.get()); + auto dispatcher = Dispatcher(); + co_await resume_foreground(dispatcher); + co_await m_mediaRecording.StopAsync(); + co_await resume_background(); + } + + // 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::UpdateTorchMode(int torchMode) + { + m_torchMode = torchMode; + if (auto mediaCapture = m_childElement.Source()) + { + auto torchControl = mediaCapture.VideoDeviceController().TorchControl(); + if (torchControl.Supported()) + { + torchControl.Enabled(torchMode == CameraTorchModeOn); + } + } + } + + 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 == CameraFlashModeOn); + flashControl.Auto(flashMode == CameraFlashModeAuto); + } + } + } + + void ReactCameraView::UpdateAspect(int aspect) + { + switch (aspect) + { + case CameraAspectFill: + m_childElement.Stretch(Stretch::Uniform); + break; + case CameraAspectFit: + m_childElement.Stretch(Stretch::UniformToFill); + break; + case CameraAspectStretch: + m_childElement.Stretch(Stretch::Fill); + break; + default: + m_childElement.Stretch(Stretch::None); + break; + } + } + + // 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 FindCameraDeviceByPanelAsync(); + 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_childElement.Source(mediaCapture); + UpdateTorchMode(m_torchMode); + UpdateFlashMode(m_flashMode); + UpdateKeepAwake(m_keepAwake); + co_await mediaCapture.StartPreviewAsync(); + 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::CleanupMediaCaptureAsync() + { + if (m_isInitialized) + { + SetEvent(m_signal.get()); // In case recording is still going on + if (auto mediaCapture = m_childElement.Source()) + { + co_await mediaCapture.StopPreviewAsync(); + if (m_rotationHelper != nullptr) + { + m_rotationHelper.OrientationChanged(m_rotationEventToken); + m_rotationHelper = nullptr; + } + m_childElement.Source(nullptr); + } + m_isInitialized = false; + } + } + + IAsyncOperation ReactCameraView::FindCameraDeviceByPanelAsync() + { + // Get available devices for capturing pictures + auto allVideoDevices = co_await winrt::DeviceInformation::FindAllAsync(winrt::DeviceClass::VideoCapture); + for (auto cameraDeviceInfo : allVideoDevices) + { + if (cameraDeviceInfo.EnclosureLocation() != nullptr && cameraDeviceInfo.EnclosureLocation().Panel() == m_panelType) + { + co_return cameraDeviceInfo; + } + } + // Nothing matched, just return the first + if (allVideoDevices.Size() > 0) + { + co_return allVideoDevices.GetAt(0); + } + // We didn't find any devices, so return a null instance + co_return nullptr; + } + // 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.GetConvertedCameraPreviewOrientation())); + 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) + { + auto ext = type == MediaTypeImage ? ".jpg" : ".mp4"; + 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 CameraCaptureTargetMemory: + case CameraCaptureTargetTemp: + return winrt::ApplicationData::Current().TemporaryFolder().CreateFileAsync(filename); + case CameraCaptureTargetCameraRoll: + return winrt::KnownFolders::CameraRoll().CreateFileAsync(filename); + case CameraCaptureTargetDisk: + if (type == MediaTypeImage) + { + return winrt::KnownFolders::PicturesLibrary().CreateFileAsync(filename); + } + else + { + return winrt::KnownFolders::VideosLibrary().CreateFileAsync(filename); + } + } + return nullptr; + } + + void ReactCameraView::SetContext(winrt::Microsoft::ReactNative::IReactContext const& reactContext) + { + m_reactContext = reactContext; + } + +} // namespace winrt::ReactNativeVideoCPP diff --git a/windows/ReactNativeCameraCPP/ReactCameraView.h b/windows/ReactNativeCameraCPP/ReactCameraView.h new file mode 100644 index 000000000..1c867d9d4 --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactCameraView.h @@ -0,0 +1,68 @@ +#pragma once +#include +#include "NativeModules.h" +#include "JSValueTreeWriter.h" +#include "CameraRotationHelper.h" +#include "ReactCameraConstants.h" + +namespace winrt::ReactNativeCameraCPP { + struct ReactCameraView : winrt::Windows::UI::Xaml::Controls::GridT { + public: + ReactCameraView() = default; + ~ReactCameraView(); + void SetContext(winrt::Microsoft::ReactNative::IReactContext const &reactContext); + void Initialize(); + void UpdateProperties(winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader); + + winrt::Windows::Foundation::IAsyncAction TakePictureAsync(std::map const& options, + winrt::Microsoft::ReactNative::ReactPromise &result); + winrt::Windows::Foundation::IAsyncAction RecordAsync(std::map const& options, winrt::Microsoft::ReactNative::ReactPromise& result); + + public: + static winrt::com_ptr Create(); + + private: + void UpdateKeepAwake(bool keepAwake); + void UpdateTorchMode(int torchMode); + void UpdateFlashMode(int flashMode); + void UpdateAspect(int aspect); + fire_and_forget UpdateDeviceType(int type); + + winrt::Windows::Foundation::IAsyncAction InitializeAsync(); + winrt::Windows::Foundation::IAsyncAction CleanupMediaCaptureAsync(); + winrt::Windows::Foundation::IAsyncOperation FindCameraDeviceByPanelAsync(); + winrt::Windows::Foundation::IAsyncOperation GetBase64DataAsync(winrt::Windows::Storage::Streams::IRandomAccessStream stream); + winrt::Windows::Foundation::IAsyncOperation GetOutputStorageFileAsync(int type, int target); + winrt::Windows::Foundation::IAsyncAction DelayStopRecording(int totalRecordingInSecs); + winrt::Windows::Foundation::IAsyncAction WaitAndStopRecording(); + winrt::Windows::Foundation::IAsyncAction UpdatePreviewOrientationAsync(); + winrt::Windows::Foundation::IAsyncAction UpdateFilePropertiesAsync(winrt::Windows::Storage::StorageFile storageFile, + std::map const& options); + + void OnOrientationChanged(const bool updatePreview); + void OnApplicationSuspending(); + void OnApplicationResuming(); + winrt::Windows::Foundation::IAsyncAction OnUnloaded(); + + bool GetNumericValue(std::map const& options, const std::wstring key, int& value); + + winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr }; + winrt::Windows::UI::Xaml::Controls::CaptureElement m_childElement; + + handle m_signal{ CreateEvent(nullptr, true, false, nullptr) }; + winrt::Windows::Media::Capture::LowLagMediaRecording m_mediaRecording{ nullptr}; + winrt::ReactNativeCameraCPP::CameraRotationHelper m_rotationHelper{nullptr}; + winrt::Windows::System::Display::DisplayRequest m_displayRequest{ nullptr }; + + winrt::event_token m_rotationEventToken{}; + winrt::Windows::UI::Xaml::Application::Suspending_revoker m_applicationSuspendingEventToken; + winrt::Windows::UI::Xaml::Application::Resuming_revoker m_applicationResumingEventToken; + winrt::Windows::UI::Xaml::FrameworkElement::Unloaded_revoker m_unloadedEventToken; + + bool m_isInitialized{ false }; + bool m_keepAwake{ false }; + int m_torchMode{ CameraTorchModeOff }; + int m_flashMode{ CameraFlashModeOff }; + winrt::Windows::Devices::Enumeration::Panel m_panelType{ winrt::Windows::Devices::Enumeration::Panel::Unknown }; + }; +} // namespace winrt::ReactNativeVideoCPP diff --git a/windows/ReactNativeCameraCPP/ReactCameraViewManager.cpp b/windows/ReactNativeCameraCPP/ReactCameraViewManager.cpp new file mode 100644 index 000000000..3d58bcf89 --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactCameraViewManager.cpp @@ -0,0 +1,156 @@ +#include "pch.h" +#include "ReactCameraViewManager.h" +#include "ReactCameraConstants.h" + +#include + +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 Microsoft::ReactNative; +} //namespace winrt + + +namespace winrt::ReactNativeCameraCPP::implementation { + // static vector to contain all currently active camera view instances + // This is a temporary workaround to map the view tag to view instance, since + // Method call from ReactCameraModule constains view tag as parameter and we need + // to forward the call to CameraView instance. + std::vector> ReactCameraViewManager::m_cameraViewInstances; + + ReactCameraViewManager::ReactCameraViewManager() {} + + // IViewManager + hstring ReactCameraViewManager::Name() noexcept { + return L"RNCamera"; + } + + FrameworkElement ReactCameraViewManager::CreateView() noexcept { + auto const& view = ReactNativeCameraCPP::ReactCameraView::Create(); + view->SetContext(m_reactContext); + m_cameraViewInstances.emplace_back(view); + + return view.as(); + } + + // IViewManagerWithReactContext + IReactContext ReactCameraViewManager::ReactContext() noexcept { + return m_reactContext; + } + + void ReactCameraViewManager::ReactContext(IReactContext reactContext) noexcept { + m_reactContext = reactContext; + } + + // IViewManagerWithNativeProperties + IMapView ReactCameraViewManager::NativeProps() noexcept { + auto nativeProps = winrt::single_threaded_map(); + + nativeProps.Insert(L"aspect", ViewManagerPropertyType::Number); + nativeProps.Insert(L"type", ViewManagerPropertyType::Number); + nativeProps.Insert(L"autoFocus", ViewManagerPropertyType::Boolean); + nativeProps.Insert(L"whiteBalance", ViewManagerPropertyType::Number); + nativeProps.Insert(L"torchMode", ViewManagerPropertyType::Number); + nativeProps.Insert(L"flashMode", ViewManagerPropertyType::Number); + // nativeProps.Insert(L"barcodeScannerEnabled", ViewManagerPropertyType::Boolean); + // nativeProps.Insert(L"barCodeTypes", ViewManagerPropertyType::Array); + nativeProps.Insert(L"keepAwake", ViewManagerPropertyType::Boolean); + + return nativeProps.GetView(); + } + + void ReactCameraViewManager::UpdateProperties( + FrameworkElement const& view, + IJSValueReader const& propertyMapReader) noexcept { + if (auto reactCameraView = view.try_as()) { + reactCameraView->UpdateProperties(propertyMapReader); + } + } + + void ReactCameraViewManager::RemoveViewFromList(winrt::com_ptr view) + { + auto it = std::find(m_cameraViewInstances.begin(), m_cameraViewInstances.end(), view); + if (it != m_cameraViewInstances.end()) + m_cameraViewInstances.erase(it); + } + + IAsyncAction ReactCameraViewManager::TakePictureAsync( + std::map const& options, + int viewTag, + ReactPromise& result) + { + auto index = co_await FindCamera(viewTag); + if (index != -1) + { + auto cameraView = m_cameraViewInstances.at(index); + co_await cameraView->TakePictureAsync(options, result); + } + else + { + result.Reject("No camera instance found!"); + } + } + + IAsyncAction ReactCameraViewManager::RecordAsync( + std::map const& options, + int viewTag, + ReactPromise& result) + { + auto index = co_await FindCamera(viewTag); + if (index != -1) + { + auto cameraView = m_cameraViewInstances.at(index); + co_await cameraView->RecordAsync(options, result); + } + else + { + result.Reject("No camera instance found!"); + } + } + + // Intialize MediaCapture and will bring up the constent dialog if this + // is the first time. RNC will display a progress indicator while waiting + // for user click. This method will resolve as no permission if user does + // not grant the permission or App does not have the appx capabilities for + // Microphone and WebCam specified in its Package.Appxmanifest. + IAsyncAction ReactCameraViewManager::CheckMediaCapturePermissionAsync( + winrt::Microsoft::ReactNative::ReactPromise& result) + { + auto mediaCapture = winrt::Windows::Media::Capture::MediaCapture(); + bool hasPermission = true; + try { + co_await mediaCapture.InitializeAsync(); + } + catch (winrt::hresult_error const&) { + hasPermission = false; + } + result.Resolve(hasPermission); + } + + IAsyncOperation ReactCameraViewManager::FindCamera(int viewTag) + { + int index = 0; + for (const auto& cameraView : m_cameraViewInstances) + { + auto element = cameraView.as(); + auto dispatcher = element.Dispatcher(); + co_await resume_foreground(dispatcher); + int currentTag = static_cast(element.GetValue(winrt::FrameworkElement::TagProperty()).as().GetInt64()); + if (currentTag == viewTag) + { + co_return index; + } + co_await resume_background(); + index++; + } + co_return -1; + } + +} // namespace winrt::ReactNativeCameraCPP::implementation diff --git a/windows/ReactNativeCameraCPP/ReactCameraViewManager.h b/windows/ReactNativeCameraCPP/ReactCameraViewManager.h new file mode 100644 index 000000000..13f461b6d --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactCameraViewManager.h @@ -0,0 +1,55 @@ +#pragma once +#include "pch.h" + +namespace winrt::ReactNativeCameraCPP::implementation { + + struct ReactCameraViewManager + : winrt::implements< + ReactCameraViewManager, + winrt::Microsoft::ReactNative::IViewManager, + winrt::Microsoft::ReactNative::IViewManagerWithReactContext, + winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties> { + public: + ReactCameraViewManager(); + + // IViewManager + winrt::hstring Name() noexcept; + winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept; + + // IViewManagerWithReactContext + winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept; + void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept; + + // IViewManagerWithNativeProperties + winrt::Windows::Foundation::Collections:: + IMapView + NativeProps() noexcept; + + void UpdateProperties( + winrt::Windows::UI::Xaml::FrameworkElement const &view, + winrt::Microsoft::ReactNative::IJSValueReader const &propertyMapReader) noexcept; + + static winrt::Windows::Foundation::IAsyncAction TakePictureAsync( + std::map const& options, + int viewTag, + winrt::Microsoft::ReactNative::ReactPromise &result); + + static winrt::Windows::Foundation::IAsyncAction RecordAsync( + std::map const& options, + int viewTag, + winrt::Microsoft::ReactNative::ReactPromise &result); + + static winrt::Windows::Foundation::IAsyncAction CheckMediaCapturePermissionAsync( + winrt::Microsoft::ReactNative::ReactPromise& result); + + static void RemoveViewFromList(winrt::com_ptr); + + private: + + static winrt::Windows::Foundation::IAsyncOperation FindCamera(int viewTag); + + winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr }; + static std::vector> m_cameraViewInstances; + }; + +} // namespace winrt::ReactNativeCameraCPP::implementation diff --git a/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.def b/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.def new file mode 100644 index 000000000..8c1a02932 --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.def @@ -0,0 +1,3 @@ +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.vcxproj b/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.vcxproj new file mode 100644 index 000000000..4affa4b8f --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.vcxproj @@ -0,0 +1,163 @@ + + + + + true + true + true + {7432c343-cc07-4bc7-9bd0-8c467be0f018} + ReactNativeCameraCPP + ReactNativeCameraCPP + en-US + 14.0 + true + Windows Store + 10.0 + 10.0.18362.0 + 10.0.15063.0 + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + DynamicLibrary + v140 + v141 + v142 + Unicode + false + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) + 28204 + _WINRT_DLL;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + + + Console + true + ReactNativeCameraCPP.def + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + NDEBUG;%(PreprocessorDefinitions) + + + + + + + + + ReactPackageProvider.idl + + + CameraRotationHelper.idl + + + + + + + + Create + + + ReactPackageProvider.idl + + + CameraRotationHelper.idl + + + + + + + + + + + + + + + + + + + {f7d32bd0-2749-483e-9a0d-1635ef7e3136} + false + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.vcxproj.filters b/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.vcxproj.filters new file mode 100644 index 000000000..0f97461ff --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactNativeCameraCPP.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + accd3aa8-1ba0-4223-9bbe-0c431709210b + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + {926ab91d-31b5-48c3-b9a4-e681349f27f0} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/windows/ReactNativeCameraCPP/ReactPackageProvider.cpp b/windows/ReactNativeCameraCPP/ReactPackageProvider.cpp new file mode 100644 index 000000000..f6397fdbe --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactPackageProvider.cpp @@ -0,0 +1,21 @@ +#include "pch.h" +#include "ReactPackageProvider.h" +#if __has_include("ReactPackageProvider.g.cpp") +#include "ReactPackageProvider.g.cpp" +#endif + +#include "ReactCameraViewManager.h" +#include "ReactCameraModule.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::ReactNativeCameraCPP::implementation { + + void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept { + AddAttributedModules(packageBuilder); + packageBuilder.AddViewManager(L"ReactCameraViewManager", []() { + return winrt::make(); + }); + } + +} // namespace winrt::ReactNativeCameraCPP::implementation diff --git a/windows/ReactNativeCameraCPP/ReactPackageProvider.h b/windows/ReactNativeCameraCPP/ReactPackageProvider.h new file mode 100644 index 000000000..215a90e9b --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactPackageProvider.h @@ -0,0 +1,20 @@ +#pragma once +#include "ReactPackageProvider.g.h" + +using namespace winrt::Microsoft::ReactNative; + +namespace winrt::ReactNativeCameraCPP::implementation { + + struct ReactPackageProvider : ReactPackageProviderT { + ReactPackageProvider() = default; + + void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept; + }; + +} // namespace winrt::ReactNativeCameraCPP::implementation + +namespace winrt::ReactNativeCameraCPP::factory_implementation { + + struct ReactPackageProvider : ReactPackageProviderT {}; + +} // namespace winrt::ReactNativeCameraCPP::factory_implementation diff --git a/windows/ReactNativeCameraCPP/ReactPackageProvider.idl b/windows/ReactNativeCameraCPP/ReactPackageProvider.idl new file mode 100644 index 000000000..7ba53915d --- /dev/null +++ b/windows/ReactNativeCameraCPP/ReactPackageProvider.idl @@ -0,0 +1,5 @@ +namespace ReactNativeCameraCPP { +[webhosthidden][default_interface] runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider { + ReactPackageProvider(); +}; +} // namespace ReactNativeCameraCPP diff --git a/windows/ReactNativeCameraCPP/packages.config b/windows/ReactNativeCameraCPP/packages.config new file mode 100644 index 000000000..9a69657f6 --- /dev/null +++ b/windows/ReactNativeCameraCPP/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/windows/ReactNativeCameraCPP/pch.cpp b/windows/ReactNativeCameraCPP/pch.cpp new file mode 100644 index 000000000..e0d2ef1a2 --- /dev/null +++ b/windows/ReactNativeCameraCPP/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/windows/ReactNativeCameraCPP/pch.h b/windows/ReactNativeCameraCPP/pch.h new file mode 100644 index 000000000..af0f28afb --- /dev/null +++ b/windows/ReactNativeCameraCPP/pch.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "winrt/Microsoft.ReactNative.h" +#include "JSValueTreeWriter.h" +#include "ReactCameraView.h"