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/.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..ac7695b5f 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,8 +136,13 @@ 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}` + ); + console.log(info.paths.length); const finalPath = await concatAudioFiles(info.paths, outputPath); const audioBuffer = await audioContext.decodeAudioData(finalPath); setRecordedBuffer(audioBuffer); @@ -262,7 +273,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/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 ac5574851..ab3b96ea5 100644 --- a/packages/audiodocs/docs/inputs/audio-recorder.mdx +++ b/packages/audiodocs/docs/inputs/audio-recorder.mdx @@ -654,17 +654,54 @@ 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. FFmpeg is never used for encoding. 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, + AmrNb, + AmrWb, + AacHe, + AacEld, + Ima4, + Ulaw, + Alaw, + Ilbc, } ``` -:::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 | ✅ | ❌ | +| `AacHe` | M4A / AAC-HE | ✅ | ❌ | +| `AacEld` | M4A / AAC-ELD | ✅ | ❌ | +| `Ima4` | CAF / IMA4 (ADPCM) | ✅ | ❌ | +| `Ulaw` | WAV / µ-law | ✅ | ❌ | +| `Alaw` | WAV / a-law | ✅ | ❌ | +| `Ilbc` | CAF / iLBC | ✅ | ❌ | +| `OpusOgg` | OGG / Opus | ❌ | ✅ | +| `OpusWebm` | WebM / Opus | ❌ | ✅ | +| `VorbisWebm` | WebM / Vorbis | ❌ | ✅ | +| `AmrNb` | 3GP / AMR-NB | ❌ | ✅ | +| `AmrWb` | 3GP / AMR-WB | ❌ | ✅ | + +:::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 a3a87ed39..3e0a042fb 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`) | Streaming, remote playback/metadata, `.aac` / `.mp4` / `.m4a` decode, 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`) | Streaming, remote playback/metadata, `.aac` / `.mp4` / `.m4a` decode — see [Runtime flags](/docs/other/runtime-flags#where-ffmpeg-is-used). Recording and `concatAudioFiles` are unaffected. | | `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 7cbe0364b..4b147d2a2 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.'); } ``` @@ -33,12 +33,19 @@ if (!isFfmpegEnabled()) { | Playback | `createFileSource`, [`