|
| 1 | +# CLAUDE.md |
| 2 | + |
| 3 | +Guidance for AI agents (Claude, Copilot, etc.) and new contributors working in |
| 4 | +this repository. Keep this file up to date when project structure, conventions, |
| 5 | +or workflows change. |
| 6 | + |
| 7 | +## What this project is |
| 8 | + |
| 9 | +`react-native-compressor` is a React Native library that compresses **video**, |
| 10 | +**image**, and **audio** files (and provides background upload/download helpers) |
| 11 | +with results comparable to WhatsApp-style compression. It ships native code for |
| 12 | +both **iOS** (Swift/Obj-C) and **Android** (Kotlin), exposed to JavaScript through |
| 13 | +a TurboModule-capable spec, and supports both the old and new React Native |
| 14 | +architectures plus an Expo config plugin. |
| 15 | + |
| 16 | +- Package name: `react-native-compressor` |
| 17 | +- Package manager: **Yarn 4** (`packageManager: yarn@4.14.1`); Yarn workspaces with `examples/*`. |
| 18 | +- Upstream: https://github.com/numandev1/react-native-compressor — this is a fork |
| 19 | + (`XChikuX/react-native-compressor`) that triages and fixes upstream issues. |
| 20 | + |
| 21 | +## Repository layout |
| 22 | + |
| 23 | +``` |
| 24 | +src/ JavaScript/TypeScript public API (the npm entry point) |
| 25 | + index.tsx Re-exports the public surface |
| 26 | + Main.tsx Aggregates the default export object |
| 27 | + Spec/NativeCompressor.ts TurboModule spec (single source of truth for native methods) |
| 28 | + Video/ Image/ Audio/ Per-domain JS wrappers (compress(), options, events) |
| 29 | + utils/ Uploader/Downloader/helpers (uuid, path normalization) |
| 30 | + expo-plugin/ Expo config plugin |
| 31 | +ios/ Native iOS implementation (Swift + Obj-C bridge) |
| 32 | + Video/VideoMain.swift Video compression entry (auto/manual helpers) |
| 33 | + Video/NextLevelSessionExporter.swift AVAssetReader/Writer export engine |
| 34 | + Image/ Audio/ Utils/ Image, audio, upload/download, thumbnails |
| 35 | + Compressor.mm / Compressor.h Obj-C bridge to the Swift module |
| 36 | +android/ Native Android implementation (Kotlin) |
| 37 | + src/main/java/com/reactnativecompressor/ |
| 38 | + Video/ Video compression (MediaCodec transcode pipeline) |
| 39 | + VideoCompressor/compressor/Compressor.kt Core encode/decode loop |
| 40 | + VideoCompressor/utils/CompressorUtils.kt Format/codec helpers |
| 41 | + Image/ Audio/ Utils/ Image, audio, upload/download, helpers |
| 42 | + src/oldarch / src/newarch Architecture-specific TurboModule specs |
| 43 | +__tests__/ Jest unit tests for the JS wrapper (native is mocked) |
| 44 | +harness/ react-native-harness on-device smoke test definitions |
| 45 | +examples/bare Bare React Native example app (build + harness target) |
| 46 | +examples/expo Expo example app |
| 47 | +TRIAGE.md Running triage of upstream issues and fixes in this fork |
| 48 | +``` |
| 49 | + |
| 50 | +## Public API surface |
| 51 | + |
| 52 | +The default export aggregates these modules/functions (see |
| 53 | +`__tests__/compressor.test.ts` for the authoritative list): |
| 54 | +`Audio`, `Image`, `Video`, `UploadType`, `UploaderHttpMethod`, `backgroundUpload`, |
| 55 | +`cancelUpload`, `clearCache`, `createVideoThumbnail`, `download`, |
| 56 | +`generateFilePath`, `getDetails`, `getFileSize`, `getImageMetaData`, |
| 57 | +`getRealPath`, `getVideoMetaData`, `uuidv4`. |
| 58 | + |
| 59 | +Video compression supports `compressionMethod: 'auto' | 'manual'`, `maxSize`, |
| 60 | +`bitrate`, `progressDivider`, `minimumFileSizeForCompress`, and `stripAudio`. |
| 61 | + |
| 62 | +## Build, test, and validate |
| 63 | + |
| 64 | +Run JS-level checks from the repo root: |
| 65 | + |
| 66 | +| Command | Purpose | |
| 67 | +| --- | --- | |
| 68 | +| `yarn install` | Install dependencies (Yarn 4) | |
| 69 | +| `yarn jest` / `yarn test` | Run the JS wrapper unit tests | |
| 70 | +| `yarn typecheck` | `tsc --noEmit` | |
| 71 | +| `yarn lint` | ESLint over `**/*.{js,ts,tsx}` | |
| 72 | +| `yarn test:pr` | `test --runInBand && typecheck && lint` (run before opening a PR) | |
| 73 | +| `yarn build:android` | Assemble the bare example (`arm64-v8a`) | |
| 74 | +| `yarn build:ios` | Build the bare example for the iOS simulator | |
| 75 | +| `yarn test:harness:android` / `yarn test:harness:ios` | On-device/simulator smoke tests | |
| 76 | + |
| 77 | +**Important:** The Jest tests mock the native module, so they validate only the |
| 78 | +JS contract. Real media decoding/encoding **cannot** be verified by unit tests — |
| 79 | +it must be smoke-tested in the example app on a simulator or device. When you |
| 80 | +change native Swift/Kotlin code, state clearly that it was not runtime-verified |
| 81 | +in CI and, where possible, validate via the example app or harness. |
| 82 | + |
| 83 | +## Native video pipeline notes (high-signal, easy to get wrong) |
| 84 | + |
| 85 | +### iOS (`ios/Video/`) |
| 86 | +- `VideoMain.swift` builds the `videoOutputConfiguration` / `compressionDict` |
| 87 | + and drives `NextLevelSessionExporter`. |
| 88 | +- `NextLevelSessionExporter.setupVideoOutput` only creates the video writer input |
| 89 | + when `writer.canApply(outputSettings:forMediaType:) == true`; otherwise it logs |
| 90 | + `"Unsupported output configuration"` and writes **audio only**, yet still ends |
| 91 | + as `.completed`. That means a bad `videoOutputConfiguration` can silently yield |
| 92 | + an **audio-only** MP4 reported as success. |
| 93 | +- Do **not** add undocumented H.264 (`avc1`) compression properties such as |
| 94 | + `AVVideoExpectedSourceFrameRateKey` or `AVVideoAverageNonDroppableFrameRateKey`: |
| 95 | + `canApply(...)` accepts them but the iOS encoder drops the video track |
| 96 | + (regression in #392, fixed for #400). After export, verify the output asset |
| 97 | + actually contains a video track before resolving success. |
| 98 | + |
| 99 | +### Android (`android/.../Video/VideoCompressor/`) |
| 100 | +- `Compressor.kt` runs an `MediaExtractor` → decoder (Surface) → encoder |
| 101 | + (`video/avc`) → `MP4Builder` transcode loop. |
| 102 | +- The decoder is created from the **input** track's MIME. Some containers (notably |
| 103 | + iPhone `.MOV`) report `video/dolby-vision`, which fails with `NAME_NOT_FOUND` |
| 104 | + on devices lacking a Dolby Vision decoder. `CompressorUtils.ensureDecodableVideoFormat` |
| 105 | + remaps such inputs to their backward-compatible HEVC/AVC base layer (profiles 8/4 |
| 106 | + → HEVC, profile 9 → AVC) or throws a clear error for non-compatible profiles |
| 107 | + (5/7). See #398. |
| 108 | +- The encoder is intentionally `c2.android.avc.encoder` (when QTI codecs exist) or |
| 109 | + `MediaCodec.createEncoderByType("video/avc")`; QTI AVC encoders can produce MP4s |
| 110 | + that do not play on Mac/iPhone, so avoid switching this without testing. |
| 111 | + |
| 112 | +## Conventions |
| 113 | + |
| 114 | +- Keep changes surgical and aligned with surrounding style. Native helper objects |
| 115 | + (e.g. `CompressorUtils`) use member imports and unqualified calls in |
| 116 | + `Compressor.kt` — match that. |
| 117 | +- When fixing an upstream issue, record it in `TRIAGE.md` (triage row + the |
| 118 | + "Minor fixes made in this branch" list) referencing the issue number. |
| 119 | +- Prefer graceful, descriptive failures over cryptic native crashes for |
| 120 | + unsupported media (clear error messages that tell the user what happened). |
| 121 | + |
| 122 | +## Merging back upstream |
| 123 | + |
| 124 | +This fork accumulates many incremental commits. When contributing back to |
| 125 | +`numandev1/react-native-compressor`, use a **squash merge** so the history lands |
| 126 | +as a single, well-described commit rather than the full incremental series. |
0 commit comments