Fix/import nitro#407
Merged
Merged
Conversation
iPhone HDR .MOV uses video/dolby-vision mime which has no
standalone Android decoder, so createDecoderByType throws
"Failed to initialize video/dolby-vision". DV profiles 8.x
carry an HEVC base layer, so remap mime to video/hevc before
configuring the decoder. Reject profile 5 (0x20) explicitly
since it has no HEVC fallback.
Perf, bundled to land with the codec rework:
- Pick HW AVC encoder via MediaCodecList(ALL_CODECS), blacklist
c2.qti.avc.encoder (corrupt MP4 on Mac/iOS).
- Feed decoder until input slots drain instead of one sample
per loop; unblocks parallel decode-render-encode.
- Drop decoded frames whose PTS precedes the next target slot
when source fps exceeds output fps.
- Encoder: VBR + KEY_PRIORITY=0 + KEY_OPERATING_RATE=MAX to
unthrottle HW codec scheduling.
- Route SurfaceTexture onFrameAvailable to a dedicated
HandlerThread so awaitNewImage stops contending with the
main/JS thread.
- Skip StreamableVideo rewrite unless caller passed a
streamableFile; halves disk I/O for chat uploads.
Android: extract METADATA_KEY_LOCATION and write an Apple-style "©xyz" udta atom into the muxed MP4 so geotags survive transcoding. iOS: forward asset.metadata plus every available metadata format to the AVAssetExportSession so location, creation date, and other tags are retained in the exported file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- fps: derive from frame_count/duration when CAPTURE_FRAMERATE absent. Cap 30→60. Drop-gate only when source>target, anchor to ideal grid. - bitrate: WhatsApp envelope (~1.5 Mbps @ 720p). Android+iOS sync. - GPS: LocationExtractor walks MP4 — ©xyz, loci, iTunes meta/keys+ilst, SEF trailer regex. Writer ©xyz moved to LocationBox class. - teardown: runCatching every dispose step. join() OutputSurface thread after quitSafely to avoid SIGABRT on stale pthread_t. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…encoder/teardown - LocationExtractor/Compressor: log only location presence + source, never the ISO 6709 coordinate string (xyz/itunes/loci/SEF/resolved values) - Compressor: preflight Dolby Vision profile 5 before allocating muxer/encoder/EGL surfaces and drop the throw from prepareDecoder, so the unsupported case no longer leaks codec/GL resources on bail-out - Compressor: restore always-on streamable rewrite (moov atom to front) for default output to preserve progressive playback (revert behavior change) - CompressorUtils/Compressor: make VBR/priority/operating-rate throughput tuning optional and fall back to a default-rate-control configure when an encoder rejects the tuned format - Compressor: release partially-initialized encoder/decoder/EGL surfaces on any setup failure or in-loop throw (dispose tolerates null handles) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MP4Builder opened its FileOutputStream/FileChannel in createMovie() but only closed them in finishMovie(). Any failure between muxer creation and a successful finishMovie() leaked the output file handle. - Add idempotent MP4Builder.close() to release streams without finalizing - createMovie() now closes its own streams if header writing throws - Compressor closes the muxer in the setup/in-loop catch, the finishMovie catch, and the outer catch (processAudio/extractor.release failures) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The bridge `Promise` interface declares `reject(...)`'s `code` parameter as non-null `String` on older React Native versions but nullable `String?` on RN 0.85. Kotlin override parameter types are invariant, so the Kotlin adapter (which matched `String?`) failed to compile on older RN with "is not abstract and does not implement abstract members". Rewrite the adapter in Java: Java erases nullability annotations for override matching, so a single implementation satisfies every RN version (matching the `react-native: "*"` peer range). No call-site changes — the Kotlin lambdas pass straight into the Java `Function1`/`Function0` constructor params. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #404.
On older React Native versions, the Android build of the Nitro module fails to compile with:
Class 'NitroPromiseAdapter' is not abstract and does not implement abstract members:
fun reject(code: String, message: String?): Unit
fun reject(code: String, throwable: Throwable?): Unit
fun reject(code: String, message: String?, throwable: Throwable?): Unit
fun reject(code: String, userInfo: WritableMap): Unit
fun reject(code: String, throwable: Throwable?, userInfo: WritableMap): Unit
fun reject(code: String, message: String?, userInfo: WritableMap): Unit
Why this happens — a React Native version difference, surfaced by Kotlin's type rules:
The bridge
Promiseinterface declares thecodeparameter ofreject(...)differently across RN versions:codeinreject(...)code: String— non-nullcode: String?— nullableNitroPromiseAdapterwas written in Kotlin withcode: String?to match RN 0.85. Since Kotlin override parameter types are invariant, a single Kotlin source can only match one nullability variant — it compiles on RN 0.85 but breaks onolder RN (the 6 overloads above stop counting as "implemented"). No single Kotlin source can satisfy both ranges at once.
The fix: Rewrite
NitroPromiseAdapterin Java. Java erases nullability annotations when matching overrides, so one Java implementation satisfies thereject(...)contract on every RN version — matching the library's"react-native": "*"peer range. The adapter is the only class in the repo that implements the bridgePromise; the single construction site inHybridCompressor.ktneeds no change because the Kotlin lambdas ((Any?) -> T,() -> Unit) flow straight intothe Java
Function1/Function0constructor params.Changelog
[ANDROID] [FIXED] - Rewrite
NitroPromiseAdapterin Java so the Nitro module compiles across all React Native versions (older RN declaredPromise.reject'scodeas non-null, RN 0.85 as nullable; Kotlin's invariant override params could notsatisfy both) (#404)
Test Plan
yarn test:prTest Suites: 1 passed, 1 total
Tests: 13 passed, 13 total
✖ 9 problems (0 errors, 9 warnings) # pre-existing warnings only
typecheck: pass
./gradlew :react-native-compressor:compileDebugKotlin :react-native-compressor:compileDebugJavaWithJavac
BUILD SUCCESSFUL
Pixel_9aemulator, rebuilt debug APK):yarn test:harness:android
Test Suites: 1 passed, 1 total
Tests: 9 passed, 9 total
yarn test:harness:ios
Test Suites: 1 passed, 1 total
Tests: 9 passed, 9 total