Skip to content
Draft
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
3 changes: 3 additions & 0 deletions .claude/skills/build-compilation-dependencies/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/post-work-checks/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

---

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` / `--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 |
2 changes: 2 additions & 0 deletions .claude/skills/thread-safety-itc/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` 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<NestedTask, …>;` 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)
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 12 additions & 2 deletions apps/common-app/src/demos/Record/Record.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>(RecordingState.Idle);
const [hasPermissions, setHasPermissions] = useState<boolean>(false);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
Loading
Loading