Web Audio API Constraints: Building Recording Modes and Handling Hardware Reality
I underestimated how capable browser audio APIs have become.
Years of Zoom, Teams, and Google Meet pushed browsers to implement serious audio processing. What started as basic getUserMedia() access evolved into a full suite of real-time audio capabilities: echo cancellation, noise suppression, automatic gain control—all running natively in the browser without plugins or external dependencies.
I needed to implement browser-based audio recording with two modes: Voice (with processing) and Music (without). The Web Audio API provides MediaTrackConstraints for this, but the gap between what you request and what you actually get turned out to be significant.
Here’s what I learned building it.
The Constraint System
The Web Audio API’s getUserMedia() accepts constraints that tell the browser how to configure the audio stream. For recording, the relevant constraints are:
const audioConstraints: MediaTrackConstraints = {
sampleRate: 44100,
echoCancellation: boolean,
noiseSuppression: boolean,
autoGainControl: boolean,
};
The idea: enable processing for voice, disable for music.
Voice Mode
{
sampleRate: 44100,
echoCancellation: true,
noiseSuppression: true,
autoGainControl: true
}
This requests the browser apply:
- Echo Cancellation (AEC): Acoustic echo cancellation to remove speaker playback from mic input
- Noise Suppression: Frequency-domain filtering for background noise (fans, traffic, typing)
- Auto Gain Control (AGC): Dynamic input gain adjustment for consistent volume
When hardware supports it, the difference is significant. Voice recordings are noticeably louder and clearer compared to raw microphone input. AGC brings quiet speech up to audible levels, noise suppression cuts room tone, and the result is consistently intelligible audio without manual gain riding.
Not all devices respect these constraints, but when they do, Voice mode substantially improves recording quality for speech.
Music Mode
{
sampleRate: 44100,
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
}
This requests no processing—pure audio from the microphone. Natural dynamics, full frequency response, no artifacts.
The Problem: Hardware Doesn’t Always Listen
Here’s where theory met reality. Some devices ignore the false constraints entirely.
I tested across:
- MacBook Pro built-in mic: Respects constraints ✓
- USB condenser mic: Respects constraints ✓
- AirPods Pro: Ignores
false, forces processing ON - iPhone Safari: Ignores
false, OS-level processing can’t be disabled - Budget USB headsets: Mixed behavior
The Web Audio API spec says implementations should respect constraints, but there’s no guarantee. Hardware limitations, OS-level processing, Bluetooth protocol constraints—all can override your settings.
The Solution: Transparency Through Diagnostics
If hardware was going to ignore my constraints, users needed to know.
I added a diagnostics panel that reads back the actual stream settings:
const audioTrack = stream.getAudioTracks()[0];
const settings = audioTrack.getSettings();
// Check if Music mode was actually applied
if (recordingMode === 'music') {
if (settings.echoCancellation ||
settings.noiseSuppression ||
settings.autoGainControl) {
console.warn('Music mode requested but processing still enabled');
}
}
The diagnostics panel shows:
Mode: Music (high fidelity)
Device: AirPods Pro
Sample Rate: 48kHz
Channels: 1
Echo Cancel: ON ⚠️
Noise Suppress: ON ⚠️
Auto Gain: ON ⚠️
⚠️ Processing is ON despite Music mode (device limitation)
Users can verify what they’re actually getting. No surprises.
Implementation Flow
- User selects Voice or Music mode
- Mode saved to localStorage for persistence
startRecording()called with mode parameter- Build
MediaTrackConstraintsbased on mode - Call
navigator.mediaDevices.getUserMedia(constraints) - Stream acquired, read actual settings via
getSettings() - Compare requested vs actual, warn if mismatch
- Store stream info for diagnostics display
- Create MediaRecorder, start encoding (WebM/MP4 at 256kbps)
The diagnostics panel is always available during recording. Tap “Show Recording Details” to see what’s happening.
Browser vs Hardware vs OS Processing
Three layers can apply audio processing:
- Hardware: Some USB mics and smartphone chipsets have DSP that runs before the OS sees audio
- OS: macOS, iOS, Android can apply processing at the driver level
- Browser: Software fallback when hardware doesn’t support requested settings
The Web Audio API sits at layer 3. If layers 1 or 2 force processing, there’s nothing the browser can do.
Common patterns:
- Desktop browsers: Usually respect constraints (good hardware control)
- Smartphone browsers: OS often forces processing (especially iOS)
- Bluetooth devices: Protocol limitations prevent full control
Stack
Full pipeline:
- Microphone → Web Audio API (with constraints)
- MediaRecorder encodes to WebM/MP4
- Upload to S3 via presigned URL
- Lambda transcodes to MP3 (128kbps for free, 320kbps VBR for Pro)
- CloudFront serves final file
Browser-native recording. No plugins, no Flash, no external dependencies.
What I’d Do Differently
Add sample rate validation earlier. Some devices only support 48kHz, not 44.1kHz. The API accepts the constraint but coerces to 48kHz silently. I should validate getSettings() immediately and warn users if sample rate doesn’t match.
Provide a “compatibility mode” toggle. If diagnostics show constraints aren’t respected, offer a Voice-only mode that works with device limitations rather than fighting them.
Test on more Bluetooth devices. AirPods were the obvious case, but there are dozens of Bluetooth audio devices with varying constraint support. Need more test coverage.
Browser Compatibility
Works on:
- Chrome 35+ (best constraint support)
- Firefox 25+ (good support)
- Safari 14.1+ (iOS has OS-level limitations)
- Edge 12+ (Chromium-based, same as Chrome)
The getUserMedia() API is widely supported. The MediaTrackConstraints behavior varies by device.
If you’re building browser-based audio recording, test your constraints on real hardware. The spec is clear, but hardware support is inconsistent. Build diagnostics into your UI so users understand what they’re actually getting.