Core, framework-agnostic vanilla-JS audio player with canvas waveform visualization.
It is the foundation the rest of the @arraypress waveform family builds on.
npm test— vitest + jsdom (run this before committing).npm run build— builds all dist targets (css, iife, esm, cjs, min).prepublishOnlyrunstest && build.npm run size— gzipped JS/CSS sizes. As of 1.24.0: ~12.5KB JS / ~1.8KB CSS. Ceiling is ~13KB JS; flag anything that moves it materially. (The old "~10KB budget" sat below actual for several releases, so it could never flag anything — keep this figure current when it moves, or it rots the same way. The marketing site quotes it too:waveform-site/src/data/packages.ts.)npm run dev— watch build (rebuilds dist while you test against a local HTML page).
core.js— theWaveformPlayerclass (the bulk: DOM build, audio, seek, lifecycle,loadTrack).audio.js—generateWaveform/extractPeaks/normalizePeaks/ placeholder peaks.drawing.js— canvas draw functions, one perwaveformStyle(drawBars,drawMirror,drawSeekbar, …);DRAWING_STYLESmaps style → fn.themes.js—DEFAULT_OPTIONS(the option surface) + color presets + per-style bar sizing.utils.js—parseDataAttributes(thedata-*contract),clamp,escapeHtml,isSafeHref,extractTitleFromUrl, etc.bpm.js— BPM detection.index.js— public entry +WaveformPlayer.utilsbridge.
index.d.tsis hand-written — when you add/rename aDEFAULT_OPTIONSkey, updateindex.d.tsin the same change, or the typed wrappers drift.- Two configuration paths, kept in sync: JS constructor options and
data-*attributes (parseDataAttributes). A new option usually needs both. - Option aliases:
style→waveformStyle,src→url(canonical wins). Normalized in the constructor +parseDataAttributes. - Logging: every
console.*and thrown message is prefixed[WaveformPlayer]. - Events: dispatch through the single
_emit()path (don't hand-rolldispatchEvent). Event details are typed inindex.d.ts(WaveformPlayerEventMap). audioMode:'self'owns an<audio>;'external'is visualization-only, driven bysetPlayingState()/setProgress()and emitswaveformplayer:request-*events. Tests use'external'to avoid Web Audio in jsdom.- Comprehensive JSDoc on public methods; match the existing density.
loadTrackmust reset per-track options. It resetsmarkers+waveformData, and (since 1.8.1)this.options.waveform.mergeOptionskeeps prior values otherwise, so a track loaded without peaks would redraw the previous track's waveform. If you add per-track options (artwork/markers/peaks/bpm), reset them inloadTracktoo.- jsdom has no
AudioContext→generateWaveformfalls back to a placeholder; that warning in test output is expected. - Canvas redraw is driven by a
ResizeObserveron the canvas's parent +resizeCanvas(); a DOM move doesn't reliably trip it — callresizeCanvas()explicitly if you relocate the player. - Seeking needs a Range-capable audio host. The
<audio>element seeks via HTTP byte-range requests; an origin that answers200with noAccept-Ranges(notably Cloudflare Pages and Workers Static Assets — both silently ignore ranges) lets the player seek only within already-buffered bytes → short tracks look fine, long tracks snap back to 0. It's the host, not the player. Serve audio from a range-capable origin (R2 / S3 / nginx / any real file server).
waveform-bar— persistent bottom-bar singleton (window.WaveformBar.init(config)); embeds one self-mode player, drives inlineexternal-mode players viadata-wb-*triggers. Ships no.d.ts— its wrappers hand-declare the config type, so adding a bar config option means updating those wrapper types manually.waveform-player-astro/-react— typed wrappers. Astro emitsdata-*; React passes constructor options. Both derive their prop types from this package'sWaveformPlayerOptionsviaOmit<>, exceptstyle(which stays the framework's CSS prop — usewaveformStylefor the visual style).waveform-bar-astro/-react— same pattern for the bar (but passconfigthrough verbatim toinit()).waveform-editor— in-browser clip editor: trim, fades (5 curve shapes), gain/normalize, auto-trim silence, export WAV / peaks / MP3 (lazy lamejs). Reuses this package'sextractPeaks+ monochrome look; edits are a non-destructive op-list baked viaOfflineAudioContext. New (v0.1).
Order matters — core first, then dependents (their peer dep is @arraypress/waveform-player@^1.x):
npm publishhere (no bump if package.json version already > npm).- Dependents:
npm version <patch|minor>→npm publish→git push --follow-tags. Keep dependents' peer ranges (^1.x) able to satisfy this package's published version.