Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
2197235
Fixed video orientation on iOS
BeMacized Dec 30, 2020
5988830
Remove unnecessary check
BeMacized Jan 4, 2021
6bcf07d
Merge branch 'master' into fix/video-photo-preview-rotation
BeMacized Jan 4, 2021
9c4d4ca
Expand platform interface to support reporting device orientation
BeMacized Jan 4, 2021
484e02f
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 4, 2021
ed56fac
Switch to flutter DeviceOrientation enum
BeMacized Jan 4, 2021
1092177
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 4, 2021
07931a7
Fix preview rotation on iOS
BeMacized Jan 4, 2021
abe4360
Fix preview rotation for android
BeMacized Jan 4, 2021
4dcbbf7
Update unit tests
BeMacized Jan 4, 2021
69f752d
Fix rotation on initialise.
BeMacized Jan 4, 2021
fb42049
Keep EXIF data and picture orientation
mvanbeusekom Jan 5, 2021
13b9305
Fix photo capture orientation
mvanbeusekom Jan 5, 2021
eab58fe
Add interface methods for (un)locking the capture orientation.
BeMacized Jan 5, 2021
273d6e0
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
27bba29
Update capture orientation interfaces and add unit tests.
BeMacized Jan 5, 2021
751921a
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
859076f
Made device orientation mandatory for locking capture orientation in …
BeMacized Jan 5, 2021
d13b8ae
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
a8a9d43
Add capture orientation locking (iOS done, Android WIP)
BeMacized Jan 5, 2021
165c1ed
Code format
BeMacized Jan 5, 2021
3e9aad7
Add orientation lock to android implementation
BeMacized Jan 5, 2021
118bd00
Update comment
BeMacized Jan 5, 2021
ef56d9d
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
aa2c392
Maintain preview rotation while recording
BeMacized Jan 5, 2021
0aba73a
Merge branch 'master' into fix/video-photo-preview-rotation-platform-…
BeMacized Jan 5, 2021
0c74a4b
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
0bb07be
Update comment.
BeMacized Jan 5, 2021
5f845fe
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
a2c3fbf
Update changelog and pubspec version
BeMacized Jan 5, 2021
99ddc49
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
BeMacized Jan 5, 2021
9242af0
Updated changelog and pubspec version
BeMacized Jan 5, 2021
1d409b9
Update packages/camera/camera_platform_interface/lib/src/events/devic…
BeMacized Jan 5, 2021
96a739a
Merge branch 'master' into fix/video-photo-preview-rotation-platform-…
mvanbeusekom Jan 6, 2021
3df1a7b
Merge branch 'master' into fix/video-photo-preview-rotation
mvanbeusekom Jan 6, 2021
3a69c94
Merge branch 'fix/video-photo-preview-rotation-platform-interface' in…
mvanbeusekom Jan 6, 2021
5ece84b
Merge with master
mvanbeusekom Jan 6, 2021
e69f746
Fix formatting
mvanbeusekom Jan 6, 2021
1d84bae
Fix deprecation warning
mvanbeusekom Jan 6, 2021
f779fdc
Merge branch 'master' into fix/video-photo-preview-rotation
BeMacized Jan 11, 2021
3ca8eaf
Update platform interface dependency
BeMacized Jan 11, 2021
2aa1b53
Merge branch 'master' into fix/video-photo-preview-rotation
BeMacized Jan 11, 2021
3135953
Rollback update to Android compileSdkVersion 30
mvanbeusekom Jan 13, 2021
fd412a3
Revert "Rollback update to Android compileSdkVersion 30"
mvanbeusekom Jan 13, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix preview rotation on iOS
  • Loading branch information
BeMacized committed Jan 4, 2021
commit 07931a78bf2986d5e850522925a57886af6b9f1d
12 changes: 6 additions & 6 deletions packages/camera/camera/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,17 +157,17 @@ class _CameraExampleHomeState extends State<CameraExampleHome> with WidgetsBindi
),
);
} else {
return AspectRatio(
aspectRatio: 1 / controller.value.aspectRatio,
child: Listener(
onPointerDown: (_) => _pointers++,
onPointerUp: (_) => _pointers--,
return Listener(
onPointerDown: (_) => _pointers++,
onPointerUp: (_) => _pointers--,
child: CameraPreview(
controller,
child: LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onScaleStart: _handleScaleStart,
onScaleUpdate: _handleScaleUpdate,
onTapDown: (details) => onViewFinderTap(details, constraints),
child: CameraPreview(controller),
);
}),
),
Expand Down
42 changes: 42 additions & 0 deletions packages/camera/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,7 @@ @interface CameraPlugin ()
@property(readonly, nonatomic) NSObject<FlutterTextureRegistry> *registry;
@property(readonly, nonatomic) NSObject<FlutterBinaryMessenger> *messenger;
@property(readonly, nonatomic) FLTCam *camera;
@property(readonly, nonatomic) FlutterMethodChannel *deviceEventMethodChannel;
@end

@implementation CameraPlugin {
Expand All @@ -1106,9 +1107,50 @@ - (instancetype)initWithRegistry:(NSObject<FlutterTextureRegistry> *)registry
NSAssert(self, @"super init cannot be nil");
_registry = registry;
_messenger = messenger;
[self initDeviceEventMethodChannel];
[self startOrientationListener];
return self;
}

- (void)initDeviceEventMethodChannel {
_deviceEventMethodChannel =
[FlutterMethodChannel methodChannelWithName:@"flutter.io/cameraPlugin/device"
binaryMessenger:_messenger];
}

- (void)startOrientationListener {
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:[UIDevice currentDevice]];
}

- (void)orientationChanged:(NSNotification *)note {
UIDevice *device = note.object;

switch (device.orientation) {
case UIDeviceOrientationPortrait:
[_deviceEventMethodChannel invokeMethod:@"orientation_changed"
arguments:@{@"orientation" : @"portraitUp"}];
break;
case UIDeviceOrientationPortraitUpsideDown:
[_deviceEventMethodChannel invokeMethod:@"orientation_changed"
arguments:@{@"orientation" : @"portraitDown"}];
break;
case UIDeviceOrientationLandscapeRight:
[_deviceEventMethodChannel invokeMethod:@"orientation_changed"
arguments:@{@"orientation" : @"landscapeLeft"}];
break;
case UIDeviceOrientationLandscapeLeft:
[_deviceEventMethodChannel invokeMethod:@"orientation_changed"
arguments:@{@"orientation" : @"landscapeRight"}];
break;
default:
break;
};
}

- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
if (_dispatchQueue == nil) {
_dispatchQueue = dispatch_queue_create("io.flutter.camera.dispatchqueue", NULL);
Expand Down
62 changes: 32 additions & 30 deletions packages/camera/camera/lib/src/camera_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class CameraValue {
this.flashMode,
this.exposureMode,
this.exposurePointSupported,
this.deviceOrientation,
}) : _isRecordingPaused = isRecordingPaused;

/// Creates a new camera controller state for an uninitialized controller.
Expand All @@ -53,6 +54,7 @@ class CameraValue {
isRecordingPaused: false,
flashMode: FlashMode.auto,
exposurePointSupported: false,
deviceOrientation: DeviceOrientation.portraitUp,
);

/// True after [CameraController.initialize] has completed successfully.
Expand Down Expand Up @@ -102,6 +104,9 @@ class CameraValue {
/// Whether setting the exposure point is supported.
final bool exposurePointSupported;

/// The current device orientation.
final DeviceOrientation deviceOrientation;

/// Creates a modified copy of the object.
///
/// Explicitly specified fields get the specified value, all other fields get
Expand All @@ -117,6 +122,7 @@ class CameraValue {
FlashMode flashMode,
ExposureMode exposureMode,
bool exposurePointSupported,
DeviceOrientation deviceOrientation,
}) {
return CameraValue(
isInitialized: isInitialized ?? this.isInitialized,
Expand All @@ -128,8 +134,8 @@ class CameraValue {
isRecordingPaused: isRecordingPaused ?? _isRecordingPaused,
flashMode: flashMode ?? this.flashMode,
exposureMode: exposureMode ?? this.exposureMode,
exposurePointSupported:
exposurePointSupported ?? this.exposurePointSupported,
exposurePointSupported: exposurePointSupported ?? this.exposurePointSupported,
deviceOrientation: deviceOrientation ?? this.deviceOrientation,
);
}

Expand All @@ -143,7 +149,8 @@ class CameraValue {
'isStreamingImages: $isStreamingImages, '
'flashMode: $flashMode, '
'exposureMode: $exposureMode, '
'exposurePointSupported: $exposurePointSupported)';
'exposurePointSupported: $exposurePointSupported, '
'deviceOrientation: $deviceOrientation)';
}
}

Expand Down Expand Up @@ -180,6 +187,7 @@ class CameraController extends ValueNotifier<CameraValue> {
bool _isDisposed = false;
StreamSubscription<dynamic> _imageStreamSubscription;
FutureOr<bool> _initCalled;
StreamSubscription _deviceOrientationSubscription;

/// Checks whether [CameraController.dispose] has completed successfully.
///
Expand All @@ -204,32 +212,32 @@ class CameraController extends ValueNotifier<CameraValue> {
try {
Completer<CameraInitializedEvent> _initializeCompleter = Completer();

_deviceOrientationSubscription = CameraPlatform.instance.onDeviceOrientationChanged().listen((event) {
value = value.copyWith(
deviceOrientation: event.orientation,
);
});

_cameraId = await CameraPlatform.instance.createCamera(
description,
resolutionPreset,
enableAudio: enableAudio,
);

unawaited(CameraPlatform.instance
.onCameraInitialized(_cameraId)
.first
.then((event) {
unawaited(CameraPlatform.instance.onCameraInitialized(_cameraId).first.then((event) {
_initializeCompleter.complete(event);
}));

await CameraPlatform.instance.initializeCamera(_cameraId);

value = value.copyWith(
isInitialized: true,
previewSize: await _initializeCompleter.future
.then((CameraInitializedEvent event) => Size(
event.previewWidth,
event.previewHeight,
)),
exposureMode: await _initializeCompleter.future
.then((event) => event.exposureMode),
exposurePointSupported: await _initializeCompleter.future
.then((event) => event.exposurePointSupported),
previewSize: await _initializeCompleter.future.then((CameraInitializedEvent event) => Size(
event.previewWidth,
event.previewHeight,
)),
exposureMode: await _initializeCompleter.future.then((event) => event.exposureMode),
exposurePointSupported: await _initializeCompleter.future.then((event) => event.exposurePointSupported),
);
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
Expand Down Expand Up @@ -298,8 +306,7 @@ class CameraController extends ValueNotifier<CameraValue> {
///
// TODO(bmparr): Add settings for resolution and fps.
Future<void> startImageStream(onLatestImageAvailable onAvailable) async {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS);

if (!value.isInitialized || _isDisposed) {
throw CameraException(
Expand All @@ -326,10 +333,8 @@ class CameraController extends ValueNotifier<CameraValue> {
} on PlatformException catch (e) {
throw CameraException(e.code, e.message);
}
const EventChannel cameraEventChannel =
EventChannel('plugins.flutter.io/camera/imageStream');
_imageStreamSubscription =
cameraEventChannel.receiveBroadcastStream().listen(
const EventChannel cameraEventChannel = EventChannel('plugins.flutter.io/camera/imageStream');
_imageStreamSubscription = cameraEventChannel.receiveBroadcastStream().listen(
(dynamic imageData) {
onAvailable(CameraImage.fromPlatformData(imageData));
},
Expand All @@ -344,8 +349,7 @@ class CameraController extends ValueNotifier<CameraValue> {
/// The `stopImageStream` method is only available on Android and iOS (other
/// platforms won't be supported in current setup).
Future<void> stopImageStream() async {
assert(defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS);
assert(defaultTargetPlatform == TargetPlatform.android || defaultTargetPlatform == TargetPlatform.iOS);

if (!value.isInitialized || _isDisposed) {
throw CameraException(
Expand Down Expand Up @@ -571,10 +575,8 @@ class CameraController extends ValueNotifier<CameraValue> {

/// Sets the exposure point for automatically determining the exposure value.
Future<void> setExposurePoint(Offset point) async {
if (point != null &&
(point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) {
throw ArgumentError(
'The values of point should be anywhere between (0,0) and (1,1).');
if (point != null && (point.dx < 0 || point.dx > 1 || point.dy < 0 || point.dy > 1)) {
throw ArgumentError('The values of point should be anywhere between (0,0) and (1,1).');
}
try {
await CameraPlatform.instance.setExposurePoint(
Expand Down Expand Up @@ -661,8 +663,7 @@ class CameraController extends ValueNotifier<CameraValue> {
}

// Check if offset is in range
List<double> range =
await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]);
List<double> range = await Future.wait([getMinExposureOffset(), getMaxExposureOffset()]);
if (offset < range[0] || offset > range[1]) {
throw CameraException(
"exposureOffsetOutOfBounds",
Expand Down Expand Up @@ -696,6 +697,7 @@ class CameraController extends ValueNotifier<CameraValue> {
if (_isDisposed) {
return;
}
unawaited(_deviceOrientationSubscription?.cancel());
_isDisposed = true;
super.dispose();
if (_initCalled != null) {
Expand Down
34 changes: 26 additions & 8 deletions packages/camera/camera/lib/src/camera_preview.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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';

/// A widget showing a live camera preview.
class CameraPreview extends StatelessWidget {
Expand All @@ -21,18 +22,35 @@ class CameraPreview extends StatelessWidget {
@override
Widget build(BuildContext context) {
return controller.value.isInitialized
? RotatedBox(
quarterTurns: _getQuarterTurns(),
child: AspectRatio(
aspectRatio: 1 / controller.value.aspectRatio,
child: CameraPlatform.instance.buildPreview(controller.cameraId),
),
)
? AspectRatio(
aspectRatio: _isLandscape() ? controller.value.aspectRatio : (1 / controller.value.aspectRatio),
child: Stack(
fit: StackFit.expand,
children: [
RotatedBox(
quarterTurns: _getQuarterTurns(),
child: CameraPlatform.instance.buildPreview(controller.cameraId),
),
child ?? Container(),
],
),
)
: Container();
}

bool _isLandscape() {
return [DeviceOrientation.landscapeLeft, DeviceOrientation.landscapeRight]
.contains(controller.value.deviceOrientation);
}

int _getQuarterTurns() {
int platformOffset = defaultTargetPlatform == TargetPlatform.iOS ? 1 : 0;
return platformOffset;
Map<DeviceOrientation, int> turns = {
DeviceOrientation.portraitUp: 0,
DeviceOrientation.portraitDown: 2,
DeviceOrientation.landscapeRight: 3,
DeviceOrientation.landscapeLeft: 1
};
return turns[controller.value.deviceOrientation] + platformOffset;
}
}
4 changes: 3 additions & 1 deletion packages/camera/camera/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ homepage: https://github.com/flutter/plugins/tree/master/packages/camera/camera
dependencies:
flutter:
sdk: flutter
camera_platform_interface: ^1.2.0
# TODO (BeMacized): Change back to version reference when the platform interface updates have been published.
camera_platform_interface:
path: ../camera_platform_interface
pedantic: ^1.8.0

dev_dependencies:
Expand Down