{"schemaVersion":"1.0","type":"Article","slug":"audio-that-sees-itself-o81sk","url":"https://api.zyvop.com/audio-that-sees-itself-o81sk","title":"Audio That Sees Itself","subtitle":null,"tldr":"A Mantine-native audio player for React with waveform visualisation and a live spectrum analyser,...","keywords":["reactjsdevelopment","TypeScript","nextjs","react"],"entities":["Giovambattista Fazioli","Senior Full-stack Engineer, Lead Developer","reactjsdevelopment","TypeScript","nextjs","react","ZyVOP"],"keyTakeaways":["A Mantine-native audio player for React with waveform visualisation and a live spectrum analyser, built on the Web Audio API.","Same three-layer architecture as Mantine Video — drop-in &lt;Audio /&gt;, composable compound API, headless useAudio hook — but designed for a medium that has no frame to look at.","Introduction Mantine Video shipped last week."],"headings":["Introduction","What is mantine-audio?","✨ Key Features","Waveform visualisation","Live spectrum analyser","Headless useAudio hook","scrubSound — hear the seek","Multiple sources with runtime fallback","asBackground preset","Sizes &amp; layouts","Keyboard shortcuts","🚀 Getting Started","🎨 Styles API","Live Demo &amp; Documentation","Links"],"outboundLinks":["https://gfazioli.github.io/mantine-audio/?t=styles-api","https://gfazioli.github.io/mantine-audio","https://www.npmjs.com/package/@gfazioli/mantine-audio","https://github.com/gfazioli/mantine-audio","https://mantine-extensions.vercel.app/"],"contentText":"A Mantine-native audio player for React with waveform visualisation and a live spectrum analyser, built on the Web Audio API. Same three-layer architecture as Mantine Video — drop-in &lt;Audio /&gt;, composable compound API, headless useAudio hook — but designed for a medium that has no frame to look at. Introduction Mantine Video shipped last week. The next obvious step was to give audio the same treatment — a polished default player, a composable control bar, a headless hook for fully custom UIs. The catch is that audio has no frame to look at: a 30-second video communicates its content the instant you hit play, while a 30-second audio file looks like a thin grey line. So the new component does the opposite of what most React audio players do — it makes the audio look at itself: decoded waveform on &lt;canvas&gt;, live FFT spectrum on the analyser node, every part theme-aware. What is mantine-audio? @gfazioli/mantine-audio wraps the native HTML &lt;audio&gt; element with three layers, layered from \"drop-in\" to \"fully custom\": A polished default &lt;Audio /&gt; factory with four built-in variants (overlay, minimal, floating, bordered) and five sizes (xs–xl), all CSS-driven via [data-variant] / [data-size] attributes. A composable compound API — ten sub-components you can reorder, replace or restyle: Audio.Controls, Audio.PlayButton, Audio.SkipButton, Audio.Timeline, Audio.TimeDisplay, Audio.MuteButton, Audio.VolumeSlider, Audio.SpeedControl, Audio.Waveform, Audio.Spectrum. A fully headless useAudio hook with 16 state values, 12 actions, the decoded peaks, the AnalyserNode, and currentSrc — to drive a 100% custom UI on top of a plain &lt;audio&gt; element. It targets Mantine 9 and React 19, is TypeScript-first, and has zero runtime dependencies beyond React, Mantine, and @tabler/icons-react (optional peer). ✨ Key Features Waveform visualisation Audio.Waveform decodes the audio file once via fetch + decodeAudioData, downsamples to waveformSamples peaks (default 512), and renders them on a &lt;canvas&gt;. Click or drag to seek. The played / unplayed boundary tracks the playhead at 60fps via requestAnimationFrame, not at the slower timeupdate cadence (~4Hz) — so the colour transition moves smoothly with the audio. &lt;Audio src=\"/track.mp3\" variant=\"floating\"&gt;&lt;Audio.Waveform height={80} mirrorGap={0} /&gt;&lt;Audio.Controls /&gt;&lt;/Audio&gt; Enter fullscreen mode Exit fullscreen mode mirror (default true) draws bars symmetrically around the centerline for the classic Audacity look. mirrorGap controls the divider thickness — 0 for a continuous filled shape, &gt;0 for a visible centerline. Live spectrum analyser Audio.Spectrum is a frequency-domain visualiser driven by Web Audio's AnalyserNode. The AudioContext is lazy-initialised on the first play (autoplay-policy compliant), and the bars react to the music in real time. Two color modes — solid (uniform fill) and gradient (vertical fade to transparent). Bin distribution is proportional: no high-frequency bins are dropped when barCount doesn't divide fftSize evenly. &lt;Audio src=\"/track.mp3\" color=\"grape\"&gt;&lt;Audio.Spectrum barCount={48} colorMode=\"gradient\" /&gt;&lt;Audio.Controls /&gt;&lt;/Audio&gt; Enter fullscreen mode Exit fullscreen mode Headless useAudio hook When the default layout doesn't fit, drop the compound layer entirely and drive your own UI: import { useAudio } from '@gfazioli/mantine-audio'; function MyPlayer() {const { playing, currentTime, duration, peaks, analyser, toggle, audioRef } = useAudio({ src: '/track.mp3',}); return ( &lt;div&gt; &lt;audio ref={audioRef} crossOrigin=\"anonymous\" /&gt; &lt;button onClick={toggle}&gt;{playing ? 'Pause' : 'Play'}&lt;/button&gt; &lt;span&gt;{currentTime.toFixed(1)} / {duration.toFixed(1)}&lt;/span&gt; &lt;/div&gt;);} Enter fullscreen mode Exit fullscreen mode The hook returns 16 state values (playing, currentTime, duration, volume, muted, playbackRate, peaks, analyser, audioContext, currentSrc, …) and 12 action functions (play, pause, toggle, seek, seekBy, setVolume, mute, unmute, toggleMute, setPlaybackRate, …). scrubSound — hear the seek Most audio players pause during a seek-drag, then jump to the new position. scrubSound keeps the audio playing during the drag — you hear short snippets of audio as the cursor moves, the way Audacity, Adobe Audition, and iTunes do it. And it works even from a paused state: the player starts on mousedown, plays through the drag, and pauses again on release. &lt;Audio src=\"/track.mp3\" scrubSound&gt;&lt;Audio.Waveform height={80} /&gt;&lt;Audio.Controls /&gt;&lt;/Audio&gt; Enter fullscreen mode Exit fullscreen mode The flag propagates via React context, so both Audio.Timeline and Audio.Waveform honour it automatically. Individual sub-components can still override their own scrubSound if you want fine-grained control. Multiple sources with runtime fallback For cross-browser compatibility, adaptive bitrate, or media-query switching, pass sources instead of src. The browser picks the first entry whose type it can play via canPlayType(): &lt;Audiosources={[ { src: '/track.aac', type: 'audio/aac' }, // iOS preferred { src: '/track.ogg', type: 'audio/ogg' }, // Firefox preferred { src: '/track.mp3', type: 'audio/mpeg' }, // universal fallback]}fallbackSrc=\"/last-resort.mp3\"/&gt; Enter fullscreen mode Exit fullscreen mode src and sources are mutually exclusive — a dev-mode warning is logged if both are set. fallbackSrc mirrors the equivalent prop on Mantine Image and kicks in when every entry in sources errors at runtime. asBackground preset Turn the same player into an ambient hero / section background in one prop: &lt;Box pos=\"relative\" h=\"100vh\"&gt;&lt;Audio src=\"/ambient.mp3\" asBackground loop muted /&gt;&lt;YourHeroContent /&gt;&lt;/Box&gt; Enter fullscreen mode Exit fullscreen mode controls and shortcuts are disabled by default in this mode, and a small floating mute toggle is rendered in the bottom-right corner (disable with backgroundMuteButton={false}). Sizes &amp; layouts size scales 11 CSS variables at once — padding, gap, icon size, play button, action button, timeline thumb, time display width, volume slider width, font size. Five presets cover most needs: &lt;Audio size=\"xs\" src=\"/voice-note.mp3\" variant=\"minimal\" /&gt; // chat bubble&lt;Audio size=\"sm\" src=\"/podcast.mp3\" variant=\"bordered\" /&gt; // card&lt;Audio size=\"lg\" src=\"/album.mp3\" variant=\"floating\" /&gt; // hero card&lt;Audio size=\"xl\" src=\"/master.wav\" variant=\"overlay\" /&gt; // studio scrub Enter fullscreen mode Exit fullscreen mode And because the default &lt;Audio.Controls /&gt; is just a horizontal row, you can drop it (controls={false}) and arrange the sub-components however you like — vertical stack, custom skip ranges, separate volume slider section — using any layout primitive from @mantine/core. Keyboard shortcuts When the player has focus: Space / K — play/pause J / L — seek backward / forward 10s ← / → — seek backward / forward 5s ↑ / ↓ — volume ±5% M — toggle mute &gt; / &lt; — speed ±0.25× 🚀 Getting Started npm install @gfazioli/mantine-audio Enter fullscreen mode Exit fullscreen mode import { Audio } from '@gfazioli/mantine-audio';import '@gfazioli/mantine-audio/styles.css'; function App() {return &lt;Audio src=\"/track.mp3\" /&gt;;} Enter fullscreen mode Exit fullscreen mode That's the entire setup — you now have a polished, accessible audio player with timeline, time display, mute, volume slider, and speed control. 🎨 Styles API Audio exposes the full Mantine Styles API: every sub-component is a styles-api selector you can target via classNames or styles. Eighteen selectors in total — root, audio, controls, controlBar, playButton, timeline, timelineBuffered, timeDisplay, muteButton, skipButton, volumeSlider, speedControl, waveform, waveformCanvas, waveformHover, spectrum, spectrumCanvas, backgroundMuteButton. Nine CSS variables let you re-skin the whole player from the outside: .mantine-Audio-root {--audio-color: hotpink;--audio-waveform-color: rgba(255, 255, 255, 0.15);--audio-waveform-played-color: hotpink;--audio-spectrum-bar-color: hotpink;--audio-spectrum-bar-color-fade: transparent;} Enter fullscreen mode Exit fullscreen mode Five data-* modifiers (data-variant, data-playing, data-paused, data-ended, data-as-background) are available on the root for state-based styling. Live Demo &amp; Documentation The full docs site has interactive demos for every feature — usage, configurator, sizes, compound API, custom layout, waveform with a live prop configurator, spectrum with another configurator, volume &amp; speed, asBackground, multiple sources, headless usage, keyboard shortcuts, styles API, and a \"use cases\" showcase with real-world layouts (inline voice-note bubble, podcast card with cover, mini sticky bar, studio scrub). → https://gfazioli.github.io/mantine-audio Links 📦 npm: https://www.npmjs.com/package/@gfazioli/mantine-audio 📖 Documentation: https://gfazioli.github.io/mantine-audio 🔗 GitHub: https://github.com/gfazioli/mantine-audio 🌐 Mantine Extensions Hub: https://mantine-extensions.vercel.app/","contentHash":"sha256:4af85f7363aeb53ccee193663f4b7cafa40620a1a04e81eb2d0b9f169e4ac670","authorName":"Giovambattista Fazioli","authorUrl":"https://api.zyvop.com/author/giovambattista","authorSameAs":["https://gfazioli.github.io","https://github.com/gfazioli","https://x.com/gfazioli","https://www.linkedin.com/in/giovambattistafazioli/"],"category":null,"tags":["reactjsdevelopment","TypeScript","nextjs","react"],"audience":"Readers researching reactjsdevelopment","tone":"Professional, senior full-stack engineer, lead developer perspective","readingTimeMinutes":6,"wordCount":1305,"faqs":null,"primaryTopic":"reactjsdevelopment","publishedAt":"2026-09-04T09:27:35.427Z","updatedAt":"2026-09-04T09:27:35.427Z","canonicalUrl":"https://dev.to/undolog/audio-that-sees-itself-54d1"}