@imgly/camera-react-native 1.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/IMGLYCameraModule.podspec +21 -0
  2. package/LICENSE.md +137 -0
  3. package/README.md +26 -0
  4. package/android/.gradle/8.9/checksums/checksums.lock +0 -0
  5. package/android/.gradle/8.9/dependencies-accessors/gc.properties +0 -0
  6. package/android/.gradle/8.9/fileChanges/last-build.bin +0 -0
  7. package/android/.gradle/8.9/fileHashes/fileHashes.lock +0 -0
  8. package/android/.gradle/8.9/gc.properties +0 -0
  9. package/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock +0 -0
  10. package/android/.gradle/buildOutputCleanup/cache.properties +2 -0
  11. package/android/.gradle/vcs-1/gc.properties +0 -0
  12. package/android/build.gradle +64 -0
  13. package/android/src/main/kotlin/ly/img/camera/reactnative/module/IMGLYCameraModule.kt +249 -0
  14. package/android/src/main/kotlin/ly/img/camera/reactnative/module/IMGLYCameraPackage.kt +35 -0
  15. package/android/src/main/kotlin/ly/img/camera/reactnative/module/model/CameraResult.kt +118 -0
  16. package/android/src/main/kotlin/ly/img/camera/reactnative/module/model/CameraSettings.kt +46 -0
  17. package/android/src/main/kotlin/ly/img/camera/reactnative/module/model/Recording.kt +83 -0
  18. package/android/src/newarch/IMGLYCameraModuleSpec.kt +7 -0
  19. package/android/src/oldarch/IMGLYEditorModuleSpec.kt +17 -0
  20. package/index.d.ts +3 -0
  21. package/index.js +3 -0
  22. package/ios/IMGLYCameraModule.h +11 -0
  23. package/ios/IMGLYCameraModule.mm +61 -0
  24. package/ios/IMGLYCameraModule.swift +70 -0
  25. package/ios/Model/CameraBuilder.swift +94 -0
  26. package/ios/Model/CameraResult.swift +181 -0
  27. package/ios/Model/CameraSettings.swift +24 -0
  28. package/package.json +46 -0
  29. package/src/NativeIMGLYCamera.ts +191 -0
@@ -0,0 +1,118 @@
1
+ package ly.img.camera.reactnative.module.model
2
+
3
+ import android.os.Parcel
4
+ import android.os.Parcelable
5
+ import ly.img.camera.reactnative.module.model.CameraResult.CREATOR.readMap
6
+ import ly.img.camera.reactnative.module.model.CameraResult.CREATOR.writeMap
7
+
8
+ /**
9
+ * The result type for a camera recording session in standard mode.
10
+ *
11
+ * @property recordings The recordings of the camera session.
12
+ */
13
+ data class CameraRecording(
14
+ val recordings: List<Recording>?,
15
+ ) : Parcelable {
16
+ /**
17
+ * Creates a new instance from a given [Parcel].
18
+ * @param parcel The [Parcel].
19
+ */
20
+ constructor(parcel: Parcel) : this(
21
+ parcel.createTypedArrayList(Recording),
22
+ )
23
+ constructor(record: ly.img.camera.core.CameraResult.Record) : this(
24
+ record.recordings.map { Recording(it) },
25
+ )
26
+
27
+ override fun writeToParcel(
28
+ parcel: Parcel,
29
+ flags: Int,
30
+ ) {
31
+ parcel.writeTypedList(recordings)
32
+ }
33
+
34
+ override fun describeContents(): Int = 0
35
+
36
+ companion object CREATOR : Parcelable.Creator<CameraResult> {
37
+ override fun createFromParcel(parcel: Parcel): CameraResult = CameraResult(parcel)
38
+
39
+ override fun newArray(size: Int): Array<CameraResult?> = arrayOfNulls(size)
40
+ }
41
+
42
+ fun toMap(): Map<String, Any?> =
43
+ mapOf(
44
+ "recordings" to recordings?.map { it.toMap() },
45
+ )
46
+ }
47
+
48
+ /**
49
+ * A class representing the result of a camera session.
50
+ *
51
+ * @property recording The result for the camera session in standard mode.
52
+ * @property metadata The associated metadata.
53
+ */
54
+ data class CameraResult(
55
+ val recording: CameraRecording?,
56
+ val metadata: Map<String, Any> = emptyMap(),
57
+ ) : Parcelable {
58
+ /**
59
+ * Creates a new instance from a given [Parcel].
60
+ * @param parcel The [Parcel].
61
+ */
62
+ constructor(parcel: Parcel) : this(
63
+ CameraRecording(parcel),
64
+ readMap(parcel),
65
+ )
66
+ constructor(record: ly.img.camera.core.CameraResult.Record) : this(
67
+ CameraRecording(record),
68
+ emptyMap(),
69
+ )
70
+
71
+ override fun writeToParcel(
72
+ parcel: Parcel,
73
+ flags: Int,
74
+ ) {
75
+ recording?.writeToParcel(parcel, flags)
76
+ writeMap(parcel, metadata, flags)
77
+ }
78
+
79
+ override fun describeContents(): Int = 0
80
+
81
+ companion object CREATOR : Parcelable.Creator<CameraResult> {
82
+ override fun createFromParcel(parcel: Parcel): CameraResult = CameraResult(parcel)
83
+
84
+ override fun newArray(size: Int): Array<CameraResult?> = arrayOfNulls(size)
85
+
86
+ private fun writeMap(
87
+ parcel: Parcel,
88
+ map: Map<String, Any>,
89
+ flags: Int,
90
+ ) {
91
+ parcel.writeInt(map.size)
92
+ for ((key, value) in map) {
93
+ parcel.writeString(key)
94
+ parcel.writeValue(value)
95
+ }
96
+ }
97
+
98
+ private fun readMap(parcel: Parcel): Map<String, Any> {
99
+ val size = parcel.readInt()
100
+ val map = mutableMapOf<String, Any>()
101
+ repeat(size) {
102
+ val key = parcel.readString()
103
+ val value = parcel.readValue(this::class.java.classLoader)
104
+ key ?: throw (IllegalStateException("Key must not be null."))
105
+ value ?: throw (IllegalStateException("Value must not be null."))
106
+
107
+ map[key] = value
108
+ }
109
+ return map
110
+ }
111
+ }
112
+
113
+ fun toMap(): Map<String, Any?> =
114
+ mapOf(
115
+ "recording" to recording?.toMap(),
116
+ "metadata" to metadata,
117
+ )
118
+ }
@@ -0,0 +1,46 @@
1
+ package ly.img.camera.reactnative.module.model
2
+
3
+ import android.os.Parcel
4
+ import android.os.Parcelable
5
+
6
+ /**
7
+ * A class containing all necessary information to configure the camera.
8
+ *
9
+ * @property license The license key.
10
+ * @property userId The id of the current user.
11
+ */
12
+ data class CameraSettings(
13
+ val license: String,
14
+ val userId: String?,
15
+ ) : Parcelable {
16
+ /**
17
+ * Creates a new instance from a given [Parcel].
18
+ * @param parcel The [Parcel].
19
+ */
20
+ constructor(parcel: Parcel) : this(
21
+ parcel.readString() ?: throw (Exception("Missing value for key 'license.'")),
22
+ parcel.readString(),
23
+ )
24
+
25
+ override fun writeToParcel(
26
+ parcel: Parcel,
27
+ flags: Int,
28
+ ) {
29
+ parcel.writeString(license)
30
+ parcel.writeString(userId)
31
+ }
32
+
33
+ override fun describeContents(): Int = 0
34
+
35
+ companion object CREATOR : Parcelable.Creator<CameraSettings> {
36
+ override fun createFromParcel(parcel: Parcel): CameraSettings = CameraSettings(parcel)
37
+
38
+ override fun newArray(size: Int): Array<CameraSettings?> = arrayOfNulls(size)
39
+
40
+ fun createFromMap(map: Map<String, Any>): CameraSettings {
41
+ val license = map["license"] as? String ?: throw (Exception("Missing value for key 'license.'"))
42
+ val userId = map["userId"] as? String
43
+ return CameraSettings(license, userId)
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,83 @@
1
+ package ly.img.camera.reactnative.module.model
2
+
3
+ import android.net.Uri
4
+ import android.os.Parcel
5
+ import android.os.Parcelable
6
+ import kotlin.time.Duration
7
+ import kotlin.time.Duration.Companion.milliseconds
8
+
9
+ /**
10
+ * A camera recording.
11
+ *
12
+ * @property videos contains one or two [Video]s, for single camera output or dual camera output respectively
13
+ * @property duration the duration of the recording
14
+ */
15
+ data class Recording(
16
+ val videos: List<Video>,
17
+ val duration: Duration,
18
+ ) : Parcelable {
19
+ constructor(parcel: Parcel) : this(
20
+ videos = parcel.createTypedArrayList(Video)!!,
21
+ duration = parcel.readLong().milliseconds,
22
+ )
23
+ constructor(recording: ly.img.camera.core.Recording) : this(
24
+ recording.videos.map { Video(it) },
25
+ recording.duration,
26
+ )
27
+
28
+ override fun writeToParcel(
29
+ parcel: Parcel,
30
+ flags: Int,
31
+ ) {
32
+ parcel.writeTypedList(videos)
33
+ parcel.writeLong(duration.inWholeMilliseconds)
34
+ }
35
+
36
+ override fun describeContents(): Int = 0
37
+
38
+ companion object CREATOR : Parcelable.Creator<Recording> {
39
+ override fun createFromParcel(parcel: Parcel): Recording = Recording(parcel)
40
+
41
+ override fun newArray(size: Int): Array<Recording?> = arrayOfNulls(size)
42
+ }
43
+
44
+ fun toMap(): Map<String, Any?> =
45
+ mapOf(
46
+ "videos" to videos.map { it.toMap() },
47
+ "duration" to duration.inWholeMilliseconds.toDouble(),
48
+ )
49
+ }
50
+
51
+ /**
52
+ * A video in a [Recording].
53
+ *
54
+ * @param uri the uri of the video
55
+ */
56
+ data class Video(
57
+ val uri: Uri,
58
+ ) : Parcelable {
59
+ constructor(parcel: Parcel) : this(uri = parcel.readParcelable(Uri::class.java.classLoader)!!)
60
+ constructor(video: ly.img.camera.core.Video) : this(
61
+ video.uri,
62
+ )
63
+
64
+ override fun writeToParcel(
65
+ parcel: Parcel,
66
+ flags: Int,
67
+ ) {
68
+ parcel.writeParcelable(uri, flags)
69
+ }
70
+
71
+ override fun describeContents(): Int = 0
72
+
73
+ companion object CREATOR : Parcelable.Creator<Video> {
74
+ override fun createFromParcel(parcel: Parcel): Video = Video(parcel)
75
+
76
+ override fun newArray(size: Int): Array<Video?> = arrayOfNulls(size)
77
+ }
78
+
79
+ fun toMap(): Map<String, Any?> =
80
+ mapOf(
81
+ "uri" to uri.toString(),
82
+ )
83
+ }
@@ -0,0 +1,7 @@
1
+ package ly.img.camera.reactnative.module
2
+
3
+ import com.facebook.react.bridge.ReactApplicationContext
4
+
5
+ abstract class IMGLYCameraModuleSpec internal constructor(
6
+ reactContext: ReactApplicationContext,
7
+ ) : NativeIMGLYCameraSpec(reactContext)
@@ -0,0 +1,17 @@
1
+ package ly.img.camera.reactnative.module
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.ReactApplicationContext
5
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
6
+ import com.facebook.react.bridge.ReadableMap
7
+
8
+ abstract class IMGLYCameraModuleSpec internal constructor(
9
+ context: ReactApplicationContext,
10
+ ) : ReactContextBaseJavaModule(context) {
11
+ abstract fun openCamera(
12
+ settings: ReadableMap?,
13
+ video: String?,
14
+ metadata: ReadableMap?,
15
+ promise: Promise?,
16
+ )
17
+ }
package/index.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './src/NativeIMGLYCamera';
2
+ export { default } from './src/NativeIMGLYCamera';
3
+
package/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from './src/NativeIMGLYCamera';
2
+ export { default } from './src/NativeIMGLYCamera';
3
+
@@ -0,0 +1,11 @@
1
+ #ifdef RCT_NEW_ARCH_ENABLED
2
+ #import <IMGLYCameraModuleSpec/IMGLYCameraModuleSpec.h>
3
+
4
+ @interface IMGLYCameraModule : NSObject <NativeIMGLYCameraSpec>
5
+ #else
6
+ #import <React/RCTBridgeModule.h>
7
+
8
+ @interface IMGLYCameraModule : NSObject <RCTBridgeModule>
9
+ #endif
10
+
11
+ @end
@@ -0,0 +1,61 @@
1
+ #import "IMGLYCameraModule.h"
2
+ #import "IMGLYCameraModule/IMGLYCameraModule-Swift.h"
3
+
4
+ @implementation IMGLYCameraModule
5
+
6
+ RCT_EXPORT_MODULE(IMGLYCamera)
7
+
8
+ #ifdef RCT_NEW_ARCH_ENABLED
9
+ - (void)openCamera:(JS::NativeIMGLYCamera::CameraSettings&)settings
10
+ video:(NSString*)video
11
+ metadata:(NSDictionary*)metadata
12
+ resolve:(RCTPromiseResolveBlock)resolve
13
+ reject:(RCTPromiseRejectBlock)reject {
14
+ NSMutableDictionary* convertedSettings = [@{
15
+ @"license" : settings.license(),
16
+ } mutableCopy];
17
+ if (settings.userId() != nil) {
18
+ convertedSettings[@"userId"] = settings.userId();
19
+ }
20
+ [self open:convertedSettings video:video metadata:metadata resolve:resolve reject:reject];
21
+ }
22
+
23
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
24
+ (const facebook::react::ObjCTurboModule::InitParams&)params {
25
+ return std::make_shared<facebook::react::NativeIMGLYCameraSpecJSI>(params);
26
+ }
27
+
28
+ #else
29
+ RCT_EXPORT_METHOD(openCamera
30
+ : (nonnull NSDictionary*)settings video
31
+ : (nullable NSString*)video metadata
32
+ : (NSDictionary*)metadata resolve
33
+ : (RCTPromiseResolveBlock)resolve reject
34
+ : (RCTPromiseRejectBlock)reject) {
35
+ [self open:settings video:video metadata:metadata resolve:resolve reject:reject];
36
+ }
37
+ #endif
38
+
39
+ - (void)open:(nonnull NSDictionary*)settings
40
+ video:(nullable NSString*)video
41
+ metadata:(NSDictionary*)metadata
42
+ resolve:(RCTPromiseResolveBlock)resolve
43
+ reject:(RCTPromiseRejectBlock)reject {
44
+ CameraSettings* cameraSettings = [CameraSettings fromDictionary:settings];
45
+ dispatch_async(dispatch_get_main_queue(), ^{
46
+ [[IMGLYCameraModuleSwiftAdapter shared]
47
+ openCameraWithSettings:cameraSettings
48
+ video:video
49
+ metadata:metadata
50
+ completion:^(CameraResult* _Nullable result, NSError* _Nullable error) {
51
+ if (result != nil) {
52
+ resolve([result toDictionary]);
53
+ } else if (error != nil) {
54
+ reject(@"E_RECORDING_FAILED", @"The recording failed due to: ", error);
55
+ } else {
56
+ resolve([NSNull null]);
57
+ }
58
+ }];
59
+ });
60
+ }
61
+ @end
@@ -0,0 +1,70 @@
1
+ import IMGLYCamera
2
+ import UIKit
3
+
4
+ /// The iOS implementation for the `@imgly/camera-react-native` native React Native module.
5
+ @objc(IMGLYCameraModuleSwiftAdapter) @objcMembers public class IMGLYCameraModuleSwiftAdapter: NSObject {
6
+ // MARK: - Typealias
7
+
8
+ /// A closure to specify an `CameraBuilder.Builder` based on a given `metadata`.
9
+ public typealias CameraBuilderClosure = (_ metadata: [String: Any]?) -> CameraBuilder.Builder
10
+
11
+ // MARK: - Properties
12
+
13
+ /// The shared instance.
14
+ public static var shared = IMGLYCameraModuleSwiftAdapter()
15
+
16
+ /// The `CameraBuilderClosure` to use for UI creation.
17
+ public var cameraBuilderClosure: CameraBuilderClosure?
18
+
19
+ /// The `UIViewController` hosting the editor.
20
+ private var presentationController: UIViewController?
21
+
22
+ /// Opens the camera.
23
+ /// - Parameters:
24
+ /// - settings: The `CameraSettings` containing all relevant information for the editor.
25
+ /// - video: The source of the video to react to.
26
+ /// - metadata: Any custom metadata used for the `CameraBuilder.Builder`.
27
+ /// - completion: The completion handler to execute once the camera failed, cancelled or exported.
28
+ public func openCamera(
29
+ settings: CameraSettings,
30
+ video: String?,
31
+ metadata: [String: Any]?,
32
+ completion: @escaping (_ result: CameraResult?, _ error: NSError?) -> Void
33
+ ) {
34
+ var videoURL: URL?
35
+ if let video {
36
+ if let sourceUrl = URL(string: video), sourceUrl.scheme != nil {
37
+ videoURL = sourceUrl
38
+ } else {
39
+ completion(nil, nil)
40
+ }
41
+ }
42
+
43
+ let builder = cameraBuilderClosure?(metadata) ?? CameraBuilder.default()
44
+ presentationController = builder(settings, videoURL, metadata) { [weak self] result in
45
+ DispatchQueue.main.async {
46
+ switch result {
47
+ case let .success(artifact):
48
+ completion(artifact, nil)
49
+ case let .failure(error):
50
+ switch error {
51
+ case CameraError.cancelled:
52
+ completion(nil, nil)
53
+ default:
54
+ completion(nil, error as NSError)
55
+ }
56
+ }
57
+ self?.presentationController?.presentingViewController?.dismiss(animated: true)
58
+ }
59
+ }
60
+ presentationController?.modalPresentationStyle = .fullScreen
61
+ if let presentationController, let windowScene = UIApplication.shared.connectedScenes
62
+ .filter({ $0.activationState == .foregroundActive })
63
+ .first as? UIWindowScene {
64
+ if let rootViewController = windowScene.windows
65
+ .filter(\.isKeyWindow).first?.rootViewController {
66
+ rootViewController.present(presentationController, animated: true, completion: nil)
67
+ }
68
+ }
69
+ }
70
+ }
@@ -0,0 +1,94 @@
1
+ import IMGLYCamera
2
+ import SwiftUI
3
+
4
+ public enum CameraBuilder {
5
+ /// A closure used to return a `Result<CameraResult?, Error>` from the camera.
6
+ public typealias CameraBuilderResult = (Result<CameraResult?, Error>) -> Void
7
+
8
+ /// Closure allowing to return a custom UI based on various input.
9
+ /// - Parameters:
10
+ /// - settings: The `CameraSettings`.
11
+ /// - url: The `URL` of the video to react to.
12
+ /// - metadata: The metadata.
13
+ /// - result: The `CameraBuilderResult` used to return a result.
14
+ /// - Returns: A `UIViewController`.
15
+ public typealias Builder = (
16
+ _ settings: CameraSettings,
17
+ _ url: URL?,
18
+ _ metadata: [String: Any]?,
19
+ _ result: @escaping CameraBuilderResult
20
+ ) -> UIViewController
21
+
22
+ /// The default camera implementation.
23
+ public static func `default`() -> Builder {
24
+ { settings, url, _, result in
25
+ @ViewBuilder func camera(settings: CameraSettings, result: @escaping CameraBuilderResult) -> some View {
26
+ ModalCamera(settings: settings, result: result, url: url)
27
+ }
28
+ return UIHostingController(rootView: camera(settings: settings, result: result))
29
+ }
30
+ }
31
+
32
+ /// A custom camera implementation.
33
+ /// - Parameter contentProvider: Used to provide a custom view.
34
+ /// - Returns: A `Builder` providing the custom view.
35
+ public static func custom(_ contentProvider: @escaping (
36
+ _ settings: CameraSettings,
37
+ _ url: URL?,
38
+ _ metadata: [String: Any]?,
39
+ _ result: @escaping CameraBuilderResult
40
+ ) -> some View) -> Builder {
41
+ { settings, preset, metadata, result in
42
+ let hostingController = UIHostingController(rootView: contentProvider(settings, preset, metadata, result))
43
+ return hostingController
44
+ }
45
+ }
46
+ }
47
+
48
+ extension CameraBuilder {
49
+ /// Generates `EngineSettings` based on an `CameraSettings`.
50
+ /// - Parameter settings: The `CameraSettings`.
51
+ /// - Returns: The derived `EngineSettings`.
52
+ private static func engineSettings(for settings: CameraSettings) -> EngineSettings {
53
+ EngineSettings(license: settings.license, userID: settings.userId)
54
+ }
55
+
56
+ /// A modal camera implementation.
57
+ public struct ModalCamera: View {
58
+ private let settings: CameraSettings
59
+ private let result: CameraBuilderResult
60
+ private let url: URL?
61
+ private let onDismiss: (Result<IMGLYCamera.CameraResult, CameraError>) -> Void
62
+
63
+ public init(settings: CameraSettings, result: @escaping CameraBuilderResult, url: URL? = nil) {
64
+ self.settings = settings
65
+ self.result = result
66
+ self.url = url
67
+
68
+ onDismiss = { cameraResult in
69
+ switch cameraResult {
70
+ case let .success(.reaction(video: recording, reaction: reaction)):
71
+ let reaction = CameraReaction(video: recording, recordings: reaction)
72
+ result(.success(CameraResult(reaction: reaction)))
73
+ case let .success(.recording(recording)):
74
+ let recording = CameraRecording(recordings: recording)
75
+ result(.success(CameraResult(recording: recording)))
76
+ case let .failure(error):
77
+ result(.failure(error))
78
+ }
79
+ }
80
+ }
81
+
82
+ public var body: some View {
83
+ if let url {
84
+ Camera(
85
+ engineSettings(for: settings),
86
+ mode: .reaction(.horizontal, video: url, positionsSwapped: false),
87
+ onDismiss: onDismiss
88
+ )
89
+ } else {
90
+ Camera(engineSettings(for: settings), onDismiss: onDismiss)
91
+ }
92
+ }
93
+ }
94
+ }