Skip to content

Commit

Permalink
feat: [iOS] Add mp4 video capture in CameraCaptureUI
Browse files Browse the repository at this point in the history
  • Loading branch information
jeromelaban committed Sep 17, 2023
1 parent 80ef443 commit ca03383
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@
<ios:Button Content="Capture Image" Click="CaptureImage_Click" />
<ios:Button Content="Capture Video" Click="CaptureVideo_Click" />
<Image x:Name="ImageControl" />
<TextBlock x:Name="videoSize"
TextWrapping="WrapWholeWords" />
</StackPanel>
</Page>
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
1 change: 1 addition & 0 deletions src/Uno.UWP/Media/Capture/CameraCaptureUI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public partial class CameraCaptureUI

public CameraCaptureUI()
{
VideoSettings.Format = CameraCaptureUIVideoFormat.Mp4;
}

public global::Windows.Foundation.IAsyncOperation<global::Windows.Storage.StorageFile> CaptureFileAsync(global::Windows.Media.Capture.CameraCaptureUIMode mode)
Expand Down
147 changes: 128 additions & 19 deletions src/Uno.UWP/Media/Capture/CameraCaptureUI.iOS.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AVFoundation;
Expand Down Expand Up @@ -44,6 +45,11 @@ private async Task<StorageFile> 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;
Expand Down Expand Up @@ -75,36 +81,139 @@ private async Task<StorageFile> 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<StorageFile> 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)
Expand Down
16 changes: 4 additions & 12 deletions src/Uno.UWP/Media/Capture/CameraCaptureUIVideoCaptureSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit ca03383

Please sign in to comment.