tauri-plugin-native-audio
Native mobile audio playback for Tauri applications.
tauri-plugin-native-audio gives you one JavaScript API over native playback engines:
- Android: Media3 ExoPlayer + MediaSessionService + notification controls
- iOS: AVPlayer + MPNowPlayingInfoCenter + MPRemoteCommandCenter
It is designed for real mobile playback flows: background-friendly behavior, lock screen / notification controls, and continuous state synchronization in the frontend.
| Platform | Supported |
|---|---|
| Linux | - |
| Windows | - |
| macOS | - |
| Android | ✓ |
| iOS | ✓ |
Install
This plugin requires a Rust version of at least 1.77.2.
There are three general methods of installation:
- crates.io + npm (simplest)
- Git dependency for Rust + npm package for JS
- Git/path dependency for both Rust and JS in monorepo/submodule setup
Install the core plugin in src-tauri/Cargo.toml:
[]
= "1.0.0"
# or from Git:
= { = "https://github.com/uvarov-frontend/tauri-plugin-native-audio", = "v1.0.0" }
Install the JavaScript guest bindings:
# or
# or
Setting up
1. Register the plugin in Rust
src-tauri/src/lib.rs
2. Enable permissions in capability
Add plugin permissions to your capability file (example: src-tauri/capabilities/default.json):
native-audio:default includes:
allow-initializeallow-register-listenerallow-remove-listenerallow-set-sourceallow-playallow-pauseallow-seek-toallow-set-rateallow-get-stateallow-get-progress-checkpointallow-dispose
If you need stricter ACL, use explicit allow/deny permissions from permissions/autogenerated/commands/*.toml.
3. Platform notes
Android:
play()starts a foreground service (NativeAudioService) for media playback notification.- On Android 13+ notification permission is requested during
initialize(). - Plugin manifest declares:
android.permission.INTERNETandroid.permission.FOREGROUND_SERVICEandroid.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACKandroid.permission.POST_NOTIFICATIONSandroid.permission.WAKE_LOCK
iOS:
- Uses
AVAudioSessioncategory.playback. - For true background audio, enable Background Modes -> Audio in your iOS target.
- Lock screen controls and now playing metadata are integrated through
MediaPlayerframework APIs.
Configuration
This plugin does not require plugin-specific tauri.conf.json > plugins.native-audio configuration.
Runtime behavior is configured from JS commands:
setSource({ src, id?, title, artist, artworkUrl })setRate(rate)seekTo(positionSeconds)
Usage
After plugin registration, APIs are available in JavaScript:
import {
initialize,
setSource,
play,
pause,
seekTo,
setRate,
getState,
getProgressCheckpoint,
dispose,
addStateListener
} from "tauri-plugin-native-audio-api";
const unlisten = await addStateListener((state) => {
console.log("native-audio state", state);
});
await initialize();
await setSource({
src: "https://example.com/audio.mp3",
id: 123,
title: "Episode 1",
artist: "My Podcast",
artworkUrl: "https://example.com/cover.jpg"
});
await setRate(1.25);
await play();
// later
await seekTo(42);
await pause();
console.log(await getState());
console.log(await getProgressCheckpoint());
unlisten();
await dispose();
JavaScript API
Types:
export type NativeAudioStatus = "idle" | "loading" | "playing" | "ended" | "error";
export type NativeAudioState = {
status: NativeAudioStatus;
currentTime: number;
duration: number;
isPlaying: boolean;
buffering: boolean;
rate: number;
error?: string;
};
export type NativeAudioSetSourcePayload = {
src: string;
id?: number;
title?: string;
artist?: string;
artworkUrl?: string;
};
export type NativeAudioProgressCheckpoint = {
id: number;
currentTime: number;
updatedAtMs: number;
status?: NativeAudioStatus;
};
Commands:
initialize(): Promise<NativeAudioState>setSource(payload: NativeAudioSetSourcePayload): Promise<NativeAudioState>play(): Promise<NativeAudioState>pause(): Promise<NativeAudioState>seekTo(position: number): Promise<NativeAudioState>setRate(rate: number): Promise<NativeAudioState>getState(): Promise<NativeAudioState>getProgressCheckpoint(): Promise<NativeAudioProgressCheckpoint | null>dispose(): Promise<void>addStateListener(handler): Promise<() => void>
Validation and errors:
setSourcerejects ifsrcis empty.seekTorejects ifpositionis missing/non-finite.setRaterejects ifrate <= 0or non-finite.- Native failures can also be reflected in
NativeAudioState.errorwith statuserror.
State semantics:
idle: player idle or pausedloading: bufferingplaying: active playbackended: reached media enderror: runtime/player error
Native Progress Checkpoint (mobile)
The plugin can persist a lightweight playback checkpoint natively (Android/iOS) for background playback scenarios where the WebView/JS runtime may be suspended.
Checkpoint shape:
idcurrentTimeupdatedAtMsstatus(optional)
How it works:
- Pass
idinsetSource(...)to associate playback with an app-level item. - During playback, the plugin stores a single checkpoint (v1) in native storage:
- Android:
SharedPreferences - iOS:
UserDefaults
- Android:
- Checkpoint updates are throttled during playback and also written on pause/seek/end/dispose (best effort).
- Near-start micro progress (<= 0.25s) is ignored to avoid noisy checkpoints.
Read the last checkpoint:
const checkpoint = await getProgressCheckpoint();
if (checkpoint) {
console.log(checkpoint.id, checkpoint.currentTime, checkpoint.updatedAtMs);
}
Notes:
- The plugin stores only audio progress checkpoint data, not your app's story cache.
- v1 stores only the latest active story checkpoint (single record).
Source handling
Supported source forms:
- remote URL (
https://..., and platform-dependenthttp://...) - local file URL (
file://...) - local file path
iOS extras:
asset://localhost/...andhttp://asset.localhost/...resolve to local file paths.- Local files with missing/
.binextension are inspected by header and remapped via temporary alias (mp3,wav,m4a,ogg) for better AVPlayer compatibility.
Troubleshooting
unknown command plugin:native-audio|...
- Ensure Rust registration exists:
.plugin(tauri_plugin_native_audio::init()) - Ensure capability includes
native-audio:default(or explicit allow permissions)
Android notification controls not shown:
- Call
initialize()before playback - Start playback using
play() - Grant notifications permission on Android 13+
iOS background playback not working:
- Enable Background Modes -> Audio for the iOS app target
- Verify stream URL is reachable and valid
iOS HTTP URL fails:
- ATS can block plain HTTP
- Prefer HTTPS or configure ATS exceptions in app settings
AI Notice
This plugin was fully developed with the help of AI. No junior developers were harmed, only a few CPU cores worked overtime 😂 Yes, this duck was also invented by AI 🤣
License
MIT or Apache-2.0, where applicable.