tauri-plugin-native-audio 1.0.3

Native mobile audio playback plugin for Tauri
Documentation

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:

  1. crates.io + npm (simplest)
  2. Git dependency for Rust + npm package for JS
  3. Git/path dependency for both Rust and JS in monorepo/submodule setup

Install the core plugin in src-tauri/Cargo.toml:

[dependencies]
tauri-plugin-native-audio = "1.0.0"
# or from Git:
tauri-plugin-native-audio = { git = "https://github.com/uvarov-frontend/tauri-plugin-native-audio", tag = "v1.0.0" }

Install the JavaScript guest bindings:

pnpm add tauri-plugin-native-audio-api
# or
npm add tauri-plugin-native-audio-api
# or
yarn add tauri-plugin-native-audio-api

Setting up

1. Register the plugin in Rust

src-tauri/src/lib.rs

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_native_audio::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

2. Enable permissions in capability

Add plugin permissions to your capability file (example: src-tauri/capabilities/default.json):

{
  "identifier": "default",
  "description": "Default capability",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "native-audio:default"
  ]
}

native-audio:default includes:

  • allow-initialize
  • allow-register-listener
  • allow-remove-listener
  • allow-set-source
  • allow-play
  • allow-pause
  • allow-seek-to
  • allow-set-rate
  • allow-get-state
  • allow-get-progress-checkpoint
  • allow-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.INTERNET
    • android.permission.FOREGROUND_SERVICE
    • android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK
    • android.permission.POST_NOTIFICATIONS
    • android.permission.WAKE_LOCK

iOS:

  • Uses AVAudioSession category .playback.
  • For true background audio, enable Background Modes -> Audio in your iOS target.
  • Lock screen controls and now playing metadata are integrated through MediaPlayer framework 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:

  • setSource rejects if src is empty.
  • seekTo rejects if position is missing/non-finite.
  • setRate rejects if rate <= 0 or non-finite.
  • Native failures can also be reflected in NativeAudioState.error with status error.

State semantics:

  • idle: player idle or paused
  • loading: buffering
  • playing: active playback
  • ended: reached media end
  • error: 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:

  • id
  • currentTime
  • updatedAtMs
  • status (optional)

How it works:

  • Pass id in setSource(...) to associate playback with an app-level item.
  • During playback, the plugin stores a single checkpoint (v1) in native storage:
    • Android: SharedPreferences
    • iOS: UserDefaults
  • 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-dependent http://...)
  • local file URL (file://...)
  • local file path

iOS extras:

  • asset://localhost/... and http://asset.localhost/... resolve to local file paths.
  • Local files with missing/.bin extension 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.