DEV Community

Cover image for 🎙️ Building Your Own Live Audio Waveform in React Native
Toshiya Matsumoto
Toshiya Matsumoto

Posted on

🎙️ Building Your Own Live Audio Waveform in React Native

🚀 Motivation

There aren’t many (if any!) well-known libraries out there for live audio recording data visualization with a waveform in React Native. So… why not just build one yourself? 😉

🎬 Demo

Animated GIF showing a loading spinner with bouncing dots

Pretty neat, right? Let’s dive in.

⚙️ Assumptions

Here’s the setup I used:

`react-native`: 0.79.5
`expo`: 53.0.17
`expo-av`: 15.1.7
`react-native-svg`: 15.12.1
Enter fullscreen mode Exit fullscreen mode

👉 Run it with:

npx expo run:ios
Enter fullscreen mode Exit fullscreen mode

🎤 1. Recording Process

1-1: Enable Metering

The magic trick 🪄 is setting isMeteringEnabled: true in recording.prepareToRecordAsync() from expo-av. This unlocks audio-level metering data (like dB values).

import { Audio, InterruptionModeIOS } from 'expo-av';

const { granted, status } = await Audio.requestPermissionsAsync();
await Audio.setAudioModeAsync({
  allowsRecordingIOS: true,
  playsInSilentModeIOS: true,
  staysActiveInBackground: true,
  interruptionModeIOS: InterruptionModeIOS.DuckOthers,
});

// Adjust as you like 🎛️
const recordingOptions = {
  android: {
    extension: '.m4a',
    outputFormat: Audio.AndroidOutputFormat.MPEG_4,
    audioEncoder: Audio.AndroidAudioEncoder.AAC,
    sampleRate: 44100,
    numberOfChannels: 2,
    bitRate: 128000,
  },
  ios: {
    extension: '.m4a',
    outputFormat: Audio.IOSOutputFormat.MPEG4AAC,
    audioQuality: Audio.IOSAudioQuality.HIGH,
    sampleRate: 44100,
    numberOfChannels: 2,
    bitRate: 128000,
    linearPCMBitDepth: 16,
    linearPCMIsBigEndian: false,
    linearPCMIsFloat: false,
  },
  web: {
    mimeType: 'audio/webm;codecs=opus',
    bitsPerSecond: 128000,
  },
};

const recording = new Audio.Recording();
await recording.prepareToRecordAsync({
  ...recordingOptions,
  isMeteringEnabled: true, // 🎯 enables audio-level metering
});
await recording.startAsync();
Enter fullscreen mode Exit fullscreen mode

1-2: Poll the Recording State

Now let’s grab that juicy data 📊. Use the recording object and poll its status every 100ms.

// poll metering data every 100ms
const interval = setInterval(async () => {
  if (recording) {
    const status = await recording.getStatusAsync();
    if (status.isRecording && typeof status.metering === 'number') {
      setLevels((prev) => {
        const newLevels = [...prev, status.metering];
        // keep last 100 samples 🪣
        return newLevels.slice(-100);
      });
    }
  }
}, 100);
Enter fullscreen mode Exit fullscreen mode

Example of status:
{"canRecord": true, "durationMillis": 602, "isRecording": true, "mediaServicesDidReset": false, "metering": -48.3475341796875}

The metering value is what you care about 🎧.

👉 Decibel scale basics:

0 dB   → loudest possible (max recording level) 🔊  
-160 dB → silence 🤫
Enter fullscreen mode Exit fullscreen mode

I only keep the latest 100 values (slice(-100)) because… who wants a million bars on screen? 🙃

Time to visualize! 🎨

Okay let's visualise it.

📊 2. Waveform Visualization with SVG

You don’t need to be a math wizard 🧙 to draw waveforms. Just rectangles on a grid!

How it works:

1. Draw a rectangle for each audio sample 📦.

  • Height = based on audio volume.
  • Width = fixed.

2. Place it at the correct (x, y) coordinate.

Visual guide (yes, it’s really this simple 👇):

Waveform formula illustration on SVG

Converting dB → amplitude
Formula:

Math.pow(10, db / 20); // standard dB → amplitude
Enter fullscreen mode Exit fullscreen mode

For easier handling, add +40 before conversion:

// Original level: -60dB(almost silence)
Math.pow(10, (-60 + 40) / 20) = Math.pow(10, -1) = 0.1

// Original level: -20dB(small sound volume)
Math.pow(10, (-20 + 40) / 20) = Math.pow(10, 1) = 10

// Original level: 0dB(maximum sound volume)
Math.pow(10, (0 + 40) / 20) = Math.pow(10, 2) = 100
Enter fullscreen mode Exit fullscreen mode

Then scale it with normalizedLevel * height * 0.05.
(That 0.05 is just a tweak factor — adjust to taste! 🎚️)

And yes, we’ll use Rect from react-native-svg to draw the bars.

Codes

// Convert levels to SVG bars
  const width = 358;
  const height = 280;
  const barWidth = 3;
  const barSpacing = 1;
  const maxBarsCount = Math.floor(width / (barWidth + barSpacing));
  const halfHeight = height / 2;

  // Create vertical bars for each level
  const bars = levels.slice(-maxBarsCount).map((level, index) => {
    const normalizedLevel = Math.max(0, Math.pow(10, (level + 40) / 20));
    const barHeight = Math.max(1, normalizedLevel * height * 0.05);

    // Calculate position so bar extends equally above and below center
    const halfBarHeight = barHeight / 2;

    const startX = 0;
    const x = startX + index * (barWidth + barSpacing);
    const y = halfHeight - halfBarHeight;

    return (
      <Rect
        key={index}
        x={x}
        y={y}
        width={barWidth}
        height={barHeight}
        fill="#8E8E93"
        rx={1}
      />
    );
  });

  return (
    <View className="bg-white p-2 flex-1">
      <Svg height={height} width={width}>
        {bars}
      </Svg>
    </View>
  );
Enter fullscreen mode Exit fullscreen mode
  • (levels.slice(-maxBarsCount), maxBarsCount removes the stale bars that overflows in the screen)

✅ Summary

With just expo-av + a little SVG magic 🪄, you can visualize live audio waveforms in React Native — no external libraries needed, no hassle.

Next time you record 🎤, make it look cool too.

Top comments (0)