DEV Community

Cover image for How I Built a Client-Side Audio Toolkit (No Server Uploads)
Thomas Yates
Thomas Yates

Posted on

How I Built a Client-Side Audio Toolkit (No Server Uploads)

The Problem

I needed to add cover art to a FLAC file. Simple task, right?

Not so fast. Every solution I found either required:

  • Installing desktop software (overkill for a one-time task)
  • Uploading my files to someone's server (privacy concerns)
  • Paying for a subscription service (unnecessary expense)

As a developer, this felt like a solvable problem. So I built a browser-based solution.

The Solution: FFmpeg.wasm

FFmpeg.wasm is a WebAssembly port of FFmpeg that runs entirely in the browser. This means you can perform complex audio/video processing without server-side code.

Key advantage: Files never leave the user's device. Everything processes locally using WebAssembly.

What I Built

What started as a simple FLAC metadata editor turned into a full audio toolkit:

  • Format Converter - Convert between MP3, FLAC, WAV, AAC, OGG, M4A
  • Metadata Editor - Add cover art and edit tags for audio files
  • Slowed + Reverb Maker - Create slowed and reverb audio effects (popular for TikTok/vaporwave)
  • Audio Trimmer - Cut and trim audio files with fade effects
  • Video to Audio - Extract audio tracks from video files
  • Volume Booster - Increase volume or normalize audio loudness

Live demo: soundtools.io

Technical Implementation

Stack:

  • FFmpeg.wasm for audio processing
  • Vanilla JavaScript (no frameworks - keeping it simple)
  • Client-side only - zero backend code

Key challenges:

1. Lazy Loading FFmpeg
FFmpeg.wasm is large (~31MB for core + wasm files). Loading it immediately would destroy page performance.

Solution: Lazy load on demand. The page loads instantly with just HTML/CSS. FFmpeg only loads when a user selects a file:

let ffmpeg = null;

async function loadFFmpeg() {
    if (ffmpeg) return; // Already loaded

    showStatus('Loading audio processor...');

    const { FFmpeg } = await import('../../ffmpeg/index.js');
    ffmpeg = new FFmpeg();

    await ffmpeg.load({
        coreURL: '../../ffmpeg/ffmpeg-core.js',
        wasmURL: '../../ffmpeg/ffmpeg-core.wasm',
    });

    showStatus('Ready to process!');
}

// Only load when user selects a file
fileInput.addEventListener('change', async (e) => {
    if (!ffmpeg) await loadFFmpeg();
    // Now process the file...
});
Enter fullscreen mode Exit fullscreen mode

Result:

  • Initial page load: ~50KB (fast!)
  • FFmpeg loads in background when needed: ~31MB
  • Users get instant page, FFmpeg ready by the time they select a file

2. Memory Management
Large audio files (500MB+) can exhaust browser memory. Solution: Process in chunks and clear memory aggressively after each operation.

3. Performance
WebAssembly is fast, but not native-fast. For typical audio files (10-100MB), performance is acceptable (10-30 seconds processing time).

4. File Format Support
Different metadata standards (ID3v2 for MP3, Vorbis Comments for FLAC, MP4 tags for M4A) required format-specific handling.

Why Client-Side Processing Matters

Privacy: Your audio files contain metadata, personal recordings, or copyrighted content. Client-side processing means zero data leaves your device.

Cost: No server infrastructure means no hosting costs. The entire site runs as static HTML/JS.

Offline Capability: Once the page loads, tools work offline (after FFmpeg.wasm is cached).

Performance: Lazy loading FFmpeg means pages load instantly while the heavy lifting happens in the background.

What's Next

Working on:

  • Audio joiner (merge multiple files)
  • Vocal remover (using AI/ML models in-browser)
  • Better mobile optimization

Try It

Check out the toolkit at soundtools.io

All tools are free, no account required, no tracking.

Technical Details

For developers interested in FFmpeg.wasm implementation, the basic pattern is:

// Load FFmpeg (lazy loaded)
const ffmpeg = new FFmpeg();
await ffmpeg.load();

// Write input file to virtual filesystem
await ffmpeg.writeFile('input.mp3', audioData);

// Run FFmpeg command
await ffmpeg.exec(['-i', 'input.mp3', '-codec:a', 'libmp3lame', 'output.mp3']);

// Read output file
const data = await ffmpeg.readFile('output.mp3');
Enter fullscreen mode Exit fullscreen mode

The real complexity comes from handling different formats, managing memory, building a clean UI around the processing pipeline, and optimizing page load performance with lazy loading.

Feedback Welcome

Would love to hear thoughts on:

  • Performance with your audio files
  • Which tools you find most useful
  • What other audio tools would be helpful

Built this as a side project to solve my own problem. Hopefully useful for others too.

Top comments (0)