Real-Time Microphone Transcription (Live Simulation)

In this lesson, we’re enhancing our transcription system to work like a live microphone transcription tool. Instead of recording the entire audio before transcribing, we now record short chunks (3 seconds each) and transcribe them one-by-one as they arrive—simulating a live transcription experience directly in the browser.


What You Will Learn

This unit covers:

  • How to capture short audio snippets (chunks) from the user's microphone in real time.
  • How to transcribe each audio chunk immediately after recording.
  • How to update the UI with live transcription results.
  • How to manage a recording session with duration limits and countdown timers.

Frontend: Simulating Live Microphone Transcription

We'll begin with public/app.js, where we configure how microphone input is handled in real time.

const mimeType = 'audio/webm;codecs=opus';
const CHUNK_DURATION = 3000;
const MAX_CHUNKS = 10;
const MAX_TIME_S = (CHUNK_DURATION * MAX_CHUNKS) / 1000;
let chunkCount = 0;
let remainingTime = MAX_TIME_S;

These constants are critical for timing and quality control:

  • mimeType: This tells the MediaRecorder what format to use. audio/webm;codecs=opus specifies WebM format with the Opus codec, which is well-suited for audio and supported by Whisper.
  • CHUNK_DURATION: Each recording session will be sliced into 3-second pieces.
  • MAX_CHUNKS: Limits the session to 10 chunks (to simulate ~30s cap).
  • MAX_TIME_S: Converts chunk duration * number of chunks into seconds for UI display.
  • chunkCount & remainingTime: Track session state and countdown for the user.

Managing Chunk Loop
let intervalId;

async function startRecordingLoop() {
  intervalId = setInterval(() => {
    recordChunk();
  }, CHUNK_DURATION);
  recordChunk(); // Start immediately
}

function stopRecordingLoop() {
  clearInterval(intervalId);
}
  • recordChunk() is the main function that performs all recording logic for a single audio segment. We will discuss in a separate section below.
  • setInterval: Automatically runs recordChunk() every 3 seconds.
  • We also invoke recordChunk() immediately to avoid waiting for the first interval.
  • clearInterval(intervalId): Essential for stopping the session; otherwise, recording will continue indefinitely even if the user presses stop.

Countdown Timer
function updateCountdown() {
  if (countdownEl) {
    countdownEl.textContent = `⏳ ${remainingTime}s remaining`;
  }
}

A simple helper that updates the visible timer on the screen using the remainingTime variable.


Chunk Recording Logic

Now let’s dive into the heart of this simulation: the recordChunk() function.

  • This function is responsible for executing one full iteration of the record → upload → transcribe cycle. Every 3 seconds, it does the following:
  • Requests microphone access to capture a short audio stream.
  • Records exactly one chunk using the browser's MediaRecorder API.
  • Packages the audio data into a Blob for upload.
  • Sends the chunk to the backend, where it’s temporarily stored.
  • Initiates transcription by sending the uploaded file to the Whisper API.
  • Appends the returned text to the live transcript on the UI.

This structure enables us to transcribe small segments in near-real-time, giving users immediate feedback as they speak. By repeating this function on an interval, we simulate continuous live transcription, without needing a streaming connection. Let’s break it down:

async function recordChunk() {
  const stream = await navigator.mediaDevices.getUserMedia({
    audio: {
      sampleRate: 44100,
      channelCount: 1,
      noiseSuppression: true,
      echoCancellation: true,
    },
    video: false,
  });

This line is crucial—it asks the browser for access to the user’s microphone using navigator.mediaDevices.getUserMedia.

  • The audio object specifies a high-quality mono stream:
    • sampleRate: 44100: CD-quality audio.
    • channelCount: 1: Mono (single channel).
    • noiseSuppression: Reduces background noise.
    • echoCancellation: Removes speaker echo (common in browser mic recordings).
  • This is called a high-quality mono microphone stream with basic noise filtering, which helps ensure the transcription is accurate and clean.

  const options = { mimeType };
  if (!MediaRecorder.isTypeSupported(mimeType)) {
    console.error('MIME type not supported:', mimeType);
    return;
  }

  const mediaRecorder = new MediaRecorder(stream, options);
  const chunks = [];
  • mimeType: Helps ensure the correct encoding (WebM + Opus).
  • We create a new MediaRecorder every time to isolate each 3s chunk into its own blob.
  • chunks[]: A temporary list to collect audio data emitted by the MediaRecorder.

  mediaRecorder.ondataavailable = async (e) => {
    if (e.data && e.data.size > 0) {
      chunks.push(e.data);
    }
  };
  • ondataavailable: Fired when the chunk is ready.
  • Validates and pushes the chunk into the chunks[] array.

When Chunk Stops: Upload + Transcribe
  mediaRecorder.onstop = async () => {
    const blob = new Blob(chunks, { type: mimeType });
    const formData = new FormData();
    formData.append('audio', blob, `chunk-${Date.now()}.webm`);
  • Blob: A binary large object that packages all audio data into a single file.
  • FormData: Simulates a form submission to send binary files over HTTP.
  • formData.append(): Adds the blob to the form data under the key 'audio', with a filename.
    try {
      const uploadRes = await fetch('/recordings/upload', {
        method: 'POST',
        body: formData,
      });
      const { path } = await uploadRes.json();
  • The audio blob is sent to the backend /recordings/upload route.
  • We retrieve the server-side file path of the uploaded chunk.

      const transcriptRes = await fetch('/transcribe', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ filePath: path }),
      });

      const { transcription } = await transcriptRes.json();
      appendTranscript(transcription || '(No text returned)');
  • We pass the file path to the /transcribe endpoint.
  • Transcription text is returned and added to the live transcript in the UI.

Limit & Cleanup
    chunkCount++;
    remainingTime = MAX_TIME_S - chunkCount * (CHUNK_DURATION / 1000);
    updateCountdown();

    if (chunkCount >= MAX_CHUNKS) {
      appendTranscript('✅ Transcription limit (30 seconds) reached.');
      stopBtn.click(); // auto-stop
    }

    stream.getTracks().forEach((track) => track.stop());
  };

  mediaRecorder.start();
  setTimeout(() => mediaRecorder.stop(), CHUNK_DURATION);
}
  • Increments counters and refreshes the session timer.
  • Automatically stops recording once max chunks are reached.
  • Cleans up the mic stream with getTracks().forEach(track => track.stop()).

Utility: Append Transcript
function appendTranscript(text) {
  resultPanel.classList.remove('hidden');
  textArea.textContent += text + '\n';
}

Adds each transcribed chunk as it’s returned from the server.


UI Handlers: Start and Stop Buttons
window.addEventListener('load', () => {
  startBtn.addEventListener('click', () => {
    resultPanel.classList.add('hidden');
    textArea.textContent = '';
    chunkCount = 0;
    remainingTime = MAX_TIME_S;
    updateCountdown();
    sessionInfoEl.classList.remove('hidden');
    recordingBadgeEl.classList.remove('hidden');
    startBtn.disabled = true;
    stopBtn.disabled = false;
    startBtn.textContent = 'Recording...';
    startRecordingLoop();
  });
  • Starts a fresh session and resets all states and UI indicators.

  stopBtn.addEventListener('click', () => {
    stopRecordingLoop();
    sessionInfoEl.classList.add('hidden');
    recordingBadgeEl.classList.add('hidden');
    startBtn.disabled = false;
    stopBtn.disabled = true;
    startBtn.textContent = 'Start Recording';
  });
});
  • Gracefully ends a session and restores UI defaults.

Backend Logic (No Change Required)

The backend from the previous unit continues to work seamlessly:

  • /recordings/upload: stores each .webm chunk.
  • /transcribe: invokes transcribe() to convert uploaded audio into text using OpenAI’s Whisper API.

Summary

In this unit, you:

  • Simulated live microphone transcription using 3-second audio chunks.
  • Learned to manage a transcription session with time and chunk limits.
  • Processed and displayed each chunk’s transcript live in the browser.
  • Built a scalable transcription pipeline with clean UI feedback and Whisper API integration.

Next up: we’ll expand on this to support long-form recordings with advanced segmentation and context-aware processing.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal