diff --git a/TRIAGE.md b/TRIAGE.md index 3fc5382..f18ad50 100644 --- a/TRIAGE.md +++ b/TRIAGE.md @@ -13,6 +13,7 @@ Legend: | Issue | Triage | Notes | | --- | --- | --- | +| #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`. | | #404 | real, fixed here | Android Nitro module failed to compile on older React Native: the bridge `Promise.reject(...)` `code` parameter is non-null `String` on older RN but nullable `String?` on RN 0.85, and Kotlin's invariant override params can't satisfy both. This branch rewrites `NitroPromiseAdapter` in Java (nullability-erased override matching) so one implementation compiles across all RN versions. | @@ -100,3 +101,4 @@ These should be closed upstream unless a current repro still exists on the lates - Android: annotate the `HybridCompressor` impl with `@DoNotStrip @Keep` so the Nitro `Compressor` HybridObject (instantiated via JNI by name) survives R8 in consumer release builds (#406) - 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) diff --git a/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt b/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt index 6fc9342..269fb28 100644 --- a/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt +++ b/android/src/main/java/com/reactnativecompressor/Video/VideoCompressor/compressor/Compressor.kt @@ -515,7 +515,7 @@ object Compressor { // finishMovie() never runs on this path, so close the MP4Builder // streams explicitly or the output file handle leaks. mediaMuxer.close() - return Result(id, success = false, failureMessage = exception.message) + return Result(id, success = false, failureMessage = failureMessage(exception)) } // Release resources @@ -553,7 +553,7 @@ object Compressor { // extractor.release) where the MP4Builder is still open. close() is // idempotent, so calling it after a successful finishMovie() is a no-op. muxer?.close() - return Result(id, success = false, failureMessage = exception.message) + return Result(id, success = false, failureMessage = failureMessage(exception)) } var resultFile = cacheFile @@ -773,6 +773,15 @@ object Compressor { } } + // A MediaCodec.CodecException often has an empty message; its diagnosticInfo + // names the codec error (e.g. android.media.MediaCodec.error_neg_12 is NO_MEMORY). + private fun failureMessage(exception: Throwable): String? = + if (exception is MediaCodec.CodecException) { + listOfNotNull(exception.message?.ifBlank { null }, exception.diagnosticInfo).joinToString(": ") + } else { + exception.message + } + // Dolby Vision profile 5 (0x20) carries no HEVC base layer, so no standard // Android decoder can render it. Detect it up front so start() can reject the // input before allocating the muxer/encoder/EGL surfaces. Profiles 8.x do carry