audio
Record from the microphone to a file and read live levels and elapsed time.
audio records microphone sound directly to a file with AVAudioRecorder. record returns an AudioRecording that is already recording, and stop ends it; levels is an async iterable of live levels for meters and timers. OneScript configures the audio session for you, so scripts don't set it up.
import { audio } from "onescript";
const recording = await audio.record("memo.m4a");
await new Promise(resolve => setTimeout(resolve, 5_000));
const { path, duration } = await recording.stop();
console.log(path, duration);Types
interface AudioPermission {
status: "notDetermined" | "denied" | "authorized";
}
interface AudioRecording {
readonly path: string; // absolute path of the recording
readonly state: "recording" | "paused" | "ended";
readonly finished: Promise<AudioRecordingResult>;
pause(): Promise<void>;
resume(): Promise<void>;
stop(): Promise<AudioRecordingResult>;
levels(options?: { interval?: number; signal?: AbortSignal }): AsyncIterable<AudioLevel>;
}
interface AudioRecordingResult {
path: string;
duration: number; // seconds
reason: "stopped" | "maxDuration" | "interrupted";
}
interface AudioLevel {
currentTime: number; // seconds recorded, excluding pauses
average: number; // average power (dBFS)
peak: number; // peak power (dBFS)
channels: { average: number; peak: number }[];
}Permission
function audio.getPermission(): Promise<AudioPermission>;
function audio.requestPermission(): Promise<AudioPermission>;The first record call asks for microphone access, so you usually don't need to ask yourself. Call requestPermission to ask ahead of time; once the user has decided, it returns the current status.
status | Meaning |
|---|---|
notDetermined | The user hasn't been asked yet. |
denied | The user denied microphone access. |
authorized | Scripts can record. |
audio.record
function audio.record(path: string, options?: {
sampleRate?: number;
channels?: 1 | 2;
bitRate?: number;
maxDuration?: number;
}): Promise<AudioRecording>;Starts recording; when the promise fulfills, recording has begun. path works like fs: relative paths resolve against the project directory, an existing file is overwritten, and the parent directory must exist. The extension picks the format:
| Extension | Format |
|---|---|
.m4a | AAC. Small files, good for speech and sharing. |
.wav | 16-bit PCM. Lossless, good for further processing. |
| Option | Description |
|---|---|
sampleRate | Sample rate in Hz. Defaults to 48000; range 8000–192000. |
channels | Number of channels. Defaults to 1. |
bitRate | AAC bit rate in bit/s, .m4a only. The encoder chooses by default. |
maxDuration | Maximum length in seconds, excluding pauses. Recording ends when it's reached. |
const recording = await audio.record("recordings/interview.m4a", {
bitRate: 64_000,
maxDuration: 10 * 60
});
const result = await recording.finished;
console.log(result.reason); // "maxDuration"Only one recording can run at a time; calling record while recording throws AUDIO_UNAVAILABLE.
Controlling a recording
pause pauses and resume continues; each does nothing if the recording is already in that state. stop ends the recording and finishes the file. You can call it more than once, and it always returns the same result.
finished fulfills however the recording ends, and reason tells you why:
reason | Meaning |
|---|---|
stopped | stop was called. |
maxDuration | maxDuration was reached. |
interrupted | A call, Siri, or similar interrupted it, or OneScript moved to the background. |
An interrupted recording ends and saves immediately, so the file is complete; call record again to keep going. If recording fails, finished and stop reject with AUDIO_RECORDING_FAILED.
The script keeps running until the recording ends. When the script ends (or the worker thread that started the recording ends), the recording ends too: what was recorded stays in the file, and finished rejects.
Levels
recording.levels(options?: { interval?: number; signal?: AbortSignal }): AsyncIterable<AudioLevel>;Samples the level and elapsed time every interval milliseconds (default 50, minimum 16) until you break, the signal aborts, or the recording ends. Levels are current state: if the loop body is slower than the updates, intermediate values are skipped and only the latest level is delivered. No levels are produced while paused.
Power is in dBFS, from -160 (silence) to 0 (full scale). average and peak are means across channels. To convert to a linear 0–1 value, use 10 ** (db / 20).
const recording = await audio.record("memo.m4a", { maxDuration: 30 });
for await (const level of recording.levels({ interval: 100 })) {
const bar = "█".repeat(Math.round(10 ** (level.peak / 20) * 30));
console.log(level.currentTime.toFixed(1), bar);
}
// The loop ends when the recording reaches maxDuration.
console.log(await recording.finished);Availability
| API | App | Action extension | Widget |
|---|---|---|---|
getPermission | ✓ | ✓ | ✓ |
requestPermission, record | ✓ | — | — |
Errors
Errors have a code property:
code | Meaning |
|---|---|
AUDIO_PERMISSION_DENIED | Microphone access was denied. |
AUDIO_UNAVAILABLE | No input device is available, the audio session couldn't be activated, or another recording is running. |
AUDIO_CONTEXT_UNAVAILABLE | The operation isn't supported here, for example recording in a widget. |
AUDIO_RECORDING_FAILED | Recording couldn't start, or a system error such as an encoder failure stopped it. |
ENOENT | The recording's parent directory doesn't exist. |
Invalid arguments (such as an unsupported extension, or bitRate with .wav) throw a TypeError. Calling pause or resume after the recording has ended throws ERR_INVALID_STATE.
How is this guide?
Last updated on