diff --git a/.claude/skills/build-compilation-dependencies/SKILL.md b/.claude/skills/build-compilation-dependencies/SKILL.md index 957f9ef7d..dcfd41f46 100644 --- a/.claude/skills/build-compilation-dependencies/SKILL.md +++ b/.claude/skills/build-compilation-dependencies/SKILL.md @@ -30,6 +30,7 @@ react-native-audio-api/ │ │ └── CMakeLists.txt # Actual Android C++ build target │ ├── common/cpp/audioapi/ # Shared C++ (used by all platforms) │ │ ├── decoding/ # Decoder factory, backends, SeekDecoderDaemon, AudioDecoding, AudioFileConcatenator +│ │ ├── encoding/ # AudioEncoder interface, EncoderCapabilities, OS encoder/remux selector headers │ │ ├── libs/ # Third-party wrappers (FFmpeg, miniaudio, pffft, …) │ │ └── external/ # Prebuilt binaries per platform │ │ ├── android/ # .a static libs (Opus, Ogg, Vorbis, OpenSSL) @@ -291,6 +292,8 @@ For `MockAudioEventHandlerRegistry`, `TestableXxx` pattern, and full CMakeLists | `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`): common code reaches platform implementations through `#if defined(__ANDROID__)` / `#elif defined(__APPLE__) && !defined(RN_AUDIO_API_TEST)` dispatch. The Apple branch must exclude `RN_AUDIO_API_TEST` because desktop test builds run on macOS (where `__APPLE__` is defined) but do not compile the `ios/` sources. 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 diff --git a/.claude/skills/post-work-checks/SKILL.md b/.claude/skills/post-work-checks/SKILL.md index 951f994fc..40cc02bec 100644 --- a/.claude/skills/post-work-checks/SKILL.md +++ b/.claude/skills/post-work-checks/SKILL.md @@ -119,13 +119,13 @@ yarn test # from monorepo root — runs test:js + test:cpp **When**: after any change to C++ files or TypeScript files in `src/`. Prefer this for a quick local test loop covering both TS and C++ logic; run `yarn validate:fast` before opening a PR. -### AudioEvent enum sync check +### Enum sync check ```bash yarn check-audio-enum-sync ``` -**When**: only when you modify the `AudioEvent` enum or any file that maps event names across C++/Kotlin/TypeScript. Skip this step if you already ran `validate:fast` (it includes enum sync). +**When**: when you modify `AudioEvent`, `FileFormat` / `AudioFileProperties::Format`, or other JSI-crossing recorder enums (`FileDirectory`, `BitDepth`, `IOSAudioQuality`). Skip if you already ran `validate:fast` (it includes enum sync). --- diff --git a/.claude/skills/post-work-checks/maintenance.md b/.claude/skills/post-work-checks/maintenance.md index 1073cb524..3a866f18a 100644 --- a/.claude/skills/post-work-checks/maintenance.md +++ b/.claude/skills/post-work-checks/maintenance.md @@ -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` / `--graph` / `--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`, `graph-tests.yml` | What CI covers vs local validation tiers | diff --git a/.claude/skills/thread-safety-itc/SKILL.md b/.claude/skills/thread-safety-itc/SKILL.md index 86d1c4bd6..16919186f 100644 --- a/.claude/skills/thread-safety-itc/SKILL.md +++ b/.claude/skills/thread-safety-itc/SKILL.md @@ -149,6 +149,8 @@ See the `utilities` skill for full API. **Pitfall — file writer / recorder shutdown:** `TaskOffloader::shutdown()` drains the SPSC queue before joining the worker. Call it (or destroy the offloader) only after `isFileOpen_` is cleared so the audio thread stops enqueueing. Otherwise rotated or closed M4A segments lose seconds of buffered audio. Types with a `.slot` member use `slot == size_t max` as the shutdown sentinel. +**Pitfall — the task type cannot be a nested struct.** `TaskOffloader` constrains `T` with `std::default_initializable`. A task struct carrying default member initializers (which the `.slot` sentinel requires) does *not* satisfy that constraint while its enclosing class is still incomplete, so `using Offloader = TaskOffloader;` inside the class fails to compile with "constraints not satisfied". Making the struct `public` does not help — it is not an access problem. Declare the task type at **namespace scope** instead (`PendingFileWrite`, `PendingCallbackFrames`). Dropping the initializers to satisfy the constraint is worse: `T{}` would then produce `slot == 0`, a valid slot index, making the shutdown sentinel indistinguishable from real work. + --- ## Driver synchronization (layered model) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3051bc4ff..bb45c5ad8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: check-audio-enum-sync: uses: ./.github/workflows/ci-check.yml with: - name: Check AudioEvent enum sync + name: Check enum sync run: yarn check-audio-enum-sync build-audio-api: diff --git a/apps/common-app/src/demos/Record/Record.tsx b/apps/common-app/src/demos/Record/Record.tsx index 6bcb7df73..140e9bab0 100644 --- a/apps/common-app/src/demos/Record/Record.tsx +++ b/apps/common-app/src/demos/Record/Record.tsx @@ -20,6 +20,12 @@ import RecordingVisualization from './RecordingVisualization'; import Status from './Status'; import { RecordingState } from './types'; +const RECORDING_EXTENSION = FileFormat.Wav; + +const RECORDING_EXTENSION_NAME_MAP = { + [FileFormat.Wav]: 'wav', + [FileFormat.M4A]: 'm4a', +} const Record: FC = () => { const [state, setState] = useState(RecordingState.Idle); const [hasPermissions, setHasPermissions] = useState(false); @@ -130,7 +136,11 @@ const Record: FC = () => { return; } - const outputPath = info.paths[0].replace(/[^/]+$/, 'recording.m4a'); + const extension = RECORDING_EXTENSION_NAME_MAP[RECORDING_EXTENSION]; + const outputPath = info.paths[0].replace( + /[^/]+$/, + `recording.${extension}` + ); const finalPath = await concatAudioFiles(info.paths, outputPath); const audioBuffer = await audioContext.decodeAudioData(finalPath); @@ -262,7 +272,7 @@ const Record: FC = () => { }, [onPauseRecording, onResumeRecording]); useEffect(() => { - Recorder.enableFileOutput({ rotateIntervalBytes: 1_000_000, format: FileFormat.M4A }); + Recorder.enableFileOutput({ rotateIntervalBytes: 1_000_000, format: RECORDING_EXTENSION }); return () => { stopPlayback(); diff --git a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj index d23589447..ce1b51eae 100644 --- a/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj +++ b/apps/fabric-example/ios/FabricExample.xcodeproj/project.pbxproj @@ -8,11 +8,10 @@ /* Begin PBXBuildFile section */ 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; - 293FA0CC5BDDACD9944867F6 /* libPods-FabricExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CC491462FE5F58FA7896F2C /* libPods-FabricExample.a */; }; + 62FAAAE962F12826D5D6BFE5 /* libPods-FabricExample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B9D63EB1C223D98783472EC /* libPods-FabricExample.a */; }; 761780ED2CA45674006654EE /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 761780EC2CA45674006654EE /* AppDelegate.swift */; }; 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; 88485BE83AA320DC6673875F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */; }; - 8E0FEE6ECB6F4EB7C6068C44 /* libPods-FabricExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EE404288C652F11542C27289 /* libPods-FabricExampleTests.a */; }; A1B2C3D41E00000100A0A001 /* AudioEngineTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D61E00000100A0A001 /* AudioEngineTests.mm */; }; A1B2C3E01E00000100A0A001 /* AudioSessionManagerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3E11E00000100A0A001 /* AudioSessionManagerTests.mm */; }; A1B2C3F01E00000100A0A001 /* SystemNotificationManagerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3F11E00000100A0A001 /* SystemNotificationManagerTests.mm */; }; @@ -21,7 +20,8 @@ A1B2C4201E00000100A0A001 /* IOSAudioRecorderTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4211E00000100A0A001 /* IOSAudioRecorderTests.mm */; }; A1B2C4301E00000100A0A001 /* AudioAPIModuleTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C4311E00000100A0A001 /* AudioAPIModuleTests.mm */; }; B2C3D541E00000100A0A001 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2C3D511E00000100A0A001 /* AppDelegate.swift */; }; - B2C3D551E00000100A0A001 /* libPods-FabricExampleTestHost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2C3D531E00000100A0A001 /* libPods-FabricExampleTestHost.a */; }; + C606AD359FD803D57BBEE4A3 /* libPods-FabricExampleTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6DBC9BF3F78155A450934DCB /* libPods-FabricExampleTests.a */; }; + CAB0C4BA36F7D9BEC3BBEB24 /* libPods-FabricExampleTestHost.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6ABABA247EF565DEE43160D2 /* libPods-FabricExampleTestHost.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -39,13 +39,15 @@ 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = FabricExample/Images.xcassets; sourceTree = ""; }; 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = FabricExample/Info.plist; sourceTree = ""; }; 13B07FB81A68108700A75B9A /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = PrivacyInfo.xcprivacy; path = FabricExample/PrivacyInfo.xcprivacy; sourceTree = ""; }; - 2072ECDFE10D5F4F44D1DA68 /* Pods-FabricExampleTestHost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTestHost.release.xcconfig"; path = "Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost.release.xcconfig"; sourceTree = ""; }; - 3CC491462FE5F58FA7896F2C /* libPods-FabricExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FabricExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - 411D0E3C93126EA2C4BF29BA /* Pods-FabricExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTests.release.xcconfig"; path = "Target Support Files/Pods-FabricExampleTests/Pods-FabricExampleTests.release.xcconfig"; sourceTree = ""; }; - 5D7A8AB58ACD5276FDF288D0 /* Pods-FabricExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExample.release.xcconfig"; path = "Target Support Files/Pods-FabricExample/Pods-FabricExample.release.xcconfig"; sourceTree = ""; }; - 71E1415974092A22784C6F6A /* Pods-FabricExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-FabricExampleTests/Pods-FabricExampleTests.debug.xcconfig"; sourceTree = ""; }; + 22FEABAF3B14E34993D0E1DD /* Pods-FabricExampleTestHost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTestHost.debug.xcconfig"; path = "Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost.debug.xcconfig"; sourceTree = ""; }; + 28414E4977AA1542F647A6FF /* Pods-FabricExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExample.release.xcconfig"; path = "Target Support Files/Pods-FabricExample/Pods-FabricExample.release.xcconfig"; sourceTree = ""; }; + 43811622962968761473DDAA /* Pods-FabricExampleTestHost.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTestHost.release.xcconfig"; path = "Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost.release.xcconfig"; sourceTree = ""; }; + 5B9D63EB1C223D98783472EC /* libPods-FabricExample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FabricExample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6ABABA247EF565DEE43160D2 /* libPods-FabricExampleTestHost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FabricExampleTestHost.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6DBC9BF3F78155A450934DCB /* libPods-FabricExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FabricExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 761780EC2CA45674006654EE /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = FabricExample/AppDelegate.swift; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = FabricExample/LaunchScreen.storyboard; sourceTree = ""; }; + 87E5AB60809BC1E2657A6544 /* Pods-FabricExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExample.debug.xcconfig"; path = "Target Support Files/Pods-FabricExample/Pods-FabricExample.debug.xcconfig"; sourceTree = ""; }; A1B2C3D51E00000100A0A001 /* FabricExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FabricExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A1B2C3D61E00000100A0A001 /* AudioEngineTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioEngineTests.mm; sourceTree = ""; }; A1B2C3E11E00000100A0A001 /* AudioSessionManagerTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioSessionManagerTests.mm; sourceTree = ""; }; @@ -54,14 +56,12 @@ A1B2C4111E00000100A0A001 /* NativeAudioRecorderTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = NativeAudioRecorderTests.mm; sourceTree = ""; }; A1B2C4211E00000100A0A001 /* IOSAudioRecorderTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = IOSAudioRecorderTests.mm; sourceTree = ""; }; A1B2C4311E00000100A0A001 /* AudioAPIModuleTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = AudioAPIModuleTests.mm; sourceTree = ""; }; - AC3379398A0B404C622EB985 /* Pods-FabricExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExample.debug.xcconfig"; path = "Target Support Files/Pods-FabricExample/Pods-FabricExample.debug.xcconfig"; sourceTree = ""; }; + AC1FC78BD7D27E1AA373DC33 /* Pods-FabricExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTests.debug.xcconfig"; path = "Target Support Files/Pods-FabricExampleTests/Pods-FabricExampleTests.debug.xcconfig"; sourceTree = ""; }; B2C3D501E00000100A0A001 /* FabricExampleTestHost.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FabricExampleTestHost.app; sourceTree = BUILT_PRODUCTS_DIR; }; B2C3D511E00000100A0A001 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; B2C3D521E00000100A0A001 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B2C3D531E00000100A0A001 /* libPods-FabricExampleTestHost.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FabricExampleTestHost.a"; sourceTree = BUILT_PRODUCTS_DIR; }; - D4C5B2173779E2C2DE12E5E2 /* Pods-FabricExampleTestHost.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTestHost.debug.xcconfig"; path = "Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost.debug.xcconfig"; sourceTree = ""; }; + D7B6EFC104F6F56C7606E663 /* Pods-FabricExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FabricExampleTests.release.xcconfig"; path = "Target Support Files/Pods-FabricExampleTests/Pods-FabricExampleTests.release.xcconfig"; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; - EE404288C652F11542C27289 /* libPods-FabricExampleTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-FabricExampleTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -69,7 +69,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 293FA0CC5BDDACD9944867F6 /* libPods-FabricExample.a in Frameworks */, + 62FAAAE962F12826D5D6BFE5 /* libPods-FabricExample.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -77,7 +77,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8E0FEE6ECB6F4EB7C6068C44 /* libPods-FabricExampleTests.a in Frameworks */, + C606AD359FD803D57BBEE4A3 /* libPods-FabricExampleTests.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -85,7 +85,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B2C3D551E00000100A0A001 /* libPods-FabricExampleTestHost.a in Frameworks */, + CAB0C4BA36F7D9BEC3BBEB24 /* libPods-FabricExampleTestHost.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -108,9 +108,9 @@ isa = PBXGroup; children = ( ED297162215061F000B7C4FE /* JavaScriptCore.framework */, - 3CC491462FE5F58FA7896F2C /* libPods-FabricExample.a */, - EE404288C652F11542C27289 /* libPods-FabricExampleTests.a */, - B2C3D531E00000100A0A001 /* libPods-FabricExampleTestHost.a */, + 5B9D63EB1C223D98783472EC /* libPods-FabricExample.a */, + 6ABABA247EF565DEE43160D2 /* libPods-FabricExampleTestHost.a */, + 6DBC9BF3F78155A450934DCB /* libPods-FabricExampleTests.a */, ); name = Frameworks; sourceTree = ""; @@ -174,12 +174,12 @@ BBD78D7AC51CEA395F1C20DB /* Pods */ = { isa = PBXGroup; children = ( - AC3379398A0B404C622EB985 /* Pods-FabricExample.debug.xcconfig */, - 5D7A8AB58ACD5276FDF288D0 /* Pods-FabricExample.release.xcconfig */, - 71E1415974092A22784C6F6A /* Pods-FabricExampleTests.debug.xcconfig */, - 411D0E3C93126EA2C4BF29BA /* Pods-FabricExampleTests.release.xcconfig */, - D4C5B2173779E2C2DE12E5E2 /* Pods-FabricExampleTestHost.debug.xcconfig */, - 2072ECDFE10D5F4F44D1DA68 /* Pods-FabricExampleTestHost.release.xcconfig */, + 87E5AB60809BC1E2657A6544 /* Pods-FabricExample.debug.xcconfig */, + 28414E4977AA1542F647A6FF /* Pods-FabricExample.release.xcconfig */, + 22FEABAF3B14E34993D0E1DD /* Pods-FabricExampleTestHost.debug.xcconfig */, + 43811622962968761473DDAA /* Pods-FabricExampleTestHost.release.xcconfig */, + AC1FC78BD7D27E1AA373DC33 /* Pods-FabricExampleTests.debug.xcconfig */, + D7B6EFC104F6F56C7606E663 /* Pods-FabricExampleTests.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -191,7 +191,7 @@ isa = PBXNativeTarget; buildConfigurationList = A1B2C3DF1E00000100A0A001 /* Build configuration list for PBXNativeTarget "FabricExampleTests" */; buildPhases = ( - 2F9199608558800273CB4F9B /* [CP] Check Pods Manifest.lock */, + 617B490094AC453F9CA898EC /* [CP] Check Pods Manifest.lock */, A1B2C3DC1E00000100A0A001 /* Sources */, A1B2C3D71E00000100A0A001 /* Frameworks */, A1B2C3DB1E00000100A0A001 /* Resources */, @@ -210,13 +210,13 @@ isa = PBXNativeTarget; buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "FabricExample" */; buildPhases = ( - 9D8A98B73FB8D0F62F67747F /* [CP] Check Pods Manifest.lock */, + 3ECEDE28C2FB9B51FC28DB8F /* [CP] Check Pods Manifest.lock */, 13B07F871A680F5B00A75B9A /* Sources */, 13B07F8C1A680F5B00A75B9A /* Frameworks */, 13B07F8E1A680F5B00A75B9A /* Resources */, 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, - A7E787E2358E3075171000CA /* [CP] Embed Pods Frameworks */, - C6C36E6FE6E8CC3A75954739 /* [CP] Copy Pods Resources */, + 1D6374A581A534695C513F3D /* [CP] Embed Pods Frameworks */, + 7A640254D025CEEC9C525AB4 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -231,12 +231,12 @@ isa = PBXNativeTarget; buildConfigurationList = B2C3D5D1E00000100A0A001 /* Build configuration list for PBXNativeTarget "FabricExampleTestHost" */; buildPhases = ( - B2C3D5A1E00000100A0A001 /* [CP] Check Pods Manifest.lock */, + 54FF5F3D12B47B05C3040E9F /* [CP] Check Pods Manifest.lock */, B2C3D571E00000100A0A001 /* Sources */, B2C3D581E00000100A0A001 /* Frameworks */, B2C3D591E00000100A0A001 /* Resources */, - B2C3D5B1E00000100A0A001 /* [CP] Embed Pods Frameworks */, - B2C3D5C1E00000100A0A001 /* [CP] Copy Pods Resources */, + F42E6E72F145D12C19D618D0 /* [CP] Embed Pods Frameworks */, + BD72D2C2F17D35E17600D849 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -328,29 +328,24 @@ shellPath = /bin/sh; shellScript = "set -e\n\nWITH_ENVIRONMENT=\"$REACT_NATIVE_PATH/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"$REACT_NATIVE_PATH/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; }; - 2F9199608558800273CB4F9B /* [CP] Check Pods Manifest.lock */ = { + 1D6374A581A534695C513F3D /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-FabricExampleTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 9D8A98B73FB8D0F62F67747F /* [CP] Check Pods Manifest.lock */ = { + 3ECEDE28C2FB9B51FC28DB8F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -372,24 +367,29 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - A7E787E2358E3075171000CA /* [CP] Embed Pods Frameworks */ = { + 54FF5F3D12B47B05C3040E9F /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-FabricExampleTestHost-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B2C3D5A1E00000100A0A001 /* [CP] Check Pods Manifest.lock */ = { + 617B490094AC453F9CA898EC /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -404,31 +404,31 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-FabricExampleTestHost-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-FabricExampleTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - B2C3D5B1E00000100A0A001 /* [CP] Embed Pods Frameworks */ = { + 7A640254D025CEEC9C525AB4 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-frameworks-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-frameworks-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources.sh\"\n"; showEnvVarsInLog = 0; }; - B2C3D5C1E00000100A0A001 /* [CP] Copy Pods Resources */ = { + BD72D2C2F17D35E17600D849 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -445,21 +445,21 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-resources.sh\"\n"; showEnvVarsInLog = 0; }; - C6C36E6FE6E8CC3A75954739 /* [CP] Copy Pods Resources */ = { + F42E6E72F145D12C19D618D0 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExample/Pods-FabricExample-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FabricExampleTestHost/Pods-FabricExampleTestHost-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -508,7 +508,7 @@ /* Begin XCBuildConfiguration section */ 13B07F941A680F5B00A75B9A /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = AC3379398A0B404C622EB985 /* Pods-FabricExample.debug.xcconfig */; + baseConfigurationReference = 87E5AB60809BC1E2657A6544 /* Pods-FabricExample.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -548,7 +548,7 @@ }; 13B07F951A680F5B00A75B9A /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 5D7A8AB58ACD5276FDF288D0 /* Pods-FabricExample.release.xcconfig */; + baseConfigurationReference = 28414E4977AA1542F647A6FF /* Pods-FabricExample.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; @@ -776,7 +776,7 @@ }; A1B2C3DD1E00000100A0A001 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 71E1415974092A22784C6F6A /* Pods-FabricExampleTests.debug.xcconfig */; + baseConfigurationReference = AC1FC78BD7D27E1AA373DC33 /* Pods-FabricExampleTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; @@ -804,7 +804,7 @@ }; A1B2C3DE1E00000100A0A001 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 411D0E3C93126EA2C4BF29BA /* Pods-FabricExampleTests.release.xcconfig */; + baseConfigurationReference = D7B6EFC104F6F56C7606E663 /* Pods-FabricExampleTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ENABLE_MODULES = YES; @@ -833,7 +833,7 @@ }; B2C3D5E1E00000100A0A001 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = D4C5B2173779E2C2DE12E5E2 /* Pods-FabricExampleTestHost.debug.xcconfig */; + baseConfigurationReference = 22FEABAF3B14E34993D0E1DD /* Pods-FabricExampleTestHost.debug.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; @@ -872,7 +872,7 @@ }; B2C3D5F1E00000100A0A001 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 2072ECDFE10D5F4F44D1DA68 /* Pods-FabricExampleTestHost.release.xcconfig */; + baseConfigurationReference = 43811622962968761473DDAA /* Pods-FabricExampleTestHost.release.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = 1; diff --git a/apps/fabric-example/ios/Podfile.lock b/apps/fabric-example/ios/Podfile.lock index 0b311863e..d1bcd5316 100644 --- a/apps/fabric-example/ios/Podfile.lock +++ b/apps/fabric-example/ios/Podfile.lock @@ -2597,7 +2597,7 @@ SPEC CHECKSUMS: ReactCodegen: d07ee3c8db75b43d1cbe479ae6affebf9925c733 ReactCommon: fe2a3af8975e63efa60f95fca8c34dc85deee360 ReactNativeDependencies: 4d5ce2683b6d74f7c686bf90a88c7d381295cf3c - RNAudioAPI: 50957b72cc742b9aa1e05349be71b9db73c9cf74 + RNAudioAPI: 7a86afb7eacb959cd0e1e3a9a492ee9eec2c4bf7 RNAudioWorklets: ff0c53fd3c3bbffacb7dd3beb03ffe0ea9f1fd05 RNGestureHandler: 187c5c7936abf427bc4d22d6c3b1ac80ad1f63c0 RNReanimated: 3a204af3747842859392545d41d443b9c5e428a3 @@ -2606,6 +2606,6 @@ SPEC CHECKSUMS: RNWorklets: 63cb8f15b2886288da888ac65cc21934c9ebc80a Yoga: e83c3121d079541e69f3c5c623faaaf933fb5812 -PODFILE CHECKSUM: 7a9375c6de5b95bc2125d07cf736baa649d6ba8e +PODFILE CHECKSUM: 3350120bb5f5e202141624d34a8632a35887fc2f COCOAPODS: 1.16.2 diff --git a/package.json b/package.json index 7c4aff502..c81888eb2 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "clean": "del-cli packages/**/android/build apps/**/android/build apps/**/android/app/build apps/**/ios/build packages/**/lib node_modules apps/**/node_modules packages/**/node_modules", "typecheck": "yarn workspaces foreach -A -p run typecheck", "test": "yarn workspace react-native-audio-api run test", - "check-audio-enum-sync": "bash packages/react-native-audio-api/scripts/check-audio-events-sync.sh", + "check-audio-enum-sync": "bash packages/react-native-audio-api/scripts/check-enum-sync.sh", "validate:fast": "bash scripts/validate.sh --fast", "validate:graph": "bash scripts/validate.sh --graph", "validate:android": "bash scripts/validate.sh --android", diff --git a/packages/audiodocs/docs/inputs/audio-recorder.mdx b/packages/audiodocs/docs/inputs/audio-recorder.mdx index c82fec84a..52ca77d4f 100644 --- a/packages/audiodocs/docs/inputs/audio-recorder.mdx +++ b/packages/audiodocs/docs/inputs/audio-recorder.mdx @@ -655,17 +655,42 @@ interface AudioRecorderFileOptions { Describes desired file extension as well as codecs, containers (and muxers!) used to encode the file. +All encoding is done with platform system APIs — iOS AVFoundation and Android MediaCodec/MediaMuxer. Because each platform exposes a different set of system encoders, format support is platform-specific. + ```tsx enum FileFormat { Wav, Caf, M4A, Flac, + Aiff, + Alac, + OpusOgg, + OpusWebm, + VorbisWebm, + Ulaw, + Alaw, } ``` -:::caution Android + FFmpeg -On Android, encoded file output for `M4A`, `FLAC`, and `CAF` uses FFmpeg. When FFmpeg is disabled in the build, only **WAV** recording to file is supported. iOS uses system AVFoundation for all listed formats. See [Runtime flags](/docs/other/runtime-flags#where-ffmpeg-is-used). +The table below lists which formats each platform can encode with its system APIs: + +| `FileFormat` | Container / codec | iOS | Android | +| :--- | :--- | :---: | :---: | +| `Wav` | WAV / PCM | ✅ | ✅ | +| `M4A` | M4A / AAC-LC | ✅ | ✅ | +| `Flac` | FLAC / FLAC | ✅ | ✅ | +| `Caf` | CAF / PCM | ✅ | ❌ | +| `Aiff` | AIFF / PCM | ✅ | ❌ | +| `Alac` | M4A / Apple Lossless | ✅ | ❌ | +| `Ulaw` | WAV / µ-law | ✅ | ❌ | +| `Alaw` | WAV / a-law | ✅ | ❌ | +| `OpusOgg` | OGG / Opus | ❌ | ✅ | +| `OpusWebm` | WebM / Opus | ❌ | ✅ | +| `VorbisWebm` | WebM / Vorbis | ❌ | ✅ | + +:::caution Platform support +Selecting a format the current platform cannot encode (for example `Caf` on Android or `OpusOgg` on iOS) fails when file output is enabled, with a descriptive error. Some Android formats also depend on the device OS version (Opus/OGG muxing requires newer releases); if a device lacks a system encoder for the requested format, recording start returns an error. Use `Wav`, `M4A`, or `Flac` for the widest cross-platform support. ::: #### FileInfo diff --git a/packages/audiodocs/docs/other/disabling-prebuilt-libraries.mdx b/packages/audiodocs/docs/other/disabling-prebuilt-libraries.mdx index 9a96ae731..bb31f2610 100644 --- a/packages/audiodocs/docs/other/disabling-prebuilt-libraries.mdx +++ b/packages/audiodocs/docs/other/disabling-prebuilt-libraries.mdx @@ -17,7 +17,7 @@ The available flags are independent and can be combined: | Flag | What it removes | What stops working | | :---: | :---- | :---- | -| `disableFFmpeg` | FFmpeg shared libraries (`libavcodec`, `libavformat`, `libavutil`, `libswresample`) | Remote URL streaming / HLS, remote URL metadata, M4A concat, **Android** non-WAV recording — see [Runtime flags](/docs/other/runtime-flags#where-ffmpeg-is-used) | +| `disableFFmpeg` | FFmpeg shared libraries (`libavcodec`, `libavformat`, `libavutil`, `libswresample`) | Remote URL streaming / HLS, remote URL metadata — see [Runtime flags](/docs/other/runtime-flags#where-ffmpeg-is-used). | | `disableStaticExternalLibs` | Static libs: `libopus`, `libopusfile`, `libogg`, `libvorbis`, `libvorbisenc`, `libvorbisfile` | Decoding `ogg`, `opus`, `oga` files | :::info diff --git a/packages/audiodocs/docs/other/runtime-flags.mdx b/packages/audiodocs/docs/other/runtime-flags.mdx index 6383a2418..49aff096c 100644 --- a/packages/audiodocs/docs/other/runtime-flags.mdx +++ b/packages/audiodocs/docs/other/runtime-flags.mdx @@ -2,7 +2,7 @@ These helpers let you check at runtime which optional native features are compiled into your app. They are synchronous and safe to call from JavaScript after the library has been installed. -Use them to branch UI or loading logic — for example, skip remote metadata preload when FFmpeg is disabled, disable hls streaming, or offer only WAV recording on Android. +Use them to branch UI or loading logic — for example, skip remote metadata preload when FFmpeg is disabled or disable hls streaming. :::info Build-time vs runtime To **disable** optional libraries at build time (and reduce app size), see [Disabling prebuilt libraries](/docs/other/disabling-prebuilt-libraries). Runtime flags only tell you what ended up in the binary you are running. @@ -20,7 +20,7 @@ Returns whether the native build includes [`FFmpeg`](https://github.com/FFmpeg/F import { isFfmpegEnabled } from 'react-native-audio-api'; if (!isFfmpegEnabled()) { - console.warn('Remote URL metadata, streaming, and Android M4A recording require an FFmpeg build.'); + console.warn('Remote URL metadata and streaming require an FFmpeg build.'); } ``` @@ -31,13 +31,9 @@ if (!isFfmpegEnabled()) { | :---: | :---: | :---- | | Streaming | [`Audio tag`](/docs/sources/audio-tag), `createFileSource` | Remote URL streaming (HTTP byte ranges) and HLS (`.m3u8`) | | Metadata | [`getAudioDuration`](/docs/utils/decoding#getaudioduration), [`