feat: Make Frame Processor Plugins object-oriented on iOS as well (#1496)
* feat: Make Frame Processor Plugins object-oriented on iOS as well * Add Plugin in AppDelegate
This commit is contained in:
		@@ -41,29 +41,35 @@ For reference see the [CLI's docs](https://github.com/mateusz1913/vision-camera-
 | 
			
		||||
2. Create an Objective-C source file, for the QR Code Plugin this will be called `QRCodeFrameProcessorPlugin.m`.
 | 
			
		||||
3. Add the following code:
 | 
			
		||||
 | 
			
		||||
```objc {12}
 | 
			
		||||
```objc
 | 
			
		||||
#import <VisionCamera/FrameProcessorPlugin.h>
 | 
			
		||||
#import <VisionCamera/Frame.h>
 | 
			
		||||
 | 
			
		||||
@interface QRCodeFrameProcessorPlugin : NSObject
 | 
			
		||||
@interface QRCodeFrameProcessorPlugin : FrameProcessorPlugin
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@implementation QRCodeFrameProcessorPlugin
 | 
			
		||||
 | 
			
		||||
static inline id scanQRCodes(Frame* frame, NSArray* args) {
 | 
			
		||||
- (NSString *)name {
 | 
			
		||||
  return @"scanQRCodes";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (id)callback:(Frame *)frame withArguments:(NSArray<id> *)arguments {
 | 
			
		||||
  CMSampleBufferRef buffer = frame.buffer;
 | 
			
		||||
  UIImageOrientation orientation = frame.orientation;
 | 
			
		||||
  // code goes here
 | 
			
		||||
  return @[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VISION_EXPORT_FRAME_PROCESSOR(scanQRCodes)
 | 
			
		||||
+ (void) load {
 | 
			
		||||
  [self registerPlugin:[[ExampleFrameProcessorPlugin alloc] init]];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::note
 | 
			
		||||
The Frame Processor Plugin will be exposed to JS through the `FrameProcessorPlugins` object using the same name as the Objective-C function. In this case, it would be `FrameProcessorPlugins.scanQRCodes(...)`.
 | 
			
		||||
The Frame Processor Plugin will be exposed to JS through the `FrameProcessorPlugins` object using the name returned from the `name` getter. In this case, it would be `FrameProcessorPlugins.scanQRCodes(...)`.
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
4. **Implement your Frame Processing.** See the [Example Plugin (Objective-C)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/ios/Frame%20Processor%20Plugins/Example%20Plugin%20%28Objective%2DC%29) for reference.
 | 
			
		||||
@@ -83,27 +89,16 @@ The Frame Processor Plugin will be exposed to JS through the `FrameProcessorPlug
 | 
			
		||||
#import <VisionCamera/Frame.h>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
4. Create an Objective-C source file with the same name as the Swift file, for the QR Code Plugin this will be `QRCodeFrameProcessorPlugin.m`. Add the following code:
 | 
			
		||||
4. In the Swift file, add the following code:
 | 
			
		||||
 | 
			
		||||
```objc
 | 
			
		||||
#import <VisionCamera/FrameProcessorPlugin.h>
 | 
			
		||||
 | 
			
		||||
@interface VISION_EXPORT_SWIFT_FRAME_PROCESSOR(scanQRCodes, QRCodeFrameProcessorPlugin)
 | 
			
		||||
@end
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
:::note
 | 
			
		||||
The first parameter in the Macro specifies the JS function name. Make sure it is unique across other Frame Processors.
 | 
			
		||||
:::
 | 
			
		||||
 | 
			
		||||
5. In the Swift file, add the following code:
 | 
			
		||||
 | 
			
		||||
```swift {8}
 | 
			
		||||
```swift
 | 
			
		||||
@objc(QRCodeFrameProcessorPlugin)
 | 
			
		||||
public class QRCodeFrameProcessorPlugin: NSObject, FrameProcessorPluginBase {
 | 
			
		||||
public class QRCodeFrameProcessorPlugin: FrameProcessorPlugin {
 | 
			
		||||
  override public func name() -> String! {
 | 
			
		||||
    return "scanQRCodes"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @objc
 | 
			
		||||
  public static func callback(_ frame: Frame!, withArgs _: [Any]!) -> Any! {
 | 
			
		||||
  public override func callback(_ frame: Frame!, withArguments arguments: [Any]!) -> Any! {
 | 
			
		||||
    let buffer = frame.buffer
 | 
			
		||||
    let orientation = frame.orientation
 | 
			
		||||
    // code goes here
 | 
			
		||||
@@ -112,7 +107,27 @@ public class QRCodeFrameProcessorPlugin: NSObject, FrameProcessorPluginBase {
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
6. **Implement your frame processing.** See [Example Plugin (Swift)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/ios/Frame%20Processor%20Plugins/Example%20Plugin%20%28Swift%29) for reference.
 | 
			
		||||
5. In your `AppDelegate.m`, add the following imports (you can skip this if your AppDelegate is in Swift):
 | 
			
		||||
 | 
			
		||||
```objc
 | 
			
		||||
#import "YOUR_XCODE_PROJECT_NAME-Swift.h"
 | 
			
		||||
#import <VisionCamera/FrameProcessorPlugin.h>
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
6. In your `AppDelegate.m`, add the following code to `application:didFinishLaunchingWithOptions:`:
 | 
			
		||||
 | 
			
		||||
```objc {5}
 | 
			
		||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
 | 
			
		||||
{
 | 
			
		||||
  ...
 | 
			
		||||
 | 
			
		||||
  [FrameProcessorPlugin registerPlugin:[[QRCodeFrameProcessorPlugin alloc] init]];
 | 
			
		||||
 | 
			
		||||
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
7. **Implement your frame processing.** See [Example Plugin (Swift)](https://github.com/mrousavy/react-native-vision-camera/blob/main/example/ios/Frame%20Processor%20Plugins/Example%20Plugin%20%28Swift%29) for reference.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
</TabItem>
 | 
			
		||||
 
 | 
			
		||||
@@ -11,12 +11,16 @@
 | 
			
		||||
 | 
			
		||||
// Example for an Objective-C Frame Processor plugin
 | 
			
		||||
 | 
			
		||||
@interface ExampleFrameProcessorPlugin : NSObject
 | 
			
		||||
@interface ExampleFrameProcessorPlugin : FrameProcessorPlugin
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
@implementation ExampleFrameProcessorPlugin
 | 
			
		||||
 | 
			
		||||
static inline id example_plugin(Frame* frame, NSArray* arguments) {
 | 
			
		||||
- (NSString *)name {
 | 
			
		||||
  return @"example_plugin";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (id)callback:(Frame *)frame withArguments:(NSArray<id> *)arguments {
 | 
			
		||||
  CVPixelBufferRef imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer);
 | 
			
		||||
  NSLog(@"ExamplePlugin: %zu x %zu Image. Logging %lu parameters:", CVPixelBufferGetWidth(imageBuffer), CVPixelBufferGetHeight(imageBuffer), (unsigned long)arguments.count);
 | 
			
		||||
  
 | 
			
		||||
@@ -36,6 +40,8 @@ static inline id example_plugin(Frame* frame, NSArray* arguments) {
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
VISION_EXPORT_FRAME_PROCESSOR(example_plugin)
 | 
			
		||||
+ (void) load {
 | 
			
		||||
  [self registerPlugin:[[ExampleFrameProcessorPlugin alloc] init]];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//  ExamplePluginSwift.m
 | 
			
		||||
//  VisionCamera
 | 
			
		||||
//
 | 
			
		||||
//  Created by Marc Rousavy on 01.05.21.
 | 
			
		||||
//  Copyright © 2021 mrousavy. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#import <VisionCamera/FrameProcessorPlugin.h>
 | 
			
		||||
 | 
			
		||||
@interface VISION_EXPORT_SWIFT_FRAME_PROCESSOR(example_plugin_swift, ExamplePluginSwift)
 | 
			
		||||
@end
 | 
			
		||||
@@ -9,34 +9,38 @@
 | 
			
		||||
import AVKit
 | 
			
		||||
import Vision
 | 
			
		||||
 | 
			
		||||
@objc(ExamplePluginSwift)
 | 
			
		||||
public class ExamplePluginSwift: NSObject, FrameProcessorPluginBase {
 | 
			
		||||
    @objc
 | 
			
		||||
    public static func callback(_ frame: Frame!, withArgs args: [Any]!) -> Any! {
 | 
			
		||||
        guard let imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer) else {
 | 
			
		||||
            return nil
 | 
			
		||||
        }
 | 
			
		||||
        NSLog("ExamplePlugin: \(CVPixelBufferGetWidth(imageBuffer)) x \(CVPixelBufferGetHeight(imageBuffer)) Image. Logging \(args.count) parameters:")
 | 
			
		||||
@objc
 | 
			
		||||
public class ExamplePluginSwift : FrameProcessorPlugin {
 | 
			
		||||
  
 | 
			
		||||
        args.forEach { arg in
 | 
			
		||||
            var string = "\(arg)"
 | 
			
		||||
            if let array = arg as? NSArray {
 | 
			
		||||
                string = (array as Array).description
 | 
			
		||||
            } else if let map = arg as? NSDictionary {
 | 
			
		||||
                string = (map as Dictionary).description
 | 
			
		||||
            }
 | 
			
		||||
            NSLog("ExamplePlugin:   -> \(string) (\(type(of: arg)))")
 | 
			
		||||
        }
 | 
			
		||||
  override public func name() -> String! {
 | 
			
		||||
    return "example_plugin_swift"
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
        return [
 | 
			
		||||
            "example_str": "Test",
 | 
			
		||||
            "example_bool": true,
 | 
			
		||||
            "example_double": 5.3,
 | 
			
		||||
            "example_array": [
 | 
			
		||||
                "Hello",
 | 
			
		||||
                true,
 | 
			
		||||
                17.38,
 | 
			
		||||
            ],
 | 
			
		||||
        ]
 | 
			
		||||
    }
 | 
			
		||||
  public override func callback(_ frame: Frame!, withArguments arguments: [Any]!) -> Any! {
 | 
			
		||||
      guard let imageBuffer = CMSampleBufferGetImageBuffer(frame.buffer) else {
 | 
			
		||||
          return nil
 | 
			
		||||
      }
 | 
			
		||||
      NSLog("ExamplePlugin: \(CVPixelBufferGetWidth(imageBuffer)) x \(CVPixelBufferGetHeight(imageBuffer)) Image. Logging \(arguments.count) parameters:")
 | 
			
		||||
 | 
			
		||||
      arguments.forEach { arg in
 | 
			
		||||
          var string = "\(arg)"
 | 
			
		||||
          if let array = arg as? NSArray {
 | 
			
		||||
              string = (array as Array).description
 | 
			
		||||
          } else if let map = arg as? NSDictionary {
 | 
			
		||||
              string = (map as Dictionary).description
 | 
			
		||||
          }
 | 
			
		||||
          NSLog("ExamplePlugin:   -> \(string) (\(type(of: arg)))")
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return [
 | 
			
		||||
          "example_str": "Test",
 | 
			
		||||
          "example_bool": true,
 | 
			
		||||
          "example_double": 5.3,
 | 
			
		||||
          "example_array": [
 | 
			
		||||
              "Hello",
 | 
			
		||||
              true,
 | 
			
		||||
              17.38,
 | 
			
		||||
          ],
 | 
			
		||||
      ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -436,7 +436,7 @@ PODS:
 | 
			
		||||
    - React-Core
 | 
			
		||||
  - RNVectorIcons (9.2.0):
 | 
			
		||||
    - React-Core
 | 
			
		||||
  - VisionCamera (3.0.0-rc.1):
 | 
			
		||||
  - VisionCamera (3.0.0-rc.2):
 | 
			
		||||
    - React
 | 
			
		||||
    - React-callinvoker
 | 
			
		||||
    - React-Core
 | 
			
		||||
@@ -644,7 +644,7 @@ SPEC CHECKSUMS:
 | 
			
		||||
  RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
 | 
			
		||||
  RNStaticSafeAreaInsets: 055ddbf5e476321720457cdaeec0ff2ba40ec1b8
 | 
			
		||||
  RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
 | 
			
		||||
  VisionCamera: 0d154cd0ab9043a3c8a4908fb57ad65c9e1f3baf
 | 
			
		||||
  VisionCamera: e4f19a6c22cfa146736bdfe8df057e2ed5ca8dd3
 | 
			
		||||
  Yoga: 5ed1699acbba8863755998a4245daa200ff3817b
 | 
			
		||||
 | 
			
		||||
PODFILE CHECKSUM: d53724fe402c2547f1dd1cc571bbe77d9820e636
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,9 @@
 | 
			
		||||
		13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
 | 
			
		||||
		13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
 | 
			
		||||
		81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
 | 
			
		||||
		B8DB3BD5263DE8B7004C18D7 /* BuildFile in Sources */ = {isa = PBXBuildFile; };
 | 
			
		||||
		B8DB3BD5263DE8B7004C18D7 /* (null) in Sources */ = {isa = PBXBuildFile; };
 | 
			
		||||
		B8DB3BDC263DEA31004C18D7 /* ExampleFrameProcessorPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = B8DB3BD8263DEA31004C18D7 /* ExampleFrameProcessorPlugin.m */; };
 | 
			
		||||
		B8DB3BDD263DEA31004C18D7 /* ExamplePluginSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8DB3BDA263DEA31004C18D7 /* ExamplePluginSwift.swift */; };
 | 
			
		||||
		B8DB3BDE263DEA31004C18D7 /* ExamplePluginSwift.m in Sources */ = {isa = PBXBuildFile; fileRef = B8DB3BDB263DEA31004C18D7 /* ExamplePluginSwift.m */; };
 | 
			
		||||
		B8F0E10825E0199F00586F16 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8F0E10725E0199F00586F16 /* File.swift */; };
 | 
			
		||||
		C0B129659921D2EA967280B2 /* libPods-VisionCameraExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CDCFE89C25C89320B98945E /* libPods-VisionCameraExample.a */; };
 | 
			
		||||
/* End PBXBuildFile section */
 | 
			
		||||
@@ -31,7 +30,6 @@
 | 
			
		||||
		81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = VisionCameraExample/LaunchScreen.storyboard; sourceTree = "<group>"; };
 | 
			
		||||
		B8DB3BD8263DEA31004C18D7 /* ExampleFrameProcessorPlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExampleFrameProcessorPlugin.m; sourceTree = "<group>"; };
 | 
			
		||||
		B8DB3BDA263DEA31004C18D7 /* ExamplePluginSwift.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExamplePluginSwift.swift; sourceTree = "<group>"; };
 | 
			
		||||
		B8DB3BDB263DEA31004C18D7 /* ExamplePluginSwift.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ExamplePluginSwift.m; sourceTree = "<group>"; };
 | 
			
		||||
		B8F0E10625E0199F00586F16 /* VisionCameraExample-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "VisionCameraExample-Bridging-Header.h"; sourceTree = "<group>"; };
 | 
			
		||||
		B8F0E10725E0199F00586F16 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
 | 
			
		||||
		C1D342AD8210E7627A632602 /* Pods-VisionCameraExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-VisionCameraExample.debug.xcconfig"; path = "Target Support Files/Pods-VisionCameraExample/Pods-VisionCameraExample.debug.xcconfig"; sourceTree = "<group>"; };
 | 
			
		||||
@@ -138,7 +136,6 @@
 | 
			
		||||
			isa = PBXGroup;
 | 
			
		||||
			children = (
 | 
			
		||||
				B8DB3BDA263DEA31004C18D7 /* ExamplePluginSwift.swift */,
 | 
			
		||||
				B8DB3BDB263DEA31004C18D7 /* ExamplePluginSwift.m */,
 | 
			
		||||
			);
 | 
			
		||||
			path = "Example Plugin (Swift)";
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
@@ -383,11 +380,10 @@
 | 
			
		||||
			files = (
 | 
			
		||||
				13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
 | 
			
		||||
				B8DB3BDC263DEA31004C18D7 /* ExampleFrameProcessorPlugin.m in Sources */,
 | 
			
		||||
				B8DB3BD5263DE8B7004C18D7 /* BuildFile in Sources */,
 | 
			
		||||
				B8DB3BD5263DE8B7004C18D7 /* (null) in Sources */,
 | 
			
		||||
				B8DB3BDD263DEA31004C18D7 /* ExamplePluginSwift.swift in Sources */,
 | 
			
		||||
				B8F0E10825E0199F00586F16 /* File.swift in Sources */,
 | 
			
		||||
				13B07FC11A68108700A75B9A /* main.m in Sources */,
 | 
			
		||||
				B8DB3BDE263DEA31004C18D7 /* ExamplePluginSwift.m in Sources */,
 | 
			
		||||
			);
 | 
			
		||||
			runOnlyForDeploymentPostprocessing = 0;
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,8 @@
 | 
			
		||||
#import "AppDelegate.h"
 | 
			
		||||
 | 
			
		||||
#import <React/RCTBundleURLProvider.h>
 | 
			
		||||
#import "VisionCameraExample-Swift.h"
 | 
			
		||||
#import <VisionCamera/FrameProcessorPlugin.h>
 | 
			
		||||
 | 
			
		||||
@implementation AppDelegate
 | 
			
		||||
 | 
			
		||||
@@ -11,6 +13,8 @@
 | 
			
		||||
  // They will be passed down to the ViewController used by React Native.
 | 
			
		||||
  self.initialProps = @{};
 | 
			
		||||
  
 | 
			
		||||
  [FrameProcessorPlugin registerPlugin:[[ExamplePluginSwift alloc] init]];
 | 
			
		||||
 | 
			
		||||
  return [super application:application didFinishLaunchingWithOptions:launchOptions];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,57 +6,27 @@
 | 
			
		||||
//  Copyright © 2021 mrousavy. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef FrameProcessorPlugin_h
 | 
			
		||||
#define FrameProcessorPlugin_h
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#import "FrameProcessorPluginRegistry.h"
 | 
			
		||||
#import "Frame.h"
 | 
			
		||||
 | 
			
		||||
@protocol FrameProcessorPluginBase
 | 
			
		||||
+ (id) callback:(Frame*)frame withArgs:(NSArray<id>*)args;
 | 
			
		||||
/// The base class for a Frame Processor Plugin which can be called synchronously from a JS Frame Processor.
 | 
			
		||||
///
 | 
			
		||||
/// Subclass this class in a Swift or Objective-C class and override the `callback:withArguments:` method, and
 | 
			
		||||
/// implement your Frame Processing there.
 | 
			
		||||
/// Then, in your App's startup (AppDelegate.m), call `FrameProcessorPluginBase.registerPlugin(YourNewPlugin())`
 | 
			
		||||
@interface FrameProcessorPlugin : NSObject
 | 
			
		||||
 | 
			
		||||
/// Get the name of the Frame Processor Plugin.
 | 
			
		||||
/// This will be exposed to JS under the `FrameProcessorPlugins` Proxy object.
 | 
			
		||||
- (NSString *)name;
 | 
			
		||||
 | 
			
		||||
/// The actual callback when calling this plugin. Any Frame Processing should be handled there.
 | 
			
		||||
/// Make sure your code is optimized, as this is a hot path.
 | 
			
		||||
- (id) callback:(Frame*)frame withArguments:(NSArray<id>*)arguments;
 | 
			
		||||
 | 
			
		||||
/// Register the given plugin in the Plugin Registry. This should be called on App Startup.
 | 
			
		||||
+ (void) registerPlugin:(FrameProcessorPlugin*)plugin;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#define VISION_CONCAT2(A, B) A##B
 | 
			
		||||
#define VISION_CONCAT(A, B) VISION_CONCAT2(A, B)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Use this Macro to register the given function as a Frame Processor.
 | 
			
		||||
 * * Make sure the given function is a C-style function with the following signature: static inline id callback(Frame* frame, NSArray* args)
 | 
			
		||||
 * * Make sure the given function's name is unique across other frame processor plugins
 | 
			
		||||
 * * Make sure your frame processor returns a Value that can be converted to JS
 | 
			
		||||
 * * Make sure to use this Macro in an @implementation, not @interface
 | 
			
		||||
 *
 | 
			
		||||
 * The JS function will have the same name as the given Objective-C function. It can be accessed through the FrameProcessorPlugins object exposed by VisionCamera.
 | 
			
		||||
 */
 | 
			
		||||
#define VISION_EXPORT_FRAME_PROCESSOR(frame_processor)                              \
 | 
			
		||||
                                                                                    \
 | 
			
		||||
+(void)load                                                                         \
 | 
			
		||||
{                                                                                   \
 | 
			
		||||
  [FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #frame_processor callback:^id(Frame* frame, NSArray<id>* args) { \
 | 
			
		||||
    return frame_processor(frame, args);                                           \
 | 
			
		||||
  }];                                                                               \
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Same as VISION_EXPORT_FRAME_PROCESSOR, but uses __attribute__((constructor)) for
 | 
			
		||||
 * registration. Useful for registering swift classes that forbids use of +(void)load.
 | 
			
		||||
 */
 | 
			
		||||
#define VISION_EXPORT_SWIFT_FRAME_PROCESSOR(name, objc_name) \
 | 
			
		||||
objc_name : NSObject<FrameProcessorPluginBase>                                      \
 | 
			
		||||
@end                                                                                \
 | 
			
		||||
                                                                                    \
 | 
			
		||||
@interface objc_name (FrameProcessorPlugin)                                         \
 | 
			
		||||
@end                                                                                \
 | 
			
		||||
@implementation objc_name (FrameProcessorPlugin)                                    \
 | 
			
		||||
                                                                                    \
 | 
			
		||||
+(void)load                                                                          \
 | 
			
		||||
{                                                                                   \
 | 
			
		||||
  [FrameProcessorPluginRegistry addFrameProcessorPlugin:@ #name callback:^id(Frame* frame, NSArray<id>* args) {    \
 | 
			
		||||
    return [objc_name callback:frame withArgs:args];                               \
 | 
			
		||||
  }];                                                                               \
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif /* FrameProcessorPlugin_h */
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								ios/Frame Processor/FrameProcessorPlugin.m
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								ios/Frame Processor/FrameProcessorPlugin.m
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
//
 | 
			
		||||
//  FrameProcessorPlugin.m
 | 
			
		||||
//  VisionCamera
 | 
			
		||||
//
 | 
			
		||||
//  Created by Marc Rousavy on 24.02.23.
 | 
			
		||||
//  Copyright © 2023 mrousavy. All rights reserved.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#import "FrameProcessorPlugin.h"
 | 
			
		||||
#import "FrameProcessorPluginRegistry.h"
 | 
			
		||||
 | 
			
		||||
@implementation FrameProcessorPlugin
 | 
			
		||||
 | 
			
		||||
- (NSString *)name {
 | 
			
		||||
  [NSException raise:NSInternalInconsistencyException
 | 
			
		||||
              format:@"Frame Processor Plugin \"%@\" does not override the `name` getter!", [self name]];
 | 
			
		||||
  return nil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
- (id)callback:(Frame *)frame withArguments:(NSArray<id> *)arguments {
 | 
			
		||||
  [NSException raise:NSInternalInconsistencyException
 | 
			
		||||
              format:@"Frame Processor Plugin \"%@\" does not override the `callback(frame:withArguments:)` method!", [self name]];
 | 
			
		||||
  return nil;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
+ (void)registerPlugin:(FrameProcessorPlugin *)plugin {
 | 
			
		||||
  [FrameProcessorPluginRegistry addFrameProcessorPlugin:plugin];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
@@ -10,12 +10,11 @@
 | 
			
		||||
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#import "Frame.h"
 | 
			
		||||
 | 
			
		||||
typedef id (^FrameProcessorPlugin) (Frame* frame, NSArray<id>* arguments);
 | 
			
		||||
#import "FrameProcessorPlugin.h"
 | 
			
		||||
 | 
			
		||||
@interface FrameProcessorPluginRegistry : NSObject
 | 
			
		||||
 | 
			
		||||
+ (NSMutableDictionary<NSString*, FrameProcessorPlugin>*)frameProcessorPlugins;
 | 
			
		||||
+ (void) addFrameProcessorPlugin:(NSString*)name callback:(FrameProcessorPlugin)callback;
 | 
			
		||||
+ (NSMutableDictionary<NSString*, FrameProcessorPlugin*>*)frameProcessorPlugins;
 | 
			
		||||
+ (void) addFrameProcessorPlugin:(FrameProcessorPlugin*)plugin;
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -11,19 +11,19 @@
 | 
			
		||||
 | 
			
		||||
@implementation FrameProcessorPluginRegistry
 | 
			
		||||
 | 
			
		||||
+ (NSMutableDictionary<NSString*, FrameProcessorPlugin>*)frameProcessorPlugins {
 | 
			
		||||
  static NSMutableDictionary<NSString*, FrameProcessorPlugin>* plugins = nil;
 | 
			
		||||
+ (NSMutableDictionary<NSString*, FrameProcessorPlugin*>*)frameProcessorPlugins {
 | 
			
		||||
  static NSMutableDictionary<NSString*, FrameProcessorPlugin*>* plugins = nil;
 | 
			
		||||
  if (plugins == nil) {
 | 
			
		||||
    plugins = [[NSMutableDictionary alloc] init];
 | 
			
		||||
  }
 | 
			
		||||
  return plugins;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
+ (void) addFrameProcessorPlugin:(NSString*)name callback:(FrameProcessorPlugin)callback {
 | 
			
		||||
  BOOL alreadyExists = [[FrameProcessorPluginRegistry frameProcessorPlugins] valueForKey:name] != nil;
 | 
			
		||||
  NSAssert(!alreadyExists, @"Tried to two Frame Processor Plugins with the same name! Either choose unique names, or remove the unused plugin.");
 | 
			
		||||
+ (void) addFrameProcessorPlugin:(FrameProcessorPlugin*)plugin {
 | 
			
		||||
  BOOL alreadyExists = [[FrameProcessorPluginRegistry frameProcessorPlugins] valueForKey:plugin.name] != nil;
 | 
			
		||||
  NSAssert(!alreadyExists, @"Tried to add a Frame Processor Plugin with a name that already exists! Either choose unique names, or remove the unused plugin. Name: %@", plugin.name);
 | 
			
		||||
 | 
			
		||||
  [[FrameProcessorPluginRegistry frameProcessorPlugins] setValue:callback forKey:name];
 | 
			
		||||
  [[FrameProcessorPluginRegistry frameProcessorPlugins] setValue:plugin forKey:plugin.name];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@end
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
#import <Foundation/Foundation.h>
 | 
			
		||||
#import "FrameProcessorRuntimeManager.h"
 | 
			
		||||
#import "FrameProcessorPluginRegistry.h"
 | 
			
		||||
#import "FrameProcessorPlugin.h"
 | 
			
		||||
#import "FrameHostObject.h"
 | 
			
		||||
 | 
			
		||||
#import <memory>
 | 
			
		||||
@@ -83,14 +84,14 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
 | 
			
		||||
    auto pluginName = [pluginKey UTF8String];
 | 
			
		||||
 | 
			
		||||
    NSLog(@"FrameProcessorBindings: Installing Frame Processor plugin \"%s\"...", pluginName);
 | 
			
		||||
    // Get the Plugin callback func
 | 
			
		||||
    FrameProcessorPlugin callback = [[FrameProcessorPluginRegistry frameProcessorPlugins] valueForKey:pluginKey];
 | 
			
		||||
    // Get the Plugin
 | 
			
		||||
    FrameProcessorPlugin* plugin = [[FrameProcessorPluginRegistry frameProcessorPlugins] valueForKey:pluginKey];
 | 
			
		||||
 | 
			
		||||
    // Create the JSI host function
 | 
			
		||||
    auto function = [callback, callInvoker](jsi::Runtime& runtime,
 | 
			
		||||
                                            const jsi::Value& thisValue,
 | 
			
		||||
                                            const jsi::Value* arguments,
 | 
			
		||||
                                            size_t count) -> jsi::Value {
 | 
			
		||||
    auto function = [plugin, callInvoker](jsi::Runtime& runtime,
 | 
			
		||||
                                          const jsi::Value& thisValue,
 | 
			
		||||
                                          const jsi::Value* arguments,
 | 
			
		||||
                                          size_t count) -> jsi::Value {
 | 
			
		||||
      // Get the first parameter, which is always the native Frame Host Object.
 | 
			
		||||
      auto frameHostObject = arguments[0].asObject(runtime).asHostObject(runtime);
 | 
			
		||||
      auto frame = static_cast<FrameHostObject*>(frameHostObject.get());
 | 
			
		||||
@@ -101,7 +102,7 @@ __attribute__((objc_runtime_name("_TtC12VisionCamera10CameraView")))
 | 
			
		||||
                                                 count - 1, // use smaller count
 | 
			
		||||
                                                 callInvoker);
 | 
			
		||||
      // Call the FP Plugin, which might return something.
 | 
			
		||||
      id result = callback(frame->frame, args);
 | 
			
		||||
      id result = [plugin callback:frame->frame withArguments:args];
 | 
			
		||||
 | 
			
		||||
      // Convert the return value (or null) to a JS Value and return it to JS
 | 
			
		||||
      return convertObjCObjectToJSIValue(runtime, result);
 | 
			
		||||
 
 | 
			
		||||
@@ -110,6 +110,7 @@
 | 
			
		||||
		B86DC970260E2D5200FB17B2 /* AVAudioSession+trySetAllowHaptics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAudioSession+trySetAllowHaptics.swift"; sourceTree = "<group>"; };
 | 
			
		||||
		B86DC973260E310600FB17B2 /* CameraView+AVAudioSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+AVAudioSession.swift"; sourceTree = "<group>"; };
 | 
			
		||||
		B86DC976260E315100FB17B2 /* CameraView+AVCaptureSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CameraView+AVCaptureSession.swift"; sourceTree = "<group>"; };
 | 
			
		||||
		B86F803429A90DBD00205E48 /* FrameProcessorPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FrameProcessorPlugin.m; sourceTree = "<group>"; };
 | 
			
		||||
		B8805065266798AB00EAD7F2 /* JSConsoleHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSConsoleHelper.h; sourceTree = "<group>"; };
 | 
			
		||||
		B8805066266798B600EAD7F2 /* JSConsoleHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = JSConsoleHelper.mm; sourceTree = "<group>"; };
 | 
			
		||||
		B882720F26AEB1A100B14107 /* AVCaptureConnection+setInterfaceOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVCaptureConnection+setInterfaceOrientation.swift"; sourceTree = "<group>"; };
 | 
			
		||||
@@ -285,6 +286,7 @@
 | 
			
		||||
				B80C0DFE260BDD97001699AB /* FrameProcessorPluginRegistry.h */,
 | 
			
		||||
				B80C0DFF260BDDF7001699AB /* FrameProcessorPluginRegistry.mm */,
 | 
			
		||||
				B88873E5263D46C7008B1D0E /* FrameProcessorPlugin.h */,
 | 
			
		||||
				B86F803429A90DBD00205E48 /* FrameProcessorPlugin.m */,
 | 
			
		||||
			);
 | 
			
		||||
			path = "Frame Processor";
 | 
			
		||||
			sourceTree = "<group>";
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user