Skip to content
Merged
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
31 changes: 19 additions & 12 deletions .claude/skills/build-compilation-dependencies/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ react-native-audio-api/
│ │ ├── macosx/ # macOS .a libs
│ │ ├── ffmpeg_ios/ # FFmpeg .xcframeworks (iOS only)
│ │ ├── include/ # Headers for Opus/Ogg/Vorbis/OpenSSL
│ │ └── include_ffmpeg/ # Headers for FFmpeg
│ │ └── include_ffmpeg/ # FFmpeg headers (downloaded, FFmpeg builds only)
│ ├── common/cpp/test/
│ │ ├── CMakeLists.txt # Standalone test build (no Android/iOS)
│ │ ├── run-tests.sh / filters.sh # smoke|extended|full (+ categories, CI path filters)
Expand Down Expand Up @@ -120,10 +120,14 @@ https://github.com/software-mansion-labs/rn-audio-libs/releases/download/<TAG>/
Current tag: **v3.0.0** (see `scripts/download-prebuilt-binaries.sh`).

The download script is triggered automatically:
- **iOS**: by podspec `prepare_command` during `pod install` **and** by a `script_phase` (`[CP-User] Download RNAudioAPI prebuilt binaries`, `:before_headers`, always-out-of-date) on every Xcode build — downloads `ffmpeg_ios`, `iphoneos`, `iphonesimulator`, `macosx`. `prepare_command` ensures all vendored `.xcframework`s exist when CocoaPods generates `[CP] Copy XCFrameworks` (required for correct FFmpeg linking). The build-time phase handles CI setups that cache `Pods/` independently from `node_modules/` — it is a fast no-op when binaries are already present. **It must use `:before_headers`, not `:before_compile`** — CocoaPods inserts its own `[CP] Copy XCFrameworks` phase right after `Headers`; `:before_compile` would land *after* it. The phase's `output_files` declares both the `-force_load` static libs and the four FFmpeg xcframeworks. The whole phase is skipped only when *both* `DISABLE_AUDIOAPI_STATIC_EXTERNAL_LIBS` and `DISABLE_AUDIOAPI_FFMPEG` are set.
**FFmpeg is opt-in (off by default).** iOS: `ENV['ENABLE_AUDIOAPI_FFMPEG'] = '1'` in the Podfile; Android: `enableAudioapiFFmpeg=true` in `gradle.properties`; Expo: `enableFFmpeg: true`. The pre-1.0 `DISABLE_AUDIOAPI_FFMPEG` / `disableAudioapiFFmpeg` / `disableFFmpeg` flags are **not** read any more and produce no warning either: a leftover disable flag asks for the default, so there is nothing to tell the user, and a 1.0 migration note in the docs covers the switch. Internally every build file and source keys off the positive `$RN_AUDIO_API_FFMPEG_ENABLED` / `RN_AUDIO_API_FFMPEG_ENABLED`. The define is only emitted (`=1`) in an FFmpeg build; `#if RN_AUDIO_API_FFMPEG_ENABLED` reads an undefined macro as `0`, so the test, clangd and WPT builds do not mention it at all. The download script passes `skipffmpeg` in the default build, so neither the `.so`/xcframeworks nor (once published) the `include_ffmpeg.zip` header archive are fetched.

- **iOS**: by podspec `prepare_command` during `pod install` **and** by a `script_phase` (`[CP-User] Download RNAudioAPI prebuilt binaries`, `:before_headers`, always-out-of-date) on every Xcode build — downloads `ffmpeg_ios`, `iphoneos`, `iphonesimulator`, `macosx`. `prepare_command` ensures all vendored `.xcframework`s exist when CocoaPods generates `[CP] Copy XCFrameworks` (required for correct FFmpeg linking). The build-time phase handles CI setups that cache `Pods/` independently from `node_modules/` — it is a fast no-op when binaries are already present. **It must use `:before_headers`, not `:before_compile`** — CocoaPods inserts its own `[CP] Copy XCFrameworks` phase right after `Headers`; `:before_compile` would land *after* it. The phase's `output_files` declares both the `-force_load` static libs and the four FFmpeg xcframeworks. The whole phase is skipped only when `DISABLE_AUDIOAPI_STATIC_EXTERNAL_LIBS` is set *and* FFmpeg is not enabled.
- **Android**: by `downloadPrebuiltBinaries` Gradle task, which runs before `preBuild` — downloads `android`, `jniLibs`

The script is idempotent — it skips any archive whose destination directory already exists, so the per-build invocations are fast no-ops once binaries are present.
The script is idempotent — it skips any archive whose expected artifacts already exist (`artifacts_present`), so the per-build invocations are fast no-ops once binaries are present. A failed download is a hard error (`exit 1`): a silent skip used to surface much later as a missing header or `undefined symbol`.

**FFmpeg headers.** `external/include_ffmpeg/` is not tracked: it is gitignored, negated in `package.json` `files`, and fetched as `include_ffmpeg.zip` from the pinned rn-audio-libs release in the same pass as the FFmpeg binaries (`FFMPEG_HEADERS_ARCHIVE`, presence checked through `FFMPEG_HEADER_MARKERS`, skipped with `skipffmpeg`). The archive is produced by `yarn export` (`scripts/pack_outputs.sh`) in `rn-audio-libs`, which zips every `outputs/*` directory except `include`, so a new release of that repo ships the headers automatically — but every tag the download script pins must carry the asset or FFmpeg builds fail at the download step. Nothing else references the directory outside the gated include-path entries (podspec `pod_target_xcconfig`, Android CMake); `common/cpp/clangd/CMakeLists.txt` adds the path unconditionally, so in a checkout that never enabled FFmpeg clangd shows unresolved `libav*` includes only inside `FfmpegDecoder.cpp`. `FfmpegDecoder.h` includes `libav*` directly, but `DecoderFactory.cpp` is the only consumer and gates that include behind `RN_AUDIO_API_FFMPEG_ENABLED`, every disabled build also drops `FfmpegDecoder.cpp`, and the Gradle prefab header copy excludes `audioapi/external/**`.

Downloaded artifacts land in:
- `common/cpp/audioapi/external/android/<ABI>/` — `.a` static libs for Android ABIs
Expand All @@ -146,7 +150,9 @@ Downloaded artifacts land in:
- `common/cpp/audioapi/compatibility/StableAPI.h` — single public C++ compatibility header for extensions

### Key behaviors
- Feature flags (`newArchEnabled`, `disableAudioapiFFmpeg`) are read from app's `gradle.properties` and forwarded to both CMake and Kotlin `BuildConfig`
- Feature flags (`newArchEnabled`, `enableAudioapiFFmpeg`, `disableAudioapiStaticExternalLibs`) are read from app's `gradle.properties` and forwarded to both CMake and Kotlin `BuildConfig`
- Source exclusion in `android/src/main/cpp/audioapi/CMakeLists.txt` must use `list(FILTER … EXCLUDE REGEX)` — `COMMON_CPP_SOURCES` holds paths relative to that directory, so a `list(REMOVE_ITEM …)` with an absolute path silently matches nothing (this is how the FFmpeg exclusion was a no-op for a long time)
- Third-party include dirs (`external/include`, `external/include_ffmpeg`) are only added when the corresponding library set is enabled
- DSP sources always compiled with `-O3` regardless of overall build type
- Sources gathered with `GLOB_RECURSE CONFIGURE_DEPENDS` — CMake re-runs automatically when files are added/removed
- 16KB page size alignment enabled for Android 15+
Expand Down Expand Up @@ -202,9 +208,9 @@ open apps/fabric-example/ios/FabricExample.xcworkspace
- After changing `rnaa_utils.rb` or `scripts/validate-worklets-version.js`
- When prebuilt binaries need to be re-downloaded (podspec `prepare_command` runs on `pod install`)

**Disable FFmpeg on iOS**:
**Enable FFmpeg on iOS** (off by default; `apps/fabric-example/ios/Podfile` enables it):
```bash
DISABLE_AUDIOAPI_FFMPEG=1 pod install
ENABLE_AUDIOAPI_FFMPEG=1 pod install
```

### Android (fabric-example)
Expand All @@ -215,9 +221,9 @@ yarn workspace fabric-example android
open apps/fabric-example/android
```

**Disable FFmpeg on Android**: set in `android/gradle.properties`:
**Enable FFmpeg on Android** (off by default; `apps/fabric-example/android/gradle.properties` enables it):
```
disableAudioapiFFmpeg=true
enableAudioapiFFmpeg=true
```

**Clean CMake cache** (fixes most mysterious native build failures):
Expand Down Expand Up @@ -258,7 +264,7 @@ Script: [`scripts/validate.sh`](../../../scripts/validate.sh) at monorepo root.
| Prebuilt libs (Opus, FFmpeg, etc.) | **No** | `--android` / `--ios` (via `download-prebuilt-binaries.sh`) |
| TurboModule codegen, worklets linking | **No** | `--android` / `--ios` |

The C++ test build excludes HostObjects, `AudioContext.cpp`, `FFmpegDecoding.cpp`, and worklet nodes — see [C++ Tests](#c-tests-standalone-build) below. Passing `yarn test` alone does not prove native layers compile.
The C++ test build excludes HostObjects, `AudioContext.cpp`, `FfmpegDecoder.cpp`, and worklet nodes — see [C++ Tests](#c-tests-standalone-build) below. Passing `yarn test` alone does not prove native layers compile.

### Shared prebuild phase (before native tiers)

Expand Down Expand Up @@ -314,7 +320,7 @@ CI runs a parallel `cpp-coverage` job via `.github/workflows/cpp-coverage-job.ym
- Sources resolved from `node_modules` (symlinked to `packages/` in yarn workspaces)
- HostObjects, worklets nodes, AudioContext, and FfmpegDecoder are excluded from the test build
- HostObject **headers** still compile under `RN_AUDIO_API_TEST`; only their `.cpp` bodies are missing. When a test first links core code that constructs one (e.g. `AudioRecorderCallback` → `AudioBufferHostObject`), the linker fails on the constructor. Fix it with a stub translation unit under `test/src/` that defines just the constructors (see `test/src/AudioBufferHostObjectStub.cpp`), never with an `#if RN_AUDIO_API_TEST` test double inside the production header. The static lib also compiles `jsi/jsi.cpp` so `jsi::HostObject`'s virtuals resolve
- Compile definitions: `RN_AUDIO_API_ENABLE_WORKLETS=0`, `RN_AUDIO_API_TEST=1`, `RN_AUDIO_API_FFMPEG_DISABLED=1`
- Compile definitions: `RN_AUDIO_API_ENABLE_WORKLETS=0`, `RN_AUDIO_API_TEST=1` (FFmpeg is off because the define is simply absent)
- 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
Expand Down Expand Up @@ -348,7 +354,7 @@ Resolution pitfalls learned the hard way (both handled inside `package-root.js`)

| Flag | Android (CMake) | iOS (podspec) | Tests |
|---|---|---|---|
| `RN_AUDIO_API_FFMPEG_DISABLED` | `-DRN_AUDIO_API_FFMPEG_DISABLED` | `-DRN_AUDIO_API_FFMPEG_DISABLED=1` | Always set to 1 |
| `RN_AUDIO_API_FFMPEG_ENABLED` | `-DRN_AUDIO_API_FFMPEG_ENABLED=1` (only when `enableAudioapiFFmpeg=true`) | `-DRN_AUDIO_API_FFMPEG_ENABLED=1` (only when `ENABLE_AUDIOAPI_FFMPEG=1`) | Not set (off) |
| `RN_AUDIO_API_ENABLE_WORKLETS` | `-DRN_AUDIO_API_ENABLE_WORKLETS=1/0` | `-DRN_AUDIO_API_ENABLE_WORKLETS=1` | Always set to 0 |
| `RCT_NEW_ARCH_ENABLED` | `-DRCT_NEW_ARCH_ENABLED` | `-DRCT_NEW_ARCH_ENABLED` | Not set |
| `HAVE_ARM_NEON_INTRINSICS` | Set by CMake SIMD detection | Set by Xcode/Clang for arm64 | Set by CMake SIMD detection |
Expand All @@ -366,7 +372,8 @@ Resolution pitfalls learned the hard way (both handled inside `package-root.js`)
|---|---|---|
| `file not found: libopus.a` | Prebuilt binaries not downloaded | Run `pod install` (iOS) or Gradle build (triggers download task) |
| `No such module 'RNAudioAPI'` | Pod not installed | `cd apps/fabric-example/ios && pod install` |
| `undefined symbol: av_*` | FFmpeg .so not in jniLibs | Build triggers download; verify `disableAudioapiFFmpeg` not set unexpectedly |
| `undefined symbol: av_*` | FFmpeg .so not in jniLibs | Build triggers download; verify `enableAudioapiFFmpeg=true` is set (FFmpeg is off by default) |
| `'libavcodec/avcodec.h' file not found` | FFmpeg include dir is gated on the enable flag; something compiled `FfmpegDecoder.cpp` in a disabled build | Check the source filter (`list(FILTER … FfmpegDecoder\.cpp$)` / podspec `exclude_files`) picked the file up |
| CMake error on clean build | Stale `.cxx` cache | `rm -rf packages/react-native-audio-api/android/.cxx` |
| Test build: `Cannot open include file: audioapi/...` | Node modules not linked | `yarn install` from root, then re-run tests |
| New `.cpp` not compiled in tests | Glob picks it up automatically — may need cmake reconfigure | Delete `test/build/` and re-run |
Expand Down
19 changes: 8 additions & 11 deletions .claude/skills/build-compilation-dependencies/build-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
}
def isFFmpegDisabled() {
return rootProject.hasProperty("disableAudioapiFFmpeg") && rootProject.getProperty("disableAudioapiFFmpeg") == "true"
def isFFmpegEnabled() {
return isPropertyTrue("enableAudioapiFFmpeg")
}
```

### Forwarding flags to CMake

```groovy
"-DRN_AUDIO_API_FFMPEG_DISABLED=${IS_RN_AUDIO_API_FFMPEG_DISABLED}"
"-DRN_AUDIO_API_FFMPEG_ENABLED=${IS_RN_AUDIO_API_FFMPEG_ENABLED}"
"-DRN_AUDIO_API_WORKLETS_ENABLED=${isWorkletsAvailable}"
"-DIS_NEW_ARCHITECTURE_ENABLED=${IS_NEW_ARCHITECTURE_ENABLED}"
```

### Forwarding flags to Kotlin via BuildConfig

```groovy
buildConfigField "boolean", "RN_AUDIO_API_FFMPEG_DISABLED", isFFmpegDisabled().toString()
buildConfigField "boolean", "RN_AUDIO_API_FFMPEG_ENABLED", IS_RN_AUDIO_API_FFMPEG_ENABLED.toString()
buildConfigField "boolean", "RN_AUDIO_API_ENABLE_WORKLETS", "${isWorkletsAvailable}"
```

Expand Down Expand Up @@ -123,10 +123,8 @@ file(GLOB_RECURSE COMMON_CPP_SOURCES CONFIGURE_DEPENDS "${COMMON_CPP_DIR}/audioa
### FFmpeg conditional exclusion

```cmake
if(RN_AUDIO_API_FFMPEG_DISABLED)
list(REMOVE_ITEM COMMON_CPP_SOURCES
"${COMMON_CPP_DIR}/audioapi/decoding/backends/FfmpegDecoder.cpp"
)
if(NOT RN_AUDIO_API_FFMPEG_ENABLED) # FILTER, not REMOVE_ITEM: the list holds relative paths
list(FILTER COMMON_CPP_SOURCES EXCLUDE REGEX ".*FfmpegDecoder\\.cpp$")
endif()
```

Expand Down Expand Up @@ -200,10 +198,10 @@ s.xcconfig = {
CocoaPods handles embedding and signing automatically:

```ruby
s.ios.vendored_frameworks = $RN_AUDIO_API_FFMPEG_DISABLED ? [] : [
s.ios.vendored_frameworks = $RN_AUDIO_API_FFMPEG_ENABLED ? [
'common/cpp/audioapi/external/ffmpeg_ios/libavcodec.xcframework',
...
]
] : []
```

### Header search paths split between pod target and consumers
Expand Down Expand Up @@ -270,7 +268,6 @@ list(REMOVE_ITEM RNAUDIOAPI_SRC ... "FfmpegDecoder.cpp") #
```cmake
add_compile_definitions(RN_AUDIO_API_ENABLE_WORKLETS=0)
add_compile_definitions(RN_AUDIO_API_TEST=1)
add_compile_definitions(RN_AUDIO_API_FFMPEG_DISABLED=1)
```

Use `RN_AUDIO_API_TEST` in source code to conditionally compile test-only hooks.
Expand Down
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,13 @@ openssl-prebuilt/
prebuilt_libs/
output/

# External libs
# External libs (prebuilt binaries and FFmpeg headers fetched by
# scripts/download-prebuilt-binaries.sh from the rn-audio-libs releases).
packages/react-native-audio-api/android/src/main/jniLibs/
packages/react-native-audio-api/common/cpp/audioapi/external/**/*.a
packages/react-native-audio-api/common/cpp/audioapi/external/*.xcframework
packages/react-native-audio-api/common/cpp/audioapi/external/ffmpeg_ios/
packages/react-native-audio-api/common/cpp/audioapi/external/include_ffmpeg/

# Clangd cache
.cache
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ check out the [Getting Started](https://docs.swmansion.com/react-native-audio-ap
Decode m4a/mp4/aac/ogg/opus audio files, allowing for playback of these formats in the audio graph.

- **HLS streaming support** 🌊 <br />
Stream audio from HLS sources, allowing for playback of live audio streams or pre-recorded audio files.
Stream audio from HLS sources, allowing for playback of live audio streams or pre-recorded audio files. Requires the optional FFmpeg build flag — see the [Prebuilt libraries support](https://docs.swmansion.com/react-native-audio-api/docs/other/prebuilt-libraries-support) docs.

### <a href="https://github.com/software-mansion/react-native-audio-api/releases/tag/0.7.0"><img src="https://img.shields.io/badge/Released_in-0.7.0-green" /></a>

Expand All @@ -107,7 +107,7 @@ We are open to new ideas and general feedback. If you want to share your opinion

## License

react-native-audio-api library is licensed under [The MIT License](./LICENSE). Some of the source code uses implementation directly copied from Webkit and utilizes FFmpeg binaries. Copyrights are held by respective organizations, check [THIRD_PARTY](./THIRD_PARTY.md) file for further details.
react-native-audio-api library is licensed under [The MIT License](./LICENSE). Some of the source code uses implementation directly copied from Webkit, and builds that opt into FFmpeg link FFmpeg binaries. Copyrights are held by respective organizations, check [THIRD_PARTY](./THIRD_PARTY.md) file for further details.

## Credits

Expand Down
4 changes: 2 additions & 2 deletions THIRD_PARTY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ WebKit, 3 Clause BSD
Copyright (c) The WebKit Authors
[https://github.com/WebKit/webkit](https://github.com/WebKit/webkit)

In addition we utilize FFmpeg library under GNU Lesser General Public License (LGPL) version 2.1 or later
Builds that enable the optional FFmpeg flag (`enableFFmpeg` / `ENABLE_AUDIOAPI_FFMPEG` / `enableAudioapiFFmpeg`, off by default) link the FFmpeg library under GNU Lesser General Public License (LGPL) version 2.1 or later

- Source: https://github.com/FFmpeg/FFmpeg/releases/tag/n8.0
- Used in audio decoding module [packages/react-native-audio-api/common/cpp/audioapi/decoding/backends/FfmpegDecoder.cpp](https://github.com/software-mansion/react-native-audio-api/blob/main/packages/react-native-audio-api/common/cpp/audioapi/decoding/backends/FfmpegDecoder.cpp)
- Instruction for relinking: [packages/react-native-audio-api/common/cpp/audioapi/libs/ffmpeg/INSTRUCTIONS.md](https://github.com/software-mansion/react-native-audio-api/blob/main/packages/react-native-audio-api/common/cpp/audioapi/libs/ffmpeg/INSTRUCTIONS.md)
- Instruction for relinking: [packages/react-native-audio-api/common/cpp/audioapi/libs/ffmpeg/relinking.md](https://github.com/software-mansion/react-native-audio-api/blob/main/packages/react-native-audio-api/common/cpp/audioapi/libs/ffmpeg/relinking.md)

Other LGPL components may be included as dependencies.
The full text of the LGPL license is included in lgplv3 file.
3 changes: 3 additions & 0 deletions apps/fabric-example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,6 @@ edgeToEdgeEnabled=true
# Starting from AGP 10.x these opt outs will be removed.
android.builtInKotlin=false
android.newDsl=false

# The example app exercises remote streaming / HLS demos, which need FFmpeg (off by default).
enableAudioapiFFmpeg=true
2 changes: 2 additions & 0 deletions apps/fabric-example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
ENV['RCT_NEW_ARCH_ENABLED'] = '1'
# The example app exercises remote streaming / HLS demos, which need FFmpeg (off by default).
ENV['ENABLE_AUDIOAPI_FFMPEG'] = '1'

# Uncomment when building for macOS with Catalyst
# ENV['RCT_BUILD_FROM_SOURCE'] = '1'
Expand Down
2 changes: 1 addition & 1 deletion apps/fabric-example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2514,6 +2514,6 @@ SPEC CHECKSUMS:
SocketRocket: 37aec555668fb852ec12e3c0de59a86ca58f0871
Yoga: d1c536142c5ff8ec8cd856ab2a7c227a1d875c8e

PODFILE CHECKSUM: 2fbbdb3fb754264aa345dc912d51c3c6d295cdd9
PODFILE CHECKSUM: 238dee53a572b493b134736d9cb0ed6197a22876

COCOAPODS: 1.17.0
4 changes: 4 additions & 0 deletions packages/audiodocs/docs/fundamentals/getting-started.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Install the `react-native-audio-api` package from npm:

Add `react-native-audio-api` expo plugin to your `app.json` or `app.config.js`. Read more about the plugin [here](../other/audio-api-plugin.mdx)!

:::tip Streaming remote audio?
FFmpeg, which powers remote URL streaming and HLS playback, is not linked by default. If your app needs it, enable it as described in [Prebuilt libraries support](../other/prebuilt-libraries-support.mdx#enabling-ffmpeg).
:::

<details>
<summary>app.json</summary>
```javascript
Expand Down
Loading
Loading