Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions .github/workflows/build-android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,12 @@ on:

jobs:
build:
name: Build Android Example App
name: Build Android Example App (audio=${{ matrix.enableAudio }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
enableAudio: ['true', 'false']
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -60,7 +64,14 @@ jobs:

- name: Run Gradle Build for examples/bare/android/
working-directory: examples/bare/android
run: ./gradlew assembleDebug --no-daemon --build-cache
run: ./gradlew assembleDebug --no-daemon --build-cache -PCompressor_enableAudio=${{ matrix.enableAudio }}

- name: Check that the APK has the LAME encoder only when audio is enabled
working-directory: examples/bare/android
run: |
if unzip -l app/build/outputs/apk/debug/app-debug.apk | grep -q libandroidlame.so; then found=true; else found=false; fi
echo "libandroidlame.so in APK: $found"
test "$found" = "${{ matrix.enableAudio }}"

- name: Stop Gradle Daemon
if: always()
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ We should use **react-native-compressor** instead of **FFmpeg** because **react-
- [Cancel Video Compression](#cancel-video-compression)
- [Video Api Docs](#video-1)
- [Audio](#audio)
- [Build without audio compression (Android)](#build-without-audio-compression-android)
- [Background Upload](#background-upload)
- [Cancel Background Upload](#cancel-background-upload)
- [Download File](#download)
Expand Down Expand Up @@ -292,6 +293,16 @@ const result = await Audio.compress(
);
```

##### Build without audio compression (Android)

If your app does not use `Audio.compress`, add this line to `android/gradle.properties`:

```properties
Compressor_enableAudio=false
```

The Android build then leaves out audio compression, the `TAndroidLame` dependency (`libandroidlame.so`), `jlayer` and the JitPack repository. `Audio.compress` on Android rejects with an error whose message starts with `[E_AUDIO_DISABLED]`. The default is `true`. iOS does not change.

### Background Upload

```js
Expand Down
2 changes: 2 additions & 0 deletions TRIAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Legend:

| Issue | Triage | Notes |
| --- | --- | --- |
| #427 | feature, added here | Every Android app shipped `libandroidlame.so` (TAndroidLame, from JitPack) even when it never calls `Audio.compress`. This branch adds the `Compressor_enableAudio` Gradle property (default `true`). With `false`, the build leaves out the LAME-based `AudioCompressor.kt`, TAndroidLame, jlayer and the JitPack repository, and `Audio.compress` rejects with `[E_AUDIO_DISABLED]`. |
| #428 | real, fixed here | Android `Video.compress` rejected with the bare `CodecException` message, which is usually empty, so every codec failure read the same. This branch appends `CodecException.diagnosticInfo` (e.g. `android.media.MediaCodec.error_neg_12` = `NO_MEMORY`) to the rejection message. |
| #423 | real, fixed here | Android `Video.compress` rejects with `NO_MEMORY` on Samsung Exynos encoders (`c2.exynos.h264.encoder`) at higher resolutions (e.g. 1080p manual mode). #399 set `KEY_PRIORITY = 0` + `KEY_OPERATING_RATE = Short.MAX_VALUE`; Exynos accepts them in `configure()` but fails `start()` ("real-time(32767 fps) is not supported"), which the `prepareEncoder` fallback doesn't cover. Small `auto` outputs (360×640) still fit the realtime budget. This branch removes both keys. Verified on a Galaxy A56 (Exynos 1580): 1080p manual fails with the keys and succeeds without them (94.1 MB → 32.0 MB). |
| #406 | real, fixed here | Nitro module not linked in Android **release** builds (works in debug). R8 strips the `HybridCompressor` implementation, which is instantiated from C++/JNI by class name (`NitroCompressorOnLoad.cpp` `kJavaDescriptor`) and was the only Nitro class lacking keep protection — the generated spec has `@DoNotStrip @Keep`, but the hand-written impl didn't. This branch annotates `HybridCompressor` with `@DoNotStrip @Keep` (the same convention nitrogen and other Nitro modules like react-native-mmkv use), so no consumer ProGuard rules are needed. Verified with a minified release build: without the annotation R8 lists the class in `usage.txt` (removed); with it, the class + no-arg constructor are kept in `mapping.txt`. |
Expand Down Expand Up @@ -102,3 +103,4 @@ These should be closed upstream unless a current repro still exists on the lates
- Android: rewrite `NitroPromiseAdapter` in Java so the Nitro module compiles across all React Native versions regardless of `Promise.reject`'s `code` nullability (#404)
- Android: drop `KEY_PRIORITY`/`KEY_OPERATING_RATE` from the encoder format so Exynos H.264 encoders don't fail `start()` with `NO_MEMORY` (#423)
- Android: name the codec error (`CodecException.diagnosticInfo`) when video compression fails (#428)
- Android: `Compressor_enableAudio=false` builds without audio compression and the TAndroidLame dependency (#427)
8 changes: 8 additions & 0 deletions __tests__/compressor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ describe('react-native-compressor JS wrapper API', () => {
});
});

it('passes a native audio rejection to the caller', async () => {
mockCompressor.compress_audio.mockRejectedValueOnce(
new Error('[E_AUDIO_DISABLED] Audio compression is disabled in this build (Compressor_enableAudio=false).'),
);

await expect(api.Audio.compress(localAudioUri)).rejects.toThrow('E_AUDIO_DISABLED');
});

it('wraps native utility functions and normalizes image metadata', async () => {
reactNative.Platform.OS = 'android';
mockCompressor.generateFilePath.mockResolvedValue('/tmp/output.jpg');
Expand Down
20 changes: 17 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Compressor_" + name]).toInteger()
}

def getBooleanExtOrDefault(String name, boolean fallback) {
def key = "Compressor_" + name
def value = rootProject.ext.has(key) ? rootProject.ext.get(key) : project.findProperty(key)
return value == null ? fallback : value.toString().toBoolean()
}

// Set Compressor_enableAudio=false to build without audio compression and the TAndroidLame dependency.
def enableAudio = getBooleanExtOrDefault("enableAudio", true)

android {
namespace "com.reactnativecompressor"

Expand All @@ -48,6 +57,7 @@ android {
sourceSets {
main {
manifest.srcFile "src/main/AndroidManifestNew.xml"
java.srcDirs += [enableAudio ? "src/audio/java" : "src/noaudio/java"]
}
}

Expand Down Expand Up @@ -114,7 +124,9 @@ android {
repositories {
mavenCentral()
google()
maven { url "https://jitpack.io" }
if (enableAudio) {
maven { url "https://jitpack.io" }
}
}

def kotlin_version = getExtOrDefault("kotlinVersion")
Expand All @@ -131,6 +143,8 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
implementation 'org.mp4parser:isoparser:1.9.56'
implementation 'com.github.kaushik-naik:TAndroidLame:277c2ab4b0'
implementation 'javazoom:jlayer:1.0.1'
if (enableAudio) {
implementation 'com.github.kaushik-naik:TAndroidLame:277c2ab4b0'
implementation 'javazoom:jlayer:1.0.1'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.reactnativecompressor.Audio

import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableMap

// Built instead of the LAME-based AudioCompressor when Compressor_enableAudio=false.
class AudioCompressor {
companion object {
val TAG = "AudioMain"
private const val ERROR_CODE = "E_AUDIO_DISABLED"
private const val ERROR_MESSAGE =
"[E_AUDIO_DISABLED] Audio compression is disabled in this build (Compressor_enableAudio=false)."

@JvmStatic
fun CompressAudio(
fileUrl: String,
optionMap: ReadableMap,
context: ReactApplicationContext,
promise: Promise,
) {
promise.reject(ERROR_CODE, ERROR_MESSAGE)
}
}
}