Headless, state-driven audio playback API for Tauri 2.x apps with native transport control integration.
This plugin provides a cross-platform audio playback interface with transport controls (play, pause, stop, seek), volume/rate settings, and OS media integration (lock screen, notification shade, headphone controls). It is designed to be wrapped by a consuming app's own API layer.
- State-machine-driven playback with type-safe action gating
- OS transport control integration via metadata (title, artist, artwork)
- Real-time state change events (status, time, volume, etc.)
- Volume, mute, playback rate, and loop controls
- Cross-platform support (Windows, iOS, Android)
| Platform | Supported |
|---|---|
| Windows | Planned |
| macOS | Planned |
| Android | Planned |
| iOS | Planned |
-
Install NPM dependencies:
npm install
-
Build the TypeScript bindings:
npm run build
-
Build the Rust plugin:
cargo build
Run all tests (TypeScript and Rust):
npm testRun TypeScript tests only:
npm run test:tsRun Rust tests only:
cargo test --workspace --libThis plugin requires a Rust version of at least 1.89
Add the plugin to your Cargo.toml:
src-tauri/Cargo.toml
[dependencies]
tauri-plugin-audio = { git = "https://github.com/silvermine/tauri-plugin-audio" }Install the JavaScript bindings:
npm install @silvermine/tauri-plugin-audioInitialize the plugin in your tauri::Builder:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_audio::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}import { getPlayer, PlaybackStatus } from '@silvermine/tauri-plugin-audio';
async function checkPlayer() {
const player = await getPlayer();
console.debug(`Status: ${player.status}, Time: ${player.currentTime}`);
}The API uses discriminated unions with type guards for compile-time safety. Only valid transport actions are available based on the player's status.
import {
getPlayer, PlaybackStatus, hasAction, AudioAction,
} from '@silvermine/tauri-plugin-audio';
async function loadAndPlay() {
const player = await getPlayer();
if (player.status === PlaybackStatus.Idle) {
const { player: ready } = await player.load(
'https://example.com/song.mp3',
{
title: 'My Song',
artist: 'Artist Name',
artwork: 'https://example.com/cover.jpg',
},
);
await ready.play();
}
}
async function managePlayback() {
const player = await getPlayer();
if (hasAction(player, AudioAction.Pause)) {
await player.pause();
} else if (hasAction(player, AudioAction.Play)) {
await player.play();
}
}Volume, mute, playback rate, and loop controls are always available regardless of playback status.
import { getPlayer } from '@silvermine/tauri-plugin-audio';
async function adjustSettings() {
const player = await getPlayer();
await player.setVolume(0.5);
await player.setMuted(false);
await player.setPlaybackRate(1.5);
await player.setLoop(true);
}listen receives updates for state transitions (status changes,
volume, settings, errors).
import { getPlayer, PlaybackStatus } from '@silvermine/tauri-plugin-audio';
async function watchPlayback() {
const player = await getPlayer();
const unlisten = await player.listen((updated) => {
console.debug(`Status: ${updated.status}`);
if (updated.status === PlaybackStatus.Ended) {
console.debug('Playback finished');
}
});
// To stop listening:
unlisten();
}onTimeUpdate receives lightweight, high-frequency updates
(~250ms) carrying only currentTime and duration, avoiding the
overhead of serializing the full player state on every tick.
import { getPlayer } from '@silvermine/tauri-plugin-audio';
async function trackProgress() {
const player = await getPlayer();
const unlisten = await player.onTimeUpdate((time) => {
const pct = time.duration > 0
? (time.currentTime / time.duration) * 100
: 0;
console.debug(`${time.currentTime}s / ${time.duration}s (${pct.toFixed(1)}%)`);
});
// To stop listening:
unlisten();
}The player follows a state machine where transport actions are gated by
the current PlaybackStatus:
| Status | Allowed Actions |
|---|---|
| Idle | load |
| Loading | stop |
| Ready | play, seek, stop |
| Playing | pause, seek, stop |
| Paused | play, seek, stop |
| Ended | play, seek, load, stop |
| Error | load |
Settings (setVolume, setMuted, setPlaybackRate, setLoop),
listen, and onTimeUpdate are always available regardless of
status.
This project follows the Silvermine standardization guidelines. Key standards include:
- EditorConfig: Consistent editor settings across the team
- Markdownlint: Markdown linting for documentation
- Commitlint: Conventional commit message format
- Code Style: 3-space indentation, LF line endings
npm run standardsMIT
Contributions are welcome! Please follow the established coding standards and commit message conventions.