Music & Audioplayer
- CustomAudioPlayer.tsx
- CustomAudioPlayer.module.css
- AudioWrapper.tsx
- AudioWrapper.module.css
- CryptoMusicSection.tsx
Module: CustomAudioPlayer
Overview
A drop-in React audio player component built on Howler.js, offering:
- Play/pause controls
- Seekable timeline (mouse + touch)
- Volume slider + mute button (with “remember last volume”)
- Accessible announcements for screen readers
Basic Usage
<CustomAudioPlayer
title="Altcoin Love"
audioSrc="/audio/Altcoin_Love.mp3"
/>
Layout & Positioning
By default, the component:
- Takes up 100% of its container width
- Has no inherent max-width constraints
- Centers its controls horizontally
- Uses flexbox for internal layout
To control width and positioning, wrap the component in a container:
// Centered with constrained width
<div
style={{
display: 'flex',
justifyContent: 'center',
marginTop: '2rem',
}}
>
<div style={{ maxWidth: '350px', width: '90%' }}>
<CustomAudioPlayer
title="Track Title"
audioSrc="/audio/track.mp3"
/>
</div>
</div>
Note: For scroll-revealed presentations with image + description, see the AudioWrapper module below.
Props API
Prop | Type | Required | Description |
---|---|---|---|
title | string | Yes | Track title (visible and used in aria-labels) |
audioSrc | string | Yes | URL or path to the audio file |
Internal State
State | Type | Initial | Purpose |
---|---|---|---|
isPlaying | boolean | false | true when audio is playing |
currentTime | number | 0 | Playback position in seconds |
duration | number | 0 | Total track length (set once Howl loads) |
volume | number | 1 | Current volume (0.00–1.00) |
isMuted | boolean | false | Whether audio is muted |
isDragging | boolean | false | True while user is scrubbing the timeline |
lastVolume | number | 1 | Remembers pre-mute volume for unmute restore |
announcement | string | '' | Live-region text for play/pause announcements |
Key Features
Dependencies
- React (useState, useEffect, useRef, useCallback)
- Howler.js
- CSS Modules for styling
Lifecycle & Effects
- Mount or
audioSrc
change: Instantiate Howl with html5: true - Sync volume & mute: Updates Howl instance on state changes
- Playhead updater: rAF loop when playing and not dragging
- Scrubbing: Mouse and touch event handling for timeline
- Announcements: Live region updates for accessibility
- Cleanup: Unload Howl and cancel animations on unmount
Accessibility Features
- Container: role=region, aria-label=“Audio player”
- Live region: role=status, aria-live=polite
- Play/Pause button: Dynamic aria-label and aria-pressed
- Timeline slider: role=slider with value announcements
- Volume slider: Percentage-based value text
CSS Module Classes
Core Classes (from CustomAudioPlayer.module.css
)
Class Category | Class Names |
---|---|
Main Container | audioPlayer , title , announcementVisible |
Controls | controls , playPauseButton , volumeButton |
Timeline | timelineContainer , timeline , timelineProgress , timelineCursor |
Time & Volume | timeCounter , volumeContainer , volumeSlider |
Styling Sample
A concise extract from AudioWrapper.module.css
showing animation, basic layout, and responsive behavior:
/* AudioWrapper.module.css */
/* 1) Scroll-in animation */
.wrapper {
opacity: 0;
transform: translateY(20px);
transition: opacity 1.5s ease, transform 1.5s ease;
margin-bottom: 5rem;
}
.visible {
opacity: 1;
transform: translateY(0);
}
/* 2) Layout basics */
.content {
display: flex;
flex-direction: column;
align-items: center;
gap: 2rem;
}
/* 3) Responsive at ≥730px */
@media (min-width: 730px) {
.content {
flex-direction: row;
gap: 8rem;
max-width: 1000px;
margin: 0 auto;
}
}
/* 4) Reduced-motion fallback */
@media (prefers-reduced-motion: reduce) {
.wrapper,
.visible {
transition: none;
}
}
Module: AudioWrapper
Crypto Music

An anthemic ode to altcoins by Rito Rhymes… California Love style
Overview
AudioWrapper
is a scroll-revealed media section that combines:
- Headline (h2)
- Image + Description
- CustomAudioPlayer component
This wrapper requires two components:
- The
AudioWrapper
itself (imported from the module) - A separate component that uses
AudioWrapper
(likeCryptoMusicSection
shown above)
On first scroll-into-view (40% threshold), the entire section fades up into place.
Component Structure
// Step 1: Create your own component that uses AudioWrapper
'use client';
import React from 'react';
import AudioWrapper from '@components/media/audio/AudioWrapper';
export default function YourAudioSection() {
return (
<AudioWrapper
headline="Your Headline"
imageSrc="/path/to/image.jpg"
imageAlt="Description of image"
description={<>Your rich text description</>}
title="Audio Track Title"
audioSrc="/path/to/audio.mp3"
/>
);
}
// Step 2: Import and use your component in MDX or pages
import YourAudioSection from '@components/YourAudioSection';
<YourAudioSection />
Props API
Prop | Type | Required | Description |
---|---|---|---|
headline | string | Yes | Section title, rendered as an <h2> above the image |
imageSrc | string | Yes | URL for the display image |
imageAlt | string | Yes | Accessible alt text for the image |
description | ReactNode | Yes | Caption or rich content below the image |
title | string | Yes | Label passed to the audio player (accessibility) |
audioSrc | string | Yes | URL for the audio file (mp3, wav, etc.) |
Styling & Layout
Core Classes (from AudioWrapper.module.css
)
Class | Effect |
---|---|
.wrapper | Initial: opacity: 0; transform: translateY(20px) + 1.5s transition |
.visible | Active: opacity: 1; transform: translateY(0) |
.headline | Centered, 3rem, uses var(--accent-color) |
.content | Flex container (column to row at 730px+) |
.left , .right | Max-width: 400px constraint |
.image | Responsive, auto height, 10px border-radius |
Responsive Behavior
- Mobile (less than 730px): Stacked layout (image above player)
- Desktop (730px and above): Side-by-side with 8rem gap
- Max container width: 1000px centered
Scroll Animation
- IntersectionObserver: 40% visibility threshold
- One-time trigger: Disconnects after first activation
- Reduced motion: Respects
prefers-reduced-motion
Example Implementation
Here’s the CryptoMusicSection
component shown in the demo:
'use client';
import React from 'react';
import AudioWrapper from '@components/media/audio/AudioWrapper';
export default function CryptoMusicSection() {
return (
<AudioWrapper
headline="Crypto Music"
imageSrc="/images/music/altcoin-love-coverart-square.jpg"
imageAlt="Altcoin Love Cover Art"
description={
<>
An anthemic ode to altcoins by Rito Rhymes… <em>California Love</em> style
</>
}
title="Altcoin Love"
audioSrc="/audio/Altcoin_Love.mp3"
/>
);
}
Usage Notes
- Each instance creates its own IntersectionObserver
- The
CustomAudioPlayer
is automatically included—no need to import separately - Supports JSX/ReactNode for rich
description
content - Animation runs once per page load (no retriggers on scroll)