react-native-compressor 1.19.0 → 2.0.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 (133) hide show
  1. package/android/CMakeLists.txt +27 -0
  2. package/android/build.gradle +56 -58
  3. package/android/gradle.properties +5 -5
  4. package/android/src/main/AndroidManifestNew.xml +3 -0
  5. package/android/src/main/cpp/cpp-adapter.cpp +8 -0
  6. package/android/src/main/java/com/margelo/nitro/compressor/HybridCompressor.kt +213 -0
  7. package/android/src/main/java/com/reactnativecompressor/NitroCompressorPackage.kt +30 -0
  8. package/android/src/main/java/com/reactnativecompressor/NitroPromiseAdapter.kt +55 -0
  9. package/android/src/main/java/com/reactnativecompressor/Utils/EventEmitterHandler.kt +49 -45
  10. package/android/src/main/java/com/reactnativecompressor/Utils/Utils.kt +1 -1
  11. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt +13 -22
  12. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/utils/CompressorUtils.kt +72 -0
  13. package/android/src/main/java/com/reactnativecompressor/Video/VideoCompressorHelper.kt +4 -1
  14. package/ios/Audio/FormatConverter/FormatConverter+Compressed.swift +1 -1
  15. package/ios/Audio/FormatConverter/FormatConverter+Utilities.swift +1 -1
  16. package/ios/Audio/FormatConverter/FormatConverter.swift +5 -3
  17. package/ios/HybridCompressor.swift +247 -0
  18. package/ios/Image/ImageCompressor.swift +8 -2
  19. package/ios/Utils/EventEmitterHandler.swift +56 -34
  20. package/ios/Video/NextLevelSessionExporter.swift +43 -8
  21. package/ios/Video/VideoMain.swift +17 -3
  22. package/lib/commonjs/Audio/index.js +1 -1
  23. package/lib/commonjs/Audio/index.js.map +1 -1
  24. package/lib/commonjs/Image/index.js +2 -22
  25. package/lib/commonjs/Image/index.js.map +1 -1
  26. package/lib/commonjs/Main.js +8 -8
  27. package/lib/commonjs/Main.js.map +1 -1
  28. package/lib/commonjs/Video/index.js +25 -65
  29. package/lib/commonjs/Video/index.js.map +1 -1
  30. package/lib/commonjs/specs/Compressor.nitro.js +6 -0
  31. package/lib/commonjs/specs/Compressor.nitro.js.map +1 -0
  32. package/lib/commonjs/utils/Downloader.js +8 -24
  33. package/lib/commonjs/utils/Downloader.js.map +1 -1
  34. package/lib/commonjs/utils/Uploader.js +3 -18
  35. package/lib/commonjs/utils/Uploader.js.map +1 -1
  36. package/lib/commonjs/utils/helpers.js +18 -1
  37. package/lib/commonjs/utils/helpers.js.map +1 -1
  38. package/lib/commonjs/utils/index.js +13 -13
  39. package/lib/commonjs/utils/index.js.map +1 -1
  40. package/lib/module/Audio/index.js +2 -2
  41. package/lib/module/Audio/index.js.map +1 -1
  42. package/lib/module/Image/index.js +3 -23
  43. package/lib/module/Image/index.js.map +1 -1
  44. package/lib/module/Main.js +9 -9
  45. package/lib/module/Main.js.map +1 -1
  46. package/lib/module/Video/index.js +26 -66
  47. package/lib/module/Video/index.js.map +1 -1
  48. package/lib/module/specs/Compressor.nitro.js +4 -0
  49. package/lib/module/specs/Compressor.nitro.js.map +1 -0
  50. package/lib/module/utils/Downloader.js +10 -26
  51. package/lib/module/utils/Downloader.js.map +1 -1
  52. package/lib/module/utils/Uploader.js +5 -20
  53. package/lib/module/utils/Uploader.js.map +1 -1
  54. package/lib/module/utils/helpers.js +16 -0
  55. package/lib/module/utils/helpers.js.map +1 -1
  56. package/lib/module/utils/index.js +2 -1
  57. package/lib/module/utils/index.js.map +1 -1
  58. package/lib/typescript/src/Image/index.d.ts.map +1 -1
  59. package/lib/typescript/src/Main.d.ts +2 -1
  60. package/lib/typescript/src/Main.d.ts.map +1 -1
  61. package/lib/typescript/src/Video/index.d.ts +1 -1
  62. package/lib/typescript/src/Video/index.d.ts.map +1 -1
  63. package/lib/typescript/src/index.d.ts.map +1 -1
  64. package/lib/typescript/src/specs/Compressor.nitro.d.ts +44 -0
  65. package/lib/typescript/src/specs/Compressor.nitro.d.ts.map +1 -0
  66. package/lib/typescript/src/utils/Downloader.d.ts.map +1 -1
  67. package/lib/typescript/src/utils/Uploader.d.ts.map +1 -1
  68. package/lib/typescript/src/utils/helpers.d.ts +7 -0
  69. package/lib/typescript/src/utils/helpers.d.ts.map +1 -1
  70. package/lib/typescript/src/utils/index.d.ts.map +1 -1
  71. package/nitrogen/generated/android/NitroCompressor+autolinking.cmake +81 -0
  72. package/nitrogen/generated/android/NitroCompressor+autolinking.gradle +27 -0
  73. package/nitrogen/generated/android/NitroCompressorOnLoad.cpp +60 -0
  74. package/nitrogen/generated/android/NitroCompressorOnLoad.hpp +34 -0
  75. package/nitrogen/generated/android/c++/JFunc_void.hpp +75 -0
  76. package/nitrogen/generated/android/c++/JFunc_void_double.hpp +75 -0
  77. package/nitrogen/generated/android/c++/JFunc_void_double_double.hpp +75 -0
  78. package/nitrogen/generated/android/c++/JHybridCompressorSpec.cpp +293 -0
  79. package/nitrogen/generated/android/c++/JHybridCompressorSpec.hpp +78 -0
  80. package/nitrogen/generated/android/c++/JVideoThumbnailResult.hpp +73 -0
  81. package/nitrogen/generated/android/kotlin/com/margelo/nitro/compressor/Func_void.kt +80 -0
  82. package/nitrogen/generated/android/kotlin/com/margelo/nitro/compressor/Func_void_double.kt +80 -0
  83. package/nitrogen/generated/android/kotlin/com/margelo/nitro/compressor/Func_void_double_double.kt +80 -0
  84. package/nitrogen/generated/android/kotlin/com/margelo/nitro/compressor/HybridCompressorSpec.kt +141 -0
  85. package/nitrogen/generated/android/kotlin/com/margelo/nitro/compressor/NitroCompressorOnLoad.kt +35 -0
  86. package/nitrogen/generated/android/kotlin/com/margelo/nitro/compressor/VideoThumbnailResult.kt +71 -0
  87. package/nitrogen/generated/ios/c++/HybridCompressorSpecSwift.cpp +11 -0
  88. package/nitrogen/generated/ios/c++/HybridCompressorSpecSwift.hpp +204 -0
  89. package/nitrogen/generated/ios/react_native_compressor+autolinking.rb +62 -0
  90. package/nitrogen/generated/ios/react_native_compressor-Swift-Cxx-Bridge.cpp +89 -0
  91. package/nitrogen/generated/ios/react_native_compressor-Swift-Cxx-Bridge.hpp +337 -0
  92. package/nitrogen/generated/ios/react_native_compressor-Swift-Cxx-Umbrella.hpp +51 -0
  93. package/nitrogen/generated/ios/react_native_compressorAutolinking.mm +33 -0
  94. package/nitrogen/generated/ios/react_native_compressorAutolinking.swift +26 -0
  95. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  96. package/nitrogen/generated/ios/swift/Func_void_VideoThumbnailResult.swift +46 -0
  97. package/nitrogen/generated/ios/swift/Func_void_double.swift +46 -0
  98. package/nitrogen/generated/ios/swift/Func_void_double_double.swift +46 -0
  99. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  100. package/nitrogen/generated/ios/swift/Func_void_std__shared_ptr_AnyMap_.swift +46 -0
  101. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  102. package/nitrogen/generated/ios/swift/HybridCompressorSpec.swift +70 -0
  103. package/nitrogen/generated/ios/swift/HybridCompressorSpec_cxx.swift +493 -0
  104. package/nitrogen/generated/ios/swift/VideoThumbnailResult.swift +49 -0
  105. package/nitrogen/generated/shared/c++/HybridCompressorSpec.cpp +36 -0
  106. package/nitrogen/generated/shared/c++/HybridCompressorSpec.hpp +83 -0
  107. package/nitrogen/generated/shared/c++/VideoThumbnailResult.hpp +99 -0
  108. package/package.json +8 -12
  109. package/react-native-compressor.podspec +6 -24
  110. package/src/Audio/index.tsx +2 -2
  111. package/src/Image/index.tsx +3 -26
  112. package/src/Main.tsx +14 -16
  113. package/src/Video/index.tsx +25 -77
  114. package/src/specs/Compressor.nitro.ts +56 -0
  115. package/src/utils/Downloader.tsx +8 -27
  116. package/src/utils/Uploader.tsx +12 -24
  117. package/src/utils/helpers.ts +18 -0
  118. package/src/utils/index.tsx +3 -2
  119. package/android/src/main/java/com/reactnativecompressor/CompressorModule.kt +0 -180
  120. package/android/src/main/java/com/reactnativecompressor/CompressorPackage.kt +0 -34
  121. package/android/src/newarch/CompressorSpec.kt +0 -5
  122. package/android/src/oldarch/CompressorSpec.kt +0 -36
  123. package/ios/Compressor-Bridging-Header.h +0 -6
  124. package/ios/Compressor.h +0 -5
  125. package/ios/Compressor.mm +0 -82
  126. package/ios/CompressorManager.swift +0 -117
  127. package/lib/commonjs/Spec/NativeCompressor.js +0 -9
  128. package/lib/commonjs/Spec/NativeCompressor.js.map +0 -1
  129. package/lib/module/Spec/NativeCompressor.js +0 -5
  130. package/lib/module/Spec/NativeCompressor.js.map +0 -1
  131. package/lib/typescript/src/Spec/NativeCompressor.d.ts +0 -30
  132. package/lib/typescript/src/Spec/NativeCompressor.d.ts.map +0 -1
  133. package/src/Spec/NativeCompressor.ts +0 -41
@@ -0,0 +1,27 @@
1
+ project(NitroCompressor)
2
+ cmake_minimum_required(VERSION 3.9.0)
3
+
4
+ set(PACKAGE_NAME NitroCompressor)
5
+ set(CMAKE_VERBOSE_MAKEFILE ON)
6
+ set(CMAKE_CXX_STANDARD 20)
7
+
8
+ # Define the C++ library and add all of our own sources.
9
+ add_library(${PACKAGE_NAME} SHARED
10
+ src/main/cpp/cpp-adapter.cpp
11
+ )
12
+
13
+ # Add all files generated by Nitrogen (sources, headers, prefab links).
14
+ include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/NitroCompressor+autolinking.cmake)
15
+
16
+ # Local includes
17
+ include_directories(
18
+ "src/main/cpp"
19
+ )
20
+
21
+ find_library(LOG_LIB log)
22
+
23
+ target_link_libraries(
24
+ ${PACKAGE_NAME}
25
+ ${LOG_LIB}
26
+ android
27
+ )
@@ -1,5 +1,5 @@
1
1
  buildscript {
2
- // Buildscript is evaluated before everything else so we can't use getExtOrDefault
2
+ // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3
3
  def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["Compressor_kotlinVersion"]
4
4
 
5
5
  repositories {
@@ -8,25 +8,21 @@ buildscript {
8
8
  }
9
9
 
10
10
  dependencies {
11
- classpath "com.android.tools.build:gradle:7.2.1"
11
+ classpath "com.android.tools.build:gradle:9.2.1"
12
12
  // noinspection DifferentKotlinGradleVersion
13
13
  classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14
14
  }
15
15
  }
16
16
 
17
- def isNewArchitectureEnabled() {
18
- return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
17
+ def reactNativeArchitectures() {
18
+ def value = rootProject.getProperties().get("reactNativeArchitectures")
19
+ return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
19
20
  }
20
21
 
21
22
  apply plugin: "com.android.library"
22
23
  apply plugin: "kotlin-android"
23
-
24
-
25
- def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') }
26
-
27
- if (isNewArchitectureEnabled()) {
28
- apply plugin: "com.facebook.react"
29
- }
24
+ // Nitrogen: registers the generated Kotlin sources with this Gradle project.
25
+ apply from: '../nitrogen/generated/android/NitroCompressor+autolinking.gradle'
30
26
 
31
27
  def getExtOrDefault(name) {
32
28
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Compressor_" + name]
@@ -36,40 +32,62 @@ def getExtOrIntegerDefault(name) {
36
32
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Compressor_" + name]).toInteger()
37
33
  }
38
34
 
39
- def supportsNamespace() {
40
- def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
41
- def major = parsed[0].toInteger()
42
- def minor = parsed[1].toInteger()
43
-
44
- // Namespace support was added in 7.3.0
45
- if (major == 7 && minor >= 3) {
46
- return true
47
- }
48
-
49
- return major >= 8
50
- }
51
-
52
35
  android {
53
- if (supportsNamespace()) {
54
- namespace "com.reactnativecompressor"
36
+ namespace "com.reactnativecompressor"
55
37
 
56
- sourceSets {
57
- main {
58
- manifest.srcFile "src/main/AndroidManifestNew.xml"
59
- }
38
+ ndkVersion getExtOrDefault("ndkVersion")
39
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
40
+
41
+ sourceSets {
42
+ main {
43
+ manifest.srcFile "src/main/AndroidManifestNew.xml"
60
44
  }
61
45
  }
62
46
 
63
- compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
64
-
65
47
  defaultConfig {
66
48
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
67
49
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
68
- buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
50
+
51
+ externalNativeBuild {
52
+ cmake {
53
+ cppFlags "-frtti -fexceptions -Wall -Wextra -fstack-protector-all -std=c++20"
54
+ arguments "-DANDROID_STL=c++_shared", "-DANDROID_SUPPORT_FLEXIBLE_PAGE_SIZES=ON"
55
+ abiFilters(*reactNativeArchitectures())
56
+ }
57
+ }
69
58
  }
59
+
60
+ externalNativeBuild {
61
+ cmake {
62
+ path "CMakeLists.txt"
63
+ }
64
+ }
65
+
70
66
  buildFeatures {
71
- buildConfig true
67
+ prefab true
72
68
  }
69
+
70
+ packagingOptions {
71
+ excludes = [
72
+ "META-INF",
73
+ "META-INF/**",
74
+ "**/libc++_shared.so",
75
+ "**/libfbjni.so",
76
+ "**/libjsi.so",
77
+ "**/libfolly_json.so",
78
+ "**/libfolly_runtime.so",
79
+ "**/libglog.so",
80
+ "**/libhermes.so",
81
+ "**/libhermes-executor-debug.so",
82
+ "**/libhermes_executor.so",
83
+ "**/libreactnative.so",
84
+ "**/libreactnativejni.so",
85
+ "**/libturbomodulejsijni.so",
86
+ "**/libreact_nativemodule_core.so",
87
+ "**/libjscexecutor.so"
88
+ ]
89
+ }
90
+
73
91
  buildTypes {
74
92
  release {
75
93
  minifyEnabled false
@@ -81,22 +99,8 @@ android {
81
99
  }
82
100
 
83
101
  compileOptions {
84
- sourceCompatibility JavaVersion.VERSION_11
85
- targetCompatibility JavaVersion.VERSION_11
86
- }
87
-
88
- sourceSets {
89
- main {
90
- if (isNewArchitectureEnabled()) {
91
- java.srcDirs += [
92
- "src/newarch",
93
- // This is needed to build Kotlin project with NewArch enabled
94
- "${project.buildDir}/generated/source/codegen/java"
95
- ]
96
- } else {
97
- java.srcDirs += ["src/oldarch"]
98
- }
99
- }
102
+ sourceCompatibility JavaVersion.VERSION_17
103
+ targetCompatibility JavaVersion.VERSION_17
100
104
  }
101
105
  }
102
106
 
@@ -114,6 +118,8 @@ dependencies {
114
118
  //noinspection GradleDynamicVersion
115
119
  implementation "com.facebook.react:react-native:+"
116
120
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
121
+ // Nitro core (provided as a Gradle subproject by autolinking).
122
+ implementation project(":react-native-nitro-modules")
117
123
 
118
124
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
119
125
  implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
@@ -121,11 +127,3 @@ dependencies {
121
127
  implementation 'com.github.kaushik-naik:TAndroidLame:277c2ab4b0'
122
128
  implementation 'javazoom:jlayer:1.0.1'
123
129
  }
124
-
125
- if (isNewArchitectureEnabled()) {
126
- react {
127
- jsRootDir = file("../src/")
128
- libraryName = "Compressor"
129
- codegenJavaPackageName = "com.reactnativecompressor"
130
- }
131
- }
@@ -1,5 +1,5 @@
1
- Compressor_kotlinVersion=1.7.0
2
- Compressor_minSdkVersion=21
3
- Compressor_targetSdkVersion=31
4
- Compressor_compileSdkVersion=31
5
- Compressor_ndkversion=21.4.7075529
1
+ Compressor_kotlinVersion=2.1.20
2
+ Compressor_minSdkVersion=24
3
+ Compressor_targetSdkVersion=36
4
+ Compressor_compileSdkVersion=36
5
+ Compressor_ndkVersion=27.1.12297006
@@ -1,2 +1,5 @@
1
1
  <manifest xmlns:android="http://schemas.android.com/apk/res/android">
2
+ <!-- activateBackgroundTask acquires a PARTIAL_WAKE_LOCK; declare it here so it
3
+ merges into consuming apps (otherwise wakeLock.acquire() throws SecurityException). -->
4
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
2
5
  </manifest>
@@ -0,0 +1,8 @@
1
+ #include <fbjni/fbjni.h>
2
+ #include <jni.h>
3
+
4
+ #include "NitroCompressorOnLoad.hpp"
5
+
6
+ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
7
+ return facebook::jni::initialize(vm, [] { margelo::nitro::compressor::registerAllNatives(); });
8
+ }
@@ -0,0 +1,213 @@
1
+ package com.margelo.nitro.compressor
2
+
3
+ import com.facebook.react.bridge.Arguments
4
+ import com.facebook.react.bridge.ReadableMap
5
+ import com.facebook.react.bridge.WritableMap
6
+ import com.margelo.nitro.NitroModules
7
+ import com.margelo.nitro.core.AnyMap
8
+ import com.margelo.nitro.core.Promise
9
+ import com.reactnativecompressor.Audio.AudioMain
10
+ import com.reactnativecompressor.Image.ImageMain
11
+ import com.reactnativecompressor.NitroPromiseAdapter
12
+ import com.reactnativecompressor.Utils.CreateVideoThumbnailClass
13
+ import com.reactnativecompressor.Utils.Downloader
14
+ import com.reactnativecompressor.Utils.EventEmitterHandler
15
+ import com.reactnativecompressor.Utils.Uploader
16
+ import com.reactnativecompressor.Utils.Utils
17
+ import com.reactnativecompressor.Video.VideoMain
18
+ import java.util.UUID
19
+ import java.util.concurrent.Executors
20
+ import com.facebook.react.bridge.Promise as RNPromise
21
+
22
+ /**
23
+ * Nitro HybridObject implementation of the single `Compressor` native module.
24
+ *
25
+ * Thin binding layer: it converts Nitro's `AnyMap` options into the
26
+ * `ReadableMap` the existing domain code already consumes, bridges the Nitro
27
+ * `Promise` to the domain layer's `com.facebook.react.bridge.Promise` via
28
+ * [NitroPromiseAdapter], and registers progress callbacks (keyed by `uuid`)
29
+ * with [EventEmitterHandler]. All heavy logic stays in the domain classes,
30
+ * which run on a background executor so the JS thread is never blocked.
31
+ */
32
+ class HybridCompressor : HybridCompressorSpec() {
33
+ private val reactContext
34
+ get() = NitroModules.applicationContext
35
+ ?: throw IllegalStateException("react-native-compressor: ReactApplicationContext is not available")
36
+
37
+ private val executor = Executors.newCachedThreadPool()
38
+
39
+ private val imageMain by lazy { ImageMain(reactContext) }
40
+ private val videoMain by lazy { VideoMain(reactContext) }
41
+ private val audioMain by lazy { AudioMain(reactContext) }
42
+ private val uploader by lazy { Uploader(reactContext) }
43
+ private val videoThumbnail by lazy { CreateVideoThumbnailClass(reactContext) }
44
+
45
+ // region Converters
46
+
47
+ private val toStringResult: (Any?) -> String = { it as? String ?: "" }
48
+
49
+ private val toAnyMapResult: (Any?) -> AnyMap = { value ->
50
+ val hashMap = (value as? ReadableMap)?.toHashMap() ?: HashMap()
51
+ AnyMap.fromMap(hashMap, true)
52
+ }
53
+
54
+ private val toThumbnailResult: (Any?) -> VideoThumbnailResult = { value ->
55
+ val map = value as ReadableMap
56
+ VideoThumbnailResult(
57
+ if (map.hasKey("path")) map.getString("path") ?: "" else "",
58
+ if (map.hasKey("size")) map.getDouble("size") else 0.0,
59
+ if (map.hasKey("mime")) map.getString("mime") ?: "" else "",
60
+ if (map.hasKey("width")) map.getDouble("width") else 0.0,
61
+ if (map.hasKey("height")) map.getDouble("height") else 0.0,
62
+ )
63
+ }
64
+
65
+ // endregion
66
+
67
+ // region Image
68
+
69
+ override fun image_compress(imagePath: String, optionMap: AnyMap, onDownloadProgress: ((progress: Double) -> Unit)?): Promise<String> {
70
+ val map = toWritableMap(optionMap)
71
+ // Remote-image download progress is keyed by `uuid` inside the Downloader. The JS
72
+ // layer no longer sends one (the callback is passed directly), so mint one here.
73
+ val uuid = if (map.hasKey("uuid")) map.getString("uuid") ?: UUID.randomUUID().toString() else UUID.randomUUID().toString()
74
+ map.putString("uuid", uuid)
75
+ EventEmitterHandler.registerDownloadProgress(uuid, onDownloadProgress)
76
+ return runOnExecutor(toStringResult, { EventEmitterHandler.unregister(uuid) }) { promise ->
77
+ imageMain.image_compress(imagePath, map, promise)
78
+ }
79
+ }
80
+
81
+ override fun getImageMetaData(filePath: String): Promise<AnyMap> {
82
+ return runOnExecutor(toAnyMapResult) { promise -> imageMain.getImageMetaData(filePath, promise) }
83
+ }
84
+
85
+ // endregion
86
+
87
+ // region Video
88
+
89
+ override fun compress(
90
+ fileUrl: String,
91
+ optionMap: AnyMap,
92
+ onProgress: ((progress: Double) -> Unit)?,
93
+ onDownloadProgress: ((progress: Double) -> Unit)?,
94
+ ): Promise<String> {
95
+ val map = toWritableMap(optionMap)
96
+ val uuid = if (map.hasKey("uuid")) map.getString("uuid") ?: "" else ""
97
+ EventEmitterHandler.registerVideoCompressProgress(uuid, onProgress)
98
+ EventEmitterHandler.registerDownloadProgress(uuid, onDownloadProgress)
99
+ return runOnExecutor(toStringResult, { EventEmitterHandler.unregister(uuid) }) { promise ->
100
+ videoMain.compress(fileUrl, map, promise)
101
+ }
102
+ }
103
+
104
+ override fun cancelCompression(uuid: String) {
105
+ videoMain.cancelCompression(uuid)
106
+ }
107
+
108
+ override fun getVideoMetaData(filePath: String): Promise<AnyMap> {
109
+ return runOnExecutor(toAnyMapResult) { promise -> videoMain.getVideoMetaData(filePath, promise) }
110
+ }
111
+
112
+ override fun activateBackgroundTask(options: AnyMap, onExpired: (() -> Unit)?): Promise<String> {
113
+ EventEmitterHandler.setBackgroundTaskExpiredCallback(onExpired)
114
+ val map = toWritableMap(options)
115
+ return runOnExecutor(toStringResult) { promise -> videoMain.activateBackgroundTask(map, promise) }
116
+ }
117
+
118
+ override fun deactivateBackgroundTask(options: AnyMap): Promise<String> {
119
+ EventEmitterHandler.setBackgroundTaskExpiredCallback(null)
120
+ val map = toWritableMap(options)
121
+ return runOnExecutor(toStringResult) { promise -> videoMain.deactivateBackgroundTask(map, promise) }
122
+ }
123
+
124
+ // endregion
125
+
126
+ // region Audio
127
+
128
+ override fun compress_audio(fileUrl: String, optionMap: AnyMap): Promise<String> {
129
+ val map = toWritableMap(optionMap)
130
+ return runOnExecutor(toStringResult) { promise -> audioMain.compress_audio(fileUrl, map, promise) }
131
+ }
132
+
133
+ // endregion
134
+
135
+ // region Upload / Download
136
+
137
+ override fun upload(fileUrl: String, options: AnyMap, onProgress: ((written: Double, total: Double) -> Unit)?): Promise<AnyMap> {
138
+ val map = toWritableMap(options)
139
+ val uuid = if (map.hasKey("uuid")) map.getString("uuid") ?: "" else ""
140
+ EventEmitterHandler.registerUploadProgress(uuid, onProgress)
141
+ return runOnExecutor(toAnyMapResult, { EventEmitterHandler.unregister(uuid) }) { promise ->
142
+ uploader.upload(fileUrl, map, reactContext, promise)
143
+ }
144
+ }
145
+
146
+ override fun cancelUpload(uuid: String, shouldCancelAll: Boolean) {
147
+ uploader.cancelUpload(uuid, shouldCancelAll)
148
+ }
149
+
150
+ override fun download(fileUrl: String, options: AnyMap, onProgress: ((progress: Double) -> Unit)?): Promise<String> {
151
+ val uuid = if (options.contains("uuid") && options.isString("uuid")) options.getString("uuid") else ""
152
+ val progressDivider = if (options.contains("progressDivider") && options.isDouble("progressDivider")) options.getDouble("progressDivider").toInt() else 0
153
+ EventEmitterHandler.registerDownloadProgress(uuid, onProgress)
154
+ return runOnExecutor(toStringResult, { EventEmitterHandler.unregister(uuid) }) { promise ->
155
+ val downloadedFilePath = Downloader.downloadMediaWithProgress(fileUrl, uuid, progressDivider, reactContext)
156
+ if (downloadedFilePath != null) promise.resolve(downloadedFilePath) else promise.reject("Unable to download", "Unable to download")
157
+ }
158
+ }
159
+
160
+ // endregion
161
+
162
+ // region Others
163
+
164
+ override fun generateFilePath(fileExtension: String): Promise<String> {
165
+ return runOnExecutor(toStringResult) { promise -> promise.resolve(Utils.generateCacheFilePath(fileExtension, reactContext)) }
166
+ }
167
+
168
+ override fun getRealPath(path: String, type: String): Promise<String> {
169
+ // Utils.getRealPath already returns a `file://`-prefixed path via slashifyFilePath,
170
+ // so it must not be prefixed again (that produced a malformed `file://file:///…`).
171
+ return runOnExecutor(toStringResult) { promise -> promise.resolve(Utils.getRealPath(path, reactContext)) }
172
+ }
173
+
174
+ override fun getFileSize(filePath: String): Promise<String> {
175
+ return runOnExecutor(toStringResult) { promise -> Utils.getFileSize(filePath, promise, reactContext) }
176
+ }
177
+
178
+ override fun createVideoThumbnail(fileUrl: String, options: AnyMap): Promise<VideoThumbnailResult> {
179
+ val map = toWritableMap(options)
180
+ return runOnExecutor(toThumbnailResult) { promise -> videoThumbnail.create(fileUrl, map, promise) }
181
+ }
182
+
183
+ override fun clearCache(cacheDir: String?): Promise<String> {
184
+ return runOnExecutor(toStringResult) { promise -> CreateVideoThumbnailClass.clearCache(cacheDir, promise, reactContext) }
185
+ }
186
+
187
+ // endregion
188
+
189
+ // region Helpers
190
+
191
+ /** Convert a Nitro [AnyMap] to a React Native [WritableMap] that the domain parsers consume. */
192
+ private fun toWritableMap(map: AnyMap): WritableMap = Arguments.makeNativeMap(map.toHashMap())
193
+
194
+ /**
195
+ * Runs [block] on a background thread, handing it a bridge [RNPromise] that
196
+ * resolves/rejects the returned Nitro [Promise]. Synchronous throws are routed
197
+ * to the same adapter so the Promise is never double-settled.
198
+ */
199
+ private fun <T> runOnExecutor(convert: (Any?) -> T, onSettle: () -> Unit = {}, block: (RNPromise) -> Unit): Promise<T> {
200
+ val promise = Promise<T>()
201
+ val adapter = NitroPromiseAdapter(promise, convert, onSettle)
202
+ executor.execute {
203
+ try {
204
+ block(adapter)
205
+ } catch (e: Throwable) {
206
+ adapter.reject(e)
207
+ }
208
+ }
209
+ return promise
210
+ }
211
+
212
+ // endregion
213
+ }
@@ -0,0 +1,30 @@
1
+ package com.reactnativecompressor
2
+
3
+ import com.facebook.react.BaseReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.module.model.ReactModuleInfoProvider
7
+ import com.margelo.nitro.compressor.NitroCompressorOnLoad
8
+
9
+ /**
10
+ * Empty React package whose sole jobs are:
11
+ * 1. Make this library discoverable by React Native autolinking (which keys off a
12
+ * `ReactPackage`), so the Gradle project + native `.so` get wired into the app.
13
+ * 2. Load the Nitro C++ library (`libNitroCompressor.so`) on first class-load so the
14
+ * `Compressor` HybridObject is registered. The module itself is served by Nitro,
15
+ * not by `getModule`, so no native modules are returned here.
16
+ *
17
+ * Lives in the `com.reactnativecompressor` namespace (not `com.margelo.nitro.compressor`)
18
+ * because the React Native CLI derives the autolink import path from the Android namespace.
19
+ */
20
+ class NitroCompressorPackage : BaseReactPackage() {
21
+ override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? = null
22
+
23
+ override fun getReactModuleInfoProvider(): ReactModuleInfoProvider = ReactModuleInfoProvider { HashMap() }
24
+
25
+ companion object {
26
+ init {
27
+ NitroCompressorOnLoad.initializeNative()
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,55 @@
1
+ package com.reactnativecompressor
2
+
3
+ import com.facebook.react.bridge.Promise
4
+ import com.facebook.react.bridge.WritableMap
5
+ import java.util.concurrent.atomic.AtomicBoolean
6
+ import com.margelo.nitro.core.Promise as NitroPromise
7
+
8
+ /**
9
+ * Adapts the React Native bridge [Promise] to a Nitro [NitroPromise] so the existing
10
+ * domain methods — which speak the bridge `Promise` contract (`resolve`/`reject`) — can
11
+ * drive a Nitro Promise unchanged.
12
+ *
13
+ * [convert] maps the resolved bridge value to the Nitro result type [T];
14
+ * [onSettle] runs once on resolve/reject (used to unregister progress callbacks).
15
+ */
16
+ class NitroPromiseAdapter<T>(
17
+ private val promise: NitroPromise<T>,
18
+ private val convert: (Any?) -> T,
19
+ private val onSettle: () -> Unit = {},
20
+ ) : Promise {
21
+ private val settled = AtomicBoolean(false)
22
+
23
+ override fun resolve(value: Any?) {
24
+ if (!settled.compareAndSet(false, true)) return
25
+ onSettle()
26
+ promise.resolve(convert(value))
27
+ }
28
+
29
+ private fun rejectInternal(message: String?, throwable: Throwable?) {
30
+ if (!settled.compareAndSet(false, true)) return
31
+ onSettle()
32
+ promise.reject(throwable ?: Throwable(message ?: "react-native-compressor error"))
33
+ }
34
+
35
+ override fun reject(code: String?, message: String?) = rejectInternal(message ?: code, null)
36
+
37
+ override fun reject(code: String?, throwable: Throwable?) = rejectInternal(code, throwable)
38
+
39
+ override fun reject(code: String?, message: String?, throwable: Throwable?) = rejectInternal(message ?: code, throwable)
40
+
41
+ override fun reject(throwable: Throwable) = rejectInternal(throwable.message, throwable)
42
+
43
+ override fun reject(throwable: Throwable, userInfo: WritableMap) = rejectInternal(throwable.message, throwable)
44
+
45
+ override fun reject(code: String?, userInfo: WritableMap) = rejectInternal(code, null)
46
+
47
+ override fun reject(code: String?, throwable: Throwable?, userInfo: WritableMap) = rejectInternal(code, throwable)
48
+
49
+ override fun reject(code: String?, message: String?, userInfo: WritableMap) = rejectInternal(message ?: code, null)
50
+
51
+ override fun reject(code: String?, message: String?, throwable: Throwable?, userInfo: WritableMap?) = rejectInternal(message ?: code, throwable)
52
+
53
+ @Deprecated("Prefer passing a module-specific error code to JS.", ReplaceWith("reject(code, message)"))
54
+ override fun reject(message: String) = rejectInternal(message, null)
55
+ }
@@ -1,66 +1,70 @@
1
1
  package com.reactnativecompressor.Utils
2
2
 
3
- import com.facebook.react.bridge.Arguments
4
- import com.facebook.react.bridge.ReactApplicationContext
5
- import com.facebook.react.modules.core.DeviceEventManagerModule
3
+ import java.util.concurrent.ConcurrentHashMap
6
4
 
5
+ /**
6
+ * Routes native progress emissions to the per-call JS callbacks that
7
+ * `HybridCompressor` registers. This replaces the old `RCTDeviceEventEmitter`
8
+ * bridge: Nitro delivers progress through callback parameters, so the domain
9
+ * layer's `emit*` calls (unchanged) are dispatched to the callback registered
10
+ * under the same `uuid` that the JS layer threads through the options map.
11
+ */
7
12
  class EventEmitterHandler {
8
13
  companion object {
9
- public var reactContext: ReactApplicationContext?=null
14
+ private val videoCompressProgressCallbacks = ConcurrentHashMap<String, (Double) -> Unit>()
15
+ private val downloadProgressCallbacks = ConcurrentHashMap<String, (Double) -> Unit>()
16
+ private val uploadProgressCallbacks = ConcurrentHashMap<String, (Double, Double) -> Unit>()
10
17
 
11
- private fun sendEvent(eventName: String,
12
- params: Any?) {
13
- reactContext
14
- ?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
15
- ?.emit(eventName, params)
18
+ @Volatile
19
+ private var backgroundTaskExpiredCallback: (() -> Unit)? = null
20
+
21
+ // Registration (called by HybridCompressor)
22
+
23
+ fun registerVideoCompressProgress(uuid: String, callback: ((Double) -> Unit)?) {
24
+ if (callback != null) videoCompressProgressCallbacks[uuid] = callback
16
25
  }
17
26
 
18
- fun emitBackgroundTaskExpired(backgroundId: String?){
19
- sendEvent("backgroundTaskExpired",backgroundId)
27
+ fun registerDownloadProgress(uuid: String, callback: ((Double) -> Unit)?) {
28
+ if (callback != null) downloadProgressCallbacks[uuid] = callback
20
29
  }
21
30
 
22
- fun emitVideoCompressProgress(progress:Double,uuid:String){
23
- val params = Arguments.createMap()
24
- val data = Arguments.createMap()
25
- params.putString("uuid", uuid)
26
- data.putDouble("progress", progress)
27
- params.putMap("data", data)
28
- sendEvent("videoCompressProgress", params)
31
+ fun registerUploadProgress(uuid: String, callback: ((Double, Double) -> Unit)?) {
32
+ if (callback != null) uploadProgressCallbacks[uuid] = callback
29
33
  }
30
34
 
31
- fun emitDownloadProgress(progress:Double,uuid:String){
32
- val params = Arguments.createMap()
33
- val data = Arguments.createMap()
34
- params.putString("uuid", uuid)
35
- data.putDouble("progress", progress)
36
- params.putMap("data", data)
37
- sendEvent("downloadProgress", params)
35
+ fun unregister(uuid: String) {
36
+ videoCompressProgressCallbacks.remove(uuid)
37
+ downloadProgressCallbacks.remove(uuid)
38
+ uploadProgressCallbacks.remove(uuid)
38
39
  }
39
40
 
40
- fun emitDownloadProgressError(uuid:String?, error:String?){
41
- if (uuid != null && error != null) {
42
- val params = Arguments.createMap()
43
- val data = Arguments.createMap()
44
- params.putString("uuid", uuid)
45
- params.putString("error", error)
46
- params.putMap("data", data)
47
- sendEvent("downloadProgressError", params)
48
- }
41
+ fun setBackgroundTaskExpiredCallback(callback: (() -> Unit)?) {
42
+ backgroundTaskExpiredCallback = callback
49
43
  }
50
44
 
51
- fun sendUploadProgressEvent(numBytes: Long, totalBytes: Long, uuid:String?) {
52
- if(uuid!=null) {
53
- val _params = Arguments.createMap()
54
- val _data = Arguments.createMap()
55
- _params.putString("uuid", uuid)
56
- _data.putDouble("written", numBytes.toDouble())
57
- _data.putDouble("total", totalBytes.toDouble())
58
- _params.putMap("data", _data)
59
- sendEvent("uploadProgress", _params)
60
- }
45
+ // Emission (called by the domain layer — method names preserved)
46
+
47
+ fun emitBackgroundTaskExpired(backgroundId: String?) {
48
+ backgroundTaskExpiredCallback?.invoke()
61
49
  }
62
50
 
51
+ fun emitVideoCompressProgress(progress: Double, uuid: String) {
52
+ videoCompressProgressCallbacks[uuid]?.invoke(progress)
53
+ }
63
54
 
64
- }
55
+ fun emitDownloadProgress(progress: Double, uuid: String) {
56
+ downloadProgressCallbacks[uuid]?.invoke(progress)
57
+ }
58
+
59
+ fun emitDownloadProgressError(uuid: String?, error: String?) {
60
+ // No JS consumer for `downloadProgressError`; download failures surface
61
+ // through the rejected Promise instead. Kept so domain call sites compile.
62
+ }
65
63
 
64
+ fun sendUploadProgressEvent(numBytes: Long, totalBytes: Long, uuid: String?) {
65
+ if (uuid != null) {
66
+ uploadProgressCallbacks[uuid]?.invoke(numBytes.toDouble(), totalBytes.toDouble())
67
+ }
68
+ }
69
+ }
66
70
  }
@@ -134,7 +134,7 @@ object Utils {
134
134
  if (fileSize >= 0) {
135
135
  promise.resolve(fileSize.toString())
136
136
  } else {
137
- promise.resolve("")
137
+ promise.reject("FILE_SIZE_ERROR", "Failed to get file size for path: $filePath")
138
138
  }
139
139
  }
140
140
  }