diff --git a/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml b/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml index 26dcc44e00c1..83ffbe072cc6 100644 --- a/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml +++ b/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml @@ -16,5 +16,7 @@ + diff --git a/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml.cs b/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml.cs index c8036447b7b9..8cf42b25fdaa 100644 --- a/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml.cs +++ b/src/SamplesApp/UITests.Shared/Windows_Media/CameraCaptureUISample.xaml.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using System.IO; using Uno.UI.Samples.Controls; using Windows.Media.Capture; using Windows.UI.Xaml; @@ -47,7 +48,16 @@ private async void CaptureVideo_Click(object sender, RoutedEventArgs e) { var captureUI = new CameraCaptureUI(); - _ = await captureUI.CaptureFileAsync(CameraCaptureUIMode.Video); + var result = await captureUI.CaptureFileAsync(CameraCaptureUIMode.Video); + + if (result != null) + { + videoSize.Text = $"Captured file: {result.Path}, Size: {new FileInfo(result?.Path!).Length}"; + } + else + { + videoSize.Text = "Nothing was selected"; + } } #endif } diff --git a/src/Uno.UWP/Generated/3.0.0.0/Windows.Media.Capture/CameraCaptureUIVideoCaptureSettings.cs b/src/Uno.UWP/Generated/3.0.0.0/Windows.Media.Capture/CameraCaptureUIVideoCaptureSettings.cs index 62f68de31ede..2669e8bda3ed 100644 --- a/src/Uno.UWP/Generated/3.0.0.0/Windows.Media.Capture/CameraCaptureUIVideoCaptureSettings.cs +++ b/src/Uno.UWP/Generated/3.0.0.0/Windows.Media.Capture/CameraCaptureUIVideoCaptureSettings.cs @@ -36,7 +36,7 @@ public float MaxDurationInSeconds } } #endif -#if false || false || false || false || __SKIA__ || __NETSTD_REFERENCE__ || __MACOS__ +#if false [global::Uno.NotImplemented("__SKIA__", "__NETSTD_REFERENCE__", "__MACOS__")] public global::Windows.Media.Capture.CameraCaptureUIVideoFormat Format { diff --git a/src/Uno.UWP/Media/Capture/CameraCaptureUI.cs b/src/Uno.UWP/Media/Capture/CameraCaptureUI.cs index b2f8e51a93a8..c58ce1329df3 100644 --- a/src/Uno.UWP/Media/Capture/CameraCaptureUI.cs +++ b/src/Uno.UWP/Media/Capture/CameraCaptureUI.cs @@ -21,6 +21,7 @@ public partial class CameraCaptureUI public CameraCaptureUI() { + VideoSettings.Format = CameraCaptureUIVideoFormat.Mp4; } public global::Windows.Foundation.IAsyncOperation CaptureFileAsync(global::Windows.Media.Capture.CameraCaptureUIMode mode) diff --git a/src/Uno.UWP/Media/Capture/CameraCaptureUI.iOS.cs b/src/Uno.UWP/Media/Capture/CameraCaptureUI.iOS.cs index ee2a38ba7430..4c9357dac1ea 100644 --- a/src/Uno.UWP/Media/Capture/CameraCaptureUI.iOS.cs +++ b/src/Uno.UWP/Media/Capture/CameraCaptureUI.iOS.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using AVFoundation; @@ -44,6 +45,11 @@ private async Task CaptureFile(CancellationToken ct, CameraCaptureU #pragma warning restore CA1416 picker.CameraCaptureMode = UIImagePickerControllerCameraCaptureMode.Video; + if (VideoSettings.Format != CameraCaptureUIVideoFormat.Mp4) + { + throw new NotSupportedException("The capture format CameraCaptureUIVideoFormat.Mp4 is the only supported format"); + } + await ValidateCameraAccess(); await ValidateMicrophoneAccess(); break; @@ -75,36 +81,139 @@ private async Task CaptureFile(CancellationToken ct, CameraCaptureU if (result != null) { - var image = result.ValueForKey(new NSString("UIImagePickerControllerOriginalImage")) as UIImage; - var metadata = result.ValueForKey(new NSString("UIImagePickerControllerOriginalImage")) as UIImage; - - var correctedImage = FixOrientation(image); + if (result.ValueForKey(new NSString("UIImagePickerControllerOriginalImage")) is UIImage image) + { + var correctedImage = FixOrientation(image); - (Stream data, string extension) GetImageStream() + (Stream data, string extension) GetImageStream() + { + return PhotoSettings.Format switch + { + CameraCaptureUIPhotoFormat.Jpeg => (image.AsJPEG().AsStream(), ".jpg"), + CameraCaptureUIPhotoFormat.Png => (image.AsPNG().AsStream(), ".png"), + _ => throw new NotSupportedException($"{PhotoSettings.Format} is not supported"), + }; + }; + + var (data, extension) = GetImageStream(); + return await CreateTempImage(data, extension); + } + else { - switch (PhotoSettings.Format) + var assetUrl = result[UIImagePickerController.MediaURL] as NSUrl; + PHAsset phAsset = null; + + if (this.Log().IsEnabled(LogLevel.Debug)) { - case CameraCaptureUIPhotoFormat.Jpeg: - return (image.AsJPEG().AsStream(), ".jpg"); + this.Log().Debug($"Asset url {assetUrl}"); + } - case CameraCaptureUIPhotoFormat.Png: - return (image.AsPNG().AsStream(), ".png"); + if (assetUrl is not null) + { + if (OperatingSystem.IsIOSVersionAtLeast(11, 0)) + { + if (!assetUrl.Scheme.Equals("assets-library", StringComparison.OrdinalIgnoreCase)) + { + var doc = new UIDocument(assetUrl); + var fullPath = doc.FileUrl?.Path; + + if (fullPath is null) + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().LogWarning($"Unable determine file path from asset library"); + } + + return null; + } + else + { + return await ConvertToMp4(fullPath); + } + } + + phAsset = result.ValueForKey(UIImagePickerController.PHAsset) as PHAsset; + } + } - default: - throw new NotSupportedException($"{PhotoSettings.Format} is not supported"); + if (phAsset == null) + { +#if !__MACCATALYST__ + assetUrl = result[UIImagePickerController.ReferenceUrl] as NSUrl; + + if (this.Log().IsEnabled(LogLevel.Debug)) + { + this.Log().Debug($"Asset url {assetUrl}"); + } + + if (assetUrl != null) + { + phAsset = PHAsset.FetchAssets(new NSUrl[] { assetUrl }, null)?.LastObject as PHAsset; + } +#endif } - }; - var (data, extension) = GetImageStream(); - return await CreateTempImage(data, extension); - } - else - { - return null; + if (phAsset is not null) + { + var originalFilename = PHAssetResource.GetAssetResources(phAsset).FirstOrDefault()?.OriginalFilename; + + if (originalFilename is null) + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().LogWarning($"Unable determine Asset Resources from PHAssetResource"); + } + } + else + { + return await ConvertToMp4(originalFilename); + } + } + else + { + if (this.Log().IsEnabled(LogLevel.Warning)) + { + this.Log().LogWarning($"Could not determine asset url"); + } + } + } } + + return null; } } + private async Task ConvertToMp4(string originalFilename) + { + if (originalFilename == null) + { + return null; + } + + var outputFilePath = Path.Combine(ApplicationData.Current.TemporaryFolder.Path, Guid.NewGuid() + ".mp4"); + + if (this.Log().IsEnabled(LogLevel.Debug)) + { + this.Log().Debug($"Converting {originalFilename} to {outputFilePath}"); + } + + var asset = AVAsset.FromUrl(NSUrl.FromFilename(originalFilename)); + + AVAssetExportSession export = new(asset, AVAssetExportSession.PresetPassthrough); + + export.OutputUrl = NSUrl.FromFilename(outputFilePath); + export.OutputFileType = AVFileTypesExtensions.GetConstant(AVFileTypes.Mpeg4); + export.ShouldOptimizeForNetworkUse = true; + + await export.ExportTaskAsync(); + + if (this.Log().IsEnabled(LogLevel.Debug)) + { + this.Log().Debug($"Done converting to {outputFilePath}"); + } + + return await StorageFile.GetFileFromPathAsync(outputFilePath); + } // As of iOS 10, usage description keys are required for many more permissions private static bool IsUsageKeyDefined(string usageKey) diff --git a/src/Uno.UWP/Media/Capture/CameraCaptureUIVideoCaptureSettings.cs b/src/Uno.UWP/Media/Capture/CameraCaptureUIVideoCaptureSettings.cs index 4b43717b93be..3e51c647b8f8 100644 --- a/src/Uno.UWP/Media/Capture/CameraCaptureUIVideoCaptureSettings.cs +++ b/src/Uno.UWP/Media/Capture/CameraCaptureUIVideoCaptureSettings.cs @@ -32,20 +32,12 @@ public float MaxDurationInSeconds } } #endif -#if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ - [global::Uno.NotImplemented] - public global::Windows.Media.Capture.CameraCaptureUIVideoFormat Format + + public CameraCaptureUIVideoFormat Format { - get - { - throw new global::System.NotImplementedException("The member CameraCaptureUIVideoFormat CameraCaptureUIVideoCaptureSettings.Format is not implemented in Uno."); - } - set - { - global::Windows.Foundation.Metadata.ApiInformation.TryRaiseNotImplemented("Windows.Media.Capture.CameraCaptureUIVideoCaptureSettings", "CameraCaptureUIVideoFormat CameraCaptureUIVideoCaptureSettings.Format"); - } + get; set; } -#endif + #if __ANDROID__ || __IOS__ || IS_UNIT_TESTS || __WASM__ [global::Uno.NotImplemented] public bool AllowTrimming