diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md
index 4f4f160cf85a..59b704cad517 100644
--- a/packages/camera/CHANGELOG.md
+++ b/packages/camera/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 0.4.4
+
+* Added rotation metadata to iOS recorded videos.
+* **Breaking change**. The `aspectRatio` parameter now returns width/height instead of height/width as aspect ratio is always width:height.
+* **Breaking change**. Due to platform specific handling of Texture objects, the `CameraPreview` now use an `AspectRatio` and `RotatedBox` widget internally to display the preview with the correct ratio and rotation. Users should not wrap `CameraPreview` in a `AspectRatio` anymore.
+
## 0.4.3+2
* Bump the minimum Flutter version to 1.2.0.
diff --git a/packages/camera/example/ios/Runner/Info.plist b/packages/camera/example/ios/Runner/Info.plist
index f389a129e028..bc69da2fb686 100644
--- a/packages/camera/example/ios/Runner/Info.plist
+++ b/packages/camera/example/ios/Runner/Info.plist
@@ -41,6 +41,7 @@
UIInterfaceOrientationPortrait
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
+ UIInterfaceOrientationPortraitUpsideDown
UISupportedInterfaceOrientations~ipad
diff --git a/packages/camera/example/lib/main.dart b/packages/camera/example/lib/main.dart
index ca01a7ac0f57..f8ef8bdd8bd5 100644
--- a/packages/camera/example/lib/main.dart
+++ b/packages/camera/example/lib/main.dart
@@ -94,10 +94,7 @@ class _CameraExampleHomeState extends State {
),
);
} else {
- return AspectRatio(
- aspectRatio: controller.value.aspectRatio,
- child: CameraPreview(controller),
- );
+ return CameraPreview(controller);
}
}
diff --git a/packages/camera/ios/Classes/CameraPlugin.m b/packages/camera/ios/Classes/CameraPlugin.m
index 7d5d28d3a3a7..74e4cad0e3f4 100644
--- a/packages/camera/ios/Classes/CameraPlugin.m
+++ b/packages/camera/ios/Classes/CameraPlugin.m
@@ -96,6 +96,7 @@ - (UIImageOrientation)getImageRotation {
float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y,
_motionManager.accelerometerData.acceleration.x)) *
180 / M_PI;
+
if (isNearValue(-90.0, yxAtan)) {
return UIImageOrientationRight;
} else if (isNearValueABS(180.0, yxAtan)) {
@@ -184,14 +185,15 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
@{(NSString *)kCVPixelBufferPixelFormatTypeKey : @(videoFormat)};
[_captureVideoOutput setAlwaysDiscardsLateVideoFrames:YES];
[_captureVideoOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
-
AVCaptureConnection *connection =
[AVCaptureConnection connectionWithInputPorts:_captureVideoInput.ports
output:_captureVideoOutput];
+
if ([_captureDevice position] == AVCaptureDevicePositionFront) {
connection.videoMirrored = YES;
}
- connection.videoOrientation = AVCaptureVideoOrientationPortrait;
+ connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;
+
[_captureSession addInputWithNoConnections:_captureVideoInput];
[_captureSession addOutputWithNoConnections:_captureVideoOutput];
[_captureSession addConnection:connection];
@@ -536,21 +538,28 @@ - (BOOL)setupWriterForPath:(NSString *)path {
[self setUpCaptureSessionForAudio];
}
_videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL
- fileType:AVFileTypeQuickTimeMovie
+ fileType:AVFileTypeMPEG4
error:&error];
NSParameterAssert(_videoWriter);
if (error) {
_eventSink(@{@"event" : @"error", @"errorDescription" : error.description});
return NO;
}
+
NSDictionary *videoSettings = [NSDictionary
dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,
- [NSNumber numberWithInt:_previewSize.height], AVVideoWidthKey,
- [NSNumber numberWithInt:_previewSize.width], AVVideoHeightKey,
+ [NSNumber numberWithInt:_previewSize.width], AVVideoWidthKey,
+ [NSNumber numberWithInt:_previewSize.height], AVVideoHeightKey,
nil];
+
_videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings];
+
NSParameterAssert(_videoWriterInput);
+ // Add orientation metadata.
+ CGFloat rotationDegrees = [self getDeviceRotation];
+ _videoWriterInput.transform = CGAffineTransformMakeRotation(rotationDegrees * M_PI / 180);
+
_videoWriterInput.expectsMediaDataInRealTime = YES;
// Add the audio input
@@ -568,6 +577,7 @@ - (BOOL)setupWriterForPath:(NSString *)path {
_audioWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeAudio
outputSettings:audioOutputSettings];
_audioWriterInput.expectsMediaDataInRealTime = YES;
+
[_videoWriter addInput:_videoWriterInput];
[_videoWriter addInput:_audioWriterInput];
[_captureVideoOutput setSampleBufferDelegate:self queue:_dispatchQueue];
@@ -603,6 +613,32 @@ - (void)setUpCaptureSessionForAudio {
}
}
}
+
+- (float)getDeviceRotation {
+ float const threshold = 45.0;
+ BOOL (^isNearValue)(float value1, float value2) = ^BOOL(float value1, float value2) {
+ return fabsf(value1 - value2) < threshold;
+ };
+ BOOL (^isNearValueABS)(float value1, float value2) = ^BOOL(float value1, float value2) {
+ return isNearValue(fabsf(value1), fabsf(value2));
+ };
+ float yxAtan = (atan2(_motionManager.accelerometerData.acceleration.y,
+ _motionManager.accelerometerData.acceleration.x)) *
+ 180 / M_PI;
+ if (isNearValue(-90.0, yxAtan)) {
+ return 90;
+ } else if (isNearValueABS(180.0, yxAtan)) {
+ return 0;
+ } else if (isNearValueABS(0.0, yxAtan)) {
+ return 180;
+ } else if (isNearValue(90.0, yxAtan)) {
+ return 270;
+ }
+ // If none of the above, then the device is likely facing straight down or straight up -- just
+ // pick something arbitrary
+ return 0;
+}
+
@end
@interface CameraPlugin ()
diff --git a/packages/camera/lib/camera.dart b/packages/camera/lib/camera.dart
index 2db347092f53..3461a29c97ce 100644
--- a/packages/camera/lib/camera.dart
+++ b/packages/camera/lib/camera.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
@@ -115,8 +116,27 @@ class CameraPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ // ISSUE: The Texture seems to return a buffer in portrait in Android (width and height are inverted) and a buffer in landscape on iOS (which respects the aspect ratio).
+ // The ideal code (if the Texture was oriented properly in landscape for both Android and iOS) would be:
+ /*
+ RotatedBox(
+ quarterTurns: controller.description.sensorOrientation ~/ 90,
+ child: AspectRatio(
+ aspectRatio: controller.value.aspectRatio,
+ child: Texture(textureId: controller._textureId),
+ ),
+ )
+ */
return controller.value.isInitialized
- ? Texture(textureId: controller._textureId)
+ ? RotatedBox(
+ quarterTurns: Platform.isAndroid ? 0 : 1,
+ child: AspectRatio(
+ aspectRatio: Platform.isAndroid
+ ? 1 / controller.value.aspectRatio
+ : controller.value.aspectRatio,
+ child: Texture(textureId: controller._textureId),
+ ),
+ )
: Container();
}
}
@@ -156,12 +176,14 @@ class CameraValue {
/// The size of the preview in pixels.
///
/// Is `null` until [isInitialized] is `true`.
+ ///
+ /// The preview size may be smaller than the size of the recorded video (for performance issues). But the aspect ratio will be preserved.
final Size previewSize;
- /// Convenience getter for `previewSize.height / previewSize.width`.
+ /// Convenience getter for `previewSize.width / previewSize.height`.
///
/// Can only be called when [initialize] is done.
- double get aspectRatio => previewSize.height / previewSize.width;
+ double get aspectRatio => previewSize.width / previewSize.height;
bool get hasError => errorDescription != null;
diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml
index 4649e80bb429..5d7794bed80c 100644
--- a/packages/camera/pubspec.yaml
+++ b/packages/camera/pubspec.yaml
@@ -2,7 +2,7 @@ name: camera
description: A Flutter plugin for getting information about and controlling the
camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video,
and streaming image buffers to dart.
-version: 0.4.3+2
+version: 0.4.4
authors:
- Flutter Team
- Luigi Agosti