Engee documentation
Notebook

Audio Recorder

This example shows the implementation of a web application for recording audio from your device's microphone. The full cycle of the application is described below.

  1. The user clicks the 🎤 → button to start recording
  2. During recording:
    • The button turns red with animation
    • The timer is running
    • Audio data is being collected
  3. When stopping:
  • An audio
    file is formed - A player and a download button appear
  1. Downloading creates the file "recording-{date}.wav"

A detailed analysis of the audio recorder code is provided below. The code starts with a basic HTML structure and CSS styles for a beautiful interface.:

<div class="container">
    <div class="title">Audio Recorder</div>
    <div class="timer" id="timer">00:00</div>
    <button id="recordBtn">🎤</button>
    <button class="download-btn hidden" id="downloadBtn" title="Download recording"></button>
    <div id="status">Press 🎤 to start recording</div>
    <div class="audio-container" id="audioContainer">
        <audio id="audioPlayer" controls></audio>
    </div>
</div>

Key elements:

  • recordBtn – round record button,
  • downloadBtn – download button (initially hidden),
  • timer – displays the recording time,
  • audioPlayer – an element for playing recorded audio.

Next comes the initialization of variables

let mediaRecorder;
let audioChunks = [];
let audioBlob;
let audioUrl;
let startTime;
let timerInterval;
let stream;
let isRecording = false;

Variables store the application state:

  • mediaRecorder – an object for recording media,
  • audioChunks – an array for storing recorded data,
  • audioBlob/audioUrl – final audio,
  • timerInterval – to update the timer,
  • stream – data stream from the microphone.

After that, auxiliary functions are declared.

function formatTime(seconds) {
    const mins = Math.floor(seconds / 60).toString().padStart(2, '0');
    const secs = Math.floor(seconds % 60).toString().padStart(2, '0');
    return `${mins}:${secs}`;
}

function updateTimer() {
    const elapsed = Math.floor((Date.now() - startTime) / 1000);
    timerElement.textContent = formatTime(elapsed);
}

formatTime converts seconds to the "MM:SS" format, and updateTimer updates the timer display.

Next comes the recording startup function.

function startRecording() {
    navigator.mediaDevices.getUserMedia({ audio: true })
        .then(function(s) {
            stream = s;
            mediaRecorder = new MediaRecorder(stream);
            audioChunks = [];
            
            mediaRecorder.ondataavailable = function(e) {
                if (e.data.size > 0) {
                    audioChunks.push(e.data);
                }
            };
            
            mediaRecorder.onstop = function() {
                audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
                audioUrl = URL.createObjectURL(audioBlob);
                audioPlayer.src = audioUrl;
                downloadBtn.classList.remove('hidden');
                statusElement.textContent = "Recording saved! Click 🎤 to record again";
            };
            
            mediaRecorder.start(100);
            startTime = Date.now();
            timerInterval = setInterval(updateTimer, 1000);
            updateTimer();
            isRecording = true;
            recordBtn.classList.add('recording');
            recordBtn.textContent = "⏹️";
            statusElement.textContent = "Recording... Click ⏹️ to stop";
        })
        .catch(function(err) {
            console.error('Error accessing microphone:', err);
            statusElement.textContent = "Error accessing microphone";
        });
}

The logic of the operation is described below.

  1. Request access to the microphone via getUserMedia
  2. Create MediaRecorder to record a stream
  3. Setting up handlers:
    • ondataavailable – collects pieces of audio,
    • onstop – creates the final file and shows the download button.
  4. Start the timer and change the interface

After that, the recording stop is implemented.

function stopRecording() {
    if (mediaRecorder && mediaRecorder.state !== 'inactive') {
        mediaRecorder.stop();
        clearInterval(timerInterval);
        stream.getTracks().forEach(track => track.stop());
        isRecording = false;
        recordBtn.classList.remove('recording');
        recordBtn.textContent = "🎤";
    }
}

Stops recording, timer, and releases microphone resources.

And downloading the recording:

function downloadRecording() {
    if (audioUrl) {
        const a = document.createElement('a');
        a.href = audioUrl;
        const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
        a.download = `recording-${timestamp}.wav`;
        a.click();
        statusElement.textContent = "Download started...";
    }
}

Creates a temporary download link for a file with a unique name.

There is also one event handler that allows the recording buttons to work as a start/stop switch, and also makes it possible to link the entire interface and its update to the audio file recording event.

recordBtn.addEventListener('click', function() {
    if (isRecording) {
        stopRecording();
    } else {
        startRecording();
    }
});

downloadBtn.addEventListener('click', downloadRecording);

Now let's launch the app itself and see how it works.

In [ ]:
display("text/html", read("audio_recorder.html", String))
Audio Recorder
Audio Recorder
00:00
Press 🎤 to start recording

Conclusion

This example and the tool itself can be useful for generating test data samples for DSP.