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
89 changes: 62 additions & 27 deletions .claude/skills/build-compilation-dependencies/SKILL.md

Large diffs are not rendered by default.

37 changes: 14 additions & 23 deletions .claude/skills/build-compilation-dependencies/build-details.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@

## Android: `build.gradle` — detailed analysis

### Feature flags (set by app's `gradle.properties`)
### Feature flags (`newArchEnabled` from `gradle.properties`, build options from the app's `package.json`)

```groovy
def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
def isNewArchitectureEnabled() { // on unless the app sets newArchEnabled=false
return !rootProject.hasProperty("newArchEnabled") || rootProject.getProperty("newArchEnabled") != "false"
}
def isFFmpegEnabled() {
return isPropertyTrue("enableAudioapiFFmpeg")
return booleanBuildOption("enableFFmpeg") // app package.json "react-native-audio-api" section
}
```

Expand Down Expand Up @@ -179,30 +179,21 @@ ReactAndroid/src/main/jni/... — TurboModule JNI headers

miniaudio's implementation file must be compiled as Objective-C++ on iOS (it uses Apple APIs). CocoaPods can't set per-file compiler flags the same way CMake can, so it gets its own subspec with `compiler_flags = "-x objective-c++"`.

### Prebuilt binaries linked via `-force_load`
### All prebuilt binaries listed in `s.ios.vendored_frameworks`

All static libs (Opus, Ogg, Vorbis, OpenSSL) use `-force_load` to prevent dead-stripping of symbols needed at runtime but not referenced at link time:
Each library is a `<lib>.xcframework` downloaded into `external/xcframeworks/` — the
static Opus/Ogg/Vorbis ones unless `DISABLE_AUDIOAPI_STATIC_EXTERNAL_LIBS`, the dynamic FFmpeg ones
only with FFmpeg enabled. CocoaPods' `[CP] Copy XCFrameworks` picks the device / simulator /
Catalyst slice, links the static libraries normally (no `-force_load`; every symbol used is
referenced from miniaudio's decoders) and embeds + signs the FFmpeg frameworks:

```ruby
s.xcconfig = {
'OTHER_LDFLAGS' => %W[
-force_load #{lib_dir}/libopus.a
-force_load #{lib_dir}/libogg.a
...
].join(" ")
}
s.ios.vendored_frameworks = vendored_xcframeworks.map { |xcframework| "#{external_dir_relative}/xcframeworks/#{xcframework}" }
```

### FFmpeg xcframeworks listed in `s.ios.vendored_frameworks`

CocoaPods handles embedding and signing automatically:

```ruby
s.ios.vendored_frameworks = $RN_AUDIO_API_FFMPEG_ENABLED ? [
'common/cpp/audioapi/external/ffmpeg_ios/libavcodec.xcframework',
...
] : []
```
They are the same release assets `Package.swift` downloads through `binaryTarget(url:checksum:)`.
FFmpeg headers still come from `external/include_ffmpeg/` (`include_ffmpeg.zip`) for CocoaPods;
SwiftPM uses the header-only `FFmpegHeaders.xcframework` instead.

### Header search paths split between pod target and consumers

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Review this skill when `pre-push-update` reports changes in:
| `android/CMakeLists.txt` | SIMD detection, RN version flag workaround |
| `android/src/main/cpp/audioapi/CMakeLists.txt` | Source glob patterns, FFmpeg exclusion, DSP `-O3` targets, IMPORTED lib list, include paths |
| `android/build.gradle` | Feature flag detection, CMake arg forwarding, BuildConfig fields, worklets task dependency, packaging options |
| `RNAudioAPI.podspec` | Subspecs table, `miniaudio_impl` workaround, `-force_load` list, xcframeworks list, `rnaa_utils.rb` dynamic paths |
| `RNAudioAPI.podspec` | Subspecs table, `miniaudio_impl` workaround, vendored xcframeworks list (mirrors `Package.swift` binary targets), `rnaa_utils.rb` dynamic paths |
| `apps/fabric-example/ios/Podfile` | New Architecture enablement, minimum iOS version helper |
| `common/cpp/test/CMakeLists.txt` | Excluded sources list, compile definitions, GoogleTest fetch URL, include paths, `ENABLE_COVERAGE` |
| `common/cpp/test/run-coverage.sh` | Coverage build dir, Clang CC/CXX defaults, llvm-profdata/llvm-cov report+HTML, `GITHUB_STEP_SUMMARY`, ignore regexes, smoke filter via `filters.sh` |
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,16 @@ output/
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/xcframeworks/
# Pre-xcframework download layout, still present in older checkouts.
packages/react-native-audio-api/common/cpp/audioapi/external/ffmpeg_ios/
packages/react-native-audio-api/common/cpp/audioapi/external/include_ffmpeg/

# SwiftPM local state (Package.swift is only resolved inside an app's SPM build)
.build/
.swiftpm/
packages/react-native-audio-api/Package.resolved

# Clangd cache
.cache

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

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
Builds that enable the optional FFmpeg flag (`enableFFmpeg: true` in the Expo plugin options, or `"enableFFmpeg": true` in the `"react-native-audio-api"` section of the app's package.json in a bare React Native app; 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)
Expand Down
2 changes: 0 additions & 2 deletions apps/fabric-example/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,3 @@ edgeToEdgeEnabled=true
android.builtInKotlin=false
android.newDsl=false

# The example app exercises remote streaming / HLS demos, which need FFmpeg (off by default).
enableAudioapiFFmpeg=true
2 changes: 0 additions & 2 deletions apps/fabric-example/ios/Podfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
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
3 changes: 3 additions & 0 deletions apps/fabric-example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
"engines": {
"node": ">=22.11.0"
},
"react-native-audio-api": {
"enableFFmpeg": true
},
"reactNativeVectorIcons": {
"fontDir": "./assets/fonts"
}
Expand Down
8 changes: 6 additions & 2 deletions packages/audiodocs/docs/other/audio-api-plugin.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ interface Options {
androidForegroundService: boolean;
androidFSTypes: string[];
androidFSStopWithTask: boolean;
enableFFmpeg: boolean;
disableStaticExternalLibs: boolean;
enableFFmpeg?: boolean;
disableStaticExternalLibs?: boolean;
}
```

Expand Down Expand Up @@ -160,3 +160,7 @@ Links the prebuilt FFmpeg libraries into the app, which enables remote URL strea
Defaults to `false`.

Removes the bundled Opus / Ogg / Vorbis static libraries. See [Prebuilt libraries support](./prebuilt-libraries-support.mdx#disabling-other-external-libraries).

:::note
`expo prebuild` writes `enableFFmpeg` and `disableStaticExternalLibs` into the `react-native-audio-api` section of the app's `package.json`, which every build (CocoaPods, Swift Package Manager, Gradle) reads. The one given here overwrites it on every prebuild, so keep each option in one place only.
:::
172 changes: 122 additions & 50 deletions packages/audiodocs/docs/other/prebuilt-libraries-support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,71 +7,143 @@ import TabItem from '@theme/TabItem';

# Prebuilt libraries support

`react-native-audio-api` can link a number of prebuilt third-party libraries that are downloaded at build time. They enable additional decoding and streaming features, but also add several megabytes to the resulting application binary, so each one can be toggled with a build flag.
`react-native-audio-api` can link a number of prebuilt third-party libraries that are downloaded at build time. They enable additional decoding and streaming features, but also add several megabytes to the resulting application binary, so each one can be toggled with a build option.

| Library set | Default | Flag | What it adds |
| Library set | Default | Option | What it adds |
| :---- | :---: | :---: | :---- |
| FFmpeg (`libavcodec`, `libavformat`, `libavutil`, `libswresample`) | **off** | `enableFFmpeg` | Remote URL streaming (HTTP byte ranges), HLS (`.m3u8`), remote URL metadata — see [Runtime flags](./runtime-flags#where-ffmpeg-is-used). |
| Static libs (`libopus`, `libopusfile`, `libogg`, `libvorbis`, `libvorbisenc`, `libvorbisfile`) | **on** | `disableStaticExternalLibs` | Decoding `ogg`, `opus`, `oga` files |

The flags are independent and can be combined.
The options are independent and can be combined.

:::tip Reducing app size
Leaving FFmpeg off (the default) and setting `disableStaticExternalLibs` can shave several MB off the final app size across all ABIs / device slices. Pick the set of formats you actually need and turn the rest off.
:::

:::info FFmpeg is opt-in
Apps that would like to decode HLS or make use of URL streaming have to enable it explicitly (see below). Use [`isFfmpegEnabled`](./runtime-flags#isffmpegenabled) to branch at runtime.
:::

## Setting the options

Every build (CocoaPods, [Swift Package Manager](./swift-package-manager.mdx) and Gradle) reads the
options from a `react-native-audio-api` section of the app's `package.json`. In an Expo app, pass
them as [Expo plugin options](./audio-api-plugin.mdx#enableffmpeg) instead and `expo prebuild`
writes that section for you.

<Tabs groupId="app-type">
<TabItem value="expo" label="Expo" default>

`app.json` / `app.config.js`:

```json
"plugins": [
[
"react-native-audio-api",
{
"enableFFmpeg": true,
"disableStaticExternalLibs": false
}
]
]
```

</TabItem>
<TabItem value="bare" label="React Native">

The app's `package.json`:

```json
"react-native-audio-api": {
"enableFFmpeg": true,
"disableStaticExternalLibs": false
}
```

</TabItem>
</Tabs>
Comment thread
closetcaiman marked this conversation as resolved.

After changing them:

<Tabs groupId="app-type">
<TabItem value="expo" label="Expo" default>

Run `npx expo prebuild`. It updates the `package.json` section from the plugin options and then runs `pod install`.

</TabItem>
<TabItem value="bare" label="React Native">

- **CocoaPods**: run `pod install`.
- **Swift Package Manager**: run `npx react-native spm` in `ios`. If the app was already built, [reset the package cache](./swift-package-manager.mdx#build-options) first.
- **Android**: just rebuild. Gradle reads the file on every build.

</TabItem>
</Tabs>

## Enabling FFmpeg

<Tabs queryString="platform">
<TabItem value="expo" label="Expo" default>
Add an entry in the [Audio API Expo plugin](./audio-api-plugin.mdx) configuration:

```
"enableFFmpeg": true
```
</TabItem>
<TabItem value="iOS" label="iOS">
Add an entry to the Podfile (before the targets), then run `pod install`:

```
ENV['ENABLE_AUDIOAPI_FFMPEG'] = '1'
```
</TabItem>
<TabItem value="android" label="Android">
In `gradle.properties`:

```
enableAudioapiFFmpeg=true
```
</TabItem>
<Tabs groupId="app-type">
<TabItem value="expo" label="Expo" default>

`app.json` / `app.config.js`:

```json
"plugins": [
[
"react-native-audio-api",
{
"enableFFmpeg": true
}
]
]
```

</TabItem>
<TabItem value="bare" label="React Native">

The app's `package.json`:

```json
"react-native-audio-api": {
"enableFFmpeg": true
}
```

</TabItem>
</Tabs>

With FFmpeg enabled, the FFmpeg binaries are downloaded during `pod install` / the first Gradle build and linked into the app. FFmpeg is distributed under the LGPL — see `THIRD_PARTY.md` in the repository for the notice that applies to FFmpeg-enabled builds.
With FFmpeg enabled, the FFmpeg binaries are downloaded during `pod install` / Swift package resolution / the first Gradle build and linked into the app. FFmpeg is distributed under the LGPL — see `THIRD_PARTY.md` in the repository for the notice that applies to FFmpeg-enabled builds.

## Disabling other external libraries

This removes the bundled Opus / Ogg / Vorbis static libraries. With this flag enabled, attempts to decode `.ogg`, `.opus`, or `.oga` files through `AudioContext.decodeAudioData` (or any other decoding path) will return an error — those formats are handled inside miniaudio via the now-disabled custom backends.

<Tabs queryString="platform">
<TabItem value="expo" label="Expo" default>
Add an entry in the [Audio API Expo plugin](./audio-api-plugin.mdx) configuration:

```
"disableStaticExternalLibs": true
```
</TabItem>
<TabItem value="iOS" label="iOS">
Add an entry to the Podfile:

```
ENV['DISABLE_AUDIOAPI_STATIC_EXTERNAL_LIBS'] = '1'
```
</TabItem>
<TabItem value="android" label="Android">
In `gradle.properties`:

```
disableAudioapiStaticExternalLibs=true
```
</TabItem>
<Tabs groupId="app-type">
<TabItem value="expo" label="Expo" default>

`app.json` / `app.config.js`:

```json
"plugins": [
[
"react-native-audio-api",
{
"disableStaticExternalLibs": true
}
]
]
```

</TabItem>
<TabItem value="bare" label="React Native">

The app's `package.json`:

```json
"react-native-audio-api": {
"disableStaticExternalLibs": true
}
```

</TabItem>
</Tabs>

This removes the bundled Opus / Ogg / Vorbis static libraries. With this option enabled, attempts to decode `.ogg`, `.opus`, or `.oga` files through `AudioContext.decodeAudioData` (or any other decoding path) will return an error — those formats are handled inside miniaudio via the now-disabled custom backends.
34 changes: 34 additions & 0 deletions packages/audiodocs/docs/other/swift-package-manager.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
id: swift-package-manager
sidebar_label: Swift Package Manager (iOS)
---

# Swift Package Manager (iOS)

Swift Package Manager (SwiftPM) is an experimental alternative to CocoaPods for building React
Native iOS apps. To find out what it requires and how to switch an app to it, see the
[React Native 0.87 announcement](https://reactnative.dev/blog/2026/08/11/react-native-0.87).

`react-native-audio-api` ships a `Package.swift`, so in a SwiftPM app it is autolinked like any
other library. There is no extra setup step. Swift Package Manager downloads the prebuilt
Opus / Ogg / Vorbis (and, if enabled, FFmpeg) libraries while it resolves packages.

## Build options

[`enableFFmpeg` and `disableStaticExternalLibs`](./prebuilt-libraries-support.mdx#setting-the-options)
are read from the `react-native-audio-api` section of the app's `package.json`, as with CocoaPods
and Gradle.

:::caution
Swift Package Manager caches an evaluated `Package.swift` by its contents, not by the files it
reads, so a change to these options is ignored until that cache is reset. After changing them:

```bash
rm -rf ~/Library/Caches/org.swift.swiftpm/manifests # or Xcode ▸ File ▸ Packages ▸ Reset Package Caches
cd ios && npx react-native spm
```

To confirm an `enableFFmpeg` change took effect, check
[`isFfmpegEnabled()`](./runtime-flags#isffmpegenabled) at runtime. There is no runtime flag for
`disableStaticExternalLibs`.
:::
Loading
Loading