using Newtonsoft.Json.Linq; using ReactNative.Bridge; using System; using System.Collections.Generic; using System.IO; using System.Threading; using System.Threading.Tasks; using Windows.Devices.Enumeration; using Windows.Devices.Sensors; using Windows.Foundation; using Windows.Graphics.Imaging; using Windows.Media.Capture; using Windows.Media.MediaProperties; using Windows.Storage; using Windows.Storage.FileProperties; using Windows.Storage.Streams; using ZXing; using static System.FormattableString; namespace RNCamera { class RCTCameraModule : ReactContextNativeModuleBase, ILifecycleEventListener { public const int CameraAspectFill = 0; public const int CameraAspectFit = 1; public const int CameraAspectStretch = 2; public const int CameraCaptureTargetMemory = 0; public const int CameraCaptureTargetDisk = 1; public const int CameraCaptureTargetCameraRoll = 2; public const int CameraCaptureTargetTemp = 3; public const int CameraOrientationAuto = int.MaxValue; public const int CameraOrientationPortrait = (int)SimpleOrientation.NotRotated; public const int CameraOrientationPortraitUpsideDown = (int)SimpleOrientation.Rotated180DegreesCounterclockwise; public const int CameraOrientationLandscapeLeft = (int)SimpleOrientation.Rotated90DegreesCounterclockwise; public const int CameraOrientationLandscapeRight = (int)SimpleOrientation.Rotated270DegreesCounterclockwise; public const int CameraTypeFront = (int)Panel.Front; public const int CameraTypeBack = (int)Panel.Back; public const int CameraFlashModeOff = 0; public const int CameraFlashModeOn = 1; public const int CameraFlashModeAuto = 2; public const int CameraTorchModeOff = 0; public const int CameraTorchModeOn = 1; public const int CameraTorchModeAuto = 2; public const int CameraCaptureQualityHigh = (int)VideoEncodingQuality.HD1080p; public const int CameraCaptureQualityLow = (int)VideoEncodingQuality.HD720p; public const int CameraCaptureQuality1080p = (int)VideoEncodingQuality.HD1080p; public const int CameraCaptureQuality720p = (int)VideoEncodingQuality.HD720p; public const int MediaTypeImage = 1; public const int MediaTypeVideo = 2; private static readonly Guid RotationKey = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1"); private Task _recordingTask; private CancellationTokenSource _recordingCancellation; public RCTCameraModule(ReactContext reactContext) : base(reactContext) { } public override string Name { get { return "RNCameraModule"; } } public override IReadOnlyDictionary Constants { get { return new Dictionary { { "Aspect", GetAspectConstants() }, { "BarCodeType", GetBarcodeConstants() }, { "Type", GetTypeConstants() }, { "VideoQuality", GetCaptureQualityConstants() }, { "CaptureTarget", GetCaptureTargetConstants() }, { "Orientation", GetOrientationConstants() }, { "FlashMode", GetFlashModeConstants() }, { "TorchMode", GetTorchModeConstants() }, }; } } public CameraForViewManager CameraManager { get; } = new CameraForViewManager(); [ReactMethod] public async void checkMediaCapturePermission(IPromise promise) { 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) { promise.Reject("No camera found."); return; } await CapturePhotoAsync(cameraForView, options, promise).ConfigureAwait(false); } [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; } if (_recordingTask != null) { 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) { var recordingTask = _recordingTask; if (recordingTask != null) { _recordingCancellation.Cancel(); await recordingTask; promise.Resolve("Finished recording."); } else { promise.Resolve("Not recording."); } } [ReactMethod] public void hasFlash(JObject options, IPromise promise) { var viewTag = options.Value("view"); var mediaCapture = CameraManager.GetCameraForView(viewTag).MediaCapture; if (mediaCapture == null) { promise.Reject("No camera found."); return; } var isSupported = mediaCapture.VideoDeviceController.FlashControl.Supported; promise.Resolve(isSupported); } public void OnSuspend() { var recordingTask = _recordingTask; if (recordingTask != null) { _recordingCancellation.Cancel(); recordingTask.Wait(); } } public void OnResume() { } public void OnDestroy() { } private async Task CapturePhotoAsync(CameraForView cameraForView, JObject options, IPromise promise) { var mediaCapture = cameraForView.MediaCapture; var encoding = ImageEncodingProperties.CreateJpeg(); var target = options.Value("target"); using (var stream = new InMemoryRandomAccessStream()) { await mediaCapture.CapturePhotoToStreamAsync(encoding, stream).AsTask().ConfigureAwait(false); if (target == CameraCaptureTargetMemory) { var data = await GetBase64DataAsync(stream).ConfigureAwait(false); promise.Resolve(new JObject { { "data", data }, }); } else { var storageFile = await GetOutputStorageFileAsync(MediaTypeImage, target).AsTask().ConfigureAwait(false); var orientation = await cameraForView.GetCameraCaptureOrientationAsync().ConfigureAwait(false); var photoOrientation = CameraRotationHelper.ConvertSimpleOrientationToPhotoOrientation(orientation); await ReencodeAndSavePhotoAsync(stream, storageFile, photoOrientation).ConfigureAwait(false); ; await UpdateImagePropertiesAsync(storageFile, options).ConfigureAwait(false); promise.Resolve(new JObject { { "path", storageFile.Path }, }); } } } private async Task RecordAsync(CameraForView cameraForView, JObject options, IPromise promise, CancellationToken token) { var mediaCapture = cameraForView.MediaCapture; var taskCompletionSource = new TaskCompletionSource(); using (var cancellationTokenSource = new CancellationTokenSource()) using (token.Register(cancellationTokenSource.Cancel)) using (cancellationTokenSource.Token.Register(async () => await StopRecordingAsync(mediaCapture, taskCompletionSource))) { var quality = (VideoEncodingQuality)options.Value("quality"); var encodingProfile = MediaEncodingProfile.CreateMp4(quality); mediaCapture.AudioDeviceController.Muted = options.Value("audio"); var orientation = await cameraForView.GetCameraCaptureOrientationAsync().ConfigureAwait(false); encodingProfile.Video.Properties.Add(RotationKey, CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(orientation)); var stream = default(InMemoryRandomAccessStream); try { var target = options.Value("target"); var outputFile = default(StorageFile); if (target == CameraCaptureTargetMemory) { stream = new InMemoryRandomAccessStream(); await mediaCapture.StartRecordToStreamAsync(encodingProfile, stream).AsTask().ConfigureAwait(false); } else { outputFile = await GetOutputStorageFileAsync(MediaTypeVideo, target).AsTask().ConfigureAwait(false); await mediaCapture.StartRecordToStorageFileAsync(encodingProfile, outputFile).AsTask().ConfigureAwait(false); } if (options.ContainsKey("totalSeconds")) { var totalSeconds = options.Value("totalSeconds"); if (totalSeconds > 0) { cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(totalSeconds)); } } await taskCompletionSource.Task.ConfigureAwait(false); if (target == CameraCaptureTargetMemory) { var data = await GetBase64DataAsync(stream).ConfigureAwait(false); promise.Resolve(new JObject { { "data", data }, }); } else { await UpdateVideoPropertiesAsync(outputFile, options).ConfigureAwait(false); promise.Resolve(new JObject { { "path", outputFile.Path }, }); } } finally { stream?.Dispose(); } } _recordingCancellation.Dispose(); _recordingTask = null; } private async Task StopRecordingAsync(MediaCapture mediaCapture, TaskCompletionSource taskCompletionSource) { await mediaCapture.StopRecordAsync().AsTask().ConfigureAwait(false); taskCompletionSource.SetResult(true); } private static async Task GetBase64DataAsync(IRandomAccessStream stream) { var size = (int)stream.Size; var data = new byte[size]; using (var readableStream = stream.AsStreamForRead()) { await readableStream.ReadAsync(data, 0, size).ConfigureAwait(false); return Convert.ToBase64String(data); } } private static async Task UpdateVideoPropertiesAsync(StorageFile storageFile, JObject options) { var props = await storageFile.Properties.GetVideoPropertiesAsync().AsTask().ConfigureAwait(false); if (options.ContainsKey("title")) { props.Title = options.Value("title"); } if (options.ContainsKey("latitude")) { await props.SavePropertiesAsync(GeolocationHelper.GetLatitudeProperties(options.Value("latitude"))).AsTask().ConfigureAwait(false); } if (options.ContainsKey("longitude")) { await props.SavePropertiesAsync(GeolocationHelper.GetLongitudeProperties(options.Value("longitude"))).AsTask().ConfigureAwait(false); } } private static async Task UpdateImagePropertiesAsync(StorageFile storageFile, JObject options) { var props = await storageFile.Properties.GetImagePropertiesAsync().AsTask().ConfigureAwait(false); if (options.ContainsKey("title")) { props.Title = options.Value("title"); } if (options.ContainsKey("latitude")) { await props.SavePropertiesAsync(GeolocationHelper.GetLatitudeProperties(options.Value("latitude"))).AsTask().ConfigureAwait(false); } if (options.ContainsKey("longitude")) { await props.SavePropertiesAsync(GeolocationHelper.GetLongitudeProperties(options.Value("longitude"))).AsTask().ConfigureAwait(false); } } private static IAsyncOperation GetOutputStorageFileAsync(int type, int target) { var ext = type == MediaTypeImage ? ".jpg" : ".mp4"; var filename = DateTimeOffset.Now.ToString("yyyyMMdd_HHmmss") + ext; switch (target) { case CameraCaptureTargetMemory: case CameraCaptureTargetTemp: return ApplicationData.Current.TemporaryFolder.CreateFileAsync(filename); case CameraCaptureTargetCameraRoll: return KnownFolders.CameraRoll.CreateFileAsync(filename); case CameraCaptureTargetDisk: if (type == MediaTypeImage) { return KnownFolders.PicturesLibrary.CreateFileAsync(filename); } else { return KnownFolders.VideosLibrary.CreateFileAsync(filename); } default: throw new InvalidOperationException( Invariant($"Unknown capture target '{target}'.")); } } private static async Task ReencodeAndSavePhotoAsync(IRandomAccessStream stream, StorageFile file, PhotoOrientation photoOrientation) { using (var inputStream = stream) { var decoder = await BitmapDecoder.CreateAsync(inputStream).AsTask().ConfigureAwait(false); using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite).AsTask().ConfigureAwait(false)) { var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder).AsTask().ConfigureAwait(false); var properties = new BitmapPropertySet { { "System.Photo.Orientation", new BitmapTypedValue(photoOrientation, PropertyType.UInt16) } }; await encoder.BitmapProperties.SetPropertiesAsync(properties).AsTask().ConfigureAwait(false); await encoder.FlushAsync().AsTask().ConfigureAwait(false); } } } private static IReadOnlyDictionary GetAspectConstants() { return new Dictionary { { "stretch", CameraAspectStretch }, { "fit", CameraAspectFit }, { "fill", CameraAspectFill }, }; } private static IReadOnlyDictionary GetBarcodeConstants() { // TODO: code39mod43, itf14, etc. return new Dictionary { { BarcodeFormat.UPC_E.GetName(), BarcodeFormat.UPC_E }, { BarcodeFormat.CODE_39.GetName(), BarcodeFormat.CODE_39 }, { BarcodeFormat.EAN_13.GetName(), BarcodeFormat.EAN_13 }, { BarcodeFormat.EAN_8.GetName(), BarcodeFormat.EAN_8 }, { BarcodeFormat.CODE_93.GetName(), BarcodeFormat.CODE_93 }, { BarcodeFormat.CODE_128.GetName(), BarcodeFormat.CODE_128 }, { BarcodeFormat.PDF_417.GetName(), BarcodeFormat.PDF_417 }, { BarcodeFormat.QR_CODE.GetName(), BarcodeFormat.QR_CODE }, { BarcodeFormat.AZTEC.GetName(), BarcodeFormat.AZTEC }, { BarcodeFormat.ITF.GetName(), BarcodeFormat.ITF }, { BarcodeFormat.DATA_MATRIX.GetName(), BarcodeFormat.DATA_MATRIX }, }; } private static IReadOnlyDictionary GetTypeConstants() { return new Dictionary { { "front", CameraTypeFront }, { "back", CameraTypeBack }, }; } private static IReadOnlyDictionary GetCaptureQualityConstants() { return new Dictionary { { "low", CameraCaptureQualityLow }, { "high", CameraCaptureQualityHigh }, { "720p", CameraCaptureQuality720p }, { "1080p", CameraCaptureQuality1080p }, }; } private static IReadOnlyDictionary GetCaptureTargetConstants() { return new Dictionary { { "memory", CameraCaptureTargetMemory }, { "disk", CameraCaptureTargetDisk }, { "cameraRoll", CameraCaptureTargetCameraRoll }, { "temp", CameraCaptureTargetTemp }, }; } private static IReadOnlyDictionary GetOrientationConstants() { return new Dictionary { { "auto", CameraOrientationAuto }, { "landscapeLeft", CameraOrientationLandscapeLeft }, { "landscapeRight", CameraOrientationLandscapeRight }, { "portrait", CameraOrientationPortrait }, { "portraitUpsideDown", CameraOrientationPortraitUpsideDown }, }; } private static IReadOnlyDictionary GetFlashModeConstants() { return new Dictionary { { "off", CameraFlashModeOff }, { "on", CameraFlashModeOn }, { "auto", CameraOrientationAuto }, }; } private static IReadOnlyDictionary GetTorchModeConstants() { return new Dictionary { { "off", CameraTorchModeOff }, { "on", CameraTorchModeOn }, { "auto", CameraTorchModeAuto }, }; } } }