Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d34ce7f
feat: 1st version of encoder
mdydek Jul 21, 2026
113e6bf
Merge branch 'main' into feat/encoder-interface
mdydek Aug 11, 2026
5c76ba2
feat: remove unnecessary formats
mdydek Aug 12, 2026
42d9269
feat: 2nd version of encoders
mdydek Aug 12, 2026
1b22746
Merge branch 'main' into feat/encoder-interface
mdydek Aug 18, 2026
8c61e1e
feat: 3 codecs can be concatenated
mdydek Aug 18, 2026
afa2b05
Merge branch 'main' into feat/encoder-interface
mdydek Aug 18, 2026
303d8f1
fix: ci
mdydek Aug 18, 2026
6398c34
Merge branch 'main' into feat/encoder-interface
mdydek Aug 20, 2026
e254f1c
refactor: put common code in the base class
mdydek Aug 20, 2026
e42a5b2
feat: constexpresssions in encoder formats
mdydek Sep 8, 2026
9e45371
Merge branch 'main' into feat/encoder-interface
mdydek Sep 8, 2026
607491c
refactor: naming approach
mdydek Sep 9, 2026
5bfff82
fix: get rid of writer factory in rotating writer
mdydek Sep 9, 2026
e0225c9
fix: comments
mdydek Sep 9, 2026
f224230
Merge branch 'main' into feat/encoder-interface
mdydek Sep 14, 2026
e576ea4
refactor: removed unnecessary abstraction
mdydek Sep 14, 2026
fec7b1f
Merge branch 'main' into feat/encoder-interface
mdydek Sep 14, 2026
f3da783
feat: only one encoder
mdydek Sep 17, 2026
8b0bed4
Merge branch 'main' into feat/encoder-interface
mdydek Sep 17, 2026
c118567
fix: tests
mdydek Sep 17, 2026
1b41618
fix: changes from #1295 and #1296
mdydek Sep 18, 2026
527c095
Merge branch 'main' into feat/encoder-interface
mdydek Sep 23, 2026
e3214d7
feat: correct interleaved format coming from recorder and codec info …
mdydek Sep 25, 2026
6c44d63
fix: failing ci
mdydek Sep 25, 2026
8607652
Merge branch 'main' into feat/encoder-interface
mdydek Sep 25, 2026
fd5f0af
refactor: grouped file properties
mdydek Sep 29, 2026
e208c33
refactor: further recorder abstraction
mdydek Sep 29, 2026
71a0b87
refactor: renames of some function and namespaces
mdydek Sep 29, 2026
4ebd32d
feat: use os encoder to concatenate wav
mdydek Sep 29, 2026
498abf2
refactor: fileinfo struct
mdydek Sep 29, 2026
5ccc332
refactor: 2 flags merged into one behind enum
mdydek Sep 29, 2026
b833fd2
refactor: better choosing of backends
mdydek Sep 29, 2026
606f0ef
Merge branch 'main' into feat/encoder-interface
mdydek Sep 29, 2026
39e2444
refactor: stricter validation and utils
mdydek Sep 29, 2026
a84156a
refactor: remove mp4 and faster validation of wrong options
mdydek Sep 29, 2026
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
24 changes: 23 additions & 1 deletion .claude/skills/audio-nodes/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ classDiagram
AudioNode <|-- WorkletNode
AudioNode <|-- AnalyserNode
AudioNode <|-- AudioDestinationNode
AudioNode <|-- AudioRecorder

AudioScheduledSourceNode <|-- AudioBufferBaseSourceNode
AudioScheduledSourceNode <|-- OscillatorNode
Expand All @@ -152,6 +151,29 @@ classDiagram
AudioBufferBaseSourceNode <|-- AudioBufferQueueSourceNode
```

### AudioRecorder (not an AudioNode)

`core/inputs/AudioRecorder` is a standalone base class, not part of the `AudioNode` hierarchy — it
feeds the graph through a `RecorderAdapterNode` instead of being processed by it.

The split between it and `IOSAudioRecorder` / `AndroidAudioRecorder` is: the base owns everything
that happens to recorded frames (file writer, JS callback, adapter node — `enableFileOutput`,
`setupFileWriter`, `setOnAudioReadyCallback`, `connect`, `detachSideEffects`/`finalizeSideEffects`), the
subclasses own only the platform input stream. The one thing the base needs from the platform is
`resolveStreamFormat()`, returning sample rate, channel count and max frames per buffer; iOS reads
it from `NativeAudioRecorder` on every call (a route change invalidates it), Android returns values
cached when the Oboe stream opened. Add shared recorder behavior to the base, not to one platform;
state only one platform touches (Android's `streamMutex_`, iOS's `inputChannelCount_`) lives in
that subclass. State a consumer already owns stays with the consumer: the adapter node's ring
layout (`RecorderAdapterNode::writeFrames`) and the session's file paths (returned by
`AudioFileWriter::closeFile()`) are not mirrored in the recorder.

Pitfall: never redeclare a base member (`streamSampleRate_`, `fileWriter_`,
`lastCallbackFrameCount_`) in a platform recorder. The shadowing copy compiles fine, but the base's
audio-thread fan-out reads its own member and silently drops that output.

---

### AudioScheduledSourceNode (internal only — not exposed to JS directly)

Base class for source nodes that have a scheduled start and stop time. **Not instantiated directly.**
Expand Down
7 changes: 6 additions & 1 deletion .claude/skills/build-compilation-dependencies/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ react-native-audio-api/
│ │ └── src/main/cpp/audioapi/
│ │ └── CMakeLists.txt # Actual Android C++ build target
│ ├── common/cpp/audioapi/ # Shared C++ (used by all platforms)
│ │ ├── decoding/ # Decoder factory, backends, SeekDecoderDaemon, AudioDecoding, AudioFileConcatenator
│ │ ├── decoding/ # Decoder factory, backends, SeekDecoderDaemon, AudioDecoding
│ │ ├── encoding/ # AudioEncoder interface, EncoderCapabilities, OS encoder/remux selector headers, AudioFileConcatenator
│ │ ├── libs/ # Third-party wrappers (FFmpeg, miniaudio, pffft, …)
│ │ └── external/ # Prebuilt binaries per platform
│ │ ├── android/ # .a static libs (Opus, Ogg, Vorbis, OpenSSL)
Expand Down Expand Up @@ -316,6 +317,7 @@ CI runs a parallel `cpp-coverage` job via `.github/workflows/cpp-coverage-job.ym
- Compile definitions: `RN_AUDIO_API_ENABLE_WORKLETS=0`, `RN_AUDIO_API_TEST=1`, `RN_AUDIO_API_FFMPEG_DISABLED=1`
- Google Test auto-fetched via `FetchContent` if not installed locally
- New test files in `test/src/**/*.cpp` are picked up automatically by glob — no CMakeLists edit needed
- `jsi.cpp` is compiled into the static lib so library members that reference JSI symbols (e.g. `AudioFileProperties::CreateFromJSIValue`) link when a test first pulls them in; a static-lib member costs nothing unless demanded. If a new test triggers `Undefined symbols: facebook::jsi::...`, the referenced runtime source is missing from the lib — add it there rather than stubbing the symbol

For `MockAudioEventHandlerRegistry`, `TestableXxx` pattern, and full CMakeLists analysis see [build-details.md](build-details.md#c-test-build--commoncpptestcmakeliststxt--detailed-analysis).

Expand Down Expand Up @@ -354,6 +356,8 @@ Resolution pitfalls learned the hard way (both handled inside `package-root.js`)
| `HAVE_ACCELERATE` | Not set | `GCC_PREPROCESSOR_DEFINITIONS` | Not set |
| `RN_AUDIO_API_TEST` | Not set | Not set | Always set to 1 |

**OS-API selector headers** (`decoding/OSDecoding.h`, `encoding/OSEncoding.h`, `encoding/OSRemux.h`, `encoding/OSFilePath.h`): common code reaches platform implementations through `#if defined(__ANDROID__)` / `#elif defined(__APPLE__) && !defined(RN_AUDIO_API_TEST) && !defined(RN_AUDIO_API_NODE)` dispatch. The Apple branch must exclude **both** desktop defines: the gtest build (`RN_AUDIO_API_TEST`) and the WPT node addon (`RN_AUDIO_API_NODE`) run on macOS (where `__APPLE__` is defined) but do not compile or link the `ios/` ObjC++ sources. The node build cannot borrow `RN_AUDIO_API_TEST` instead — that flag also switches on gtest-only code (`gtest_prod.h` includes, test `ArrayBuffer` shims). When adding a new OS-selector header, copy the full three-clause guard; an incremental `wpt_tests/build` dir can mask a missing clause for a long time, so verify with a clean `yarn node:build`. Platform glue selected this way lives in `android/src/main/cpp/audioapi/android/` (e.g. `AndroidDecoding`, `AndroidEncoder`, `AndroidRemux`) and `ios/audioapi/ios/core/utils/` (e.g. `IOSDecoding`, `IOSEncoder`, `IOSRemux`) — both picked up automatically by the CMake glob / podspec glob, no build-file edits needed.

---

## Common Build Failure Patterns
Expand All @@ -368,6 +372,7 @@ Resolution pitfalls learned the hard way (both handled inside `package-root.js`)
| New `.cpp` not compiled in tests | Glob picks it up automatically — may need cmake reconfigure | Delete `test/build/` and re-run |
| iOS compile error `unknown type 'id'` | C++ file included ObjC-only header | Compile that file as ObjC++ (separate subspec with `-x objective-c++`) |
| `RCT_NEW_ARCH_ENABLED` undefined on Android | Old RN gradle plugin | Ensure `newArchEnabled=true` in app's `gradle.properties` |
| iOS: `'to_chars' is unavailable: introduced in iOS 16.3` from `formatter_floating_point.h`, instantiated by `std::format<...>` | `std::format` in code compiled for iOS. libc++ availability-gates the whole `<format>` library to iOS 16.3; the podspec minimum is `ios_min_version = '14.0'`. The desktop C++ test build and Android NDK have no such gate, so `yarn test:cpp` passes and only the iOS build fails. | Use `std::string` concatenation / `std::to_string` in `common/cpp` and `ios/`. Zero-pad by hand (`insert(0, n, '0')`). Android-only files (`android/src/main/cpp`) may keep `std::format`. |
| clangd only: `'React/RCTBridgeModule.h' file not found` in `.mm` files | `compile_commands.json` has no framework search path | See *clangd compile database* below — regenerate with `yarn setup:clangd` |

## clangd compile database
Expand Down
7 changes: 7 additions & 0 deletions .claude/skills/expressive-code/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ If the **semantics are complex**, it might be due to one of the following:

Generally, prefer standard, idiomatic names.

C++ namespaces are `snake_case` with every word separated (`encoder_capabilities`,
`recording_file_name`, `ios_file_path`) — never PascalCase, never run-together (`filepath`).

C++ constants are `SCREAMING_CASE` (`POOL_SIZE`, `DRAIN_TIMEOUT_US`). A constant only one class uses is
a private `static constexpr` member of that class — no class-name prefix, the scope already says it.
Only a constant shared by free functions stays file-private in the `.cpp`'s anonymous namespace.

### Frequency of Comments
Use **comments only when necessary**. Add them only when something cannot be expressed easily in code. Their purpose is to make complex fragments easier to understand. Most code fragments are relatively easy to understand simply by **reading them like prose** (as explained above).

Expand Down
4 changes: 3 additions & 1 deletion .claude/skills/post-work-checks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ enum mirrored between C++ and Kotlin — currently `AudioEvent` and `RecorderSta
cross JNI as plain ints and Kotlin maps them back by ordinal, so entry *order* is part of
the contract; the check compares ignoring case and underscores, since the two languages
name entries differently by convention. Add a new pair to the `MIRRORED_ENUMS` table at
the top of the script.
the top of the script. It then runs `check-audio-file-properties-enum-sync.sh`, which does
the same for the `AudioFileProperties` enums that cross JSI into TypeScript (`FileFormat`,
`FileDirectory`, `BitDepth`, `IOSAudioQuality`).

**When**: only when you modify one of those enums or any file that maps event names across
C++/Kotlin/TypeScript. Skip this step if you already ran `validate:fast` (it includes enum sync).
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/post-work-checks/maintenance.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ Review this skill when `pre-push-update` reports changes in:
| `packages/react-native-audio-api/package.json` scripts | Package-level command changes (including per-language lint/format) |
| `lefthook.yml` | Pre-commit / commit-msg hook changes |
| `scripts/validate.sh` | Tier behavior (`--fast` / `--cpp-extended` / `--android` / `--ios` / `--full`), skip rules |
| `scripts/check-audio-enum-sync*` or `packages/react-native-audio-api/scripts/check-audio-events-sync.sh` | Enum sync check details |
| `scripts/check-audio-enum-sync*` or `packages/react-native-audio-api/scripts/check-*-enum-sync.sh` / `check-enum-sync.sh` | Enum sync check details (AudioEvent + AudioFileProperties) |
| `.github/workflows/ci.yml`, `tests.yml`, `cpp-job.yml` | What CI covers vs local validation tiers |
Loading
Loading