diff --git a/packages/camera/camera/ios/Classes/CameraPlugin.m b/packages/camera/camera/ios/Classes/CameraPlugin.m index 2c12081da807..cb5c056204d2 100644 --- a/packages/camera/camera/ios/Classes/CameraPlugin.m +++ b/packages/camera/camera/ios/Classes/CameraPlugin.m @@ -11,6 +11,9 @@ #import #import #import "FLTThreadSafeFlutterResult.h" +#import "FLTThreadSafeMethodChannel.h" +#import "FLTThreadSafeEventChannel.h" +#import "FLTThreadSafeTextureRegistry.h" @interface FLTSavePhotoDelegate : NSObject @property(readonly, nonatomic) NSString *path; @@ -305,7 +308,7 @@ @interface FLTCam : NSObject *)messen FlutterEventChannel *eventChannel = [FlutterEventChannel eventChannelWithName:@"plugins.flutter.io/camera/imageStream" binaryMessenger:messenger]; + FLTThreadSafeEventChannel *threadSafeEventChannel = [[FLTThreadSafeEventChannel alloc] initWithEventChannel:eventChannel]; _imageStreamHandler = [[FLTImageStreamHandler alloc] init]; - [eventChannel setStreamHandler:_imageStreamHandler]; + [threadSafeEventChannel setStreamHandler:_imageStreamHandler]; _isStreamingImages = YES; } else { @@ -1285,10 +1289,10 @@ - (void)setUpCaptureSessionForAudio { @end @interface CameraPlugin () -@property(readonly, nonatomic) NSObject *registry; +@property(readonly, nonatomic) FLTThreadSafeTextureRegistry *registry; @property(readonly, nonatomic) NSObject *messenger; @property(readonly, nonatomic) FLTCam *camera; -@property(readonly, nonatomic) FlutterMethodChannel *deviceEventMethodChannel; +@property(readonly, nonatomic) FLTThreadSafeMethodChannel *deviceEventMethodChannel; @end @implementation CameraPlugin { @@ -1308,7 +1312,7 @@ - (instancetype)initWithRegistry:(NSObject *)registry messenger:(NSObject *)messenger { self = [super init]; NSAssert(self, @"super init cannot be nil"); - _registry = registry; + _registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:registry]; _messenger = messenger; [self initDeviceEventMethodChannel]; [self startOrientationListener]; @@ -1316,9 +1320,9 @@ - (instancetype)initWithRegistry:(NSObject *)registry } - (void)initDeviceEventMethodChannel { - _deviceEventMethodChannel = - [FlutterMethodChannel methodChannelWithName:@"flutter.io/cameraPlugin/device" - binaryMessenger:_messenger]; + FlutterMethodChannel *methodChannel = [FlutterMethodChannel methodChannelWithName:@"flutter.io/cameraPlugin/device" + binaryMessenger:_messenger]; + _deviceEventMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel: methodChannel]; } - (void)startOrientationListener { @@ -1446,8 +1450,9 @@ - (void)handleMethodCallAsync:(FlutterMethodCall *)call methodChannelWithName:[NSString stringWithFormat:@"flutter.io/cameraPlugin/camera%lu", (unsigned long)cameraId] binaryMessenger:_messenger]; - _camera.methodChannel = methodChannel; - [methodChannel + FLTThreadSafeMethodChannel *threadSafeMethodChannel = [[FLTThreadSafeMethodChannel alloc] initWithMethodChannel: methodChannel]; + _camera.methodChannel = threadSafeMethodChannel; + [threadSafeMethodChannel invokeMethod:@"initialized" arguments:@{ @"previewWidth" : @(_camera.previewSize.width), diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h new file mode 100644 index 000000000000..dd84a6733d64 --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper for FlutterEventChannel that always sends events on the main thread + */ +@interface FLTThreadSafeEventChannel : NSObject + +/** + * Creates a FLTThreadSafeEventChannel by wrapping a FlutterEventChannel object. + * @param channel The FlutterEventChannel object to be wrapped. + */ +- (instancetype)initWithEventChannel:(FlutterEventChannel *)channel; + +/* + * Registers a handler for stream setup requests from the Flutter side on main thread. + */ +- (void)setStreamHandler:(nullable NSObject *)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m new file mode 100644 index 000000000000..4af82b05b9cf --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTThreadSafeEventChannel.h" + +@implementation FLTThreadSafeEventChannel { + FlutterEventChannel *_channel; +} + +- (instancetype)initWithEventChannel:(FlutterEventChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + } + return self; +} + +- (void)setStreamHandler:(NSObject *)handler { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_channel setStreamHandler: handler]; + }); + } else { + [_channel setStreamHandler: handler]; + } +} + +@end diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h new file mode 100644 index 000000000000..df75858bb03f --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h @@ -0,0 +1,27 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper for FlutterMethodChannel that always invokes messages on the main thread + */ +@interface FLTThreadSafeMethodChannel : NSObject + +/** + * Creates a FLTThreadSafeMethodChannel by wrapping a FlutterMethodChannel object. + * @param channel The FlutterMethodChannel object to be wrapped. + */ +- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel; + +/** + * Invokes the specified flutter method with the specified arguments on main thread. + */ +- (void)invokeMethod:(NSString*)method arguments:(nullable id)arguments; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m new file mode 100644 index 000000000000..dea70bf4ea6a --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m @@ -0,0 +1,29 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTThreadSafeMethodChannel.h" + +@implementation FLTThreadSafeMethodChannel { + FlutterMethodChannel *_channel; +} + +- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)channel { + self = [super init]; + if (self) { + _channel = channel; + } + return self; +} + +- (void)invokeMethod:(NSString*)method arguments:(id)arguments { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_channel invokeMethod:method arguments:arguments]; + }); + } else { + [_channel invokeMethod:method arguments:arguments]; + } +} + +@end diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h new file mode 100644 index 000000000000..54939b41beab --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** + * Wrapper for FlutterTextureRegistry that always sends events on the main thread + */ +@interface FLTThreadSafeTextureRegistry : NSObject + +/** + * Creates a FLTThreadSafeTextureRegistry by wrapping an object conforming to FlutterTextureRegistry. + * @param registry The FlutterTextureRegistry object to be wrapped. + */ +- (instancetype)initWithTextureRegistry: (NSObject *)registry; + +/** + * Registers a `FlutterTexture` for usage in Flutter and returns an id that can be used to reference + * that texture when calling into Flutter with channels. Textures must be registered on the + * platform thread. On success returns the pointer to the registered texture, else returns 0. + * + * Runs on main thread. + */ +- (int64_t)registerTexture:(NSObject *)texture; + + +/** + * Notifies Flutter that the content of the previously registered texture has been updated. + * + * This will trigger a call to `-[FlutterTexture copyPixelBuffer]` on the raster thread. + * + * Runs on main thread. + */ +- (void)textureFrameAvailable:(int64_t)textureId; + +/** + * Unregisters a `FlutterTexture` that has previously regeistered with `registerTexture:`. Textures + * must be unregistered on the platform thread. + * + * Runs on main thread. + * + * @param textureId The result that was previously returned from `registerTexture:`. + */ +- (void)unregisterTexture:(int64_t)textureId; + +@end + +NS_ASSUME_NONNULL_END diff --git a/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m new file mode 100644 index 000000000000..0549933d116f --- /dev/null +++ b/packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m @@ -0,0 +1,51 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FLTThreadSafeTextureRegistry.h" + +@implementation FLTThreadSafeTextureRegistry { + NSObject *_registry; +} + +- (instancetype)initWithTextureRegistry: (NSObject *)registry { + self = [super init]; + if (self) { + _registry = registry; + } + return self; +} + +- (int64_t)registerTexture:(NSObject *)texture { + if (!NSThread.isMainThread) { + __block int64_t textureId; + dispatch_sync(dispatch_get_main_queue(), ^{ + textureId = [self->_registry registerTexture:texture]; + }); + return textureId; + } else { + return [_registry registerTexture:texture]; + } +} + +- (void)textureFrameAvailable:(int64_t)textureId { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_registry textureFrameAvailable:textureId]; + }); + } else { + [_registry textureFrameAvailable:textureId]; + } +} + +- (void)unregisterTexture:(int64_t)textureId { + if (!NSThread.isMainThread) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self->_registry unregisterTexture:textureId]; + }); + } else { + [_registry unregisterTexture:textureId]; + } +} + +@end