Play audio and record microphone files on iOS
One.Audio plays a local file:// URI or an https:// audio URL through
AVPlayer. It handles one playback or recording operation at a time. Starting
playback replaces the previous player; stop a paused player before recording.
An ended or failed player is cleared when recording starts. Calls reject with
a coded error when the requested operation cannot run.
import { One } from 'one'
await One.Audio.play('https://example.com/episode.m4a')const status = await One.Audio.getPlaybackStatus()if (status.state === 'playing') { await One.Audio.pause() await One.Audio.seek(30_000) await One.Audio.resume()}await One.Audio.stop()getPlaybackStatus() returns state, positionMs, and a durationMs when the
media has a finite duration. States are idle, loading, playing, paused,
ended, and failed. A remote source may initially be loading; read its
status again when your app needs current progress. A failed source reports its
error in the status. seek() needs a ready item and rejects with
E_AUDIO_STATE while a source is loading or failed. resume() restarts an
ended item from the beginning.
Recording writes AAC in an .m4a file under the app cache. Set
native.app.audio.microphone to the purpose shown in the iOS microphone
prompt and rerun prebuild. Playback
alone needs no microphone permission or purpose string.
For playback while another app is in front, set native.app.audio.background: true
and rerun prebuild. This adds audio to UIBackgroundModes. Audio.play() activates the iOS
playback category before your app enters the background. Apple
requires that category and background mode
for continued playback. Picture in Picture also declares the same mode. Request
microphone access separately if you record. The iOS 27 simulator continued
playing without the mode in our control run, so confirm background behavior on a
device before relying on it in production.
const permission = await One.Audio.requestRecordingPermission()if (permission === 'granted') { const started = await One.Audio.startRecording() await One.Audio.pauseRecording() await One.Audio.resumeRecording() const recorded = await One.Audio.stopRecording() console.log(started.uri, recorded.uri, recorded.durationMs, recorded.size) await One.Audio.play(recorded.uri)}getRecordingPermissionStatus() reads undetermined, granted, or denied
without prompting. getRecordingStatus() reports idle, recording, or
paused, its file URI, and elapsed time. An interrupted recorder reports
paused; call resumeRecording() after the interruption ends. Keep a file you want to
retain by moving it from cache with One.FileSystem.move.
Use watchInterruptions() to react when another audio session interrupts playback
or recording. The callback receives type (began or ended) and shouldResume.
On began, One pauses its current player or recorder. Resume
only after ended, according to your app’s playback policy; shouldResume is
the system’s recommendation, not an automatic action. Call the returned function
when the listener is no longer needed.
If iOS suspends the app, it may deliver a delayed began after the app returns
without a matching ended. Use app foreground state when deciding whether to
offer playback again in that case.
const stopWatching = One.Audio.watchInterruptions((event) => { if (event.type === 'ended' && event.shouldResume) { void One.Audio.resume() }})// call stopWatching() when this screen unmountsAfter play(), call setNowPlayingInfo() to register the current player for
Lock Screen and Control Center media controls. The handlers play, pause, and
seek the current track, and watchRemoteCommands() reports those actions
so your screen can update its state. Set the info again when the title or artwork
changes. Artwork must be an existing local file:// image; download remote
artwork to cache first. stop() or a new play() clears the current info and
remote handlers. clearNowPlayingInfo() removes the metadata and handlers while
the player remains active. Enable the background audio mode described above if
playback must continue while another app is in front.
await One.Audio.play(trackUri)await One.Audio.setNowPlayingInfo({ title: 'Chapter One', artist: 'Example Author', artworkUri: coverFileUri,})const stopWatchingRemote = One.Audio.watchRemoteCommands((event) => { // event.type is play, pause, or seek; seek includes positionMs. void One.Audio.getPlaybackStatus().then((status) => { console.log(event.type, status.state, status.positionMs) })})// call stopWatchingRemote() when this screen unmountsThe iOS 27 simulator verified that metadata setup and cleanup completed through the native bridge. Control Center and Lock Screen stayed empty; the cause is unconfirmed. Confirm the visible controls, tile removal, and remote callbacks on a device before relying on them in production.
One.Audio.createPlayer() returns an independent AudioPlayer for each
track. Players are independent of each other and of
the singleton play() above; each holds its own position, volume, and rate.
import { One, useAudioPlayer, useAudioPlayerStatus } from 'one'
const player = One.Audio.createPlayer('https://example.com/episode.m4a')player.setPlaybackRate(1.5)player.play()
function Track() { const player = useAudioPlayer('https://example.com/episode.m4a') const status = useAudioPlayerStatus(player) return <>{status.playing ? 'playing' : 'paused'}</>}The source is a URI string, { uri, headers, name }, or null for a player
with no media yet. Numeric asset ids throw: bundle audio as a file and pass
its URI. Options are { updateInterval, crossOrigin }: the status polling
interval in milliseconds (default 500, driving addListener and
useAudioPlayerStatus) and the web-only CORS mode. useAudioPlayer(source, options) creates one player, replaces its source when the source value
changes, and removes it on unmount. useAudioPlayerStatus(player) returns
the current status and re-renders on every update.
Time reads in seconds: currentTime and duration (0 while unknown),
playing, paused, isLoaded, and isBuffering. loop, muted,
volume (0 to 1), and shouldCorrectPitch (default true) read and write;
playbackRate reads the current rate. isAudioSamplingSupported is
false. play() starts playback, loading first when needed, and restarts a
naturally ended track from the beginning; pause() pauses.
seekTo(seconds, toleranceMillisBefore?, toleranceMillisAfter?) returns a
promise that resolves when the seek lands (tolerances are iOS only).
replace(source) swaps the media, clearing any error, and remove()
releases the player idempotently. Calling any other method after remove()
throws.
getStatus() reads the current status synchronously: { currentTime, didJustFinish, duration, error, id, isBuffering, isLoaded, loop, mute, playbackRate, playing, shouldCorrectPitch }. mute mirrors the muted property.
didJustFinish is set when a track ends naturally and cleared by play(),
seekTo(), or replace(). error carries a load or playback failure and
clears on replace() or successful playback. addListener('playbackStatusUpdate', listener) emits the status on the update interval plus one initial emit;
removeAllListeners() stops the polling.
setPlaybackRate(rate, pitchCorrectionQuality?) sets the speed without
reloading. The range is 0 to 2 on iOS and web and 0.1 to 2 on Android;
outside it the call throws E_AUDIO_RATE. Pitch quality is 'low',
'medium', or 'high' (iOS only, default 'medium') and applies while
pitch correction is on.
Instance playback activates the shared playback category like the singleton
API and never deactivates it on remove(). Not covered: per-player lock
screen methods, audio sampling, the status live-stream and playback-state
fields, and the downloadFirst, keepAudioSessionActive, and
preferredForwardBufferDuration options. Source headers apply on iOS and
Android and are ignored on web.
Errors expose error.code: E_AUDIO_URI, E_AUDIO_FILE,
E_AUDIO_MANIFEST, E_AUDIO_PERMISSION, E_AUDIO_BUSY, E_AUDIO_STATE,
E_AUDIO_POSITION, E_AUDIO_METADATA, E_AUDIO_ARTWORK, E_AUDIO_RATE, or
E_AUDIO_FAILED.
This API is iOS only. Playback and
recording use the app’s shared audio session. Playback switches its category
to playback and can replace another feature’s audio session configuration,
including picture in picture. Stop this audio before starting speech recognition
or another audio feature. Background recording is not configured by this API.
Audio is implemented on iOS and Android. On Android, playback runs over MediaPlayer, recording over MediaRecorder as AAC .m4a under cache, interruptions from audio focus, and now-playing metadata with play/pause/seek commands over MediaSession. Recording needs native.app.audio.microphone; background playback needs native.app.audio.background, which stamps a media-playback foreground service. SSR reports denied recording permission and idle status. Stop and metadata effects do nothing; listeners return a remover that does nothing. Starting playback or recording and requesting a recording file reject with Audio.<verb> needs an iOS or Android build where unimplemented.
On web, HTMLAudioElement provides playback, pause, resume, millisecond seek,
status, and stop. URI inputs use browser HTTP, HTTPS, data, or blob URLs.
Autoplay policy can reject playback without a user gesture. MediaRecorder and
getUserMedia provide microphone permission, recording, pause, resume, and stop.
Playback and recording cannot run together. Recording duration excludes pauses,
and a stopped recording returns its encoded byte length and a blob URL; the
browser chooses the audio codec. Release returned URLs with
URL.revokeObjectURL when finished. Media Session supplies metadata and remote
commands when present. Metadata needs an existing player and decodable artwork.
Remote play, pause, and seek update the player before notifying listeners;
clearing metadata or stopping the player removes its controls. Removing a
listener leaves controls active until the metadata is cleared. Operating-system audio interruptions have no equivalent
browser event, so watchInterruptions retains its inert remover. Missing media
capabilities and invalid operations reject rather than report success.
Edit this page on GitHub.