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

Commit

Permalink
[camera]fix threading issue with thread safe types to ensure dispatch…
Browse files Browse the repository at this point in the history
…ing to main thread before calling engine api
  • Loading branch information
hellohuanlin committed Dec 10, 2021
1 parent ab99ed5 commit 82b933d
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 10 deletions.
25 changes: 15 additions & 10 deletions packages/camera/camera/ios/Classes/CameraPlugin.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
#import <libkern/OSAtomic.h>
#import <uuid/uuid.h>
#import "FLTThreadSafeFlutterResult.h"
#import "FLTThreadSafeMethodChannel.h"
#import "FLTThreadSafeEventChannel.h"
#import "FLTThreadSafeTextureRegistry.h"

@interface FLTSavePhotoDelegate : NSObject <AVCapturePhotoCaptureDelegate>
@property(readonly, nonatomic) NSString *path;
Expand Down Expand Up @@ -305,7 +308,7 @@ @interface FLTCam : NSObject <FlutterTexture,
@property(nonatomic, copy) void (^onFrameAvailable)(void);
@property BOOL enableAudio;
@property(nonatomic) FLTImageStreamHandler *imageStreamHandler;
@property(nonatomic) FlutterMethodChannel *methodChannel;
@property(nonatomic) FLTThreadSafeMethodChannel *methodChannel;
@property(readonly, nonatomic) AVCaptureSession *captureSession;
@property(readonly, nonatomic) AVCaptureDevice *captureDevice;
@property(readonly, nonatomic) AVCapturePhotoOutput *capturePhotoOutput API_AVAILABLE(ios(10));
Expand Down Expand Up @@ -1115,9 +1118,10 @@ - (void)startImageStreamWithMessenger:(NSObject<FlutterBinaryMessenger> *)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 {
Expand Down Expand Up @@ -1285,10 +1289,10 @@ - (void)setUpCaptureSessionForAudio {
@end

@interface CameraPlugin ()
@property(readonly, nonatomic) NSObject<FlutterTextureRegistry> *registry;
@property(readonly, nonatomic) FLTThreadSafeTextureRegistry *registry;
@property(readonly, nonatomic) NSObject<FlutterBinaryMessenger> *messenger;
@property(readonly, nonatomic) FLTCam *camera;
@property(readonly, nonatomic) FlutterMethodChannel *deviceEventMethodChannel;
@property(readonly, nonatomic) FLTThreadSafeMethodChannel *deviceEventMethodChannel;
@end

@implementation CameraPlugin {
Expand All @@ -1308,17 +1312,17 @@ - (instancetype)initWithRegistry:(NSObject<FlutterTextureRegistry> *)registry
messenger:(NSObject<FlutterBinaryMessenger> *)messenger {
self = [super init];
NSAssert(self, @"super init cannot be nil");
_registry = registry;
_registry = [[FLTThreadSafeTextureRegistry alloc] initWithTextureRegistry:registry];
_messenger = messenger;
[self initDeviceEventMethodChannel];
[self startOrientationListener];
return self;
}

- (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 {
Expand Down Expand Up @@ -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),
Expand Down
27 changes: 27 additions & 0 deletions packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.h
Original file line number Diff line number Diff line change
@@ -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 <Flutter/Flutter.h>

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<FlutterStreamHandler> *)handler;

@end

NS_ASSUME_NONNULL_END
29 changes: 29 additions & 0 deletions packages/camera/camera/ios/Classes/FLTThreadSafeEventChannel.m
Original file line number Diff line number Diff line change
@@ -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<FlutterStreamHandler> *)handler {
if (!NSThread.isMainThread) {
dispatch_async(dispatch_get_main_queue(), ^{
[self->_channel setStreamHandler: handler];
});
} else {
[_channel setStreamHandler: handler];
}
}

@end
27 changes: 27 additions & 0 deletions packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.h
Original file line number Diff line number Diff line change
@@ -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 <Flutter/Flutter.h>

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
29 changes: 29 additions & 0 deletions packages/camera/camera/ios/Classes/FLTThreadSafeMethodChannel.m
Original file line number Diff line number Diff line change
@@ -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
51 changes: 51 additions & 0 deletions packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.h
Original file line number Diff line number Diff line change
@@ -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 <Flutter/Flutter.h>

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<FlutterTextureRegistry> *)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<FlutterTexture> *)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
51 changes: 51 additions & 0 deletions packages/camera/camera/ios/Classes/FLTThreadSafeTextureRegistry.m
Original file line number Diff line number Diff line change
@@ -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<FlutterTextureRegistry> *_registry;
}

- (instancetype)initWithTextureRegistry: (NSObject<FlutterTextureRegistry> *)registry {
self = [super init];
if (self) {
_registry = registry;
}
return self;
}

- (int64_t)registerTexture:(NSObject<FlutterTexture> *)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

0 comments on commit 82b933d

Please sign in to comment.