MediaElement Provider
The MediaElementPlaylistProvider is a simplified playlist provider for single-track playback using HTMLAudioElement. It offers pitch-preserving playback rate control (0.5x - 2.0x) without loading the full audio into memory.
Choose MediaElementPlaylistProvider for:
Language learning apps - Speed control for pronunciation practice
Podcast players - Variable playback rate without pitch shift
Single-track audio viewers - Simple visualization without editing
Large audio files - Stream audio instead of loading into AudioBuffer
Limited memory environments - Mobile devices with memory constraints
Choose WaveformPlaylistProvider for:
Multi-track editing - Mix multiple audio sources
Real-time effects - Apply Tone.js effects during playback
Recording - Capture audio from microphone
Precise editing - Sample-accurate clip trimming and splitting
Basic Usage
import {
MediaElementPlaylistProvider ,
useMediaElementControls ,
useMediaElementState ,
} from '@waveform-playlist/browser' ;
import { Waveform } from '@waveform-playlist/ui-components' ;
function Player () {
const track = {
source: '/audio/speech.mp3' ,
waveformData: precomputedPeaks , // WaveformData object
name: 'Lesson 1' ,
};
return (
< MediaElementPlaylistProvider
track = { track }
playbackRate = { 1.0 }
samplesPerPixel = { 1024 }
>
< Waveform />
< Controls />
</ MediaElementPlaylistProvider >
);
}
function Controls () {
const { play , pause , setPlaybackRate } = useMediaElementControls ();
const { playbackRate } = useMediaElementState ();
return (
< div >
< button onClick = { () => play () } > Play </ button >
< button onClick = { () => pause () } > Pause </ button >
< label >
Speed: { playbackRate } x
< input
type = "range"
min = { 0.5 }
max = { 2.0 }
step = { 0.1 }
value = { playbackRate }
onChange = { ( e ) => setPlaybackRate ( parseFloat ( e . target . value )) }
/>
</ label >
</ div >
);
}
Track Configuration
interface MediaElementTrackConfig {
source : string ; // Audio source URL or Blob URL
waveformData : WaveformDataObject ; // Pre-computed waveform peaks
name ?: string ; // Track name for display
}
The MediaElement provider requires pre-computed peaks for visualization:
import WaveformData from 'waveform-data' ;
async function generatePeaks ( audioUrl : string ) {
// Fetch audio file
const response = await fetch ( audioUrl );
const arrayBuffer = await response . arrayBuffer ();
// Decode to AudioBuffer
const audioContext = new AudioContext ();
const audioBuffer = await audioContext . decodeAudioData ( arrayBuffer );
// Generate waveform data
const waveformData = WaveformData . create ( audioBuffer );
// Use in provider
return {
source: audioUrl ,
waveformData: waveformData ,
name: 'Track Name' ,
};
}
Or use a server-side tool like audiowaveform :
audiowaveform -i speech.mp3 -o speech.json --pixels-per-second 50
const response = await fetch ( '/peaks/speech.json' );
const peaksData = await response . json ();
const waveformData = WaveformData . create ( peaksData );
Provider Props
interface MediaElementPlaylistProviderProps {
track : MediaElementTrackConfig ;
samplesPerPixel ?: number ; // Default: 1024
waveHeight ?: number ; // Default: 100
timescale ?: boolean ; // Default: false
playbackRate ?: number ; // Default: 1 (range: 0.5 - 2.0)
automaticScroll ?: boolean ; // Default: false
theme ?: Partial < WaveformPlaylistTheme >;
controls ?: { show : boolean ; width : number };
annotationList ?: {
annotations ?: AnnotationData [];
isContinuousPlay ?: boolean ;
};
barWidth ?: number ; // Default: 1
barGap ?: number ; // Default: 0
progressBarWidth ?: number ; // Default: barWidth + barGap
onAnnotationsChange ?: ( annotations : AnnotationData []) => void ;
onReady ?: () => void ;
children : ReactNode ;
}
Playback Rate Control
The key feature of MediaElement provider is pitch-preserving playback rate:
function SpeedControl () {
const { setPlaybackRate } = useMediaElementControls ();
const { playbackRate } = useMediaElementState ();
return (
< div >
< button onClick = { () => setPlaybackRate ( 0.5 ) } > 0.5x </ button >
< button onClick = { () => setPlaybackRate ( 0.75 ) } > 0.75x </ button >
< button onClick = { () => setPlaybackRate ( 1.0 ) } > 1.0x </ button >
< button onClick = { () => setPlaybackRate ( 1.25 ) } > 1.25x </ button >
< button onClick = { () => setPlaybackRate ( 1.5 ) } > 1.5x </ button >
< button onClick = { () => setPlaybackRate ( 2.0 ) } > 2.0x </ button >
< input
type = "range"
min = { 0.5 }
max = { 2.0 }
step = { 0.05 }
value = { playbackRate }
onChange = { ( e ) => setPlaybackRate ( parseFloat ( e . target . value )) }
/>
</ div >
);
}
Playback rate is clamped to the range 0.5 - 2.0:
// Rates outside the range are automatically clamped
setPlaybackRate ( 3.0 ); // Sets to 2.0
setPlaybackRate ( 0.25 ); // Sets to 0.5
Hooks
Four context hooks provide access to different aspects of the player:
High-frequency updates for playback position (avoid using in complex components):
const { isPlaying , currentTime , currentTimeRef } = useMediaElementAnimation ();
Playlist state (annotations, playback rate, etc.):
const {
continuousPlay ,
annotations ,
activeAnnotationId ,
playbackRate ,
isAutomaticScroll ,
} = useMediaElementState ();
Playback controls and setters:
const {
play ,
pause ,
stop ,
seekTo ,
setPlaybackRate ,
setContinuousPlay ,
setAnnotations ,
setActiveAnnotationId ,
setAutomaticScroll ,
setScrollContainer ,
scrollContainerRef ,
} = useMediaElementControls ();
Playlist data (duration, peaks, dimensions):
const {
duration ,
peaksDataArray ,
sampleRate ,
waveHeight ,
timeScaleHeight ,
samplesPerPixel ,
playoutRef ,
controls ,
barWidth ,
barGap ,
progressBarWidth ,
} = useMediaElementData ();
MediaElement provider supports the same annotation system as the full provider:
import { parseAeneas } from '@waveform-playlist/annotations' ;
function AnnotatedPlayer () {
const [ annotations , setAnnotations ] = useState < AnnotationData []>([]);
useEffect (() => {
fetch ( '/subtitles/lesson1.json' )
. then (( res ) => res . json ())
. then (( data ) => {
const parsed = parseAeneas ( data );
setAnnotations ( parsed );
});
}, []);
return (
< MediaElementPlaylistProvider
track = { track }
annotationList = { {
annotations ,
isContinuousPlay: true ,
} }
onAnnotationsChange = { setAnnotations }
>
< Waveform showAnnotations />
< AnnotationControls />
</ MediaElementPlaylistProvider >
);
}
Annotations must be pre-parsed with numeric start and end values. Use parseAeneas() from @waveform-playlist/annotations before passing to the provider.
Keep the playhead centered during playback:
< MediaElementPlaylistProvider
track = { track }
automaticScroll = { true }
>
< Waveform />
</ MediaElementPlaylistProvider >
Or control it dynamically:
function Player () {
const { setAutomaticScroll } = useMediaElementControls ();
const { isAutomaticScroll } = useMediaElementState ();
return (
<>
< label >
< input
type = "checkbox"
checked = { isAutomaticScroll }
onChange = { ( e ) => setAutomaticScroll ( e . target . checked ) }
/>
Auto-scroll
</ label >
</>
);
}
Implementation Details
How MediaElement provider works
Use Cases
Language Learning App
function LanguageLessonPlayer ({ lessonId } : { lessonId : string }) {
const [ track , setTrack ] = useState ( null );
const [ playbackRate , setPlaybackRate ] = useState ( 1.0 );
useEffect (() => {
// Load lesson audio + peaks
loadLesson ( lessonId ). then ( setTrack );
}, [ lessonId ]);
if ( ! track ) return < div > Loading... </ div > ;
return (
< MediaElementPlaylistProvider
track = { track }
playbackRate = { playbackRate }
annotationList = { {
annotations: track . subtitles ,
isContinuousPlay: false , // Pause at each phrase
} }
automaticScroll
>
< Waveform showAnnotations />
< div >
< button onClick = { () => setPlaybackRate ( 0.5 ) } > Slow </ button >
< button onClick = { () => setPlaybackRate ( 1.0 ) } > Normal </ button >
< button onClick = { () => setPlaybackRate ( 1.5 ) } > Fast </ button >
</ div >
</ MediaElementPlaylistProvider >
);
}
Podcast Player
function PodcastPlayer ({ episodeUrl , peaksUrl } : Props ) {
const [ track , setTrack ] = useState ( null );
const [ playbackRate , setPlaybackRate ] = useState ( 1.0 );
useEffect (() => {
fetch ( peaksUrl )
. then (( res ) => res . json ())
. then (( data ) => {
setTrack ({
source: episodeUrl ,
waveformData: WaveformData . create ( data ),
name: 'Episode Title' ,
});
});
}, [ episodeUrl , peaksUrl ]);
if ( ! track ) return null ;
return (
< MediaElementPlaylistProvider
track = { track }
playbackRate = { playbackRate }
samplesPerPixel = { 2048 } // Lower resolution for long episodes
>
< Waveform />
< SpeedControl onSpeedChange = { setPlaybackRate } />
</ MediaElementPlaylistProvider >
);
}
Limitations
MediaElement provider does NOT support:
Multi-track mixing
Audio effects (reverb, delay, EQ, etc.)
Recording from microphone
Clip-based editing (trim, split, move)
WAV export with effects
For these features, use WaveformPlaylistProvider instead.
Migration from Full Provider
If you have a single-track use case with the full provider:
// Before: Full provider for single track
< WaveformPlaylistProvider tracks = { [ track ] } >
< Waveform />
</ WaveformPlaylistProvider >
// After: MediaElement provider
< MediaElementPlaylistProvider track = { track } >
< Waveform />
</ MediaElementPlaylistProvider >
Benefits:
Simpler API (no track array)
Built-in playback rate control
Lower memory usage (no AudioBuffer)
Faster initial load (streaming instead of decode)
Browser Compatibility
playbackRate with pitch preservation is supported in all modern browsers:
Chrome/Edge: Yes (preservesPitch = true by default)
Firefox: Yes (mozPreservesPitch)
Safari: Yes (preservesPitch in iOS 15+)
No polyfills needed for modern browsers.