Voice Recording in a Firefox Extension: MediaRecorder, Background Scripts, and the MP3 Download
Recording audio in a browser extension uses APIs that most web developers have worked with in web apps but rarely in extension contexts. Here is what is different about voice recording in a Firefox WebExtension.
Recording audio in a browser extension uses APIs that most web developers have worked with in web apps but rarely in extension contexts. Here is what is different about voice recording in a Firefox WebExtension.
The MediaRecorder API
Voice Notes uses the standard MediaRecorder API. This is the same API used by web applications for audio recording — there is nothing extension-specific about the recording itself.
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
const recorder = new MediaRecorder(stream, { mimeType: 'audio/webm;codecs=opus' });
const chunks = [];
recorder.ondataavailable = (e) => chunks.push(e.data);
recorder.onstop = () => {
const blob = new Blob(chunks, { type: 'audio/webm;codecs=opus' });
// convert to MP3 or save as-is
};
recorder.start();
The extension-specific part: popup context
In a web app, the page persists while recording. In a Firefox extension popup, the popup closes when the user clicks elsewhere — which terminates the MediaRecorder.
The solution: move the recording logic to the background script (or a persistent side panel), not the popup UI. The popup sends messages to the background script to start and stop recording. The background script holds the MediaRecorder instance, which persists even when the popup closes.
// popup.js
document.getElementById('record').onclick = () => {
browser.runtime.sendMessage({ action: 'startRecording' });
};
// background.js
browser.runtime.onMessage.addListener((msg) => {
if (msg.action === 'startRecording') {
navigator.mediaDevices.getUserMedia({ audio: true })
.then(stream => startRecorder(stream));
}
});
Permissions
The manifest.json requires the activeTab permission for content scripts and the microphone permission via permissions: ["activeTab"]. The microphone access is requested via getUserMedia() at runtime — Firefox will show the browser-level microphone permission prompt the first time.
For Manifest V3 (which Firefox is migrating toward), background service workers replace persistent background pages. Service workers do not persist indefinitely — this affects MediaRecorder workflows since a service worker may be terminated during a long recording. The current implementation uses background pages (MV2) for reliability.
The download
The recorded audio is a Blob in audio/webm format. I convert it to MP3 using the lamejs library (a pure-JavaScript MP3 encoder) so the output file is widely compatible.
The download is triggered using a hidden <a> element with the download attribute:
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `voice-note-${Date.now()}.mp3`;
a.click();
URL.revokeObjectURL(url);
This saves the file to the user's Downloads folder without any server interaction.
Privacy
Nothing in this flow touches a server. The audio is captured by Firefox via the Web Audio API, encoded in the browser using lamejs, and saved to disk. The extension has no network permissions for audio data.
Voice Notes and Audio Recorder — Firefox Add-ons
Voice Notes is one of 11 Firefox extensions I built — see the full series.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.