| Crates.io | tauri-plugin-media-toolkit |
| lib.rs | tauri-plugin-media-toolkit |
| version | 0.1.1 |
| created_at | 2025-12-18 20:14:40.575295+00 |
| updated_at | 2025-12-18 21:40:28.913774+00 |
| description | Comprehensive media toolkit for Tauri: edit, play, convert audio/video files |
| homepage | https://github.com/brenogonzaga/tauri-plugin-media-toolkit |
| repository | https://github.com/brenogonzaga/tauri-plugin-media-toolkit |
| max_upload_size | |
| id | 1993390 |
| size | 317,870 |
Cross-platform media toolkit plugin for Tauri 2.x applications. Provides media editing, playback, and analysis capabilities for desktop (Windows, macOS, Linux) and mobile (iOS, Android).
Add the plugin to your Cargo.toml:
[dependencies]
tauri-plugin-media-toolkit = "0.1"
Install the JavaScript guest bindings:
npm install tauri-plugin-media-toolkit-api
# or
yarn add tauri-plugin-media-toolkit-api
# or
pnpm add tauri-plugin-media-toolkit-api
In your Tauri app setup:
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_media_toolkit::init())
.run(tauri::generate_context!())
.expect("error while running application");
}
Add permissions to your capabilities/default.json:
{
"permissions": ["media-toolkit:default"]
}
For granular permissions, you can specify individual commands:
{
"permissions": [
"media-toolkit:allow-select-media-file",
"media-toolkit:allow-get-media-info",
"media-toolkit:allow-trim",
"media-toolkit:allow-convert",
"media-toolkit:allow-extract-audio",
"media-toolkit:allow-play",
"media-toolkit:allow-pause",
"media-toolkit:allow-resume",
"media-toolkit:allow-stop",
"media-toolkit:allow-seek",
"media-toolkit:allow-get-playback-status",
"media-toolkit:allow-set-volume",
"media-toolkit:allow-check-permission",
"media-toolkit:allow-request-permission",
"media-toolkit:allow-cleanup-cache"
]
}
Add to AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Add to Info.plist:
<key>NSAppleMusicUsageDescription</key>
<string>This app needs access to play audio files.</string>
import { getMediaInfo } from "tauri-plugin-media-toolkit-api";
const info = await getMediaInfo("/path/to/video.mp4");
console.log(`Type: ${info.mediaType}`);
console.log(`Duration: ${info.durationMs}ms`);
console.log(`Resolution: ${info.width}x${info.height}`);
console.log(`Audio: ${info.audioCodec}, Video: ${info.videoCodec}`);
import { trim } from "tauri-plugin-media-toolkit-api";
// Trim video from 10s to 30s
const result = await trim({
inputPath: "/path/to/video.mp4",
outputPath: "/path/to/trimmed", // without extension
startMs: 10000,
endMs: 30000,
preserveQuality: true, // no re-encoding
});
console.log(`Output: ${result.outputPath}, Size: ${result.fileSize} bytes`);
import { convert } from "tauri-plugin-media-toolkit-api";
// Convert WebM to MP4
const result = await convert({
inputPath: "/path/to/video.webm",
outputPath: "/path/to/converted",
format: "mp4",
videoQuality: "high",
});
import { extractAudio } from "tauri-plugin-media-toolkit-api";
// Extract audio as MP3
const result = await extractAudio({
inputPath: "/path/to/video.mp4",
outputPath: "/path/to/audio",
format: "mp3",
audioQuality: "high",
});
import {
play,
pause,
resume,
stop,
seek,
setVolume,
getPlaybackStatus,
} from "tauri-plugin-media-toolkit-api";
// Start playback
await play({
filePath: "/path/to/audio.mp3",
volume: 0.8,
});
// Check status
const status = await getPlaybackStatus();
console.log(
`Playing: ${status.isPlaying}, Position: ${status.currentPositionMs}ms`
);
// Control playback
await pause();
await resume();
await seek({ positionMs: 30000 }); // Seek to 30s
await setVolume(0.5); // 50% volume
await stop();
import { onProgress } from "tauri-plugin-media-toolkit-api";
// Listen for operation progress
const unlisten = await onProgress(event => {
console.log(`${event.operation}: ${event.progress}%`);
if (event.estimatedTimeMs) {
console.log(`ETA: ${event.estimatedTimeMs}ms`);
}
});
// Start a long operation
await convert({
inputPath: "/path/to/large-video.mp4",
outputPath: "/path/to/converted",
format: "webm",
});
// Clean up listener
unlisten();
import { trim, parseErrorType } from "tauri-plugin-media-toolkit-api";
try {
await trim(config);
} catch (error) {
const errorType = parseErrorType(String(error));
switch (errorType) {
case "FileNotFound":
console.error("Input file does not exist");
break;
case "PermissionDenied":
console.error("No permission to access file");
break;
case "InsufficientStorage":
console.error("Not enough disk space");
break;
case "UnsupportedFormat":
console.error("Format not supported on this platform");
break;
default:
console.error("Unknown error:", error);
}
}
getMediaInfo(filePath: string): Promise<MediaInfo>Get detailed metadata from a media file.
Returns:
mediaType: "audio" | "video" | "unknown"durationMs: Duration in millisecondsfileSize: Size in bytesformat: File extensionaudioCodec, videoCodec: Codec nameswidth, height, frameRate: Video propertiessampleRate, channels, audioBitrate: Audio propertiestrim(config: TrimConfig): Promise<OperationResult>Cut media to a specific time range.
Config:
inputPath: Source file pathoutputPath: Destination (without extension)startMs, endMs: Time range in millisecondsformat: Output format (optional, defaults to input)preserveQuality: Stream copy without re-encoding (default: false)audioQuality, videoQuality: Quality presetsconvert(config: ConvertConfig): Promise<OperationResult>Convert media to a different format.
Config:
inputPath: Source file pathoutputPath: Destination (without extension)format: Target format (mp3, mp4, wav, etc.)audioQuality, videoQuality: Quality presetsextractAudio(config: ExtractAudioConfig): Promise<OperationResult>Extract audio track from video.
Config:
inputPath: Source video pathoutputPath: Destination (without extension)format: Audio format (mp3, wav, aac, etc.)audioQuality: Quality presetplay(config: PlayConfig): Promise<void>Start media playback.
Config:
filePath: Media file pathvolume: Volume level (0.0-1.0, default: 1.0)pause(): Promise<void>Pause current playback.
resume(): Promise<void>Resume paused playback.
stop(): Promise<void>Stop playback and release resources.
seek(config: SeekConfig): Promise<void>Seek to specific position.
Config:
positionMs: Target position in millisecondssetVolume(volume: number): Promise<void>Set playback volume (0.0 = mute, 1.0 = full).
getPlaybackStatus(): Promise<PlaybackStatus>Get current playback status.
Returns:
isPlaying: Whether currently playingisPaused: Whether pausedcurrentPositionMs: Current positiondurationMs: Total durationvolume: Current volume levelselectMediaFile(): Promise<FileSelectionResult> (Android only)Open native file picker and copy selected file to cache.
checkPermission(): Promise<PermissionResponse>Check storage permission status.
requestPermission(): Promise<PermissionResponse>Request storage permissions.
cleanupCache(): Promise<CleanupResult>Clean up temporary media files.
onProgress(handler: (event: ProgressEvent) => void): Promise<UnlistenFn>Listen for operation progress events.
parseErrorType(error: string): MediaEditorErrorTypeParse error message to determine error type.
interface MediaInfo {
path: string;
mediaType: "audio" | "video" | "unknown";
durationMs: number;
fileSize: number;
format: string;
hasAudio: boolean;
hasVideo: boolean;
audioCodec?: string;
videoCodec?: string;
sampleRate?: number;
channels?: number;
audioBitrate?: number;
width?: number;
height?: number;
frameRate?: number;
videoBitrate?: number;
}
interface TrimConfig {
inputPath: string;
outputPath: string; // without extension
startMs: number;
endMs: number;
format?: OutputFormat;
audioQuality?: AudioQuality;
videoQuality?: VideoQuality;
preserveQuality?: boolean; // stream copy, no re-encoding
}
interface ConvertConfig {
inputPath: string;
outputPath: string;
format: OutputFormat;
audioQuality?: AudioQuality;
videoQuality?: VideoQuality;
}
interface OperationResult {
success: boolean;
outputPath: string;
durationMs: number;
fileSize: number;
}
interface PlaybackStatus {
isPlaying: boolean;
isPaused: boolean;
currentPositionMs: number;
durationMs: number;
volume: number;
}
| Preset | Bitrate | Use Case |
|---|---|---|
low |
96 kbps | Voice/Podcasts |
medium |
192 kbps | General purpose |
high |
320 kbps | Music/High quality |
lossless |
Original | No re-encoding |
| Preset | Resolution | Use Case |
|---|---|---|
low |
480p | Small file size |
medium |
720p | Balanced |
high |
1080p | High quality |
original |
Unchanged | No re-encoding |
mp3, wav, aac, m4a, ogg, flacmp4, webm| Feature | Windows | macOS | Linux | iOS | Android |
|---|---|---|---|---|---|
| Get Media Info | ✅ | ✅ | ✅ | ✅ | ✅ |
| Trim | ✅ | ✅ | ✅ | ✅ | ✅ |
| Convert | ✅ | ✅ | ✅ | ⚠️ | ✅ |
| Extract Audio | ✅ | ✅ | ✅ | ✅ | ✅ |
| Playback | ✅ | ✅ | ✅ | ✅ | ✅ |
| Progress Events | ✅ | ✅ | ✅ | ⚠️ | ✅ |
| File Picker | ❌ | ❌ | ❌ | ❌ | ✅ |
| Hardware Accel | ✅ | ✅ | ✅ | ✅ | ✅ |
Legend:
MediaPlayer for playbackMediaMetadataRetriever for metadataAVFoundation for all operationsAVPlayer for playbackDesktop:
fs.existsSync(path)Mobile:
selectMediaFile() to properly copy files from external storageAndroid:
const perm = await requestPermission();
if (!perm.granted) {
console.error("Storage permission required");
return;
}
iOS: Add required keys to Info.plist:
<key>NSAppleMusicUsageDescription</key>
<string>Access media files</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Access photo library</string>
iOS Limitations:
result.warning for iOS-specific messagesAndroid:
Tips:
preserveQuality: true for trim when possible (stream copy, no re-encoding)videoQuality: "low"onProgress() listenerDesktop:
// Check available space before operations
const info = await getMediaInfo(inputPath);
const estimatedSize = info.fileSize * 1.5; // Estimate output size
// Compare with available disk space
Mobile:
await cleanupCache()Checklist:
getPlaybackStatus() for current stateDebug:
const status = await getPlaybackStatus();
console.log("Playing:", status.isPlaying);
console.log("Paused:", status.isPaused);
console.log("Position:", status.currentPositionMs);
Solution: Verify video has audio track:
const info = await getMediaInfo(videoPath);
if (!info.hasAudio) {
console.error("Video has no audio track");
return;
}
Note: The plugin automatically appends the correct extension:
// Input: outputPath = "/path/to/output"
// With format = "mp4"
// Actual output: "/path/to/output.mp4"
// Don't include extension in outputPath:
// ❌ Wrong: outputPath = "/path/to/output.mp4"
// ✅ Correct: outputPath = "/path/to/output"
Common issues:
Linux:
# Install ffmpeg and codecs
sudo apt install ffmpeg libavcodec-extra
See the examples/media-toolkit-example directory for a complete working demo with React + Material UI.
| Component | Version |
|---|---|
| Tauri | 2.9+ |
| Rust | 1.77+ |
| Android SDK | 24+ (Android 7.0+) |
| iOS | 14.0+ |
MIT