Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
[camera] Fix iOS rotation issue (#3591)
Browse files Browse the repository at this point in the history
* Fix iOS rotation issue

* Fix orientation issues on iOS

* Merged with master and added test

* Test RotationBox turns according to device orientation

* Fix formatting

* Removed merge conflict tags from CHANGELOG

* Fix license header in test
  • Loading branch information
mvanbeusekom authored Mar 24, 2021
1 parent 9548bc2 commit 9cd84bd
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 56 deletions.
4 changes: 4 additions & 0 deletions packages/camera/camera/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.8.1

* Solved a rotation issue on iOS which caused the default preview to be displayed as landscape right instead of portrait.

## 0.8.0

* Stable null safety release.
Expand Down
104 changes: 62 additions & 42 deletions packages/camera/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ @interface FLTCam : NSObject <FlutterTexture,

@implementation FLTCam {
dispatch_queue_t _dispatchQueue;
UIDeviceOrientation _deviceOrientation;
}
// Format used for video and image streaming.
FourCharCode videoFormat = kCVPixelFormatType_32BGRA;
Expand All @@ -353,6 +354,7 @@ @implementation FLTCam {
- (instancetype)initWithCameraName:(NSString *)cameraName
resolutionPreset:(NSString *)resolutionPreset
enableAudio:(BOOL)enableAudio
orientation:(UIDeviceOrientation)orientation
dispatchQueue:(dispatch_queue_t)dispatchQueue
error:(NSError **)error {
self = [super init];
Expand All @@ -370,6 +372,7 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
_exposureMode = ExposureModeAuto;
_focusMode = FocusModeAuto;
_lockedCaptureOrientation = UIDeviceOrientationUnknown;
_deviceOrientation = orientation;

NSError *localError = nil;
_captureVideoInput = [AVCaptureDeviceInput deviceInputWithDevice:_captureDevice
Expand All @@ -389,10 +392,11 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
AVCaptureConnection *connection =
[AVCaptureConnection connectionWithInputPorts:_captureVideoInput.ports
output:_captureVideoOutput];

if ([_captureDevice position] == AVCaptureDevicePositionFront) {
connection.videoMirrored = YES;
}
connection.videoOrientation = AVCaptureVideoOrientationLandscapeRight;

[_captureSession addInputWithNoConnections:_captureVideoInput];
[_captureSession addOutputWithNoConnections:_captureVideoOutput];
[_captureSession addConnection:connection];
Expand All @@ -406,6 +410,8 @@ - (instancetype)initWithCameraName:(NSString *)cameraName
[_motionManager startAccelerometerUpdates];

[self setCaptureSessionPreset:_resolutionPreset];
[self updateOrientation];

return self;
}

Expand All @@ -417,6 +423,40 @@ - (void)stop {
[_captureSession stopRunning];
}

- (void)setDeviceOrientation:(UIDeviceOrientation)orientation {
if (_deviceOrientation == orientation) {
return;
}

_deviceOrientation = orientation;
[self updateOrientation];
}

- (void)updateOrientation {
if (_isRecording) {
return;
}

UIDeviceOrientation orientation = (_lockedCaptureOrientation != UIDeviceOrientationUnknown)
? _lockedCaptureOrientation
: _deviceOrientation;

[self updateOrientation:orientation forCaptureOutput:_capturePhotoOutput];
[self updateOrientation:orientation forCaptureOutput:_captureVideoOutput];
}

- (void)updateOrientation:(UIDeviceOrientation)orientation
forCaptureOutput:(AVCaptureOutput *)captureOutput {
if (!captureOutput) {
return;
}

AVCaptureConnection *connection = [captureOutput connectionWithMediaType:AVMediaTypeVideo];
if (connection && connection.isVideoOrientationSupported) {
connection.videoOrientation = [self getVideoOrientationForDeviceOrientation:orientation];
}
}

- (void)captureToFile:(FlutterResult)result API_AVAILABLE(ios(10)) {
AVCapturePhotoSettings *settings = [AVCapturePhotoSettings photoSettings];
if (_resolutionPreset == max) {
Expand All @@ -437,18 +477,6 @@ - (void)captureToFile:(FlutterResult)result API_AVAILABLE(ios(10)) {
return;
}

AVCaptureConnection *connection = [_capturePhotoOutput connectionWithMediaType:AVMediaTypeVideo];

if (connection) {
if (_lockedCaptureOrientation != UIDeviceOrientationUnknown) {
connection.videoOrientation =
[self getVideoOrientationForDeviceOrientation:_lockedCaptureOrientation];
} else {
connection.videoOrientation =
[self getVideoOrientationForDeviceOrientation:[[UIDevice currentDevice] orientation]];
}
}

[_capturePhotoOutput capturePhotoWithSettings:settings
delegate:[[FLTSavePhotoDelegate alloc] initWithPath:path
result:result]];
Expand Down Expand Up @@ -812,9 +840,11 @@ - (void)startVideoRecordingWithResult:(FlutterResult)result {
- (void)stopVideoRecordingWithResult:(FlutterResult)result {
if (_isRecording) {
_isRecording = NO;

if (_videoWriter.status != AVAssetWriterStatusUnknown) {
[_videoWriter finishWritingWithCompletionHandler:^{
if (self->_videoWriter.status == AVAssetWriterStatusCompleted) {
[self updateOrientation];
result(self->_videoRecordingPath);
self->_videoRecordingPath = nil;
} else {
Expand Down Expand Up @@ -854,12 +884,18 @@ - (void)lockCaptureOrientationWithResult:(FlutterResult)result
result(getFlutterError(e));
return;
}
_lockedCaptureOrientation = orientation;

if (_lockedCaptureOrientation != orientation) {
_lockedCaptureOrientation = orientation;
[self updateOrientation];
}

result(nil);
}

- (void)unlockCaptureOrientationWithResult:(FlutterResult)result {
_lockedCaptureOrientation = UIDeviceOrientationUnknown;
[self updateOrientation];
result(nil);
}

Expand Down Expand Up @@ -1101,6 +1137,7 @@ - (BOOL)setupWriterForPath:(NSString *)path {
if (_enableAudio && !_isAudioSetup) {
[self setUpCaptureSessionForAudio];
}

_videoWriter = [[AVAssetWriter alloc] initWithURL:outputURL
fileType:AVFileTypeMPEG4
error:&error];
Expand All @@ -1109,11 +1146,9 @@ - (BOOL)setupWriterForPath:(NSString *)path {
[_methodChannel invokeMethod:errorMethod arguments:error.description];
return NO;
}
NSDictionary *videoSettings = [NSDictionary
dictionaryWithObjectsAndKeys:AVVideoCodecH264, AVVideoCodecKey,
[NSNumber numberWithInt:_previewSize.width], AVVideoWidthKey,
[NSNumber numberWithInt:_previewSize.height], AVVideoHeightKey,
nil];

NSDictionary *videoSettings = [_captureVideoOutput
recommendedVideoSettingsForAssetWriterWithOutputFileType:AVFileTypeMPEG4];

This comment has been minimized.

Copy link
@cfchris

cfchris Oct 9, 2021

This change has spawned at least 5 issues because video encoding went from always being MPEG-4/H264 to sometimes on iOS being MPEG-H/HEVC/H265 (which is far less compatible). See flutter/flutter#83074

We updated Flutter, and several plugins, and fixed some bugs, that made it through PR and TestFlight. And about a month later, our customers started telling us some of their customers are getting "black" videos with only audio. It is because those customers are getting HEVC encoded videos.

image

I cannot use the old version of the plugin because of version and compatibility issues. So, I must move forward.

I'm hopeful this will eventually either be reverted or made a configuration setting so I can opt back in.

In the meantime, I have started a fork to revert this one change. But, I'm not an iOS developer, and when I try to start recording video in the example app, it immediately crashes and logs errors.

Here is the link to my fork. https://github.com/DealerPeak/plugins/tree/revert-to-h264-v2

I would appreciate any help getting this working again.

This comment has been minimized.

Copy link
@cfchris

cfchris Oct 9, 2021

OK. The fork above records videos now. But, the video seems to preview portrait, and then output landscape. So, the aspect ratio is wrong.

_videoWriterInput = [AVAssetWriterInput assetWriterInputWithMediaType:AVMediaTypeVideo
outputSettings:videoSettings];

Expand All @@ -1124,14 +1159,7 @@ - (BOOL)setupWriterForPath:(NSString *)path {
}];

NSParameterAssert(_videoWriterInput);
CGFloat rotationDegrees;
if (_lockedCaptureOrientation != UIDeviceOrientationUnknown) {
rotationDegrees = [self getRotationFromDeviceOrientation:_lockedCaptureOrientation];
} else {
rotationDegrees = [self getRotationFromDeviceOrientation:[UIDevice currentDevice].orientation];
}

_videoWriterInput.transform = CGAffineTransformMakeRotation(rotationDegrees * M_PI / 180);
_videoWriterInput.expectsMediaDataInRealTime = YES;

// Add the audio input
Expand Down Expand Up @@ -1194,21 +1222,6 @@ - (void)setUpCaptureSessionForAudio {
}
}
}

- (int)getRotationFromDeviceOrientation:(UIDeviceOrientation)orientation {
switch (orientation) {
case UIDeviceOrientationPortraitUpsideDown:
return 270;
case UIDeviceOrientationLandscapeRight:
return 180;
case UIDeviceOrientationLandscapeLeft:
return 0;
case UIDeviceOrientationPortrait:
default:
return 90;
};
}

@end

@interface CameraPlugin ()
Expand Down Expand Up @@ -1257,7 +1270,13 @@ - (void)startOrientationListener {

- (void)orientationChanged:(NSNotification *)note {
UIDevice *device = note.object;
[self sendDeviceOrientation:device.orientation];
UIDeviceOrientation orientation = device.orientation;

if (_camera) {
[_camera setDeviceOrientation:orientation];
}

[self sendDeviceOrientation:orientation];
}

- (void)sendDeviceOrientation:(UIDeviceOrientation)orientation {
Expand Down Expand Up @@ -1318,6 +1337,7 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call result:(FlutterResult)re
FLTCam *cam = [[FLTCam alloc] initWithCameraName:cameraName
resolutionPreset:resolutionPreset
enableAudio:[enableAudio boolValue]
orientation:[[UIDevice currentDevice] orientation]
dispatchQueue:_dispatchQueue
error:&error];

Expand Down
31 changes: 18 additions & 13 deletions packages/camera/camera/lib/src/camera_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
// found in the LICENSE file.

import 'package:camera/camera.dart';
import 'package:camera_platform_interface/camera_platform_interface.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
Expand All @@ -29,23 +28,23 @@ class CameraPreview extends StatelessWidget {
child: Stack(
fit: StackFit.expand,
children: [
RotatedBox(
quarterTurns: _getQuarterTurns(),
child:
CameraPlatform.instance.buildPreview(controller.cameraId),
),
_wrapInRotatedBox(child: controller.buildPreview()),
child ?? Container(),
],
),
)
: Container();
}

DeviceOrientation _getApplicableOrientation() {
return controller.value.isRecordingVideo
? controller.value.recordingOrientation!
: (controller.value.lockedCaptureOrientation ??
controller.value.deviceOrientation);
Widget _wrapInRotatedBox({required Widget child}) {
if (defaultTargetPlatform != TargetPlatform.android) {
return child;
}

return RotatedBox(
quarterTurns: _getQuarterTurns(),
child: child,
);
}

bool _isLandscape() {
Expand All @@ -54,13 +53,19 @@ class CameraPreview extends StatelessWidget {
}

int _getQuarterTurns() {
int platformOffset = defaultTargetPlatform == TargetPlatform.iOS ? 1 : 0;
Map<DeviceOrientation, int> turns = {
DeviceOrientation.portraitUp: 0,
DeviceOrientation.landscapeLeft: 1,
DeviceOrientation.portraitDown: 2,
DeviceOrientation.landscapeRight: 3,
};
return turns[_getApplicableOrientation()]! + platformOffset;
return turns[_getApplicableOrientation()]!;
}

DeviceOrientation _getApplicableOrientation() {
return controller.value.isRecordingVideo
? controller.value.recordingOrientation!
: (controller.value.lockedCaptureOrientation ??
controller.value.deviceOrientation);
}
}
2 changes: 1 addition & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.8.0
version: 0.8.1
homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera

dependencies:
Expand Down
Loading

0 comments on commit 9cd84bd

Please sign in to comment.